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

資訊專欄INFORMATION COLUMN

請用一句優(yōu)雅的話表達javascript閉包

mochixuan / 1008人閱讀

摘要:局部變量只在當前函數(shù)體內(nèi)有效,出了函數(shù)體,就上一級的范圍,局部變量無效。但是在中,函數(shù)內(nèi)部有一個函數(shù),它的函數(shù)體內(nèi)的是指中聲明的局部變量,而非全局變量。這就是一個非常典型的閉包了。

嚴格的講,閉包常常表現(xiàn)為一個函數(shù)內(nèi)部的函數(shù),它使用了非自己定義的、自己所在作用域內(nèi)的變量,并且使這些變量突破了作用域的限制。

函數(shù)內(nèi)部的函數(shù):私有函數(shù)

首先,我們從這個內(nèi)部函數(shù)去說開,因為這個是形式上的,如果一開始講作用域,有點故意。閉包在形式上就是函數(shù)內(nèi)部的函數(shù),比如:

jQuery(function($){ 
    function message(msg) { 
        alert(msg); 
    } 
 
    if($(window).width() > 1000) message("window寬度大于1000"); 
});

如果你用jquery,這段代碼應該經(jīng)常使用吧。如果你仔細去觀察,就會發(fā)現(xiàn),第一個function被作為參數(shù),傳給了jQuery()這個函數(shù),而在function內(nèi),又有一個message()函數(shù)。所有的jQuery代碼被放在第一個function中去處理。第二個函數(shù)就是函數(shù)體內(nèi)部的函數(shù),這個函數(shù)在函數(shù)體內(nèi)聲明,一旦外層函數(shù)執(zhí)行完畢,那么這個函數(shù)就失去了作用,在jQuery()外無法使用message(),因此,message()是第一個函數(shù)內(nèi)部的私有函數(shù)。

變量的作用域

函數(shù)內(nèi)部的變量有兩種,一種是局部變量,一種是全局變量。局部變量只在當前函數(shù)體內(nèi)有效,出了函數(shù)體,就上一級的范圍,局部變量無效。

var age = 10; 
function a(age) { 
    return age + 1; 
} 
function b(_age) { 
    return age + _age; 
} 
function c(_age) { 
    var age = 11; 
    function add() { 
        return age + _age; 
    } 
    return add(); 
} 
 
alert(a(9)); // 10 : 9 + 1 
alert(b(2)); // 12 : 10 + 2 
alert(c(5)); // 16 : 11 + 5

在上面的代碼中,我們看b和c函數(shù)。b函數(shù)中的age直接引用了全局變量age(10),而c函數(shù)中重新聲明了局部變量age,因此,全局變量age在c函數(shù)中無效。

但是在c中,函數(shù)內(nèi)部有一個函數(shù)add(),它的函數(shù)體內(nèi)的age是指c中聲明的局部變量,而非全局變量age。從這個例子里,反映出了變量的作用域,函數(shù)內(nèi)的函數(shù)體里,如果沒有聲明局部變量,就會承認其父級甚至祖先級函數(shù)的變量,以及全局變量。

閉包

怎么樣才算是一個閉包呢?我們來看下面的代碼:

function a() { 
    var age = 10; 
    return function(){ 
        return age; 
    } 
} 
 
var age = a(); 
alert(age()); // 10

按照我們前面說的作用域,在上面這段代碼中age是a()的局部變量,按道理,出了函數(shù),就不能被訪問了。但是,a()返回了一個私有函數(shù),個這個函數(shù)返回了age,這導致我們可以在a()外部,仍然可以訪問到本來是局部變量的age,這個時候,我們就把這個內(nèi)部函數(shù)稱為閉包。它的原理就是,函數(shù)內(nèi)部的函數(shù),可以訪問父函數(shù)的局部變量。

綜合上面的闡述,我們要這樣去理解閉包:

閉包是一個函數(shù),它使用了自己之外的變量。

閉包是一個作用域。

閉包是“由函數(shù)和與其相關的引用環(huán)境組合而成的實體”。

嚴格的講,閉包常常表現(xiàn)為一個函數(shù)內(nèi)部的函數(shù),它使用了非自己定義的、自己所在作用域內(nèi)的變量,并且使這些變量突破了作用域的限制。

一個典型的閉包:

函數(shù)內(nèi)的函數(shù)

這個內(nèi)部函數(shù)引用了父函數(shù)的局部變量

