闭包和高级函数
闭包(closure)
两个前置知识
-
变量的作用域
- 函数中声明一个变量,如果没有加 var 之类的声明关键字,这个变量就会成为全局变量,造成冲突,在 strict 模式下报错。
- var 在函数中声明的变量是局部变量,只有在该函数内部才能访问到这个变量,在函数外面是访问不到的。
var func = function () { var a = 1; console.log(a); // 1 }; func(); console.log(a); // undefined- const、let 新增了块级作用域概念,即 {} 内部声明的变量在其内部才能访问,在 {} 外部是访问不到的。
-
变量的生存周期
- 全局变量的生存周期是永久的,除非我们主动销毁。
- var 关键字生命的局部变量,当退出函数时,局部变量也会随着函数调用结束而被销毁。
// 退出函数后,局部变量a将被销毁 let func = function () { let a = 1; console.log(a); }; func(); /** * 与之前的相反,退出函数后,局部变量a并没有消失,这是因为,当执行 let fn = func(); 时,f返回了一个匿名函数的引用,它可以访问到func()被调用时的函数,而局部变量a一直处在这个环境中,这就产生了一个闭包结构,局部变量的声明看起来被延续了。 */ let func = function () { let a = 1; return function () { a++; console.log(a); }; }; let fn = func(); fn(); // 2 fn(); // 3 fn(); // 4
根据闭包,我们可以编写 Type 对象来封装一个类型判断工具,代码如下:
let Type = {};
for (let i = 0, type; (type = ['String', 'Array', 'Number'][i++]); ) {
(function (type) {
Type['is' + type] = function (obj) {
return Object.prototype.toString.call(obj) === '[object ' + type + ']';
};
})(type);
}
console.log(Type.isArray([])); // true
console.log(Type.isString('str')); // true闭包的作用
- 封装变量 闭包可以帮助把一些不需要暴露再全局的变量封装成“私有变量”
- 延长变量寿命
- 可以用闭包来实现面向对象
- 可以用闭包实现一些设计模式
闭包和面向对象设计
过程与数据的结合是面向对象系统中形容“对象”的常用表达。对象是以方法的形式包含了过程,而闭包则实在过程中以环境的形式包含了数据。所以我们可以用闭包来实现一个完整的面向对象系统。
闭包的写法是:
var extent = function () {
var value = 0;
return {
call: function () {
value++;
console.log(value);
},
};
};
var extent = extent();
extent.call(); // 1
extent.call(); // 2面向对象的写法是:
var extent = {
value: 0,
call: function () {
this.value++;
console.log(this.value);
},
};
extent.call(); // 1
extent.call(); // 2或者:
let Extent = function () {
this.value = 0;
};
Extent.prototype.call = function () {
this.value++;
console.log(this.value);
};
let extent = new Extent();
extent.call(); // 1
extent.call(); // 2闭包和命令模式
命令模式的意图是把请求封装成对象,从而分离请求的发起者和请求的接收者(执行者)之间的耦合关系。在命令执行之前,可以预先往命令对象中植入命令的接收者。
var Tv = {
open: function () {
console.log('打开电视机');
},
close: function () {
console.log('关闭电视机');
},
};
var createCommand = function (receiver) {
var execute = function () {
return receiver.open();
};
var undo = function () {
return receiver.close();
};
return {
execute: execute,
undo: undo,
};
};
var setCommand = function (command) {
document.getElementById('execute').onclick = function () {
command.execute();
};
document.getElementById('undo').onclick = function () {
command.undo();
};
};
setCommand(createCommand(Tv));闭包和内存管理
人们对闭包有很多误解,其中最出名的是闭包会造成内存的泄露,所以要减少闭包的使用。
局部变量本应在函数退出时被解除引用,但如果局部变量被封印在闭包形成的环境中 ,那么这个局部变量就能一直生存下去。这样来看闭包确实会造成数据无法及时被销毁。
但是使用闭包的一部分原因就是我们选择将局部变量封闭起来,以待未来使用,所以对于内存的影响和全局变量是一致的,这不能说是内存泄露。如果需要回收这部分变量,我们可以手动将其置为 null。
真正的内存泄露来自使用闭包时容易形成循环引用,如果闭包的作用域链中保存着一些 DOM 节点,就有可能造成内存泄露。但是这并非 JavaScript 或者闭包的问题,而是 IE 浏览器选择了引用计数策略的垃圾收集机制,如果产生了循环引用,那么互相引用的两个对象都无法被回收。 需要解决这个问题,我们只需要把循环引用的变量设为 null 即可。