溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么徹底搞懂JS閉包各種坑

發(fā)布時(shí)間:2021-12-14 11:37:54 來(lái)源:億速云 閱讀:150 作者:柒染 欄目:開(kāi)發(fā)技術(shù)

本篇文章為大家展示了怎么徹底搞懂JS閉包各種坑,內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過(guò)這篇文章的詳細(xì)介紹希望你能有所收獲。

老司機(jī)帶你徹底搞懂JS閉包各種坑

閉包是js開(kāi)發(fā)慣用的技巧,什么是閉包?

閉包指的是:能夠訪問(wèn)另一個(gè)函數(shù)作用域的變量的函數(shù)。清晰的講:閉包就是一個(gè)函數(shù),這個(gè)函數(shù)能夠訪問(wèn)其他函數(shù)的作用域中的變量。eg:

function outer() {     
var  a = '變量1'     
var  inner = function () {            
console.info(a)     }   
 return inner    
 // inner 就是一個(gè)閉包函數(shù),因?yàn)樗軌蛟L問(wèn)到outer函數(shù)的作用域}

很多人會(huì)搞不懂匿名函數(shù)與閉包的關(guān)系,實(shí)際上,閉包是站在作用域的角度上來(lái)定義的,因?yàn)閕nner訪問(wèn)到outer作用域的變量,所以inner就是一個(gè)閉包函數(shù)。雖然定義很簡(jiǎn)單,但是有很多坑點(diǎn),比如this指向、變量的作用域,稍微不注意可能就造成內(nèi)存泄露。我們先把問(wèn)題拋一邊,思考一個(gè)問(wèn)題:為什么閉包函數(shù)能夠訪問(wèn)其他函數(shù)的作用域 ?

從堆棧的角度看待js函數(shù)

基本變量的值一般都是存在棧內(nèi)存中,而對(duì)象類型的變量的值存儲(chǔ)在堆內(nèi)存中,棧內(nèi)存存儲(chǔ)對(duì)應(yīng)空間地址?;镜臄?shù)據(jù)類型: Number 、Boolean、Undefined、String、Null。

var  a = 1   //a是一個(gè)基本類型var  b = {m: 20 }   //b是一個(gè)對(duì)象

對(duì)應(yīng)內(nèi)存存儲(chǔ):

當(dāng)我們執(zhí)行 b={m:30}時(shí),堆內(nèi)存就有新的對(duì)象{m:30},棧內(nèi)存的b指向新的空間地址( 指向{m:30} ),而堆內(nèi)存中原來(lái)的{m:20}就會(huì)被程序引擎垃圾回收掉,節(jié)約內(nèi)存空間。我們知道js函數(shù)也是對(duì)象,它也是在堆與棧內(nèi)存中存儲(chǔ)的,我們來(lái)看一下轉(zhuǎn)化:

var a = 1;function fn(){    
var b = 2;    
function fn1(){        
console.log(b);
    }    
   fn1();
  }fn();

**

棧是一種先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu):

1 在執(zhí)行fn前,此時(shí)我們?cè)谌謭?zhí)行環(huán)境(瀏覽器就是window作用域),全局作用域里有個(gè)變量a;

2 進(jìn)入fn,此時(shí)棧內(nèi)存就會(huì)push一個(gè)fn的執(zhí)行環(huán)境,這個(gè)環(huán)境里有變量b和函數(shù)對(duì)象fn1,這里可以訪問(wèn)自身執(zhí)行環(huán)境和全局執(zhí)行環(huán)境所定義的變量

3 進(jìn)入fn1,此時(shí)棧內(nèi)存就會(huì)push 一個(gè)fn1的執(zhí)行環(huán)境,這里面沒(méi)有定義其他變量,但是我們可以訪問(wèn)到fn和全局執(zhí)行環(huán)境里面的變量,因?yàn)槌绦蛟谠L問(wèn)變量時(shí),是向底層棧一個(gè)個(gè)找,如果找到全局執(zhí)行環(huán)境里都沒(méi)有對(duì)應(yīng)變量,則程序拋出underfined的錯(cuò)誤。

4 隨著fn1()執(zhí)行完畢,fn1的執(zhí)行環(huán)境被杯銷毀,接著執(zhí)行完fn(),fn的執(zhí)行環(huán)境也會(huì)被銷毀,只剩全局的執(zhí)行環(huán)境下,現(xiàn)在沒(méi)有b變量,和fn1函數(shù)對(duì)象了,只有a 和 fn(函數(shù)聲明作用域是window下)


在函數(shù)內(nèi)訪問(wèn)某個(gè)變量是根據(jù)函數(shù)作用域鏈來(lái)判斷變量是否存在的,而函數(shù)作用域鏈?zhǔn)浅绦蚋鶕?jù)函數(shù)所在的執(zhí)行環(huán)境棧來(lái)初始化的,所以上面的例子,我們?cè)趂n1里面打印變量b,根據(jù)fn1的作用域鏈的找到對(duì)應(yīng)fn執(zhí)行環(huán)境下的變量b。所以當(dāng)程序在調(diào)用某個(gè)函數(shù)時(shí),做了一下的工作:準(zhǔn)備執(zhí)行環(huán)境,初始函數(shù)作用域鏈和arguments參數(shù)對(duì)象

