1. 程式人生 > 實用技巧 >JavaScript:作用域:函式作用域/全域性變數汙染/名稱空間

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只能模擬:

  1. 先定義一個唯一的全域性變數(物件)
  2. 其他變數都寫出是上述全域性變數的成員(屬性和方法)
  3. 還可以多層巢狀,最後形成名稱空間一樣的“樣式”
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啊!作為一個“先天嚴重不足,後天各種補丁”的語言,這一切才剛剛開始……

作業

    1. 使用“模擬名稱空間”技術,構建一個函式函式yz.fei.get(number);

    2. yz.fei.get(number)除number以外,還可以接受任意多個回撥函式做引數
      1. 這些回撥函式能對number進行運算,並返回bool值的,比如has9()/has8()/has6()
      2. get()函式依次執行它的回撥函式,只要回撥函式執行結果為真,就累加計數
      3. 最後返回累加值
      讓yz.fei.get(number)呼叫has9()/has8()/has6(),實現之前“統計含9/8/6數字個數”的作業