深刻了解JS類型判斷

關注『前端開發博客』公衆號,回覆 加羣,加入高薪前端羣

JS中判斷數據類型的方式有不少javascript

  • typeof
  • Object.prototype.toString
  • instanceof
  • Array.isArray

1、回顧

JS數據類型分爲基本類型和引用類型。
基本類型html

  • undefined
  • null
  • Number
  • String
  • Boolean
  • Symbol

引用類型前端

  • Object
  • Function

函數是一種特殊的對象,便可調用的對象。java

2、typeof

2.1 語法

typeof操做符能夠區分基本類型,函數和對象。git

console.log(typeof null) // object
console.log(typeof undefined) // undefined
console.log(typeof 1) // number
console.log(typeof 1.2) // number
console.log(typeof "hello") // string
console.log(typeof true) // boolean
console.log(typeof Symbol()) // symbol
console.log(typeof (() => {})) // function
console.log(typeof {}) // object
console.log(typeof []) // object
console.log(typeof /abc/) // object
console.log(typeof new Date()) // object
  1. typeof有個明顯的bug就是typeof nullobject;
  2. typeof沒法區分各類內置的對象,如Array, Date等。

2.2 原理

JS是動態類型的變量,每一個變量在存儲時除了存儲變量值外,還須要存儲變量的類型。JS裏使用32位(bit)存儲變量信息。低位的1~3個bit存儲變量類型信息,叫作類型標籤(type tag)github

.... XXXX X000 // object
.... XXXX XXX1 // int
.... XXXX X010 // double
.... XXXX X100 // string
.... XXXX X110 // boolean
  1. 只有int類型的type tag使用1個bit,而且取值爲1,其餘都是3個bit, 而且低位爲0。這樣能夠經過type tag低位取值判斷是否爲int數據;
  2. 爲了區分int,還剩下2個bit,至關於使用2個bit區分這四個類型:object, double, string, boolean
  3. 可是nullundefinedFunction並無分配type tag

如何識別Function

函數並無單獨的type tag,由於函數也是對象。typeof內部判斷若是一個對象實現了[[call]]內部方法則認爲是函數。segmentfault

如何識別undefined

undefined變量存儲的是個特殊值JSVAL_VOID(0-2^30)typeof內部判斷若是一個變量存儲的是這個特殊值,則認爲是undefinedapi

#define JSVAL_VOID              INT_TO_JSVAL(0 - JSVAL_INT_POW2(30))

如何識別null

null變量存儲的也是個特殊值JSVAL_NULL,而且恰巧取值是空指針機器碼(0),正好低位bit的值跟對象的type tag是同樣的,這也致使著名的bug:數組

typeof null // object

很不幸,這個bug也不修復了,由於初版JS就存在這個bug了。祖傳代碼,不敢修改啊。瀏覽器

有不少方法能夠判斷一個變量是一個非null的對象,以前遇到一個比較經典的寫法:

// 利用Object函數的裝箱功能
function isObject(obj) {
    return Object(obj) === obj;
}

isObject({}) // true
isObject(null) // false

3、Object.prototype.toString

通常使用Object.prototype.toString區分各類內置對象。

3.2 語法

console.log(Object.prototype.toString.call(1)); // [object Number],隱式類型轉換
console.log(Object.prototype.toString.call('')); // [object String],隱式類型轉換
console.log(Object.prototype.toString.call(null)); // [object Null],特殊處理
console.log(Object.prototype.toString.call(undefined)); // [object Undefined],特殊處理
console.log(Object.prototype.toString.call(true)); // [object Boolean],隱式類型轉換
console.log(Object.prototype.toString.call( {})); // [object Object]
console.log(Object.prototype.toString.call([])); // [object Array]
console.log(Object.prototype.toString.call(function(){})); // [object Function]
  1. 若是實參是個基本類型,會自動轉成對應的引用類型;

Object.prototype.toString不能區分基本類型的,只是用於區分各類對象;

  1. nullundefined不存在對應的引用類型,內部特殊處理了;

3.3 原理

內部屬性[[Class]]

每一個對象都有個內部屬性[[Class]],內置對象的[[Class]]的值都是不一樣的("Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String"),而且目前[[Class]]屬性值只能經過Object.prototype.toString訪問。

Symbol.toStringTag屬性

其實Object.prototype.toString內部先訪問對象的Symbol.toStringTag屬性值拼接返回值的。

var a = "hello"
console.log(Object.prototype.toString.call(a)); // "[object String]"

// 修改Symbol.toStringTag值
Object.defineProperty(String.prototype, Symbol.toStringTag, {
    get() {
        return 'MyString'
    }
})

console.log(Object.prototype.toString.call(a)); // "[object MyString]"

若是哪一個貨偷偷修改了內置對象的Symbol.toStringTag屬性值,那Object.prototype.toString也就不能正常工做了。

3.4 Object.prototype.toString內部邏輯

綜上能夠總結Object.prototype.toString的內部邏輯:

  1. 若是實參是undefined, 則返回"\[object Undefined\]";
  2. 若是實參是null, 則返回"\[object Null\]";
  3. 把實參轉成對象
  4. 獲取對象的Symbol.toStringTag屬性值subType

    • 若是subType是個字符串,則返回[object subType]
    • 不然獲取對象的[[Class]]屬性值type,並返回[object type]

4、instanceof

4.1 語法

object instanceof constructorFunc

instanceof 操做符判斷構造函數constructorFuncprototype屬性是否在對象object的原型鏈上。

Object.create({}) instanceof Object // true
Object.create(null) instanceof Object // false

Function instanceof Object // true
Function instanceof Function // true
Object instanceof Object // true
  1. 做爲類型判斷的一種方式,instanceof 操做符不會對變量object進行隱式類型轉換
"" instanceof String; // false,基本類型不會轉成對象
new String('') instanceof String; // true
  1. 對於沒有原型的對象或則基本類型直接返回false
1 instanceof Object // false
Object.create(null) instanceof Object // false
  1. constructorFunc必須是個對象。而且大部分狀況要求是個構造函數(即要具備prototype屬性)
// TypeError: Right-hand side of 'instanceof' is not an object
1 instanceof 1

// TypeError: Right-hand side of 'instanceof' is not callable
1 instanceof ({})

// TypeError: Function has non-object prototype 'undefined' in instanceof check
({}) instanceof (() => {})

4.2 intanceof的缺陷

不一樣的全局執行上下文的對象和函數都是不相等的,因此對於跨全局執行上下文intanceof就不能正常工做了。

<!DOCTYPE html>
<html>
    <head></head>
    <body>
        <iframe src=""></iframe>
        <script type="text/javascript">
            var iframe = window.frames[0];
            var iframeArr = new iframe.Array();

            console.log([] instanceof iframe.Array) // false
            console.log(iframeArr instanceof Array)  // false
            console.log(iframeArr instanceof iframe.Array)  // true       
        </script>
    </body>
</html>

4.3 原理

Symbol.hasInstance函數

instanceof操做符判斷構造函數constructorFuncprototype屬性是否在對象object的原型鏈上。可是能夠利用Symbol.hasInstance自定義instanceof操做邏輯。

var obj = {}

// 自定義Symbol.hasInstance方法
Object.defineProperty(obj, Symbol.hasInstance, {
    value: function() {
        return true;
    }
});

1 instanceof obj // true

固然了這個舉例沒有任何實際意義。只是說明下Symbol.hasInstance的功能。Symbol.hasInstance本意是自定義構造函數判斷實例對象的方式,不要改變instanceof 的含義。

原型鏈

4.4 instanceof內部邏輯

綜上能夠梳理instanceof內部邏輯

