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 对象。
四、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,最终链路为:
Person → Function.prototype → Object.prototype → null
六、原型链的顶层:Object.prototype
几乎所有对象的原型链最终都会到达 Object.prototype,它是原型链的终点。
console.log(person1.__proto__.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
七、原型链查找机制
当访问一个对象的属性或方法时,JavaScript 引擎会按以下顺序查找:
- 在对象自身上查找;
- 如果没找到,则去
__proto__(即构造函数的prototype)上查找; - 再往上走,直到
Object.prototype; - 最终返回
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.prototype在new 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 规范