【前端面试小册】JS-第32节:实现 instanceof

一、核心概念

1.1 instanceof 的作用

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

语法object instanceof constructor

返回值truefalse

1.2 基本使用

console.log([] instanceof Array);        // true
console.log({} instanceof Object);      // true
console.log([] instanceof Object);      // true
console.log(2 instanceof Number);       // false(基本类型)
console.log(new Number(2) instanceof Number);  // true

二、实现原理

2.1 基础实现

function myInstanceof(left, right) {
    let _left = left.__proto__;
    let _right = right.prototype;
    
    while (true) {
        if (_left === null) {
            return false;
        }
        if (_left === _right) {
            return true;
        }
        _left = _left.__proto__;
    }
}

2.2 原理解析

执行流程

graph TD
    A[调用 myInstanceof] --> B[获取 left 的 __proto__]
    B --> C[获取 right 的 prototype]
    C --> D{left.__proto__ === null?}
    D -->|是| E[返回 false]
    D -->|否| F{left.__proto__ === right.prototype?}
    F -->|是| G[返回 true]
    F -->|否| H[向上查找 __proto__]
    H --> D

关键点

  • 沿着 left 的原型链向上查找
  • 比较 left.__proto__right.prototype
  • 如果找到则返回 true,到达 null 则返回 false

2.3 执行示例

// 示例:[] instanceof Array
// 步骤 1: left = [], right = Array
// 步骤 2: _left = [].__proto__ = Array.prototype
// 步骤 3: _right = Array.prototype
// 步骤 4: _left === _right,返回 true

// 示例:[] instanceof Object
// 步骤 1: left = [], right = Object
// 步骤 2: _left = [].__proto__ = Array.prototype
// 步骤 3: _right = Object.prototype
// 步骤 4: _left !== _right,继续向上
// 步骤 5: _left = Array.prototype.__proto__ = Object.prototype
// 步骤 6: _left === _right,返回 true

三、优化版本

3.1 添加参数验证

function myInstanceof(left, right) {
    // 参数验证
    if (left === null || left === undefined) {
        return false;
    }
    
    if (typeof right !== 'function') {
        throw new TypeError('Right-hand side of instanceof is not callable');
    }
    
    let _left = left.__proto__;
    let _right = right.prototype;
    
    while (true) {
        if (_left === null) {
            return false;
        }
        if (_left === _right) {
            return true;
        }
        _left = _left.__proto__;
    }
}

3.2 使用 Object.getPrototypeOf

function myInstanceof(left, right) {
    if (left === null || left === undefined) {
        return false;
    }
    
    if (typeof right !== 'function') {
        throw new TypeError('Right-hand side of instanceof is not callable');
    }
    
    let proto = Object.getPrototypeOf(left);
    const prototype = right.prototype;
    
    while (proto !== null) {
        if (proto === prototype) {
            return true;
        }
        proto = Object.getPrototypeOf(proto);
    }
    
    return false;
}

优点

  • 使用标准 API Object.getPrototypeOf
  • 不依赖 __proto__(非标准属性)

3.3 完整版本

function myInstanceof(left, right) {
    // 基本类型直接返回 false
    if (left === null || left === undefined) {
        return false;
    }
    
    // right 必须是函数
    if (typeof right !== 'function') {
        throw new TypeError('Right-hand side of instanceof is not callable');
    }
    
    // 基本类型的包装对象需要特殊处理
    if (typeof left !== 'object' && typeof left !== 'function') {
        return false;
    }
    
    // 沿着原型链向上查找
    let proto = Object.getPrototypeOf(left);
    const prototype = right.prototype;
    
    while (proto !== null) {
        if (proto === prototype) {
            return true;
        }
        proto = Object.getPrototypeOf(proto);
    }
    
    return false;
}

四、测试用例

4.1 基础测试

// 测试 1:数组
const res = myInstanceof([], Array);
console.log(res);  // true

// 测试 2:对象
const res1 = myInstanceof({}, Object);
console.log(res1);  // true

// 测试 3:数组是 Object 的实例
const res2 = myInstanceof([], Object);
console.log(res2);  // true

// 测试 4:基本类型
const res3 = myInstanceof(2, Number);
console.log(res3);  // false

4.2 复杂测试

// 测试 5:自定义类
class Person {}
class Student extends Person {}

const student = new Student();
console.log(myInstanceof(student, Student));  // true
console.log(myInstanceof(student, Person));    // true
console.log(myInstanceof(student, Object));    // true

