闭包
面试经常问到
- 有用过闭包吗?
- 什么情况下使用?
- 项目中哪些地方使用到闭包?
- 有可能造成什么问题?怎么解决?
我在另一篇文章也总结过闭包的一些应用。
变量的作用域
变量的作用域,就是指变量的有效范围。我们最常谈到的是在函数中声明的变量作用域
当在函数中搜索 一个变量的时候,如果该函数内并没有声明这个变量,那么此次搜索的过程会随着代码执行环境 创建的作用域链往外层逐层搜索,一直搜索到全局对象为止。变量的搜索是从内到外。
变量的生命周期
对于全局变量来说,全局变量的生存周期当然是永久的,除非我们主动销毁这个全局变量。 而对于在函数内用 var 关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了 它们的价值,它们都会随着函数调用的结束而被销毁。
闭包
应用场景:
- 封装全局变量 计数功能
- 延续局部变量的生命周期上报功能
- 其实还很多很多,就不一一列举了。
var Type = {};
for ( var i = 0, type; type = [ 'String', 'Array', 'Number' ][ i++ ]; ){
(function( type ){
Type[ 'is' + type ] = function( obj ){
return Object.prototype.toString.call( obj ) === '[object '+ type +']';
}
})( type )
};
Type.isArray( [] ); // 输出:true
Type.isString( "str" ); // 输出:true
封装变量
// 1. 未封装
// 计算参数乘积
let multi = function() {
var a = 1
for(let i = 0, l = arguments.length; i < l; i++) {
a = a * arguments[i]
}
return a
}
// 2. 加入缓存机制
var cache = {}
let multi = function() {
let args = Array.prototype.join.call(arguments, ',')
console.log(args)
console.log(cache)
if(cache[args]) {
return cache[args]
}
var a = 1
for(let i = 0, l = arguments.length; i < l; i++) {
a = a * arguments[i]
}
return cache[args] = a
}
// 3. 闭包
// cache在全局使用,有可能会被无意修改到引发错误。闭包后也可以减少全局变量。
let multi = (function() {
let cache = {}
return function() {
let args = Array.prototype.join.call(arguments, ',')
console.log(args)
console.log(cache)
if(cache[args]) {
return cache[args]
}
var a = 1
for(let i = 0, l = arguments.length; i < l; i++) {
a = a * arguments[i]
}
return cache[args] = a
}
})()
// 4. 提炼函数
let multi = (function() {
let cache = {}
let calculate = function() {
var a = 1
for(let i = 0, l = arguments.length; i < l; i++) {
a = a * arguments[i]
}
return a
}
return function() {
let args = Array.prototype.join.call(arguments, ',')
console.log(args)
console.log(cache)
if(cache[args]) {
return cache[args]
}
return cache[args] = calculate.apply(null, arguments)
}
})()
console.log(multi(1, 2, 3))
console.log(multi(1, 2, 3))
延续局部变量的寿命
数据上报
var report = function( src ){
var img = new Image();
img.src = src;
};
report( 'http://xxx.com/getUserInfo' );
丢失数据的原因是 img 是 report 函数中的局部变量,当 report 函数的 调用结束后,img 局部变量随即被销毁,而此时或许还没来得及发出 HTTP 请求,所以此次请求 就会丢失掉。
闭包和面向对象设计
通常用面向对象思想能实现的功能,用闭包也能实现。反之亦然
// 闭包写法
var extent = function(){
var value = 0;
return {
call: function(){
value++;
console.log( value );
}
}
};
var extent = extent();
extent.call(); // 输出:1
extent.call(); // 输出:2
extent.call(); // 输出:3
其他写法
// 对象写法
var extent = {
value: 0,
call: function(){
this.value++;
console.log( this.value );
}
};
extent.call(); // 输出:1
extent.call(); // 输出:2
extent.call(); // 输出:3
// 构造函数写法
var Extent = function(){
this.value = 0;
};
Extent.prototype.call = function(){
this.value++;
console.log( this.value );
};
var extent = new Extent();
extent.call();
extent.call();
extent.call();
这里写了三种写法,方便对该实现有个整体的认识。
闭包与内存管理
使用闭包的一部分原因是我们选择主动把一些变量封闭在闭包中,因为可能在以后还需要 使用这些变量,把这些变量放在闭包中和放在全局作用域,对内存方面的影响是一致的,这里并 不能说成是内存泄露
跟闭包和内存泄露有关系的地方是,使用闭包的同时比较容易形成循环引用,如果闭包的作用域链中保存着一些 DOM 节点,这时候就有可能造成内存泄露
在 IE 浏览器中,由于 BOM 和 DOM 中的对象是使用 C++以 COM 对象 的方式实现的,而 COM 对象的垃圾收集机制采用的是引用计数策略。在基于引用计数策略的垃圾回收机制中,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收,但循环引用 造成的内存泄露在本质上也不是闭包造成的
如果要解决循环引用带来的内存泄露问题,我们只需要把循环引用中的变量设为 null 即可。将变量设置为 null 意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运 行时,就会删除这些值并回收它们占用的内存