首頁 > web前端 > js教程 > 主體

深入了解JavaScript中的閉包

PHPz
發布: 2021-05-31 15:05:14
轉載
1960 人瀏覽過

本篇文章為大家介紹一下JavaScript中的閉包。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。

深入了解JavaScript中的閉包

閉包——非常重要但又難以掌握的概念,理解閉包可以看作是某種意義上的重生——《你不知道的Js》
雖然關於閉包,雖然大家可能已經看膩了,但我仍要試著去總結下它! ! !

一、什麼是閉包

顧名思義,遇見問題先問為什麼是我們一貫的思考方式,我們試著回答:

  • 閉包就是函數內部的子函數- 等于没说

  • 當函數可以記住並存取所在的詞法作用域時,就產生了閉包,即使函數是在目前詞法作用域之外執行。 ——靠谱

  • 閉包就是能夠讀取其他函數內部變數的函數,本質上是函數內部和函數外部連結的橋樑——靠谱

  • 函數和對其周圍狀態(詞法環境)的引用捆綁在一起構成閉包(closure)——很靠谱

我們試著用程式碼來描述上面的回答,看看你最中意哪一個~

1.1 閉包是函數內部的子函數

先看這段程式碼:

function foo(params) {
    var a = '余光';

    function bar() {
        console.log(a);
    }
    bar()
}

foo(); // 余光
登入後複製

基於詞法作用域的查找規則,bar函数可以成功的打印a變量,並且它也是foo的子函數,但嚴格來說它並沒有清晰的表達出閉包這一概念,說它表達的是巢狀函數可以存取聲明於大外部作用域的變數更準確一些。

1.2 閉包就是能夠讀取其他函數內部變數的函數,本質上是函數內部和函數外部連結的橋樑

再來看下面的例子:

function foo(params) {
    var a = '余光';

    function bar() {
        console.log(a);
    }
    return bar;
}

var res = foo();
res(); // 余光
登入後複製

結果一致,這是因為此時res是執行foo函數時返回的bar引用,bar函數得以保存了它餓詞法環境。

1.3 函數和對其周圍狀態(詞法環境)的引用捆綁在一起構成閉包(closure)

我們來看看下面程式碼:

var name = '余光';

function foo() {
  console.log(name); // 余光 
}

foo(); //余光
登入後複製

foo的上下文被靜態的保存了下來,而且是在該函數創建的時候就保存了。下面我們來驗證一下:

var name = '余光';

function foo() {
  console.log(name); // 余光
}

(function (func) {
    var name = '老王';

    func()
})(foo); // 余光
登入後複製

這裡我們就可以理解-函數被創建後就形成了閉包,他們保存了上層上下文的作用域鏈,並且保存在[[scope]]中,如果你對[[scope]]的概念已經模糊了,不妨花幾分鐘看看《JavaScript中的執行上下文》這篇文章。

1.4 總結

注意:閉包是函數內部的回傳的子函數這句話本身沒錯,但要看從什麼角度出發:

ECMAScript中,閉包指的是:

  • 從理論角度:所有的函數。因為它們都在創建的時候就將上層上下文的資料保存起來了。即使是簡單的全域變數也是如此,因為函數中存取全域變數就等於是在存取自由變量,這個時候使用最外層的作用域。

  • 從實踐角度:以下函數才算是閉包:

    • 即使創建它的上下文已經銷毀,它仍然存在(例如,內部函數從父函數中回傳)
    • 在程式碼中引用了自由變數

總結:

  • 閉包程式碼區塊建立該程式碼區塊的上下文中資料的結合
  • 閉包就是能夠讀取其他函數內部變數的函數,本質上是函數內部和函數外部連結的橋樑
  • 不同的角度對閉包的解釋不同的

注意:這些並不是閉包的全部,就好像當你被問到-閉包是什麼的時候,你上述的回答並不能結束這個話題,往往會引申出更多的話題。

深入了解JavaScript中的閉包

二、嘗試分析閉包

還是那段經典程式碼:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope();
foo(); // local scope
登入後複製

