这两个属性都与 JavaScript 的原型系统相关,但扮演完全不同的角色,经常容易混淆。
1. 核心区别总结
| 特性 | prototype |
__proto__ |
|---|---|---|
| 所有者 | 函数 | 对象 |
| 作用 | 构造函数创建对象的原型 | 对象的原型链引用 |
| 访问 | 构造函数.prototype | 对象.proto |
| 标准 | 正式标准 | 非标准(但被广泛实现) |
| 推荐访问 | Object.getPrototypeOf(obj) | |
| 推荐设置 | Object.setPrototypeOf(obj, proto) |
2. 基本概念
2.1 prototype 属性
js
// 构造函数才有 prototype
function Person(name) {
this.name = name;
}
// prototype 用于定义通过 new 创建的对象共享的属性和方法
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
Person.prototype.species = 'Human';
// 查看 prototype
console.log(typeof Person.prototype); // "object"
console.log(Person.prototype.constructor === Person); // true
2.2 __proto__属性
js
// 对象才有 __proto__
const person = new Person('Alice');
// __proto__ 指向创建该对象的构造函数的 prototype
console.log(person.__proto__ === Person.prototype); // true
// 原型链查找
console.log(person.name); // "Alice" - 自有属性
console.log(person.species); // "Human" - 从原型找到
console.log(person.toString()); // [object Object] - 从 Object.prototype 找到
3. 详细对比
3.1 属性所有者
js
function Animal() {}
// prototype 属于函数
console.log(Animal.hasOwnProperty('prototype')); // true
console.log(typeof Animal.prototype); // "object"
// __proto__ 属于对象
const dog = new Animal();
console.log(dog.hasOwnProperty('__proto__')); // false
console.log(dog.__proto__ === Animal.prototype); // true
console.log(typeof dog.__proto__); // "object"
// 函数也是对象,所以函数也有 __proto__
console.log(Animal.__proto__ === Function.prototype); // true
3.2 创建时的行为
js
function Car(brand) {
this.brand = brand;
}
// 设置 prototype
Car.prototype.drive = function() {
console.log(`${this.brand} is driving`);
};
// 创建实例
const myCar = new Car('Toyota');
// 此时发生:
// 1. 创建新对象:{}
// 2. 设置对象的 __proto__ 指向 Car.prototype
// 3. 执行构造函数,this 指向新对象
// 4. 返回对象
console.log(myCar.__proto__ === Car.prototype); // true
console.log(Object.getPrototypeOf(myCar) === Car.prototype); // true
4. 原型链示例
js
function Vehicle(type) {
this.type = type;
}
Vehicle.prototype.move = function() {
console.log(`${this.type} is moving`);
};
function Car(brand) {
Vehicle.call(this, 'car');
this.brand = brand;
}
// 继承 Vehicle
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.constructor = Car;
Car.prototype.honk = function() {
console.log(`${this.brand} honks!`);
};
// 创建实例
const toyota = new Car('Toyota');
// 原型链:
console.log(toyota.__proto__ === Car.prototype); // true
console.log(toyota.__proto__.__proto__ === Vehicle.prototype); // true
console.log(toyota.__proto__.__proto__.__proto__ === Object.prototype); // true
console.log(toyota.__proto__.__proto__.__proto__.__proto__); // null
// 方法查找:
toyota.honk(); // 在 Car.prototype 找到
toyota.move(); // 在 Vehicle.prototype 找到
toyota.toString(); // 在 Object.prototype 找到
5. 标准 vs 非标准
5.1 标准访问方法
js
function Person() {}
const p = new Person();
// 标准获取原型的方法
console.log(Object.getPrototypeOf(p) === Person.prototype); // true
// 标准设置原型的方法
const obj = {};
const newProto = { x: 1 };
Object.setPrototypeOf(obj, newProto);
console.log(Object.getPrototypeOf(obj) === newProto); // true
// 检查是否是原型
console.log(newProto.isPrototypeOf(obj)); // true
5.2 __proto__的问题
js
// __proto__ 不是标准属性,但被广泛支持
const obj = {};
// 在一些环境中可能不可用
console.log('__proto__' in {}); // 大多数是 true
// 但可以修改
const originalProto = Object.getPrototypeOf(obj);
console.log(obj.__proto__ === originalProto); // true
// 修改 __proto__
obj.__proto__ = { custom: true };
console.log(obj.custom); // true
// 但性能差,不推荐
// Object.setPrototypeOf 是更好的选择
6. 特殊情况
6.1 箭头函数
js
// 箭头函数没有 prototype
const arrow = () => {};
console.log(arrow.prototype); // undefined
// 普通函数有 prototype
function normal() {}
console.log(normal.prototype); // { constructor: normal }
6.2 内置对象
js
// 数组的原型链
const arr = [];
console.log(arr.__proto__ === Array.prototype); // true
console.log(arr.__proto__.__proto__ === Object.prototype); // true
console.log(arr.__proto__.__proto__.__proto__); // null
// 字符串的原型链
const str = 'hello';
console.log(str.__proto__ === String.prototype); // true
console.log(str.__proto__.__proto__ === Object.prototype); // true
// 函数的原型链
function fn() {}
console.log(fn.__proto__ === Function.prototype); // true
console.log(fn.__proto__.__proto__ === Object.prototype); // true
6.3 Object.create
js
// 使用 Object.create 创建对象
const proto = { x: 10 };
const obj = Object.create(proto);
console.log(Object.getPrototypeOf(obj) === proto); // true
console.log(obj.__proto__ === proto); // true
console.log(obj.x); // 10,从原型继承
// 创建没有原型的对象
const nullObj = Object.create(null);
console.log(Object.getPrototypeOf(nullObj)); // null
console.log(nullObj.__proto__); // undefined
console.log(nullObj.toString); // undefined
7. 继承机制详解
7.1 构造函数继承
js
function Parent(name) {
this.name = name;
this.parentProperty = 'parent';
}
Parent.prototype.parentMethod = function() {
console.log('Parent method:', this.name);
};
function Child(name, age) {
// 1. 构造函数继承(实例属性)
Parent.call(this, name);
this.age = age;
this.childProperty = 'child';
}
// 2. 原型继承(方法)
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.childMethod = function() {
console.log('Child method:', this.age);
};
// 创建实例
const child = new Child('Alice', 10);
console.log(child.__proto__ === Child.prototype); // true
console.log(child.__proto__.__proto__ === Parent.prototype); // true
console.log(child.hasOwnProperty('name')); // true
console.log(child.hasOwnProperty('parentMethod')); // false
7.2 ES6 类继承
js
class Parent {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello from ${this.name}`);
}
}
class Child extends Parent {
constructor(name, age) {
super(name);
this.age = age;
}
sayAge() {
console.log(`I'm ${this.age} years old`);
}
}
const child = new Child('Bob', 12);
// 本质上还是原型继承
console.log(child.__proto__ === Child.prototype); // true
console.log(child.__proto__.__proto__ === Parent.prototype); // true
console.log(Child.__proto__ === Parent); // true
console.log(Child.prototype.__proto__ === Parent.prototype); // true
8. 性能考虑
8.1 原型链查找优化
js
// JavaScript 引擎会优化原型链查找
function getProperty(obj, prop) {
// 1. 检查对象自身属性
if (obj.hasOwnProperty(prop)) {
return obj[prop];
}
// 2. 查找原型链
let proto = Object.getPrototypeOf(obj);
while (proto) {
if (proto.hasOwnProperty(prop)) {
return proto[prop];
}
proto = Object.getPrototypeOf(proto);
}
return undefined;
}
8.2 避免修改 __proto__
js
// ❌ 不推荐:直接修改 __proto__
const obj = { x: 1 };
console.time('修改 __proto__');
for (let i = 0; i < 10000; i++) {
obj.__proto__ = { y: 2 };
}
console.timeEnd('修改 __proto__');
// ✅ 推荐:使用 Object.setPrototypeOf
console.time('Object.setPrototypeOf');
for (let i = 0; i < 10000; i++) {
Object.setPrototypeOf(obj, { y: 2 });
}
console.timeEnd('Object.setPrototypeOf');
// 最佳:创建时就确定原型
console.time('Object.create');
for (let i = 0; i < 10000; i++) {
const newObj = Object.create({ y: 2 });
newObj.x = 1;
}
console.timeEnd('Object.create');
// 输出:
// 修改 __proto__: 16.87109375 ms
// Object.setPrototypeOf: 37.158203125 ms
// Object.create: 19.46484375 ms
9. 实际应用
9.1 对象扩展
js
// 使用原型扩展内置对象
if (!Array.prototype.find) {
Array.prototype.find = function(predicate) {
for (let i = 0; i < this.length; i++) {
if (predicate(this[i], i, this)) {
return this[i];
}
}
return undefined;
};
}
// 检查是否是自定义扩展
console.log([].hasOwnProperty('find')); // false
console.log([].__proto__.hasOwnProperty('find')); // true
9.2 混入模式
js
// 使用原型实现混入
const canEat = {
eat(food) {
console.log(`${this.name} eats ${food}`);
}
};
const canSleep = {
sleep(hours) {
console.log(`${this.name} sleeps for ${hours} hours`);
}
};
function Animal(name) {
this.name = name;
}
// 混入到原型
Object.assign(Animal.prototype, canEat, canSleep);
const dog = new Animal('Dog');
dog.eat('meat');
dog.sleep(8);
// 输出:
// Dog eats meat
// Dog sleeps for 8 hours
9.3 原型检测
js
// 检测对象关系
function getPrototypeChain(obj) {
const chain = [];
let current = obj;
while (current) {
chain.push(current.constructor.name || '[Anonymous]');
current = Object.getPrototypeOf(current);
}
return chain;
}
const arr = [];
console.log(getPrototypeChain(arr)); // ["Array", "Object", "[Anonymous]"]
console.log(getPrototypeChain(Function)); // ["Function", "Object", "[Anonymous]"]
10. 常见误区
10.1 混淆 prototype 和 __proto__
js
function MyClass() {}
// ❌ 错误理解
console.log(MyClass.__proto__ === MyClass.prototype); // false
// ✅ 正确理解
console.log(MyClass.prototype.__proto__ === Object.prototype); // true
console.log(MyClass.__proto__ === Function.prototype); // true
const instance = new MyClass();
console.log(instance.__proto__ === MyClass.prototype); // true
10.2 修改构造函数的 prototype
js
function Original() {}
Original.prototype.value = 1;
const obj1 = new Original();
console.log(obj1.value); // 1
// 修改构造函数的 prototype
Original.prototype = { value: 2 };
const obj2 = new Original();
console.log(obj2.value); // 2
console.log(obj1.value); // 1,已存在的对象不受影响
console.log(obj1.__proto__ === obj2.__proto__); // false
10.3 原型污染
js
// 原型污染攻击
// ❌ 危险:用户输入控制原型
function unsafeMerge(target, source) {
for (const key in source) {
target[key] = source[key];
}
return target;
}
const userInput = JSON.parse('{"__proto__": {"admin": true}}');
const config = {};
unsafeMerge(config, userInput);
console.log({}.admin); // true!污染了所有对象
// ✅ 安全的方法
function safeMerge(target, source) {
for (const key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
}
11. 现代最佳实践
11.1 使用 ES6 类
js
class Base {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello, ${this.name}`);
}
}
class Derived extends Base {
constructor(name, value) {
super(name);
this.value = value;
}
showValue() {
console.log(`Value: ${this.value}`);
}
}
// 清晰的继承关系
console.log(Object.getPrototypeOf(Derived) === Base); // true
console.log(Object.getPrototypeOf(Derived.prototype) === Base.prototype); // true
11.2 使用 Object.create
js
// 组合式继承
const canFly = {
fly() {
console.log(`${this.name} is flying`);
}
};
const canSwim = {
swim() {
console.log(`${this.name} is swimming`);
}
};
function createAnimal(name, abilities) {
const animal = Object.create(
abilities.reduce((proto, ability) => {
return Object.assign(Object.create(proto), ability);
}, { name })
);
return animal;
}
const duck = createAnimal('Duck', [canFly, canSwim]);
duck.fly();
duck.swim();
11.3 使用代理
js
// 使用 Proxy 控制原型访问
const handler = {
get(target, prop) {
if (prop === '__proto__') {
throw new Error('Direct __proto__ access is forbidden');
}
return Reflect.get(target, prop);
},
set(target, prop, value) {
if (prop === '__proto__') {
throw new Error('Direct __proto__ modification is forbidden');
}
return Reflect.set(target, prop, value);
}
};
const safeObject = new Proxy({}, handler);
console.log(safeObject.x = 1); // 正常
// safeObject.__proto__ = {}; // 抛出错误
总结
关键点
prototype是函数的属性,用于定义通过该函数作为构造函数创建的对象所共享的属性和方法__proto__是对象的属性 ,指向创建该对象的构造函数的prototype- 原型链是通过
__proto__链接的 ,而不是prototype - 使用标准方法 :
Object.getPrototypeOf()和Object.setPrototypeOf()
记忆口诀
js
函数有prototype,对象有__proto__
new的时候,对象.__proto__ = 函数.prototype
原型链是对象链,函数只在链的开端
标准方法更可靠,__proto__要慎用
现代开发建议
- 使用 ES6 类语法
- 避免直接操作
__proto__ - 使用
Object.create()创建对象 - 了解原型机制,但让框架/引擎处理细节
- 注意原型污染的安全问题