You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
function returnfunc (propertyName) {
return function (obj) { //-----这行定义并返回了一个闭包,也被称之为一个匿名函数
return obj[propertyName]; //这里用方括号法访问属性,因为属性是变量(returnfunc()函数的参数)
};
}
var savefunc = returnfunc("name"); //调用returnfunc()
var result = savefunc({name:"Picasso"});//调用savefunc()
alert(result); //返回字符串“Picasso”
执行环境
执行环境(execution context),即执行上下文。他定义了变量或函数有权访问的其他数据。
全局执行环境
在浏览器环境中,全局执行环境指window对象
函数执行环境
每个函数都有自己的执行环境。当执行流进入函数时,函数的执行环境被推入环境栈中,而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。
作用域链
作用域链的用途时保证对执行环境有权访问的所有变量和函数的有序访问。
作用域链的最前端始终都是当前执行的代码所在的环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。
闭包
闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式就是在一个函数内部创建另一个函数。
闭包作用域链
在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。
函数销毁
闭包外部函数在执行完毕后,其活动对象不会销毁,因为匿名函数的作用域仍然在引用这个活动对象。换句话说,当外部函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中,直到匿名函数被销毁后,外部函数的活动对象才会被销毁。
注意: 由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存,过度使用闭包可能会导致内存占用过多。所以慎用。
this对象
this对象是在运行时基于函数的执行环境绑定的,在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window。
内部函数this对象
内部函数在搜索this和arguments时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。
实例
此时后台会创建一个作用域链(scope chain),这个作用域链包含了全局环境的变量对象并被保存在pub()函数内部的scope属性中。但是,当我们打开浏览器的时候已经存在了一个全局的执行环境,这个全局的执行环境属于浏览器,JS里浏览器被称为window对象,我们把这个环境叫做A环境,只要没有关闭浏览器,A环境会一直存在下面会提到执行环境什么时候会被创建。我们用色块表示执行环境,链条表示作用域链,作用域链上半部分是活动对象区域,下半部分是变量对象区域,如下图:
当我们要调用pub()函数的时候,又会创建一个执行环境B,没错,执行环境如其名是在运行和执行代码的时候才存在的,所以我们运行浏览器的时候会创建全局的执行环境。这个时候根据pub()函数scope属性中的作用域链把pub()函数内的变量对象放入新的B环境中,作用域链也得到更新,如下图:
上图的虚线表示正在执行,全局变量对象此时处于作用域链的第二位,所以标号变成了1。你可能也注意到那个arguments对象,它是在函数被创建的时候就一直存在的,无需用户创建。arguments对象保存的是函数圆括号内定义的参数,准确来说保存的是参数的值,因这里我们没有设置参数,所以显示未定义。
此时我们把属于B环境的变量对象(也就是pub()函数中的所有函数和变量)叫做活动对象。
因此我们可以说变量对象包含了活动对象,活动对象就是作用域链上正在被执行和引用的变量对象。我们从活动对象的名称中也能看出 “执行、运行、激活” 等意味。你可以这样理解,整个代码的运行总有一个起始的对象吧,不管这个起始是变量还是函数,总要有一个称呼,虽然我们把执行环境中的变量和函数的总称叫做变量对象,但这不能反映代码的动态性,为了区别于普通的变量对象,我们创造了活动对象的概念。
这就完了?没有,上一段我们说到代码的动态性,所谓动态性在这里的意思是有些代码参与了运行,有些没有,就像高中的化学反应一样,总有一些化学物不会参与到整个反应中来。我们把上面的代码变成如下:
这个时候全局作用域链和执行环境如下:
接着我们调用pub()函数,执行环境和作用域链如下:
你看没有被调用的pub2()函数仍然只是闲着,甚至没有被pub()函数在内部引用。由于pub2()没有参与整个pub()函数的调用过程,所以pub2()中不存在活动对象,只有“处于静止状态”的变量对象,当然也没有创建执行环境。
到这里就完了?也没有,我们刚才也只是讨论了两个平行且毫不关联的函数其中一个被调用的状况,言下之意就是也存在函数相互影响的例子,最典型的就是闭包,闭包是一种函数嵌套的情况。定义如下代码:
以上代码的最开始的作用域链和执行环境:
我们先开始调用returnfunc()函数,马上会创建一个包含returnfunc()变量对象的行环境,作用域链开始变化,如下图:
图的白色虚线表示执行程序产生的效果,它可能表示的是返回一个结果、复制某种值、产生一个新物体、建立某种联系等。
题外话:你会发现上图的arguments参数的值和propertyName的值是一样的,这是因为arguments保存的就是参数,采用实时映射的方式与参数建立联系,如果你在returnfunc()函数内再加一个值为{name:"picasso"},名为obj的参数,那么arguments的值变成[{name:"nicholas"},{name:picasso}],是的,你没看错,arguments是一个数组!!!
随后returnfunc()函数会返回它内部的匿名函数,当匿名函数被返回后,整个作用域链和执行环境又发生了变化:
我们看到匿名函数(闭包)被添加到了最作用域链的最前端,returnfunc()的执行环境被销毁,但我们注意到returnfunc()函数的活动对象仍然在被引用(匿名函数仍在访问propertyName参数),因此returnfunc()函数的变量对象仍然在内存中,成为活动对象。这就是为什么匿名函数就能访问returnfunc()函数定义的所有变量和全局环境定义的变量,毕竟returnfunc()的活动对象仍然保持“激活”状态。
根据上面所述,随着代码一行一行的被执行、执行环境不断被创建和销毁、变量对象间的各种关系被建立,这些背后的逻辑导致活动对象也在不断变化,这足以证明活动对象只是正在被执行和引用的变量对象。
参考地址
The text was updated successfully, but these errors were encountered: