JavaScript
的類型判斷是前端工程師們天天代碼中必備的部分,天天確定會寫上個不少遍if (a === 'xxx')
或if (typeof a === 'object')
相似的類型判斷語句,因此掌握JavaScript
中類型判斷也是前端必備技能,如下會從JavaScript
的類型,類型判斷以及一些內部實現來讓你深刻了解JavaScript
類型的那些事。javascript
JavaScript
中類型主要包括了primitive
和object
類型,其中primitive
類型包括了:null
、undefined
、boolean
、number
、string
和symbol(es6)
。其餘全部的都爲object
類型。前端
類型檢測主要包括了:typeof
、instanceof
和toString
的三種方式來判斷變量的類型。java
typeof
接受一個值並返回它的類型,它有兩種可能的語法:node
typeof x
typeof(x)
當在primitive
類型上使用typeof
檢測變量類型時,咱們總能獲得咱們想要的結果,好比:git
typeof 1; // "number" typeof ""; // "string" typeof true; // "boolean" typeof bla; // "undefined" typeof undefined; // "undefined"
而當在object
類型上使用typeof
檢測時,有時可能並不能獲得你想要的結果,好比:es6
typeof []; // "object" typeof null; // "object" typeof /regex/ // "object" typeof new String(""); // "object" typeof function(){}; // "function"
這裏的[]
返回的確倒是object
,這可能並非你想要的,由於數組是一個特殊的對象,有時候這可能並非你想要的結果。github
對於這裏的null
返回的確倒是object
,wtf,有些人說null
被認爲是沒有一個對象。面試
當你對於typeof
檢測數據類型不肯定時,請謹慎使用。數組
typeof
的問題主要在於不能告訴你過多的對象信息,除了函數以外:前端工程師
typeof {key:'val'}; // Object is object typeof [1,2]; // Array is object typeof new Date; // Date object
而toString
不論是對於object
類型仍是primitive
類型,都能獲得你想要的結果:
var toClass = {}.toString; console.log(toClass.call(123)); console.log(toClass.call(true)); console.log(toClass.call(Symbol('foo'))); console.log(toClass.call('some string')); console.log(toClass.call([1, 2])); console.log(toClass.call(new Date())); console.log(toClass.call({ a: 'a' })); // output [object Number] [object Boolean] [object Symbol] [object String] [object Array] [object Date] [object Object]
在underscore
中你會看到如下代碼:
// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { _['is' + name] = function(obj) { return toString.call(obj) == '[object ' + name + ']'; }; });
這裏就是使用toString
來判斷變量類型,好比你能夠經過_.isFunction(someFunc)
來判斷someFunc
是否爲一個函數。
從上面的代碼咱們能夠看到toString
是可依賴的,不論是object
類型仍是primitive
類型,它都能告訴咱們正確的結果。但它只能夠用於判斷內置的數據類型,對於咱們本身構造的對象,它仍是不能給出咱們想要的結果,好比下面的代碼:
function Person() { } var a = new Person(); // [object Object] console.log({}.toString.call(a)); console.log(a instanceof Person);
咱們這時候就要用到咱們下面介紹的instanceof
了。
對於使用構造函數建立的對象,咱們一般使用instanceof
來判斷某一實例是否屬於某種類型,例如:a instanceof Person
,其內部原理其實是判斷Person.prototype
是否在a
實例的原型鏈中,其原理能夠用下面的函數來表達:
function instance_of(V, F) { var O = F.prototype; V = V.__proto__; while (true) { if (V === null) return false; if (O === V) return true; V = V.__proto__; } } // use function Person() { } var a = new Person(); // true console.log(instance_of(a, Person));
由於JavaScript
是動態類型,變量是沒有類型的,能夠隨時賦予任意值。可是各類運算符或條件判斷中是須要特定類型的,好比if
判斷時會將判斷語句轉換爲布爾型。下面就來深刻了解下JavaScript
中類型轉換。
當咱們須要將變量轉換爲原始類型時,就須要用到ToPrimitive
,下面的代碼說明了ToPrimitive
的內部實現原理:
// ECMA-262, section 9.1, page 30. Use null/undefined for no hint, // (1) for number hint, and (2) for string hint. function ToPrimitive(x, hint) { // Fast case check. if (IS_STRING(x)) return x; // Normal behavior. if (!IS_SPEC_OBJECT(x)) return x; if (IS_SYMBOL_WRAPPER(x)) throw MakeTypeError(kSymbolToPrimitive); if (hint == NO_HINT) hint = (IS_DATE(x)) ? STRING_HINT : NUMBER_HINT; return (hint == NUMBER_HINT) ? DefaultNumber(x) : DefaultString(x); } // ECMA-262, section 8.6.2.6, page 28. function DefaultNumber(x) { if (!IS_SYMBOL_WRAPPER(x)) { var valueOf = x.valueOf; if (IS_SPEC_FUNCTION(valueOf)) { var v = %_CallFunction(x, valueOf); if (IsPrimitive(v)) return v; } var toString = x.toString; if (IS_SPEC_FUNCTION(toString)) { var s = %_CallFunction(x, toString); if (IsPrimitive(s)) return s; } } throw MakeTypeError(kCannotConvertToPrimitive); } // ECMA-262, section 8.6.2.6, page 28. function DefaultString(x) { if (!IS_SYMBOL_WRAPPER(x)) { var toString = x.toString; if (IS_SPEC_FUNCTION(toString)) { var s = %_CallFunction(x, toString); if (IsPrimitive(s)) return s; } var valueOf = x.valueOf; if (IS_SPEC_FUNCTION(valueOf)) { var v = %_CallFunction(x, valueOf); if (IsPrimitive(v)) return v; } } throw MakeTypeError(kCannotConvertToPrimitive); }
上面代碼的邏輯是這樣的:
!IS_SPEC_OBJECT(x)
,直接返回IS_SYMBOL_WRAPPER(x)
,則拋出異常不然會根據傳入的hint
來調用DefaultNumber
和DefaultString
,好比若是爲Date
對象,會調用DefaultString
DefaultNumber
:首先x.valueOf
,若是爲primitive
,則返回valueOf
後的值,不然繼續調用x.toString
,若是爲primitive
,則返回toString
後的值,不然拋出異常DefaultString
:和DefaultNumber
正好相反,先調用toString
,若是不是primitive
再調用valueOf
那講了實現原理,這個ToPrimitive
有什麼用呢?實際不少操做會調用ToPrimitive
,好比加
、相等
或比較
操。在進行加
操做時會將左右操做數轉換爲primitive
,而後進行相加。
下面來個實例,({}) + 1
(將{}
放在括號中是爲了內核將其認爲一個代碼塊)會輸出啥?可能平常寫代碼並不會這樣寫,不過網上出過相似的面試題。
加
操做只有左右運算符同時爲String
或Number
時會執行對應的%_StringAdd
或%NumberAdd
,下面看下({}) + 1
內部會通過哪些步驟:
{}
和1
首先會調用ToPrimitive
{}
會走到DefaultNumber
,首先會調用valueOf
,返回的是Object {}
,不是primitive
類型,從而繼續走到toString
,返回[object Object]
,是String
類型[object Object]1
再好比有人問你[] + 1
輸出啥時,你可能知道應該怎麼去計算了,先對[]
調用ToPrimitive
,返回空字符串,最後結果爲"1"
。
除了ToPrimitive
以外,還有更細粒度的ToBoolean
、ToNumber
和ToString
,好比在須要布爾型時,會經過ToBoolean
來進行轉換。看一下源碼咱們能夠很清楚的知道這些布爾型、數字等之間轉換是怎麼發生:
// ECMA-262, section 9.2, page 30 function ToBoolean(x) { if (IS_BOOLEAN(x)) return x; // 字符串轉布爾型時,若是length不爲0就返回true if (IS_STRING(x)) return x.length != 0; if (x == null) return false; // 數字轉布爾型時,變量不爲0或NAN時返回true if (IS_NUMBER(x)) return !((x == 0) || NUMBER_IS_NAN(x)); return true; } // ECMA-262, section 9.3, page 31. function ToNumber(x) { if (IS_NUMBER(x)) return x; // 字符串轉數字調用StringToNumber if (IS_STRING(x)) { return %_HasCachedArrayIndex(x) ? %_GetCachedArrayIndex(x) : %StringToNumber(x); } // 布爾型轉數字時true返回1,false返回0 if (IS_BOOLEAN(x)) return x ? 1 : 0; // undefined返回NAN if (IS_UNDEFINED(x)) return NAN; // Symbol拋出異常,例如:Symbol() + 1 if (IS_SYMBOL(x)) throw MakeTypeError(kSymbolToNumber); return (IS_NULL(x)) ? 0 : ToNumber(DefaultNumber(x)); } // ECMA-262, section 9.8, page 35. function ToString(x) { if (IS_STRING(x)) return x; // 數字轉字符串,調用內部的_NumberToString if (IS_NUMBER(x)) return %_NumberToString(x); // 布爾型轉字符串,true返回字符串true if (IS_BOOLEAN(x)) return x ? 'true' : 'false'; // undefined轉字符串,返回undefined if (IS_UNDEFINED(x)) return 'undefined'; // Symbol拋出異常 if (IS_SYMBOL(x)) throw MakeTypeError(kSymbolToString); return (IS_NULL(x)) ? 'null' : ToString(DefaultString(x)); }
講了這麼多原理,那這個ToPrimitive
有什麼卵用呢?這對於咱們瞭解JavaScript內部的隱式轉換和一些細節是很是有用的,好比:
var a = '[object Object]'; if (a == {}) { console.log('something'); }
你以爲會不會輸出something
呢,答案是會的,因此這也是爲何不少代碼規範推薦使用===
三等了。那這裏爲何會相等呢,是由於進行相等操做時,對{}
調用了ToPrimitive
,返回的結果就是[object Object]
,也就返回了true
了。咱們能夠看下JavaScript中EQUALS的源碼就一目瞭然了:
// ECMA-262 Section 11.9.3. EQUALS = function EQUALS(y) { if (IS_STRING(this) && IS_STRING(y)) return %StringEquals(this, y); var x = this; while (true) { if (IS_NUMBER(x)) { while (true) { if (IS_NUMBER(y)) return %NumberEquals(x, y); if (IS_NULL_OR_UNDEFINED(y)) return 1; // not equal if (IS_SYMBOL(y)) return 1; // not equal if (!IS_SPEC_OBJECT(y)) { // String or boolean. return %NumberEquals(x, %$toNumber(y)); } y = %$toPrimitive(y, NO_HINT); } } else if (IS_STRING(x)) { // 上面的代碼就是進入了這裏,對y調用了toPrimitive while (true) { if (IS_STRING(y)) return %StringEquals(x, y); if (IS_SYMBOL(y)) return 1; // not equal if (IS_NUMBER(y)) return %NumberEquals(%$toNumber(x), y); if (IS_BOOLEAN(y)) return %NumberEquals(%$toNumber(x), %$toNumber(y)); if (IS_NULL_OR_UNDEFINED(y)) return 1; // not equal y = %$toPrimitive(y, NO_HINT); } } else if (IS_SYMBOL(x)) { if (IS_SYMBOL(y)) return %_ObjectEquals(x, y) ? 0 : 1; return 1; // not equal } else if (IS_BOOLEAN(x)) { if (IS_BOOLEAN(y)) return %_ObjectEquals(x, y) ? 0 : 1; if (IS_NULL_OR_UNDEFINED(y)) return 1; if (IS_NUMBER(y)) return %NumberEquals(%$toNumber(x), y); if (IS_STRING(y)) return %NumberEquals(%$toNumber(x), %$toNumber(y)); if (IS_SYMBOL(y)) return 1; // not equal // y is object. x = %$toNumber(x); y = %$toPrimitive(y, NO_HINT); } else if (IS_NULL_OR_UNDEFINED(x)) { return IS_NULL_OR_UNDEFINED(y) ? 0 : 1; } else { // x is an object. if (IS_SPEC_OBJECT(y)) { return %_ObjectEquals(x, y) ? 0 : 1; } if (IS_NULL_OR_UNDEFINED(y)) return 1; // not equal if (IS_SYMBOL(y)) return 1; // not equal if (IS_BOOLEAN(y)) y = %$toNumber(y); x = %$toPrimitive(x, NO_HINT); } } }
因此瞭解變量如何轉換爲primitive
類型的重要性也就可想而知了。具體的代碼細節能夠看這裏:runtime.js。
ToObject
顧名思義就是將變量轉換爲對象類型。能夠看下它是如何將非對象類型轉換爲對象類型:
// ECMA-262, section 9.9, page 36. function ToObject(x) { if (IS_STRING(x)) return new GlobalString(x); if (IS_NUMBER(x)) return new GlobalNumber(x); if (IS_BOOLEAN(x)) return new GlobalBoolean(x); if (IS_SYMBOL(x)) return %NewSymbolWrapper(x); if (IS_NULL_OR_UNDEFINED(x) && !IS_UNDETECTABLE(x)) { throw MakeTypeError(kUndefinedOrNullToObject); } return x; }
由於平常代碼不多用到,就不展開了。
本文首發於有贊技術博客:http://tech.youzan.com/javasc...