原型与原型链:面试中的关键问题深入剖析

原型与原型链:面试中的关键问题深入剖析

一、instanceof 的底层机制

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。其底层算法可概括为:

javascript 复制代码
function myInstanceOf(instance, constructor) {
  let proto = Object.getPrototypeOf(instance);
  while (proto !== null) {
    if (proto === constructor.prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
  return false;
}

核心要点:

  • 判断依据是原型链上的对象 ,而非对象的 constructor 属性。
  • 右操作数必须是一个构造函数(拥有 prototype 属性)。
  • 如果原型链被手动修改(例如通过 Object.setPrototypeOf),instanceof 的结果会随之改变。

二、function、原型与对象之间的关系

在 JavaScript 中,函数、原型和对象构成一个三角关系:

  1. 函数也是对象 :每个函数都是 Function 构造函数的实例,因此函数具备 __proto__ 指向 Function.prototype
  2. 函数的 prototype 属性 :只有函数(非箭头函数)拥有 prototype 属性。当函数作为构造函数(使用 new 调用)时,生成的实例对象的 __proto__ 会指向该函数的 prototype
  3. 普通对象的 __proto__ :指向其构造函数的 prototype

关系示例:

javascript 复制代码
function Person(name) { this.name = name; }
const p = new Person('Alice');

// p.__proto__ === Person.prototype
// Person.prototype.constructor === Person
// Person.__proto__ === Function.prototype
// Function.prototype.__proto__ === Object.prototype

三、寄生式组合继承与 extends 继承的性能差异

寄生式组合继承(手动实现)

javascript 复制代码
function inherit(subType, superType) {
  const prototype = Object.create(superType.prototype);
  prototype.constructor = subType;
  subType.prototype = prototype;
}

function Super() { this.a = 1; }
function Sub() { Super.call(this); this.b = 2; }
inherit(Sub, Super);
  • 优点 :只调用一次父类构造函数(在 Sub 内部通过 Super.call(this)),避免在子类原型上创建多余的父类实例属性。
  • 性能:原型链较短,属性查找快;内存占用小(父类实例属性只存在于子类实例中,不在原型上)。

extends 继承(ES6 class)

javascript 复制代码
class Super { constructor() { this.a = 1; } }
class Sub extends Super { constructor() { super(); this.b = 2; } }
  • 底层:Babel 编译后近似于寄生式组合继承,但原生引擎实现更优化。
  • 性能差异
    • 构造速度extends 在 V8 等引擎中有专门优化,通常比手写寄生式组合继承快 10-20%。
    • 属性访问:几乎无差别。
    • 内存:二者相同,都不会将父类实例属性挂载到子类原型上。

结论 :优先使用 extends,除非需要兼容 ES5 环境。手写寄生式组合继承在现代引擎中略慢,但差距可忽略。

四、原型与构造函数的关系 & instanceof 为何不依赖 constructor

原型与构造函数的关系

  • 每个函数(构造函数)都有一个 prototype 属性,指向其原型对象。
  • 原型对象默认拥有一个 constructor 属性,指回该函数。
  • 实例对象的 __proto__ 指向构造函数的 prototype,因此实例可通过 __proto__.constructor 访问构造函数。

instanceof 不用 constructor 的原因

  1. constructor 可被随意修改

    javascript 复制代码
    function Dog() {}
    const dog = new Dog();
    dog.constructor = Array;
    console.log(dog instanceof Dog); // true(正确)
    console.log(dog.constructor === Dog); // false(被篡改)

    instanceof 依赖 constructor,结果会被误导。

  2. 原型链中可能存在多个构造函数instanceof 关心的是整个链条上是否存在目标原型,而不是某个对象的 constructor 值。例如,[] instanceof Objecttrue,但 [].constructor === Objectfalse(实际上是 Array)。

  3. constructor 不是标准原型链遍历的必由之路Object.getPrototypeOf 直接获取原型,不依赖 constructor 属性,更可靠。

五、隐式原型(__proto__)与显式原型(prototype)的关系与差异

特性 __proto__(隐式原型) prototype(显式原型)
拥有者 所有对象(包括函数) 只有函数(非箭头函数)
作用 指向构造函数的原型,构成原型链 定义通过该函数创建的实例的公共原型
标准性 非标准(但 ES6 定义了 Object.getPrototypeOf / setPrototypeOf 替代) 标准规范,所有函数自动拥有
关系 实例.__proto__ === 构造函数.prototype prototype 本身也是一个对象,拥有自己的 __proto__

关键差异

  • prototype 是构造函数专属,用于设置继承;__proto__ 是实例属性,用于读取原型链。
  • 修改 prototype 会影响所有未来实例的 __proto__;修改单个实例的 __proto__ 只影响该实例(不推荐)。
  • 函数也是对象,所以函数既有 __proto__(指向 Function.prototype),又有 prototype(指向其原型对象)。

六、Object.create 方式继承的优点

Object.create(proto, [propertiesObject]) 创建一个新对象,其 __proto__ 指向 proto

优点

  1. 纯粹的委托继承:无需构造函数,直接指定原型。适用于单纯的共享行为,不涉及构造逻辑。

  2. 避免构造函数副作用 :传统 new 会执行构造函数,可能产生不必要的初始化代码。Object.create 不会调用任何函数。

  3. 更自然的原型链 :常用于实现"原型式继承"(道格拉斯·克罗克福德提出),比 new 更直观地表达"继承自那个对象"。

  4. 支持 null 原型对象 :创建完全空的对象(无原型链),用于纯字典存储(避免 toString 等属性冲突):

    javascript 复制代码
    const pureDict = Object.create(null);
  5. 与寄生式组合继承完美配合 :寄生式组合继承中的 Object.create(superType.prototype) 正是利用该特性来隔离父类原型与子类原型,避免重复调用父类构造函数。

示例对比

javascript 复制代码
// 传统构造函数继承(需要两步)
function Parent() { this.name = 'parent'; }
function Child() { Parent.call(this); }
Child.prototype = new Parent(); // 副作用:Parent 被调用一次

// Object.create 方式
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child; // 无调用副作用,性能更优

总结Object.create 是 JavaScript 原型继承的核心工具,它提供了更纯粹、更灵活的原型链操作方式,是现代继承模式(包括 ES6 extends 底层)的基础。

相关推荐
weixin_382395235 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__5 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.6 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~7 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华8 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子10 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅10 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni11 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学12 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端