JavaScript的做用域链仍是颇有味道的,搞懂了这个知识点,闭包的问题也就迎刃而解咯前端
一、JavaScript的全局变量和局部变量闭包
首先,先来看看js的全局变量和局部变量,js不是块级做用域,因此不能把你学过的C/C++做用域的知识用在js中!前端优化
(1)全局变量函数
js的全局变量也能够看作window对象的属性,这句话怎么理解,请看如下代码:优化
var x = 10; alert(window.x);//弹出10
也就是说var x = 10;等价于window.x=10;spa
再来看一段代码3d
function foo(){ x = 10; } foo(); alert(window.x);
这会弹出什么呢?answer is 10!code
若是在函数中定义变量时没有用关键字var,那么实际上定义的就是全局变量。你常常会看到前端优化的一个点:尽可能少定义全局变量!若是不可避免的用到全局变量,那么就在局部变量中保存。像这样:对象
function foo(){ var doc = document; var divObj = doc.getElementByTagName('div'); }
把document对象保存在局部变量doc中,而后对doc进行操做。blog
可是,问题来了,为何这样能提升效率呢?这个问题咱们先留着,等讲完做用域链再来看。
(2)局部变量
要说块级做用域,那么在js中就只有函数块,函数中定义的变量就是局部变量,固然必须有关键字var!(没有关键字var定义的都是全局变量)
也就是说if else语句和for循环中建立的变量在外部均可以访问的到
function foo(){ var x = 1; } for(var i = 0;i<10;i++){ } if(i){ var y = 10; } foo(); alert(i);//10 alert(y);//10 alert(x);//error x is not defined
二、做用域链
这是重点咯,什么是做用域链,仍是经过代码来解释
var x = 1; function foo(){ var y = 2; function bar(){ var z = 3; alert(x+y+z); } bar(); } foo();
答案是几不用说吧,在bar函数中没有y和z,执行x+y+z时,js搜索x,y,z变量的一种机制就是做用域链,这个例子的搜索顺序:bar->foo->window
前面讲的太简单,可能已经有人看不下去了,来点干货吧
bar的做用域链是:
barScopeChain = [ bar.AO, foo.AO, global.VO ];
foo的做用域链是:
fooScopeChain = [
foo.AO,
global.VO
];
可能各位看官都会迷糊,可能会问,这个AO,VO,是个什么玩意儿?咱们慢慢来,先来看看变量bar函数变量搜寻过程
例如:找x变量;bar函数在搜寻变量x的过程当中,先从自身AO对象上找,若是bar.AO存在这个属性,那么会直接使用这个属性的值,若是不存在,则会转到父级函数的AO对象,也就是foo.AO
若是找到x属性则使用,找不到继续 在global.VO对象查找,找到x的属性,返回属性值。若是在global.VO中没有找到,则会抛出异常ReferenceError。
在函数执行过程当中,每遇到一个变量,都会检索从哪里获取和存储数据,该过程从做用域链头部,也就是从活动对象开始搜索,查找同名的标识符,若是找到了就使用这个标识符对应的变量,若是没有则继续搜索做用域链中的下一个对象,若是搜索完全部对象都未找到,则认为该标识符未定义,函数执行过程当中,每一个标识符都要经历这样的搜索过程。
知道了这一点,回过头看刚开始的那个问题,为何要少定义全局变量,从而进行优化?
由于做用域链是栈的结构,全局变量在栈底,每次访问全局变量都会遍历一次栈,这样确定会影响效率。
在函数建立时,每一个函数都会建立一个活动对象Active Object(AO),全局对象为Global Object(VO),建立函数的过程也就是为这个对象添加属性的过程,做用域链就是由这些绑定了属性的活动对象构成的。
在函数执行的过程当中,会建立函数的执行上下文,这里就不作过多解释,能够去看Tom大叔的深刻理解JavaScript系列
执行上下文是一个动态的概念,当函数运行的时候建立,活动对象 Active Object 也是一个动态的概念,它是被执行上下文的做用域链引用的,能够得出结论:执行上下文和活动对象都是动态概念,而且执行上下文的做用域链是由函数做用域链初始化的。PS:这些概念都是js引擎解析代码的内部机制,外部是没法访问的!
仍是刚才那段代码,咱们来看看js引擎编译的过程,进一步了解具体是怎么建立做用域链的
函数进入全局,建立VO对象,绑定x属性<入栈>(这里只是预解析,为ao对象绑定声明的属性,函数执行时才会执行赋值语句,因此值是underfind)
global.VO = { x:underfind; foo:reference of function }
遇到foo函数,建立foo.VO,绑定y属性<入栈>
foo.AO = { y:undefined; fbar:reference of function }
接下来是bar函数,z属性<入栈>
bar.AO = {
z:undefined;
}
做用域链和执行上下文都会保存在堆栈中,因此
bar函数的scope chain为[0]bar.AO-->[1]foo.AO-->[2]global.VO
foo函数的scope chain为[0]foo.AO-->[1]global.VO
这里有一个等式:scope=AO|VO + [[scope]]
函数scope等于自身的AO对象加上父级的scope,也能够理解为一个函数的做用域等于自身活动对象加上父级做用域。
三、来看一个闭包的例子
个人本意是给每一个li标签绑定一个点击事件,点击后弹出对应的索引,单实际上每次都会弹出“这是第四个li标签”
分析:在建立foo函数时,foo.AO={liObj:undefined,i:undefined,onclick:refeerence of function}
在函数执行中,这个匿名函数自身没有i这个变量,因此会到foo的活动对象中找i变量,此时for循环已执行,变量i的值已经改变,因此老是会输出4
那怎么解决这个问题呢?也很简单
第一种解决方法:
用一个函数来保存变量i便可
第二种解决方法:
细心的人绝对会发现实际上是一种方法,都是把全局变量放在局部保存
最后一个点:变量查找时原型链是优先于做用域链的。js引擎先在函数AO对象查找,再到原型链查找,接着是做用域链。
还没写完,今天实在没什么感受,脑子很乱,仍是出去走走,透透气吧,有不对的地方还但愿好心人指出。