对于一名前端开发者来讲,深刻理解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.浏览器关闭时,全局执行上下文出栈
执行上下文栈特色: