likes
comments
collection
share

每天一道JS手写题💪「Day1数据类型判断+手写instanceof」

作者站长头像
站长
· 阅读数 10

数据类型判断

typeof

typeof 可以区分基础数据类型,而对所有的引用数据类型都会返回 object, 对所有的类都会返回 function(在 ECMA-262 中实现 [[Call]];classes也是函数)。

typeof "Ken"                  // 返回 "string"
typeof 3.14                   // 返回 "number"
typeof false                  // 返回 "boolean"
typeof function () {}         // 返回 "function"
typeof undefined              // 返回 "undefined"
typeof [1,2,3,4]              // 返回 "object"
typeof {name:'Ken', age:18}   // 返回 "object"
typeof new Date()             // 返回 "object"
typeof null                   // 返回 "object"
typeof Object                 // 返回 "function"
typeof Number                 // 返回 "function"

instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。基础数据类型、undefined、null没有构造函数,都无法使用这一方法。

123 instanceof Number                // 返回 false
new Number(123) instanceof Number    // 返回 true

'123' instanceof String              // 输出 false
new String('123') instanceof String  // 返回 true

([]) instanceof Array                // 返回 true
({}) instanceof Object               // 返回 true
(function(){}) instanceof Function   // 返回 true

constructor

Object 实例的 constructor 数据属性返回一个引用,指向创建该实例对象的构造函数。注意,此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。

constructor能判断基本数据类型string、number、boolean和对象类型(array、function等等),但是它不能判断 undefined 和 null,试图从 undefined 和 null 上读属性是会报错的。

const o1 = {};
o1.constructor === Object; // true

const o2 = new Object();
o2.constructor === Object; // true

const a1 = [];
a1.constructor === Array; // true

const a2 = new Array();
a2.constructor === Array; // true

const n = 3;
n.constructor === Number; // true

Object.prototype.toString.call()

function typeOf(obj) {
    // Object.prototype.toString() 的输出是 [Object object] 这种格式,这里通过 slice 做了裁剪
    return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}

typeOf([])        //返回 'array'
typeOf({})        //返回 'object'
typeOf(new Date)  //返回 'date'

关于这种方法 mdn 文档中有提及 Object.prototype.toString()

以这种方式使用 toString() 是不可靠的;对象可以通过定义 Symbol.toStringTag 属性来更改 Object.prototype.toString() 的行为,从而导致意想不到的结果。

但实际开发中我们大多不会去修改 Symbol.toStringTag 属性的值,故而这种方法已经够用了。

为什么Object.prototype.toString.call()可以准确判断数据类型呢?

每天一道JS手写题💪「Day1数据类型判断+手写instanceof」

简单的说就是官方的定义,Object.prototype上的 toString 方法,它就是干这个的。

但要注意的是每个数据类,他们都重写了 toString() 方法,例如我们熟悉的打印数组和对象。所以判断类型必须使用 Object.prototype 上的 toString 方法,而不是当前数据本身的 toString

为什么最后要加 call() 呢?

因为 Object.prototype.toString() 返回的是调用者的类型。不论你 toString() 本身的入参写的是什么,在Object.prototype.toString() 中,他的调用者永远都是 Object.prototype;所以,在不加 call() 情况下,我们的出来的结果永远都是 [object Object]

这里的 call() 方法, 是为了改变 Object.prototype.toString 这个函数中的 this 指向。让 Object.prototype.toString 这个方法的 this 指向我们所传入的数据。

对比

typeofinstanceofconstructorObject.prototype.toString
可用性可以判断基本数据类型instanceof 可以用于判断具有构造函数的引用数据类型constructor能判断基本数据类型string、number、boolean和对象类型Object.prototype.toString.call() 方法是判断类型的最准确的方法
局限性对所有的引用类型都会返回object无法判断不具有构造函数的基本数据类型、undefined 和 null不能判断undefined和null对象可以通过定义Symbol.toStringTag 属性来更改 Object.prototype.toString() 的行为

写到这里我还有一个小疑惑,为什么 instanceof 无法判断基本数据类型,但是 constructor 可以呢?原来JavaScript 在读取基本数据类型的 constructor 属性时,会隐式地将原始值封装成相应的包装对象,这种行为是 JavaScript 语言规范中定义的, 最后其实你访问到的是这个临时的包装对象上的属性。

实现一个 instanceof

在动手实现 instanceof 之前,我们要先知道它的功能是如何实现的:

  1. instancof 有两个参数,一个待判断的对象obj和一个构造函数constructor
  2. 如果 instancof 右侧的参数不是一个函数,它会报错 Right-hand side of 'instanceof' is not an object.
  3. 它检查obj是否为一个对象或函数,如果不是,则返回false,因为基础数据类型不具有构造函数。
  4. 获取obj的原型,并沿着原型链向上查找,直到找到与constructor.prototype相同的原型或者到达原型链的顶端。如果找到了相同的原型,则返回true,否则返回false

代码:

function myInstanceOf(obj, constructor) {
    if (typeof constructor !== 'function') {
        throw new TypeError('Right-hand side of \'instanceof\' is not an object');
    }
    if (obj === null || typeof obj !== 'object' && typeof obj !== 'function') {
        return false;
    }
    let proto = Object.getPrototypeOf(obj);
    while (proto) {
        if (proto === constructor.prototype) {
            return true;
        }
        proto = Object.getPrototypeOf(proto);
    }
    return false;
}