前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库ES5 原型继承写法 Object.create 改进
JavaScript对象与原型

ES5 继承为什么要写 Child.prototype = new Parent()?Object.create 改进了什么?

把 Child.prototype 设成 new Parent(),是为了让子类实例顺着原型链找到 Parent.prototype 上的方法。代价是父构造函数被白跑一次,父类的实例属性还跑到了原型上被所有子实例共享。改成 Object.create(Parent.prototype) 就只接上原型链,不执行父构造函数。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#对象与原型
先看核心答案读代码示例
理解线索

寄生组合继承的三块拼图

  1. Parent.call(this)在子构造函数里借用父构造函数,每个实例拿到自己的属性
  2. Object.create(Parent.prototype)造一个空对象当子类原型,它的原型是父类原型
  3. constructor 修正新原型对象上没有 constructor,要手动指回子类

ES6 的 class extends 在原型链上做的就是这套事情,另外还会把子类构造函数本身的原型指向父类,所以静态方法也能继承,这一点 ES5 写法通常会漏掉。

核心回答

先记住这个答案

子类实例要能调用父类方法,必须让 Child.prototype 的原型指向 Parent.prototype。早年没有 Object.create,唯一能造出这样一个对象的办法就是 new Parent(),所以才有了 Child.prototype = new Parent() 的写法。它的问题有两个:父构造函数在定义继承时就被执行了一次,可能带副作用或缺参数;父类在 this 上定义的属性变成了原型属性,所有子实例共享同一份。改进办法是 Child.prototype = Object.create(Parent.prototype),只接原型链不跑构造函数,再在子构造函数里 Parent.call(this) 初始化实例属性。

  • new Parent() 会接上父类原型链
  • 副作用:父构造函数多跑一次,引用类型属性被共享
  • Object.create 接链但不执行父构造器
  • 改完原型要把 constructor 指回子类

老写法的坑:数组被所有实例共享

先看 new Parent() 当原型会发生什么。

一个实例改了,另一个也跟着变JavaScript
function Animal(name) {
  this.name = name
  this.tags = []
}
Animal.prototype.say = function () {
  return 'I am ' + this.name
}

function Dog(name) {}
Dog.prototype = new Animal('proto') // 父构造函数在这里就跑了一次

const a = new Dog('a')
const b = new Dog('b')
a.tags.push('cute')
console.log(a.name, b.tags.length)
console.log(a.constructor === Animal)
查看输出与解释
proto 1
true

a 和 b 自己身上什么都没有,name 和 tags 都是从 Dog.prototype 那个 Animal 实例上找到的。所以 a.name 是 'proto' 而不是 'a',而 a.tags.push 改的是原型上那唯一一个数组,b 读到的也是它。最后一行说明 constructor 也丢了:Dog.prototype 被整个替换,新对象上的 constructor 是从 Animal.prototype 继承来的。

寄生组合继承:ES5 的标准答案

改进分两半做:实例属性交给 Parent.call(this, name),每次 new Child 都在新对象上跑一遍父构造函数,属性各有一份;方法继承交给 Object.create(Parent.prototype),它返回一个空对象,原型指向父类原型,不会执行 Parent。

三行代码接好继承
写法是这样的:子构造函数里调用 Animal.call(this, name);然后 Cat.prototype = Object.create(Animal.prototype);最后用 Object.defineProperty(Cat.prototype, 'constructor', { value: Cat, writable: true, configurable: true }) 把 constructor 补回来。用 defineProperty 而不是直接赋值,是为了让它和原生的一样不可枚举,for...in 时不会冒出来。 这样 new Cat('mimi').say() 返回 'I am mimi',两个 Cat 实例的 tags 互不影响,constructor 也指回了 Cat。

为什么说组合继承还不够

很多教材讲的「组合继承」是 Parent.call(this) 加 Child.prototype = new Parent()。实例属性确实不共享了,因为实例自己的 tags 会遮住原型上那个。但父构造函数一共跑了两次,原型上还留着一份没用的 name 和 tags,占内存,调试时看着也乱。寄生组合继承只是把第二步换成 Object.create,就把这两个问题一起去掉了。

回答前,多想一步

容易答错的地方

以为 Child.prototype = Parent.prototype 更简单
这样两个构造函数共用同一个原型对象,给 Child.prototype 加方法等于给 Parent.prototype 加方法,父类实例也能调子类方法,继承关系直接乱了。
忘了修 constructor
不修的话 child.constructor 指向父类。平时影响不大,但有些库会用 obj.constructor 创建同类对象,比如克隆工具,这时会造出父类实例。
以为 Object.create 会调用构造函数
Object.create(proto) 只是造一个原型为 proto 的空对象,完全不碰 proto.constructor,这正是它能避开副作用的原因。
试着用自己的话回答

面试官还会怎么问?

ES6 的 class extends 和寄生组合继承有什么区别?

原型链的结构一样。区别有三点:extends 会把 Child.__proto__ 设成 Parent,静态方法也能继承;子类必须先调用 super() 才能用 this,实例由父类构造函数创建,所以能正确继承 Array、Error 这些内置类;class 必须用 new 调用。

没有 Object.create 的环境怎么写?

用一个空的中间函数:function F() {},F.prototype = Parent.prototype,再 Child.prototype = new F()。F 什么都不做,所以没有副作用,这就是 Object.create 的经典 polyfill。

怎么让子类继承父类的静态方法?

ES5 写法里静态方法挂在构造函数本身上,原型链上接不到。可以 Object.setPrototypeOf(Child, Parent),或者把静态方法逐个复制过去,class extends 则会自动处理。

从一道题,走向一组知识

把知识连起来

对象与原型

Object.preventExtensions() 之后还能给对象加属性吗?怎么判断?

原型对象是普通对象,被 Object.preventExtensions 之后就不能再往上加方法,也不能再改它的原型。

对象与原型

call、apply、bind 有什么区别?各自什么时候用?

寄生组合继承里的 Parent.call(this, name) 就是 call 最经典的用法之一。

参考资料

  • JavaScript:对象与原型参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 老写法的坑:数组被所有实例共享
  3. 寄生组合继承:ES5 的标准答案
  4. 为什么说组合继承还不够
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