prototype proto 和 constructor
new 关键字
当我们使用new关键字执行构造函数时,函数内部的this会指向新创建的实例对象。
function Person() {
  this.name = 'Tom';
}
var person = new Person();
console.log(person);
执行上述代码后,实例对象person上就挂载了一个属性name: 'Tom'。

原型对象
在 JavaScript 中,每个函数都有一个prototype属性,指向该函数的原型对象。原型对象是所有通过该构造函数创建的实例对象的公共祖先,实例对象可以访问原型对象上的属性和方法。
function Person() {
  this.name = 'Tom';
}
Person.prototype.age = 20;
var person = new Person();
console.log(person.age); // 20
实例对象person可以访问到原型对象上的age属性。

访问原型对象的两种方式
我们可以通过以下两种方式来访问一个对象的原型:
- 通过实例对象的
__proto__属性 - 通过构造函数的
prototype属性 
function Person() {
  this.name = 'Tom';
}
Person.prototype.age = 20;
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
实例对象的__proto__属性和构造函数的prototype属性指向同一个原型对象。
实例对象与 Object 原型的关系
在 JavaScript 中,几乎所有对象都是Object的实例,因此它们的原型链最终都会指向Object.prototype。但实例对象的原型和Object.prototype并不是同一个对象。
function Person() {
  this.name = 'Tom';
}
var person = new Person();
console.log(person.__proto__ === Object.prototype); // false
console.log(person.__proto__.__proto__ === Object.prototype); // true

实例对象person的原型是Person.prototype,而Person.prototype的原型才是Object.prototype。
属性的颜色区分
在浏览器控制台中查看对象时,不同颜色的属性有不同的含义:
- 淡紫色表示系统内置的属性
 - 深紫色表示用户自定义的属性
 
function Person() {
  this.name = 'Tom';
}
var person = new Person();
console.log(person);

访问原型对象的最佳实践
虽然我们可以通过实例对象的__proto__属性来访问原型对象,但这种做法并不推荐。更好的方式是使用Object.getPrototypeOf()方法。
function Person() {
  this.name = 'Tom';
}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
console.log(Object.getPrototypeOf(person) === Person.prototype); // true
使用Object.getPrototypeOf()可以避免直接访问__proto__可能带来的问题。
constructor 属性
每个原型对象都有一个constructor属性,指向与之关联的构造函数。
function Person() {
  this.name = 'Tom';
}
var person = new Person();
console.log(person);

可以看到,Person.prototype.constructor指向了Person构造函数本身。
重写原型对象与 constructor
如果我们直接重写一个函数的原型对象,那么新原型对象的constructor属性会指向Object构造函数,而不是原来的构造函数。
function Person() {
  this.name = 'Tom';
}
Person.prototype = {
  age: 20,
  sayHi: function () {
    console.log('Hi');
  },
};
var person = new Person();
console.log(person);

重写Person.prototype后,新原型对象的constructor指向了Object,而不是Person。
为了保持constructor属性的正确性,我们在重写原型对象时,需要手动恢复constructor属性:
function Person() {
  this.name = 'Tom';
}
Person.prototype = {
  age: 20,
  sayHi: function () {
    console.log('Hi');
  },
  constructor: Person,
};
var person = new Person();
console.log(person);

这样修改后,Person.prototype.constructor又重新指向了Person构造函数。