摘要:一先有雞還有先有蛋直覺上會認為代碼在執行時是由上到下一行一行執行的。不幸的是兩種猜測都是不對的。換句話說,我們的問題先有雞還是先有蛋的結論是先有蛋聲明后有雞賦值。
一、先有雞還有先有蛋?
直覺上會認為javascript代碼在執行時是由上到下一行一行執行的。但實際上這并不完全正確,有一種特殊情況會導致這個假設是錯誤的。
a = 2; var a; console.log(a);
大家覺得console.log(...)會輸出什么呢?
很多開發者會認為是undefined,因為var a聲明在a = 2之后,他們自然而然地認為變量被重新復制了,因此會被賦予默認值undefined。但是,真正的輸出結果是2。
考慮另一段代碼:
console.log(a); var a = 2;
鑒于上一個代碼片段所表現出來的某種自上而下的行為特點,你可能會認為這個代碼段也會有同樣的行為而輸出2.還有人可能認為,由于變量a在使用前沒有先進行聲明因此會拋出ReferenceError異常。
不幸的是兩種猜測都是不對的。輸出的結果是undefined。
那么到底發生了什么?看起來我們面對的是一個先有雞還是先有蛋的問題,到底是聲明(蛋)在前,還是賦值(雞)在前?
你需要知道的編譯器引擎會再解釋javascript代碼之前首先對其進行編譯。在編譯階段中的一部分工作就是找到所有的聲明,并用合適的作用域將它們關聯起來。
因此,正確的思考思路是,包括變量和函數在內的所有聲明都會在任何代碼被執行前首先被處理。
當你看到var a = 2;時,可能會認為這是一個聲明,但Javascript實際上會將其看成兩個聲明:var a 和 a = 2;第一個定義聲明是在編譯階段進行的。第二個賦值聲明會被留在原地等待執行階段
第一個代碼片段會以如下形式進行處理:
var a; a = 2; console.log(a);
其中第一部分是編譯,而第二部分是執行。
類似地,我們的第二個代碼片段實際是按照以下流程處理的:
var a; console.log(a); a = 2;
因此,打個比方,這個過程就好像變量和函數聲明從它們在代碼中出現的位置被“移動”到了最上面。這個過程就叫作提升。
換句話說,我們的問題“先有雞還是先有蛋”的結論是:先有蛋(聲明)后有雞(賦值)。
只有聲明本身會被提升,而賦值或其他運行邏輯會留在原地。如果提升改變了代碼執行的順序,會造成非常嚴重的破壞。
foo() function(){ console.log(a);//undefined var a = 2;
foo函數的聲明(在這個例子還包括實際函數的隱含值)被提升了,因此第一行中的調用可以正常執行。
另外值得注意的是,每個作用域都會進行提升操作 。盡管前面大部分的代碼片段已經簡化了(因為它們只包含全局作用域),而我們正在討論的foo(...)函數自身也會在內部對var a進行提升(顯然并不是提升到了整個程序的最上方 )。因此這段代碼實際上會被理解為下面的形式:
function foo(){ var a; console.log(a);//undefined a = 2; } foo();
可以看到,函數聲明會被提升,但是函數表達式卻不會被提升。
foo();//不是ReferenceError,而是TypeError! var foo = function bar(){ //... };
這段程序中的變量標識符foo()被提升并分配給所在作用域(在這里是全局作用域),因此foo()不會導致ReferenceError。但是foo此時并沒有賦值(如果它是一個函數聲明二不是函數表達式,那么就會賦值)。
foo()由于對undefined值進行函數調用而導致非法操作因此拋出TypeError異常。
同時也要記住,即使是具名的函數表達式,名稱標識符在賦值之前也無法在所在作用域中使用
foo();//TypeError bar();//ReferenceError var foo = function(){ //... }
這個代碼片段經過提升后,實際上會被理解為以下形式:
var foo; foo();//TypeError bar();//ReferenceError foo = function(){ //... }函數優先
函數聲明和變量聲明都會被提升。但是一個值得注意的細節(這個細節可以出現在有多個“重復”聲明的代碼中)是函數會首先被提升,然后才是變量
考慮以下代碼:
foo(); var foo; function foo(){ console.log(1); } foo = function(){ console.log(2); }
會輸出1二不是2!這個代碼片段會被引擎理解為如下形式:
function foo(){ console.log(1); } foo();//1 foo = function(){ console.log(1); } foo();//1 foo = function(){ console.log(2); }
注意:var foo盡管出現在 function foo()...的聲明之前,但它是重復的聲明(因此被忽略了),因為函數聲明會被提升到普通變量之前。
盡管重復的var 聲明會被忽略掉,但出現在后面的函數聲明還是可以覆蓋前面的。
foo();//3 function foo(){ console.log(1); } var foo = function(){ console.log(2); } function foo(){ console.log(3); }
雖然這些聽起來都是些無用的學院理論,但是它說明了在同一個作用域中進行重復定義是非常糟糕的,而且經常會導致各種奇怪的問題。
一個普通塊內部的函數聲明通常會被提升到所在作用域的頂部,這個過程不會像下面的代碼暗示的那樣可以被條件判斷所控制。
foo();//"b" var a = true; if(a){ function foo(){ console.log("a"); } } else{ function foo(){ console.log("a"); } }
但是需要注意這個行為并不可靠,在javascript未來的版本中有可能發生改變,因此應該盡可能避免在塊內部聲明函數。
小結我們習慣將var a = 2;看作一個聲明,而實際上Javascript引擎并不這么認為。它將var a和a = 2當作兩個多帶帶的聲明,第一個是編譯階段的任務,而第二個則是執行階段的任務。
這意味著無論作用域中的聲明出現在什么地方,都將在代碼本身被執行前首先進行處理。可以將這個過程形象地想象成所有的聲明(變量和函數)都會被“移動”到各自作用域的最頂端,這個過程被稱為提升。
聲明本身會被提升,而包括函數表達式的賦值在內的賦值操作并不會提升。
要注意避免重復聲明,特別是當普通的var 聲明和函數聲明混合在一起的時候,否則會引起很多危險的問題!
《你不知道的Javascript 上卷》
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/91051.html
摘要:而閉包的神奇之處正是可以阻止事情的發生。拜所聲明的位置所賜,它擁有涵蓋內部作用域的閉包,使得該作用域能夠一直存活,以供在之后任何時間進行引用。依然持有對該作用域的引用,而這個引用就叫閉包。 引子 先看一個問題,下面兩個代碼片段會輸出什么? // Snippet 1 a = 2; var a; console.log(a); // Snippet 2 console.log(a); v...
摘要:標準對象,語義由本規范定義的對象。這意味著雖然有,本質上依然是構造函數,并不能像那樣表演多繼承嵌套類等高難度動作。不過這里的并不是我們所說的數據類型,而是對象構造函數。 序 ECMAScript is an object-oriented programming language for performing computations and manipulating computat...
摘要:而作為構造函數,需要有個屬性用來作為以該構造函數創造的實例的繼承。 歡迎來我的博客閱讀:「JavaScript 原型中的哲學思想」 記得當年初試前端的時候,學習JavaScript過程中,原型問題一直讓我疑惑許久,那時候捧著那本著名的紅皮書,看到有關原型的講解時,總是心存疑慮。 當在JavaScript世界中走過不少旅程之后,再次萌發起研究這部分知識的欲望,翻閱了不少書籍和資料,才搞懂...
摘要:的隱式原型是母,母是由構造函數構造的,但函數的隱式原型又是。。。。可能是考慮到它也是由構造函數生成的吧,所以返回的值也是。 showImg(https://segmentfault.com/img/bVyLk0); 首先,我們暫且把object類型和function類型分開來,因為 function是一個特殊的對象類型,我們這里這是便于區分,把function類型單獨拿出來。順便一提,...
摘要:寫在前面找工作的時候,總是被經驗不足拒絕很多次。就像前端技術,看得懂到做出來,中間的就是經驗了。很明顯,工作兩年的人占據了優勢。工作幾年后你會發現,這些留下的總結于自己而言是多么珍貴。開啟菜鳥的前端之路 寫在前面 找工作的時候,總是被‘經驗不足’拒絕很多次。當時一直覺得這個問題無異于先有雞還是先有蛋,沒工作哪來的工作經驗?沒工作經驗哪來的工作?甚是苦惱。不過,這個話題就止于此,只要...
閱讀 2038·2021-09-30 09:47
閱讀 713·2021-09-22 15:43
閱讀 1992·2019-08-30 15:52
閱讀 2443·2019-08-30 15:52
閱讀 2552·2019-08-30 15:44
閱讀 916·2019-08-30 11:10
閱讀 3377·2019-08-29 16:21
閱讀 3303·2019-08-29 12:19