JavaScript:作用域:函式作用域/全域性變數汙染/名稱空間
在C#中我們都基本上不講作用域,因為一切都是自然而然的(用語言描述反而有些困難)。但JavaScript的作用域,讓人非常頭大!
區域性變數
如果一個變數在函式體內部(用var)宣告,則該變數的作用域為整個函式體,在函式體外不可引用該變數。(另見:let)
不同函式內部的同名變數互相獨立,互不影響。
這樣被宣告的變數被稱之為:區域性變數。
function scope() {
var sname = '李志博';
console.log('in function:' + sname);
}
scope();
console.log('out function:' + sname);
全域性變數“汙染”
不在任何函式內定義的變數具有全域性作用域,被稱之為全域性變數。
演示:
如果script只是在HTML頁面中使用,全域性變數還可以接受(而且比較方便);
但隨著JavaScript規模擴大,一個專案可能引用多個(第三方/其他人寫的)類庫(js檔案),各個檔案間的名稱衝突就越來越難以避免,給開發/維護帶來極大的問題!這就被稱之為“全域性變數汙染”。
詞法作用域
觀察以下程式碼:
var sname = "飛哥";
function smart() {
alert(`${sname}最帥`);
}
function reallySmart() {
var sname = '子祥';
smart();
}
reallySmart();
reallySmart()呼叫smart(),smart()中需要sname但又沒有宣告sname,怎麼辦?
- 使用reallySmart()中定義的'子祥',還是
- 全域性變數"飛哥"
由var宣告的變數的作用範圍,其作用域由是由其原始碼的書寫位置,(而不是在哪裡執行)決定的。
JavaScript的函式中不僅可以使用全域性變數,還可以使用該函式外部的函式所宣告的變數:
function outFunc(sname) {
var age = 100;
function innerFunc() {
alert(age); //age定義在innerFunc()之外
}
innerFunc();
}
這樣就形成了一個作用域鏈:JavaScript會沿著這個鏈條由內向外查詢,直到undefined。
@想一想@:如何剪下/貼上第一個函式,才能顯示:子祥最帥?
名稱空間(namespace)
其他成熟的工程化語言內建了名稱空間(namespace)來解決這個問題,比如:
//雖然都是“源棧”,但他們顯然是不一樣的:
China.Chongqing.Luckystack
China.Bejin.Luckystack
US.NewYork.Luckstack
JavaScript只能模擬:
- 先定義一個唯一的全域性變數(物件)
- 其他變數都寫出是上述全域性變數的成員(屬性和方法)
- 還可以多層巢狀,最後形成名稱空間一樣的“樣式”
var China = {};
China.Chongqing = {};
China.Chongqing.LuckyStack = {};
China.Chongqing.LuckyStack.wpz = function () {
JQuery等類庫就是這樣做的。(演示)
strict模式(ES5)
把我們之前的程式碼稍作改動:
function scope() {
/*var*/ sname = '李志博'; //註釋掉var
@猜一猜@:會有什麼結果?
如果在JavaScript的函式中宣告變數,不使用var,該變數就具有全域性作用域!——特性超級坑爹的一個“特性(bug)”,尤其是在程式碼review的時候,你根本不知道這是在:
- 使用一個已宣告的變數,還是
- 要宣告一個全域性變數
所以從ES5開始,JavaScript就引入了所謂的“嚴格”模式,在程式碼頂部新增一行:
'use strict'; -- 如果瀏覽器不支援?
使用嚴格模式,就能強制JavaScript宣告變數時必須使用:var;否則會報錯。(以及其他約束)
演示:除錯視窗報錯
官方推薦總是使用嚴格模式。但是,如果
- a.js 檔案上聲明瞭'use strict'
-
b.js 檔案沒有宣告'use strict'且沒有按照嚴格模式書寫程式碼
- 在html檔案中先引用了a.js,然後再引用了 b.js
@想一想@:會出現什麼情況?
所以,更多時候,我們不得不把'use strict'宣告在函式頂部。
體會:JavaScript在處理大型專案,進行工程化開發方面先天不足!由此誕生了很多“奇巧淫技”,以及不斷進化的ES標準。
但你以為這樣就結束了?too young too simple啊!作為一個“先天嚴重不足,後天各種補丁”的語言,這一切才剛剛開始……
作業
-
使用“模擬名稱空間”技術,構建一個函式函式yz.fei.get(number);
- yz.fei.get(number)除number以外,還可以接受任意多個回撥函式做引數
- 這些回撥函式能對number進行運算,並返回bool值的,比如has9()/has8()/has6()
- get()函式依次執行它的回撥函式,只要回撥函式執行結果為真,就累加計數
- 最後返回累加值