JS作用域[[scope]]+闭包

作用域精解

  1. [[scope]]:每个javascript函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供javascript引擎存取,[[scope]]就是其中一个(隐式属性)。
    [[scope]]指的就是我们所说的作用域,其中存储了运行期/执行期上下文的集合。

  2. 作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。

  3. 运行期/执行期上下文:在函数执行的前一刻,会创建一个称为执行期上下文的内部对象(AO对象)。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被销毁。

  4. 查找变量:在哪个函数里查找变量 就从这个函数的作用域链的顶端依此向下查找。
    例:

<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。

闭包

当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄漏(内存越来越少)。

闭包的作用

  1. 实现共有变量
    函数累加器
 /*函数累加器*/
   <script>
       function add(){
           var count = 0;
           function demo(){
               console.log(++count);
           }
           return demo;
       }
       var counter = add();
       counter();
       counter();
       counter();
       counter();

   </script>

在这里插入图片描述

  1. 可以做缓存(存储结构)
<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>

在这里插入图片描述

  1. 可以实现封装,属性私有化
	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对象后立即被销毁。

  1. 模块化开发,防止污染全局变量

两个自调函数各返回一个功能方法,各自会形成返回的方法函数的闭包,闭包中存储着该方法函数的局部变量,功能执行完毕,局部变量立即销毁,不会和全局变量产生冲突,各个功能方法之间即使变量名相同也不会冲突

<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>

在这里插入图片描述

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值