了解JavaScript的機制
我們學習作用域的方法是將這個過程模擬成幾個人之間的對話劫哼,那么我們先看一下都有誰參與到了這個對話:
演員表
引擎:從頭到尾負責整個JavaScript的編譯和執(zhí)行的過程
編譯器:引擎的好朋友之一,主要的工作是語法的分析和代碼的生成
作用域:引擎的另外一位好朋友浑彰,負責收集并且維護所有聲明的標識符進而組成一系列的查詢颇玷,并執(zhí)行一非常嚴格的規(guī)則锌云,確定當前執(zhí)行的代碼的這些標識符的訪問權限。
對話
當我們看到var a = 2褂乍;
這段程序的時候持隧,很可能認為這是一句聲明的語句。但是在引擎的眼中逃片,這個是兩個過程:一個由編譯器在編譯的時候處理屡拨,另外的一個是引擎在執(zhí)行的時候處理。
我們仔細的看一下這段代碼在底層究竟是如何實現(xiàn)的:
- 遇到
var a题诵;
這個語句的時候洁仗,會詢問作用域是否已經(jīng)有一個該名字的變量存在在同一個作用域中,如果有的話性锭,就會忽略這個聲明赠潦,否則,他會要求作用域在當前作用域的集合中聲明一個新的變量草冈。 - 接下來編譯器會為引擎生成運行時需要的代碼她奥,這些代碼用來處理
a = 2
這個賦值操作,引擎運行時先訪問作用域怎棱,在當前的作用中是否有一個叫做a
的變量哩俭,如果有,引擎就會使用這個變量拳恋,如果沒有凡资,引擎就會繼續(xù)查找這個變量,如果找到這個變量谬运,就會將2
賦值給他隙赁,如果沒有找到的話,就會拋出一個異常梆暖。
函數(shù)作用域
在任意的代碼片段外添加包裝函數(shù)伞访,可以將內(nèi)部的變量和函數(shù)定義隱藏起來,導致外部是沒有辦法訪問內(nèi)部的任何內(nèi)容的轰驳。
var a = 2;
foo = ()=>{ // <--添加了這一行
var a = 3;
console.log(a); //3
}// <--添加了這一行
foo();// <--添加了這一行
console.log(a) // 2
這個技術解決了一寫問題厚掷,但是還是有一點不理想弟灼,因為會導致一些其他的問題,因為這么做應該先聲明一個具名函數(shù)foo()
,但是這個foo()
污染了全局作用域冒黑,其次田绑,還是需要顯示的調(diào)用才可以,但是我們期望可以不用使用函數(shù)名抡爹,并且可以自行運行辛馆,這樣就更加理想了。
var a = 2;
(()=>{ // <--添加了這一行
var a = 3;
console.log(a); // 3
})() // <--添加了這一行
console.log(a) // 2
匿名和具名
對于函數(shù)表達式最熟悉的場景就是毀掉參數(shù)豁延,舉個例子:
setTimeout(()=>{
console.log("I waited 1 second")
})
上面的就是匿名函數(shù)表達式,因為function()..
沒有名稱標識符腊状,函數(shù)表達式是可以匿名的诱咏,然而函數(shù)表達式是不可以忽略函數(shù)名的-- 在JavaScript
中是違法的。
匿名函數(shù)的缺點:
- 匿名韓式在棧追蹤中不會顯示出有具體意義的函數(shù)名缴挖,讓調(diào)試變得很有困難袋狞。
- 沒有函數(shù)名,當函數(shù)需要引用自身的時候只能使用已經(jīng)過期的
arguments.callee
引用映屋,比如:在遞歸中苟鸯,另一個行數(shù)需要引用自身的例子,在時間觸發(fā)之后時間監(jiān)聽器需要解綁自身棚点。 - 匿名函數(shù)省略了對于代碼可讀性/可理解性很重要的函數(shù)名早处,一個描述性的名稱可以讓代碼不言而喻。
立即執(zhí)行函數(shù)表達式
var a = 2;
(foo =()=>{
var a = 3;
console.log(a); // 3
})();
console.log(a); // 2
上面的函數(shù)被包含在一個括號的內(nèi)部瘫析,因此形成了一個表達式砌梆,然后在結尾還添加了一個(),可以來立即執(zhí)行這個函數(shù),比如(foo=()={..})()
立即執(zhí)行函數(shù)表達式的進階使用方法:就是你把他們當做函數(shù)調(diào)用并傳遞參數(shù)進去贬循。
var a = 2;
(function foo(global){
var a = 3;
console.log(a); // 3
console.log(global.a); // 2
})(window);
console.log(a); // 2
我們將window
對象的引用傳遞進去咸包,并將其命名為global
,因此在代碼風格上對全局對象的引用變得比引用一個沒有全局字樣的變量更加清楚啦杖虾。這個模式的另外的一個應用場景就是解決undefined
標識符的默認值被錯誤覆蓋導致異常的情況烂瘫。比如說:我們將一個參數(shù)命名成undefined
,但是在對應的位置不傳入任何的值奇适,這樣就可以保證在代碼塊中的undefined
標識符的值就是undefined
坟比。
undefined = true; // 這樣做對其他的代碼是不好的,一定不要這么做
(foo= (undefined)=>{
var a ;
if(a ===undefined){
console.log('undefined is safe here');
}
})();
任何的聲明在某個作用域內(nèi)的變量滤愕,都將依附于這個作用域温算。
a = 2;
var a;
console.log(a); // 2
// ===> 等價于
var a; // 進行編譯
a = 2; // 執(zhí)行的時候進行賦值
console.log(a)
console.log(a); // undefined
var a = 2;
導致上面的結果很簡單,聲明本身會被提升间影,提升到最開始注竿,然而賦值或是其他的運行邏輯,會被留在原地。注意一下巩割,我們在進行狀態(tài)提升的時候會先提升函數(shù)的狀態(tài)裙顽,其次才是函數(shù)的狀態(tài)。
foo(); // 1
var foo;
function foo(){
console.log(1);
}
foo = function(){
console.log(2)
}
因為對于上面的代碼宣谈,JavaScript
的引擎是這樣進行理解的:
function foo(){
console.log(1);
}
foo();
foo = function(){
console.log(2)
}