摘要:類型判斷類型檢測主要包括了和的三種方式來判斷變量的類型。對于這里的返回的確卻是,,有些人說被認為是沒有一個對象。但是各種運算符或條件判斷中是需要特定類型的,比如判斷時會將判斷語句轉換為布爾型。顧名思義就是將變量轉換為對象類型。
概述
JavaScript的類型判斷是前端工程師們每天代碼中必備的部分,每天肯定會寫上個很多遍if (a === "xxx")或if (typeof a === "object")類似的類型判斷語句,所以掌握JavaScript中類型判斷也是前端必備技能,以下會從JavaScript的類型,類型判斷以及一些內部實現來讓你深入了解JavaScript類型的那些事。
類型JavaScript中類型主要包括了primitive和object類型,其中primitive類型包括了:null、undefined、boolean、number、string和symbol(es6)。其他所有的都為object類型。
類型判斷類型檢測主要包括了:typeof、instanceof和toString的三種方式來判斷變量的類型。
typeoftypeof接受一個值并返回它的類型,它有兩種可能的語法:
typeof x
typeof(x)
當在primitive類型上使用typeof檢測變量類型時,我們總能得到我們想要的結果,比如:
typeof 1; // "number" typeof ""; // "string" typeof true; // "boolean" typeof bla; // "undefined" typeof undefined; // "undefined"
而當在object類型上使用typeof檢測時,有時可能并不能得到你想要的結果,比如:
typeof []; // "object" typeof null; // "object" typeof /regex/ // "object" typeof new String(""); // "object" typeof function(){}; // "function"
這里的[]返回的確卻是object,這可能并不是你想要的,因為數組是一個特殊的對象,有時候這可能并不是你想要的結果。
對于這里的null返回的確卻是object,wtf,有些人說null被認為是沒有一個對象。
當你對于typeof檢測數據類型不確定時,請謹慎使用。
toStringtypeof的問題主要在于不能告訴你過多的對象信息,除了函數之外:
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對于使用構造函數創建的對象,我們通常使用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,下面的代碼說明了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。
ToObjectToObject顧名思義就是將變量轉換為對象類型。可以看下它是如何將非對象類型轉換為對象類型:
// 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...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84323.html
摘要:異步那些事一基礎知識異步那些事二分布式事件異步那些事三異步那些事四異步那些事五異步腳本加載事件概念異步回調首先了講講中兩個方法和定義和用法方法用于在指定的毫秒數后調用函數或計算表達式。功能在事件循環的下一次循環中調用回調函數。 JS異步那些事 一 (基礎知識)JS異步那些事 二 (分布式事件)JS異步那些事 三 (Promise)JS異步那些事 四(HTML 5 Web Workers...
摘要:一前言記錄語言類型的一些問題。其它瀏覽器則完全按照對象定義的順序遍歷屬性。所以,順序這種事,還是要用數組來保證。詳細請參考對象遍歷順序三后記參考鏈接對象遍歷順序 一 前言 記錄javascript語言object類型的一些問題。 1. typeof []; // object 2. typeof {};// object 3. typeof null; //objec...
摘要:實際上也就是在原型鏈繼承的代碼中添加在子類的構造函數中調用父類構造函數。寄生組合式繼承在指定子類的原型的時候不必調用父類的構造函數,而是直接使用創建父類原型的副本。 原本地址:http://www.ahonn.me/2017/01/2... 眾所周知,JavaScript 的繼承是實現繼承,而沒有 Java 中的接口繼承。這是因為 JavaScript 中函數沒有簽名,而實現繼承依靠的...
摘要:遵循的是異步模塊定義規范,遵循的是通用模塊定義規范。不同的腳本加載這個模塊,得到的都是同一個實例。關于異步那些事就寫到這里了,很多地方理解的不夠深刻希望大家多多指教。 JS異步那些事 一 (基礎知識)JS異步那些事 二 (分布式事件)JS異步那些事 三 (Promise)JS異步那些事 四(HTML 5 Web Workers)JS異步那些事 五 (異步腳本加載) 異步腳本加載 阻塞性...
摘要:假設有兩個域名域名域名域名有分級的概念,也就是說域名與域名都是的子域名,又是的子域名在域名所使用的服務中,可以設置域名在服務端設置的時候,設置為或沒有區別,注意前面的點,即只要是為顯式的聲明,前面帶不帶點沒有區別。 1 Cookie簡介 Cookie是由W3C組織提出,最早由NetScape社區發展的一種機制。Cookie是存儲于訪問者的計算機中的變量。每當同一臺計算機通過瀏覽器請求某...
閱讀 1964·2021-11-22 15:29
閱讀 3259·2021-10-14 09:43
閱讀 1227·2021-10-08 10:22
閱讀 3349·2021-08-30 09:46
閱讀 1435·2019-08-30 15:55
閱讀 1930·2019-08-30 15:44
閱讀 853·2019-08-30 14:19
閱讀 1448·2019-08-30 13:13