本文将从「 词法作用域 」、「 闭包概念及实例 」、「 闭包应用场景 」等三个方面来讲述JS中的闭包
词法作用域是指一个变量在源码中声明的位置作为它的作用域。同时嵌套的函数可以访问到其外层作用域中声明的变量。
看下面的代码:
function init() {
var name = '朱翊钧'
function displayName() { // displayName() 是一个闭包
console.log(name)
}
displayName()
}
init() // 朱翊钧
init() 函数创建了本地变量 name 和函数 displayName()。
displayName () 是定义在 init () 的内部函数,因此只能在 init () 函数内部访问。 displayName () 没有内部变量,但是由于内部函数可以访问外部函数的变量,displayName () 可以访问 init () 中的变量 name。
运行上述代码,name 的值被打印了出来。
这是JS 解析器处理嵌套函数中的变量的一个例子,也是词法作用域一处体现。
闭包就是能够读取其他函数内部变量的函数。
「 闭包 = 函数 + 内部词法作用域 」
如果返回一个函数(闭包)去引用包裹这个函数(闭包)的函数中的变量,该变量会一直在内存中。
现在来看一个关于闭包的例子:
var name = "The Window"
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name
}
}
}
console.log(object.getNameFunc()()) // 思考此处的打印结果
上面代码中在 全局 和 object 内部分别定义了 name,调用object.getNameFunc() 函数返回下面的函数:
function() { return this.name }
而上面的 object.getNameFunc()() 等同于:
(function() { return this.name })()
在JS中,当前方法属于谁,this就指向谁。上面的代码中的方法显然是属于 window,所以打印结果为 「 "The Window" 」。
下面是另一个例子:
var name = "The Window"
var object = {
name : "My Object",
getNameFunc : function(){
var that = this
return function(){
return that.name
}
}
}
console.log(object.getNameFunc()())
和上一个例子类似,只不过多了一行
var that = this // that 指向 object 的 this
此时的 object.getNameFunc()() 等同于:
(function() { return that.name })()
虽然 object.getNameFunc() 的调用已经结束了,但是 that 变量一直被其闭包引用,导致 that 也一直存在于内存中。
而 that 指向 object 的 this,所以打印结果为「 "My Object" 」。
1、「 使用闭包代替全局变量 」
全局变量有变量污染和变量安全等问题
//全局变量,test1是全局变量
var test1 = 1
function outer(){
alert(test1)
}
outer() // 1
alert(test1) // 1
//闭包,test2是局部变量,这是闭包的目的
//我们经常在小范围使用全局变量,这个时候就可以使用闭包来代替。
(function(){
var test2 = 2
function outer(){
alert(test2)
}
function test(){
alert("测试闭包:"+test2)
}
outer() // 2
test() //测试闭包:2
})()
alert(test2) //未定义,这里就访问不到test2
2、「 函数外或在其他函数中访问某一函数内部的参数 」
主要是为了解决在Ajax callback回调函数中经常需要继续使用主调函数的某一些参数。
function f1(){
let test = 1
tmp_test = function(){
return test
}
//tmp_test是全局变量,这里对test的引用,产生闭包
}
function f2(){
alert("测试一:"+tmp_test())
let test1 = tmp_test()
alert("测试二:"+test1)
}
f1()
f2()
//测试一:1
//测试二:1
alert(tmp_test()) // 1
tmp_test = null
3、「 在函数执行之前为要执行的函数提供具体参数 」
某些情况下,是无法为要执行的函数提供参数,只能在函数执行之前,提前提供参数。
例如:setTimeOut 、setInterval、Ajax callbacks、 event handler[el.οnclick=func 、 el.attachEvent("onclick",func)]
// 无法传参的情况
let parm = 2
function f1() {
alert(1)
}
function f2(obj) {
alert(obj)
}
setTimeout(f1,500) // 正确,无参数
let test1 = f2(parm) // 执行一次f2函数
setTimeout(f2,500) // undefined,传参失败
setTimeout(f2(parm),500) // 参数无效,传参失败
setTimeout(function(parm){
alert(parm)
},500) // undefined,传参失败
document.getElementById("hello").onclick=f1 // 正确
document.getElementById("hello").attachEvent("onclick",f1) // 正确
//正确做法,使用闭包
function f3(obj){
return function(){
alert(obj)
}
}
let test2 = f3(parm) // 返回f3的内部函数的引用
setTimeout(test2,500) // 正确, 2
document.getElementById("hello").onclick = test2; // 正确, 2
document.getElementById("hello").attachEvent("onclick",test2) // 正确,2