這個內(nèi)部函數(shù)使引用的變量突破了作用域限制

var a = 1;
function fun() {return a + 1;}
alert(fun());

這也可以算作一個閉包,a()引用了它之外定義的變量。但是這不算嚴格的閉包,因為它沒有在突破作用域的這個點上表現(xiàn)出來。

var a = 1; 
function fun() { 
    var b = 2; 
    return function(){ 
        return a + ++b; 
    }; 
} 
var c = fun(); 
alert(c()); // 4 
alert(c()); // 5

這就是一個非常典型的閉包了。而且為什么alert(c())兩次的值不同,我們還會在下面解釋到。

為了讓你更加明晰的和你曾經(jīng)開發(fā)過的案例聯(lián)系在一起,我們來看我們曾經(jīng)做過的這樣的事:

define(function(){ 
    var age = 10; 
 
    function getAge() { 
        return age; 
    } 
    function grow() { 
        age ++; 
    } 
 
    return { 
        age : getAge, 
        grow : grow 
    }; 
});

這是我們在require.js中的一種寫法,把它還原為我們熟悉的閉包模式:

function Cat(){ 
    var age = 10; 
 
    function getAge() { 
        return age; 
    } 
    function grow() { 
        age ++; 
    } 
 
    return { 
        ageAge : getAge, 
        grow : grow 
    }; 
}; 
var cat = Cat(); 
var age = cat.getAge(); 
alert(age); // 10 
cat.grow(); 
age = cat.getAge(); 
alert(age); // 11

從內(nèi)存看閉包

現(xiàn)在,我們就要來解釋為什么上面的alert()兩次的結(jié)果不同的原因了。

首先,我們來看下普通的函數(shù)聲明和使用過程中內(nèi)存的變化:

function fun(a,b) { 
  return a+b; 
} 
alert(fun(1,2)); 
alert(fun(3,4));

上面是我們沒有遇到閉包的情況,內(nèi)存我們這樣來畫(注意,我這里只是抽象的畫出內(nèi)存變化,而不是真實的javascript內(nèi)存機制。)

【圖片缺失,請查看文末的原文鏈接,原文中圖片正?!?/p>

在每一次執(zhí)行完fun()之后,fun()函數(shù)的執(zhí)行環(huán)境被釋放(回收機制)。

接下來我們來看一個閉包:

function fun(a) { 
    return function(b){return a + b;} 
} 
var add = fun(2); 
alert(add(2)); 
alert(add(4));

上面就出現(xiàn)閉包了,注意,我們這里出現(xiàn)了一個add變量。

【圖片缺失,請查看文末的原文鏈接,原文中圖片正?!?/p>

在后兩步中,實際上fun(2)部分沒有任何變化,所變的,則是在內(nèi)部函數(shù)所對應的內(nèi)存區(qū)域中有變化。細心的你,可能會發(fā)現(xiàn)這里面的問題所在,當執(zhí)行完add(2)之后,fun對應的內(nèi)存沒有被釋放掉,而它的內(nèi)部函數(shù),也就是function(2)被釋放掉了,在執(zhí)行add(4)的時候,僅僅重新運行了內(nèi)部函數(shù)。如果連fun()對應的內(nèi)存出現(xiàn)了變化怎么辦?我們來看下面的例子:

function fun() { 
    var b = 2; 
    return function(a){ 
        return a + ++b; 
    }; 
} 
var c = fun(); 
alert(c(1)); // 4 
alert(c(1)); // 5

注意,這可是個非常典型的閉包的例子,它有一個局部變量b,我們來看它的內(nèi)存圖。

【圖片缺失,請查看文末的原文鏈接,原文中圖片正?!?/p>

注意第2、3、4步中內(nèi)存的變化。第2步時,我們僅僅將fun()賦給變量c,這個時候,內(nèi)部函數(shù)function(a)并沒有被執(zhí)行,所以++b也沒有被執(zhí)行,b的值還是2。但是第3步開始,++b被先執(zhí)行,++b的意思是先自加,再進行運算,和b++是不同的,如果是b++,雖然c(1)的最終結(jié)果還是為4,但是在c(1)執(zhí)行開始時,b應該為2,執(zhí)行完之后才是3。

