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