javascript 中的对象原型通过 __proto__ 属性(或 object.getprototypeof())指向其构造函数的 prototype 属性,从而形成一条用于查找共享属性和方法的链。理解这一机制的关键在于区分实例对象的内部原型链接与构造函数自身的原型对象,前者是实例继承行为的载体,后者是定义共享行为的地方。

构造函数 prototype 与实例 __proto__ 的关系是什么
在 JavaScript 中,每个函数都有一个名为 prototype 的属性,它是一个对象,专门用于存放该函数所创建实例的共享属性和方法。当使用 new 关键字调用构造函数时,新创建的实例对象内部会有一个隐藏的属性 [[Prototype]](通常通过 __proto__ 访问),这个属性被自动设置为构造函数的 prototype 对象。这意味着实例对象可以通过原型链访问到构造函数 prototype 上定义的所有内容。

function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello, ' + this.name);
};
const person1 = new Person('Alice');
console.log(person1.__proto__ === Person.prototype); // true
person1.sayHello(); // Hello, Alice
上述代码展示了 Person 构造函数、其 prototype 对象以及实例 person1 之间的关系。person1.__proto__ 直接指向 Person.prototype,因此 person1 可以调用 sayHello 方法。这种关系确保了所有由 Person 创建的实例都能共享 prototype 上的方法,而不会在每个实例中重复创建。
Object.prototype 在原型链中扮演什么角色
Object.prototype 是 JavaScript 原型链的终点,几乎所有对象的原型最终都会指向它。它是所有非原始类型对象的基类原型,定义了如 toString、hasOwnProperty 等通用方法。如果一个对象在其自身及其构造函数的 prototype 上找不到某个属性或方法,引擎会继续沿着原型链向上查找,直到找到 Object.prototype。如果仍未找到,则返回 undefined。

function Animal(type) {
this.type = type;
}
Animal.prototype.eat = function() {
console.log(this.type + ' is eating');
};
const animal1 = new Animal('Dog');
console.log(animal1.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
animal1.eat(); // Dog is eating
console.log(animal1.toString()); // [object Object]
这段代码清晰地展示了原型链的层级结构:animal1 指向 Animal.prototype,而 Animal.prototype 又指向 Object.prototype,最后 Object.prototype 的 __proto__ 为 null,标志着原型链的结束。animal1 能够调用 toString 方法,正是因为该方法定义在 Object.prototype 上,并通过原型链查找得到。这种设计允许开发者在不修改内置对象的情况下,扩展所有对象的行为。
修改 Object.prototype 会影响所有对象吗
由于 Object.prototype 是所有对象的最终原型,直接修改它会影响到程序中几乎所有的对象。例如,如果在 Object.prototype 上添加一个新方法,那么所有通过 new 创建的对象以及字面量创建的对象(如 {}、[])都将能够访问这个方法。这种做法虽然强大,但极易引发命名冲突和不可预见的副作用,因此在现代开发中应尽量避免直接修改 Object.prototype。
// 不推荐的做法:修改 Object.prototype
Object.prototype.customMethod = function() {
return 'Custom';
};
const obj1 = {};
const obj2 = [];
console.log(obj1.customMethod()); // Custom
console.log(obj2.customMethod()); // Custom
// 验证影响范围
for (let key in obj1) {
console.log(key); // 可能会遍历到 customMethod,除非使用 hasOwnProperty 过滤
}
上述代码演示了向 Object.prototype 添加 customMethod 后,空对象 obj1 和空数组 obj2 都能访问该方法。需要注意的是,使用 for...in 循环遍历对象属性时,也会枚举出原型链上的可枚举属性,包括 customMethod。为了避免此类问题,建议使用 Object.defineProperty 将新方法定义为不可枚举,或者使用 hasOwnProperty 进行过滤。直接修改全局原型是一种高风险操作,应谨慎使用。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











