一、JavaScript 是“基于对象”的语言
虽然 ES6 引入了 class 关键字,但 JavaScript 本质上仍是基于原型(Prototype-based)的语言,而非传统类式 OOP(如 Java/C++)。
这意味着:
- 所有对象都通过 原型链 实现属性/方法共享;
- “类”只是语法糖,底层仍是函数 + 原型;
- 没有真正的私有成员(ES2022 后可用
#私有字段,但非主流)。
二、从对象字面量到构造函数:封装实例化过程
❌ 原始模式(不推荐)
js
编辑
var cat1 = { name: '加菲猫', color: '橘色' };
var cat2 = { name: '黑猫警长', color: '黑色' };
- 问题:代码重复、无法批量创建、无类型标识。
✅ 构造函数模式(基础封装)
js
编辑
function Cat(name, color) {
this.name = name;
this.color = color;
}
const cat1 = new Cat('加菲猫', '橘色');
console.log(cat1 instanceof Cat); // true
🔍 new 调用时发生了什么?
- 创建一个空对象
{}; - 将
this指向该对象; - 执行构造函数体(初始化属性);
- 返回该对象(除非显式返回非原始值)。
⚠️ 若直接调用
Cat()(无new),this指向window(非严格模式),造成污染!
三、Prototype 模式:解决方法重复问题
每个实例都拥有自己的方法副本?太浪费!
✅ 正确做法:将公共属性/方法挂到 prototype
js
编辑
function Cat(name, color) {
this.name = name;
this.color = color;
}
Cat.prototype.type = '猫科动物';
Cat.prototype.eat = function() {
console.log('吃 jerry');
};
- 所有
Cat实例共享type和eat; - 内存高效,符合“单一职责”。
💡 注意:若在实例上赋值同名属性(如
cat1.type = '铲屎官'),会遮蔽(shadow) 原型上的属性,但不影响其他实例。
四、属性查找三剑客:hasOwnProperty vs in vs isPrototypeOf
| 方法 | 作用 | 是否查原型链 | 典型用途 |
|---|---|---|---|
obj.hasOwnProperty(prop) | 判断自身是否有某属性 | ❌ 否 | 过滤原型属性(如 for-in 循环) |
prop in obj | 判断自身或原型链是否有某属性 | ✅ 是 | 安全调用前检查 |
Proto.isPrototypeOf(obj) | 判断 Proto 是否在 obj 的原型链中 | ✅ 是 | 类型判断、继承验证 |
🧪 示例验证
js
编辑
const cat1 = new Cat('tom', '灰色');
console.log(cat1.hasOwnProperty('name')); // true(自身)
console.log(cat1.hasOwnProperty('type')); // false(原型)
console.log('type' in cat1); // true(原型链有)
console.log(Cat.prototype.isPrototypeOf(cat1)); // true
✅ for-in 循环建议配合
hasOwnProperty使用:
js
编辑
for (let key in cat1) {
if (cat1.hasOwnProperty(key)) {
console.log(key, cat1[key]); // 只打印自身属性
}
}
五、继承:如何让 Cat 继承 Animal?
方案 1️⃣:仅用 apply(借用构造函数)—— 只能继承实例属性
js
编辑
function Animal() {
this.species = '动物';
}
Animal.prototype.sayHi = function() { console.log('hi'); };
function Cat(name, color) {
Animal.apply(this); // this 指向 Cat 实例
this.name = name;
this.color = color;
}
const cat = new Cat('加菲猫', '橘色');
console.log(cat.species); // ✅ '动物'
cat.sayHi(); // ❌ TypeError! sayHi 不在原型链上
❓ 为什么拿不到 sayHi?
apply只执行Animal函数体,不修改原型链;cat.__proto__仍指向Cat.prototype(默认为空),与Animal.prototype无关。
🧠 类比:
Animal是工具箱(含species工具),Animal.prototype是公共仓库(含sayHi)。
apply只把工具箱内容复制过来,没进仓库。
方案 2️⃣:组合继承(推荐)—— 同时继承属性 + 原型方法
js
编辑
function Cat(name, color) {
Animal.apply(this); // 继承实例属性
this.name = name;
this.color = color;
}
// 关键:设置 Cat.prototype 为 Animal 实例
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat; // 修复 constructor
Cat.prototype.catchMouse = function() {
console.log(this.name + '抓老鼠');
};
const cat = new Cat('tom', '灰色');
cat.sayHi(); // ✅ 'hi'
cat.catchMouse(); // ✅ 'tom抓老鼠'
### `constructor` 修复的意义(不是为了继承,是为了规范)
为什么要修复 `constructor`?不是为了继承方法,而是为了:
1. **语义正确**:`cat.constructor` 应该指向创建它的构造函数 `Cat`,而非 `Animal`;
- 不修复:`cat.constructor === Animal`(语义错误);
- 修复后:`cat.constructor === Cat`(语义正确)。
1. **避免后续逻辑出错**:如果代码中依赖 `constructor` 判断类型 / 创建新实例,错误的 `constructor` 会导致 bug。
cat(自身:name、color、species)→ 没 sayHi →
cat.__proto__(Cat.prototype,自身:species、constructor:Cat)→ 没 sayHi →
cat.__proto__.__proto__(Animal.prototype)→ 找到 sayHi → 执行
🔗 原型链结构:
text
编辑
cat
└─ __proto__ → Cat.prototype (即 new Animal())
└─ __proto__ → Animal.prototype
└─ __proto__ → Object.prototype → null
✅ 重点澄清你的疑问:
cat.__proto__确实直接指向Cat.prototype,而Cat.prototype被赋值为new Animal()(一个 Animal 实例),所以:js 编辑 cat.__proto__ === Cat.prototype // true cat.__proto__ === new Animal() // true(如果引用相同) cat.__proto__.__proto__ === Animal.prototype // true
⚠️ 常见误区:不要直接 Cat.prototype = Animal.prototype
- 会导致父子原型共享同一对象,修改子类原型会污染父类!
六、ES6 Class:语法糖,本质不变
js
编辑
class Animal {
constructor(name) {
this.name = name;
}
run() { console.log(this.name + '跑'); }
}
class Cat extends Animal {
constructor(name, color) {
super(name); // 等价于 Animal.apply(this, [name])
this.color = color;
}
catchMouse() { console.log(this.name + '抓老鼠'); }
}
-
extends自动完成:- 原型链关联(
Cat.prototype.__proto__ = Animal.prototype) - 构造函数调用(
super())
- 原型链关联(
💡 底层仍是原型继承,
class只是更清晰的写法。
七、总结要点 ✅
| 主题 | 核心结论 |
|---|---|
| 构造函数 | new 调用时自动绑定 this 到新实例 |
| Prototype | 公共方法放 prototype,节省内存 |
| 属性查找 | hasOwnProperty(自身)、in(自身+原型)、isPrototypeOf(原型链验证) |
| 继承关键 | 仅 apply → 无原型方法;需 Cat.prototype = new Animal() 补全原型链 |
| 原型指向 | cat.__proto__ 永远 = Cat.prototype;若后者被赋值为 Animal 实例,则间接继承其原型 |
| ES6 class | 语法糖,extends + super = 组合继承的简化版 |
八、拓展思考 💭
-
现代替代方案:
Object.create(Animal.prototype)可避免调用父构造函数(寄生组合继承);Reflect.construct提供更灵活的构造控制。
-
性能注意:
- 原型链过深会影响属性查找速度;
- 避免动态修改
__proto__(已被废弃,用Object.setPrototypeOf替代)。
-
调试技巧:
- 浏览器控制台展开对象可直观看到
[[Prototype]]; - 用
Object.getPrototypeOf(obj)安全获取原型(比__proto__更标准)
- 浏览器控制台展开对象可直观看到