本文将带你穿越 ES6 异步编程与 JavaScript 面向对象的核心机制,通过一段看似"诡异"的代码,揭示原型链的本质、动态性及其在实际开发中的意义。
引子:一段"奇怪"的代码
先来看这段来自 2.js 的代码:
ini
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.speci = '人类';
let zhen = new Person('郑总', 18);
console.log(zhen.speci); // 输出:人类
const kong = {
name: '孔子',
hobbies: ['读书', '喝酒']
};
zhen.__proto__ = kong;
console.log(zhen.hobbies, zhen.speci); // 输出:['读书', '喝酒'] undefined
你可能会疑惑:
- 为什么修改
zhen.__proto__后,speci属性就消失了? - 这和我们常说的"原型链"有什么关系?
- 这种操作在真实项目中有用吗?
别急,让我们从 JavaScript 的面向对象本质说起。
一、JavaScript 的面向对象:不是血缘,而是委托
与 Java、C++ 等基于"类继承"的语言不同,JavaScript 的面向对象是基于原型(Prototype)的 。它没有"父子类"的血缘概念,只有对象之间的委托关系。
核心三要素:
- 构造函数(Constructor)
如Person,用于创建实例。 - 原型对象(Prototype)
每个函数都有一个prototype属性,指向一个对象,该对象会被实例的__proto__所引用。 - 原型链(Prototype Chain)
当访问一个对象的属性时,若自身没有,则沿着__proto__向上查找,直到null。
✅ 记住:
obj.__proto__ === ObjConstructor.prototype
因此,当我们执行:
ini
let zhen = new Person('郑总', 18);
实际上建立了这样的关系:
javascript
zhen.__proto__ → Person.prototype → Object.prototype → null
所以 zhen.speci 能找到 '人类',因为它委托给了 Person.prototype。
二、动态原型:运行时改变对象的"行为模板"
关键来了!JavaScript 的原型链是动态可变的。
当你写下:
ini
zhen.__proto__ = kong;
你就强行切断了 zhen 与 Person.prototype 的联系,转而让它委托给 kong 对象。
于是新的原型链变成:
javascript
zhen.__proto__ → kong → Object.prototype → null
zhen.hobbies→ 在kong上找到 →['读书', '喝酒']zhen.speci→kong上没有 → 继续找Object.prototype→ 没有 → 返回undefined
⚠️ 注意:这种操作虽然合法,但性能差且不推荐(现代引擎会优化固定原型链,动态修改会破坏优化)。但在某些特殊场景(如 mock、调试、元编程)中仍有价值。
三、从原型链到异步:Promise.all 与 ES6 的设计哲学
你可能注意到 readme.md 中提到了:
Promise.all Promise es6提供的异步解决方案 实例 proto 指向原型对象
这其实暗示了一个更深层的联系:ES6 的 Promise 也是基于原型链构建的!
例如:
ini
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const all = Promise.all([p1, p2]);
console.log(all.__proto__ === Promise.prototype); // true
Promise.all 返回的仍然是一个 Promise 实例,它继承了 Promise.prototype 上的方法(如 .then, .catch)。这体现了 JavaScript 一切皆对象、统一委托模型的设计哲学。
🌟 无论是同步的对象方法,还是异步的 Promise 链,底层都依赖同一个原型机制。
四、思考:原型链 vs 类继承 ------ 哪种更灵活?
| 特性 | 原型链(JS) | 类继承(Java/Python) |
|---|---|---|
| 运行时修改 | ✅ 支持(如 __proto__) |
❌ 编译时固定 |
| 多继承模拟 | ✅ 通过混入(Mixin) | ❌ 通常单继承 |
| 性能 | ⚠️ 动态修改影响优化 | ✅ 静态结构易优化 |
| 可读性 | ❌ 初学者易混淆 | ✅ 直观 |
JavaScript 的原型系统牺牲了一点"直观性",换来了极致的灵活性。这也是为什么像 Vue、React 等框架能通过原型扩展实现强大的响应式或组件系统。
五、最佳实践建议
-
避免直接操作
__proto__使用
Object.setPrototypeOf(obj, proto)(仍不推荐),或更好的方式:在创建对象时就确定原型 (如Object.create(proto))。 -
理解
constructor的作用
Person.prototype.constructor === Person,但如果你重写整个prototype,记得手动修复:iniPerson.prototype = { /* ... */ }; Person.prototype.constructor = Person; // 修复 -
善用原型进行方法共享
将公共方法放在
prototype上,节省内存:javascriptPerson.prototype.sayHi = function() { console.log(`Hi, I'm ${this.name}`); };
结语:原型链,JavaScript 的灵魂
从 new Person() 到 Promise.all(),从静态属性到动态委托,原型链是贯穿 JavaScript 语言的核心脉络。它不仅是面试题,更是理解这门语言"为何如此设计"的钥匙。
下次当你看到 __proto__,不要只想到"黑魔法",而应看到:这是一个对象在运行时寻找答案的旅程。
正如孔子曰:"学而不思则罔。"
在 JS 的世界里,用而不悟原型,则码如浮云。