您现在的位置是:首页 > 文章详情

Function执行原理 & 闭包

日期:2018-07-12点击:386

Execution Context 执行期上下文

在java或c语言中,都有块级作用域这个概念,而js中则没有。

在js中,作用域只有一种,即函数级作用域。

而执行期上下文,可以理解为函数的作用域或执行环境。

在代码层面,执行期上下文是嵌套存在的

15314682222249

在js引擎内,执行期上下文是以栈的形式进行存放

js引擎中存放

栈的最底部存放的global上下文,每次执行一个函数,则会创建一个上下文放入栈中,执行结束后再pop移除。

(function foo(i) { if (i === 3) { return; } else { foo(++i); } }(0));

当前的执行环境则永远使用存放在栈顶的上下文对象。

1111111

参考博文:

Function 执行原理

js中有在function上面有很多的用法和概念,就比如作用域链,闭包这些。

其实问题归结到了根本,都在于Function在执行时做了什么。

这个是以下内容的一个思维导图:

15314687305329

以Function的生命周期划分,分为:创建阶段执行阶段

这里从执行阶段开始,以 执行函数内部代码 为时间点分成3个阶段:

  • 执行前
  • 执行时
  • 执行后

执行前

当执行一个函数时,会创建一个函数的执行环境,即执行期上下文对象(execution context)。

ExecutionContext = { VO:{ // Variable Object(全局上下文独有) // 变量 (var, 变量声明); // 函数声明 (FunctionDeclaration, 缩写为FD); }, AO:{ // Activation Object (函数上下文独有) // 变量 (var, 变量声明); // 函数声明 (FunctionDeclaration, 缩写为FD); // arguments }, scopeChain:{..}, this:{..} }

该对象主要做了三件事情:

  1. 确定函数内所有的变量 (AO | VO)

    • AO:通过上下文栈中的当前上下文,获取参数,创建arguments对象
    • AO & VO:扫描代码,创建所有函数声明(hoist作用域提升的原因,这些函数将进入创建阶段)和变量声明(值为undefined)
  2. 创建作用域链 (scopeChain = (AO | VO) + [[Scope]])
  3. 确定this指向 (由当前所处的执行期上下文提供)

VO & AO

VO 和 AO 的作用是存储函数中执行时需要用到的所有函数和变量。

执行期上下文分为两种:

  • 全局上下文 (global,在上下文栈最底部,这个对象只存在一份,它的属性在程序中任何地方都可以访问)
  • 函数上下文 (每次在函数调用时进行创建)

这两种上下文的区别就在于其创建方式和VO的访问性。

全局上下文的VO可以直接访问,VO对象指向的是global自身

var name = {}; name === this.name // true name === window.name // true

函数上下文的VO不能直接访问,创建活动对象AO来代替VO。

因此函数上下文只有AO,VO没有。

创建作用域

函数生命周期分为两个阶段:

  • 创建阶段 (创建 [[scope]]
  • 执行阶段 (创建 scopeChain

在函数创建阶段,[[scope]]就已经被创建了。

function say(){ var words = "hello"; hello(); // 输出:hello function hello(){ console.log(words); } } say.[[scope]] = [ GlobalExecutionContext.VO ]

遵循 [[scope]] = superExecutionContext.scopeChain + superFn.[[scope]] 这个规则。

该变量[[scope]]是一种静态变量,一直存在,直至函数被delete或垃圾回收。

进入执行阶段,执行期上下文初始化作用域链scopeChain。

say.ExecutionContext = { AO : { words : undefined, arguments : [...] }, scopeChain : say.[[scope]].concat(this.AO) }

say.ExecutionContext = { AO : { words : undefined, arguments : [...] }, scopeChain : [ say.ExecutionContext.AO, // 创建自身活动对象 GlobalExecutionContext.VO ] }

遵循 scopeChain = (AO | VO) + [[Scope]] 这个规则。

闭包也遵循这个规则,在say函数执行时,内部函数hello进入创建阶段。

hello.[[scope]] = [ say.scopeChain, GlobalExecutionContext.VO ]

在执行hello()时,进入执行阶段

hello.ExecutionContext = { AO : { arguments : [...] }, scopeChain : [ hello.ExecutionContext.AO, say.scopeChain, GlobalExecutionContext.VO ] } 

参考博文:

确定this执向

this由该函数的执行环境所确定

function person = { say : function(){ console.log(this); } } person.say() // this指向person对象 var say = person.say; say() // this指向window

参考博文:

执行时

函数的内部代码执行时,由于在执行前将函数声明(function关键字)和变量声明(var关键字)全部创建到了AO中。

因此会存在一种hoist,即作用域提升的问题。

执行后

在函数的内部代码执行后,会销毁函数的执行期上下文。

与此同时AO也将被销毁,除非有引用的情况。

function say(){ var words = "hello"; hello(); // 输出:hello function hello(){ console.log(words); } return hello; // 将hello抛出 } var hello = hello.say();

此时的作用域情况如下

window.hello.[[scope]] = [ say.scopeChain : [ say.ExecutionContext.AO, GlobalExecutionContext.VO ] ]

由于 hello.[[scope]] 中留有 say.scopeChainsay.ExecutionContext.AO 的引用,所有不会被删除。

原文链接:https://yq.aliyun.com/articles/610344
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章