在很多初学者眼中,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.prototypePerson.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);
也就是说:
- 创建一个空对象;
- 设置它的原型;
- 执行构造函数,把
this指向新对象; - 返回新对象。
理解这一点,可以帮你看清 JS 的对象生成逻辑。
六、总结
JavaScript 的面向对象机制不是“没有类”,而是不依赖类。它通过构造函数 + 原型链 + new 实现了完整的对象创建、继承和复用能力。
ES6 的 class 本质上只是语法糖。真正理解 JS 的继承模型,还是要回到构造函数和原型链。
如果你曾被 __proto__、prototype 弄得头晕,不妨自己动手写几个构造函数,把原型链打印出来,你会发现——
JS 虽然没有类,却比类还灵活。