成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

[譯] 透過(guò)重新實(shí)作來(lái)學(xué)習(xí)參透閉包

CoXie / 1971人閱讀

摘要:不過(guò)到底是怎麼保留的另外為什麼一個(gè)閉包可以一直使用區(qū)域變數(shù),即便這些變數(shù)在該內(nèi)已經(jīng)不存在了為了解開(kāi)閉包的神秘面紗,我們將要假裝沒(méi)有閉包這東西而且也不能夠用嵌套來(lái)重新實(shí)作閉包。

原文出處: 連結(jié)

話說(shuō)網(wǎng)路上有很多文章在探討閉包(Closures)時(shí)大多都是簡(jiǎn)單的帶過(guò)。大多的都將閉包的定義濃縮成一句簡(jiǎn)單的解釋?zhuān)蔷褪?b>一個(gè)閉包是一個(gè)函數(shù)能夠保留其建立時(shí)的執(zhí)行環(huán)境。不過(guò)到底是怎麼保留的?

另外為什麼一個(gè)閉包可以一直使用區(qū)域變數(shù),即便這些變數(shù)在該 scope 內(nèi)已經(jīng)不存在了?

為了解開(kāi)閉包的神秘面紗,我們將要假裝 Javascript 沒(méi)有閉包這東西而且也不能夠用嵌套 function 來(lái)重新實(shí)作閉包。這麼做我們將會(huì)發(fā)現(xiàn)閉包真實(shí)的本質(zhì)是什麼以及在底層到底是怎麼運(yùn)作的。

為了這個(gè)練習(xí)我們同時(shí)也需要假裝 Javascript 本身具備了另一個(gè)不存在的功能。那就是一個(gè)原始的物件當(dāng)它如果被當(dāng)成 function 調(diào)用的時(shí)候是可以執(zhí)行的。
你可能已經(jīng)在其他語(yǔ)言中看過(guò)這個(gè)功能,在 Python 中你可以定義一個(gè) __call__ 方法,在 PHP 則有一個(gè)特殊的方法叫 __invoke
這些方法(Method)會(huì)在當(dāng)物件被當(dāng)作 function 調(diào)用時(shí)執(zhí)行。如果我們假裝 Javascript 也有這個(gè)功能,我們可能需要這麼實(shí)作:

let o = {
  n: 42,
  __call__() {
    return this.n;
  }
};

// 當(dāng)我們把物件當(dāng)作 function 一樣調(diào)用時(shí)
o(); // 42, 當(dāng)然現(xiàn)在你會(huì)得到 `TypeError: o is not a function` 的錯(cuò)誤

這邊我們得到一個(gè)普通的物件,我們假裝我們可以把它當(dāng)做 function 來(lái)呼叫,然後當(dāng)我們這個(gè)做的同時(shí)其實(shí)我們是執(zhí)行一個(gè)特殊的方法 __call__ 如果你真的要實(shí)作記得用 o.__call__()。

譯者註: 注意! 假如您想實(shí)作的時(shí),呼叫 可調(diào)用物件 例如上面的 o() 都要換成 o.__call__()

現(xiàn)在讓我們先來(lái)看看一個(gè)簡(jiǎn)單的閉包範(fàn)例。

function f() {
  // 下面這個(gè)變數(shù)是 f() 的區(qū)域變數(shù)
  // 通常,當(dāng)我們離開(kāi) f 的 scope 時(shí),這個(gè)變數(shù) n 就應(yīng)該要被回收了
  let n = 42;

  // 嵌套的 function 參考了 n
  function g() {
    return n;
  }

  return g;
}

// 讓我們透過(guò) f() 來(lái)建立一個(gè) g 函數(shù)
let g = f();

// 理論上這個(gè)變數(shù) n 在 f() 執(zhí)行完畢之後就應(yīng)該要立即被回收,對(duì)吧?
// 畢竟 f 已經(jīng)執(zhí)行完畢了,而且我們也離開(kāi)了該 scope
// 那為什麼 g 可以繼續(xù)參考一個(gè)已經(jīng)被釋放的變數(shù)呢?
g(); // 42

外層的 function f 有一個(gè)區(qū)域變數(shù),然後裡面的 function g 參考 f 的區(qū)域變數(shù)。