// 测试 6:函数
function fn() {}
console.log(myInstanceof(fn, Function));      // true
console.log(myInstanceof(fn, Object));        // true

// 测试 7:Date
const date = new Date();
console.log(myInstanceof(date, Date));        // true
console.log(myInstanceof(date, Object));      // true

// 测试 8:RegExp
const regex = /test/;
console.log(myInstanceof(regex, RegExp));     // true

4.3 边界情况

// 测试 9:null 和 undefined
console.log(myInstanceof(null, Object));      // false
console.log(myInstanceof(undefined, Object)); // false

// 测试 10:基本类型
console.log(myInstanceof('string', String));  // false
console.log(myInstanceof(true, Boolean));     // false

// 测试 11:包装对象
console.log(myInstanceof(new String(''), String));  // true
console.log(myInstanceof(new Number(2), Number));  // true

五、与原生的区别

5.1 行为一致性

// 原生 instanceof
console.log([] instanceof Array);        // true
console.log({} instanceof Object);      // true

// 我们的实现
console.log(myInstanceof([], Array));    // true
console.log(myInstanceof({}, Object));  // true

5.2 错误处理

// 原生 instanceof
try {
    [] instanceof null;
} catch (e) {
    console.log(e.message);  // Right-hand side of instanceof is not callable
}

// 我们的实现也应该抛出相同的错误
try {
    myInstanceof([], null);
} catch (e) {
    console.log(e.message);  // Right-hand side of instanceof is not callable
}

六、实际应用场景

6.1 类型检查

function processData(data) {
    if (myInstanceof(data, Array)) {
        return data.map(item => processItem(item));
    }
    
    if (myInstanceof(data, Object)) {
        return Object.keys(data).reduce((acc, key) => {
            acc[key] = processData(data[key]);
            return acc;
        }, {});
    }
    
    return data;
}

6.2 参数验证

function createUser(userData) {
    if (!myInstanceof(userData, Object)) {
        throw new TypeError('userData must be an object');
    }
    
    if (!myInstanceof(userData.name, String)) {
        throw new TypeError('userData.name must be a string');
    }
    
    // 创建用户
    return new User(userData);
}

七、面试要点总结

核心知识点

  1. instanceof 原理:沿着原型链向上查找
  2. 实现方式:循环比较 __proto__prototype
  3. 边界情况:null、undefined、基本类型
  4. 标准 API:使用 Object.getPrototypeOf 更规范

常见面试题

Q1: 如何实现 instanceof?

答:沿着对象的原型链向上查找,比较 __proto__ 和构造函数的 prototype,如果找到则返回 true,到达 null 则返回 false

Q2: instanceof 和 typeof 的区别?

答:

  • typeof:返回基本类型字符串,无法区分对象类型
  • instanceof:判断对象是否为某个构造函数的实例,可以判断对象类型

Q3: 为什么基本类型 instanceof 返回 false?

答:instanceof 检查的是对象,基本类型不是对象,所以返回 false。需要使用包装对象(如 new Number(2))才能返回 true

实战建议

  • ✅ 理解原型链的查找机制
  • ✅ 掌握 __proto__prototype 的关系
  • ✅ 注意边界情况的处理
  • ✅ 使用标准 API Object.getPrototypeOf
#前端面试##银行##百度##滴滴##腾讯#
前端面试小册 文章被收录于专栏

每天更新3-4节,持续更新中... 目标:50天学完,上岸银行总行!

全部评论

相关推荐

