摘要:閉包是這門語言中有些復(fù)雜并且充滿誤解的特性。說明返回了內(nèi)部方法,調(diào)用了的參數(shù),閉包創(chuàng)建。過度使用閉包會導(dǎo)致腳本執(zhí)行變慢并消耗額外內(nèi)存。本節(jié)我們說幾種場景要注意避免閉包的產(chǎn)生。循環(huán)中循環(huán)中創(chuàng)建出閉包會導(dǎo)致結(jié)果異常。
譯者:閉包都被討論爛了,不理解閉包都不好意思說自己會js,但我看到這篇文章還是感覺眼前一亮,也讓我對閉包有了一些新的理解,并且涉及了一些類和原型鏈的知識,這是一篇2012年的文章,稍微有點早,內(nèi)容也略微基礎(chǔ),但是很明晰,希望能給讀者帶來新的理解。
閉包(Closure) 是javascript這門語言中有些復(fù)雜并且充滿誤解的特性。簡言之,閉包是一個對象,這個對象包含一個方法(function)和該方法創(chuàng)建時環(huán)境的引用(reference to the enviroment)。為了完全理解閉包,我們還需要理解兩個js中的特性,一個是一級方法(first-class function),另一個是內(nèi)部方法(inner function)。
一級方法/First-Class Functions在js中,方法是頭等公民,因為它可以被輕易轉(zhuǎn)換成其他數(shù)據(jù)類型。比如,一級方法可以實時構(gòu)建并且賦值給一個變量。也可以傳遞給其他方法,或者通過其他方法返回。除了滿足這些標(biāo)準(zhǔn)以外,方法也擁有自己的屬性和方法。
通過下述例子,我們來看一下一級方法的能力。
var foo = function() { alert("Hello World!"); }; var bar = function(arg) { return arg; }; bar(foo)();
譯者注:省略原文對代碼的文字解釋,這里體現(xiàn)的是一級方法可以返回參數(shù),參數(shù)可以是另外一個一級函數(shù),返回的結(jié)果還可以調(diào)用。內(nèi)部方法/Inner Functions
內(nèi)部方法或者說嵌套方法,是指定義在其他方法內(nèi)部的方法,每當(dāng)外部方法被喚起,內(nèi)部方法的實例就被創(chuàng)建。下面的例子反應(yīng)內(nèi)部方法的使用,add方法是外部方法,doAdd是內(nèi)部方法。
function add(value1, value2) { function doAdd(operand1, operand2) { return operand1 + operand2; } return doAdd(value1, value2); } var foo = add(1, 2); // foo equals 3
這個例子中,一個重要的特性是,內(nèi)部方法獲取到了外部方法的作用域,這意味著內(nèi)部方法能夠使用外部方法的變量,參數(shù)等。例子中add()的參數(shù)value1,value2傳遞給doAdd()的operand1,operand2參數(shù)。然而這并沒有必要,因為doAdd可以直接獲取value1,value2。所以上面的例子我們還可以這么寫:
function add(value1, value2) { function doAdd() { return value1 + value2; } return doAdd(); } var foo = add(1, 2); // foo equals 3創(chuàng)建閉包/Creating Closures
內(nèi)部方法獲取外部方法的作用域,便形成了一個閉包。典型的場景是外部函數(shù)將其內(nèi)部方法返回,內(nèi)部方法保持了外部環(huán)境的引用,并保存了作用域下的所有變量。
一下例子展示閉包如何創(chuàng)建并使用。
function add(value1) { return function doAdd(value2) { return value1 + value2; }; } var increment = add(1); var foo = increment(2); // foo equals 3
說明:
add返回了內(nèi)部方法doAdd,doAdd調(diào)用了add的參數(shù),閉包創(chuàng)建。
value1是add方法的本地變量,對doAdd來說是非本地變量(非本地變量指變量既不在函數(shù)體本身,也不在全局),value2是doAdd的本地變量。
當(dāng)add(1)被調(diào)用,一個閉包被創(chuàng)建并儲存在increment中,在該閉包的引用環(huán)境中,value1綁定了1,被綁定的1相當(dāng)于“封鎖”在這個函數(shù)中,這也是“閉包”這個名字的由來。
當(dāng)increment(2)被調(diào)用,進(jìn)入閉包函數(shù),這意味著攜帶著value1為1的doAdd被調(diào)用,因此該閉包本質(zhì)上可以當(dāng)做如下函數(shù):
function increment(value2) { return 1 + value2; }何時使用閉包?
閉包可以實現(xiàn)很多功能。比如將回調(diào)函數(shù)綁定指定參數(shù)。我們說兩個讓你的生活和開發(fā)變得更簡單的場景。
配合定時器
閉包結(jié)合setTimeout和setInterval非常有用,閉包允許你向回調(diào)函數(shù)傳入指定參數(shù),比如下面的例子,每秒鐘在給指定dom插入字符串。
Closures
遺憾的是,IE不支持向setInterval的回調(diào)傳參,IE中頁面不會展現(xiàn)“some message”而是“undefined”(無值傳入showMessage()),解決這個問題,可以通過閉包將期望值綁定于回調(diào)函數(shù)里,我們可以改寫如上代碼:
window.addEventListener("load", function() { var showMessage = getClosure("some message
"); window.setInterval(showMessage, 1000); }); function getClosure(message) { function showMessage() { document.getElementById("message").innerHTML += message; } return showMessage; }
2.模擬私有屬性
絕大多數(shù)面向?qū)ο蟮某绦蛘Z言支持對象的私有屬性,然而js不是純正的面向?qū)ο蟮恼Z言,因此也沒有私有屬性的概念。不過,我們可以通過閉包來模擬私有屬性。回想一下,閉包包含了一份其創(chuàng)建環(huán)境的引用,這份引用已經(jīng)不在當(dāng)前作用域中了,因此這份引用只能在閉包中訪問,這本質(zhì)上就是私有屬性。
看如下例子(譯者:省略對代碼的文字描述):
function Person(name) { this._name = name; this.getName = function() { return this._name; }; }
這里有一個嚴(yán)重的問題,因為js不支持私有屬性,所以我們沒法阻止別人修改實例的name字段,比如我們創(chuàng)建一個Person實例叫Colin,然后可以將他的名字改成Tom。
var person = new Person("Colin"); person._name = "Tom"; // person.getName() now returns "Tom"
沒有人愿意不經(jīng)同意就被別人改名字,為了阻止這種情況的發(fā)生,通過閉包讓_name字段變成私有。看如下代碼,注意這里的_name是Person構(gòu)造器的本地變量,而不是對象的屬性,閉包形成了,因為外層方法Person對外暴露了一個內(nèi)部方法getName。
function Person(name) { var _name = name;// 注:區(qū)別在這里 this.getName = function() { return _name; }; }
現(xiàn)在,當(dāng)getName被調(diào)用,能夠保證返回的是最初傳入類構(gòu)造器的值。我們依然可以為對象添加新的_name屬性,但這并不影響閉包getName最初綁定的值,下面的代碼證明,_name字段,事實私有。
var person = new Person("Colin"); person._name = "Tom"; // person._name is "Tom" but person.getName() returns "Colin"什么時候不要用閉包?
正確理解閉包如何工作何時使用非常重要,而理解什么時候不應(yīng)該用它也同樣重要。過度使用閉包會導(dǎo)致腳本執(zhí)行變慢并消耗額外內(nèi)存。由于閉包太容易創(chuàng)建了,所以很容易發(fā)生你都不知道怎么回事,就已經(jīng)創(chuàng)建了閉包的情況。本節(jié)我們說幾種場景要注意避免閉包的產(chǎn)生。
1.循環(huán)中
循環(huán)中創(chuàng)建出閉包會導(dǎo)致結(jié)果異常。下例中,頁面上有三個按鈕,分別點擊彈出不同的話術(shù)。然而實際運(yùn)行,所有的按鈕都彈出button4的話術(shù),這是因為,當(dāng)按鈕被點擊時,循環(huán)已經(jīng)執(zhí)行完畢,而循環(huán)中的變量i也已經(jīng)變成了最終值4.
Closures
去解決這個問題,必須在循環(huán)中去掉閉包(譯者:這里的閉包指的是click事件回調(diào)函數(shù)綁定了外層引用i),我們可以通過調(diào)用一個引用新環(huán)境的函數(shù)來解決。下面的代碼中,循環(huán)中的變量傳遞給getHandler函數(shù),getHandler返回一個閉包(譯者:這個閉包指的是getHandler返回的內(nèi)部方法綁定傳入的i參數(shù)),獨立于原來的for循環(huán)。
function getHandler(i) { return function handler() { alert("Clicked button " + i); }; } window.addEventListener("load", function() { for (var i = 1; i < 4; i++) { var button = document.getElementById("button" + i); button.addEventListener("click", getHandler(i)); } });
2.構(gòu)造函數(shù)里的非必要使用
類的構(gòu)造函數(shù)里,也是經(jīng)常會產(chǎn)生閉包的錯誤使用。我們已經(jīng)知道如何通過閉包設(shè)置類的私有屬性,而如果當(dāng)一個方法不需要調(diào)用私有屬性,則造成的閉包是浪費的。下面的例子中,Person類增加了sayHello方法,但是它沒有使用私有屬性。
function Person(name) { var _name = name; this.getName = function() { return _name; }; this.sayHello = function() { alert("Hello!"); }; }
每當(dāng)Person被實例化,創(chuàng)建sayHello都要消耗時間,想象一下有大量的Person被實例化。更好的實踐是將sayHello放入Person的原型鏈里(prototype),原型鏈里的方法,會被所有的實例化對象共享,因此節(jié)省了為每個實例化對象去創(chuàng)建一個閉包(譯者:指sayHello),所以我們有必要做如下修改:
function Person(name) { var _name = name; this.getName = function() { return _name; }; } Person.prototype.sayHello = function() { alert("Hello!"); };需要記得一些事情
閉包包含了一個方法,以及創(chuàng)建它的代碼環(huán)境引用
閉包會在外部函數(shù)包含內(nèi)部函數(shù)的情況下形成
閉包可以輕松的幫助回調(diào)函數(shù)傳入?yún)?shù)
類的私有屬性可以通過閉包模擬
類的構(gòu)造器中使用閉包不是一個好主意,將它們放到原型鏈中
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/98458.html
摘要:前端日報精選劉海打理指北中的錯誤處理模式與反模式譯圖解和譯你并不知道中文裝飾器讓你的代碼更簡潔眾成翻譯第期每個程序員第一份工作前應(yīng)該知道的件事中的不變性眾成翻譯寫的一次小結(jié)掘金內(nèi)部機(jī)制探秘和文末附彩蛋和源碼前端雜談開發(fā)實戰(zhàn) 2017-09-30 前端日報 精選 iPhone X 劉海打理指北React16中的錯誤處理ES6 Promise:模式與反模式「譯」圖解 ArrayBuffer...
摘要:語言新特性現(xiàn)在返回源代碼的所有內(nèi)容,包括空格和注釋。隨著去年發(fā)布的新的字節(jié)碼解釋器,我們擴(kuò)展了這個功能,以便在后臺線程上將源代碼編譯為字節(jié)碼。 每六周,我們都會創(chuàng)建一個 V8 的新分支,作為我們發(fā)布流程的一部分。每個版本都是在 Chrome Beta 里程碑之前從 V8 的 Git master 分支出來的。今天(2018-03-27),我們很高興地宣布,我們發(fā)布了一個新的分支:V8 ...
摘要:語言新特性現(xiàn)在返回源代碼的所有內(nèi)容,包括空格和注釋。隨著去年發(fā)布的新的字節(jié)碼解釋器,我們擴(kuò)展了這個功能,以便在后臺線程上將源代碼編譯為字節(jié)碼。 每六周,我們都會創(chuàng)建一個 V8 的新分支,作為我們發(fā)布流程的一部分。每個版本都是在 Chrome Beta 里程碑之前從 V8 的 Git master 分支出來的。今天(2018-03-27),我們很高興地宣布,我們發(fā)布了一個新的分支:V8 ...
摘要:一事件流事件流描述的是從頁面中接受事件的順序。級事件處理程序級事件定義了兩個方法用于處理指定和刪除事件處理程序的操作和。第二個方法是,它返回事件的目標(biāo)。第三個方法是,用于取消事件的默認(rèn)行為。首先嘗試使用方法阻止事件流,否則就使用屬性。 一、事件流 事件流描述的是從頁面中接受事件的順序。IE的事件流是事件冒泡流,而Netscape的事件流是事件捕獲流 1、事件冒泡 事件冒泡,即事件最開始...
閱讀 3591·2023-04-26 01:43
閱讀 2977·2021-10-14 09:42
閱讀 5445·2021-09-30 09:59
閱讀 2177·2021-09-04 16:40
閱讀 1212·2019-08-30 15:52
閱讀 828·2019-08-29 17:09
閱讀 2000·2019-08-26 13:37
閱讀 3436·2019-08-26 10:20