接著我們把內(nèi)層的 g 回傳指派給 f scope 外的變數(shù)。但我們好奇的是如果 f 執(zhí)行完畢被釋放了,那為什麼 g 仍然可以取得已被釋放的 f 的區(qū)域變數(shù)呢?

這個(gè)的魔法便是 - 一個(gè)閉包不僅僅只是一個(gè) function。它是一個(gè)物件,具有建構(gòu)子和私有資料。然後我們可以它當(dāng)作 function 來(lái)使用。
那如果 Javascript 沒(méi)有閉包這種用法,我們必須自己實(shí)作它呢?這就是我們接下來(lái)要看到的。

// 譯者註: 這邊請(qǐng)先不要糾結(jié) Babel 或其他 Complier 實(shí)際編譯出來(lái)的 ES5 還是用 function
class G {
  constructor(n) {
    this._n = n
  }

  __call__() {
    return this._n;
  }
}

function f() {
  let n = 42;

  // 這就是一個(gè)閉包
  // 這個(gè)內(nèi)層的 function 其實(shí)不只是一個(gè) function
  // 它其實(shí)是一個(gè)可以被調(diào)用的物件,然後我們傳入 n 到它的建構(gòu)子
  let g = new G(n);


  return g;
}

// 透過(guò)呼叫 f() 取得一個(gè)可以被調(diào)用的物件 g
let g = f();

// 現(xiàn)在就算原來(lái)從 f 拿到的區(qū)域變數(shù) n 被回收了也沒(méi)關(guān)係
// 可被調(diào)用的物件 g 實(shí)際上是參考自己私有的資料
g(); // 42

如果您曾看過(guò) ECMAScript 規(guī)範(fàn),可能會(huì)對(duì)實(shí)際上是參考自己私有的資料這句話產(chǎn)生一些疑問(wèn),先別急著否定。這邊不過(guò)是試著用另外一個(gè)較淺的角度解釋。

這邊我們把內(nèi)部的 function g 用一個(gè) G class 的實(shí)例物件(即 new 出來(lái)的物件) 取代,然後我們透過(guò)把 f 的區(qū)域變數(shù) n 傳進(jìn) G 的建構(gòu)子,藉此將變數(shù)儲(chǔ)存在新的實(shí)例物件私有的資料中。最終我們可以取得 f 的區(qū)域變數(shù)(n)。

OK! 各位觀眾這就是一個(gè)閉包的行為。閉包就是一個(gè)可調(diào)用的物件,可以把透過(guò)建構(gòu)子把傳入的參數(shù)保留在私有的空間中。

讓我們?cè)偕钊胍稽c(diǎn)

聰明的讀者已經(jīng)發(fā)現(xiàn)還有一些行為還沒(méi)解釋清楚或者說(shuō)我們的模擬實(shí)作是有漏洞的。讓我們來(lái)觀察其他的閉包範(fàn)例

function f() {
  let n = 42;

  // 內(nèi)部函數(shù)取得變數(shù) n
  function get() {
    return n;
  }

  // 另外一個(gè)內(nèi)部函數(shù)也同時(shí)存取 n
  function next() {
    return n++;
  }

  return { get, next };
}

let o = f();
o.get(); // 42
o.next();
o.get(); // 43

在這個(gè)範(fàn)例中,我們得到兩個(gè)閉包同時(shí)參考變數(shù) n 。其中一個(gè)函數(shù)的操作變數(shù)會(huì)影響另外一個(gè)變數(shù)取得得值。
但如果 Javascript 沒(méi)有閉包,單靠我們上面的實(shí)作和 JS 的行為將不會(huì)一樣。

class Get {
  constructor(n) {
    this._n = n;
  }

  __call__() {
    return this._n;
  }
}

class Next {
  constructor(n) {
    this._n = n;
  }

  __call__() {
    this._n++;
  }
}

function f() {
  let n = 42;

  // 這邊的閉包我們一樣換成可調(diào)用的物件
  // 它們可以將參數(shù)傳入建構(gòu)子,進(jìn)而將值保留起來(lái)
  let get = new Get(n);
  let next = new Next(n);

  return { get, next };
}

let o = f();
o.get(); // 42
o.next();
o.get(); // 42

