1、執行環境及作用域
(1).執行環境:
定義了變量或函數有權訪問的其它數據,決定了它們的各自行為。每個執行環境都有一個與之關聯的變量對象(variable object, VO),執行環境中定義的所有變量和函數都會保存在這個對象中,解析器在處理數據的時候就會訪問這個內部對象。
全局執行環境是最外層的一個執行環境,在web瀏覽器中全局執行環境是window對象,因此所有全局變量和函數都是作為window對象的屬性和方法創建的。每個函數都有自己的執行環境,當執行流進入一個函數的時候,函數的環境會被推入一個函數棧中,而在函數執行完畢后執行環境出棧並被銷毀,保存在其中的所有變量和函數定義隨之銷毀,控制權返回到之前的執行環境中,全局的執行環境在應用程序退出(瀏覽器關閉)才會被銷毀。
(2).作用域:
作用域就是變量和函數的可訪問范圍,控制着變量和函數的可見性與生命周期,在JavaScript中變量的作用域有全局作用域和局部作用域。
全局作用域:任何地方都可以定義擁有全局作用域的變量。
a.沒有用var聲明的變量(除去函數的參數)都具有全局作用域,成為全局 變量,所以聲明局部變量必須要用var。
b.window的所有屬性都具有全局作用域
c.最外層函數體外聲明的變量也具有全局作用域
局部作用域:局部變量的優先級高於全局變量。
a.函數體內用var聲明的變量具有局部作用域,成為局部變量
b.函數的參數也具有局部作用域
JavaScript是函數作用域(function scope),沒有塊級作用域。無論函數體內的變量在什么地方聲明,對整個函數都是可見的,即JavaScript函數里聲明的所有變量都被提前到函數體的頂部,只是提前變量聲明,變量的賦值還是保留在原位置
(3).作用域鏈:
JavaScript的變量都是對象的屬性,而該對象可能又是其它對象的屬性,而所有的對象都是全局對象的屬性,所以這些對象的關系可以看作是一條鏈,由於每個對象都有一個作用域,所以形成了一個作用域鏈。鏈頭就是變量所處的對象,鏈尾就是全局對象。
當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈來保證對執行環境有權訪問的變量和函數的有序訪問。
2、原型鏈、繼承
(1)理解原型對象
1).只要創建了一個新函數,就會為該函數創建一個prototype屬性,這個屬性指向函數的原型對象;
2).所有原型對象都會自動獲得一個constructor(構造函數)屬性,這個屬性包含一個指向prototype屬性所在函數的指針;
3).當調用構造函數創建一個新實例后,該實例的內部將包含一個指針(內部屬性),指向構造函數的原型對象。
(2)原型鏈
綜上所述,假如我們讓原型對象等於另一個類型的實例,則此時的原型對象將包含一個指向另一個原型的實例,相應地,另一個原型中也包含着一個指向另一個構造函數的指針。假如另一個原型又是另一個類型的實例,那么上述關系依然成立,如此層層遞進,就構成了實例與原型的鏈條,這就是原型鏈。
【注】:所有引用類型默認都繼承了Object,而這個繼承也是通過原型鏈實現的。所有函數的默認原型都是Object的實例,因此默認原型都會包含一個內部指針,指向Object.prototype。這也正是所有自定義類型都會繼承toString(),valueOf()等默認方法的根本原因。
(3)繼承
1).通過原型鏈實現。不能使用對象字面量創建原型方法,因為這樣會重寫原型鏈。
缺點:最主要的問題是包含引用類型值的原型;二是在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。
2)借用構造函數。在子類型構造函數的內部調用超類型構造函數。使用apply()和call()方法,可以向超類型的構造函數中傳遞參數。apply()和call()的作用一樣,只是專遞的參數形式不同,apply()的參數是以數組的形式傳遞,call()中是展開的形式。
缺點:函數復用無從談起,而且在超類型的原型中定義的方法,對子類型而言是不可見的。
3).組合繼承(原型鏈+借用構造函數)。避免了1)、2)的缺陷,融合了他們的優點,成為javascript最常用的繼承模式,而且,instanceof和isPrototypeOf()也能夠用於識別基於組合繼承創建的對象。
3、閉包
(1).概念:有權訪問另一個函數作用域中的變量的函數。簡單理解為“定義在一個函數內部的函數”。
(2).好處:保護函數內的變量安全,加強了封裝性;在內存中維持一個變量(緩存);匿名自執行函數;模擬面向對象編程。
(3).應用場景:使用閉包代替全局變量;函數外或在其他函數中訪問某一函數內部的參數;包裝相關功能;為節點循環綁定click事件,在事件函數中使用當次循環的值或節點,而不是最后一次循環的值或節點;
(4).缺點:常駐內存,會增大內存使用量,使用不當很容易造成內存泄露,更重要的是,對閉包的使用不當會造成無效內存的產生。