执行环境及作用域
概念
执行环境定义了变量或函数有权访问其他数据,决定他们各自的行为。每个环境中都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在变量对象中,解析器会在处理数据时在后台使用它。
全局执行环境是最外层的执行环境,web浏览器中默认是window对象,因此所有的全局变量和对象都是作为window对象的属性创建的。环境中的代码执行完毕后,环境会被销毁,其中保存的变量和函数也会销毁。
每个函数都有自己的执行环境。当执行流进入一个函数是,函数的环境就会被推入一个环境栈,函数执行完成后再出栈。
代码在一个环境中执行的时候会创建一个作用域链,它保证了对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行代码所在环境的变量对象。如果环境是函数则将其活动对象作为变量对象。活动对象最开始只包含一个变量:arguments对象。作用域链下一个变量对象就是外部环境,再下一个就是外部的外部,一直到全局。
标识符解析就是沿着作用域链一级一级搜索标识符的过程,总是从前端开始,向后回溯。
例子
- 本例来源于知乎问答
1 | var pubvar = 1; |
全局环境中定义了变量pubvar和函数pub(),这时后台会创建一个作用域链
这个作用域链会保存在pub的scpoe属性中
- 调用pub()函数后,又会创建一个执行环境B,没错,执行环境如其名是在运行和执行代码的时候才存在的,所以我们运行浏览器的时候会创建全局的执行环境。这个时候根据pub()函数scope属性中的作用域链把pub()函数内的变量对象(也就是活动对象)放入新的B环境中,作用域链也得到更新,如下图:
- 上图的虚线表示正在执行,全局变量对象此时处于作用域链的第二位,所以标号变成了1。你可能也注意到那个arguments对象,它是在函数被创建的时候就一直存在的,无需用户创建。arguments对象保存的是函数圆括号内定义的参数,准确来说保存的是参数的值,因这里我们没有设置参数,所以显示未定义。此时我们把属于B环境的变量对象(也就是pub()函数中的所有函数和变量)叫做活动对象。因此我们可以说变量对象包含了活动对象,活动对象就是作用域链上正在被执行和引用的变量对象。我们从活动对象的名称中也能看出 “执行、运行、激活” 等意味。你可以这样理解,整个代码的运行总有一个起始的对象吧,不管这个起始是变量还是函数,总要有一个称呼,虽然我们把执行环境中的变量和函数的总称叫做变量对象,但这不能反映代码的动态性,为了区别于普通的变量对象,我们创造了活动对象的概念。
1 | var pubvar = 1; |
全局作用域链和执行环境如下:
调用pub()函数,执行环境和作用域链如下:
有被调用的pub2()函数仍然只是闲着,甚至没有被pub()函数在内部引用。由于pub2()没有参与整个pub()函数的调用过程,所以pub2()中不存在活动对象,只有“处于静止状态”的变量对象,当然也没有创建执行环境。
闭包
概念
由于作用域链的原因,内部环境可以访问外部的变量,反之则不行,因此我们使用闭包,将内部环境返回出去,将内部函数返回后复制给一个全局变量,这样就可以实现外部对内部的访问。
例子
- 本例来源于知乎问答
1 |
|
最开始的作用域链和执行环境:
调用returnfunc()函数,马上会创建一个包含returnfunc()变量对象的行环境,作用域链开始变化,如下图
图的白色虚线表示执行程序产生的效果,它可能表示的是返回一个结果、复制某种值、产生一个新物体、建立某种联系等。随后returnfunc()函数会返回它内部的匿名函数,当匿名函数被返回后,整个作用域链和执行环境又发生了变化:
我们看到匿名函数(闭包)被添加到了最作用域链的最前端,returnfunc()的执行环境被销毁,但我们注意到returnfunc()函数的活动对象仍然在被引用(匿名函数仍在访问propertyName参数),因此returnfunc()函数的变量对象仍然在内存中,成为活动对象。这就是为什么匿名函数就能访问returnfunc()函数定义的所有变量和全局环境定义的变量,毕竟returnfunc()的活动对象仍然保持“激活”状态。
闭包带来的问题
闭包只能取到包含函数中任何变量的最后一个值,因为它保存的时整个变量对象。
1 | function createFunctions() { |
这时返回函数数组,每个函数都返回10。因为调用完createFunctions后,i的值变为10,此时每个匿名函数所保存的createFunctions()的活动对象中i都为10。
1 | function createFunctions() { |
通过返回一个匿名函数,用IIFE的形式传参
闭包的作用
##模仿块级作用域
1 | (function () { |
以上代码定义并立即调用了一个匿名函数。将函数声明包裹在一对圆括号中,表示它是一个函数表达式,紧跟一个括号会立即调用这个函数
这种写法叫做IIFE(立即执行函数)
##设计模式
私有变量
在函数中定义的变量就可以当作私有变量,如果在函数内部创建一个闭包,闭包就可以通过作用域链访问这些变量,通过这种特性可以创建访问私有变量的特权方法
构造函数中定义特权方法
1 | function MyObject() { |
私有作用域中定义私有变量或函数
通过在私有作用域中低音私有变量或函数,同样也可以创立特权方法,其基本模式如下
1 | (function () { |
在本模式中,私有变量和函数是由实例共享的,由于特权方法在原型上定义,因此所有实例使用的是同一个函数
模块化
为单例创建私有变量和特权方法。所谓单例(singleton),指的就是只有一个实例的对象。
1 | var singleton = function(){ |
这个模块模式使用了一个返回对象的匿名函数。在匿名函数内部首先定义私有变量和函数,然后返回一个对象字面量作为函数的值
返回,其中只包含可以公开的属性和方法。
1 | var application = function() { |
##增强版模块化