官术网_书友最值得收藏!

第1章 類型

大多數開發者認為,像JavaScript這樣的動態語言是沒有類型(type)的。讓我們來看看ES5.1規范(http://www.ecma-international.org/ecma-262/5.1/)對此是如何界定的:

本規范中的運算法則所操縱的值均有相應的類型。本節中定義了所有可能出現的類型。ECMAScript類型又進一步細分為語言類型和規范類型。

ECMAScript語言中所有的值都有一個對應的語言類型。ECMAScript語言類型包括Undefined、Null、Boolean、String、Number和Object。

喜歡強類型(又稱靜態類型)語言的人也許會認為“類型”一詞用在這里不妥。“類型”在強類型語言中的涵義要廣很多。

也有人認為,JavaScript中的“類型”應該稱為“標簽”(tag)或者“子類型”(subtype)。

本書中,我們這樣來定義“類型”(與規范類似):對語言引擎和開發人員來說,類型是值的內部特征,它定義了值的行為,以使其區別于其他值。

換句話說,如果語言引擎和開發人員對42(數字)和"42"(字符串)采取不同的處理方式,那就說明它們是不同的類型,一個是number,一個是string。通常我們對數字42進行數學運算,而對字符串"42"進行字符串操作,比如輸出到頁面。它們是不同的類型。

上述定義并非完美,不過對于本書已經足夠,也和JavaScript語言對自身的描述一致。

1.1 類型

撇開學術界對類型定義的分歧,為什么說JavaScript是否有類型也很重要呢?

要正確合理地進行類型轉換(參見第4章),我們必須掌握JavaScript中的各個類型及其內在行為。幾乎所有的JavaScript程序都會涉及某種形式的強制類型轉換,處理這些情況時我們需要有充分的把握和自信。

如果要將42作為string來處理,比如獲得其中第二個字符"2",就需要將它從number(強制類型)轉換為string。

這看似簡單,但是強制類型轉換形式多樣。有些方式簡明易懂,也很安全,然而稍不留神,就會出現意想不到的結果。

強制類型轉換是JavaScript開發人員最頭疼的問題之一,它常被詬病為語言設計上的一個缺陷,太危險,應該束之高閣。

全面掌握JavaScript的類型之后,我們旨在改變對強制類型轉換的成見,看到它的好處并且意識到它的缺點被過分夸大了。現在先讓我們來深入了解一下值和類型。

1.2 內置類型

JavaScript有七種內置類型:

? 空值(null)

? 未定義(undefined)

? 布爾值(boolean)

? 數字(number)

? 字符串(string)

? 對象(object)

? 符號(symbol, ES6中新增)

除對象之外,其他統稱為“基本類型”。

我們可以用typeof運算符來查看值的類型,它返回的是類型的字符串值。有意思的是,這七種類型和它們的字符串值并不一一對應:

        typeof undefined     === "undefined"; // true
        typeof true          === "boolean";   // true
        typeof 42            === "number";    // true
        typeof "42"          === "string";    // true
        typeof { life: 42 }  === "object";    // true

        // ES6中新加入的類型
        typeof Symbol()      === "symbol";    // true

以上六種類型均有同名的字符串值與之對應。符號是ES6中新加入的類型,我們將在第3章中介紹。

你可能注意到null類型不在此列。它比較特殊,typeof對它的處理有問題:

        typeof null === "object"; // true

正確的返回結果應該是"null",但這個bug由來已久,在JavaScript中已經存在了將近二十年,也許永遠也不會修復,因為這牽涉到太多的Web系統,“修復”它會產生更多的bug,令許多系統無法正常工作。

我們需要使用復合條件來檢測null值的類型:

        var a = null;

        (! a && typeof a === "object"); // true

null是“假值”(falsy或者false-like,參見第4章),也是唯一一個用typeof檢測會返回"object"的基本類型值。

還有一種情況:

        typeof function a(){ /* .. */ } === "function"; // true

這樣看來,function(函數)也是JavaScript的一個內置類型。然而查閱規范就會知道,它實際上是object的一個“子類型”。具體來說,函數是“可調用對象”,它有一個內部屬性[[Call]],該屬性使其可以被調用。

函數不僅是對象,還可以擁有屬性。例如:

        function a(b, c) {
            /* .. */
        }

函數對象的length屬性是其聲明的參數的個數:

        a.length; // 2

因為該函數聲明了兩個命名參數,b和c,所以其length值為2。

再來看看數組。JavaScript支持數組,那么它是否也是一個特殊類型?

        typeof [1,2,3] === "object"; // true

不,數組也是對象。確切地說,它也是object的一個“子類型”(參見第3章),數組的元素按數字順序來進行索引(而非像普通對象那樣通過字符串鍵值),其length屬性是元素的個數。

1.3 值和類型

JavaScript中的變量是沒有類型的,只有值才有。變量可以隨時持有任何類型的值。

換個角度來理解就是,JavaScript不做“類型強制”;也就是說,語言引擎不要求變量總是持有與其初始值同類型的值。一個變量可以現在被賦值為字符串類型值,隨后又被賦值為數字類型值。

42的類型為number,并且無法更改。而"42"的類型為string。數字42可以通過強制類型轉換(coercion)為字符串"42"(參見第4章)。

在對變量執行typeof操作時,得到的結果并不是該變量的類型,而是該變量持有的值的類型,因為JavaScript中的變量沒有類型。

        var a = 42;
        typeof a; // "number"

        a = true;
        typeof a; // "boolean"

typeof運算符總是會返回一個字符串:

        typeof typeof 42; // "string"

typeof 42首先返回字符串"number",然后typeof "number"返回"string"。

1.3.1 undefined和undeclared

變量在未持有值的時候為undefined。此時typeof返回"undefined":

        var a;

        typeof a; // "undefined"

        var b = 42;
        var c;

        // later
        b = c;

        typeof b; // "undefined"
        typeof c; // "undefined"

大多數開發者傾向于將undefined等同于undeclared(未聲明),但在JavaScript中它們完全是兩回事。

已在作用域中聲明但還沒有賦值的變量,是undefined的。相反,還沒有在作用域中聲明過的變量,是undeclared的。

例如:

        var a;

        a; // undefined
        b; // ReferenceError: b is not defined

瀏覽器對這類情況的處理很讓人抓狂。上例中,“b is not defined”容易讓人誤以為是“b is undefined”。這里再強調一遍,“undefined”和“is not defined”是兩碼事。此時如果瀏覽器報錯成“b is not found”或者“b is not declared”會更準確。

更讓人抓狂的是typeof處理undeclared變量的方式。例如:

        var a;

        typeof a; // "undefined"

        typeof b; // "undefined"

對于undeclared(或者not defined)變量,typeof照樣返回"undefined"。請注意雖然b是一個undeclared變量,但typeof b并沒有報錯。這是因為typeof有一個特殊的安全防范機制。

此時typeof如果能返回undeclared(而非undefined)的話,情況會好很多。

1.3.2 typeof Undeclared

該安全防范機制對在瀏覽器中運行的JavaScript代碼來說還是很有幫助的,因為多個腳本文件會在共享的全局命名空間中加載變量。

很多開發人員認為全局命名空間中不應該有變量存在,所有東西都應該被封裝到模塊和私有/獨立的命名空間中。理論上這樣沒錯,卻不切實際。然而這仍不失為一個值得為之努力奮斗的目標。好在ES6中加入了對模塊的支持,這使我們又向目標邁近了一步。

舉個簡單的例子,在程序中使用全局變量DEBUG作為“調試模式”的開關。在輸出調試信息到控制臺之前,我們會檢查DEBUG變量是否已被聲明。頂層的全局變量聲明var DEBUG =true只在debug.js文件中才有,而該文件只在開發和測試時才被加載到瀏覽器,在生產環境中不予加載。

問題是如何在程序中檢查全局變量DEBUG才不會出現ReferenceError錯誤。這時typeof的安全防范機制就成了我們的好幫手:

        // 這樣會拋出錯誤
        if (DEBUG) {
            console.log( "Debugging is starting" );
        }

        // 這樣是安全的
        if (typeof DEBUG ! == "undefined") {
            console.log( "Debugging is starting" );
        }

這不僅對用戶定義的變量(比如DEBUG)有用,對內建的API也有幫助:

        if (typeof atob === "undefined") {
            atob = function() { /*..*/ };
        }

如果要為某個缺失的功能寫polyfill(即襯墊代碼或者補充代碼,用來補充當前運行環境中缺失的功能),一般不會用var atob來聲明變量atob。如果在if語句中使用var atob,聲明會被提升(hoisted,參見《你不知道的JavaScript(上卷)》注1:此書已由人民郵電出版社出版。——編者注中的“作用域和閉包”部分)到作用域(即當前腳本或函數的作用域)的最頂層,即使if條件不成立也是如此(因為atob全局變量已經存在)。在有些瀏覽器中,對于一些特殊的內建全局變量(通常稱為“宿主對象”, host object),這樣的重復聲明會報錯。去掉var則可以防止聲明被提升。

還有一種不用通過typeof的安全防范機制的方法,就是檢查所有全局變量是否是全局對象的屬性,瀏覽器中的全局對象是window。所以前面的例子也可以這樣來實現:

        if (window.DEBUG) {
            // ..
        }

        if (! window.atob) {
            // ..
        }

與undeclared變量不同,訪問不存在的對象屬性(甚至是在全局對象window上)不會產生ReferenceError錯誤。

一些開發人員不喜歡通過window來訪問全局對象,尤其當代碼需要運行在多種JavaScript環境中時(不僅僅是瀏覽器,還有服務器端,如node.js等),因為此時全局對象并非總是window。

從技術角度來說,typeof的安全防范機制對于非全局變量也很管用,雖然這種情況并不多見,也有一些開發人員不大愿意這樣做。如果想讓別人在他們的程序或模塊中復制粘貼你的代碼,就需要檢查你用到的變量是否已經在宿主程序中定義過:

        function doSomethingCool() {
            var helper =
              (typeof FeatureXYZ ! == "undefined") ?
              FeatureXYZ :
              function() { /*.. default feature ..*/ };

            var val = helper();
            // ..
        }

其他模塊和程序引入doSomethingCool()時,doSomethingCool()會檢查FeatureXYZ變量是否已經在宿主程序中定義過;如果是,就用現成的,否則就自己定義:

        // 一個立即執行函數表達式(IIFE,參見《你不知道的JavaScript(上卷)》“作用域和閉包”
        // 部分的3.3.2節)
        (function(){
            function FeatureXYZ() { /*.. my XYZ feature ..*/ }

            // 包含doSomethingCool(..)
            function doSomethingCool() {
              var helper =
                  (typeof FeatureXYZ ! == "undefined") ?
                  FeatureXYZ :
                  function() { /*.. default feature ..*/ };

              var val = helper();
              // ..
            }

            doSomethingCool();
        })();

這里,FeatureXYZ并不是一個全局變量,但我們還是可以使用typeof的安全防范機制來做檢查,因為這里沒有全局對象可用(像前面提到的window.)。

還有一些人喜歡使用“依賴注入”(dependency injection)設計模式,就是將依賴通過參數顯式地傳遞到函數中,如:

        function doSomethingCool(FeatureXYZ) {
            var helper = FeatureXYZ ||
              function() { /*.. default feature ..*/ };
            var val = helper();
            // ..
        }

上述種種選擇和方法各有利弊。好在typeof的安全防范機制為我們提供了更多選擇。

1.4 小結

JavaScript有七種內置類型:null、undefined、boolean、number、string、object和symbol,可以使用typeof運算符來查看。

變量沒有類型,但它們持有的值有類型。類型定義了值的行為特征。

很多開發人員將undefined和undeclared混為一談,但在JavaScript中它們是兩碼事。undefined是值的一種。undeclared則表示變量還沒有被聲明過。

遺憾的是,JavaScript卻將它們混為一談,在我們試圖訪問"undeclared"變量時這樣報錯:ReferenceError: a is not defined,并且typeof對undefined和undeclared變量都返回"undefined"。

然而,通過typeof的安全防范機制(阻止報錯)來檢查undeclared變量,有時是個不錯的辦法。

主站蜘蛛池模板: 扶余县| 囊谦县| 文山县| 腾冲县| 郓城县| 福鼎市| 黑河市| 霍州市| 金乡县| 宝鸡市| 嵩明县| 乌鲁木齐县| 徐水县| 大悟县| 临沧市| 正安县| 尼勒克县| 富阳市| 峡江县| 南漳县| 蓬溪县| 临武县| 鄢陵县| 枣阳市| 寿宁县| 万山特区| 阿克苏市| 陇西县| 忻城县| 郁南县| 始兴县| 和平区| 景洪市| 理塘县| 莱西市| 新竹县| 遂溪县| 蛟河市| 留坝县| 崇义县| 永济市|