欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文剖析JavaScript中閉包的難點(diǎn)

 更新時(shí)間:2022年09月14日 11:14:07   作者:陳童學(xué)  
這篇文章主要為大家詳細(xì)介紹了JavaScript中閉包的一些難點(diǎn),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)JavaScript有一定幫助,需要的可以參考一下

一、作用域基本介紹

ES6之前只有全局作用域與函數(shù)作用域兩種,ES6出現(xiàn)之后,新增了塊級(jí)作用域。

1. 全局作用域

在JavaScript中,全局變量是掛載在window對(duì)象下的變量,所以在網(wǎng)頁(yè)中的任何位置你都可以使用并且訪問到這個(gè)全局變量。

當(dāng)我們定義很多全局變量的時(shí)候,會(huì)容易引起變量命名的沖突,所以在定義變量的時(shí)候應(yīng)該注意作用域的問題:

var globalName = 'global'
function getName() {
  console.log(globalName) // global
  var name = 'inner'
  console.log(name) // inner
}
getName()
console.log(name) // 報(bào)錯(cuò)
console.log(globalName) // global
function setName() {
  vName = 'setName'
}
setName()
console.log(vName) // setName
console.log(windwo.vName) // setName

2. 函數(shù)作用域

在JavaScript中,函數(shù)定義的變量叫作函數(shù)變量,這個(gè)時(shí)候只能在函數(shù)內(nèi)部才能訪問到它,所以它的作用域也就是函數(shù)的內(nèi)存,稱為函數(shù)作用域。

當(dāng)這個(gè)函數(shù)被執(zhí)行完之后,這個(gè)局部變量也相應(yīng)會(huì)被銷毀。所以你會(huì)看到在getName函數(shù)外面的name是訪問不到的:

function getName() {
  var name = 'inner'
  console.log(name) // inner
}
getName()
console.log(name) // 報(bào)錯(cuò)

3. 塊級(jí)作用域

ES6新增了塊級(jí)作用域,最直接的表現(xiàn)就是新增的let關(guān)鍵詞,使用let關(guān)鍵詞定義的變量只能在塊級(jí)作用域中被訪問,有"暫時(shí)性死區(qū)"的特定,也就是說這個(gè)變量在定義之前是不能被使用的。

if語句及for語句后面的{...}這里面所包括的,就是塊級(jí)作用域:

console.log(a) // a is not defined
if (true) {
  let a = '123'
  console.log(a) // 123
}
console.log(a) // a is not defined

二、什么是閉包

紅寶書:閉包是指有權(quán)訪問另外一個(gè)函數(shù)作用域中的變量的函數(shù) MDN:一個(gè)函數(shù)和對(duì)其周圍狀態(tài)的引用捆綁在一起(或者說函數(shù)被引用包圍),這樣的組合就是閉包。也就是說,閉包讓你可以在一個(gè)內(nèi)層函數(shù)中訪問到其外層函數(shù)的作用域。

1. 閉包的基本概念

閉包其實(shí)就是一個(gè)可以訪問其他函數(shù)內(nèi)部變量的函數(shù)。即一個(gè)定義在函數(shù)內(nèi)部的函數(shù),或者直接說閉包是個(gè)內(nèi)嵌函數(shù)也可以。

因?yàn)橥ǔG闆r下,函數(shù)內(nèi)部變量是無法在外部訪問的(即全局變量和局部變量的區(qū)別),因此使用閉包的作用,就具備實(shí)現(xiàn)了能在外部訪問某個(gè)函數(shù)內(nèi)部變量的功能,讓這些內(nèi)部變量的值始終可以保存在內(nèi)存中。

function fun1() {
  var a = 1
  return function () {
    console.log(a)
  }
}
fun1()
var result = fun1()
result() // 1

2. 閉包產(chǎn)生的原因

當(dāng)訪問一個(gè)變量時(shí),代碼解釋器會(huì)首先在當(dāng)前的作用域查找,如果沒找到,就去父級(jí)作用域去查找,直到找到該變量或者不存在父級(jí)作用域中,這樣的鏈路就是作用域鏈。

var a = 1
function fun1() {
  var a = 2
  function fun2() {
    var a = 3
    console.log(a) // 3
  }
}
// fun1 函數(shù)的作用域指向全局作用域(window)和它自己本身;fun2 函數(shù)的作用域指向全局作用域(window)、fun1 和它本身;而作用域是從最底層向上找,直到找到全局作用域 window 為止,如果全局還沒有的話就會(huì)報(bào)錯(cuò)

