《JavaScript 中的原型与构造函数:理解对象创建的本质》

125 阅读1分钟

JavaScript 中的原型与构造函数:理解对象创建的本质

在现代 JavaScript 中,我们已经习惯使用 class 关键字来定义类和创建对象。但在 ES6 之前,JavaScript 并没有 class 的概念,而是通过 构造函数 + prototype 的方式来模拟面向对象编程。本文将深入解析这种经典模式,并结合图示帮助你彻底理解原型链、__proto__prototype 的关系。


一、构造函数创建对象的基本模式

在没有 class 的年代,开发者通过定义一个构造函数(普通函数),并使用 new 操作符来创建对象。

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

// 创建实例
const person1 = new Person("Alice", 25);
console.log(person1.name); // Alice

✅ 注意:new 调用时会自动创建一个新对象,并将 this 指向该对象。


二、prototype 属性的作用

每个函数都有一个内置的 prototype 属性,它是一个对象,用于存放所有实例共享的方法或属性。

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

person1.sayHello(); // Hello, I'm Alice

此时,person1 并没有直接拥有 sayHello 方法,但它可以通过原型链访问到这个方法。


三、__proto__ 是什么?

每个对象都包含一个内部属性 [[Prototype]],在浏览器中可以通过 __proto__ 访问(非标准但广泛支持)。

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

这意味着:实例对象的 __proto__ 指向其构造函数的 prototype 对象

image.png


四、constructor 的作用与陷阱

默认情况下,Person.prototype 上有一个 constructor 属性,指向 Person 函数本身:

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

但如果你手动重写 prototype,就会丢失这个关联:

Person.prototype = {
  sayHello: function() {
    console.log("Hello!");
  }
};

console.log(Person.prototype.constructor); // undefined ❌

要修复这个问题,可以手动添加 constructor

Person.prototype = {
  constructor: Person,
  sayHello: function() {
    console.log("Hello!");
  }
};

五、函数的原型链:为什么 F.__proto__ === Function.prototype

所有函数都是 Function 的实例,因此它们的 __proto__ 指向 Function.prototype

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

Function.prototype 又继承自 Object.prototype,最终链路为:

PersonFunction.prototypeObject.prototypenull

六、原型链的顶层:Object.prototype

几乎所有对象的原型链最终都会到达 Object.prototype,它是原型链的终点。

console.log(person1.__proto__.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null

image.png


七、原型链查找机制

当访问一个对象的属性或方法时,JavaScript 引擎会按以下顺序查找:

  1. 在对象自身上查找;
  2. 如果没找到,则去 __proto__(即构造函数的 prototype)上查找;
  3. 再往上走,直到 Object.prototype
  4. 最终返回 undefined 或抛出错误。
function Animal() {}
Animal.prototype.eat = function() { console.log("eating..."); };

function Dog() {}
Dog.prototype = Object.create(Animal.prototype); // 继承
Dog.prototype.bark = function() { console.log("barking..."); };

const dog = new Dog();
dog.eat(); // eating... ✅ 找到了 Animal.prototype.eat
dog.bark(); // barking... ✅ 找到了 Dog.prototype.bark

八、总结:prototype 的特殊性

  • prototype 只有在被设置在一个构造函数上,并且通过 new 调用时,才具有“作为实例原型”的特殊意义。
  • F.prototypenew F() 时会被赋值给新对象的 __proto__
  • 原型链是 JavaScript 实现继承的核心机制。
  • 理解 __proto__prototype 的区别,是掌握 JS 面向对象的关键。

附录:代码演示完整版

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

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

const p = new Person("Bob");
p.sayHi(); // Hi, I'm Bob

console.log(p.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
console.log(Person.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

结语

虽然现在我们可以使用 class 语法,但底层仍然是基于原型的实现。掌握 prototype__proto__ 的工作原理,不仅能帮你写出更高效的代码,也能更好地理解框架如 React、Vue 的底层机制。

💡 提示:class 语法只是语法糖,编译后依然是构造函数 + prototype 的形式。


参考资料:MDN Web Docs, You Don't Know JS (系列), ECMAScript 规范