闭包是Js的一个难点,也是它的一个特色,很多高级应用都要靠闭包来实现。
1.变量的作用域
要理解闭包,首先必须要理解Js特殊的变量作用域。
变量的作用域无非只有2种,全局变量
和局部变量
JavaScript语言的特殊之处还在于,函数内部可以直接读取全局变量。
// 函数内部可以直接读取全局变量
var n=100
function f1(){
console.log(n)
}
f1() // 100
上面代码中,函数f1的内部,是可以直接读取全局变量n的。
另一方面,函数外部自然不能读取函数内部的局部变量。
// 函数外部不可以读取函数内部变量
function f1(){
var n=100
}
f1()
console.log(n) // Uncaught ReferenceError: n is not defined
如上面代码,在函数f1外部去尝试打印n的时候,就报错。
那么如何从函数外部去读取函数内部的变量呢?
出于种种原因,有时候我们需要从函数外部去读取函数内部的变量。 但是前面说过了,正常情况下,这是办不到的,那么就要使用变通的方法。
那就是在函数内部,再定义一个函数,并将其作为返回值
// 函数作为返回值
function f1(){
var n=100
return function bar(x){
if(x>n){
console.log(n)
}
}
}
var f=f1()
f(102) // 100
上面的代码中,我就打印出了函数内部变量n
2.闭包的概念
上面代码中的bar函数,就是闭包。
闭包实际上就是指闭包函数,它是一个函数。
闭包实际上就是
能够读取其它函数内部变量的函数
切记,闭包是一个函数
3.闭包的用途
闭包可以用在很多方面。它最大的用处有2个。
读取其它函数内部变量
和 让这些变量始终保持在内存中
请看下面的代码
// 函数作为返回值
function f1(){
var n=100
nAdd=function(){
n++
}
return function bar(){
console.log(n)
}
}
var f=f1()
f() // 100
nAdd()
f() // 101
上面代码中,f实际上就是闭包函数bar
它一共运行了2次,第一次的结果是100,第二次的结果是101。
这证明了函数f1中的变量一直保存在内存中。并没有在f1的调用结束后被清除
。
为什么会这样呢? 原因就在于,f1是bar的父函数,而子函数bar被赋值给了一个全新的全局变量f
,而且bar的存在依赖于f1,因此f1
也始终存在于内存中,不会因为f1的调用完成而被垃圾回收机制销毁
。
这段代码另一个值得注意的地方就在于nAdd=function(){n++}
这段,首先,nAdd
前面没有使用var
关键字,、因此nAdd是一个全局变量,而不是局部变量。 你看nAdd里面也能读到其它函数内部的变量n
,因此nAdd也是一个闭包函数。
所以nAdd相当于一个setter,可以在函数外部对函数内部的变量进行操控。
4.使用闭包注意点
-
由于使用闭包的使用会使得函数中定义的变量都保存在内存中,内存消耗很大,所以不能滥用闭包。否则会造成网页性能问题。
解决办法,是在退出函数之前,将不使用的局部变量全部删除。
- 闭包会在父函数外部,改变父函数内部变量的值。 所以如果你把父函数当做对象(object)来使用,把闭包当作它的共有方法来使用,把内部变量当作它的私有属性来使用,这时一定要小心,不要随便改变父函数内部的值。
5.例子
// 函数作为返回值
var name="The Window"
var object={
name:"My object",
getNameFunc:function(){
console.log(this) // {name:"My object",getNameFunc:f}
return function(){
console.log(this) // Window
return this.name
}
}
}
console.log(object.getNameFunc()()) // The Window
上面代码中,this作为对象object的一个属性被调用时,指向是object这个对象,但是再return一个函数的时候,this的指向就变成了全局window
函数的作用域是创建的时候确定的,不是在调用的时候,最内层那个函数创建的时候就是全局。 所以this指的是全局window
6. 请看下面这段代码
// 函数作为返回值
function a(){
var i=0;
return b=()=>{
console.log(++i)
}
}
var c=a()
c() // 1
上面代码有2个特点:
1.函数b嵌套在函数a内
2.函数a返回函数b
引用关系如图
这样在执行完 var c=a()后,变量c实际上是指向了函数b,再执行c()就会打印i的值。
上面的代码实际上就创建了一个闭包。
因为函数a外的变量c引用了函数a内部的函数b
就是说,
当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包