前言
最近在准备面试,总被问到instanceof的实现原理。看了不少文章,感觉都是在背答案。今天自己动手写一遍,才发现原型链这东西真没那么玄乎。
我的实现思路
先上代码,这是我一个字一个字敲出来的:
function isinstanceof(a, b) {
// 第一反应:空值直接返回false
if (a == null || b == null) {
return false;
}
// 右边必须是个构造函数,不然还查什么?
if (typeof(b) != 'function') {
throw new TypeError("右边的参数不是构造函数")
}
// 左边必须是对象或函数,这是硬性要求
if (typeof(a) != 'object' && typeof(a) != 'function') {
throw new TypeError("左边的参数不是对象实例,或者函数")
}
// 核心逻辑:顺着原型链往上找
while(a.__proto__ != null) {
a = a.__proto__; // 关键一步:往上爬一级
if(a == b.prototype) { // 找到了!
return true;
}
if(a == null) { // 爬到顶了还没找到
return false;
}
}
return false;
}
几个让我踩坑的地方
- 为什么要用 a.proto?
一开始我很迷惑,__proto__到底是个啥?后来想明白了:
function Person() {}
const p = new Person();
// 这三者的关系要搞清楚
console.log(p.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
__proto__就是对象的“爹”,每个对象都知道自己从哪儿来。
- 为什么要判断null?
这里有个坑:null没有原型链!所以:
// 这两个都会返回false
console.log(isinstanceof(null, Object)); // false
console.log(isinstanceof({}, null)); // false
- 为什么原始类型要报错?
这是我的设计选择。原生的instanceof对原始类型直接返回false:
console.log(1 instanceof Number); // false
console.log('hello' instanceof String); // false
但我觉得这样太隐晦了,不如直接告诉开发者:“老哥,你这参数传错了!”
实测一下
function foo() {}
const x = new foo();
console.log(isinstanceof(x, foo)); // true - 最基本的
console.log(isinstanceof(x, Object)); // true - 继承关系
console.log(isinstanceof({}, Object)); // true - 字面量对象
// 报错情况
console.log(isinstanceof(1, Number)); // 报错:左边的参数不是对象实例,或者函数
console.log(isinstanceof({}, 123)); // 报错:右边的参数不是构造函数
我学到的
- 原型链就是一条链:每个对象都有__proto__,指向创建它的构造函数的prototype。一直找到null为止。
- 构造函数是特殊的:只有函数才有prototype属性,普通对象没有。
- instanceof不是类型检查:它检查的是原型链,不是数据类型。所以数组也是对象,函数也是对象。
可以改进的地方
写完后看了下MDN,发现我的实现有几个问题:
- __proto__不是标准属性,最好用Object.getPrototypeOf()
- 应该先获取原型再判断是否为null
- 循环条件可以更简洁
改进版:
function isinstanceofV2(a, b) {
if (typeof b !== 'function') {
throw new TypeError('右边必须是个函数');
}
// 沿用原生行为:原始类型返回false
if (a === null || typeof a !== 'object' && typeof a !== 'function') {
return false;
}
let proto = Object.getPrototypeOf(a);
while (proto !== null) {
if (proto === b.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
最后说两句
以前总觉得原型链很高深,自己动手写一遍发现也就那么回事。面试官再问instanceof原理,我就可以说:“这个我写过,核心就是顺着__proto__往上找……”
代码还是要自己写,光看是记不住的。