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...