首页 >> 大全

js小记:作用域链 和 闭包

2023-12-15 大全 25 作者:考证青年

这篇文章主要重点是 闭包,只需要读懂 闭包 那节的例1、例2闭包你就懂了。

文章讲解思路:首先我们讲解下 不同语言的作用域解析方式,其中js用的是静态作用域(其实就是作用域链),然后我们讲讲作用域链,最后我们再开始正题“闭包及其应用”。

一、两种解析

变量作用域两种解析方式:

1、静态作用域(作用域链):

又称词法作用域。使用词法环境来管理。

一段代码执行前,先初始化词法环境。

下面代码若使用静态作用域:

var x = 10;
function foo(){console.log(x);
}
function bar(){var x = 20;foo();
}
bar();//10

看下面作用域链:执行bar函数时,在bar的作用域(scope)中找不到foo函数,然后再到全局作用域( scope)中找,正好全局作用域中有,然后执行foo;

执行foo时又找不到x变量,然后到全局作用域中找,发现x=10,然后打印 10

2、动态作用域:

程序运行时决定。动态作用域一般是使用动态栈来管理。

下面代码若使用动态作用域:

var x = 10;
function foo(){console.log(x);
}
function bar(){var x = 20;foo();
}
bar();//20

看下图动态栈:在执行过程中,

(1)首先var x = 10,把x:10放入栈内;

(2)之后再依次执行foo,bar函数的声明,我们再把foo,bar函数放入栈内

(3)在执行bar()时,首先执行var x = 20,我们把x:20放入栈内

(4)然后执行bar函数里面的foo函数,由于foo要打印x变量,所以我们在栈里找到最近的x值。为20,所以打印结果为20.

二、作用域链

作用域链的实质就是一个指向 变量对象/活动对象 的指针列表,它只是引用 不包含实际对象。

# 一些概念: # 作用域链:

作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其 活动对象 作为变量对象。活动对象在最开始时只包含一个变量,即 对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。

标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)。

例1:

var color = "blue";function changeColor(){if (color === "blue"){color = "red";} else {color = "blue";}
}console.log(changeColor());

在这个简单的例子中,函数 () 的作用域链包含两个对象:它自己的变量对象(其中定义着 对象)和全局环境的变量对象。可以在函数内部访问变量 color,就是因为可以在这个作用域链中找到它。

例2:

