Repository navigation
JavaScript深入之执行上下文 #8
Description
Activity
checkscope 函数 和 f 函数,在代码执行这一阶段,没有对各自的 this 做任何操作,所以沿着作用域链,最终找到全局 this 的引用,即 globalContext.VO 对象,是这样吧?
@zuoyi615 this 是在函数执行的时候才确定下来的,checkscope 函数 和 f 函数的 this 的值跟作用域链没有关系,具体的取值规则还需要参照上一篇文章《JavaScript深入之从ECMAScript规范解读this》, 两者的 this 其实都是 undefined ,只是在非严格模式下,会转为全局对象。嗯,如果讲的不明白的话,就跟我说一下,我看怎么再表述下这个东西哈~
Reacted by zhangwy9898, NieHang and bigtomissyue从ECMAScript规范解读this,太不好理解了
作者您好!之前有道题,通过看您的文章,大致有了一个猜想,但是还是不能很清晰的说出原因,烦请您看一下,谢谢!
let nAdd; let t = () => { let n = 99; nAdd = () => { n++; }; let t2 = () => { console.log(n); }; return t2; }; let a1 = t(); let a2 = t(); nAdd(); a1(); //99 a2(); //100
不知是不是a2()的作用域置顶了,所以nAdd()修改的是a2()作用域里的变量,但闭包的话,同一个变量名难道不是指向同一个内存地址的值吗
Reacted by NieHang@flyerH 这真的是个好问题!我们先看个简单的例子:
var t = function() { var n = 99; var t2 = function() { n++ console.log(n) } return t2; }; var a1 = t(); var a2 = t(); a1(); // 100 a1(); // 101 a2(); // 100 a2(); // 101
我们会发现,n 的值都是从 99 开始,执行 一次a1() 的时候,值会加一,再执行一次,值再加一,但是 n 在 a1() 和 a2() 并不是公用的。你可以理解为:同一个函数形成的多个闭包的值都是相互独立的。
接下来看这道题目,关键在于 nAdd 函数
var nAdd; var t = function() { var n = 99; nAdd = function() { n++; } var t2 = function() { console.log(n) } return t2; }; var a1 = t(); var a2 = t(); nAdd(); a1(); //99 a2(); //100
当执行
var a1 = t()的时候,变量 nAdd 被赋值为一个函数 ,这个函数是function (){n++},我们命名这个匿名函数为 fn1 吧。接着执行var a = t()的时候,变量 nAdd 又被重写了,这个函数跟以前的函数长得一模一样,也是function (){n++},但是这已经是一个新的函数了,我们就命名为 fn2 吧。所以当执行 nAdd 函数,我们执行的是其实是 fn2,而不是 fn1,我们更改的是 a2 形成的闭包里的 n 的值,并没有更改 a1 形成的闭包里的 n 的值。所以 a1() 的结果为 99 ,a2()的结果为 100。
Reacted by Jelly, roy-k, wan, cyn, zhaorui, xc, ClarenceChan, enggirl, Wick Magic, 小动物保护协会 and 290 moreReacted by alamx, xsfxtsxxrxfy, yanghsiungwen, PaulaSham, 褚宏光, David, Newt7, TouchFish and zhangshuoReacted by 小动物保护协会, xiaoping wu , Zeig, zhaoyf1214, weiyang.su, 大流星, EzJane, Swell, Sanki, gxr404 and 25 moreReacted by Swell, song, 褚宏光, Saturday, TouchFish and Evan YuanReacted by flyerH, lv, woasder, Po Chun, Lu, Yoomin, Jelly, qujsh, cyn, zhaorui, xc and 33 moreReacted by Sanki, gxr404, Jackcaos, Ron-LaoLuo, David, 包国杰, lujiashen, guanxc9876, alamx, kk and 8 moreReacted by Jackcaos, 包国杰, it_xiaopang, lujiashen, song, alamx, xsfxtsxxrxfy, rabbitandcat, TouchFish and QF-study@mqyqingfeng 非常感谢您的解答,谢谢!
@flyerH 哈哈,不用这么客气,有问题就留言讨论哈~
@zuoyi615 哈哈,确实不好理解,因为涉及到很多规范上的内容,需要边查规范边读,但我也正是通过研究 this 第一次克服了对于全英文的规范的恐惧,希望你也去试一试~
Reacted by LittleMonsterAK, bao_tao1997, withme, answer33, 褚宏光, ChenPt and BeixiaomiReacted by wang xiao yu第一个函数查找上级作用域中scope
第二个函数式闭包,保存了父级函数中scope的引用
所以两个值相等;Reacted by Deer, chenjz08, Evan Chen and 颜磊博主,请问那个nAdd(); 什么时候调用的? 我看不懂
@Flying-Eagle2 当然是执行这个函数的时候调用的啦~
同时 f 函数被创建,保存作用域链到 f 函数的内部属性[[scope]]
checkscopeContext = { AO: { arguments: { length: 0 }, scope: undefined, f: reference to function f(){} }, Scope: [AO, globalContext.VO], this: undefined } checkscope();checkscope预编译阶段,形参、函数f声明、变量scope声明。
f 函数被创建的活动是在checkscope函数预编译阶段进行还是在函数执行阶段进行的?@suoz 我认为是在 checkscope 函数预编译阶段
Reacted by cloudXA@mqyqingfeng 大大
function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f(); }你看我这么理解对么~
执行函数checkscope时,分为预编译阶段和执行阶段,预编译阶段就是你所说的创建执行上下文、执行上下文初始化(复制函数[[scope]]属性创建作用域链、使用arguments创建活动对象、初始化活动对象{即形参、函数声明、变量声明}、将活动对象压入作用域链的顶端)。
当函数checkscope执行,处于预编译阶段中函数声明的时候,此时只是创建了f函数(只是创建了f函数的[[scope]]属性,这个属性只包含了checkscope函数的活动对象和全局变量对象,并不包含f函数的活动对象)
等到函数checkscope处于执行阶段时,就是
return f();,此时调用f(),这时候才会创建f函数的上下文,以及上面所提到的相同四步骤。Reacted by TTTTTTTZL, David, DragonGirlsDriver, Miller Shi, kk, beexpectedone, 褚宏光, Beyound and ShyRobinReacted by Alice Jia, Wick Magic, dcharlie123, LiaoLiao and Beyound50 remaining items
@yh284914425
@mqyqingfeng
关于很久之前的整个问题
var b = 10;
(function b(){
b = 20;
console.log(b);
})();
这个问题真是一个宝藏问题,查询途中发现了好多知识点。
立即执行表达式的本质:定义了一个函数表达式,然后立即执行该表达式。
大多数情况下我们会使用匿名函数表达式,如果用命名表达式——《javascript权威指南》8.1章节的函数定义部分,有这样一段话:“如果一个函数定义表达式包含名称,函数的局部作用域将会包含一个绑定到函数对象的名称。实际上,函数的名称将成为函数内部的一个局部变量”。
下面是规范中的说明: The BindingIdentifier in a FunctionExpression can be referenced from inside the FunctionExpression's FunctionBody to allow the function to call itself recursively. However, unlike in a FunctionDeclaration, the BindingIdentifier in a FunctionExpression cannot be referenced from and does not affect the scope enclosing the FunctionExpression.
还有一个被称为JSbug的问题。一般情况下,如果出现temp=10;这样的给未声明变量赋值的操作,temp会声明为全局变量。但是如果这样的情况出现在立即表达式中,temp会被声明为局部变量而不是全局变量。。这个首先就解决您的问题,b=20并不会影响到全局变量中的b。接下来的是我自己不明白的问题,立即表达式的局部变量b难道不会被修改为20吗,为什么打印出现的是b函数本身,而不是20呢?
var b = 10;
(function b() {
var b = 20;
console.log(b);//20
})();
(function b() {
console.log(b);//[Function b]
})();
(function b() {
b=20;
console.log(b);//[Function b]
})();我的理解:在表达是内部有一个隐式的var b的变量(跟表达式同名)。因此,在内部如果再次显式的var b =20,那显然会覆盖掉隐式的。但是,没有var的关键字声明,那么b=20只不过是修改这个隐式的b,而同时,这个b是只读的,不可修改的。因此输出的还是Function b.
其实你只要在开头加上'use strict',那么执行就会直接报错:b=20. 因为b是constant variable
function a() {
var aaa = 123;
function b(){
console.log(aaa); // 123
aaa = 234;
};
console.dir(b);
};
a();
function a() {
var aaa = 123;
function b(){
console.log(aaa); // 123
var aaa = 234;
};
console.dir(b);
};
a();博主,以上两段代码输出不一样,请问用这个作用域链该如何解释?
第一个,函数b里面引用了函数a中的变量 aaa,所以形成了闭包, 第二个,函数b 没有引用函数a中的变量aaa,因为b函数内部用var声明了aaa,打印应该是undefined, 没有形成闭包, 所以 console.dir(b) 结果也不一样
Reacted by beexpectedonevar scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
我来仿照大大的思路分析一下第二段代码。如果有哪里说得不对还请多指正~
1.执行全局代码。创建全局上下文,全局上下文被压入栈。ECStack=[ globalContext ];2.全局上下文初始化:
globalContext = { VO: [global], Scope: [globalContext.VO], this: undefined }初始化上下文的同时,创建checkscope函数,保存作用域到函数内部[[scope]]属性。
checkscope.[[scope]] = [ globalContext.VO ];3.执行checkscope函数。创建checkscope函数上下文,函数上下文被压入栈。
ECStack=[ checkscopeContext, globalContext ];4.checkscope函数执行上下文初始化:
1.复制函数 [[scope]] 属性创建作用域链,
2.用 arguments 创建活动对象,
3.初始化活动对象,即加入形参、函数声明、变量声明,
4.将活动对象压入 checkscope 作用域链顶端。
同时 f 函数被创建,保存作用域链到 f 函数的内部属性[[scope]]checkscopeContext = { AO: { arguments: { length: 0 }, scope: undefined, f: reference to function f(){} }, Scope: [AO, globalContext.VO], this: undefined }5.checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出。
ECStack=[ globalContext ];6.执行f函数,创建f函数上下文,上下文被压入栈。
ECStack=[ fContext, globalContext ];7.f函数执行上下文初始化。
1.复制函数 [[scope]] 属性创建作用域链,
2.用 arguments 创建活动对象,
3.初始化活动对象,即加入形参、函数声明、变量声明,
4.将活动对象压入 checkscope 作用域链顶端。fContext = { AO: { arguments: { length: 0 } }, Scope: [AO, checkscopeContext.AO, globalContext.VO], this: undefined }8.f 函数执行,沿着作用域链查找 scope 值,返回 scope 值
9.f 函数执行完毕,f 函数上下文从执行上下文栈中弹出ECStack = [ globalContext ];和我的想法有点差异,欢迎交流。
- 创建checkscope函数的时候 ,不是保存 作用域到[[scope]]属性,而是保存所有父级的变量对象到[[scope]]属性,感觉也可以说成作用域。。
- 执行f函数的初始化过程, 应该是将函数f执行上下文中的AO压入自身上下文中的作用域链前端, 接着函数f执行, 沿着作用域链查找 scope属性,在自身AO中没有找到,再去上一层,也就是函数f创建时候复制自身[[scope]]属性得到的作用域链,应该是 [checkscope.AO, globalContext.VO] 在函数checkscope中找到了, 此处checkscope函数虽然出栈了,但是变量对象并没有销毁, 因为还被 函数f 的 [[scope]]属性引用,这也就是闭包吧
Reacted by beexpectedone and Lanbasara@yh284914425
@mqyqingfeng
关于很久之前的整个问题
var b = 10;
(function b(){
b = 20;
console.log(b);
})();
这个问题真是一个宝藏问题,查询途中发现了好多知识点。
立即执行表达式的本质:定义了一个函数表达式,然后立即执行该表达式。
大多数情况下我们会使用匿名函数表达式,如果用命名表达式——《javascript权威指南》8.1章节的函数定义部分,有这样一段话:“如果一个函数定义表达式包含名称,函数的局部作用域将会包含一个绑定到函数对象的名称。实际上,函数的名称将成为函数内部的一个局部变量”。
下面是规范中的说明: The BindingIdentifier in a FunctionExpression can be referenced from inside the FunctionExpression's FunctionBody to allow the function to call itself recursively. However, unlike in a FunctionDeclaration, the BindingIdentifier in a FunctionExpression cannot be referenced from and does not affect the scope enclosing the FunctionExpression.
还有一个被称为JSbug的问题。一般情况下,如果出现temp=10;这样的给未声明变量赋值的操作,temp会声明为全局变量。但是如果这样的情况出现在立即表达式中,temp会被声明为局部变量而不是全局变量。。这个首先就解决您的问题,b=20并不会影响到全局变量中的b。接下来的是我自己不明白的问题,立即表达式的局部变量b难道不会被修改为20吗,为什么打印出现的是b函数本身,而不是20呢?
var b = 10;
(function b() {
var b = 20;
console.log(b);//20
})();
(function b() {
console.log(b);//[Function b]
})();
(function b() {
b=20;
console.log(b);//[Function b]
})();你自己也说了呀 。如果一个函数定义表达式包含名称,函数的局部作用域将会包含一个绑定到函数对象的名称。实际上,函数的名称将成为函数内部的一个局部变量。 既然这个变量b(指向命名函数表达式的引用)存在于函数内部, 那么进行 b = 20 的赋值操作时,首先会找到 指向命名函数表达式引用的变量b,进行赋值,而这个变量又是不可改变的, 自然不会产生任何效果,输出都是命名函数表达式了
博主大大or各位路过大大,您们好!有一个疑惑也可能是犯二。。。,就是这篇文章的正文和评论部分,在ECStack的添加时都是按照unshift方法压入的,但是在博主大大的《JavaScript深入之执行上下文栈》一文中,是按照push和pop来分别压入和弹出函数执行上下文的,我现在有点懵,到底哪个是对的啊,怎么大家都是按照unshift压入的,应该以哪个为正确啊。。。哭唧唧.jpg
假如我们用JS数组这种数据结构来模拟栈的话,根据栈“先进先出“的特点,在进栈时可以理解为 要到 整个栈(数组)的头部去,也就是用unshift方法 ,当然也看你怎么来定义 头部和尾部对应数组的开头还是结尾了。。
命名的函数表
function b () {
b = 20
console.log(b)
}
b() // 20
大大,还是这里,如果不用立即执行函数,打印的b就是20, 使用立即函数打印的就是函数b命名的函数表
function b () {
b = 20
console.log(b)
}
b() // 20
大大,还是这里,如果不用立即执行函数,打印的b就是20, 使用立即函数打印的就是函数bfunction b(){} 等于window.b,当执行函数b后把window.b重新赋值为20,所以打印会是20。立即执行函数中函数名优先级高些,函数体中给函数同名变量赋值不会覆盖原有操作。
分析第二段代码
var scope = "global scope"; function checkscope() { var scope = "local scope"; function f() { return scope; } return f; } checkscope()();
1.执行全局代码,创建全局执行上下文,全局上下文被压入执行上下文栈:
ECStack = [ globalContext ]
2.开始执行代码,全局上下文初始化:
globalContext = { VO: [ global ], Scope: [ globalContext.VO ], this: globalContext.VO }
3.初始化的同时,
checkscope函数被创建,保存作用域链到内部属性[[scope]]:checkscope.[[scope]] = [ globalContext.VO ];
4.开始执行
checkscope函数,创建checkscope函数执行上下文,并将checkscope函数上下文压入执行上下文栈:ECStack = [ checkscopeContext, globalContext ];
5.
checkscope函数上下文初始化:- 复制函数 [[scope]] 属性创建作用域链,
- 用 arguments 创建活动对象,
- 初始化活动对象,即加入形参、函数声明、变量声明,
- 将活动对象压入
checkscope函数作用域链顶端。
checkscopeContext = { AO: { arguments: { length: 0 }, scope: undefined, f: reference to function f(){} }, Scope: [AO, globalContext.VO], this: undefined }
初始化的同时,
f函数被创建,保存作用域链到f函数的内部属性[[scope]]:f.[[scope]] = [checkscopeContext.AO, globalContext.VO]
6.
checkscope函数执行,随着函数的执行,修改AO的值,所以此时checkscopeContext变更为:checkscopeContext = { AO: { arguments: { length: 0 }, scope: "local scope", f: reference to function f(){} }, Scope: [AO, globalContext.VO], this: checkscopeContext.AO }
接着返回了
f函数.7.
checkscope函数执行完毕,checkscope执行上下文从执行上下文栈中弹出:ECStack = [ globalContext ];
8.开始执行
f 函数,创建f函数执行上下文,并将f函数上下文压入执行上下文栈:ECStack = [ fContext, globalContext ];
9.
f函数上下文初始化:- 复制函数 [[scope]] 属性创建作用域链,
- 用 arguments 创建活动对象,
- 初始化活动对象,即加入形参、函数声明、变量声明,
- 将活动对象压入
f 函数作用域链顶端。
fContext = { AO: { arguments: { length: 0 } }, Scope: [AO, checkscopeContext.AO, globalContext.VO], this: undefined }
10.
f函数执行,沿着作用域链查找scope的值,找到并返回了scope.可是当
f 函数执行的时候,checkscope 函数上下文已经被销毁了(即从执行上下文栈中被弹出),怎么还会读取到checkscope作用域下的scope值呢?这是因为
checkscope函数执行上下文初始化时,f 函数同时被创建,保存作用域链到f 函数的内部属性[[scope]],所以即使checkscope函数执行完毕,被弹出执行上下文栈,但是checkscopeContext.AO依然存在于f 函数的内部属性[scope]]中:f.[[scope]] = [checkscopeContext.AO, globalContext.VO]
所以在
f 函数执行的时候仍然可以通过f 函数的作用域链能找到scope.所以这里就产生了闭包:- 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
- 在代码中引用了自由变量
11.
f函数执行完毕,f执行上下文从执行上下文栈中弹出:ECStack = [ globalContext ];
Reacted by atzcl, YingXiangMoJi, xieyezi, zhaogongwei and sinkx0@flyerH 这真的是个好问题!我们先看个简单的例子:
var t = function() { var n = 99; var t2 = function() { n++ console.log(n) } return t2; }; var a1 = t(); var a2 = t(); a1(); // 100 a1(); // 101 a2(); // 100 a2(); // 101
我们会发现,n 的值都是从 99 开始,执行 一次a1() 的时候,值会加一,再执行一次,值再加一,但是 n 在 a1() 和 a2() 并不是公用的。你可以理解为:同一个函数形成的多个闭包的值都是相互独立的。
接下来看这道题目,关键在于 nAdd 函数
var nAdd; var t = function() { var n = 99; nAdd = function() { n++; } var t2 = function() { console.log(n) } return t2; }; var a1 = t(); var a2 = t(); nAdd(); a1(); //99 a2(); //100
当执行
var a1 = t()的时候,变量 nAdd 被赋值为一个函数 ,这个函数是function (){n++},我们命名这个匿名函数为 fn1 吧。接着执行var a = t()的时候,变量 nAdd 又被重写了,这个函数跟以前的函数长得一模一样,也是function (){n++},但是这已经是一个新的函数了,我们就命名为 fn2 吧。所以当执行 nAdd 函数,我们执行的是其实是 fn2,而不是 fn1,我们更改的是 a2 形成的闭包里的 n 的值,并没有更改 a1 形成的闭包里的 n 的值。所以 a1() 的结果为 99 ,a2()的结果为 100。
大佬,这个虽然你解读的很合理,但是还是有点疑惑,函数不是在调用时才执行嘛?你解读的怎么会先执行var a1=t()这样的函数呢?如果把例子改成把nAdd()放在var a1=t() 的上一行呢 @mqyqingfeng
关于 this 有个疑问: 箭头函数的执行上下文是没有自己 this 的,继承定义时执行上下文的 this 。 那问题来了,定义时的上下文执行完成就销毁了,那么当箭头函数执行时。是怎么确定this的指向的, 我目前的理解是把this看成一个普通属性,保存在AO中,函数访问this就像查找普通变量一样,通过原型链来访问。
关于 this 有个疑问: 箭头函数的执行上下文是没有自己 this 的,继承定义时执行上下文的 this 。 那问题来了,定义时的上下文执行完成就销毁了,那么当箭头函数执行时。是怎么确定this的指向的, 我目前的理解是把this看成一个普通属性,保存在AO中,函数访问this就像查找普通变量一样,通过原型链来访问。
对于第二个例子,请教一下,各位大佬看下理解的对吗?
//执行全局代码 1.初始化全局对象 GO = { scope: undefined; checkscope: reference to function checkscope(){} // 内存地址 } // checkscope 函数被创建,保存作用域链到函数的内部属性[[scope]] checkscope.[[scope]] = [ globalContext.VO ] 2.构建全局上下文 globalContext = { VO: GO, scope: [globalContext.VO], this: window } 3.压入ECS ECS = [ globalContext ] 4.全局执行上下文赋值 globalContext = { VO: { arguments:{ length: 0 }, scope: 'global scope', checkscope: reference to function checkscope(){}, } scope: [globalContext.VO], his: window } // 执行全局代码遇到函数执行 5. 压入ECS ECS = [ checkscopeContext, globalContext ] 6.函数执行上下文初始化 checkscopeContext = { AO: { arguments:{ length: 0 } scope: "local scope", f: reference to function f(){} }, scope:[AO, globalContext.VO] this: undefined } //f 函数被创建,保存作用域链到函数的内部属性[[scope]] f.[[scope]] = [ checkscopeContext.AO, globalContext.VO ] 7.函数 checkscope 执行完毕,返回 函数 f(){return scope;},checkscope 从ECS中出栈 ECS = [ globalContext ] 8.此时函数 f 执行 ,fContext压入栈中 ECS = [ fContext, globalContext ] 9.函数执行上下文初始化 fContext = { AO: { arguments:{ length: 0 } }, scope:[AO, checkscopeContext.AO, globalContext.VO] this: undefined } 10. f 函数执行,在作用域链 checkscopeContext.AO 中找到 scope 值,返回 'local scope' 11. f 函数执行完毕, fContext 从ECS中出栈 12. globalContext f 从ECS中出栈
博主,请问下这个应该是ES3的上下文标准吧?查了下ES6/ES5的上下文已经是把变量对象这些概念去掉,引进词法环境、变量环境新的概念,以及外部词法环境引用。理解不到位的地方还烦请指正下~
个人感觉第3,第4步是不是反了,应该现实checkscope函数执行上下文初始化,然后再是checkscope函数的执行。这样的话在执行的过程中才会去将checkscopeContext中活动对象(AO)的部分值给赋上,比如AO.scope = 'local scope'
个人感觉第3,第4步是不是反了,应该现实checkscope函数执行上下文初始化,然后再是checkscope函数的执行。这样的话在执行的过程中才会去将checkscopeContext中活动对象(AO)的部分值给赋上,比如AO.scope = 'local scope'
第3步应该只是把这个创建的执行上下文压入栈,压入栈不等于立刻执行。第4步对这个上下文进行初始化,初始化完成后才真正开始执行。