object instanceof constructorFunc
  1. 若是constructorFunc不是個對象,或則是null,直接拋TypeError異常;
  2. 若是constructorFunc[Symbole.hasInstance]方法,則返回!!constructorFunc[Symbole.hasInstance](object )
  3. 若是constructorFunc不是函數,直接拋TypeError異常;
  4. 遍歷object的原型鏈,逐個跟constructorFunc.prototype屬性比較:

    • 若是object沒有原型,則直接返回false;
    • 若是constructorFunc.prototype不是對象,則直接拋TypeError異常。

5、內置的類型判斷方法

5.1 Array.isArray

ES5引入了方法Array.isArray專門用於數組類型判斷。Object.prototype.toStringinstanceof都不夠嚴格

var arr = []
Object.defineProperty(Array.prototype, Symbol.toStringTag, {
    get() {
        return 'myArray'
    }
})

console.log(Object.prototype.toString.call(arr)); // [object myArray]
console.log(Array.isArray(arr)); // true

console.log(Array.prototype instanceof Array); // false
console.log(Array.isArray(Array.prototype)); // true

不過現實狀況下基本都是利用Object.prototype.toString做爲Array.isArray的polyfill:

if (!Array.isArray) {
    Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
    };
}

6、內置對象的prototype屬性類型判斷

內置的對象Number, String, Boolean, Object, Function, Date, RegExp, Array都是各自類型對象的構造函數,而且他們的prototype屬性都是各自實例對象的原型。可是這些內置對象的prototype屬性又是什麼類型呢?

6.1 Number.prototype

Number.prototype也是個數字,相似Number(0),可是Number.prototype並非Number的實例。

var prototype = Number.prototype

console.log(prototype == 0); // true

console.log(prototype instanceof Number); // false
console.log(Object.prototype.toString.call(protoype)); // [object Number]

6.2 String.prototype

String.prototype也是字符串,相似"",可是String.prototype並非String的實例。

var prototype = String.prototype

console.log(prototype == ''); // true
console.log(prototype instanceof String); // false
console.log(Object.prototype.toString.call(prototype)); // [object String]

6.3 Boolean.prototype

Boolean.prototype也是Boolean,相似false,可是Boolean.prototype並非Boolean的實例。

var prototype = Boolean.prototype

console.log(prototype == false); // true
console.log(prototype instanceof Boolean); // false
console.log(Object.prototype.toString.call(prototype)); // [object Boolean]

6.4 Object.prototype

Object.prototype也是Object,相似Object.create(null)的值(原型爲null的空對象),可是Object.prototype並非Object的實例。

var prototype = Object.prototype

Object.getPrototypeOf(prototype); // null
console.log(prototype instanceof Object); // false
console.log(Object.prototype.toString.call(prototype)); // [object Object]

6.5 Function.prototype

Function.prototype也是Function,是個空函數,可是Function.prototype並非Function的實例。

var prototype = Function.prototype

console.log(prototype()) // undefined
console.log(prototype instanceof Function); // false
console.log(Object.prototype.toString.call(prototype)); // [object Function]

6.6 Array.prototype

Array.prototype也是Array,是個空數組,可是Array.prototype並非Array的實例。

var prototype = Array.prototype

console.log(prototype instanceof Array); // false
console.log(Array.isArray(prototype)) // true
console.log(Object.prototype.toString.call(prototype)); // [object Array]

6.6 RegExp.prototype

RegExp.prototype並非RegExp的實例。可是關於RegExp.prototypeRegExp仍是對象存在兼容性問題,有些瀏覽器下RegExp.prototype也是RegExp,而且是個總返回true的正則。

var prototype = RegExp.prototype
console.log(prototype.test('hello')) // true
console.log(prototype instanceof RegExp); // false
// Chrome v84返回"[object Object]", IE返回"[object RegExp]"
console.log(Object.prototype.toString.call(prototype)); //

整理自gitHub筆記:

  1. JS-類型&類型判斷
  2. JS-操做符-instanceof
關注公衆號「前端開發博客」,回覆1024,領取前端資料包

原文:https://segmentfault.com/a/11...

相關文章
相關標籤/搜索