【JS】类型判断instanceof、typeof、isArray对比与原理解析

1,419 阅读4分钟

谈到JS的类型判断,首先我们先了解JS当中的数据类型内容。

JS数据基本类型和引用类型

类型判断的几种方式

typeof value

  • 取值:'undefined'/'boolean'/'string'/'number'/'object'/'function'/'symbol'
  • typeof不能判断object具体是哪种类型,比如:Array之类的
  • typeof null 为'object',其实是错误的,因为null不是object类型。

判断原理:根据变量的机器码低位1-3位存储其类型信息。

  • 000:对象

  • 010:浮点数

  • 100:字符串

  • 110:布尔

  • 1:整数 因为null所有机器码都是0,所以typeof会把null判断为对象 typeof 原理

  • 适合的使用场景:判断除了object之外的基本类型,避免判断null。

  • 局限性:不适合判断Object,function,array等引用类型


value instanceof Type == true/false

  • instanceof实现原理:看实例的___proto____指向的原型链上,有没有跟右侧类型的prototype指向同一个对象的
function new_instance_of(leftVaule, rightVaule) { 
    let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值
    leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值
    while (true) {
    	if (leftVaule === null) {
            return false;	
        }
        if (leftVaule === rightProto) {
            return true;	
        } 
        leftVaule = leftVaule.__proto__ 
    }
}

instanceof原理

  • 局限:假设只有一个单一的全局环境。如果网页当中包含多个框架,存在两个以上不同的全局执行环境,因为存在两个以上不同版本的Array构造函数。如果从一个 框架向另一个框架传入一个数组,传入的数组与在第二个框架中原生创建的数组构造函数不同。《Javascript高级教程》

判断引用类型:Object.prototype.toString.call(value)

  • 原理

Object.prototype.toString关键代码中,最终返回的对象是'[object ' + classof(this) + ']'字符串。 可以理解为:说该方法会返回当前this对象的对应的类型class字符串

'use strict';
  require('./_redefine')(Object.prototype, 'toString', function toString() {
    return '[object ' + classof(this) + ']';
  }, true);

classof(this)伪代码逻辑:

this === undefined;return 'Undefined'
this === null;return 'Null'
O = ToObject(this value)
把当前对象转换成Object
因为js中Object key值只允许string或者Symbol类型
IsArray(O)判断是否是数组对象
IsArray判断逻辑:
Type(O)判断是否是Object,不是直接return false;
O是否为Array exotic object:
如果O的[[DefineOwnProperty]]和Array的方法一样:
大概逻辑是:
内置定义的length属性,length属性不可以人为修改
O内置定义的所有key值是可以类型转换成数字的字符串,
并且最终数字范围必须在0~2^32-1次方以内,
length的取值要大于0所有的key对应的array index
....
就认为这个是个Array对象,return 'Array';
...

Array DefineOwnProperty

注:不同版本的js规范中实现逻辑可能略有不同

ECMA#sec-object.prototype.tostring

  • 应用:
Array.isArray = function(value) { 
    return Object.prototype.toString.call(value) === '[object Array]';
}

// 通用的类型判断方法
function type(value,typeStr){
	var str =  Object.prototype.toString.call(value);
    var curType = str.slice(8,str.length-1);
    return curType.toLowerCase() === typeStr.toLowerCase(); 
}

Object toString参考

Object.prototype.toString方法的演进

最近查看最新的ECMA262文档中,有了以下Note:

Historically, this function was occasionally used to access the String value of the [[Class]] internal slot that was used in previous editions of this specification as a nominal type tag for various built-in objects. 上一版本中该方法是读取的[[Class]]内置插槽的字符串数值作为内置对象的类型名称 The above definition of toString preserves compatibility for legacy code that uses toString as a test for those specific kinds of built-in objects. It does not provide a reliable type testing mechanism for other kinds of built-in or program defined objects. In addition, programs can use @@toStringTag in ways that will invalidate the reliability of such legacy type tests. 在新的版本当中可以读取对象上面的@@toStringTag来判断内置对象的类型名称

[[Class]]内置插槽的字符串在后续更新的内置对象中弃用了。 [[class]] property in ES2015

@@toStringTag属性可以通过内置Symbol,Symbol.toStringTag访问

Symbol.toStringTag

我们可以来尝试一下,如果把Symbol.toStringTag改掉会怎么样

var str = new String('123'); 
str[Symbol.toStringTag] = 'test'; 
console.log(Object.prototype.toString.call(str));
//输出 [object test]

所以此属性是可以被覆盖的

不同类型的Symbol.toStringTag内容(代码来源lodash)
const argsTag = '[object Arguments]'
const arrayTag = '[object Array]'
const boolTag = '[object Boolean]'
const dateTag = '[object Date]'
const errorTag = '[object Error]'
const mapTag = '[object Map]'
const numberTag = '[object Number]'
const objectTag = '[object Object]'
const regexpTag = '[object RegExp]'
const setTag = '[object Set]'
const stringTag = '[object String]'
const symbolTag = '[object Symbol]'
const weakMapTag = '[object WeakMap]'

const arrayBufferTag = '[object ArrayBuffer]'
const dataViewTag = '[object DataView]'
const float32Tag = '[object Float32Array]'
const float64Tag = '[object Float64Array]'
const int8Tag = '[object Int8Array]'
const int16Tag = '[object Int16Array]'
const int32Tag = '[object Int32Array]'
const uint8Tag = '[object Uint8Array]'
const uint8ClampedTag = '[object Uint8ClampedArray]'
const uint16Tag = '[object Uint16Array]'
const uint32Tag = '[object Uint32Array]'

总结

  • 判断数据是否是基础数据类型:typeof
  • 判断数据是否具体是哪种引用类型:Object.prototype.toString.call(this)
  • 判断数据是否是其他类型的子集:instanceof

其他参考

JavaScript专题之类型判断(下)