前言
在《JavaScript深入之执行上下文栈》中讲到,当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。
对于每个执行上下文,都有三个重要属性:
然后分别在《JavaScript深入之变量对象》、《JavaScript深入之作用域链》、《JavaScript深入之从ECMAScript规范解读this》中讲解了这三个属性。
阅读本文前,如果对以上的概念不是很清楚,希望先阅读这些文章。
因为,这一篇,我们会结合着所有内容,讲讲执行上下文的具体处理过程。
思考题
在《JavaScript深入之词法作用域和动态作用域》中,提出这样一道思考题:
两段代码都会打印'local scope'。虽然两段代码执行的结果一样,但是两段代码究竟有哪些不同呢?
紧接着就在下一篇《JavaScript深入之执行上下文栈》中,讲到了两者的区别在于执行上下文栈的变化不一样,然而,如果是这样笼统的回答,依然显得不够详细,本篇就会详细的解析执行上下文栈和执行上下文的具体变化过程。
具体执行分析
我们分析第一段代码:
执行过程如下:
1.执行全局代码,创建全局执行上下文,全局上下文被压入执行上下文栈
2.全局上下文初始化
2.初始化的同时,checkscope 函数被创建,保存作用域链到函数的内部属性[[scope]]
3.执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
4.checkscope 函数执行上下文初始化:
同时 f 函数被创建,保存作用域链到 f 函数的内部属性[[scope]]
5.执行 f 函数,创建 f 函数执行上下文,f 函数执行上下文被压入执行上下文栈
6.f 函数执行上下文初始化, 以下跟第 4 步相同:
7.f 函数执行,沿着作用域链查找 scope 值,返回 scope 值
8.f 函数执行完毕,f 函数上下文从执行上下文栈中弹出
9.checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
第二段代码就留给大家去尝试模拟它的执行过程。
不过,在下一篇《JavaScript深入之闭包》中也会提及这段代码的执行过程。
下一篇文章
《JavaScript深入之闭包》
相关链接
《JavaScript深入之词法作用域和动态作用域》
《JavaScript深入之执行上下文栈》
《JavaScript深入之变量对象》
《JavaScript深入之作用域链》
《JavaScript深入之从ECMAScript规范解读this》
重要参考
《一道js面试题引发的思考》
本文写的太好,给了我很多启发。感激不尽!
深入系列
JavaScript深入系列目录地址:https://github.com/mqyqingfeng/Blog。
JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。