|
習(xí)慣于OOP語(yǔ)言編程后,會(huì)發(fā)現(xiàn)Javascript世界有很多匪夷所思的奇奇怪怪的現(xiàn)象(比如閉包),我花了大量的精力研究這些奇怪現(xiàn)象的根源,最后發(fā)現(xiàn):源自于Javascript的作用域不是塊級(jí)作用域,同時(shí)它有一套基于作用域鏈的標(biāo)識(shí)查找機(jī)制。本文大部分內(nèi)容來(lái)自互聯(lián)網(wǎng),經(jīng)過(guò)整理、改進(jìn)而成。
- Javascript引擎和DOM采用的垃圾回收算法:引用計(jì)數(shù)
Javascript和DOM有各自的垃圾回收器,單獨(dú)運(yùn)作良好,合作時(shí)一不小心會(huì)出問(wèn)題。引用計(jì)數(shù)這個(gè)算法的缺陷就是:Javascript 對(duì)象和DOM對(duì)象彼此循環(huán)引用,造成彼此的引用計(jì)數(shù)永遠(yuǎn)不能為0,垃圾回收器無(wú)法正確回收這些參與循環(huán)引用的對(duì)象,最終造成內(nèi)存泄漏(Memory Leak)。閉包是循環(huán)引用“大戶”。如果對(duì)垃圾回收感興趣,可以看看 垃圾收集趣史 - 詞法作用域(lexical scope,一般簡(jiǎn)稱(chēng)作用域)、with/eval
簡(jiǎn)單來(lái)說(shuō)Javascript的作用域是由function劃分的。讀完這篇文章你會(huì)了解詞法作用域 Javascript運(yùn)行機(jī)制淺探,with/eval這 兩個(gè)特例會(huì)擾亂作用域,即所謂動(dòng)態(tài)作用域(dynamic scope) - 作用域鏈(Scope Chain) 和 標(biāo)識(shí)查找機(jī)制
作用域鏈?zhǔn)且粋€(gè)鏈表(數(shù)據(jù)結(jié)構(gòu)),它是Javascript的靈魂,只有理解了它才能理解Javascript世界奇奇怪怪的現(xiàn)象。作用域鏈由活動(dòng)對(duì)象鏈成。
標(biāo)識(shí)查找機(jī)制稍后結(jié)合函數(shù)執(zhí)行的原理加以說(shuō)明。 - 活動(dòng)對(duì)象(call object)
國(guó)內(nèi)很多人稱(chēng)之為調(diào)用對(duì)象(call object),本文用英文call obejct(但我私下認(rèn)為翻譯為"活動(dòng)對(duì)象"更好,不至于和this所指的對(duì)象混淆。)
非常特殊的Javascript引擎內(nèi)的對(duì)象,ECMAScript規(guī)范術(shù)語(yǔ)稱(chēng)之為activation object(活動(dòng)對(duì)象)。多個(gè)call object和全局對(duì)象組成作用域鏈(scope chain ) - 函數(shù)的本質(zhì)(有名函數(shù)、匿名函數(shù))、函數(shù)的[[scope]]屬性 函數(shù)在Javascript里面是一個(gè)特殊的引用類(lèi)型 ,它繼承于位于Javascript世界最頂端的object,類(lèi)型是Function,是其他常見(jiàn)引用類(lèi)型的構(gòu)造函數(shù)的所屬類(lèi)型。
在定義函數(shù)的時(shí)候,Javascript引擎會(huì)為function對(duì)象的一個(gè)私有[[scope]]屬性賦值,理論上只有js引擎自己才能訪問(wèn)(也即:一般情況下無(wú)法通過(guò)語(yǔ)法來(lái)訪問(wèn),但Firefox下有一個(gè)__parent___可以訪問(wèn)到)。匿名函數(shù)的[[scope]]屬性指向匿名函數(shù)定義時(shí)的上下文對(duì)象;有名函數(shù)除了和匿名函數(shù)一樣,還會(huì)在[[scope]]屬性的頂端再指向一個(gè)Javascript對(duì)象(繼承自obejct.prototype),這個(gè)對(duì)象被鏈接到函數(shù)定義時(shí)的Scope Chain,他本身帶有一個(gè)屬性就是函數(shù)的名字,這確保函數(shù)內(nèi)部的代碼可以無(wú)誤地訪問(wèn)到自己的函數(shù)名以便進(jìn)行遞歸。
當(dāng)定義函數(shù)的時(shí)候,Javascript解析器會(huì)將函數(shù)的作用域鏈(scope chain)設(shè)置為定義函數(shù)時(shí)函數(shù)所在的“環(huán)境”,如果函數(shù)是一個(gè)全局函數(shù),則scope chain中只有window對(duì)象。
當(dāng)執(zhí)行函數(shù)時(shí)的微觀世界,請(qǐng)看稍后的說(shuō)明。 - 閉包(closure)
Javascript所有的函數(shù)都是閉包,但是只有嵌套形式的閉包(也是我們經(jīng)常討論的形式)才能體現(xiàn)這個(gè)Javascript 特性的強(qiáng)大。推薦閱讀這篇文章: 深入理解JavaScript閉包(closure) - 函數(shù)執(zhí)行時(shí)的作用域鏈和活動(dòng)對(duì)象是如何形成的及與閉包的關(guān)系
1、Javascript解析器啟動(dòng)時(shí)就會(huì)初始化建立一個(gè)全局對(duì)象global object,這個(gè)全局對(duì)象就 擁有了一些預(yù)定義的全局變量和全局方法,如Infinity, parseInt, Math,所有程序中定義的全局變量都是這個(gè)全局對(duì)象的屬性。在客戶端Javascript中,Window就是這個(gè)Javascript的全局對(duì)象。
2、當(dāng)Javascript執(zhí)行一個(gè)function時(shí),會(huì)生成一個(gè)對(duì)象,稱(chēng)之為call object,function中的局部變量和function的參數(shù)都成為這個(gè)call object的屬性,以免覆寫(xiě)同名的全局變量。
3、Javascript解析器每次執(zhí)行function時(shí),都會(huì)為此function創(chuàng)建一個(gè)execution context執(zhí)行環(huán)境,在此function執(zhí)行環(huán)境中最重要的一點(diǎn)就是function的作用域鏈scope chain,這是一個(gè)對(duì)象鏈,由全局對(duì)象和活動(dòng)對(duì)象構(gòu)成,對(duì)象鏈具體構(gòu)成過(guò)程見(jiàn)下面說(shuō)明。
4、標(biāo)識(shí)的查找機(jī)制:當(dāng)Javascript查詢變量x的值時(shí),就會(huì)檢查此作用域鏈中第一個(gè)對(duì)象,可能是function的call object或全局對(duì)象(比如window),如果對(duì)象中有定義此x屬性,則返回值,不然檢查作用域鏈中的下一個(gè)對(duì)象是否定義x屬性,在作用域鏈中沒(méi)有找到,最后返回undefined。
5、當(dāng)Javascript執(zhí)行一個(gè)function時(shí),它會(huì)先將此function定義時(shí)的作用域作為其作用域鏈,然后創(chuàng)建一個(gè)活動(dòng)對(duì)象(call object),置于作用域鏈的頂部,function的參數(shù)及內(nèi)部var聲明的所有局部變量都會(huì)成為此調(diào)用對(duì)象的屬性。
6、this關(guān)鍵詞指向方法的調(diào)用者,而不是以調(diào)用對(duì)象的屬性存在,同一個(gè)方法中的this在不同的function調(diào)用中,可能指向不同的對(duì)象。
7、The Call Object as a Namespace。將活動(dòng)對(duì)象當(dāng)作命名空間使用,避免命名污染。
(function() {
// 在方法體內(nèi)用var聲明的所有局部變量,都是以方法調(diào)用時(shí)創(chuàng)建的活對(duì)象的屬性形式 存在。
// 這樣就避免與全局變量發(fā)生命名沖突。
})();
8、Javascript中所有的function都是一個(gè)閉包,但只有當(dāng)一個(gè)嵌套函數(shù)被導(dǎo)出到它所定義的作用域外時(shí),這種閉包才強(qiáng)大。如果理解了閉包,就會(huì)理解function執(zhí)行時(shí)的作用域鏈和活動(dòng)對(duì)象,才能真正掌握Javascript。
9、嵌套閉包的微觀世界:在嵌套閉包時(shí),當(dāng)內(nèi)部函數(shù)的引用被保存到嵌套閉包之外一個(gè)全局變量或者一個(gè)對(duì)象的屬性時(shí),在這種情況下,此內(nèi)部函數(shù)有一個(gè)外部引用,并且在其外圍調(diào)用函數(shù)的活動(dòng)對(duì)象中有一個(gè)屬性指向此內(nèi)部函數(shù)。因?yàn)橛衅渌麑?duì)象引用此內(nèi)部函數(shù),所以在外圍函數(shù)被調(diào)用一次后,其創(chuàng)建的活動(dòng)對(duì)象會(huì)繼續(xù)存在,并不會(huì)被垃圾回收器回收(因?yàn)橐糜?jì)數(shù)不為0),內(nèi)部函數(shù)的參數(shù)和局部變量都會(huì)在這個(gè)活動(dòng)對(duì)象中得以維持,Javascript代碼任何形式都不能直接訪問(wèn)此活動(dòng)對(duì)象,但是此活動(dòng)對(duì)象是內(nèi)部函數(shù)被調(diào)用時(shí)創(chuàng)建的作用域鏈的一部分,可以被內(nèi)部函數(shù)訪問(wèn)并修改。
最后介紹一個(gè)奇怪現(xiàn)象:下面的代碼,為什么鼠標(biāo)移動(dòng)到li上,title總是6,而不是我們所預(yù)想的數(shù)字呢?看你能不能根據(jù)以上的知識(shí),解釋這種現(xiàn)象的原因。提示:變量查找機(jī)制。
<html>
<head>
<title>循環(huán)內(nèi)的閉包 應(yīng)該謹(jǐn)慎title>
head>
<body>
<ul id="list">
<li>第1條記錄li>
<li>第2條記錄li>
<li>第3條記錄li>
<li>第4條記錄li>
<li>第5條記錄li>
<li>第6條記錄li>
ul>
<script type="text/Javascript">
var list_obj = document.getElementById("list").getElementsByTagName("li"); //獲取list下面的所有l(wèi)i的對(duì)象數(shù)組
for (var i = 0; i <= list_obj.length; i++) {
list_obj[i].onmousemove = function() {
this.style.backgroundColor = "#eee";
document.title=i
};
list_obj[i].onmouseout = function() {
this.style.backgroundColor = "#fff";
}
}
<.script>
body>
html>
it知識(shí)庫(kù):向高級(jí)Javascript程序員陣營(yíng)邁進(jìn):Javascript一些概念研究總結(jié),轉(zhuǎn)載需保留來(lái)源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。