闭包

本文最后更新于:1 年前

闭包

当内部函数被返回到外部并保持时,一定会产生闭包,闭包会产生原来的作用域链不释放,
过度的闭包可能会导致内存泄漏或加载过慢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function test1() {
function test2() {
var b = 2
console.log(a) //1
}
var a = 1
return test2 //test1执行结束,test1自己的AO销毁,返回test2,
//当test1赋值到全局的test3时,test2被挂载到全局GO
}
var c = 3
var test3 = test1() //此处返回的是函数test2。
//test2中包含test1的AO,可以访问到test1内部的变量
//test3() //test3执行结束时,test2也结束,test2的AO销毁,但是test1的AO未断开,
//再执行test3仍可以操作test1的变量

什么是闭包

函数嵌套函数,内部函数就是闭包。就是能够访问其他函数内部变量的函数
闭包可以做数据缓存

  1. 封装一段代码利用闭包实现模块化功能,读取/设置一个函数内部的私有变量
  2. 让变量的值始终保持在内存中

    普通闭包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function test() {
    let n = 100
    function add() {
    n++
    console.log(n)
    }
    function reduce() {
    n--
    console.log(n)
    }
    return [add, reduce] //返回两个函数
    }
    let arr = test()
    arr[0]()
    arr[1]()
    arr[1]()
    //101
    //100
    //99
    add和reduce函数属于同级,两个的AO互不干扰,不能互相访问。但是都有上一级test的AO,可以同时访问test的变量

    对象闭包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function test() {
    let num = 0
    let compute = {
    add: function () {
    num++
    console.log(num)
    },
    minus: function () {
    num--
    console.log(num)
    },
    }
    return compute
    }
    let compute = test()
    compute.add() //1
    compute.add() //2
    compute.minus() //1

    构造函数闭包

    构造函数被实例化时,内部产生一个this,最后隐式返回this
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function Compute() {
    let num = 10
    this.add = function () {
    num++
    console.log(num)
    }
    this.minus = function () {
    num--
    console.log(num)
    }
    //return this //构造函数被实例化时,内部产生一个this,最后隐式返回this
    }
    let compute = new Compute()
    compute.add() //11
    compute.add() //12
    compute.minus() //11

    闭包中的this对象

    在闭包中使用 this 会让代码变复杂。
    如果内部函数没有使用箭头函数定义,则 this 对象会在运行时绑定到执行函数的上下文。
    如果在全局函数中调用,则 this 在非严格模式下等于 window,在严格模式下等于 undefined。
    如果作为某个对象的方法调用,则 this 等于这个对象。

匿名函数在这种情况下不会绑定到某个对象,这就意味着 this 会指向 window,除非在严格模式下 this 是 undefined。
不过,由于闭包的写法所致,这个事实有时候没有那么容易看出来。

1
2
3
4
5
6
7
8
9
10
window.identity = 'The Window'
let object = {
identity: 'My Object',
getIdentityFunc() {
return function () {
return this.identity
}
},
}
console.log(object.getIdentityFunc()()) // 'The Window'

为什么匿名函数没有使用其包含作用域(getIdentityFunc())的 this 对象呢?
每个函数在被调用时都会自动创建两个特殊变量:this 和 arguments。内部函数永远不可能直接访问外部函数的这两个变量。但是,如果把 this 保存到闭包可以访问的另一个变量中, 则是行得通的。

1
2
3
4
5
6
7
8
9
10
11
window.identity = 'The Window'
let object = {
identity: 'My Object',
getIdentityFunc() {
let that = this
return function () {
return that.identity
}
},
}
console.log(object.getIdentityFunc()()) // 'My Object'

在定义匿名函数之前,先把外部函数的 this 保存到变量 that 中。然后在定义闭包时,就可以让它访问 that,因为这是包含函数中名称没有任何冲突的一个变量。即使在外部函数返回之后,that 仍然指向 object,所以调用 object.getIdentityFunc()() 就会返回”My Object”

闭包必需了解的问题

引用的变量可能发生变化

以下代码输出什么

1
2
3
4
5
6
7
8
9
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0]();//3
data[1]();//3
data[2]();//3

为什么打印出来都是3?
循环可以改写成

1
2
3
4
5
6
7
8
// var data = []
// var i = 0
// for (; i < 3; ) {
// data[i] = function () {
// console.log(i)
// } //不是立即执行函数,i=2时就存了3个匿名函数
// }
// i++

循环结束后等价于

1
2
3
4
5
6
7
8
9
data[0] = function () {
console.log(i);
};
data[1] = function () {
console.log(i);
};
data[2] = function () {
console.log(i);
};

此时 i 的值已经为 3 ,所以当 data[0]、data[1]、data[2] 中任意一个执行时输出结果都为 3。
利用闭包解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var data = []
for (var i = 0; i < 3; i++) {
(function (i) {
data[i] = function () {
console.log(i)
}
})(i)//立即执行函数,传参传i把变量缓存
// 或者写成下面这种形式
// data[i] = (function(i) {
// return function() {
// console.log(i);
// };
// })(i);
}
data[0]() // => 0
data[1]() // => 1
data[2]() // => 2

当然更简单的方法就是使用 let 声明变量。

this指向问题

匿名函数在这种情况下不会绑定到某个对象,这就意味着 this 会指向 window,除非在严格模式下 this 是 undefined。

1
2
3
4
5
6
7
8
9
10
      var object = {
name: 'object',
getName: function () {
return function () {
console.info(this.name);
};
},
};
object.getName()(); // underfined
// 因为里面的闭包函数是在window作用域下执行的,也就是说,this指向windows

IE中内存泄露问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function  showId() {
var el = document.getElementById("app")
el.onclick = function(){ //.onclick就相当于是return,每点击一下就形成了闭包
aler(el.id) // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
}
}

// 改成下面
function showId() {
var el = document.getElementById("app")
var id = el.id
el.onclick = function(){
aler(id) // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
}
el = null // 主动释放el
}


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!