Javascript 数据类型检测及原理

2019-06-09  本文已影响0人  Nevermind

前言

数据类型检测是 JavaScript 中既基础又考验原理的知识,如果你对 JavaScript 数据类型检测还不清楚,那么这篇文章应该会帮到你。

一、typeof

返回所属类型的字符串格式。但不能判断 object 类型下具体的数据结构,是普通对象、Array 或 Date 等。

// "number"
typeof 123         
typeof NaN

typeof "123"        // "string"
typeof true         // "boolean"
typeof undefined    // "undefined"

// "symbol"
typeof Symbol('a')     
typeof Symbol.iterator 

// "object"
typeof {a:1}       
typeof [1,2,3]   
typeof null        
typeof new Date() 
typeof new Number(1)  

// "function"
function aaa(){}
typeof aaa         
typeof new Function()

为什么 typeof null === "object"

在 JavaScript 最初实现中,JavaScript 是由一个表示类型的标签和实际数据值表示,对象的类型标签是 0。null 代表空指针,在大多数平台下值为 0x00,因此 null 的类型标签也成了0。

为什么 typeof new String('abc') === "object"

首先涉及到装箱转换的概念

每种基本数据类型在对象中都有对应的类。装箱转换就是将基本类型转换成对应的对象

new String('abc') 是将基本字符串装箱转换成字符串对象,所以返回"object"
我们可以通过 valueOf() 方法将字符串对象转换回基本字符串。

二、 instanceof

obj instanceof Foo检测构造函数的 prototype 是否出现在对象原型链中的任何位置。
问题1:由于是使用原型链查找机制,所以不能检测基本数据类型。
问题2: Foo.prototype 的值和 obj.__proto__ 都可能发生改变,导致检测结果不正确

function D(){}
function C(){}
let c = new C();
let d = new D()
c instanceof C  // true
d instanceof D  // true

// 修改 d.__proto__
Object.setPrototypeOf(d, C.prototype)   
d instanceof D  // false
d instanceof C  // true

// 修改 C.prototype
C.prototype = {};
c instanceof C  // false

扩展
使用 Symbol.hasInstance 可以自定义 instanceof 在某个类上的行为

class MyArray {  
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
}
console.log([] instanceof MyArray); // true

三、Object.prototype.toString.call()

Symbol.toStringTag
Symbol.toStringTag 通常作为对象的属性使用,属性值是字符串类型,表示对象的自定义类型标签。Object.prototype.toString() 方法会读取这个标签,并包含在返回值中。

原理
这个方法会获取 this 对象的 [[class]]私有属性(ES6之后获取Symbol.toStringTag属性),然后包装进[object, ]返回

自定义的类,需要通过Symbol.toStringTag 属性设置类型标签后,才能返回正确的类

// 第一种情况
class Person{
    get [Symbol.toStringTag](){
        return 'Person'
    }
}

let p = new Person();
Object.prototype.toString.call(p);  // "[object Person]"
p.toString();                       // "[object Person]"

// 第二种情况
class Person{
    get [Symbol.toStringTag](){
        return 'Person'
    }
    toString() {
       return 'abc';
    }
}

let p = new Person();
Object.prototype.toString.call(p); // "[object Person]"
p.toString()                       // "abc"

对比以上两种情况,实例调用toString方法时,会先检测当前类的原型上是否有 toString() 方法,有则调用,没有则调用父类的toString()方法。所以第一种情况返回的结果和Object.prototype.toString.call(p) 执行结果一致。
所以我们一般使用Object.prototype.toString.call() 来检测数据类型而不是通过实例直接调用 toString()方法,是防止类重写 toString()方法。

上一篇下一篇

猜你喜欢

热点阅读