function fun1() {
  var a = 2
  function fun2() {
    console.log(a) // 2
  }
  return fun2
}
var result = fun1()
result()
// 那是不是只有返回函數(shù)才算是產(chǎn)生了閉包呢?其實(shí)也不是,回到閉包的本質(zhì),**我們只需要讓父級(jí)作用域的引用存在即可**

var fun3
function fun1() {
  var a = 2
  fun3 = function () {
    console.log(a)
  }
}
fun1()
fun3()

閉包產(chǎn)生的本質(zhì):當(dāng)前環(huán)境中存在指向父級(jí)作用域的引用。

3. 閉包的表現(xiàn)形式

返回一個(gè)函數(shù),上面將原因的時(shí)候已經(jīng)說過,這里就不在贅述了。

在定時(shí)器、事件監(jiān)聽、Ajax請(qǐng)求、Web Workers 或者任何異步中,只要使用了回調(diào)函數(shù),實(shí)際上就是在使用閉包。

// 2.1定時(shí)器
setTimeout(function handler() {
  console.log('1')
}, 1000)
// 2.2事件監(jiān)聽
$('app').click(function () {
  console.log('Event Listener')
})

作為函數(shù)參數(shù)傳遞的形式,比如下面的例子:

// 3.作為函數(shù)參數(shù)傳遞的形式
var a = 1
function foo() {
  var a = 2
  function baz() {
    console.log(a)
  }
  bar(baz)
}
function bar(fn) {
  // 這就是閉包
  fn()
}
foo() // 輸出2,而不是1

IIFE(立即執(zhí)行函數(shù)),創(chuàng)建了閉包,保存了全局作用域(window)和當(dāng)前函數(shù)的作用域,因此可以輸出全局的變量,如下所示:

// 4.IIFE(立即執(zhí)行函數(shù))
var a = 2
(function IIFE() {
  console.log(a) // 輸出2
})()

IIFE 這個(gè)函數(shù)會(huì)稍微有些特殊,算是一種自執(zhí)行匿名函數(shù),這個(gè)匿名函數(shù)擁有獨(dú)立的作用域。這不僅可以避免了外界訪問此 IIFE 中的變量,而且又不會(huì)污染全局作用域,我們經(jīng)常能在高級(jí)的 JavaScript 編程中看見此類函數(shù)。

三、如何解決循環(huán)輸出問題

for (var i = 1; i <= 5; i++) {
  setTimeout(function () {
    console.log(i)
  }, 0)
}
// 依次輸出 5個(gè)6

setTimeout 為宏任務(wù),由于 JS 中單線程 eventLoop 機(jī)制,在主線程同步任務(wù)執(zhí)行完后才去執(zhí)行宏任務(wù),因此循環(huán)結(jié)束后 setTimeout 中的回調(diào)才依次執(zhí)行。

因?yàn)?setTimeout 函數(shù)也是一種閉包,往上找它的父級(jí)作用域就是 window,變量 i 為 window 上的全局變量,開始執(zhí)行 setTimeout 之前變量 i 已經(jīng)是 6 了,因此最后輸出的連續(xù)都是 6。

1. 利用 IIFE

利用 IIFE,當(dāng)每次 for 循環(huán)時(shí),把此時(shí)的變量 i 傳遞到定時(shí)器中,然后執(zhí)行:

for (var i = 1; i <= 5; i++) {
  (function (j) {
    setTimeout(function timer() {
      console.log(j)
    }, 0)
  })(i)
}

2. 使用 ES6 中的 let

let 讓 JS 有了塊級(jí)作用域,代碼的作用域以塊級(jí)為單位進(jìn)行執(zhí)行:

for(let i = 1; i <= 5; i++) {
  setTimeout(function() {
    console.log()
  },0)
}

3. 定時(shí)器傳入第三個(gè)參數(shù)

setTimeout 作為經(jīng)常使用的定時(shí)器,它是存在第三個(gè)參數(shù)的,日常工作中我們經(jīng)常使用的一般是前兩個(gè),一個(gè)是回調(diào)函數(shù),另外一個(gè)是時(shí)間,而第三個(gè)參數(shù)用得比較少:

for(var i=1;i<=5;i++) {
  setTimeout(function(j) {
    console.log(j)
  },0,i)
}

第三個(gè)參數(shù)的傳遞,改變了 setTimeout 的執(zhí)行邏輯,從而實(shí)現(xiàn)我們想要的結(jié)果,這也是一種解決循環(huán)輸出問題的途徑。

到此這篇關(guān)于一文剖析JavaScript中閉包的難點(diǎn)的文章就介紹到這了,更多相關(guān)JavaScript閉包內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論