JavaScript:原型链和原型对象
1. 前言
原型链和原型对象一直是JavaScript中的一个重要的概念,因为涉及到面向对象。
在现在的高级语言中,比如Java,C#,C++都采用了面向对象的设计方式,正因如此,JavaScript并没有类的概念,但是JavaScript却大量的使用了对象,而为了保持对象之间的联系,JavaScript引入了原型和原型链的概念。
即使在ES6后添加了class关键字,但它其实是一种语法糖,它的实现原理依然是通过原型链和原型对象。
在现在的React和Vue中,已经大量的使用ES6的语法,所以ES6语法必须要进行学习。
2. 原型对象
在初步学习JavaScript原型和原型链的概念时,很容易不知所云,这个时候只有反复研究,再去查阅相关的资料,因为原型和原型链是JavaScript中非常重要的一个概念。
在JavaScript中,对象本质上就是一个函数,在一般的书写中,对象的声明一般首字母要使用大写,例如:function Person()。而对象的实例化则需要使用到关键字new,例如:let person = new Person();。
至于new关键字到底做了什么,我们下文中会着重讲解。
2.1 __proto__
对象.__proto__:实例的原型对象。
对象.__proto__ === 函数.prototype。
例子:
function Person() {
}
let person = new Person(); // 实例化对象console.log(person.__proto__ === Person.prototype); // true2.2 原型prototype
- 
每一个构造函数都拥有一个 prototype属性,这个属性指向一个对象,也就是原型对象。当使用这个构造函数创建实例的时候,prototype属性指向的原型对象就成为实例的原型对象。function Parsen() {}Parsen.prototype.run = function () {console.log('在运动');};let p = new Parsen();console.log(Parsen.prototype === p.__proto__); //true
- 
原型对象默认拥有一个 constructor属性,指向指向它的那个构造函数(也就是说构造函数和原型对象是互相指向的关系)。function Parsen() {}Parsen.prototype.run = function () {console.log('在运动');};let p = new Parsen();console.log(Parsen.prototype.constructor === Parsen); //true
- 
每个对象都拥有一个隐藏的属性 [[prototype]],指向它的原型对象,这个属性可以通过Object.getPrototypeOf(obj)或obj.__proto__来访问。function Parsen() {}Parsen.prototype.run = function () {console.log('在运动');};let p = new Parsen();console.log(Object.getPrototypeOf(p) === p.__proto__); //trueconsole.log(Object.getPrototypeOf(p) === Parsen.prototype); //true
- 
实际上,构造函数的 prototype属性与它创建的实例对象的[[prototype]]属性指向的是同一个对象,即对象.__proto__ === 函数.prototype。
- 
如上文所述,原型对象就是用来存放实例中共有的那部分属性。 
- 
在 JavaScript中,所有的对象都是由它的原型对象继承而来,反之,所有的对象都可以作为原型对象存在。
- 
访问对象的属性时, JavaScript会首先在对象自身的属性内查找,若没有找到,则会跳转到该对象的原型对象中查找。
3. 原型链
了解了什么是原型对象后,我们就可以进一步学习原型链。
因为原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链(prototype chain)。
通过下面的图中,我们可以很清晰的观测到整个原型链。

注意:原型链的终点都是Object函数的prototype属性,因为在JavaScript中的对象都默认由Object()构造。Objec.prototype指向的原型对象同样拥有原型,不过它的原型是null,而null则没有原型。
例子:
function Parsen() {}Parsen.prototype.run = function() {  console.log("在运动");};
console.log(Parsen.prototype.__proto__ === Object.prototype); //true4. new关键字
可以看到,在实例化对象的时候我们使用了new关键字,即:let person = new Person();,那么这个new关键字到底起了什么样的作用呢?
其实new关键字做了下面的几件事情:
- 创建一个临时对象。
- 给临时对象绑定原型。
- 给临时对象对应的属性赋值。
- prototype对象的方法的- this指向实例对象。
- 将临时对象return。
function Fun() {  //new做的事情  var obj = {};  obj.__proto__ = Fun.prototype; // Base为构造函数  obj.name = 'Damonare';  ... // 一系列赋值以及更多的事  return obj;}
//例子function Fun1() {  this.name = 'Damonre';  this.age = 21;  this.sex = 'man';  this.run = function () {    return this.name + this.age + this.sex + '正在跑步';  };}
//可以改写为function Fun2() {  var obj = {}; // 创建一个临时对象。  obj.__proto__ = Fun2.prototype; // 给临时对象绑定原型。  obj.name = 'Damonre'; // 给临时对象绑定原型。  obj.age = 21;  obj.sex = 'man';  return obj; // 将临时对象return。}
Fun2.prototype.run = function () {  return this.name + this.age + this.sex + '正在跑步'; // prototype对象的方法的this指向实例对象。};
console.log(Fun2().run());注意:new关键字本质上是语法糖。
5. 最后
了解了原型和原型链后,我们接下来就可以正式进入JavaScript面向对象的学习。
面向对象的三个基本特征:封装、继承、多态。
而关于JavaScript的继承,有很多种方式,这些就留到下一篇文章再进行归纳整理。