摘要:類型判斷類型檢測(cè)主要包括了和的三種方式來判斷變量的類型。對(duì)于這里的返回的確卻是,,有些人說被認(rèn)為是沒有一個(gè)對(duì)象。但是各種運(yùn)算符或條件判斷中是需要特定類型的,比如判斷時(shí)會(huì)將判斷語句轉(zhuǎn)換為布爾型。顧名思義就是將變量轉(zhuǎn)換為對(duì)象類型。
概述
JavaScript的類型判斷是前端工程師們每天代碼中必備的部分,每天肯定會(huì)寫上個(gè)很多遍if (a === "xxx")或if (typeof a === "object")類似的類型判斷語句,所以掌握JavaScript中類型判斷也是前端必備技能,以下會(huì)從JavaScript的類型,類型判斷以及一些內(nèi)部實(shí)現(xiàn)來讓你深入了解JavaScript類型的那些事。
類型JavaScript中類型主要包括了primitive和object類型,其中primitive類型包括了:null、undefined、boolean、number、string和symbol(es6)。其他所有的都為object類型。
類型判斷類型檢測(cè)主要包括了:typeof、instanceof和toString的三種方式來判斷變量的類型。
typeoftypeof接受一個(gè)值并返回它的類型,它有兩種可能的語法:
typeof x
typeof(x)
當(dāng)在primitive類型上使用typeof檢測(cè)變量類型時(shí),我們總能得到我們想要的結(jié)果,比如:
typeof 1; // "number" typeof ""; // "string" typeof true; // "boolean" typeof bla; // "undefined" typeof undefined; // "undefined"
而當(dāng)在object類型上使用typeof檢測(cè)時(shí),有時(shí)可能并不能得到你想要的結(jié)果,比如:
typeof []; // "object" typeof null; // "object" typeof /regex/ // "object" typeof new String(""); // "object" typeof function(){}; // "function"
這里的[]返回的確卻是object,這可能并不是你想要的,因?yàn)閿?shù)組是一個(gè)特殊的對(duì)象,有時(shí)候這可能并不是你想要的結(jié)果。
對(duì)于這里的null返回的確卻是object,wtf,有些人說null被認(rèn)為是沒有一個(gè)對(duì)象。
當(dāng)你對(duì)于typeof檢測(cè)數(shù)據(jù)類型不確定時(shí),請(qǐng)謹(jǐn)慎使用。
toStringtypeof的問題主要在于不能告訴你過多的對(duì)象信息,除了函數(shù)之外:
typeof {key:"val"}; // Object is object typeof [1,2]; // Array is object typeof new Date; // Date object
而toString不管是對(duì)于object類型還是primitive類型,都能得到你想要的結(jié)果:
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中你會(huì)看到以下代碼:
// 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是否為一個(gè)函數(shù)。
從上面的代碼我們可以看到toString是可依賴的,不管是object類型還是primitive類型,它都能告訴我們正確的結(jié)果。但它只可以用于判斷內(nèi)置的數(shù)據(jù)類型,對(duì)于我們自己構(gòu)造的對(duì)象,它還是不能給出我們想要的結(jié)果,比如下面的代碼:
function Person() { } var a = new Person(); // [object Object] console.log({}.toString.call(a)); console.log(a instanceof Person);
我們這時(shí)候就要用到我們下面介紹的instanceof了。
instanceof對(duì)于使用構(gòu)造函數(shù)創(chuàng)建的對(duì)象,我們通常使用instanceof來判斷某一實(shí)例是否屬于某種類型,例如:a instanceof Person,其內(nèi)部原理實(shí)際上是判斷Person.prototype是否在a實(shí)例的原型鏈中,其原理可以用下面的函數(shù)來表達(dá):
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));類型轉(zhuǎn)換
因?yàn)?b>JavaScript是動(dòng)態(tài)類型,變量是沒有類型的,可以隨時(shí)賦予任意值。但是各種運(yùn)算符或條件判斷中是需要特定類型的,比如if判斷時(shí)會(huì)將判斷語句轉(zhuǎn)換為布爾型。下面就來深入了解下JavaScript中類型轉(zhuǎn)換。
ToPrimitive當(dāng)我們需要將變量轉(zhuǎn)換為原始類型時(shí),就需要用到ToPrimitive,下面的代碼說明了ToPrimitive的內(nèi)部實(shí)現(xiàn)原理:
// 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),則拋出異常
否則會(huì)根據(jù)傳入的hint來調(diào)用DefaultNumber和DefaultString,比如如果為Date對(duì)象,會(huì)調(diào)用DefaultString
DefaultNumber:首先x.valueOf,如果為primitive,則返回valueOf后的值,否則繼續(xù)調(diào)用x.toString,如果為primitive,則返回toString后的值,否則拋出異常
DefaultString:和DefaultNumber正好相反,先調(diào)用toString,如果不是primitive再調(diào)用valueOf
那講了實(shí)現(xiàn)原理,這個(gè)ToPrimitive有什么用呢?實(shí)際很多操作會(huì)調(diào)用ToPrimitive,比如加、相等或比較操。在進(jìn)行加操作時(shí)會(huì)將左右操作數(shù)轉(zhuǎn)換為primitive,然后進(jìn)行相加。
下面來個(gè)實(shí)例,({}) + 1(將{}放在括號(hào)中是為了內(nèi)核將其認(rèn)為一個(gè)代碼塊)會(huì)輸出啥?可能日常寫代碼并不會(huì)這樣寫,不過網(wǎng)上出過類似的面試題。
加操作只有左右運(yùn)算符同時(shí)為String或Number時(shí)會(huì)執(zhí)行對(duì)應(yīng)的%_StringAdd或%NumberAdd,下面看下({}) + 1內(nèi)部會(huì)經(jīng)過哪些步驟:
{}和1首先會(huì)調(diào)用ToPrimitive
{}會(huì)走到DefaultNumber,首先會(huì)調(diào)用valueOf,返回的是Object {},不是primitive類型,從而繼續(xù)走到toString,返回[object Object],是String類型
最后加操作,結(jié)果為[object Object]1
再比如有人問你[] + 1輸出啥時(shí),你可能知道應(yīng)該怎么去計(jì)算了,先對(duì)[]調(diào)用ToPrimitive,返回空字符串,最后結(jié)果為"1"。
除了ToPrimitive之外,還有更細(xì)粒度的ToBoolean、ToNumber和ToString,比如在需要布爾型時(shí),會(huì)通過ToBoolean來進(jìn)行轉(zhuǎn)換??匆幌略创a我們可以很清楚的知道這些布爾型、數(shù)字等之間轉(zhuǎn)換是怎么發(fā)生:
// ECMA-262, section 9.2, page 30 function ToBoolean(x) { if (IS_BOOLEAN(x)) return x; // 字符串轉(zhuǎn)布爾型時(shí),如果length不為0就返回true if (IS_STRING(x)) return x.length != 0; if (x == null) return false; // 數(shù)字轉(zhuǎn)布爾型時(shí),變量不為0或NAN時(shí)返回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; // 字符串轉(zhuǎn)數(shù)字調(diào)用StringToNumber if (IS_STRING(x)) { return %_HasCachedArrayIndex(x) ? %_GetCachedArrayIndex(x) : %StringToNumber(x); } // 布爾型轉(zhuǎn)數(shù)字時(shí)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; // 數(shù)字轉(zhuǎn)字符串,調(diào)用內(nèi)部的_NumberToString if (IS_NUMBER(x)) return %_NumberToString(x); // 布爾型轉(zhuǎn)字符串,true返回字符串true if (IS_BOOLEAN(x)) return x ? "true" : "false"; // undefined轉(zhuǎn)字符串,返回undefined if (IS_UNDEFINED(x)) return "undefined"; // Symbol拋出異常 if (IS_SYMBOL(x)) throw MakeTypeError(kSymbolToString); return (IS_NULL(x)) ? "null" : ToString(DefaultString(x)); }
講了這么多原理,那這個(gè)ToPrimitive有什么卵用呢?這對(duì)于我們了解JavaScript內(nèi)部的隱式轉(zhuǎn)換和一些細(xì)節(jié)是非常有用的,比如:
var a = "[object Object]"; if (a == {}) { console.log("something"); }
你覺得會(huì)不會(huì)輸出something呢,答案是會(huì)的,所以這也是為什么很多代碼規(guī)范推薦使用===三等了。那這里為什么會(huì)相等呢,是因?yàn)檫M(jìn)行相等操作時(shí),對(duì){}調(diào)用了ToPrimitive,返回的結(jié)果就是[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)) { // 上面的代碼就是進(jìn)入了這里,對(duì)y調(diào)用了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); } } }
所以了解變量如何轉(zhuǎn)換為primitive類型的重要性也就可想而知了。具體的代碼細(xì)節(jié)可以看這里:runtime.js。
ToObjectToObject顧名思義就是將變量轉(zhuǎn)換為對(duì)象類型。可以看下它是如何將非對(duì)象類型轉(zhuǎn)換為對(duì)象類型:
// 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; }
因?yàn)槿粘4a很少用到,就不展開了。
本文首發(fā)于有贊技術(shù)博客:http://tech.youzan.com/javasc...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/84323.html
摘要:異步那些事一基礎(chǔ)知識(shí)異步那些事二分布式事件異步那些事三異步那些事四異步那些事五異步腳本加載事件概念異步回調(diào)首先了講講中兩個(gè)方法和定義和用法方法用于在指定的毫秒數(shù)后調(diào)用函數(shù)或計(jì)算表達(dá)式。功能在事件循環(huán)的下一次循環(huán)中調(diào)用回調(diào)函數(shù)。 JS異步那些事 一 (基礎(chǔ)知識(shí))JS異步那些事 二 (分布式事件)JS異步那些事 三 (Promise)JS異步那些事 四(HTML 5 Web Workers...
摘要:一前言記錄語言類型的一些問題。其它瀏覽器則完全按照對(duì)象定義的順序遍歷屬性。所以,順序這種事,還是要用數(shù)組來保證。詳細(xì)請(qǐng)參考對(duì)象遍歷順序三后記參考鏈接對(duì)象遍歷順序 一 前言 記錄javascript語言object類型的一些問題。 1. typeof []; // object 2. typeof {};// object 3. typeof null; //objec...
摘要:實(shí)際上也就是在原型鏈繼承的代碼中添加在子類的構(gòu)造函數(shù)中調(diào)用父類構(gòu)造函數(shù)。寄生組合式繼承在指定子類的原型的時(shí)候不必調(diào)用父類的構(gòu)造函數(shù),而是直接使用創(chuàng)建父類原型的副本。 原本地址:http://www.ahonn.me/2017/01/2... 眾所周知,JavaScript 的繼承是實(shí)現(xiàn)繼承,而沒有 Java 中的接口繼承。這是因?yàn)?JavaScript 中函數(shù)沒有簽名,而實(shí)現(xiàn)繼承依靠的...
摘要:遵循的是異步模塊定義規(guī)范,遵循的是通用模塊定義規(guī)范。不同的腳本加載這個(gè)模塊,得到的都是同一個(gè)實(shí)例。關(guān)于異步那些事就寫到這里了,很多地方理解的不夠深刻希望大家多多指教。 JS異步那些事 一 (基礎(chǔ)知識(shí))JS異步那些事 二 (分布式事件)JS異步那些事 三 (Promise)JS異步那些事 四(HTML 5 Web Workers)JS異步那些事 五 (異步腳本加載) 異步腳本加載 阻塞性...
摘要:假設(shè)有兩個(gè)域名域名域名域名有分級(jí)的概念,也就是說域名與域名都是的子域名,又是的子域名在域名所使用的服務(wù)中,可以設(shè)置域名在服務(wù)端設(shè)置的時(shí)候,設(shè)置為或沒有區(qū)別,注意前面的點(diǎn),即只要是為顯式的聲明,前面帶不帶點(diǎn)沒有區(qū)別。 1 Cookie簡介 Cookie是由W3C組織提出,最早由NetScape社區(qū)發(fā)展的一種機(jī)制。Cookie是存儲(chǔ)于訪問者的計(jì)算機(jī)中的變量。每當(dāng)同一臺(tái)計(jì)算機(jī)通過瀏覽器請(qǐng)求某...
閱讀 1967·2021-11-22 15:29
閱讀 3266·2021-10-14 09:43
閱讀 1231·2021-10-08 10:22
閱讀 3354·2021-08-30 09:46
閱讀 1441·2019-08-30 15:55
閱讀 1936·2019-08-30 15:44
閱讀 859·2019-08-30 14:19
閱讀 1453·2019-08-30 13:13