原型与原型链

理解 __proto__、prototype 和 constructor 的关系

问题

解释 JavaScript 中的原型与原型链机制,说明 __proto__prototypeconstructor 三者的关系。

解答

三个属性的定义

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

const p = new Person('张三');

// prototype: 函数的属性,指向原型对象
console.log(Person.prototype); // { constructor: Person }

// __proto__: 实例的属性,指向构造函数的 prototype
console.log(p.__proto__ === Person.prototype); // true

// constructor: 原型对象的属性,指向构造函数
console.log(Person.prototype.constructor === Person); // true

三者的关系图

                    ┌─────────────────────────────────────┐
                    │                                     │
                    ▼                                     │
┌─────────────┐  prototype  ┌──────────────────┐    constructor
│   Person    │ ──────────► │ Person.prototype │ ─────────┘
│ (构造函数)   │             │    (原型对象)     │
└─────────────┘             └──────────────────┘
      │                              ▲
      │ new                          │ __proto__
      ▼                              │
┌─────────────┐                      │
│      p      │ ─────────────────────┘
│   (实例)    │
└─────────────┘

原型链的形成

function Animal(type) {
  this.type = type;
}
Animal.prototype.eat = function() {
  console.log('eating');
};

function Dog(name) {
  Animal.call(this, 'dog');
  this.name = name;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
  console.log('woof!');
};

const dog = new Dog('旺财');

// 原型链查找过程
console.log(dog.name);  // '旺财' - 自身属性
console.log(dog.bark);  // function - Dog.prototype 上
console.log(dog.eat);   // function - Animal.prototype 上
console.log(dog.toString); // function - Object.prototype 上

// 原型链
console.log(dog.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true - 链的终点

验证原型链

// instanceof 沿原型链查找
console.log(dog instanceof Dog);    // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true

// isPrototypeOf 检查是否在原型链上
console.log(Dog.prototype.isPrototypeOf(dog));    // true
console.log(Animal.prototype.isPrototypeOf(dog)); // true

// getPrototypeOf 获取原型(推荐替代 __proto__)
console.log(Object.getPrototypeOf(dog) === Dog.prototype); // true

属性查找与遮蔽

function Foo() {}
Foo.prototype.x = 1;

const foo = new Foo();
console.log(foo.x); // 1 - 来自原型

foo.x = 2; // 在实例上创建属性,遮蔽原型属性
console.log(foo.x); // 2 - 来自实例
console.log(foo.__proto__.x); // 1 - 原型上的值不变

// hasOwnProperty 区分自身属性和继承属性
console.log(foo.hasOwnProperty('x')); // true
delete foo.x;
console.log(foo.hasOwnProperty('x')); // false
console.log(foo.x); // 1 - 又能访问原型上的了

关键点

  • prototype 是函数独有的属性,指向原型对象,用于实现继承
  • __proto__ 是对象的属性,指向其构造函数的 prototype,形成原型链
  • constructor 是原型对象的属性,指回构造函数本身
  • 属性查找沿原型链向上,直到 Object.prototype.__proto__(null)为止
  • 推荐用 Object.getPrototypeOf() 替代 __proto__,后者是非标准属性