站長資訊網(wǎng)
        最全最豐富的資訊網(wǎng)站

        javascript閉包是作用域嗎

        在javascript中,閉包不是作用域,而是一個能夠持續(xù)存在的函數(shù)上下文活動對象,是同時含有對函數(shù)對象以及作用域?qū)ο笠玫膶ο蟆i]包主要是用來獲取作用域鏈或原型鏈上的變量或值。

        javascript閉包是作用域嗎

        本教程操作環(huán)境:windows7系統(tǒng)、javascript1.8.5版、Dell G3電腦。

        我們知道,作用域鏈查找標(biāo)識符的順序是從當(dāng)前作用域開始一級一級往上查找。因此,通過作用域鏈,JavaScript 函數(shù)內(nèi)部可以讀取函數(shù)外部的變量,但反過來,函數(shù)的外部通常則無法讀取函數(shù)內(nèi)部的變量。在實際應(yīng)用中,有時需要在函數(shù)外部訪問函數(shù)的局部變量,此時最常用的方法就是使用閉包。

        閉包是 JavaScript 的重要特性之一,在函數(shù)式編程中有著重要的作用,本節(jié)介紹閉包的結(jié)構(gòu)和基本用法。

        那么什么是閉包?

        閉包是一個能夠持續(xù)存在的函數(shù)上下文活動對象,是同時含有對函數(shù)對象以及作用域?qū)ο笠玫膶ο蟆i]包主要是用來獲取作用域鏈或原型鏈上的變量或值。創(chuàng)建閉包最常用的方式是在一個函數(shù)中聲明內(nèi)部函數(shù)(也稱嵌套函數(shù)),并返回內(nèi)部函數(shù)。

        此時在函數(shù)外部就可以通過調(diào)用函數(shù)得到內(nèi)部函數(shù),進而調(diào)用內(nèi)部函數(shù)來實現(xiàn)對函數(shù)局部變量的訪問。此時的內(nèi)部函數(shù)就是一個閉包。雖然按照閉包的概念,所有訪問了外部變量的 JavaScript 函數(shù)都是閉包,但我們平常絕大部分時候所謂的閉包其實指的就是內(nèi)部函數(shù)閉包。

        閉包可以將一些數(shù)據(jù)封裝為私有屬性以確保這些變量的安全訪問,這個功能給應(yīng)用帶來了極大的好處。需要注意的是,閉包如果使用不當(dāng),也會帶來一些意想不到的問題。下面就通過幾個示例來演示一下閉包的創(chuàng)建、使用和可能存在的問題及其解決方法。

        形成原理

        函數(shù)被調(diào)用時,會產(chǎn)生一個臨時上下文活動對象。它是函數(shù)作用域的頂級對象,作用域內(nèi)所有私有方法有變量、參數(shù)、私有函數(shù)等都將作為上下文活動對象的屬性而存在。

        函數(shù)被調(diào)用后,在默認(rèn)情況下上下文活動對象會被立即釋放,避免占用系統(tǒng)資源。但是,若函數(shù)內(nèi)的私有變量、參數(shù)、私有函數(shù)等被外界引用,則這個上下文活動對象暫時會繼續(xù)存在,直到所有外界引用被注銷。

        但是,函數(shù)作用域是封閉的,外界無法訪問。那么在什么情況下,外界可以訪問到函數(shù)內(nèi)的私有成員呢?

        根據(jù)作用域鏈,內(nèi)部函數(shù)可以訪問外部函數(shù)的私有成員。如果內(nèi)部函數(shù)引用了外部函數(shù)的私有成員,同時內(nèi)部函數(shù)又被傳給外界,或者對外界開放,那么閉包體就形成了。這個外部函數(shù)就是一個閉包體,它被調(diào)用后,活動對象暫時不會被注銷,其屬性會繼續(xù)存在,通過內(nèi)部函數(shù)可以持續(xù)讀寫外部函數(shù)的私有成員。

        閉包結(jié)構(gòu)

        典型的閉包體是一個嵌套結(jié)構(gòu)的函數(shù)。內(nèi)部函數(shù)引用外部函數(shù)的私有成員,同時內(nèi)部函數(shù)又被外界引用,當(dāng)外部函數(shù)被調(diào)用后,就形成了閉包。這個函數(shù)也稱為閉包函數(shù)。

        下面是一個典型的閉包結(jié)構(gòu)。

        function f(x) {  //外部函數(shù)     return function (y) {  //內(nèi)部函數(shù),通過返回內(nèi)部函數(shù),實現(xiàn)外部引用         return x + y;  //訪問外部函數(shù)的參數(shù)     }; } var c = f(5);  //調(diào)用外部函數(shù),獲取引用內(nèi)部函數(shù) console.log(c(6));  //調(diào)用內(nèi)部函數(shù),原外部函數(shù)的參數(shù)繼續(xù)存在

        解析過程簡單描述如下:

        • 在 JavaScript 腳本預(yù)編譯期,聲明的函數(shù) f 和變量 c,先被詞法預(yù)解析。

        • 在 JavaScript 執(zhí)行期,調(diào)用函數(shù) f,并傳入值 5。

        • 在解析函數(shù) f 時,將創(chuàng)建執(zhí)行環(huán)境(函數(shù)作用域)和活動對象,并把參數(shù)和私有變量、內(nèi)部函數(shù)都映射為活動對象的屬性。

        • 參數(shù) x 的值為 5,映射到活動對象的 x 屬性。

        • 內(nèi)部函數(shù)通過作用域鏈引用了參數(shù) x,但是還沒有被執(zhí)行。

        • 外部函數(shù)被調(diào)用后,返回內(nèi)部函數(shù),導(dǎo)致內(nèi)部函數(shù)被外界變量 c 引用。

        • JavaScript 解析器檢測到外部函數(shù)的活動對象的屬性被外界引用,無法注銷該活動對象,于是在內(nèi)存中繼續(xù)維持該對象的存在。

        • 當(dāng)調(diào)用 c,即調(diào)用內(nèi)部函數(shù)時,可以看到外部函數(shù)的參數(shù) x 存儲的值繼續(xù)存在。這樣就可以實現(xiàn)后續(xù)運算操作,返回 x+y=5=6=11。

        如下結(jié)構(gòu)形式也可以形成閉包:通過全局變量引用內(nèi)部函數(shù),實現(xiàn)內(nèi)部函數(shù)對外開放。

        var c;  //聲明全局變量 function f(x) {  //外部函數(shù)     c = function (y) {  //內(nèi)部函數(shù),通過向全局變量開放實現(xiàn)外部引用         return x + y;  //訪問外部函數(shù)的參數(shù)     }; } f(5);  //調(diào)用外部函數(shù) console.log(c(6));  //使用全局變量c調(diào)用內(nèi)部函數(shù),返回11

        閉包變體

        除了嵌套函數(shù)外,如果外部引用函數(shù)內(nèi)部的私有數(shù)組或?qū)ο螅踩菀仔纬砷]包。

        var add;  //全局變量 function f() {  //外部函數(shù)     var a = [1,2,3];  //私有變量,引用型數(shù)組     add = function (x) {  //測試函數(shù),對外開放         a[0] = x * x;  //修改私有數(shù)組的元素值     }     return a;  //返回私有數(shù)組的引用 } var c = f(); console.log(c[0]);  //讀取閉包內(nèi)數(shù)組,返回1 add(5);  //測試修改數(shù)組 console.log(c[0]);  //讀取閉包內(nèi)數(shù)組,返回25 add(10);  //測試修改數(shù)組 console.log(c[0]);  //讀取閉包內(nèi)數(shù)組,返回100

        與函數(shù)相同,對象和數(shù)組也是引用型數(shù)據(jù)。調(diào)用函數(shù) f,返回私有數(shù)組 a 的引用,即傳值給局部變量 c,而 a 是函數(shù) f 的私有變量,當(dāng)被調(diào)用后,活動對象繼續(xù)存在,這樣就形成了閉包。

        這種特殊形式的閉包沒有實際應(yīng)用價值,因為其功能單一,只能作為一個靜態(tài)的、單向的閉包。而閉包函數(shù)可以設(shè)計各種復(fù)雜的運算表達式,它是函數(shù)式變成的基礎(chǔ)。

        反之,如果返回的是一個簡單的值,就無法形成閉包,值傳遞是直接復(fù)制。外部變量 c 得到的僅是一個值,而不是對函數(shù)內(nèi)部變量的引用。這樣當(dāng)函數(shù)調(diào)用后,將直接注銷對象。

        function f(x) {  //外部函數(shù)     var a = 1;  //私有變量     return a; } var c = f(5); console.log(c);  //僅是一個值,返回1

        使用閉包

        下面結(jié)合示例介紹閉包的簡單使用,以加深對閉包的理解。

        示例1

        使用閉包實現(xiàn)優(yōu)雅的打包,定義存儲器。

        var f = function () {  //外部函數(shù)     var a = [];  //私有數(shù)組初始化     return function (x) {  //返回內(nèi)部函數(shù)         a.push(x);  //添加元素         return a;  //返回私有數(shù)組     }; } ()  //直接調(diào)用函數(shù),生成執(zhí)行環(huán)境 var a = f(1);  //添加值 console.log(a);  //返回1 var b = f(2);  //添加值 console.log(b);  //返回1,2

        在上面示例中,通過外部函數(shù)設(shè)計一個閉包,定義一個永久的存儲器。當(dāng)調(diào)用外部函數(shù)生成執(zhí)行環(huán)境之后,就可以利用返回的匿名函數(shù)不斷地的向閉包體內(nèi)的數(shù)組 a 傳入新值,傳入的值會持續(xù)存在。

        示例2

        在網(wǎng)頁中事件處理函數(shù)很容易形成閉包。

        <script> function f() {     var a = 1;     b = function () {         console.log("a =" + a);     }     c = function () {         a ++;     }     d = function () {         a --;     } } </script> <button onclick="f()">生成閉包</button> <button onclick="b()">查看 a 的值</button> <button onclick="c()">遞增</button> <button onclick="d()">遞減</button>

        在瀏覽器中瀏覽時,首先點擊“生成閉包”按鈕,生成一個閉包;點擊“查看 a 的值”按鈕,可以隨時查看閉包內(nèi)私有變量 a 的值;點擊“遞增”“遞減”按鈕時,可以動態(tài)修改閉包內(nèi)變量 a 的值,效果如圖所示。

        javascript閉包是作用域嗎

        閉包的局限性

        閉包的價值是方便在表達式運算過程中存儲數(shù)據(jù)。但是,它的缺點也不容忽視。

        • 由于函數(shù)調(diào)用后,無法注銷調(diào)用對象,會占用系統(tǒng)資源,在腳本中大量使用閉包,容易導(dǎo)致內(nèi)存泄漏。解決方法:慎用閉包,不要濫用。

        • 由于閉包的作用,其保存的值是動態(tài),如果處理不當(dāng)容易出現(xiàn)異常或錯誤。

        示例

        設(shè)計一個簡單的選項卡效果。HTML 結(jié)構(gòu)如下:

        <div class="tab_wrap">     <ul class="tab" id="tab">         <li id="tab_1" class="hover">Tab1</li>         <li id="tab_2" class="normal">Tab2</li>         <li id="tab_3" class="normal">Tab3</li>     </ul>     <div class="content" id="content">         <div id="content_1" class="show"><img scr="image/1.jpg" height="200" /></div>         <div id="content_2" class="show"><img scr="image/2.jpg" height="200" /></div>         <div id="content_3" class="show"><img scr="image/3.jpg" height="200" /></div>     </div> </div>

        下面請看 JavaScript 腳本。

        window.onload = function () {     var tab = document.getElementById("tab").getElementsByTagName("li"),         content = document.getElementById("content").getElementByTagName("div");     for (var i = 0; i < tab.length;i ++) {         tab[i].addEventListener("mouseover"), function () {             for (var n = 0; n < tab.length; n ++) {                 tab[n].className = "normal";                 content[n].className = "none";             }             tab[i].className = "hover";             content[i].className = "show";         });     } }

        在 load 事件處理函數(shù)中,使用 for 語句為每個 li 屬性元素綁定 mouseover 事件;在 mouseover 事件處理函數(shù)中重置所有選項卡 li 的類樣式,然后設(shè)置當(dāng)前 li 選項卡高亮顯示,同時顯示對應(yīng)的內(nèi)容容器。

        但是在瀏覽器中預(yù)覽時,會發(fā)現(xiàn)瀏覽器拋出異常。

        SCRIPT5007:無法設(shè)置未定義或 null 引用的屬性"className"

        在 mouseover 事件處理函數(shù)中跟蹤變量 i 的值,i 的值都變?yōu)榱?3,tab[3] 自然是一個 null,所以也不能夠讀取 className 屬性。

        【原因分析】

        上面 JavaScript 代碼是一個典型的嵌套函數(shù)結(jié)構(gòu)。外部函數(shù)為 load 事件處理函數(shù),內(nèi)部函數(shù)為 mouseover 事件處理函數(shù),變量 i 為外部函數(shù)的私有變量。

        通過事件綁定,mouseover 事件處理函數(shù)被外界引用(li 元素),這樣就形成了一個閉包體。雖然在 for 語句中為每個選項卡 li 分別綁定事件處理函數(shù),但是這個操作是動態(tài)的,因此 tab[i] 中 i 的值也是動態(tài)的,所以就出現(xiàn)了上述異常。

        【解決方法】

        解決閉包的缺陷,最簡單的方法是阻斷內(nèi)部函數(shù)對外部函數(shù)的變量引用,這樣就形成了閉包體。針對本示例,我們可以在內(nèi)部函數(shù)(mouseover 事件處理函數(shù))外邊增加一層防火墻,不讓其直接引用外部變量。

        window.load = function () {     var tab = document.getElementById("tab").getElementsByTagName("li"),         content = document.getElementById("content").getElementsByTagName("div");     for (var i = 0; i < tab.length; i ++ ) {         (function (j) {             tab[j].addEventListener("number", function () {                 for (var n = 0; n < tab.length; n ++) {                     tab[n].className = "normal";                     content[n].className = "none";                 }                 tab[j].className = "hover";                 conteng[j].className = "show";             });         }) (i);     } }

        在 for 語句中,直接調(diào)用匿名函數(shù),把外部函數(shù)的 i 變量傳給調(diào)用函數(shù),在調(diào)用函數(shù)中接收這個值,而不是引用外部變量 i,規(guī)避了閉包體帶來的困惑。

        【推薦學(xué)習(xí):javascript高級教程】

        贊(0)
        分享到: 更多 (0)
        網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號
        主站蜘蛛池模板: 99精品免费视频| 久久精品无码一区二区三区免费| 亚洲国产精品成| 真实国产乱子伦精品一区二区三区| 国产福利电影一区二区三区,亚洲国模精品一区 | 国产精品H片在线播放| 国产成人无码久久久精品一| 合区精品中文字幕| 国产麻豆精品入口在线观看 | 中文字幕精品一区影音先锋| 国产三级精品三级在线专区1 | 国产精品欧美久久久久天天影视| 国产精品国产精品国产专区不卡| 久久精品中文騷妇女内射| 人妻少妇精品久久| 91精品国产人成网站| 精品国产日产一区二区三区| 国产精品原创巨作av女教师| 久久亚洲私人国产精品vA| 亚洲韩国精品无码一区二区三区 | 无翼乌无遮挡全彩老师挤奶爱爱帝国综合社区精品 | 亚洲中文字幕无码久久精品1| 青草国产精品视频。| 免费精品精品国产欧美在线| 免费人欧美日韩在线精品 | 国内精品久久久久久不卡影院| 成人午夜精品亚洲日韩| 欧美精品第欧美第12页| 久久er热视频在这里精品| 精品国产福利第一区二区三区| 国产亚洲精品国产| 好属妞这里只有精品久久| 97精品国产一区二区三区| 丰满人妻熟妇乱又仑精品| 92国产精品午夜福利| 国产精品igao视频网| .精品久久久麻豆国产精品| 国产精品久久久久久福利69堂| 国产精品你懂的| 国产成人无码精品久久久免费 | 国产精品玖玖美女张开腿让男人桶爽免费看 |