之前写了忘记保存。。现在重新过一遍。
一、作用域链
1.作用域
在JavaScript中,我们可以将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。标识符名称指变量名或函数名。
和执行上下文类似,作用域有全局作用域和函数作用域。eval我们平时开发中几乎不会用到它,不讨论
但作用域与执行上下文是完全不同的两个概念。JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定;执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。如图:
2.作用域链
复习一下执行上下文的生命周期:
函数在调用激活时,会开始创建对应的执行上下文,在执行上下文生成的过程中,变量对象,作用域链,以及this的值会分别被确定。之前详细说明了变量对象,现在学习作用域链。
作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。
结合例子:
var a = 20; function test() { var b = a + 10; function innerTest() { var c = 10; return b + c; } return innerTest(); } test();
innerTestEC = { VO: {...}, // 变量对象 scopeChain: [VO(innerTest), VO(test), VO(global)], // 作用域链 }
可以直接用一个数组来表示作用域链,数组的第一项scopeChain[0]为作用域链的最前端,而数组的最后一项,为作用域链的最末端,所有的最末端都为全局变量对象:
二、闭包
1.闭包的定义
闭包是一种特殊的对象。它由两部分组成:执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。当B执行时,如果访问了A中变量对象中的值,那么闭包就会产生。
在大多数理解中,包括许多著名的书籍,文章里都以函数B的名字代指这里生成的闭包。而在chrome中,则以执行上下文A的函数名代指闭包。
结合例子:
function foo() { var a = 20; var b = 30; function bar() { return a + b; } return bar; } var bar = foo(); bar();
上面的例子,首先有执行上下文foo,在foo中定义了函数bar,而通过对外返回bar的方式让bar得以执行。当bar执行时,访问了foo内部的变量a,b。因此这个时候闭包产生。
JavaScript拥有自动的垃圾回收机制,当一个值,在内存中失去引用时,垃圾回收机制会根据特殊的算法找到它,并将其回收,释放内存。
而函数的执行上下文,在执行完毕之后,生命周期结束,那么该函数的执行上下文会失去引用。其占用的内存空间很快就会被垃圾回收器释放。
可是闭包的存在,会阻止这一过程。
结合例子:
var fn = null; function foo() { var a = 2; function innnerFoo() { console.log(a); } fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn } function bar() { fn(); // 此处的保留的innerFoo的引用 } foo(); bar(); // 2
在上面的例子中,foo()
执行完毕之后,按照常理,其执行环境生命周期会结束,所占内存被垃圾收集器释放。但是通过fn = innerFoo
,函数innerFoo的引用被保留了下来,复制给了全局变量fn。这个行为,导致了foo的变量对象,也被保留了下来。于是,函数fn在函数bar内部执行时,依然可以访问这个被保留下来的变量对象。所以此刻仍然能够访问到变量a的值。这样,就可以称foo为闭包。下图展示了闭包foo的作用域链:
2.应用场景
柯里化、模块