JS 没有类,却比类还灵活:原型式继承全解析

103 阅读3分钟

在很多初学者眼中,JavaScript 一直是个“不太正统”的面向对象语言:没有 class、没有 private、没有传统意义上的继承。可当你深入了解它的原型机制之后,会发现它的灵活程度甚至远胜于传统语言。

这篇文章不聊花里胡哨的概念,我们直接从构造函数、原型对象、继承机制入手,把原型链这件事讲清楚。


一、“类”是 JS 里假扮的角色

在 ES6 之前,JavaScript 是没有 class 关键字的。我们写“类”,靠的是构造函数 + 原型。来看个例子:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function() {
  console.log(`Hi, I'm ${this.name}`);
};

let nova = new Person('Nova', 22);
nova.sayHello(); // Hi, I'm Nova

Person 就是一个普通函数,但我们用它来创建对象,赋初始值。sayHello 方法被挂到了 Person.prototype 上,这样所有实例(比如 nova)就能共享这个方法,而不是每次都复制一份。

这套路本质上是在模拟“类”的行为,只不过是我们自定义了一套约定。


二、原型链:JS 的“继承”藏在哪?

JavaScript 的每个对象,都有一个隐式属性 __proto__。它不是语法,是引擎内部用来构建“原型链”的。

当我们访问对象的属性时,如果对象本身找不到,就会去它的原型对象(__proto__)里找,一直找下去,直到找不到为止(最终到 null)。

console.log(nova.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true

上面这两行说明了:

  • nova 的原型就是 Person.prototype
  • Person.prototype 上的 constructor 指回 Person 构造函数

它们之间形成了一个结构闭环,决定了属性和方法的查找路径。


三、动手篡改原型链(虽然不推荐)

在 JS 中,__proto__ 是可以动态修改的。我们可以人为改变一个对象的继承关系,比如:

const altProto = {
  country: 'Nebula',
  greet: function() {
    console.log('Greetings from the Nebula Kingdom');
  }
};

nova.__proto__ = altProto;

console.log(nova.country); // Nebula
nova.greet();              // Greetings from the Nebula Kingdom

这样一来,nova 的原型链就被替换了,原本继承自 Person.prototype 的方法也就失效了。

虽然能这样干,但在实际项目中不建议频繁用 __proto__ 造轮子,容易让代码的结构和调试变得混乱。


四、JavaScript 的 OOP 不走寻常路

和 Java/C++ 这类语言相比,JS 的 OOP 写法自由得多:

  • 封装:靠作用域和闭包模拟“私有变量”
  • 继承:用原型链,不靠类继承树
  • 多态:靠动态类型 + 方法覆盖轻松实现

也正因为这样,JS 在表达复杂逻辑时,有着比传统语言更灵活的方式,比如对象组合、函数混入、动态扩展等。


五、new 到底做了什么?

new 是 JS 实例化的关键字,但它背后做了不少事情:

let luna = new Person('Luna', 18);

等价于:

// 手动模拟 new 的过程
let obj = {};
obj.__proto__ = Person.prototype;
Person.call(obj, 'Luna', 18);

也就是说:

  1. 创建一个空对象;
  2. 设置它的原型;
  3. 执行构造函数,把 this 指向新对象;
  4. 返回新对象。

理解这一点,可以帮你看清 JS 的对象生成逻辑。


六、总结

JavaScript 的面向对象机制不是“没有类”,而是不依赖类。它通过构造函数 + 原型链 + new 实现了完整的对象创建、继承和复用能力。

ES6 的 class 本质上只是语法糖。真正理解 JS 的继承模型,还是要回到构造函数和原型链。

如果你曾被 __proto__prototype 弄得头晕,不妨自己动手写几个构造函数,把原型链打印出来,你会发现——

JS 虽然没有类,却比类还灵活。