奇妙的事情發(fā)生了,在內(nèi)部函數(shù)中,++b導致了局部變量值發(fā)生了變化,b從2變成了3,而且,內(nèi)存并沒有被釋放,fun()的執(zhí)行環(huán)境沒有被銷毀,b還被保存在內(nèi)存中。到第4步時,b的初始值是3,經(jīng)過++b 之后,變成了4。

這個內(nèi)存分析非常形象的把閉包概念中,關于“突破作用域限制”這個點描述的非常清楚,原本按照作用域的限制,函數(shù)的局部變量不能被外部環(huán)境訪問,更不能被修改,但是閉包卻使得外部環(huán)境不僅可以讀取到局部變量的內(nèi)容,甚至可以修改它,深入一點就是:閉包會導致閉包函數(shù)所涉及到的非自身定義的變量一直保存在內(nèi)存中,包括其父函數(shù)在內(nèi)的相關環(huán)境都不會被銷毀。

閉包到底有什么用?

說了這么多,那閉包到底有什么用,我們?yōu)槭裁匆褂瞄]包呢?從上面的闡述中,你應該已經(jīng)知道了閉包的唯一作用:突破作用域限制。那如何使用這個作用為程序服務呢?

常駐內(nèi)存,意味著讀取速度快(,當然,內(nèi)存花銷也大,導致內(nèi)存溢出)。常駐內(nèi)存,意味著一旦初始化以后,就可以反復使用同一個內(nèi)存中的某個對象,而無需再次運行程序。而這一點,是很多插件、模塊的設計思想。

最好的例子就是上文我舉得那個define()的例子,后面用我們今天所了解的形式去實踐之后,你就會發(fā)現(xiàn)原來可以把function當做一個其他語言中的class來對待,cat.getAge(), cat.grow()這樣的操作是不是很符合我們在編程中的使用習慣呢?一旦一個產(chǎn)生之后,這個cat就一直在內(nèi)存中,隨時可以拿出來就用,它就是一個實例化對象。

為了更形象,我們來創(chuàng)建一個簡單的代碼塊:

function Animal() { 
    this.age = 1; 
    this.weight = 10; 
    return { 
        getAge : function(){ 
            return this.age; 
        }, 
        getWeight : function(){ 
            return this.weight; 
        }, 
        grow : function(){ 
             this.age ++; 
             this.weight = this.age * 10 * 0.8; 
        } 
    }; 
} 
function Cat() { 
    var cat = new Animal(); // 繼承 
    cat.grow = function(){ 
        cat.age ++; 
        cat.weight = cat.age * 10 * 0.6; 
    } 
    return cat; 
} 
 
var cat1 = new Cat(); 
alert(cat1.getAge()); 
cat1.grow(); 
alert(cat1.getAge());

為什么要舉這個例子呢,因為我想讓你想象這樣一種場景,如果沒有閉包怎么辦?

沒有閉包是這樣的一種狀態(tài):函數(shù)無法訪問自己父級(祖先,全局)對象的變量。比如:

var a = 1; 
function add() { 
    return ++a; // 如果沒有閉包機制,會undefined報錯 
}

這種情況怎么辦?必須以參數(shù)的形式傳入到函數(shù)中:

var a = 1; 
function add(a) { 
    return ++a; 
} 
alert(add(a)); // 2

如果是這樣,就很麻煩了,你需要在每一個函數(shù)中傳入變量。而更麻煩的是,沒有了作用域的突破,例如:

function Cat() { 
    age = 1; 
    function getAge(age) { 
        return age; 
    } 
    function grow(age) { 
        age ++; 
    } 
    return { 
        getAge : getAge, 
        grow : grow 
    } 
} 
var cat = new Cat(); 
cat.grow(); 
alert(cat.getAge()); // 1,沒有被修改

這種情況下,我們無論如何都無法使用這種辦法來實現(xiàn)我們的目的。唯一能夠?qū)崿F(xiàn)的,就是按照下面這種方法:

var cat = { 
    age : 1, 
    weight : 10, 
    grow : function(){ 
        this.age ++; 
        this.weight += 3; 
    } 
}; 
var cat1 = cat; 
alert(cat1.age); 
cat1.grow(); 
alert(cat1.age);

我們聰明的使用到了this關鍵字,但是這樣一個壞處是,age, weight這些屬性直接暴露給外部,我們只需要執(zhí)行 cat1.age = 12; 就可以馬上讓cat長到12歲,而體重卻沒任何變化。

總結(jié)而言,閉包可以帶來這么幾個方面的應用優(yōu)勢:

常駐內(nèi)存,加快運行速度

