首頁 web前端 js教程 老司機帶你徹底搞懂JS閉包各種坑

老司機帶你徹底搞懂JS閉包各種坑

Nov 25, 2019 pm 05:00 PM
js 閉包

老司機帶你徹底搞懂JS閉包各種坑

老司機帶你徹底搞懂JS閉包各種坑

##閉包是js發展慣用的技巧,什麼是閉包?

閉包指的是:能夠存取另一個函數作用域的變數的函數。清晰的講:閉包就是一個函數,這個函數能夠存取其他函數的作用域中的變數。 eg:

function outer() {
     var  a = '变量1'
     var  inner = function () {
            console.info(a)
     }
    return inner    // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}
登入後複製

很多人會搞不懂匿名函數與閉包的關係,實際上,閉包是站在作用域的角度上來定義的,因為inner訪問到outer作用域的變量,所以inner就是一個閉包函數。雖然定義很簡單,但有很多坑點,像是this指向、變數的作用域,稍微不注意可能就會造成記憶體外洩。我們先把問題拋一邊,思考一個問題:為什麼閉包函數能夠存取其他函數的作用域?

#從堆疊的角度看待js函數

基本變量的值一般都是存在棧記憶體中,而物件類型的變數的值儲存在堆疊記憶體中,棧記憶體儲存對應空間位址。基本的資料型態: Number 、Boolean、Undefined、String、Null。

var  a = 1   //a是一个基本类型
var  b = {m: 20 }   //b是一个对象
登入後複製

對應記憶體儲存:

老司機帶你徹底搞懂JS閉包各種坑

當我們執行b={m:30}時,堆記憶體就有新的物件{m:30} ,棧記憶體的b指向新的空間位址( 指向{m:30} ),而堆疊記憶體中原來的{m:20}就會被程式引擎垃圾回收掉,節省記憶體空間。我們知道js函數也是對象,它也是在堆與棧記憶體中儲存的,我們來看轉化:

var a = 1;
function fn(){
    var b = 2;
    function fn1(){
        console.log(b);
    }
    fn1();
}
fn();
登入後複製

老司機帶你徹底搞懂JS閉包各種坑

**

#棧是一種先進後出的資料結構:

1 在執行fn前,此時我們在全域執行環境(瀏覽器就是window作用域),全域作用域裡有個變數a;

2 進入fn,此時堆疊記憶體就會push一個fn的執行環境,這個環境裡有變數b和函數物件fn1,這裡可以存取自身執行環境和全域執行環境所定義的變數

3 進入fn1,此時棧內存就會push 一個fn1的執行環境,這裡面沒有定義其他變量,但是我們可以訪問到fn和全局執行環境裡面的變量,因為程序在訪問變量時,是向底層棧一個個找,如果找到全域執行環境裡都沒有對應變量,程式拋出underfined的錯誤。

4 隨著fn1()執行完畢,fn1的執行環境被杯銷毀,接著執行完fn(),fn的執行環境也會被銷毀,只剩下全局的執行環境下,現在沒有b變量,和fn1函數物件了,只有a 和fn(函數宣告作用域是window下)

**

在函數內存取某個變數是根據函數作用域鏈來判斷變數是否存在的,而函數作用域鍊是程式根據函數所在的執行環境棧來初始化的,所以上面的例子,我們在fn1裡面印出變數b,根據fn1的作用域鏈的找到對應fn執行環境下的變數b。所以當程式在呼叫某個函數時,做了一下的工作:準備執行環境,初始函數作用域鍊和arguments參數物件

我們現在看回最初的範例outer與inner

function outer() {
     var  a = '变量1'
     var  inner = function () {
            console.info(a)
     }
    return inner    // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}
var  inner = outer()   // 获得inner闭包函数
inner()   //"变量1"
登入後複製

當程式執行完var inner = outer(),其實outer的執行環境並沒有被銷毀,因為他裡面的變數a仍然被inner的函數作用域鏈所引用,當程式執行完inner(), 這時候,inner和outer的執行環境才會被銷毀調;《JavaScript高級程式設計》書中建議:由於閉包會攜帶包含它的函數的作用域,因為會比其他函數佔用更多內容,過度使用閉包,會導致記憶體佔用過多。

現在我們明白了閉包,已經對應的作用域與作用域鏈,回歸主題:

坑點1: 引用的變數可能改變

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

看起來result每個閉包函數對列印對應數字,1,2,3,4,...,10, 實際上不是,因為每個閉包函數存取變數i是outer執行環境下的變數i,隨著迴圈的結束,i已經變成10了,所以執行每個閉包函數,結果列印10, 10, ..., 10

怎麼解決這個問題呢?

function outer() {
      var result = [];
      for (var i = 0; i<10; i++){
        result.[i] = function (num) {
             return function() {
                   console.info(num);    // 此时访问的num,是上层函数执行环境的num,数组有10个函数对象,每个对象的执行环境下的number都不一样
             }
        }(i)
     }
     return result
}
登入後複製

坑點2: this指向問題

var object = {
     name: &#39;&#39;object",
     getName: function() {
        return function() {
             console.info(this.name)
        }
    }
}
object.getName()()    // underfined
// 因为里面的闭包函数是在window作用域下执行的,也就是说,this指向window
登入後複製

坑點3:記憶體外洩問題

function  showId() {
    var el = document.getElementById("app")
    el.onclick = function(){
      aler(el.id)   // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
    }
}
// 改成下面
function  showId() {
    var el = document.getElementById("app")
    var id  = el.id
    el.onclick = function(){
      aler(id)   // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
    }
    el = null    // 主动释放el
}
登入後複製

#技巧1: 用閉包解決遞歸呼叫問題

function  factorial(num) {
   if(num<= 1) {
       return 1;
   } else {
      return num * factorial(num-1)
   }
}
var anotherFactorial = factorial
factorial = null
anotherFactorial(4)   // 报错 ,因为最好是return num* arguments.callee(num-1),arguments.callee指向当前执行函数,但是在严格模式下不能使用该属性也会报错,所以借助闭包来实现
// 使用闭包实现递归
function newFactorial = (function f(num){
    if(num<1) {return 1}
    else {
       return num* f(num-1)
    }
}) //这样就没有问题了,实际上起作用的是闭包函数f,而不是外面的函数newFactorial
登入後複製

** 技巧2:用閉包模仿區塊級作用域**

es6沒出來之前,用var定義變數存在變數提升問題,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   因为i随着闭包函数的退出,执行环境销毁,变量回收
登入後複製

當然現在大多用es6的let 和const 定義。


這篇文章到這裡就已經全部結束了,更多其他精彩內容可以關注PHP中文網的

JavaScript影片教學專欄!  

以上是老司機帶你徹底搞懂JS閉包各種坑的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

建議:優秀JS開源人臉偵測辨識項目 建議:優秀JS開源人臉偵測辨識項目 Apr 03, 2024 am 11:55 AM

人臉偵測辨識技術已經是一個比較成熟且應用廣泛的技術。而目前最廣泛的網路應用語言非JS莫屬,在Web前端實現人臉偵測辨識相比後端的人臉辨識有優勢也有弱勢。優點包括減少網路互動、即時識別,大大縮短了使用者等待時間,提高了使用者體驗;弱勢是:受到模型大小限制,其中準確率也有限。如何在web端使用js實現人臉偵測呢?為了實現Web端人臉識別,需要熟悉相關的程式語言和技術,如JavaScript、HTML、CSS、WebRTC等。同時也需要掌握相關的電腦視覺和人工智慧技術。值得注意的是,由於Web端的計

C++ lambda 表達式中閉包的意思是什麼? C++ lambda 表達式中閉包的意思是什麼? Apr 17, 2024 pm 06:15 PM

在C++中,閉包是能夠存取外部變數的lambda表達式。若要建立閉包,請擷取lambda表達式中的外部變數。閉包提供可重複使用性、資訊隱藏和延遲求值等優點。它們在事件處理程序等實際情況中很有用,其中即使外部變數被銷毀,閉包仍然可以存取它們。

C++ Lambda 表達式如何實作閉包? C++ Lambda 表達式如何實作閉包? Jun 01, 2024 pm 05:50 PM

C++Lambda表達式支援閉包,即保存函數作用域變數並供函數存取。語法為[capture-list](parameters)->return-type{function-body}。 capture-list定義要捕獲的變量,可以使用[=]按值捕獲所有局部變量,[&]按引用捕獲所有局部變量,或[variable1,variable2,...]捕獲特定變量。 Lambda表達式只能存取捕獲的變量,但無法修改原始值。

C++ 函式中閉包的優點和缺點是什麼? C++ 函式中閉包的優點和缺點是什麼? Apr 25, 2024 pm 01:33 PM

閉包是一種巢狀函數,它能存取外層函數作用域的變量,優點包括資料封裝、狀態保持和靈活性。缺點包括記憶體消耗、效能影響和調試複雜性。此外,閉包還可以建立匿名函數,並將其作為回調或參數傳遞給其他函數。

解決閉包導致的記憶體洩漏問題 解決閉包導致的記憶體洩漏問題 Feb 18, 2024 pm 03:20 PM

標題:閉包造成的記憶體洩漏及解決方法引言:閉包是JavaScript中一個非常常見的概念,它可以讓內部函數存取外部函數的變數。然而,閉包在使用不當的情況下可能導致記憶體洩漏。本文將探討閉包所造成的記憶體洩漏問題,並提供解決方法及具體程式碼範例。一、閉包引起的記憶體洩漏問題閉包的特性是內部函數可以存取外部函數的變量,這意味著在閉包中引用的變數不會被垃圾回收。如果使用不當,

函數指標和閉包對Golang效能的影響 函數指標和閉包對Golang效能的影響 Apr 15, 2024 am 10:36 AM

函數指針和閉包對Go性能的影響如下:函數指針:稍慢於直接調用,但可提高可讀性和可復用性。閉包:通常更慢,但可封裝資料和行為。實戰案例:函數指標可最佳化排序演算法,閉包可建立事件處理程序,但會帶來效能損失。

js和vue的關係 js和vue的關係 Mar 11, 2024 pm 05:21 PM

js和vue的關係:1、JS作為Web開發基石;2、Vue.js作為前端框架的崛起;3、JS與Vue的互補關係;4、JS與Vue的實踐應用。

PHP 函數的鍊式呼叫與閉包 PHP 函數的鍊式呼叫與閉包 Apr 13, 2024 am 11:18 AM

是的,可以透過鍊式呼叫和閉包優化程式碼簡潔性和可讀性:鍊式呼叫可將函數呼叫連結為一個流暢介面。閉包可建立可重複使用程式碼區塊,並在函數外部存取變數。

See all articles