跟上面一樣,我們?nèi)〈藘?nèi)部 function getnext 的部分改成使用物件。它們是透過(guò)將值保留在物件內(nèi)部進(jìn)而取得 f 的區(qū)域變數(shù),每一個(gè)物件具有自己私有的資料。同時(shí)我們也注意到其中一個(gè)可調(diào)用物件 操作 n 並不會(huì)影響另外一個(gè)。這是因?yàn)樗鼈兪莻?n 的值 value而不是傳址 reference/address。白話文就是複製了一分資料。並不是操作變數(shù)本身。

為了要解釋為什麼 Javascript 的閉包會(huì)參考到相同的 n 即記憶體位置是一樣的。我們需要解釋變數(shù)本身。在底層,Javascript 的區(qū)域變數(shù)跟我們從其他語(yǔ)言理解的觀念並不相同,它們是負(fù)責(zé)動(dòng)態(tài)分配與計(jì)算參考(reference)的物件的屬性,稱(chēng)為 LexicalEnvironment 物件。Javascript 的閉包其實(shí)會(huì)有一個(gè)參考指向到整個(gè) 執(zhí)行環(huán)境, 上下文, Context 的 LexicalEnvironment 物件,而不是特定的變數(shù)。

如果您對(duì)於 scope 與 context 還不是很了解強(qiáng)烈建議您觀賞這篇

讓我們來(lái)修改我們的可調(diào)用物件讓其可以取得一個(gè) lexical environment 而不是 n 。

class Get {
  constructor(lexicalEnvironment) {
    this._lexicalEnvironment = lexicalEnvironment;
  }

  __call__() {
    return this._lexicalEnvironment.n;
  }
}

class Next {
  constructor(lexicalEnvironment) {
    this._lexicalEnvironment = lexicalEnvironment;
  }

  __call__() {
    this._lexicalEnvironment.n++;
  }
}

function f() {
  let lexicalEnvironment = {
    n: 42
  }

  // 現(xiàn)在這個(gè)可調(diào)用變數(shù)是透過(guò)一個(gè)參考 lexical environment 來(lái)改變 n
  // 所以現(xiàn)在變更的是同一個(gè) n 了
  let get = new Get(lexicalEnvironment);
  let next = new Next(lexicalEnvironment);
  return { get, next }
}

// 現(xiàn)在我們實(shí)作的物件行為跟 javascript 一致了
// 還是請(qǐng)注意如果您要時(shí)作,記得 o.get() 要換成 o.get.__call__() 喔
let o = f();
o.get(); // 42
o.next();
o.get(); // 43

上面實(shí)作我們將區(qū)域變數(shù) n 換成 lexicalEnvironment 物件,然後具有一個(gè)屬性 n 。
這時(shí) GetNext 的物件實(shí)例所存取的便是同一個(gè)參考(reference)即 lexical environment 物件。
所以現(xiàn)在修改的就是相同的地方了?;旧线@就是一個(gè)閉包的行為。

結(jié)論

閉包是一個(gè)物件而且當(dāng)它們是函數(shù)時(shí)我們可以直接調(diào)用。而事實(shí)上任何一個(gè) Javascript 中的函數(shù)都是一個(gè)可被調(diào)用的物件也稱(chēng)作 function object 或者 functor 當(dāng)它們被執(zhí)行或者說(shuō)被實(shí)例化時(shí)會(huì)帶有一個(gè)私有的 lexical environment 物件。而想要更了解關(guān)於這個(gè)物件的看官們可以參考Lexical environment的定義。
在 Javascript 不是 function 創(chuàng)造閉包,function 本身就是一個(gè)閉包。

老實(shí)說(shuō)譯者本身還是比較喜歡理解 context 與 variable object 的說(shuō)明,接著用 一個(gè)閉包是一個(gè)函數(shù)能夠保留其建立時(shí)的執(zhí)行環(huán)境 這句話來(lái)記憶。雖然翻譯了這篇文章但讓小弟對(duì)於閉包有更深入理解是這篇解讀ECMAScript[1]——執(zhí)行環(huán)境、作用域及閉包。不過(guò)原作者從這個(gè)角度來(lái)解釋的確是可以概略的理解整個(gè)運(yùn)作機(jī)制,希望這篇文章能讓你有所收穫。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/78739.html

