JavaScript 原型链:原型继承与 Object.create()
JavaScript 中的对象并非孤立存在,它们之间通过一种名为“原型链”的机制相互连接。理解这一机制,是掌握 JavaScript 对象继承的核心。本指南将直接剖析原型链的查找逻辑,并演示如何使用 Object.create() 构建清晰的继承关系。
1. 理解原型对象的自动链接
在 JavaScript 中,绝大多数对象都有一个特殊的内部链接,指向另一个对象。这个被指向的对象被称为“原型”。当你试图访问一个对象的属性时,如果该对象本身没有这个属性,JavaScript 引擎就会自动去它的原型对象里查找。
- 打开浏览器的开发者工具(通常按 F12)。
- 切换到 Console(控制台)面板。
- 输入以下代码创建一个空对象并按回车:
const myObj = {}; - 输入代码查看该对象的隐藏属性
__proto__(这是浏览器暴露出来的原型链接访问方式):myObj.__proto__; - 观察返回的结果。你会发现它返回了一个对象,这个对象就是 JavaScript 内置的
Object.prototype。这意味着myObj继承了Object.prototype上预定义的方法,如toString()和hasOwnProperty()。
2. 追踪原型链的查找路径
当访问一个不存在的属性时,引擎会沿着原型链一层层向上查找,直到找到该属性或到达链条顶端。如果到达顶端(通常是 null)仍未找到,则返回 undefined。
下面的流程图展示了属性查找的完整过程:
为了验证这条链条,我们可以构造一个三层结构。
- 定义一个父级对象
grandParent,包含一个属性name:const grandParent = { name: 'Grandpa', origin: 'Top Level' }; - 使用
Object.create()创建一个parent对象,让其原型指向grandParent:const parent = Object.create(grandParent); parent.role = 'Father'; - 再次使用
Object.create()创建一个child对象,让其原型指向parent:const child = Object.create(parent); child.role = 'Son'; - 尝试访问
child身上不存在的属性:console.log(child.origin);结果:控制台输出了
'Top Level'。这说明 JavaScript 引擎按顺序在child->parent->grandParent这条链上进行了查找。
3. 使用 Object.create() 实现纯原型继承
传统的构造函数继承方式(配合 new 关键字)往往代码冗余且逻辑复杂。Object.create() 提供了一种更直接、更纯粹的方式来指定对象的原型。它的作用是:创建一个新对象,并使用现有对象作为新创建对象的原型。
3.1 基础语法
const newObj = Object.create(proto);
proto:作为新对象原型的对象(可以是null)。newObj:创建出来的新空对象,其__proto__指向proto。
3.2 实操:构建一个工具库
假设你需要构建一个包含基础数学计算的工具库,并希望基于它创建一个带有日志功能的扩展版本。
-
创建基础计算器对象
baseCalculator,包含加法和减法方法:const baseCalculator = { add: function(a, b) { return a + b; }, sub: function(a, b) { return a - b; } }; -
使用
Object.create()创建loggingCalculator,使其继承baseCalculator:const loggingCalculator = Object.create(baseCalculator); -
添加特有的日志方法到
loggingCalculator上:loggingCalculator.log = function(result) { console.log('计算结果是:', result); }; -
调用继承来的方法验证功能:
const sum = loggingCalculator.add(5, 3); loggingCalculator.log(sum);观察:
loggingCalculator本身没有add方法,但它成功调用了原型上的add。
4. 属性遮蔽与 hasOwnProperty 判定
在原型链中,如果子对象和父原型拥有同名的属性,子对象的属性会“遮蔽”父对象的属性。这被称为属性遮蔽。
- 在之前的
parent对象上添加name属性:parent.name = 'Father'; - 重新检查
child.name的值:console.log(child.name);结果:现在输出
'Father'。因为它在parent层级找到了name,所以停止了向上查找grandParent.name。
为了区分一个属性是对象自身的,还是从原型继承的,使用 Object.prototype.hasOwnProperty() 方法。
- 测试
child对象自身是否有role属性:console.log(child.hasOwnProperty('role')); // 输出: true - 测试
child对象自身是否有name属性:console.log(child.hasOwnProperty('name')); // 输出: false
5. 创建无原型的纯净对象
在某些特定场景下(如作为字典或 Map 使用),你希望对象不继承任何属性,甚至连 toString 都没有。此时可以将 null 传给 Object.create()。
-
创建一个纯净对象
pureObj:const pureObj = Object.create(null); -
尝试访问其
toString方法:console.log(pureObj.toString);结果:输出
undefined。因为它没有继承Object.prototype。 -
验证其原型链终点:
console.log(Object.getPrototypeOf(pureObj)); // 输出: null
下表总结了不同创建方式的对象特性差异:
| 创建方式 | 原型指向 | 是否继承 Object.prototype 方法 | 适用场景 |
|---|---|---|---|
{} 或 new Object() |
Object.prototype |
是 | 普通数据对象 |
Object.create(proto) |
指定的 proto |
取决于 proto |
自定义继承结构 |
Object.create(null) |
null |
否 | 高性能字典、纯净数据存储 |

暂无评论,快来抢沙发吧!