封裝

閉包使用中的注意點

除了上面提到的內(nèi)存開銷問題外,還有一個需要被注意的地方,就是閉包所引用的外部變量,在一些特殊情況下會存在與期望值不同的誤差。

function init() {      
  var pAry = document.getElementsByTagName("p");      
  for( var i=0; i

在上面這段代碼中,你希望通過一個循環(huán),來為每一個p標簽綁定一個click事件,然而不幸的是,for循環(huán)中使用的閉包函數(shù)沒有讓你如愿以償,在閉包函數(shù)中,i被認作pAry.length,也就是循環(huán)到最后i的最終值。為什么會這樣呢? 原來,閉包引用變量,而非直接使用變量,“引用”的意思是將指針指向變量的內(nèi)容。由于這個原因,當i=0的時候,閉包里面的i確實是0,但是當隨著i的值變大的時候,閉包內(nèi)的i并沒有保存當前值,而是繼續(xù)把指針指向i的內(nèi)容,當你點擊某個p標簽的時候,i的內(nèi)容實際上是for到最后i的值。

同樣,這個問題會出現(xiàn)在setTimeout,setInterval,$.ajax等這類操作中,你只要記住,當你綁定操作時,和執(zhí)行操作時,對應的變量是否已經(jīng)變化就OK了。

原文鏈接:http://www.tangshuang.net/2368.html

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

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

相關文章

  • JS 中的閉包是什么?

    摘要:大名鼎鼎的閉包面試必問。閉包的作用是什么??吹介]包在哪了嗎閉包到底是什么五年前,我也被這個問題困擾,于是去搜了并總結(jié)下來。關于閉包的謠言閉包會造成內(nèi)存泄露錯。閉包里面的變量明明就是我們需要的變量,憑什么說是內(nèi)存泄露這個謠言是如何來的因為。 本文為饑人谷講師方方原創(chuàng)文章,首發(fā)于 前端學習指南。 大名鼎鼎的閉包!面試必問。請用自己的話簡述 什么是「閉包」。 「閉包」的作用是什么。 首先...

    Enlightenment 評論0 收藏0
  • JavaScript 編寫規(guī)范

    摘要:如果你想了解更多關于強制類型轉(zhuǎn)換的信息,你可以讀一讀的這篇文章。在只使用的情況下,所帶來的強制類型轉(zhuǎn)換使得判斷結(jié)果跟蹤變得復雜,下面的例子可以看出這樣的結(jié)果有多怪了明智地使用真假判斷當我們在一個條件語句中使用變量或表達式時,會做真假判斷。 說明 如果本文檔中有任何錯誤的、不符合行規(guī)的,敬請斧正。 引言 不管有多少人共同參與同一項目,一定要確保每一行代碼都像是同一個人編寫的。...

    MartinDai 評論0 收藏0
  • Effective JavaScript讀書筆記(二)

    摘要:盡可能的使用局部變量,少用全局變量。正確的實現(xiàn)就是在函數(shù)體內(nèi)部使用將聲明成局部變量。在新特性中,引入了塊級作用域這個概念,因此還可以使用,來聲明局部變量。它們共享外部變量,并且閉包還可以更新的值。 變量作用域 作用域,對于JavaScript語言來說無處不在,變量作用域,函數(shù)作用域(運行時上下文和定義時上下文),作用域污染等等都跟作用域息息相關,掌握JavaScript作用于規(guī)則,可以...

    Yuqi 評論0 收藏0
  • JavaScript之對象創(chuàng)建

    摘要:在構(gòu)造函數(shù)的內(nèi)部,的指向是新創(chuàng)建的對象。如果構(gòu)造函數(shù)沒有顯式的表達式,則會隱式的返回新創(chuàng)建的對象對象。原型模式在構(gòu)造函數(shù)模式中提到每次之后創(chuàng)建的新的對象是互相獨立的,是獨享的。 1.構(gòu)造函數(shù)模式 JavaScript中的構(gòu)造函數(shù)是通過new調(diào)用的,也就是說,通過new關鍵字調(diào)用的函數(shù)都被認為是構(gòu)造函數(shù)。 在構(gòu)造函數(shù)的內(nèi)部,this的指向是新創(chuàng)建的對象Object。 如果構(gòu)造函數(shù)沒有顯式...

    Michael_Lin 評論0 收藏0

發(fā)表評論

0條評論

mochixuan

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<