對於一名前端開發者來講,深刻理解JavaScript程序內部執行機制固然是頗有必要的,其中一個關鍵概念就是JavaScript的執行上下文和執行棧,理解這部份內容也有助於理解做用域、閉包等javascript
所謂的JavaScript執行上下文就是當前JS代碼代碼被解析和執行時所在環境的抽象概念,js代碼都是在執行上下文中運行的前端
它的特色有如下幾個:java
a.它是最基礎、默認的全局執行上下文node
b.它會建立一個全局對象,而且將this指向全局對象,在瀏覽器中全局對象是window,在nodejs中全局對象是global面試
c.一個程序中只有一個segmentfault
它的特色有如下幾個:瀏覽器
a.有本身的執行上下文安全
b.能夠在一個程序中存在任意數量閉包
c.是函數被執行時建立app
eval函數能夠計算某個字符串,並執行其中的js代碼,這樣就會存在一個安全性問題,在代碼字符串未知或者是來自於用戶輸入源的話,絕對不要使用eval函數
以上就是執行上下文的幾種類型和相應的特色,咱們能夠看下下面這段代碼:
裏面的三個函數都被執行了,因此是有三個函數執行上下文
// 全局執行上下文
var sayHello = 'Hello'
function someone() { // 函數執行上下文
var first = 'Tom', last = 'Ada'
function getFirstName() { // 函數執行上下文
return first
}
function getLastName() { // 函數執行上下文
return last
}
console.log(sayHello + getFirstName() + getLastName())
}
someone()
複製代碼
執行上下文的生命週期分了三個階段:
對於函數執行上下文,函數被調用的時候,可是還未執行裏面的代碼以前,會作三件事情:
建立變量對象:會初始化函數的參數,提高函數聲明和變量聲明
建立做用域鏈:做用域鏈用於標識符解析,看下面代碼:
f3函數被調用的時候,裏面的變量num要求被解析的時候,會在當前f3的做用域裏查找,若是沒找到,就會向上一層做用域中查找,直到在全局做用找到該變量爲30
var num = 30;
function f1() {
function f2() {
function f3() {
console.log(num);
}
f3();
}
f2();
}
f1();
複製代碼
在一個程序執行以前,要先解析代碼,會先建立全局執行上下文環境,把須要執行的變量和函數聲明都取出來並暫時賦值爲undefined,函數也要先聲明好待調用,這也是咱們下文中會講到的變量提高,以上幾步作完後,開始正式執行程序
執行的變量賦值、函數調用等代碼執行
執行上下文出棧,等待虛機垃圾回收執行上下文
變量提高分爲兩種:
關於變量聲明提高,先看如下代碼片斷:
console.log(a) // undefined
var a = 5
function test() {
console.log(a) // undefined
var a = 10
}
test()
複製代碼
以上代碼中,第1個 a 是在全局執行上下文環境中,因爲在全局執行上下文建立的時候,把須要執行的變量和函數聲明都取出來並暫時賦值爲undefined,因此打印出來的就是undefined
第2個 a 是在test這個函數執行上下文環境中,同上,因此打印出來的就是undefined
var a
console.log(a) // undefined
a = 5
function test() {
var a
console.log(a) // undefined
a = 10
}
test()
複製代碼
關於函數聲明提高,看如下代碼:
console.log(f1) // function f1() {}
function f1() {}
console.log(f2) // undefined
var f2 = function() {}
複製代碼
打印結果在註釋中,因爲變量聲明和函數聲明提高原則能夠把代碼改爲以下:
function f1() {}
console.log(f1) // function f1() {}
var f2;
console.log(f2) // undefined
f2 = function() {}
複製代碼
f1和f2不同的地方是:f1是普通函數聲明的方式,f2是函數表達式,在f2未被賦值的時候,它就是一個變量,這個時候變量提高,因此打印的f2爲undefined
若是一個變量既是函數聲明的方式,又是變量聲明的方式,代碼以下:
咱們發現函數聲明的優先級是高於變量提高的優先級的
function test(arg){
console.log(arg); // function arg(){console.log('hello world') }
var arg = 'hello';
function arg(){
console.log('hello world')
}
console.log(arg); // hello
}
test('hi');
複製代碼
總結:變量提高的幾個特色:
this指向問題一般會在一些面試題中出現,狀況比較多,先了解下它的一些特色:
第一種:a()直接調用的方式,this === window
function a() {
console.log(this.b)
}
var b = 0
a()
複製代碼
打印出的值爲 0
第二種:誰調用了函數,誰就是this
function a() {
console.log(this)
}
var obj = {a: a}
obj.a()
複製代碼
打印出的值爲obj這個對象
第三種:構造函數模式下,this指向當前執行類的實例
function getPersonInfo(name, age) {
this.name = name
this.age = age
console.log(this)
}
var p1 = new getPersonInfo('linda', 13)
複製代碼
打印出來的值是:
getPersonInfo{ name: 'linda', age: 13 }
第四種:call/apply/bind調用函數的方式,this指向第一個參數
function add (b, c) {
console.log(this)
return this.a + b + c
}
var obj = {a: 3}
add.call(obj, 5, 7)
add.call(obj, [10, 20])
複製代碼
打印出來的值就是obj的值
嚴格模式下,函數直接調用的方式中this指向undefined
'use strict'
function a() {
console.log(this)
}
a()
複製代碼
這個時候函數裏的this打印出 undefined
箭頭函數沒有自身的this關鍵字,看外層是否有函數,若是有函數,外層函數的this就是內部箭頭函數的this,若是沒有,this就是指向window
能夠看如下幾種狀況:
var person = {
myName: 'linda',
age:1,
clickPerson: function() {
var show = function() {
console.log(`Person name is ${this.myName}, age is ${this.age}`)
}
show()
}
}
person.clickPerson()
複製代碼
打印結果:Person name is undefined, age is undefined
裏面的函數show被調用的時候,是普通函數調用的狀況,因此this指向window,而全局函數中沒有myName和age,因此打印出來是undefined
能夠換成箭頭函數:
var person = {
myName: 'linda',
age:1,
clickPerson: function() {
var show = () => {
console.log(`Person name is ${this.myName}, age is ${this.age}`)
}
show()
}
}
person.clickPerson()
複製代碼
打印出的結果是:Person name is linda, age is 1
對於箭頭函數自身沒有this關鍵字,因此看外層函數,而外層函數中是咱們前面說到的第二種狀況,this指向person這個對象,因此是有myName和age的值
若是把clickPerson也換成箭頭函數:
var person = {
myName: 'linda',
age:1,
clickPerson: () => {
var show = () => {
console.log(`Person name is ${this.myName}, age is ${this.age}`)
}
show()
}
}
person.clickPerson()
複製代碼
咱們發現打印的結果是:Person name is undefined, age is undefined
因爲都是箭頭函數,最後找到了全局的window,因此this指向window,而全局函數中沒有myName和age,因此打印出來是undefined
再看另一個例子:
function getPersonInfo(name,age){
this.myName = name;
this.age = age;
this.show = function() {
console.log(`Person name is ${this.myName}, age is ${this.age}`)
}
}
getPersonInfo.prototype.friend = function(friends) {
var array = friends.map(function(friend) {
return `my friend ${this.myName} age is ${this.age}`
});
console.log(array);
}
var person1 = new getPersonInfo("linda",18);
person1.show()
person1.friend(['Ada', 'Tom'])
複製代碼
show()函數調用結果打印:Person name is linda, age is 18
friend()函數調用打印結果:["my friend undefined age is undefined", "my friend undefined age is undefined"]
對於friend函數內部,this指向的是當前的getPersonInfo這個構造函數初始化的實例,可是在內部使用map是一個閉包函數,且內部是普通函數的調用方式,因此內部this是指向了window,能夠把裏面普通函數調用的方式改爲箭頭函數的方式便可
function getPersonInfo(name,age){
this.myName = name;
this.age = age;
this.show = function() {
console.log(`Person name is ${this.myName}, age is ${this.age}`)
}
}
getPersonInfo.prototype.friend = function(friends) {
var array = friends.map((friend) => {
return `my friend ${this.myName} age is ${this.age}`
});
console.log(array);
}
var person1 = new getPersonInfo("linda",18);
person1.show()
person1.friend(['Ada', 'Tom'])
複製代碼
此次打印結果就是["my friend linda age is 18", "my friend linda age is 18"]
就是咱們預想的了
總結:(非嚴格模式下)能夠按照下圖規律查找this的指向
js建立了執行上下文棧來管理執行上下文,咱們經過以下一段代碼和進棧出棧順序圖來理解執行上下文棧
var name = 'Tom';
function father() {
var sonName = 'Anda';
function son() {
console.log('son name is ', sonName)
}
console.log('father name is ', name)
son();
}
father();
複製代碼
過程:
1.全局執行上下文進棧
2.調用函數father,father函數執行上下文進棧
3.father函數內部代碼執行,son函數被執行,son函數執行上下文進棧
4.son函數執行完畢,son函數的執行上下文出棧
5.father函數執行完畢,father函數的執行上下文出棧
6.瀏覽器關閉時,全局執行上下文出棧
執行上下文棧特色: