JavaScript專題之型別判斷(上)

冴羽發表於2017-06-27

JavaScript專題系列第四篇,講解型別判斷的各種方法,並且跟著 jQuery 寫一個 type 函式。

前言

型別判斷在 web 開發中有非常廣泛的應用,簡單的有判斷數字還是字串,進階一點的有判斷陣列還是物件,再進階一點的有判斷日期、正則、錯誤型別,再再進階一點還有比如判斷 plainObject、空物件、Window 物件等等。

以上都會講,今天是上半場。

typeof

我們最最常用的莫過於 typeof,注意,儘管我們會看到諸如:

console.log(typeof('yayu')) // string複製程式碼

的寫法,但是 typeof 可是一個正宗的運算子,就跟加減乘除一樣!這就能解釋為什麼下面這種寫法也是可行的:

console.log(typeof 'yayu') // string複製程式碼

引用《JavaScript權威指南》中對 typeof 的介紹:

typeof 是一元操作符,放在其單個運算元的前面,運算元可以是任意型別。返回值為表示運算元型別的一個字串。

那我們都知道,在 ES6 前,JavaScript 共六種資料型別,分別是:

Undefined、Null、Boolean、Number、String、Object

然而當我們使用 typeof 對這些資料型別的值進行操作的時候,返回的結果卻不是一一對應,分別是:

undefined、object、boolean、number、string、object

注意以上都是小寫的字串。Null 和 Object 型別都返回了 object 字串。

儘管不能一一對應,但是 typeof 卻能檢測出函式型別:

function a() {}

console.log(typeof a); // function複製程式碼

所以 typeof 能檢測出六種型別的值,但是,除此之外 Object 下還有很多細分的型別吶,如 Array、Function、Date、RegExp、Error 等。

如果用 typeof 去檢測這些型別,舉個例子:

var date = new Date();
var error = new Error();
console.log(typeof date); // object
console.log(typeof error); // object複製程式碼

返回的都是 object 吶,這可怎麼區分~ 所以有沒有更好的方法呢?

Obejct.prototype.toString

是的,當然有!這就是 Object.prototype.toString!

那 Object.protototype.toString 究竟是一個什麼樣的方法呢?

為了更加細緻的講解這個函式,讓我先獻上 ES5 規範地址:es5.github.io/#x15.2.4.2

在第 15.2.4.2 節講的就是 Object.prototype.toString(),為了不誤導大家,我先奉上英文版:

When the toString method is called, the following steps are taken:

  1. If the this value is undefined, return "[object Undefined]".
  2. If the this value is null, return "[object Null]".
  3. Let O be the result of calling ToObject passing the this value as the argument.
  4. Let class be the value of the [[Class]] internal property of O.
  5. Return the String value that is the result of concatenating the three Strings "[object ", class, and "]".

凡是規範上加粗或者斜體的,在這裡我也加粗或者斜體了,就是要讓大家感受原汁原味的規範!

如果沒有看懂,就不妨看看我理解的:

當 toString 方法被呼叫的時候,下面的步驟會被執行:

  1. 如果 this 值是 undefined,就返回 [object Undefined]
  2. 如果 this 的值是 null,就返回 [object Null]
  3. 讓 O 成為 ToObject(this) 的結果
  4. 讓 class 成為 O 的內部屬性 [[Class]] 的值
  5. 最後返回由 "[object " 和 class 和 "]" 三個部分組成的字串

通過規範,我們至少知道了呼叫 Object.prototype.toString 會返回一個由 "[object " 和 class 和 "]" 組成的字串,而 class 是要判斷的物件的內部屬性。

讓我們寫個 demo:

console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
console.log(Object.prototype.toString.call(null)) // [object Null]

var date = new Date();
console.log(Object.prototype.toString.call(date)) // [object Date]複製程式碼

由此我們可以看到這個 class 值就是識別物件型別的關鍵!

正是因為這種特性,我們可以用 Object.prototype.toString 方法識別出更多型別!

那到底能識別多少種型別呢?

至少 12 種!

