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