我們現(xiàn)在看回最初的例子outer與inner

function outer() {     var  a = '變量1'     var  inner = function () {            console.info(a)     }    return inner    // inner 就是一個(gè)閉包函數(shù),因?yàn)樗軌蛟L問(wèn)到outer函數(shù)的作用域}var  inner = outer()   // 獲得inner閉包函數(shù)inner()   //"變量1"

當(dāng)程序執(zhí)行完var inner = outer(),其實(shí)outer的執(zhí)行環(huán)境并沒(méi)有被銷毀,因?yàn)樗锩娴淖兞縜仍然被被inner的函數(shù)作用域鏈所引用,當(dāng)程序執(zhí)行完inner(), 這時(shí)候,inner和outer的執(zhí)行環(huán)境才會(huì)被銷毀調(diào);《JavaScript高級(jí)編程》書中建議:由于閉包會(huì)攜帶包含它的函數(shù)的作用域,因?yàn)闀?huì)比其他函數(shù)占用更多內(nèi)容,過(guò)度使用閉包,會(huì)導(dǎo)致內(nèi)存占用過(guò)多。

現(xiàn)在我們明白了閉包,已經(jīng)對(duì)應(yīng)的作用域與作用域鏈,回歸主題:

坑點(diǎn)1: 引用的變量可能發(fā)生變化

function outer() {      
var result = [];      
for (var i = 0; i<10; i++){        
result.[i] = function () {            
console.info(i)       
 }    
  }     
  return result}

看樣子result每個(gè)閉包函數(shù)對(duì)打印對(duì)應(yīng)數(shù)字,1,2,3,4,...,10, 實(shí)際不是,因?yàn)槊總€(gè)閉包函數(shù)訪問(wèn)變量i是outer執(zhí)行環(huán)境下的變量i,隨著循環(huán)的結(jié)束,i已經(jīng)變成10了,所以執(zhí)行每個(gè)閉包函數(shù),結(jié)果打印10, 10, ..., 10

怎么解決這個(gè)問(wèn)題呢?

function outer() {      
var result = [];      
for (var i = 0; i<10; i++){        
result.[i] = function (num) {             
return function() {                   
console.info(num);    
// 此時(shí)訪問(wèn)的num,是上層函數(shù)執(zhí)行環(huán)境的num,數(shù)組有10個(gè)函數(shù)對(duì)象,每個(gè)對(duì)象的執(zhí)行環(huán)境下的number都不一樣             }        }(i)     }     return result}

坑點(diǎn)2: this指向問(wèn)題

var object = {     
name: ''object",     
getName: function() {        
return function() {             
console.info(this.name) 
       }    
  }}object.getName()()   
   // underfined// 
   因?yàn)槔锩娴拈]包函數(shù)是在window作用域下執(zhí)行的,也就是說(shuō),this指向window

坑點(diǎn)3:內(nèi)存泄露問(wèn)題

function  showId() {    
var el = document.getElementById("app")    
el.onclick = function(){      
aler(el.id)   
// 這樣會(huì)導(dǎo)致閉包引用外層的el,當(dāng)執(zhí)行完showId后,el無(wú)法釋放   
 }}// 改成下面function  showId() {    var el = document.getElementById("app")    
 var id  = el.id    el.onclick = function(){      
 aler(id)   // 這樣會(huì)導(dǎo)致閉包引用外層的el,當(dāng)執(zhí)行完showId后,el無(wú)法釋放 
    }   
     el = null    // 主動(dòng)釋放el}

技巧1: 用閉包解決遞歸調(diào)用問(wèn)題

function  factorial(num) {   
if(num<= 1) {       return 1;   } else {      
return num * factorial(num-1)  
 }}var anotherFactorial = factorialfactorial = nullanotherFactorial(4)   
 // 報(bào)錯(cuò) ,因?yàn)樽詈檬莚eturn num* arguments.callee(num-1),arguments.callee指向當(dāng)前執(zhí)行函數(shù),但是在嚴(yán)格模式下不能使用該屬性也會(huì)報(bào)錯(cuò),所以借助閉包來(lái)實(shí)現(xiàn)
 // 使用閉包實(shí)現(xiàn)遞歸function newFactorial = (function f(num){   
  if(num<1) {return 1}    
  else {       
  return num* f(num-1)  
    }}) 
 //這樣就沒(méi)有問(wèn)題了,實(shí)際上起作用的是閉包函數(shù)f,而不是外面的函數(shù)newFactorial

技巧2:用閉包模仿塊級(jí)作用域**

es6沒(méi)出來(lái)之前,用var定義變量存在變量提升問(wèn)題,eg:

for(var i=0; i<10; i++){    
console.info(i)}alert(i)  // 變量提升,彈出10
//為了避免i的提升可以這樣做(function () {    
for(var i=0; i<10; i++){        
 console.info(i)   
  }})()alert(i)   
  // underfined   
  因?yàn)閕隨著閉包函數(shù)的退出,執(zhí)行環(huán)境銷毀,變量回收

上述內(nèi)容就是怎么徹底搞懂JS閉包各種坑,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

js
AI