前言
怀着一颗忐忑的心来写这篇文章,因为今天写的是闭包,关于闭包的理解可能大家都非常理解了,也怕写的不好,误导了部分的初学者,希望下面内容有任何写的不好的地方,欢迎大家积极的指出来,真正的进步就是大家共同进步。也能够让自己成长起来,这也是我写坚持写博客的原因之一。
好了,废话闲说,我们进入我们的正题:闭包
function Car () {
let wheel = 4;
function drive () {
console.log(wheel + ' wheel run');
}
return drive;
}
let drive = new Car();
drive(); // 4 wheel run
大家肯定都写过类似的代码,这段代码使用了闭包;那么到底闭包是什么?如何分析出闭包为什么产生和闭包到底在哪里?
什么是闭包?
闭包是由函数以及创建该函数的词法环境组合而成,闭包具备的几点要素
- 闭包一定是个函数对象
- 闭包和词法作用域、作用域链、垃圾回收机制息息相关
- 当函数一定是在其定义的作用域外进行访问时,才产生闭包
- 闭包是由该函数和其上层执行的上下文共同构成
为了让大家清晰的看到,我们借助chrome的调试工具看下闭包产生的过程。
当js执行到 let drive = new Car();代码,在执行car()时,此时的Call Stack只有全局上下文。
接下来执行car(); 我们可以看到,此时的drive进入了Call Stack,并且Closure(Car) 形成了。
通过调试,我们看到当函数car执行时,闭包才产生,而封闭的空间Car时闭包。
闭包是js中的一个重要的存在,它能让很多不可能实现成为可能,但是闭包虽好,但是也不能乱用,有的时候也会适得其反。
闭包导致的内存泄漏
JS分配给浏览器的可用内存数量通常比分配给桌面应用程序的少,主要是防止js的网页全部讲系统内存耗尽。我们要想让页面具备更好的性能就必须保证页面占用最少的内存,也就是说,我们应该保证执行的代码只保存有用的数据,一旦数据不再有用,我们就该进行垃圾回收,释放内存。闭包是阻止垃圾回收的,因此变量永远存在内存中。当变量不再被使用时,会造成内存泄漏,会影响页面的性能。
因此当变量对象不再使用的时候,我们应该将其释放掉。
function Car () {
let wheel = 4;
function drive () {
console.log(wheel + ' wheel run');
}
return drive;
}
let drive = new Car();
drive(); // 4 wheel run
drive = null; // 在drive不再使用,将其指向对象释放
闭包的应用方法
- 模块
var module = (function (window, undefined) {
let name = '王小端Coder';
function getName () {
return name;
}
return {
name,
getName
}
})(window);
console.log(module.name); // 王小端Coder
console.log(module.getName()); // 王小端Coder
一个模块具有私有属性、私有方法和公有属性、公有方法,而闭包能很好的将模块的公有属性、方法暴露出来。return关键字将对象引用赋值给module,从而使用的闭包。
- 延时器、计时器
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 1000 * i);
}
在上面这个例子中我们实际上想依次输出 0,1,2,3,4;而不是现在的输出5个5。下面我们进行改造。
for (var i = 0;i < 5; i++) {
(j => {
setTimeout(() => {
console.log(j);
}, 1000 * j);
})(i)
}
在setTimeout中应用了闭包,使其内部能够记住每次循环所在的词法作用域和作用域链。由于setTimeout中的回调函数会在当前任务队列的尾部进行执行,因此上面第一个例子中每次循环中的setTimeout回调函数记住的i的值是for循环作用域中的值,此时都是5,而第二个例子记住的i的数为setTimeout的父级作用域自执行函数中的j的值,依次为0,1,2,3,4。
- 数据缓存
var fn = function () {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
这个函数是返回传递参数之和,如果每次传递参数一致。结果返回都是一致的,就会造成浪费。如果改造这个函数,提高函数的性能。
var fn = (function () {
var cache = {};
return function () {
var str = JSON.stringify(arguments);
if (cache[str]) {
return cache[str];
} else {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return cache[str] = sum;
}
}
})();
上面的示例将计算后的结果缓存到局部变量cache当中,在调用这个函数时,先在缓存中查找,如果找不到,则进行计算,然后将结果放到缓存中并返回,如果找到了,直接返回查找到的值。
如有对闭包的理解不正确的地方欢迎大家指正。谢谢!