大家好,我是今天刚刚在 JavaScript 的 OOP 世界里打了个滚的菜鸟程序员。今天学了面向对象编程(OOP)的一些基本概念,尤其是 JavaScript 这个“非典型面向对象语言”的独特玩法——原型机制。
如果你也像我一样,被 __proto__、prototype、new 这些关键词搞得晕头转向,那就跟我一起踏上这场“JS 面向对象奇幻之旅”吧!
🧠 第一站:OOP 的三大法宝 —— 封装、继承、多态
在正式进入 JavaScript 的世界之前,我们先来复习一下面向对象编程的几个核心思想:
- 封装(Encapsulation) :把数据和行为包装在一起,就像一个快递包裹,别人只看到外包装,不知道里面装的是什么。
- 继承(Inheritance) :子类可以继承父类的属性和方法,就像孩子继承父母的基因。
- 多态(Polymorphism) :不同对象对同一消息作出不同的响应,比如狗叫是“汪汪”,猫叫是“喵喵”。
这些概念在 Java、C++ 这样的传统 OOP 语言中很常见,但在 JavaScript 中,它们的实现方式却有点“另类”。因为 JS 并没有“真正的类”,它靠的是一个叫做 原型(Prototype) 的机制来实现这一切。
🚪 第二站:JavaScript 的 OOP 初体验 —— 对象字面量 vs 构造函数
❌ 痛点一:对象字面量太重复了
let person1 = {
name: "张三",
sayHello() {
console.log("你好,我是" + this.name);
}
};
let person2 = {
name: "李四",
sayHello() {
console.log("你好,我是" + this.name);
}
};
你会发现,上面两个对象几乎一模一样,只是名字不同。如果我们要创建很多这样的“人”,这种方式就显得非常低效。
✅ 解法:构造函数登场!
于是,聪明的前辈们发明了“构造函数”,它看起来像是一个普通的函数,但专门用来创建对象。
function Person(name) {
this.name = name;
this.sayHello = function () {
console.log("你好,我是" + this.name);
};
}
let p1 = new Person("小明");
let p2 = new Person("小红");
p1.sayHello(); // 你好,我是小明
p2.sayHello(); // 你好,我是小红
这时候你可能会问:“这不就是一个函数吗?怎么变成了‘类’?”
没错,在 JavaScript 中,函数就是类,只不过我们约定俗成地用大写字母开头来表示这是一个构造函数。
🔍 第三站:揭开 new 的神秘面纱
当我们使用 new 关键字调用构造函数时,JavaScript 会默默帮我们完成以下几步:
- 创建一个新的空对象
{}; - 把这个新对象的
__proto__指向构造函数的prototype; - 把构造函数中的
this指向这个新对象; - 最后返回这个新对象。
我们可以简单理解为:
new Person("小明") ≈ {}
→ this = {}
→ this.name = "小明"
→ this.__proto__ = Person.prototype
→ return this
🧬 第四站:原型(Prototype)才是 JS OOP 的灵魂
💡 什么是 prototype?
每个函数都有一个 prototype 属性,它是一个对象,默认情况下它有一个 constructor 属性指向这个函数自己。
function Dog() {}
console.log(Dog.prototype);
// { constructor: Dog(), __proto__: Object.prototype }
🧬 什么是 proto?
所有对象都有一个私有属性 __proto__,它指向它的构造函数的 prototype。换句话说,对象通过 __proto__ 找到自己的“原型”。
let d = new Dog();
console.log(d.__proto__); // Dog.prototype
console.log(d.__proto__.__proto__); // Object.prototype
console.log(d.__proto__.__proto__.__proto__); // null
这就是所谓的 原型链(Prototype Chain) ,也是 JS 实现继承的核心机制。
🤯 第五站:JS 本无“类”,全靠“原型”撑起一片天
在传统的 OOP 语言中,“类”是模板,“实例”是根据模板创建出来的对象。但在 JavaScript 中,根本没有“类”这个东西,只有“构造函数”和“原型对象”。
我们可以这样理解:
- 类 = 构造函数(大写)
- 实例 = new 出来的对象
- 继承 = 原型链查找机制
所以你可以想象,JavaScript 的 OOP 更像是“模仿类”,而不是真正意义上的类。
🔄 第六站:原型链的尽头是 null
刚才我们看到:
d.__proto__.__proto__.__proto__
最终会变成 null,说明原型链是有终点的。这个结构就像一条链条,从实例出发,逐级向上查找,直到找到 Object.prototype,最后是 null。
这也解释了为什么几乎所有对象都默认拥有 toString()、valueOf() 这些方法,因为它们是从 Object.prototype 上继承来的。
🧱 第七站:手动修改原型链,打造你的“继承”系统
既然原型链是可以更改的,那我们就可以手动设置对象之间的继承关系。
举个例子,让 Dog 继承自 Animal:
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
console.log(this.name + " 叫了一声");
};
function Dog(name) {
Animal.call(this, name); // 调用父类构造函数
}
// 设置原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
let buddy = new Dog("Buddy");
buddy.speak(); // Buddy 叫了一声
这里我们用了 Object.create() 来创建一个新的对象,作为 Dog.prototype,并让它继承 Animal.prototype 的方法。这就是 JS 中“模拟继承”的标准做法。
📚 第八站:总结一下今天的收获
今天我们穿越了 JS 的 OOP 世界,经历了以下几个重要知识点:
| 主题 | 内容 |
|---|---|
| 构造函数 | 使用 new 创建对象,身兼“类”和“构造函数”两职 |
| 原型对象 | 每个函数都有 prototype,对象通过 __proto__ 查找 |
| 原型链 | 通过 __proto__ 一层层往上找,直到 Object.prototype 和 null |
| 继承机制 | 手动设置原型链,模拟类继承 |
| new 的过程 | 创建空对象 → 绑定 this → 设置原型 → 返回对象 |
📖 结语:别怕 JS 的原型,它其实很温柔
刚接触 JS 的原型机制时,很多人都会感到困惑甚至恐惧,特别是面对 __proto__ 和 prototype 这两个长得差不多又作用不同的家伙。
但只要你理解了它们的关系,以及原型链的工作原理,你会发现 JS 的 OOP 其实非常有趣,甚至比传统 OOP 更加自由和富有创意。
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、转发,让更多小伙伴也能轻松理解 JS 的原型机制!
“JS 不是面向对象的语言,它是面向原型的语言。”
——《JavaScript: The Definitive Guide》