首先我們🎜>首先我們要分析這段程式碼中執行上下文堆疊和執行上下文的變化情況。

  • 進入全域程式碼,建立全域執行上下文,全域執行上下文壓入執行上下文堆疊

  • 全域執行上下文初始化

  • 執行checkscope 函數,建立checkscope 函數執行上下文,checkscope 執行上下文被壓入執行上下文堆疊

  • checkscope 執行上下文初始化,建立變數物件、作用域物件、this等

  • checkscope 函數執行完畢,checkscope 執行上下文從執行上下文堆疊中彈出

  • 执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈

  • f 执行上下文初始化,创建变量对象、作用域链、this等

  • f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

深入了解JavaScript中的閉包

f 函数执行的时候,checkscope 函数上下文已经被销毁了啊(即从执行上下文栈中被弹出),怎么还会读取到 checkscope 作用域下的 scope 值呢?

当我们了解了具体的执行过程后,我们知道 f 执行上下文维护了一个作用域链:

因为这个作用域链:

  • f 函数依然可以读取到 checkscopeContext.AO 的值;
  • f 函数引用了 checkscopeContext.AO 中的值的时候,即使 checkscopeContext 被销毁了,JavaScript 依然会让 checkscopeContext.AO 活在内存中;
  • f 函数依然可以通过 f 函数的作用域链找到它,正是因为 JavaScript 做到了这一点,从而实现了闭包这个概念。

多么浪漫的思想——只要你需要我,那我我本应该被销毁,你也能找到我~

深入了解JavaScript中的閉包

三、经典问题

3.1 多个对象引用同一个[[Scope]],你遇到过吗?

直接上代码:

var child1;
var child2;
function parent() {
    var x = 1;

    child1 = function () {
        console.log(++x)
    };
    child2 = function () {
        console.log(--x)
    };
}
parent();
child1(); // 2
child1(); // 3
child2(); // 2
登入後複製

大家可能不理解,child1child他们两个函数在创建后都保存了上层上下文,万万没想到,同一个上下文创建的闭包是共用一个[[scope]]属性的,某个闭包对其中[[Scope]]的变量做修改会影响到其他闭包对其变量的读取。

3.2 闭包轻松解决的经典问题

大家一定对下面这段代码很眼熟:

var arr = []
for(var i = 0; i < 10; i++){
    arr[i] = function () {
        console.log(i)
    }
}
arr[0](); // 10
arr[1](); // 10
arr[2](); // 10
arr[3](); // 10
登入後複製

我们这么解释它:同一个上下文中创建的闭包是共用一个[[Scope]]属性的

因此上层上下文中的变量i是可以很容易就被改变的。

arr[0],arr[1]…arr[9]他们共用一个[[scope]],最终执行的时候结果当然一样。

如何利用闭包来解决这个问题呢?

var arr = []
for(var i = 0; i < 10; i++){
    arr[i] = (function (i) {
        return function () {
            console.log(i);
        }
    })(i)
}
arr[0](); // 0
arr[1](); // 1
arr[2](); // 2
arr[3](); // 3
登入後複製

我们通过立即执行匿名函数的方式隔离了作用域,当执行 arr[0] 函数的时候,arr[0] 函数的作用域链发生了改变:

arr[0]Context = {
    Scope: [AO, 匿名函数Context.AO globalContext.VO]
}
登入後複製

匿名函数执行上下文的AO为:

匿名函数Context = {
    AO: {
        arguments: {
            0: 0,
            length: 1
        },
        i: 0
    }
}
登入後複製

我们看到,这时函数的[[Scope]]属性就有了真正想要的值了,为了达到这样的目的,我们不得不在[[Scope]]中创建额外的变量对象。要注意的是,在返回的函数中,如果要获取i的值,那么该值还是会是10。

3.3 总结

  • 函数内的所有内部函数都共享一个父作用域,因此创建的闭包是共用的。
  • 利用闭包隔离作用域的特性可以解决共享作用域的问题

推荐学习:《PHP视频教程

相關標籤:
來源:csdn.net
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!