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