# 原型和原型链

相信大家肯定见过下面这几个单词,但是有时候又是傻傻分不清楚,不知道这几个单词到底是做啥的,又有啥区别.今天我们就来好好的瞧一瞧,剖析剖析它们.它们分别是 prototyoe, __proto__, constructor.

# prototype

在JavaScript中,每一个对象(除了null)在创建的时候都会与之关联另一个对象,这另一个对象就是我们所说的原型,每一个对象都会从原型"继承"属性.这里的继承并不是真继承.因为继承意味着复制操作,然而JavaScript默认并不会复制对象的属性.这里只是在两个对象之间建立一个关联.这样,一个对象就能通过委托访问另一个对象的方法或属性. 所有函数都有prototype属性,称之为原型属性(也叫显式原型),而普通对象是没有prototype的. 虽然函数也是对象,但是我们这里的普通对象显然是不包含函数的.

console.log(({}).prototype)  // undefined
console.log((function(){}).prototype)  // {constructor: ƒ}
1
2

其实,这个prototype属性指向一个对象,这个对象就是调用该构造函数而创建的实例的原型,其中包含了一些属性方法,而这些属性方法可以让所有的实例共享访问.

function Person(name){
  this.name = name 
}
Person.prototype.say = function(){
  console.log('Hello World')
}
let p = new Person('zhangsan')
let p2 = new Person('lisi')
console.log(p.say === p2.say)   // true
1
2
3
4
5
6
7
8
9

Person是个函数,通过构造调用的方式生成了pp2两个对象.而Person的原型对象上又定义了一个say方法,从运行结果看,pp2两个实例对象都可以调用say方法输出Hello World,并且p.say === p2.say的结果为true也证明了它们是共享访问. 下面我们来看关系图: 在这里插入图片描述

# proto

在JavaScript中,所有对象(除了null)都会有一个内部属性__proto__(也叫隐式原型),该属性指向被构造调用函数的原型对象

function Person(){} 
let p = new Person()
console.log(p.__proto__ === Person.prototype)
1
2
3

由此,我们又可以在关系图上面再添加点东西:从实例指向原型对象 在这里插入图片描述

上面也说到了,所有对象(除了null)都有__proto__,那么Person.__proto__又指向什么呢?我们在上面的代码后面添加如下

console.log(Person.__proto__  === Function.prototype)  // true
1

这是因为函数也是一种对象,可以认为Person函数是调用了内置构造函数Function后生成的实例,所以上面的结果为true 我们再来看下面代码

let num = new Number(1)
console.log(num.__proto__ === Number.prototype)  // true
let str = new String('Hello')
console.log(str.__proto__ === String.prototype)  // true
let bool = new Boolean(true)
console.log(bool.__proto__ === Boolean.prototype)  // true
let f = new Function()
console.log(f.__proto__ === Function.prototype)  // true
console.log(Function.__proto__ === Function.prototype)  // true 这个我目前还不太清楚是为什么 QAQ
1
2
3
4
5
6
7
8
9

可以得出一个结论:

let o = new F()
o.__proto__ === F.prototype
1
2

或者:

o.__proto__ === o.constructor.prototype  // 这个原因在后面的constructor中会提到
1

注意: 使用__proto__是有争议的,因此不鼓励使用它,现在更推荐使用Object.getPrototypeOf/Reflect.getPrototypeOf.参考地址:MDN (opens new window)

# constructor

默认情况下,每个原型对象都会自动获得一个constructor属性,指向其关联的构造函数.

function Person(){} 
console.log(Person.prototype.constructor === Person) 
let p = new Person()
// 使用 getPrototypeOf 方法还能获取对象的原型
console.log(Object.getPrototypeOf(p) === Person.prototype)  // true
1
2
3
4
5

我们再来看下面代码

function Person(){}
let p = new Person()
console.log(p.constructor === Person)  // true
1
2
3

实例p并没有constructor属性,但它从原型Person.prototype中去读取,因此上面的输出结果为true.此时再回过头去看o.__proto__ === o.constructor.prototype是不是就明白为什么了.

由此,我们又可以在关系图上面再添加点东西:从原型对象指回被构造调用的函数 在这里插入图片描述

# 原型链

当访问一个对象的方法或属性时,若找不到则会查找与该对象关联的原型中的方法(属性),若还是找不到,则继续向上去原型的原型中查找,直到找到为止,就这样一直到最顶层.可以理解__proto__一层一层的指向就是原型链了.

function Person(){} 
Person.prototype.say = function(){
  console.log('Hello World')
}
let p = new Person()
console.log(p.__proto__ === Person.prototype)  // true
console.log(Person.prototype.__proto__ === Object.prototype)  // true 因为所有的对象都是直接或间接继承自Object
console.log(Object.prototype.__proto__)  // null  因为Object作为最顶层对象,是原型链的最后一环.所以这里的null表示了Object.prototype没有原型
1
2
3
4
5
6
7
8

我们在上面的代码后面继续添加如下代码:

Object.prototype.say = function(){
  console.log('Hi Universe')
}
Object.prototype.shout = function(){
  console.log('Hello Universe')
}
p.say()  // Hello World
p.shout()  // Hello Universe
1
2
3
4
5
6
7
8

实例对象p自身没有sayshout方法,向原型查找,在Person原型对象中找到了say,就不再继续向上查找,而shout方法不在Person原型对象中,因此继续向上查找.最终在Object原型对象中找到了shout,发出了Hello Universe的呐喊 此时我们的关系图变成了如下: 在这里插入图片描述

# 补充

function Person(){} 
console.log(Person.__proto__ === Function.prototype)  // true
console.log(Object.__proto__ === Function.prototype)  // true
1
2
3

PersonObject,它们一个是自定义函数,另外一个是内置构造函数,都算是函数实例,因此也是由Function构造生成. 最后,嘿嘿嘿,给大家出一道题目,看看大家知不知道为什么

console.log(Function instanceof Object)
console.log(Object instanceof Function)
1
2

总结: 有关原型和原型链的内容就暂时讲到这里了,相信大家对这几个东西应该都有所了解了.确实,这块内容中各种指来指去,关系图中也是箭头满天飞,还需要大家好好消化一下.