11-12 08:07
已编辑
门头沟学院 Java
第一次面试这种中大厂,我个人感觉有点压力面(也可能是鼠鼠太菜了),被狠狠拷打力1、开场直接自我介绍2、我看你项目利用threadlocal解决kryo序列化器的线程安全,你是怎么解决的?被指出我的描述错误,实际上是解决了kryo上下文使用的一个问题,并没有解决线程安全,我跟面试官说用threadlocal不就保证一个线程使用一个独立kryo序列化器了吗?他说这不还是没有解决kryo本身的线程安全问题么。我也不想说什么了3、api和spi的区别是什么?回答的有点乱,之前没有系统去理解他们的区别4、jdk动态代理里你是怎么知道要代理哪个服务呢?你说你用服务发现从etcd得到服务列表,那你有用到哪些负载均衡器?有没有用容错和熔断限流?具体是哪些?那你动态代理里需要配置什么吗?回答有点乱,有点没清楚面试官想问配置的是什么东西5、你说你用rabbitmq解决订单业务解耦,我没太懂是解耦什么?我说是解耦订单创建业务,结果被拷打。为什么你要解耦订单创建呢?我说为了提高用户响应速度,提高服务体验。结果说你知道哪些平台是异步创建订单的?鼠鼠也不知道啊,面试官告诉我,主流平台没有异步订单创建的,因为如果用户发现订单列表没有自己刚刚买的订单怎么办呢?被"夸"我这个是奇思妙想😭😭😭,我之前问ai大人也没告诉过我这个有问题啊6、你说使用Redis➕caffeine实现二级缓存,实现缓存降级处理,怎么实现的?你确定能实现缓存降级吗,那如果Redis宕机,你caffeine未命中的请求你是怎么处理的?打到数据库的话,那怎么可以解决缓存降级呢?鼠鼠麻了,ai大人之前还是没指出我的问题,这里我心态其实有点炸了。可能面试看我有点窘迫,就说问点八股吧。我以为八股总不能这么难堪了吧,还是太年轻了。7、你说说jvm的垃圾回收器吧,你配置过哪些?(鼠鼠完全没有配置jvm的经验)说了默认是使用g1,然后把常见的垃圾回收器回答了。他说:cms有点老了,你可能不太用过,那你说说cms为什么会被替代吧,相比于g1来说?我不知道呢,我就说cms初衷是为了并发回收减少STW的时间,但是总体时间更长,因此后面被替代了。但是可能我说的有点乱,面试官有点没太理解,然后跟我说了一下g1的好处,并且让我多去了解一下8、面试官可能看我很紧张回答很乱,终于问了个简单的了。那你说说缓存三大问题吧,都是怎么解决的呢?鼠鼠这种基础八股可熟啊,直接按流程分类说完了,这次面试官终于没有追问了,完全胜利✌🏻9、那你说说缓存一致性怎么保证吧?回答了最终一致性使用延迟双删,强一致用分布式锁➕事务。那如果事务的缓存重建出现了异常怎么办?我说可以先重试多次,如果任然不行就抛出异常让事务回滚。结果面试官问,如果数据库没写成功,缓存写成功怎么处理呢,你总不能让缓存回滚吧?鼠鼠又有点懵了,支支吾吾说了那就规定顺序,必须数据库写成功之后才能进行写缓存,不知道对不对。那删除缓存你是怎么做的?我说使用删除语句。面试官问还有吗?我没懂什么意思,结果面试官说让我去了解一下Binlog的作用。我还没了解过Binlog这玩意在这有什么作用啊。那你说说延迟双删是怎么做的呢?那为什么要先操作数据库在删缓存呢?这里面试官其实想问我旁路缓存,但是这个有点忘了啊😭😭,总之一顿乱答10、那你讲讲mvcc吧。我简单说了什么redo log、版本链、读视图,事务可见版本4个规则,说实话有点忘了。面试官可能也看我了解不多也没追问了。11、双亲委派机制了解吗?那怎么打破呢?这个常规八股我直接朗诵了。行,那打破之后这个可以加载多次吗?我又懵了,我不确定的回答:应该只有当这个类加载失败的时候jvm会进行重新加载吧,但是如果正常加载的话应该不能重复加载吧。面试官直接说:是可以重复加载,只要在loadclass进行重写就行。这个我真不知道啊12、手撕算法环节:给你一个二叉树,返回这个二叉树的反向层序遍历。我简单想了一分钟,说先将正序层序遍历结果保存到数组a,再保存每一层节点数到数组len,再反向遍历len,从a中读取正确答案。因为没有电脑所以前十多分钟就是再讲思路,后面用了朋友的电脑在牛客上写,写了几分钟还没写完,由于时间不太够了,面试官说就这样吧,大概能理解我的思路了,但是说细节不是很完善,可以用更简单的办法实现层序遍历。我力扣第一遍还没过完,对这题不是很有印象,但是感觉我的思路应该没有问题,编码时间不够细节也不可能一次完善吧反问环节:13、您对我本次面试的表现给点建议和意见吧。就是让我把简历上刚刚提出来的问题好好改一下,也没说别的。然后就是问了公司的业务和技术栈方面,没了。总结:这次真的指出了我很多问题,无论是八股还是项目,感觉面试官人挺好,至少帮我纠错,还告诉我正确解决思路,虽然感觉对我来说全程压力面,但是我还是学到和了解到很多,就是被刷我还是要很感谢面试官啊。这一周把这次问题去好好处理巩固一下吧,这次实习面试强度好大啊
查看25道真题和解析
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务