我刚开始接触这个概念的时候,要么是被误导,要么就是自己看错,反正是误解了。这个误解就是,闭包是外部函数作用域的变量被内部函数引用了。其实这段话说的其实是js应用闭包的场景。
基本概念
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
闭包的特性就是, 内层作用域可以访问外层的, 但是外层不能访问内层的。这个就是私有变量的一种实现方式, 也就是我们说的作用域链。
然后就是函数作用域, 是函数创建时生成的 ,就是定义函数时形成的。闭包就是随之而来的词法环境,让外部作用域无法访问到内部变量。闭包这个词法环境包含了局部变量,但是外部访问不了,如果要访问就要return出来。但是,直接返回出来 ,等于是每次调用函数,都重新声明了一次变量,这是正常的。 如果用了我们在开始说的外部函数的变量被内部引用,会有什么不同。
function fn1(params) {
let obj = {num:10}
return obj
}
function fn2(){
let count =0
let obj = {num:20}
let fn = function(){
console.log(count)
return obj
}
obj.num++
count++
obj = {abs:20}
return fn
}
let n1 = fn1(), fn3 = fn2(), n2= fn3();
console.log(n1,n2);
n1.num++;
n2.num++;
console.log(fn1(), fn3())
差点把自己绕进去了。 在fn2中, 新声明的fn使用了外部的obj,虽然是在声明之后修改了obj的引用,但是,fn2中obj就是修改之后的引用。fn3能访问到fn2的词法作用域(闭包),并且还是它的最终结果。注意是最终结果, 是内部函数没有这个变量就会,去外部函数找,函数作用域链。 因为fn3内部没有声明count, 所以找到了fn2中的count,而这个count的最终值是1,所以访问的结果是1.
执行一下这段代码就会发现 再次调用fn3取得的引用的num属性已经被修改了。这是为什么呢。
首先fn2定义的时候有一个作用域 ,然后调用fn2返回fn3,这个fn3里面的obj就是在这一次调用fn2时被声明的,所以fn3不论调用多少次都是访问到声明fn3的那一次,一起声明的obj。
如果你认为,应该返回新的obj。 我们可以假设一下,如果返回新的obj,意味着fn2再次被调用, 也会返回一个新的函数, 不再是fn3了, 所以只能是 fn3还是fn3 ,obj还是obj ,反证成功。他们都在是那一次调用fn2时被声明的。这就是所谓的经典闭包吧。
这里是拿引用类型来举例的,多少有些疏漏,说好的私有变量呢?上面私有变量是obj,obj的引用没有被修改,只不过修改了下面的属性。换成原始值。
function fn1(params) {
let num = 10 ;
return num << 2;
}
function fn2(){
let num = 10 ;
return function(){
num <<2
return num
}
}
let n1 = fn1(), fn3 = fn2(), n2= fn3();
console.log(n1,n2);
n1++;
n2++;
console.log(fn1(), fn3())
小结
简单的说,当一个函数被创建时,它的内部作用域就固化了,从原型链,执行上下文的角度来说,就是这个函数内部的执行上下文已经确定了,它所有的依赖都确定了,原始值的值确定了,引用类型的引用确定了,在函数外部无法修改。
注意 通过call apply bind 等方式改变函数内部的this,实际上是返回了一个新的函数的调用,不是以前那个了。所以,也可以认为函数是不可变数据。