你咋知道的?

我數的!

……

讓我們看個 demo:

// 以下是11種:
var number = 1;          // [object Number]
var string = '123';      // [object String]
var boolean = true;      // [object Boolean]
var und = undefined;     // [object Undefined]
var nul = null;          // [object Null]
var obj = {a: 1}         // [object Object]
var array = [1, 2, 3];   // [object Array]
var date = new Date();   // [object Date]
var error = new Error(); // [object Error]
var reg = /a/g;          // [object RegExp]
var func = function a(){}; // [object Function]

function checkType() {
    for (var i = 0; i < arguments.length; i++) {
        console.log(Object.prototype.toString.call(arguments[i]))
    }
}

checkType(number, string, boolean, und, nul, obj, array, date, error, reg, func)複製程式碼

除了以上 11 種之外,還有:

console.log(Object.prototype.toString.call(Math)); // [object Math]
console.log(Object.prototype.toString.call(JSON)); // [object JSON]複製程式碼

除了以上 13 種之外,還有:

function a() {
    console.log(Object.prototype.toString.call(arguments)); // [object Arguments]
}
a();複製程式碼

所以我們可以識別至少 14 種型別,當然我們也可以算出來,[[class]] 屬性至少有 12 個。

type API

既然有了 Object.prototype.toString 這個神器!那就讓我們寫個 type 函式幫助我們以後識別各種型別的值吧!

我的設想:

寫一個 type 函式能檢測各種型別的值,如果是基本型別,就使用 typeof,引用型別就使用 toString。此外鑑於 typeof 的結果是小寫,我也希望所有的結果都是小寫。

考慮到實際情況下並不會檢測 Math 和 JSON,所以去掉這兩個型別的檢測。

我們來寫一版程式碼:

// 第一版
var class2type = {};

// 生成class2type對映
"Boolean Number String Function Array Date RegExp Object Error Null Undefined".split(" ").map(function(item, index) {
    class2type["[object " + item + "]"] = item.toLowerCase();
})

function type(obj) {
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[Object.prototype.toString.call(obj)] || "object" :
        typeof obj;
}複製程式碼

嗯,看起來很完美的樣子~~ 但是注意,在 IE6 中,null 和 undefined 會被 Object.prototype.toString 識別成 [object Object]!

我去,竟然還有這個相容性!有什麼簡單的方法可以解決嗎?那我們再改寫一版,絕對讓你驚豔!

// 第二版
var class2type = {};

// 生成class2type對映
"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
    class2type["[object " + item + "]"] = item.toLowerCase();
})

function type(obj) {
    // 一箭雙鵰
    if (obj == null) {
        return obj + "";
    }
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[Object.prototype.toString.call(obj)] || "object" :
        typeof obj;
}複製程式碼

isFunction

有了 type 函式後,我們可以對常用的判斷直接封裝,比如 isFunction:

function isFunction(obj) {
    return type(obj) === "function";
}複製程式碼

陣列

jQuery 判斷陣列型別,舊版本是通過判斷 Array.isArray 方法是否存在,如果存在就使用該方法,不存在就使用 type 函式。

var isArray = Array.isArray || function( obj ) {
    return type(obj) === "array";
}複製程式碼

但是在 jQuery v3.0 中已經完全採用了 Array.isArray。

結語

到此,型別判斷的上篇就結束了,我們已經可以判斷日期、正則、錯誤型別啦,但是還有更復雜的判斷比如 plainObject、空物件、Window物件、類陣列物件等,路漫漫其修遠兮,吾將上下而求索。

哦, 對了,這個 type 函式抄的 jQuery,點選檢視 type 原始碼

專題系列

JavaScript專題系列目錄地址:github.com/mqyqingfeng…

JavaScript專題系列預計寫二十篇左右,主要研究日常開發中一些功能點的實現,比如防抖、節流、去重、型別判斷、拷貝、最值、扁平、柯里、遞迴、亂序、排序等,特點是研(chao)究(xi) underscore 和 jQuery 的實現方式。

如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。

相關文章