ES5进阶-闭包
阅读 (474)一、作用域链
执行环境对象属性,在一个作用域生成,就会把对象放到这个链中,以栈的形式
//全局作用域
var a = 1;
function f(){//局部作用域f
var b = 2;
function n(){//局部作用域n
var c = 3;
console.log(a + b + c);
}
n();
}
f();
二、利用闭包突破作用域链
-
闭包的概念
在函数体中定义的内部函数,并且使用了外部函数的变量,然后把内部函数给返回,那么这个内部函数就是闭包
-
通俗理解
别人家有某个东西,你想拿到但是因为权限不够(不打死你才怪),但是你可以跟那个家里的孩子套近乎,通过他拿到。这个家就是局部作用域,外部无法访问内部变量,孩子是返回对象,对家里的东西有访问权限,借助返回对象间接访问内部变量!
-
代码示例
var a = 1; function f(){ var b = 2; function n(){ return b; } return n; } nn = f(); console.log(nn());
// 与上面的相同,只是在实现方法上存在一些细微的不同 var nn = null; var a = 1; function f(){ var b = 2; function n(){ return b; } nn = n; } f(); console.log(nn());
三、闭包的优缺点
-
优点
避免污染全局环境,这样我们就可以在函数体外使用函数体中定义的变量
-
缺点
数据长期驻留在内存中,造成内存极大浪费,在IE浏览器上,特别容易崩溃,所以使用闭包需谨慎
四、闭包的用途(模块化代码)
作用:将属性私有化,避免污染全局环境
var scope = (function(){
var secret = 100
return {
getSecret: function(){
return secret;
},
setSecret: function(value){
secret = value
}
}
})()
console.log(scope.getSecret());
scope.setSecret(10);
console.log(scope.getSecret());
五、闭包的用途(循环中找到索引)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
.box{
width: 100px;
height: 100px;
}
#box1{
background-color: red;
}
#box2{
background-color: yellow;
}
#box3{
background-color: green;
}
</style>
</head>
<body>
<div id="box1" class="box">0</div>
<div id="box2" class="box">1</div>
<div id="box3" class="box">2</div>
<script type="text/javascript">
var jsBoxs = document.getElementsByClassName("box");
for (var i = 0; i < jsBoxs.length; i++){
jsBoxs[i].onclick = function(){
alert(i);
}
}
</script>
</body>
</html>
原因:每个DIV添加一个点击事件,每个事件函数中都引用了一个共同的变量i,它们所拥有的只是相关域在创建时的一个引用,此时变量i恰巧存在于定义这三个事件函数的域中。对这三个事件函数中任何一个而言,当它要去获取某个变量时,它会从其所在的域开始逐级寻找那个距离最近的i值,由于循环结束时i的值为3,所以这三个函数都指向了一个共同值
使用闭包解决问题:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
.box{
width: 100px;
height: 100px;
}
#box1{
background-color: red;
}
#box2{
background-color: yellow;
}
#box3{
background-color: green;
}
</style>
</head>
<body>
<div id="box1" class="box">0</div>
<div id="box2" class="box">1</div>
<div id="box3" class="box">2</div>
<script type="text/javascript">
var jsBoxs = document.getElementsByClassName("box");
for (var i = 0; i < jsBoxs.length; i++){
jsBoxs[i].onclick = (function(index){
function inner(){
alert(index);
}
return inner;
})(i)
}
</script>
</body>
</html>
另一种写法
var jsBoxs = document.getElementsByClassName("box");
for (var i = 0; i < jsBoxs.length; i++){
(function(index){
jsBoxs[index].onclick = function(){
alert(index);
}
})(i)
}
推荐使用:给对象额外添加属性保存需要的信息
var jsBoxs = document.getElementsByClassName("box");
for (var i = 0; i < jsBoxs.length; i++){
// 给标签添加一个额外的属性
jsBoxs[i].index = i;
jsBoxs[i].onclick = function(){
alert(this.index);
}
}
六、闭包的用途(生成器)
使用数据长期驻留在内存中这个特性可以实现生成器机制
生成器:受到内存限制,数组容量肯定是有限的。而且,创建一个包含100万个元素的数组,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果数组元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的数组,从而节省大量的空间。这种一边循环一边计算的机制,称为生成器机制
使用场景:都知道如何用循环遍历一个简单的数组,但是有时候我们需要面对更为复杂的数据结构,它们通常会有着与数组截然不同的序列规则。这时候就需要将一些“谁是下一个”的复杂逻辑封装成易于使用的next()函数。然后,我们只需要简单地调用next()函数就能实现相关的遍历操作
需求:从1开始无限制打印数字的平方值
function numSquare(){
var i = 1;
function inner(){
num = i++
return num * num;
}
return inner;
}
var next = numSquare();
console.log(next());
console.log(next());
console.log(next());
console.log(next());