var color = "blue";function changeColor(){var anotherColor = "red";function swapColors(){var tempColor = anotherColor;anotherColor = color;color = tempColor;// 这里可以访问color、anotherColor和tempColor}// 这里可以访问color和anotherColor,但不能访问tempColorswapColors();
}// 这里只能访问color
changeColor();

以上代码共涉及3个执行环境:全局环境、() 的局部环境和 () 的局部环境。全局环境中有一个变量 color 和一个函数 ()。() 的局部环境中有一个名为 的变量和一个名为 () 的函数,但它也可以访问全局环境中的变量 color。() 的局部环境中有一个变量 ,该变量只能在这个环境中访问到。无论全局环境还是 () 的局部环境都无权访问 。然而,在 () 内部则可以访问其他两个环境中的所有变量,因为那两个环境是它的父执行环境。下图形象地展示了前面这个例子的作用域链。

这里写图片描述

上图中的矩形表示特定的执行环境。其中,内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。这些环境之间的联系是线性、有次序的。每个环境都可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个执行环境。对于这个例子中的 () 而言,其作用域链中包含3个对象:() 的变量对象、() 的变量对象和全局变量对象。() 的局部环境开始时会先在自己的变量对象中搜索变量和函数名,如果搜索不到则再搜索上一级作用域链。() 的作用域链中只包含两个对象:它自己的变量对象和全局变量对象。这也就是说,它不能访问 () 的环境。函数参数也被当作变量来对待,因此其访问规则与执行环境中的其他变量相同。

# 延长作用域链:

with

//作用域链:全局 -> aaa -> o
var o = {b:1}
function aaa(){var x = 2;with(o){var sum = b + x; //b相当于o.b}return sum;
}
console.log(aaa()); //3

try-catch

//作用域链:全局 -> catch
try{throw Error('sss');
}catch(e){console.log(e); //Error: sss
}

三、闭包

读懂例1、例2,闭包你就懂了!

闭包可以用在许多地方。它的最大用处有两个,一个是可以读取函数内部的变量(作用域链),另一个就是让这些变量的值始终保持在内存中。

怎么来理解这句话呢?请看下面的代码。

例1:

function add(){            //function add(){  //一般这么写var i = 0;             //   var i = 0;function add2(){       //   return function(){console.log(++i);  //      console.log(++i);}                      //   }return add2;           //}
}
var f = add();
f();//1
f();//2

在这段代码中,f 实际上就是函数 add2。它一共运行了两次,第一次的值是 1,第二次的值是 2。这证明了,函数 add 中的局部变量 i 一直保存在内存中,并没有在 add 调用后被自动清除。

为什么会这样呢?原因就在于add 是 add2 的父函数,而 add2 被赋给了一个全局变量 f ,这导致 add2 始终在内存中,而 add2 的存在依赖于 add,因此 add 也始终在内存中,不会在调用结束后,被垃圾回收机制回收。

注:并不是add 的所有变量都在内存里,而是 add2 所用到的 add 中的变量(或add的参数)保存在内存中。 (add2自己的变量不保存在内存中)

看下面例子:

例2:

 function add(){                   //function add(){  //一般这么写var i = 0;                   //    var i = 0; function add2(){             //    return function(){var a = 0;              //        var a = 0;console.log('a:',++a);  //        console.log('a:',++a);console.log('i:',++i);  //        console.log('i:',++i);}                            //    }return add2;                 //}}var f = add();f();//a:1  i:1f();//a:1  i:2

我们发现并不是 add 的所有变量都保存在内存中(eg: 变量a),而是 add2 所用到的 add 中的变量 i 保存在内存中。

例3

function aaa(d){var i = 0;var inout = 0;console.log('inout:',++inout);return function(){var inin = 0;console.log('i:',++i,' d:',++d,' inin:',++inin);}
}
var a = aaa(0);
a();
a();

输出:

inout: 1

i: 1 d: 1 inin: 1

i: 2 d: 2 inin: 1

使用闭包的注意点:

思考题:为什么这个输出就都是1?

 var f = function(){var i = 0;return function(){console.log(++i);}; };f()();//1f()();//1

解答:

上面很明显没实现闭包。这里的变量 f 保存的并不是 的函数, 我们需要将 的函数 赋给一个全局变量,然后分析同上。

闭包应当是一个封闭内的数据保留,然后由内向外提供一个外部调用接口。这里的问题是外部接口并不是由封闭区域提供。

可以正确工作的例子

var f = (function(){   //这里将return 的函数赋与了全局变量fvar i = 0;return function(){console.log(++i);}; 
})();
f();//1
f();//2

或(脑子抽筋了才用下面的写法)

var f = (function(){    var i= 0; return function(){      return function(){console.log(++i);   }    }})() 
f()() ;
f()() ;

四、闭包应用 1、保存变量现场

作用域链js__作用域链和闭包

有个需求,我们想绑定一堆DOM节点,然后点击任意节点都能弹出对于的i的值,告诉我们这是第几个节点。

看下面代码:我们会发现无论点击第几个节点,弹出都是nodes.的值,这时我们就要用闭包了。

<li>0li>
<li>1li>
<li>2li>
<script type="text/javascript">var aLi = document.getElementsByTagName('li');var handle = function(nodes){for(var i = 0;i < nodes.length;i++){nodes[i].onclick = function(){alert(i);}}};handle(aLi);
script>

改进:我们写个闭包来保存变量现场就ok了

写法一:

var aLi = document.getElementsByTagName('li');
var handle = function(nodes){function closer(i){return function(){alert(i);};}for(var i = 0;i < nodes.length;i++){nodes[i].onclick = closer(i);}
};
handle(aLi);

写法二:

var aLi = document.getElementsByTagName('li');
var handle = function(nodes){for(var i = 0;i < nodes.length; i++){nodes[i].onclick = (function(i){return function(){alert(i);};})(i);}
};
handle(aLi);

2、封装

封装也就是将可见的 和 不可见的分开,就像面向对象编程,有些变量方法是可以暴露给外部的(),而有些是不能暴露给外部的。

封装还有一个好处就是能避免内部变量污染外部环境

var observer = (function(){var observerList = [];return {add:function(obj){observerList.push(obj);},empty:function(){observerList:[];},getCount:function(){return observerList.length;}};
})();

上面代码:

((){})就是一个闭包,而((){})()就是闭包的对象,我们用来保存这个对象。

在外部:

最后我们再来两个检测的例子,看看你是否真的懂了~

练习1:

scope = "stone";function Func() {var scope = "sophie";function inner() {console.log(scope);}scope = "tommy";return inner;
}var ret = Func();
ret();    // ?

练习2:

scope = "stone";function Bar() {console.log(scope);
}function Func() {var scope = "sophie";return Bar;
}var ret = Func();
ret();    // ?

答案输出是·····························

············

····························

············

··········

···············

·········

·········

············

···········

·········

········

练习1:tommy

练习2:见下图,在Func中调用Bar,因为Func的作用域没找到Bar,所以到全局作用域中寻找Bar;找到后函数Bar又调用了变量scope,因为Bar作用域中没有变量scope,所以又回到全局作用域中寻找,发现scope=”stone”.

所以答案为 : stone

这里写图片描述

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了