闭包就是指有权访问另一个函数作用域中变量的函数,闭包是一种特殊的对象,它有两部分构成,一部分是函数,另外一部分是创造该函数的环境。环境由闭包创建时作用域中任何局部变量所组成
创建闭包最常见的方式就是在一个函数内部定义另一个函数
"use strict";
function createCounter(initial) {
var counter = initial;
function increment(value) {
counter += value;
}
function get() {
return counter;
}
return {
increment: increment,
get: get
};
}
var myCounter = createCounter(100);
console.log(myCounter.get()); // 返回 100
myCounter.increment(5);
console.log(myCounter.get()); // 返回 105
闭包的作用域链包含了自身作用域,被定义所在函数作用域以及全局作用域
通常情况下当函数返回没有被引用的时候,就会被垃圾回收器回收。即使两个相互引用的对象而不被第三个对象所引用,那么这两个相互引用的对象也会被回收。
但是对于闭包,即使外部函数返回了,函数对象仍会引用它被创建时的作用域对象。
单独调用increment(5)时,参数value保存在当前的作用域对象。当函数要访问counter时,没有找到,于是沿着作用域链向上查找,在createCounter(100)的作用域找到了对应的标示符,increment()就会修改counter的值。除此之外,没有其他方式来修改这个变量。闭包的强大也在于此,能够存贮私有数据。
定义两个函数
var myCounter1 = createCounter(100);
var myCounter2 = createCounter(200);
关系图如下:
myCounter1.increment和myCounter2.increment的函数对象拥有着一样的代码以及一样的属性值(name,length等等),但是它们的[[scope]]指向的是不一样的作用域对象。
function test() {
var arr = []
for (var i = 0; i < 10; i++) {
arr[i] = function () { return i }
}
for (var a = 0; a < arr.length; a++) {
console.log('arr', arr[a]())
}
}
test() // 10个10
对于以上的输出并不是我们想要的,那需要我们改动一下上面的代码
function test() {
var arr = []
for (let i = 0; i < 10; i++) {
arr[i] = function () { return i }
}
for (var a = 0; a < arr.length; a++) {
console.log('arr', arr[a]())
}
}
test() // 0-10
上述问题的原因引出一个经典的面试问题
js闭包是如何处理父函数中let形成块级作用域中定义的变量的
解答:区别在于每次for循环里面都是一个单独的块极作用域,而let就是块极作用域里面声明变量,可以理解为每次执行for的时候都是一个新的i,所以在for循环的块中找,自然i不一样,可以理解为每次循环都保存了一个变量i,这个i属于对应的那次执行的块极作用域,而var是函数作用域,for循环无论执行多少次,都是去最近的堆栈里面找,而不是块中找,所以只有一个i,现在的i是10
var name = 'liqian'
var obj = {
name: 'zhansan',
getName: function () {
return function () {
return this.name
}
}
}
obj.getName()() // liqian
执行obj.getName()()实际上执行的是匿名函数,匿名函数的作用域指向了全局window,匿名函数的执行函数具有全局性
定义了过多私有变量占用大量的内存,容易造成内存泄漏
闭包会对脚本的性能造成影响
function fun(n, o) {
console.log(o)
return {
fun: function (m) {
return fun(m, n)
}
}
}
var a = fun(0) // undefiend
a.fun(1) // 0
a.fun(2) // 0
a.fun(3) // 0
var b = fun(0).fun(1).fun(2).fun(3) // undefiend 0 1 2
var c = fun(0).fun(1) // undefiend 0
c.fun(2) // 1
c.fun(3) // 1