原型-2:prototype 和 __proto__ 的区别详解

这两个属性都与 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__ = {};  // 抛出错误

总结

关键点

  1. prototype是函数的属性,用于定义通过该函数作为构造函数创建的对象所共享的属性和方法
  2. __proto__是对象的属性 ,指向创建该对象的构造函数的 prototype
  3. 原型链是通过 __proto__链接的 ,而不是 prototype
  4. 使用标准方法Object.getPrototypeOf()Object.setPrototypeOf()

记忆口诀

js 复制代码
函数有prototype,对象有__proto__

new的时候,对象.__proto__ = 函数.prototype

原型链是对象链,函数只在链的开端

标准方法更可靠,__proto__要慎用

现代开发建议

  • 使用 ES6 类语法
  • 避免直接操作 __proto__
  • 使用 Object.create()创建对象
  • 了解原型机制,但让框架/引擎处理细节
  • 注意原型污染的安全问题
相关推荐
浩星6 小时前
css实现类似element官网的磨砂屏幕效果
前端·javascript·css
一只小风华~6 小时前
Vue.js 核心知识点全面解析
前端·javascript·vue.js
2022.11.7始学前端6 小时前
n8n第七节 只提醒重要的待办
前端·javascript·ui·n8n
SakuraOnTheWay6 小时前
React Grab实践 | 记一次与Cursor的有趣对话
前端·cursor
阿星AI工作室6 小时前
gemini3手势互动圣诞树保姆级教程来了!附提示词
前端·人工智能
徐小夕6 小时前
知识库创业复盘:从闭源到开源,这3个教训价值百万
前端·javascript·github
xhxxx6 小时前
函数执行完就销毁?那闭包里的变量凭什么活下来!—— 深入 JS 内存模型
前端·javascript·ecmascript 6
StarkCoder6 小时前
求求你试试 DiffableDataSource!别再手算 indexPath 了(否则迟早崩)
前端
fxshy6 小时前
Cursor 前端Global Cursor Rules
前端·cursor
红彤彤6 小时前
前端接入sse(EventSource)(@fortaine/fetch-event-source)
前端