摘要:檢查當前上下文中的參數,建立該對象下的屬性與屬性值。檢查當前上下文的函數聲明,也就是使用關鍵字聲明的函數。如果該變量名的屬性已經存在,為了防止同名的函數被修改為,則會直接跳過,原屬性值不會被修改。
上一篇:《javascript高級程序設計》筆記:內存與執行環境
上篇文章中說到:
(1)當執行流進入函數時,對應的執行環境就會生成
(2)執行環境創建時會生成變量對象,確定作用域鏈,確定this指向
(2)每個執行環境都有一個與之關聯的變量對象,環境中定義的所有變量和函數都保存在這個對象中
變量對象是在進入執行環境就確定下來的,顧名思義,變量對象是用于存儲在這個執行環境中的所有變量和函數的一個對象。只是這個對象是用于解析器處理數據時使用,我們無法直接調用
下圖描述了執行流在執行環境中的執行過程(執行環境的生命周期)
(1)建立arguments對象。檢查當前上下文中的參數,建立該對象下的屬性與屬性值。
(2)檢查當前上下文的函數聲明,也就是使用function關鍵字聲明的函數。在變量對象中以函數名建立一個屬性,屬性值為指向該函數所在內存地址的引用。如果函數名的屬性已經存在,那么該屬性將會被新的引用所覆蓋。
(3)檢查當前上下文中的變量聲明,每找到一個變量聲明,就在變量對象中以變量名建立一個屬性,屬性值為undefined。如果該變量名的屬性已經存在,為了防止同名的函數被修改為undefined,則會直接跳過,原屬性值不會被修改。
總之:function聲明會比var聲明優先級更高一點
下面通過具體的例子來看變量對象:
function test() { console.log(a); console.log(foo()); var a = 1; function foo() { return 2; } } test();
當執行到test()時會生成執行環境testEC,具體形式如下:
// 創建過程 testEC = { // 變量對象(variable object) VO: {}, // 作用域鏈 scopeChain: [], // this指向 this: {} }
僅針對變量對象來具體展開:
VO = { // 傳參對象 arguments: {}, // 在testEC中定義的function foo: "", // 在testEC中定義的var a: undefined }
未進入執行階段之前,變量對象中的屬性都不能訪問!但是進入執行階段之后,變量對象轉變為了活動對象,里面的屬性都能被訪問了,然后開始進行執行階段的操作
// 執行階段 VO -> AO // Active Object AO = { arguments: {...}, foo: function(){return 2}, a: 1 }
最后我們將變量對象創建時的VO和執行階段的AO整合到一起就可以得到整個執行環境中代碼的執行順序:
function test() { function foo() { return 2; } var a; console.log(a);// undefined console.log(foo());// 2 a = 1; } test();
這個就是分步變量對象的創建和執行階段來解讀預解析
2. 預解析預解析分為變量提升和函數提升
變量提升:提升的是當前變量的聲明,賦值還保留在原來的位置
函數提升:函數聲明,可以認為是把整個函數體聲明了
函數表達式方式定義函數相當于變量聲明 var fn = function(){}
一個簡單的變量提升的例子
!function(){ console.log(a); var a = 1; }() // 實際執行順序 !function(){ var a; console.log(a); a = 1; }()
技巧:
將執行過程手動拆分成兩個步驟
1.創建階段:也稱作編譯階段,主要是變量的聲明和函數的定義(找var和function)
2.執行階段:變量賦值和函數執行
這樣看預解析還是比較容易的,但是涉及到命名沖突時,又是怎樣的情況呢?
(1)一個函數和一個變量出現同名
情況一:變量只聲明了,但沒有賦值
!function(){ var f; function f() {}; console.log(f); // f(){} }()
情況二:變量只聲明且賦值
!function(){ console.log(f); // f(){} var f = 123; function f() {}; console.log(f); // 123 }()
結論:
1.一個函數和一個變量出現同名,如果是變量只聲明了,但沒有賦值,變量名會被忽略
2.一個函數和一個變量出現同名,變量聲明且賦值,賦值前值為函數,賦值后為變量的值
(2)兩個變量出現同名
!function(){ console.log(f); // undefined var f = 123; var f = 456; console.log(f); // 456 }()
結論:
兩個變量出現同名,重復的var聲明無效,會被忽略,只會起到賦值的作用,前面賦值會被后面的覆蓋
(3)兩個函數出現同名
!function(){ console.log(f); // function f(){return 456}; function f(){return 123}; function f(){return 456}; console.log(f); // function f(){return 456}; }()
結論:
兩個函數出現同名,由于javascript中函數沒有重載,前面的同名函數會被后面的覆蓋
(4)變量名與參數名相同
函數參數的本質是什么?函數參數也是變量,相當于在該函數的執行環境內最頂部聲明了實參
情況一:參數為變量,與變量命名沖突
(function (a) { console.log(a); // 100 var a = 10; console.log(a); // 10 })(100); // 相當于 (function (a) { var a = 100; var a; // 重復聲明的var沒有意義,忽略 console.log(a); // 100 a = 10; console.log(a); // 10 })(100);
情況二:參數為函數,與變量命名沖突
(function (a) { console.log(a); // function(){return 2} var a = 10; console.log(a); // 10 })(function(){return 2}); // 相當于 (function (a) { var a = function(){return 2}; var a; // 重復聲明的var沒有意義,忽略 console.log(a); // function(){return 2} a = 10; console.log(a); // 10 })(function(){return 2});
情況三:參數為空,與變量命名沖突
(function (a) { console.log(a); // undefined var a = 10; console.log(a); // 10 })(); // 相當于 (function (a) { var a; console.log(a); // undefined a = 10; console.log(a); // 10 })();
結論:
1.重名的是一個變量,傳入了參數(變量或函數):
如果是在變量賦值之前,此時獲取的是:參數的值!
如果是在變量賦值之后,此時獲取的是:變量的值!
2.重名的是一個變量,但是沒有傳入參數:
如果是在變量賦值之前,此時獲取的是:undefined! 如果是在變量賦值之后,此時獲取的是:變量的值!
(5)函數名與參數名相同
情況一:參數為變量,與函數命名沖突
(function (a) { console.log(a); // a(){} function a(){}; console.log(a); // a(){} })(100); // 相當于 (function (a) { var a = 100; function a(){}; console.log(a); // a(){} console.log(a); // a(){} })(100);
情況二:參數為函數,與函數命名沖突
(function (a) { console.log(a); // function a(){return 1} function a(){return 1} console.log(a); // function a(){return 1} })(function(){return 2}); // 相當于 (function (a) { var a = function(){return 2}; function a(){return 1}; console.log(a); // function a(){return 1} console.log(a); // function a(){return 1} })(function(){return 2});
情況三:參數為空,與函數命名沖突
(function (a) { console.log(a); // function a(){}; function a(){}; console.log(a); // function a(){}; })(); // 相當于 (function (a) { var a; function a(){}; console.log(a); // function a(){}; console.log(a); // function a(){}; })();
注意:函數表達式聲明方式var f = function(){},在預解析中與普通的變量聲明無異,因為預解析階段主要是通過var和function來區分函數與變量的
結論:
傳參為函數時,賦值前均為參數的值,賦值后為函數的值,傳空時均為函數的值
現在,我們回到變量對象中,在變量對象的創建階段(執行流編譯階段),分別確定了arguments對象、函數聲明和變量聲明,其優先級也是arguments>function>var,arguments就是參數,反推上面變量提升的結論,同樣是成立的
經典推薦:《javascript高級程序設計》筆記:原型圖解
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/89969.html
摘要:因此,所有在方法中定義的變量都是放在棧內存中的當我們在程序中創建一個對象時,這個對象將被保存到運行時數據區中,以便反復利用因為對象的創建成本通常較大,這個運行時數據區就是堆內存。 上一篇:《javascript高級程序設計》筆記:繼承近幾篇博客都會圍繞著圖中的知識點展開 showImg(https://segmentfault.com/img/bVY0C4?w=1330&h=618);...
摘要:建筑的頂層代表全局作用域。實際的塊級作用域遠不止如此塊級作用域函數作用域早期盛行的立即執行函數就是為了形成塊級作用域,不污染全局。這便是閉包的特點吧經典面試題下面的代碼輸出內容答案個如何處理能夠輸出閉包方式方式下一篇你不知道的筆記 下一篇:《你不知道的javascript》筆記_this 寫在前面 這一系列的筆記是在《javascript高級程序設計》讀書筆記系列的升華版本,旨在將零碎...
摘要:數據類型中有種簡單數據類型也稱為基本數據類型和。在中非空字符串,非零數字,任意對象,都被認為。而空字符串,和,,認為是。用于表示整數和浮點數。標識符由數字字母下劃線美元符組成,但首字母不能是數字。變量方法對象命名推薦駝峰法。 JavaScript語法 一.語法簡介 因為JavaScript語法和Java等語法非常類似。所以只是簡單介紹一下。 大小寫 JavaScript是大小寫敏感的語...
摘要:一寫在前面最近重讀高級程序設計,總結下來,查漏補缺。但這種影響是單向的修改命名參數不會改變中對應的值。這是因為對象的長度是由傳入的參數個數決定的,不是由定義函數時的命名參數的個數決定的。實際改變會同步,改變也會同步 一、寫在前面 最近重讀《JavaScript高級程序設計》,總結下來,查漏補缺。 二、JS簡介 2.1 JS組成 ECMAscript:以ECMA-262為基礎的語言,由...
摘要:用于把對象序列化字符串,在序列化對象時,所有函數及原型成員都會被有意忽略,不體現在結果中。對第步返回的每個值進行相應的序列化。參考文檔高級程序設計作者以樂之名本文原創,有不當的地方歡迎指出。 showImg(https://segmentfault.com/img/bVburW1?w=658&h=494); JSON與JavaScript對象 JSON是一種表示結構化數據的存儲格式,語...
閱讀 5275·2021-09-22 15:50
閱讀 1873·2021-09-02 15:15
閱讀 1170·2019-08-29 12:49
閱讀 2550·2019-08-26 13:31
閱讀 3465·2019-08-26 12:09
閱讀 1216·2019-08-23 18:17
閱讀 2742·2019-08-23 17:56
閱讀 2934·2019-08-23 16:02