本篇內容主要講解“分享JavaScript閉包”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“分享JavaScript閉包”吧!
從事達州主機托管,服務器租用,云主機,虛擬主機,空間域名,CDN,網(wǎng)絡代維等服務。
1. 概述
閉包(closures),在 MDN 解釋為:
Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created.
閉包是指那些能夠訪問獨立(自由)變量的函數(shù) (變量在本地使用,但定義在一個封閉的作用域中)。換句話說,這些函數(shù)可以“記憶”它被創(chuàng)建時候的環(huán)境。
閉包是 JavaScript 語言的一個特色,當然也是它的一大難點,很多高級應用都要依靠閉包實現(xiàn),或者我們平常編碼過程中,也在有意無意間使用到閉包。
2. 作用域鏈
在理解閉包,首先就要理解 JavaScript 中的作用域鏈。
在 JavaScript 中有兩種作用域:全局作用域和函數(shù)作用域(在 ES6 中引入了塊級作用域)。
在函數(shù)中定義的變量只能在本函數(shù)體中使用到,在函數(shù)外部不能直接調用函數(shù)體內部定義的變量,但函數(shù)中可以調用到全局作用域中定義的變量。
如果函數(shù)中有內嵌函數(shù)的定義,則在內嵌函數(shù)中可以訪問到外部函數(shù)中定義的變量,也可訪問到全局作用域中的變量,但在外部函數(shù)中不能訪問內嵌函數(shù)中定義的變量。這樣,就形成了作用域鏈,即內嵌函數(shù)可調用父級或祖先級函數(shù)中定義的變量,但父級函數(shù)不能調用子級或后代函數(shù)中定義的變量。
function outer(){
var outVar = 10;
function inner(){
var inVar = 20;
console.log("inner 中調用外部函數(shù)變量 outVar = " + outVar);
}
inner();
console.log("outer 中調用內嵌函數(shù)變量 inVar = " + inVar);
}
outer();
執(zhí)行結果:
inner 中調用外部函數(shù)變量 outVar = 10
ReferenceError: Can't find variable: inVar
在 JavaScript 中,變量的作用域是由它在源代碼中所處位置決定的,并且嵌套的函數(shù)可以訪問到其外層作用域中聲明的變量。
3. 閉包
如果有這樣一種需求,我們需要在外部使用到函數(shù)內的變量,但正常情況下,通過直接調用的方式是不能訪問到的,這就需要變通的方法了。
function outer() {
var i = 1;
var inner = function(){
return ++i;
}
return inner;
}
var result = outer();
console.log("第一次調用:" + result());
console.log("第二次調用:" + result());
console.log("第三次調用:" + result());
執(zhí)行結果:
第一次調用:2
第二次調用:3
第三次調用:4
上例中,我們要使用到 outer 函數(shù)內部的變量 i,每次打印是在原有數(shù)值基礎上自增 1。因在函數(shù)外部不能直接通過變量名對其進行訪問,而嵌套在內部的 inner 函數(shù)則能夠訪問到外部函數(shù)變量 i,所以返回了內部函數(shù)的引用 inner,這樣,當 outer 函數(shù)調用結束后,放置在 result 中的實際為內嵌函數(shù)的引用,這樣就可以繼續(xù)使用到在 outer 函數(shù)內部定義的變量 i 了。這就是閉包。
以前常用到的定時器,相信大家寫過類似的代碼片段:
function fn(){
var i = 0;
var timer = setInterval(function(){
console.log(i++);
if(i > 10)
clearInterval(timer);
}, 50);
}
fn();
fn 函數(shù)調用結束后,按理說在 fn 函數(shù)內部的局部變量 i、timer 作用域該結束了,但 setInterval()函數(shù)的異步執(zhí)行過程中,仍然可以使用到這兩個變量的值。這也是典型的閉包使用情況。
4. 一個故事
來說明閉包可以有哪些適用場景前,我喜歡下面這個例子。
很久很久以前:
有一位公主......
function princess() {
她住在一個充滿冒險的奇妙世界里,遇到了她的白馬王子。白馬王子帶著她騎著獨角獸開始周游世界,與巨龍戰(zhàn)斗,巧遇會說話的動物,還有很多其他的不可思議的新奇事物。
var adventures = [];
function princeCharming() { /* ... */ }
var unicorn = { /* ... */ },
dragons = [ /* ... */ ],
squirrel = "Hello!";
但她不得不回到自己乏味的王國里,例行去見那些成年人。
return {
她會經(jīng)常給大人分享她最近作為公主時的充滿奇幻的冒險經(jīng)歷。
sayStory: function() {
return adventures[adventures.length - 1];
}
};
}
但在大人的眼里,公主僅僅只是一個小女孩兒......
var littleGirl = princess();
......在講著一些神奇的、充滿幻想的故事。
littleGirl.sayStory();
即便所有大人都知道他們眼前的小女孩是真的公主,但是他們絕不相信有巨龍或獨角獸,因為他們自己從來沒有見到過。大人們說它們只存在于小女孩的想象之中。
但是我們卻知道小女孩述說的是事實......
5. 閉包適用場景
通常閉包有如下兩種適用場景:
· 在內存中維持變量,如緩存數(shù)據(jù)
· 保護函數(shù)體內變量的安全,如為對象設置私有屬性
5.1 緩存數(shù)據(jù)
一個比較常用到的例子就是,利用循環(huán)為元素綁定事件。
讓每個 div 元素被點擊時,都能正確彈出當前被點擊的 div 的索引:
div-1
div-2
div-3
div-4
div-5
如果使用如下寫法:
這時,在每個 div 上點擊時彈出的結果都是你點擊的 div 索引為:5。這是因為事件處理是異步的,但事件綁定是同步的,會先執(zhí)行完循環(huán)體的 5 次操作,為每個 div 綁定上 onclick 事件。
這個過程中,變量 i 的值一直在遞增變化,當所有 div 元素都被遍歷后,i 的值自增到 5 退出循環(huán)結構。函數(shù) handle 調用結束后,由于在事件響應程序中仍然存在變量 i 的引用,如果釋放變量 i 的資源,會導致事件響應程序執(zhí)行錯誤,所以為了保證事件響應程序中仍然能正確使用到變量 i,會將變量 i 的值一直保留在內存中,但保留的 i 的值為 5。
如果要正確輸出索引值,可使用閉包修改如下:
在為每個 div 綁定事件時,調用 clk() 函數(shù)將與 div 關聯(lián)的變量值 i 傳遞到 clk() 函數(shù)內部使用,因為內部返回了一個內嵌函數(shù)的引用,該內嵌函數(shù)功能的實現(xiàn)依賴于外部函數(shù)中的局部變量 index,所以 index 變量的值會在內存中得以緩存。
由于每個 div 綁定事件時,都調用了 clk() 函數(shù)來實現(xiàn)事件綁定操作,所以與之對應的變量索引 i 的數(shù)值也都在內存中得以緩存,只是這個值不是以 i 的名稱來緩存。當我們再次測試時,就可以正確打印出所點擊 div 的索引了。
當然以上功能的實現(xiàn)也可以通過自定義屬性方式實現(xiàn):
或是通過 let 命令來實現(xiàn):
5.2 為對象設置私有屬性
如果有一個對象,擁有年齡這樣一個屬性,我們要限定年齡的取值范圍在 18~25 歲之間,以類似 Java 面向對象的方式來實現(xiàn),可模擬如下:
age 表示學生的年齡,這樣的一個變量如果對于任何人都可以修改值,那么如果給定一個負值,比如 -35,雖然就語法上來說沒問題,但就實際邏輯來說,一個人不可能年齡為 -35 歲,所以為了保障這種數(shù)據(jù)的安全,可以使用閉包來解決。
對 Student 函數(shù)內部的局部變量 age 來說,本應該在 Student() 函數(shù)通過 new 調用結束后就釋放掉資源,但在對象的 getAge/setAge 方法中仍然有對其的引用,釋放資源會導致 getAge/setAge 功能不能正常完成,所以其值會保存在內存中。但要修改 age 年齡值時,由于它的作用域問題,我們沒法在 Student 函數(shù)外直接通過調用 age 的方式來修改,僅能使用提供的 setAge 方法接口修改 age 值,這就保證了對 age 修改賦值的安全性。
6. 一點誤解
以前在查閱資料時,經(jīng)常見到說不要輕易使用閉包,否則容易造成內存泄漏的說法。
直到看到這篇文章:《js閉包測試》
閉包里面的變量是我們需要使用到的變量(lives),而內存泄漏通常是指訪問不到的變量依然占據(jù)內存空間,不能夠對其占據(jù)的空間再次利用。顯然閉包是不屬于訪問不到的內存空間。
之所以有這樣的說法,大概是因為 IE,特別是 IE6 的 bug 吧。當然這是 IE 瀏覽器的問題,不是閉包的問題。
現(xiàn)代瀏覽器在 JavaScript 引擎中大都優(yōu)化處理了閉包情形下的垃圾回收,所以關于內存泄漏的說法,我們大可不必再理會了。
到此,相信大家對“分享JavaScript閉包”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!
本文標題:分享JavaScript閉包
文章路徑:http://muchs.cn/article40/ipppeo.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站建設、微信小程序、手機網(wǎng)站建設、軟件開發(fā)、電子商務、網(wǎng)站營銷
聲明:本網(wǎng)站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)