JavaScript中的闭包及其应用场景

本文将从「 词法作用域 」「 闭包概念及实例 」「 闭包应用场景 」等三个方面来讲述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

 

 

你可能感兴趣的:(Javascript,闭包,JS,Javascript,JS,闭包,JS语法,词法作用域)