摘要:中没有可执行的函数了,执行完出栈。当某个函数被调用时,会创建一个执行环境及相应的作用域链。检查当前环境中的函数声明使用声明的。确定指向所以说的指向,是在函数执行时确定的。
理解js 的执行过程是很重要的,比如,作用域,作用域链,变量提升,闭包啊,要想明白这些,你就得搞懂函数执行时到底发生了什么! 一、执行环境(Execution Context)又称执行上下文
当代码执行时都会产生一个执行环境。JavaScript中的执行环境可以分为三种。
全局环境:在浏览器中,全局环境被认为是window对象,因此,所有的全局变量和函数都作为window对象的 属性 和 方法 创建的。
函数环境:当一个函数执行时,就会创建该函数的执行环境,在其中执行代码。
eval(不建议使用,可忽略)
函数内,没有使用var 声明的变量,在非严格模式下为window的属性,即全局变量。
js 是根据函数的调用(执行) 来决定 执行顺序的。每当一个函数被调用时,js 会为其创建执行环境,js引擎就会把这个执行环境 放入一个栈中 来处理。
这个栈,我们称之为函数调用栈(call stack)。栈底永远都是全局环境,而栈顶就是当前正在执行函数的环境。当栈顶的执行环境 执行完之后,就会出栈,并把执行权交给之前的执行环境。
看栗子说话:
function A(){ console.log("this is A"); function B(){ console.log("this is B"); } B(); } A();
那么这段代码执行的情况就是这样了。
首先 A() ;A 函数执行了,A执行环境入栈。
A函数执行时,遇到了 B(),B 又执行了,B入栈。
B中没有可执行的函数了,B执行完 出栈。
继续执行A, A中没有可执行的函数了,A执行完 出栈。
再来个不常规的:
function A(){ function B(){ console.log(say); } return B; } var C = A(); C();
首先 A() ;A 函数执行了,A执行环境入栈。
继续执行A, A中没有可执行的函数了,A执行完 出栈。
然后C(), 这时的C 就是 B,A 执行后,把B返回 赋值给了C,B执行环境入栈。
B中 没有可执行的函数了,B执行完 出栈。
眼尖的同学,估计看出来了,它怎么像闭包呢?其实,稍微改动下,它就是闭包了。
function A(){ var say = 666 function B(){ console.log(say); } return B; } var C = A(); C(); //666
这就是闭包了,但是这次我们不讲闭包,你就知道,它是的执行是怎么回事就行。
现在我们已经知道,每当一个函数执行时,一个新的执行环境就会被创建出来。其实,在js引擎内部,这个环境的创建过程可分为两个阶段:
A. 建立阶段(发生在调用(执行)一个函数时,但是在执行函数内部的具体代码之前)
1.建立活动对象; 2.构建作用域链; 3.确定this的值。
B. 代码执行阶段(执行函数内部的具体代码)
1.变量赋值;
2.执行其它代码。
需要注意的是,作用域链是创建函数的时候就创建了,此时的链只有全局变量对象,保存在函数的[[Scope]]属性中,然后函数执行时的,只是通过复制该属性中的对象 来 构建作用域链。本文后面还有说明。
看图更清晰!
如果把函数执行环境看成一个对象的话:
executionContextObj = { //执行上下文对象 AtiveObject: { }, //活动对象 scopeChain: { }, //作用域链 this: {} //this }
//下面这段内容,感兴趣的可以看下,不感兴趣,就跳过哈。
也许你在别家看到跟我的不一样,人家写的是建立变量对象。下面我来说说我得想法吧!
之前我按照 首先建立变量对象,其后,变量对象转变为活动对象的规则 去理解,但是呢,通过我分析JavaScript高级程序设计第三版,4.2节 和 7.2节,发现根本就不符合逻辑。
然后,我根据分析,得出了我的结论:变量对象 是执行环境中保存着环境中定义的所有变量和函数 的对象 的统称。而活动对象,是函数执行环境中创建的,它不仅保存着函数执行环境中定义的变量和函数,并且独有一个arguments 属性。因此,活动对象也可称之为变量对象。
这样,很多东西就说的通了。
比如(以下都是来自JavaScript高级程序设计第三版,4.2节 和 7.2节 中原文):
如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。
当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链。
然后,使用 arguments 和其他命名参数的值来初始化函数的活动对象。
每个执行环境都有一个表示变量的对象——变量对象。
此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执
行环境作用域链的前端。对于这个例子中 compare() 函数的执行环境而言,其作用域链中包含两个变量对象:本地活动对象和全局变量对象。
有兴趣的可以去看看这本书上说的,有不同的想法可以积极留言,咱们好好探讨,哈哈。
(一)建立阶段1、建立活动对象(AO)
A. 建立arguments对象,检查当前上下文中的参数,建立该对象下的属性以及属性值 。
B. 检查当前环境中的函数声明(使用function 声明的)。每找到一个函数声明,就在活动对象下面用函数名建立一个属性,属性值就是指向该函数在内存中的地址的一个引用,如果上述函数名已经存在于活动对象下,那么则会被新的函数引用所覆盖。
C. 检查当前上下文中的变量声明(使用 var 声明的)。每找到一个变量声明,就在活动对象下面用变量名建立一个属性,该属性值为undefined。如果该属性名已存在,则忽略新的声明。
function test(){ function a(){}; var b; } test();
test 函数 的活动对象:
testAO: { //test变量对象 arguments: { ... }; a:function(){}; b:undefined }
变量作用域
javaScript 中,只有两种变量作用域,一种是局部变量作用域,又称函数作用域。另一个则是全局作用域。
什么变量提升问题的根本原因就在建立阶段了。
console.log(A); function A(){}; console.log(B); var A = 666; var B = 566; console.log(A); console.log(B); //function A //undefined //666 //566
上面的实际顺序就是这样的了
function A(){}; //var A; 这个var 声明的 同名 A,会被忽略 var B = undefined; console.log(A); console.log(B); A = 666; //给A 重新赋值 B = 566; //给B 赋值 console.log(A); console.log(B);
注意第三点,使用var 声明时,如果VO对象下,该属性已存在,忽略新的var 声明。
因为A 使用 function 声明,VO对象下,创建A属性,然后 var 声明时,检索发现已经有该属性了,就会忽略 var A 的声明,不会把A 设置为 undefined。
2、构建作用域链
作用域链的最前端,始终都是当前执行的代码所在函数的活动对象。下一个AO(活动对象)为包含本函数的外部函数的AO,以此类推。最末端,为全局环境的变量对象。
注意:虽然作用域链是在函数调用时构建的,但是,它跟调用顺序(进入调用栈的顺序)无关,因为它只跟 包含关系(函数 包含 函数 的嵌套关系) 有关。
可能比较绕口,还是来个小栗子,再来个图
function fa(){ var va = "this is fa"; function fb(){ var vb = "this is fb"; console.log(vb); console.log(va); } return fb; } var fc = fa(); fc(); //"this is fb" //"this is fa"
函数调用栈的情况就是这样:
那么把函数 fb 的执行环境比作对象(建立阶段):
fbEC = { //执行上下文对象 fbAO: { //活动对象 AO arguments: { ... }; //arguments 对象 vb: undefined //变量声明建立的属性,设置为undefined }, scopeChain: [ AO(fa), AO(fb), VO(window) ], //作用域链 this: { ... } //this }
fb作用域的展开就是这样的:
fb 函数 被 fa 函数 包含, fa 函数 被 window 全局环境包含。作用域链只跟包含关系有关!
注意:作用域链是单向的,因此,函数内的可以访问函数外 和 全局的变量,函数,但是反过来,函数外,全局内 不能访问函数内的变量,函数。
3、确定 this 指向
所以说 this 的指向,是在函数执行时确定的。
1、变量赋值
根据代码顺序执行,遇到变量赋值时, 给对应的变量赋值。
function getColor(){ console.log(color); var color; console.log(color); color = "red"; console.log(color); } getColor(); //undefined //undefined //"red";
3、执行其他代码。
当函数执行完毕后,局部活动对象就会被销毁(也就是说,局部的变量,函数,arguments 等都会被销毁),内存中仅保存全局作用域(全局执行环境的变量对象)。
这句话对理解闭包很重要,随后,我会出一个闭包的文章,敬请期待!
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/96050.html
摘要:如下代码输出的结果是代码执行分为两个大步预解析的过程代码的执行过程预解析与变量声明提升程序在执行过程中,会先将代码读取到内存中检查,会将所有的声明在此进行标记,所谓的标记就是让解析器知道有这个名字,后面在使用名字的时候不会出现未定义的错误。 showImg(https://segmentfault.com/img/remote/1460000012922850); 如下代码输出的结果是...
摘要:为了更好的理解,在阅读此文之前建议先阅读上一篇进击之词法作用域与作用域链什么是闭包闭包的含义就是闭合,包起来,简单的来说,就是一个具有封闭功能与包裹功能的结构。在中函数构成闭包。 为了更好的理解,在阅读此文之前建议先阅读上一篇《进击JavaScript之词法作用域与作用域链》 1.什么是闭包 闭包的含义就是闭合,包起来,简单的来说,就是一个具有封闭功能与包裹功能的结构。所谓的闭包就是...
摘要:一作用域域表示的就是范围,即作用域,就是一个名字在什么地方可以使用,什么时候不能使用。概括的说作用域就是一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量。 一、作用域 域表示的就是范围,即作用域,就是一个名字在什么地方可以使用,什么时候不能使用。想了解更多关于作用域的问题推荐阅读《你不知道的JavaScript上卷》第一章(或第一部分),从编译原理的角度说明什么是作用域。概...
摘要:此时产生了闭包。导致,函数的活动对象没有被销毁。是不是跟你想的不一样其实,这个例子重点就在函数上,这个函数的第一个参数接受一个函数作为回调函数,这个回调函数并不会立即执行,它会在当前代码执行完,并在给定的时间后执行。 上一节说了执行上下文,这节咱们就乘胜追击来搞搞闭包!头疼的东西让你不再头疼! 一、函数也是引用类型的。 function f(){ console.log(not cha...
摘要:匿名函数是不能单独写的,所以就提不上立即执行了。六立即执行函数在闭包中的应用立即执行函数能配合闭包保存状态。来看下上节内容中闭包的例子现在,我们来利用立即执行函数来简化它第一个匿名函数执行完毕后,返回了第二个匿名函数。 前面的闭包中,提到与闭包相似的立即执行函数,感觉两者还是比较容易弄混吧,严格来说(因为犀牛书和高程对闭包的定义不同),立即执行函数并不属于闭包,它不满足闭包的三个条件。...
阅读 1761·2023-04-25 14:33
阅读 3339·2021-11-22 15:22
阅读 2142·2021-09-30 09:48
阅读 2664·2021-09-14 18:01
阅读 1710·2019-08-30 15:55
阅读 2988·2019-08-30 15:53
阅读 2107·2019-08-30 15:44
阅读 633·2019-08-30 10:58