js作用域,作用域链以及闭包

时间:2021-09-13 22:45:31

js的作用域1.变量作用域:一个变量在程序源代码中定义的区域,全局变量在任何地方都有定义;2.函数作用域:作用域以一个函数为基本单位

js的作用域链:先理解执行环境,变量对象

1.执行环境也叫执行上下文。ECMAScript可执行代码的类型包括:全局代码,函数代码,eval_r()代码。每当执行流转到可执行代码时,即会进入一个执行环境。活动的执行环境构成一个栈:栈的底部始终是全局环境,顶部是当前活动的执行环境(执行环境定义了一个函数执行时的环境,函数每次执行时的执行环境都是不一样的,也可以说执行环境就是运行中的函数,当函数运行结束之后,该函数的执行环境自然就被销毁了)。

2.变量对象:变量和执行环境有密切的关系,每个执行环境都有一个与之关联的变量对象,这个对象存储着在环境中定义的一下内容(1)函数的形参(arguments)(2)var声明的变量(3)函数声明(不包括函数表达式)。变量对象在执行环境里,但执行环境不只是有变量对象(例如:执行环境={变量对象:{},。。。})。

3.不同执行环境中的变量对象的初始化是怎样的呢?(1)全局环境中的变量对象是指在进入任何执行环境之前就已经创建了的对象,这个对象只存在一份,他的属性在程序中的任何地方都可以访问,全局对象的生命周期终止于程序退出的那一刻(global={Math:String:。。。。。。window:global//引用自身})。在这里,变量对象就是全局对象自己。(2)函数环境中的变量对象。在函数执行环境中,“活动对象”扮演者变量对象这个角色(变量对象和活动对象可以理解为同一个对象)。活动对象实在进入函数执行环境是创建的,他通过函数的arguments属性初始化:活动对象={arguments://包括callee,length等属性}。

4.环境中的代码被分为两个阶段来处理:进入执行环境,执行代码。变量对象的修改变化与这两个阶段紧密相关。(1)进入环境,当进入环境时,变量对象已经包括以下属性(1.函数的所有形参,由形参名称和对应值组成,作为变量对象的属性,如果没有传递相关参数,将undefined作为对应值;2.所有函数声明,函数表达式不算,由函数名和对应值组成,作为变量对象的属性。如果变量对象已经存在同名的属性,则覆盖这个属性;3.所有变量声明,由var声明的变量,同样由变量名和对应值组成,作为变量对象的属性,如果变量名与已经声明的形参或函数相同,则变量声明不会干扰已经存在的这类属性,因为还没有执行代码,此时的对应值都是undefined)。例如:

function test(a,b){ alert(c);//undefined   alert(d); //function(){}    alert(e);//undefined  alert(x);  //出错  var c = 10; function d(){} var e = function _e(){};  (function x(){}) };test(10);

当进入带有参数10的test函数环境时(代码执行前),活动对象表现如下:

活动对象(test) = { a:10,b:undefined,c:undefined,d:指向函数d,e:undefined },此时并没有函数x,因为函数x是一个函数表达式而不是声明,函数表达式不会影响变量对象(这里是活动对象),而函数_e也是函数表达式,但是赋值给了变量e,可以用e来访问。

(2)下一个阶段是执行代码。这个阶段里,变量对象已经拥有了属性,并不是所有属性都有值(传入的形参是有值的,var声明的变量要到代码赋值的地方才会被赋值);此时的活动变量就变成了 AO(test) = {  a:10,b:undefined,c:10,d:指向函数d,e:指向函数表达式_e  }。

5.上一部分是变量对象在进入执行环境(函数运行时)所发生的初始化和赋值,接下来是作用域链(作用域链大多数与内部函数相关)。

每个环境拥有自己的变量对象:对于全局环境,他是全局对象自身;对于函数,他是活动对象。作用域链正是内部环境所有变量对象(包括父变量对象)的列表。

作用域链本质上,是一个指向变量对象的指针列表,他只引用但不实际包含变量对象(和引用类型一样,只是指向变量对象,并没有复制下来,所以变量对象改变了,他相应的也会改变)。例如:

var x = 10;function foo(){  var y = 20;function bar(){  alert(x+y);  } return bar;  }  foo()();//30

这个例子,bar执行环境中的作用域链包括:bar变量对象,foo变量对象,全局变量对象。函数执行环境中的作用域链在函数调用时创建,包含这个函数的活动对象和函数的[[ scope]]属性。

活动的执行环境 = { 变量对象:{。。。},//or 活动变量 this:thisValue,Scope:[//作用域链,他的所有变量对象的列表] },其中的Scope定义为:Scope = 被调用函数的活动对象 + [[scope]]。

6.函数的生命周期

函数的生命周期分为创建和激活两个阶段

(1)函数创建

例:var x = 10; function foo(){ var y = 20; alert(x+y); }  foo();//30

变量y在foo函数中定义,但是x并未在foo环境中定义,相应的x不会添加到foo的活动对象中。那么foo是如何访问到变量x的呢?其实是函数访问更高一层环境中的变量对象。而这种机制正是通过函数内部的[[ scope ]]属性实现的。[[ scope ]]是所有父变量对象的层级链,处于当前函数环境,在函数创建时存在于其中。

注意重要的一点:[[ scope ]]属性在函数创建时被存储,永远不变,直到函数销毁。函数可以不被调用,但这个属性一直存在。且与作用域链相比,作用域链是执行环境的一个属性,而[[ scope ]]是函数的属性。上面的例子,函数foo的[[ scope ]] = [ 全局执行环境.变量对象// == Global ]。

(2)函数激活,如上面所说,进入环境创建活动/变量对象之后,环境的Scope属性(即作用域链)定义为:Scope = 活动/变量对象 + [[ scope ]]

这个定义的意思是:活动对象是被添加到[[scope]]前端,在作用域链中处于第一位,对于标识符的查找,是从自身变量对象开始的,逐渐往父变量对象查找。


接下来是闭包:

实际就是利用作用域链来访问外部函数,其实外部函数被返回了,只要作用域链中包含外部函数的变量对象,就仍然可以访问其外部函数的内部函数



推荐:http://blog.sina.com.cn/s/blog_5d64f7e3010172mk.html

            http://wenda.so.com/q/1478019197728658?src=140