相關(guān)文章

  • [ + 更新] 參透 Node 中 exports 的 7 種設(shè)計(jì)模式

    摘要:現(xiàn)在,我們可以開(kāi)始探討介面的設(shè)計(jì)模式了。匯出命名空間一個(gè)簡(jiǎn)單且常用的設(shè)計(jì)模式就是匯出一個(gè)包含數(shù)個(gè)屬性的物件,這些屬性具體的內(nèi)容主要是函式,但並不限於函式。如此,我們就能夠透過(guò)匯入該模組來(lái)取得這個(gè)命名空間下一系列相關(guān)的功能。 前言 這篇文章試著要整理,翻譯Export This: Interface Design Patterns for Node.js Modules這篇非常值得一讀的...

    wmui 評(píng)論0 收藏0
  • [] 學(xué)習(xí) CSS clip-path 屬性

    摘要:整體來(lái)說(shuō)網(wǎng)頁(yè)主要是由矩形所構(gòu)成的,另一方面印刷品則具備相對(duì)多樣性。即便我們?cè)O(shè)定的元素不再是矩形,但周?chē)脑嘏帕蟹绞饺匀痪S持原本矩形的佈局。為了達(dá)成周?chē)脑馗们械男螤?,我們可以使用屬性。周?chē)脑厝孕枰縼?lái)修正。 整體來(lái)說(shuō)網(wǎng)頁(yè)主要是由矩形所構(gòu)成的,另一方面印刷品則具備相對(duì)多樣性。造成這樣差異的原因有很多,不過(guò)其中一個(gè)即是缺少合適的工具。 這篇文章主要會(huì)介紹 clip-path 這...

    yuanxin 評(píng)論0 收藏0
  • 手把手深入理解 webpack dev middleware 原理與相關(guān) plugins

    摘要:的架構(gòu)設(shè)計(jì)促使第三方開(kāi)發(fā)者讓核心發(fā)揮出無(wú)限的潛力。當(dāng)然建置比起開(kāi)發(fā)是較進(jìn)階的議題,因?yàn)槲覀儽仨氁斫鈨?nèi)部的一些事件。這個(gè)編譯結(jié)果包含的訊息包含模組的狀態(tài),編譯後的資源檔,發(fā)生異動(dòng)的檔案,被觀察的相依套件等。 本文將對(duì) webpack 周邊的 middleware 與 plugin 套件等作些介紹,若您對(duì)於 webpack 還不了解可以參考這篇彙整的翻譯。 webpack dev ser...

    gitmilk 評(píng)論0 收藏0
  • [] CSS 載入機(jī)制的未來(lái)趨勢(shì)

    摘要:載入流程被限制在兩個(gè)階段根據(jù)上面的模式,內(nèi)嵌透過(guò)隱藏尚未套用樣式的內(nèi)容,然後非同步得載入之後呈現(xiàn)內(nèi)容。樣式表本身的載入機(jī)制是平行的,但是套用樣式卻是要照順序的。我們需要一點(diǎn)小技巧來(lái)避免。 這週閱讀到這篇有意思的文章,於是便動(dòng)手寫(xiě)下簡(jiǎn)單的翻譯,如果有理解錯(cuò)誤的地方歡迎指教。 Chrome 正在試圖改變當(dāng) 寫(xiě)在 的行為,從blink-dev 的文章並不能很清楚的知道其優(yōu)點(diǎn)。所以這篇文章...

    Astrian 評(píng)論0 收藏0
  • [] scroll-behavior 滑順的捲動(dòng)效果

    摘要:不過(guò)這個(gè)效果感覺(jué)上就像是閃一下就切換到該位置。為了使用體驗(yàn)上的感覺(jué)有時(shí)候網(wǎng)站會(huì)設(shè)計(jì)一種平滑捲動(dòng)到該位置的效果。的方式非常簡(jiǎn)單,只要在該元素設(shè)定注意是而不是這個(gè)方式非常方便不過(guò)目前只有支援,查閱。 眾所皆知 HTML 錨點(diǎn)(anchor link)透過(guò)給定標(biāo)籤 id 屬性跳到頁(yè)面上特定位置的功能。不過(guò)這個(gè)效果感覺(jué)上就像是閃一下就切換到該位置。為了使用體驗(yàn)上的感覺(jué)有時(shí)候網(wǎng)站會(huì)設(shè)計(jì)一種平滑捲...

    PiscesYE 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

CoXie

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<