每天一道JS手写题💪「Day1数据类型判断+手写instanceof」
数据类型判断
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()可以准确判断数据类型呢?
简单的说就是官方的定义,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
指向我们所传入的数据。
对比
typeof | instanceof | constructor | Object.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 之前,我们要先知道它的功能是如何实现的:
- instancof 有两个参数,一个待判断的对象
obj
和一个构造函数constructor
。 - 如果 instancof 右侧的参数不是一个函数,它会报错 Right-hand side of 'instanceof' is not an object.
- 它检查
obj
是否为一个对象或函数,如果不是,则返回false
,因为基础数据类型不具有构造函数。 - 获取
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;
}
转载自:https://juejin.cn/post/7275551289965084724