5. 闭包
5.1 变量作用域
之前所学的知识:
5.2 什么是闭包
<script>
// 闭包(closure)指有权访问另一个函数作用域中变量的函数。
// 闭包: 我们fun 这个函数作用域 访问了另外一个函数 fn 里面的局部变量 num
//闭包就是一个函数,fn就是一个闭包,即被访问的局部变量num所在的函数fn就是一个闭包
//可以在scope中查看用到了闭包fn中的局部变量num
function fn() {
var num = 10;
function fun() {
console.log(num);
}
fun();
}
fn();
</script>
5.3 闭包的作用
闭包实现:在fn外部的作用域可以访问fn内部的局部变量
<script>
// 闭包(closure)指有权访问另一个函数作用域中变量的函数。
// 一个作用域可以访问另外一个函数的局部变量
// 我们fn 外面的作用域可以访问fn 内部的局部变量
// 闭包的主要作用: 延伸了变量的作用范围
function fn() {
var num = 10;
function fun() {
console.log(num);
}
return fun;
}
var f = fn();
// var f = fn();类似于
// var f = function() {
// console.log(num);
// }
f(); //当指向f()的时候会自动调用fun(),此时就会产生一个闭包,这个闭包指向fn
//虽然调用的是fun但是num是位于fn中的,所以这个闭包还是fn
</script>
<script>
// 闭包(closure)指有权访问另一个函数作用域中变量的函数。
// 一个作用域可以访问另外一个函数的局部变量
// 我们fn 外面的作用域可以访问fn 内部的局部变量
// 闭包的主要作用: 延伸了变量的作用范围
function fn() {
var num = 10;
//更简单的写法 直接返回函数 闭包也是一种高阶函数
return function() {
console.log(num);
}
}
var f = fn();
f(); //10
</script>
num等待所以函数执行完毕才会销毁。综上:闭包的主要作用就是沿伸了变量的作用范围。
5.5 闭包案例
1. 闭包应用-点击li输出当前li的索引号
<body>
<ul class="nav">
<li>榴莲</li>
<li>臭豆腐</li>
<li>鲱鱼罐头</li>
<li>大猪蹄子</li>
</ul>
<script>
// 闭包应用-点击li输出当前li的索引号
// 1. 我们可以利用动态添加属性的方式
var lis = document.querySelector('.nav').querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
lis[i].index = i;
lis[i].onclick = function() {
console.log(this.index);
}
}
// 2. 利用闭包的方式得到当前小li 的索引号
for (var i = 0; i < lis.length; i++) {
// 利用for循环创建了4个立即执行函数
// 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量
(function(i) { //i:形参
lis[i].onclick = function() {
console.log(i);
}
})(i); //i:实参
}
//上述两种方式中闭包反而更麻烦,每次循环都要创建一个立即执行函数
//而且如果不进行点击事件,i就没办法销毁
</script>
</body>
端点放在console.log(i);上,点击某个li之后出现闭包,但是这个闭包没有名字,因为闭包是立即执行函数,点击事件的函数使用了立即执行函数里面的变量i,所以说立即执行函数就是一个闭包。
2. 闭包应用-3秒钟之后,打印所有li元素的内容
错误写法:
<body>
<ul class="nav">
<li>榴莲</li>
<li>臭豆腐</li>
<li>鲱鱼罐头</li>
<li>大猪蹄子</li>
</ul>
<script>
// 闭包应用-3秒钟之后,打印所有li元素的内容
var lis = document.querySelector('.nav').querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {//for循环是同步任务是立马执行
setTimeout(function() {
//错误写法
//这是一个异步任务是放到任务队列中,不是立即执行,这样写lis[i]会报错
// console.log(lis[i].innerHTML);
}, 3000)
}
</script>
</body>
异步任务主要有三种情况:定时器中的回调函数,事件中的回调函数,ajax中的回调函数(后面学)。异步任务只有被触发后才会执行
正确写法:
<script>
// 闭包应用-3秒钟之后,打印所有li元素的内容
var lis = document.querySelector('.nav').querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 3000)
})(i);
}
</script>
其实就是有4个立即执行函数,3秒之后,同时执行(打印)自己的内容
<script>
// 闭包应用-3秒钟之后,打印所有li元素的内容
var lis = document.querySelector('.nav').querySelectorAll('li');
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 3000)
})(0);
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 3000)
})(1);
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 3000)
})(2);
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 3000)
})(3);
</script>
3. 闭包应用-计算打车价格
<script>
// 闭包应用-计算打车价格
// 打车起步价13(3公里内), 之后每多一公里增加 5块钱. 用户输入公里数就可以计算打车价格
// 如果有拥堵情况,总价格多收取10块钱拥堵费
//以前的写法需要调用
// function fn() {};
// fn();
//现在的写法:立即执行函数不需要调用
var car = (function() {
var start = 13; // 起步价 局部变量
var total = 0; // 总价 局部变量
return { //return返回的是一个对象
// 正常的总价
price: function(n) {
if (n <= 3) {
total = start;
} else {
total = start + (n - 3) * 5
}
return total;
},
// 拥堵之后的费用
yd: function(flag) {
return flag ? total + 10 : total;
}
}
})();
//price()和yd()使用了立即执行函数中的局部变量start和total,所以立即执行函数就是一个闭包
//立即执行函数return的是一个对象,那么car就是一个对象,调用对象中的函数:car.price()
//car.price()也有一个返回值,就是total
console.log(car.price(5)); // 23 参数5传给了price中的n
console.log(car.yd(true)); // 33
console.log(car.price(1)); // 13
console.log(car.yd(false)); // 13
</script>
5.6 思考题
1. 思考:下面代码打印结果?是否有闭包产生?
<script>
// 思考题 1:
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
return function() {
return this.name;
};
}
};
console.log(object.getNameFunc()()); //The Window
// 先分析:object.getNameFunc(),由于这个函数有返回值,那么先用一个变量f接收
// var f = object.getNameFunc();
//由于object.getNameFunc()返回的是一个函数,就相当于给f赋值如下:
// var f = function() {
// return this.name;
// };
// 再分析object.getNameFunc()()相当于f调用接收到的这个函数
// f();
//f()等价于: (f接收到的函数)() 即
// (function() {
// return this.name;
// })();
//以上就是一个立即执行函数 立即执行函数中的this指向window
//没有访问到局部变量,所以没有闭包的产生
</script>
2. 思考:下面代码打印结果?是否有闭包产生?
<script>
// 思考题 2:
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
var that = this;
return function() {
return that.name;
};
}
};
console.log(object.getNameFunc()()) //My Object
// object.getNameFunc()
// var f = object.getNameFunc();
// var f = function() {
// return that.name;
// };
// object.getNameFunc()()
// f();
//此时产生了闭包:getNameFunc
</script>