作用域精解
-
[[scope]]:每个javascript函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供javascript引擎存取,[[scope]]就是其中一个(隐式属性)。
[[scope]]指的就是我们所说的作用域,其中存储了运行期/执行期上下文的集合。 -
作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。
-
运行期/执行期上下文:在函数执行的前一刻,会创建一个称为执行期上下文的内部对象(AO对象)。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被销毁。
-
查找变量:在哪个函数里查找变量 就从这个函数的作用域链的顶端依此向下查找。
例:
<script>
function a(){
function b(){
var b = 234;
}
var a = 123;
b();
}
var glob = 100;
a();
</script>
第一步:a函数定义
a函数被定义时,会生成自己的一个[[scope]]作用域(属性),里面存放执行期上下文对象的集合(即scope chain<作用域链>),a函数被定义时的环境是全局,故此时其作用域链中只在0号位存放了全局的执行期上下文GO对象(Global Object)。
第二步:a函数执行
a函数执行时,a函数会产生一个独一无二的执行期上下文AO对象(Activation Object),放在其作用域链的最顶端0号位,而GO对象则会变为1号位。当要在a函数里查找变量时,从a函数的作用域链的顶端(0号位)依此向下查找。
第三步:a函数的执行导致了b函数的定义(只有a函数执行才能导致b函数被定义 如果a函数没有执行 下面的都不会发生)
b函数被定义时,也会产生自己的一个[[scope]]作用域,里面存放自己的执行期上下文对象的集合(即作用域链),b被定义时的环境是a函数给的,故此时其作用域链中存放的是此时a函数的作用域链中的对象,0号位是a的AO对象,1号位GO对象。
第四步:b函数执行
b函数执行时,b函数也会产生一个独一无二的执行器上下文AO对象,放在其作用域链的最顶端(0号位),0号位b的AO对象,1号位a的AO对象,2号位GO对象。当要在b函数里查找变量时,从b函数的作用域链的顶端(0号位)依此向下查找。
问题1:b函数作用域链中的a的AO对象是一个独立的个体(和a的AO对象不是同一个)还是一个引用(和a的AO对象是同一个)?
<script>
function a(){
function b(){
var bb = 234;
a = 0;
}
var a = 123;
b();
console.log(a);//若输出为123 则说明a产生的AO对象和b访问的a的AO对象不是同一个 若输出为0 则是同一个
}
var glob = 100;
a();
</script>
b函数作用域链中的a的AO对象是a的AO对象的一个引用,指向同一个对象。
问题2:函数执行完后的销毁过程
第一步:当b函数执行完后,会将b的执行期上下文销毁(0号位不再指向其AO对象,不再指向任何对象,而不是销毁AO对象 <AO对象还存在>),回归其被定义的状态,等待下次被执行,b又会创建其新的独一无二的AO对象(不再是原来的那个)。
第二步:在该程序中,b函数执行完后,a函数也执行完成,会将a的执行期上下文销毁(0号位不再指向其AO对象,不再指向任何对象,而不是销毁AO对象),但由于a的执行期上下文中存在变量b函数,即第一步中等待被执行的b函数也随之被销毁。a函数回归其被定义的状态,等待下次被执行,a又会创建其新的独一无二的AO对象,AO对象中又会存在一个全新的变量b函数,该b函数的作用域链中存放的是新的a的AO对象和全局的GO对象。
例:
<script>
function a(){
function b(){
function c(){
}
c();
}
b();
}
a();
</script>
过程:(所有的aAO对象都是同一个 所有bAO对象也是同一个,所用从AO对象还是同一个 GO对象更加是同一个 相当于相互借用)
a defined a.[[scope]] – >0:GO
a doing a.[[scope]] – >0:aAO (只有a函数的执行 才会有下面的过程)
1:GO
b:defined b.[[scope]] -->0:aAO
1:GO
b:doing b.[[scope]] -->0:bAO
1:aAO
2:GO
c:defined c.[[scope]] -->0:bAO
1:aAO
2:GO
c:doing c:[[scope]] -->0:cAO
1:bAO
2:aAO
3:GO
c:finish->c:redong c:[[scope]]–>0:newcAO
1:bAO
2:aAO
3:GO
definded定义 doing执行 finish完成 redoing再次执行
例:
<script>
function a(){
function b(){
var bbb = 234;
document.write(aaa);
}
var aaa = 124;
return b;
}
var glob = 100;
var demo = a();
demo();
</script>
有些过程省略。。。
a函数执行 产生其执行期上下文aAO对象+GO对象存放在其作用域链中
a函数的执行导致b函数的定义 但b函数没有执行 不会产生其执行期上下文 只有b定义时a函数给的环境
a函数执行完 会销毁自己的执行期上下文aAO对象(0号位不再指向其AO对象,不再指向任何对象,而不是AO对象不在了) b函数本应跟随aAO对象的销毁而销毁 但在a函数的最后有个return b 故b函数被保存到外部
b函数带着原有的[[scope]]出来,在外部执行时也会生成一个执行期上下文bAO对象,放在其作用链域的最顶端(0号位)。在b函数的执行过程中,查找aaa变量,从b函数的作用域链的顶端(0号位)依此向下查找,(0号位)bAO中没有aaa变量,(1号位)aAO中有aaa=124,故会输出124。
闭包
当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄漏(内存越来越少)。
闭包的作用
- 实现共有变量
函数累加器
/*函数累加器*/
<script>
function add(){
var count = 0;
function demo(){
console.log(++count);
}
return demo;
}
var counter = add();
counter();
counter();
counter();
counter();
</script>
- 可以做缓存(存储结构)
<script>
/*可以做缓存(存储结构)*/
function eater(){
var food="";
var obj = {
eat : function(){
console.log("i like "+food);
food = "";
},
push : function(myFood){
food = myFood;
}
}
return obj;
}
var eater1 = eater();
eater1.push('banana');
eater1.eat();
</script>
- 可以实现封装,属性私有化
function LS(name,wife){
var prepareWife = "xiaoliu";
this.name = name;
this.wife = wife;
this.divorce = function(){
this.wife = prepareWife;
}
this.changePrepareWife = function(target){
prepareWife = target;
}
this.sayPreparewife = function(){
console.log(prepareWife);
}
}
var ls = new LS('ls',"dada");
ls对象的sayPreparewife()方法被保存到外部来时所形成的闭包中包含prepareWife属性,故可以输出该属性;而直接访问prepareWife属性是未被定义的,因为该属性在调用完构造方法、创建完LS对象后立即被销毁。
- 模块化开发,防止污染全局变量
两个自调函数各返回一个功能方法,各自会形成返回的方法函数的闭包,闭包中存储着该方法函数的局部变量,功能执行完毕,局部变量立即销毁,不会和全局变量产生冲突,各个功能方法之间即使变量名相同也不会冲突
<script>
var name="abc";
var init=(function (){
var name="yin";
function callName(){
console.log(name)
}
return function(){
callName();
}
}())
var init_=(function(){
var name=123;
function callName(){
console.log(name);
}
return function(){
callName();
}
}())
</script>
例题:
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
<li>d</li>
</ul>
使用原生js给每个li元素绑定一个click事件 输出他们的顺序
<script type="text/javascript">
var liCollection=document.getElementsByTagName("li");
for (var i=0;i<liCollection.length;i++){
liCollection[i].onclick=function (){
console.log(this.innerHTML);
console.log(i);//都是4
}
}
</script>
这个写法是错误的!这道题考查到了闭包的知识点 这样写 点击每个li 输出是
应用JS的立即执行函数(立即执行函数详解请参考JS立即执行函数)
<script type="text/javascript">
var liCollection=document.getElementsByTagName("li");
for (var i=0;i<liCollection.length;i++){
(function(j){
liCollection[j].onclick=function (){
console.log(this.innerHTML);
console.log(j);//都是4
}
}(i))
}
</script>