前置知识
变量作用域
要理解闭包,首先必须要理解JavaScript特殊的变量作用域,变量的作用域无非就是两种:全部变量和局部变量
Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。
// 全部变量
let n = 10
function fun() {
// 读取全局变量
console.log(n)
}
// 调用
fun()
另一方面,在函数外部自然无法读取函数内的局部变量
注意:函数内部声明变量的时候,如果不使用var
命令会被识别为一个全局变量
那么如何从外部读取局部变量呢?下面就引出了闭包的概念,在函数的内部再定义一个函数
闭包
引入MDN闭包概念
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
概念
闭包是由两部分组成:函数 + 一个自由访问的变量
闭包(closure):一个普通函数function
,如果它可以访问外层作用域中的自由变量,那么这个函数就是闭包。JavaScript中所有的function都是一个闭包,不过一般来说,嵌套的function所产生的的闭包更为强大,也就是大部分我们所谓的”闭包”。
闭包的主要作用:延伸了变量的作用范围 当函数中有一个局部变量时通常调用完毕之后就会销毁但是在闭包中不会销毁
换一句话来说闭包就是定义在一个函数内部的函数,本质上闭包就是将函数内部和函数外部连接起来的一座桥梁
实现闭包
外部函数所有局部变量对内部函数都是可见的,内部函数的局部变量对外部函数是不可见的,这就是JavaScript语言特有的“链式作用域”结构(chain scope),子对象会一级一级的寻找父对象的变量 (可能会有点晦涩),我们来看下面的例子
正常的情况下函数外部是不能访问函数内部的局部变量的
function b() {
var n = 888;
}
console.log(n); // n is not defined
由于种种原因需要得到函数内的局部变量。需要在函数的内部,再定义一个函数
function parent() {
let n = 777;
function child() {
console.log('拿到父函数中的局部变量:', n);
}
return child; // 返回子函数,在外部就可以调用到它的内部变量了
}
// 定义一个变量接收函数值
let res = parent();
console.log('res',res)
res(); // 拿到父函数中的局部变量: 777
// 释放对闭包的引用
res = null
这里在执行完let res = parent();
之后,变量res实际上指向的是child
函数,再执行res()
执行才会执行parent
函数,我们来看下输出结果:
注意:因为res
是闭包,最后通过null释放其对闭包的引用
这段代码创建了一个闭包的原因是因为parent
函数外的res
变量引用了其内部的函数child
,这个时候res
就作为外部和内部函数链接起来的一座桥梁,这个时候内部函数相较于外部函数就是可见的,所以再次执行res()
是作为内部函数的调用,从而输出内部函数
闭包的最大用处有两个:
- 一个是可以读取函数内部的变量
- 另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。
使用闭包的注意点:
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
加深对闭包的理解
假设打车起步价13(3公里),之后每多一公里增加5块钱,用户输入公里数就可以计算打车价格,如果拥堵的话价格多收取10元 ```javascript let car = (function () {
})(); console.log(car.price(5)); // 正常价格 total=(5-3)*5+13=23 console.log(car.yd(true)); // 拥堵价格=total + 10 = 33元let start = 13; // 起步价
let total = 0; // 总价
return {
price: function (n) {
if (n <= 3) {
total = start;
} else {
total = (n - 3) * 5 + 13
}
return total;
}, // 正常价
yd: function (flag) {
// 接收返回过来的total值
return flag ? total + 10 : total;
} // 拥堵费用
}
```