JavaScript prototype(原型对象)(长文解析)

JavaScript prototype(原型对象):理解对象继承的底层机制

在 JavaScript 的世界里,一切皆对象。但你有没有想过,为什么一个对象能“借用”另一个对象的方法?为什么我们能调用 Array.prototype.push?这背后的核心机制,就是 JavaScript prototype(原型对象)。

如果你是初学者,刚接触构造函数和对象创建,可能对 prototype 这个概念感到陌生。如果你是中级开发者,可能知道它存在,但不清楚它在内存中是如何运作的。这篇文章,就是为你准备的——从零开始,一步步揭开 JavaScript prototype 的神秘面纱。


什么是原型对象?一个“模板”式的共享机制

在 JavaScript 中,每个函数都有一个名为 prototype 的属性,它是一个对象。这个对象就是所谓的“原型对象”。当你用 new 关键字创建一个对象时,新对象会自动继承这个原型对象上的所有属性和方法。

想象一下:你有一张“设计图纸”(原型对象),每次用这张图纸打印出一个产品(实例对象)。这些产品虽然独立存在,但都共享同一个设计模板。这就是原型链的运作逻辑。

// 定义一个构造函数
function Animal(name) {
  this.name = name; // 实例属性
}

// 在原型对象上添加方法
Animal.prototype.speak = function () {
  console.log(`${this.name} 发出了声音`);
};

// 创建两个实例
const dog = new Animal("旺财");
const cat = new Animal("咪咪");

dog.speak(); // 旺财 发出了声音
cat.speak(); // 咪咪 发出了声音

注释Animal.prototype.speak 是在原型对象上定义的方法。dogcat 都能访问它,但方法只在内存中存在一份,节省了资源。


原型链:对象如何“找爸爸”?

当你访问一个对象的属性或方法时,JavaScript 不是直接去对象本身查找,而是先在对象自身找,找不到就去它的原型对象找,再找不到就去原型的原型找……直到找到 null 为止。

这个链条,就叫 原型链(prototype chain)

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

Person.prototype.greet = function () {
  console.log(`你好,我是 ${this.name}`);
};

const john = new Person("约翰");

// 访问实例属性(直接找)
console.log(john.name); // 约翰

// 访问原型方法(通过原型链查找)
john.greet(); // 你好,我是 约翰

// 查看原型链
console.log(john.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

注释__proto__ 是对象内部的链接,指向其构造函数的 prototype。虽然不推荐在生产中使用,但用于理解原型链非常直观。


构造函数 vs 原型对象:谁在“造”对象?

构造函数是创建对象的“工厂”,而原型对象是这个工厂提供的“通用工具库”。构造函数负责初始化实例的私有属性,原型对象则负责提供公共方法。

function Car(brand, color) {
  this.brand = brand; // 实例属性,每个对象都有自己的副本
  this.color = color;
}

// 公共方法放在原型上
Car.prototype.start = function () {
  console.log(`${this.brand} 的 ${this.color} 车启动了`);
};

Car.prototype.stop = function () {
  console.log(`${this.brand} 的车熄火了`);
};

const bmw = new Car("宝马", "黑色");
const benz = new Car("奔驰", "白色");

bmw.start(); // 宝马 的 黑色 车启动了
benz.stop();  // 奔驰 的车熄火了

// 每个实例的 brand 和 color 都是独立的
console.log(bmw.brand); // 宝马
console.log(benz.brand); // 奔驰

注释brandcolor 是每个实例独有的,所以放在构造函数里。而 startstop 是所有车共用的,放在原型上,避免重复定义。


原型对象的动态性:修改随时生效

原型对象是动态的!你在运行时修改原型,所有已创建的实例都能立刻访问到新内容。

function Dog(name) {
  this.name = name;
}

Dog.prototype.bark = function () {
  console.log(`${this.name} 汪汪叫`);
};

const puppy = new Dog("小黄");

// 调用方法
puppy.bark(); // 小黄 汪汪叫

// 动态添加新方法
Dog.prototype.wagTail = function () {
  console.log(`${this.name} 摇尾巴`);
};

// 即使实例已创建,也能调用新方法
puppy.wagTail(); // 小黄 摇尾巴

注释:JavaScript 的原型是“活”的。你修改了 Dog.prototype,所有基于 Dog 创建的对象都会继承这些变化。这在开发中非常有用,比如给某个类动态添加工具方法。


原型与继承:实现“类式”行为的基石

虽然 JavaScript 没有传统类(class),但通过原型,我们可以模拟出类似类的行为。这正是 class 语法糖的背后原理。

// 基类
function Shape(color) {
  this.color = color;
}

Shape.prototype.getArea = function () {
  return "面积未知";
};

// 子类
function Rectangle(width, height, color) {
  Shape.call(this, color); // 继承属性
  this.width = width;
  this.height = height;
}

// 建立原型链:Rectangle.prototype 继承自 Shape.prototype
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

// 重写父类方法
Rectangle.prototype.getArea = function () {
  return this.width * this.height;
};

// 创建实例
const rect = new Rectangle(5, 3, "红色");

console.log(rect.getArea()); // 15
console.log(rect.color);     // 红色

注释Object.create() 创建一个新对象,其原型指向 Shape.prototype。这样 Rectangle 实例就能通过原型链访问 getArea 方法。Shape.call(this, color) 是为了在子类中调用父类构造函数。


常见误区与最佳实践

误区一:误以为 prototype 是实例的属性

function Test() {}
console.log(Test.prototype); // 是一个对象
console.log(new Test().prototype); // undefined!实例没有 prototype 属性

注释prototype 是函数的属性,不是对象的属性。实例只能通过 __proto__ 访问原型。

误区二:直接修改 prototype 但忘记 constructor

function Foo() {}
Foo.prototype = { method: function () {} };

const f = new Foo();
console.log(f.constructor === Foo); // false!constructor 指向了新对象

注释:推荐显式修复构造函数指向:

Foo.prototype = { method: function () {} };
Foo.prototype.constructor = Foo;

最佳实践建议:

  • 将公共方法放在 prototype 上,避免重复创建
  • 使用 Object.create() 建立原型链,更清晰
  • 不要直接修改 prototypeconstructor,除非你清楚后果

总结:掌握 prototype,才能真正理解 JavaScript

JavaScript prototype(原型对象)并不是一个“高级技巧”,而是理解整个语言机制的核心。它决定了对象如何共享行为、如何实现继承、如何优化内存。

当你能清晰地说出“对象通过原型链查找方法”时,你就真正迈入了 JavaScript 的深层世界。它不像 Java 的 extends 那样显式,但它更灵活、更强大。

无论你是初学者还是中级开发者,花时间理解 prototype,都能让你写出更高效、更优雅的代码。

下一次,当你看到 Array.prototype.mapString.prototype.trim 时,不再只是调用,而是能理解:这一切,都源于一个简单的原型对象机制。