一文详解如何用原型链的方式实现JS继承
本文深入解析JavaScript原型链机制,详细演示如何通过修改构造函数原型实现继承。重点对比Object.create、__proto__及父类实例三种方案,分析各自的性能、兼容性与副作用,推荐Object.create作为最佳实践,并总结核心知识点。
今天介绍一道关于原型链的经典面试题。
原型链是什么
在 JavaScript 中,每次创建对象时,系统都会自动为该对象提供一个内置的 [[Prototype]] 属性。这个被引用的对象被称为原型对象,[[Prototype]] 属性的层层嵌套关系便构成了原型链。
当访问一个对象的属性时,若该对象自身不存在此属性,JavaScript 引擎会沿着原型链向上追溯,直到找到第一个拥有该属性的原型对象并返回其值。
原型链并非无限延伸,它类似于单链表结构。最后一个原型对象的 [[Prototype]] 值为 null。如果沿原型链查找所有对象都未找到指定属性,最终将返回 undefined。
虽然 [[Prototype]] 属性无法直接通过脚本赋值,但大多数浏览器提供了 __proto__ 属性来访问该内置对象。需要注意的是,__proto__ 并非 ECMAScript 标准,因此不具备全浏览器的兼容性。
接下来通过几个具体示例,帮助读者建立对原型链的直观认识:
- 使用对象字面量声明 a = {} 时,a 的 [[prototype]] 指向 Object.prototype。此时的原型链结构为:a -> Object.prototype -> null。这里存在一个常见误区,即认为 a 的上一个原型对象是 Object,这是错误的,因为 Object 实际上是一个构造函数。
- 若声明数组 arr = [1, 2, 4],其原型链结构则为:arr -> Array.prototype -> Object.prototype -> null。
- 调用 Object.create(null) 可以创建一个真正的空对象,其 [[prototype]] 为 null。这种技术常用于构建字符串哈希表,例如在 Vue 源码中便频繁使用此方式。
通过构造函数创建实例对象
在 JavaScript 中,配合 new 关键字使用的函数即可视为构造函数。从理论上讲,任何函数都可以充当构造函数。
当声明一个构造函数时,它会自动拥有一个名为 prototype 的属性,该属性指向一个对象,即原型对象(注意这与 [[prototype]] 不同)。该原型对象的 constructor 属性则反向指向构造函数本身。
当使用 new 关键字创建对象时,新生成的对象的 [[prototype]] 会指向该构造函数的 prototype 属性。
function Rect() {}
const rect = new Rect()
rect.__proto__ === Rect.prototype // true
Rect.prototype.constructor === Rect // true无论通过 new Rect() 创建多少次对象,其 [[prototype]] 始终指向 Rect.prototype。此外,Rect.prototype.prototype 指向的是 Object.prototype。

通过向构造函数的原型对象(如 Rect.prototype)添加方法(例如 Rect.prototype.draw),可以使多个实例对象共享同一份方法代码,从而有效减少内存占用。
用原型链的方式实现继承
理解了构造函数如何影响实例对象的原型链后,我们来探讨核心问题:如何利用原型链机制实现继承。
假设存在一个 Shape 构造函数(作为父类)和一个 Rect 构造函数(作为子类)。相关代码如下:
// 父类
function Shape() {}
Shape.prototype.draw = function() {
console.log('Shape Draw')
}
Shape.prototype.clear = function() {
console.log('Shape Clear')
}
// 子类
function Rect() {}
/**
实现继承的代码放这里
**/
Rect.prototype.draw = function() {
console.log('Rect Draw')
}基于前文知识,正常情况下使用 new Rect 创建的实例对象,其原型链结构如下:
rect -> Rect.prototype -> Object.protoype -> null我们要实现的继承,本质是在原型链中插入一个 Shape.prototype 原型对象。为此,需要对 Rect.prototype 进行特殊处理。
方法1:Object.create
Rect.prototype = Object.create(Shape.prototype)
Rect.prototype.constructor = Rect // 选用,如果要用到 constructorRect.prototype.constructor = Rect // 可选,若需使用 constructor 属性
Object.create(proto) 是一个功能强大的方法,它能创建一个空对象,并将其 [[prototype]] 设置为传入的参数对象。
由于无法直接通过代码为 [[prototype]] 属性赋值,因此可以使用 Object.create 方法作为替代方案。
由于 Rect.prototype 现在指向了一个新对象,原有的 constructor 属性会丢失。如果业务需要,可以手动将其恢复。
该方法的缺点是替换了原有的原型对象。

方法2:直接修改 [[prototype]]
如果不想创建新对象,仅希望修改原对象,可以使用已被废弃的 __proto__ 属性,但官方并不推荐此做法。
此外,还有一个 Object.setPrototypeOf() 方法可用于修改对象的 [[prototype]],但考虑到性能问题,同样不推荐使用。
Object.setPrototypeOf(Rect.prototype, Shape.prototype)
// 或
Rect.prototype.__proto__ = Shape.prototype尽管上述方法均不推荐,但在特定场景下确实可行。
方法3:使用父类的实例
Rect.prototype = new Shape()形成的原型链结构为:
rect -> shape(替代掉原来的 Rect.prototype) -> Shape.prototype -> Object.prototype -> null该方法基本可用,但存在副作用风险。执行 new Shape() 时可能会产生意外行为,例如构造函数内添加了属性或发送了网络请求,具体后果取决于构造函数内部的代码逻辑。
从某种意义上说,这一副作用可能是致命的,因此不推荐使用。
总结
利用原型链实现 JS 继承的核心目标,是确保构造函数 Son 创建的实例 son,其原型链上包含父类 Parent.prototype。最终操作即修改 Son.prototype 的 [[prototype]] 指向。
综合考虑性能、兼容性及潜在副作用,推荐使用方法 1,即通过 Object.create(Parent.prototype) 创建一个指定了 [[prototype]] 的新对象,并用其替换原来的 Son.prototype 指向的对象。
回顾几个核心知识点:
- 任何对象都具备 [[prototype]] 属性。当读写对象属性且当前对象不存在该属性时,引擎会访问 [[prototype]] 指向的对象尝试获取属性,由此形成原型链。
- 函数创建时,其 prototype 属性会指向一个原型对象。当函数作为构造函数并通过 new 创建新对象时,新对象的 [[prototype]] 将指向该原型对象。
- JS 实现 “类” 继承的本质,是通过处理构造函数的 prototype 对象来修改原型链结构。
以上是一文详解如何用原型链的方式实现JS继承的详细内容,更多关于JS 原型链 继承的资料请关注本站其它相关文章!
JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13
