JavaScript 中的面向对象与继承:从原型到 class
“JS 是基于对象的语言,但不是传统意义上的面向对象语言。”
这句话道出了 JavaScript 面向对象(OOP)机制的独特之处。它没有 Java 或 C++ 那样原生的类系统,却通过原型(prototype) 实现了强大的对象模型。本文将带你从最原始的对象字面量出发,一步步理解 JS 如何实现封装、实例化、继承,并最终演进到 ES6 的 class 语法。
一、一切皆对象?JS 的“类”从何而来?
在 JavaScript 中,你遇到的几乎所有东西都可以看作对象——函数是对象,数组是对象,甚至字符串、数字等基本类型也有对应的包装对象(如 String、Number)。
但早期的 JS 并没有 class 关键字(直到 ES6 才引入),也没有传统 OOP 中的“构造器”概念。那么,如何创建具有相同结构的多个对象呢?
最原始的方式:对象字面量
var cat1 = {};
cat1.name = "加菲猫";
cat1.color = "橘色";
var cat2 = {};
cat2.name = "黑猫警长";
cat2.color = "黑色";
这种方式虽然简单,但存在明显问题:
- 代码重复;
cat1和cat2之间没有任何“关系”;- 无法体现“模板”或“类”的抽象概念。
二、封装实例化过程:构造函数登场
为了解决上述问题,开发者开始使用函数 + new 来模拟“类”:
function Cat(name, color) {
this.name = name;
this.color = color;
}
var cat1 = new Cat("加菲猫", "橘色");
var cat2 = new Cat("黑猫警长", "黑色");
new 背后发生了什么?
当你执行 new Cat(...) 时,JS 引擎会自动完成以下四步:
- 创建一个空对象
{}; - 将该对象的
__proto__指向Cat.prototype; - 执行
Cat函数,this指向新对象; - 如果函数没有显式返回对象,则返回这个新对象。
这样,我们就实现了封装和实例化。
三、方法复用:prototype 的妙用
如果每个 Cat 实例都拥有自己的 sayHello 方法,会造成内存浪费。于是,JS 引入了 prototype:
Cat.prototype.sayHello = function() {
console.log("我是" + this.name);
};
cat1.sayHello(); // 我是加菲猫
所有通过 new Cat() 创建的实例,都会通过原型链共享 prototype 上的方法。这正是 JS 实现继承和多态的基础。
✅ 核心思想:把不变的属性和公用的方法,放到原型对象上。
四、如何实现继承?
JS 没有原生的“类继承”,但我们可以通过多种方式模拟。
1. 原型链继承(基础但有缺陷)
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() { console.log("eating..."); };
function Cat(name, color) {
this.color = color;
}
Cat.prototype = new Animal(); // 继承 Animal
问题:无法向父类传参;引用属性会被所有实例共享。
2. 构造函数绑定(借用构造器)
function Cat(name, color) {
Animal.call(this, name); // 绑定 this,调用父构造器
this.color = color;
}
优点:可传参,避免属性共享。
缺点:无法继承父类原型上的方法。
3. 组合继承(经典方案)
结合以上两种方式:
function Cat(name, color) {
Animal.call(this, name); // 属性继承
}
Cat.prototype = new Animal(); // 方法继承
Cat.prototype.constructor = Cat; // 修正 constructor
这是 ES5 时代最常用的继承模式。
4. 寄生组合继承(最优解)
避免两次调用父构造函数:
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
5. ES6 Class:语法糖,但更优雅
class Animal {
constructor(name) {
this.name = name;
}
eat() { console.log("eating..."); }
}
class Cat extends Animal {
constructor(name, color) {
super(name); // 调用父类构造器
this.color = color;
}
}
虽然 class 本质仍是基于原型,但它让代码更清晰、更接近传统 OOP 语言。
📌 重要提醒:ES6 的
class只是语法糖,底层依然是原型链机制!
五、辅助工具:判断对象关系
-
instanceof:判断实例是否属于某构造函数cat1 instanceof Cat; // true -
hasOwnProperty:判断是否是自身属性(非原型) -
for...in:遍历所有可枚举属性(包括原型链上的,需配合hasOwnProperty过滤)
结语
JavaScript 的面向对象机制看似“另类”,实则灵活而强大。从对象字面量 → 构造函数 → prototype → class,每一步都是对“如何更好地组织代码”的探索。
理解原型链和 new 的工作原理,不仅能写出更健壮的代码,还能在面试中从容应对 OOP 相关问题。
记住:JS 不是“没有类”,而是“万物皆可为类”——只要你懂得原型。