# 原型和原型链
相信大家肯定见过下面这几个单词,但是有时候又是傻傻分不清楚,不知道这几个单词到底是做啥的,又有啥区别.今天我们就来好好的瞧一瞧,剖析剖析它们.它们分别是
prototyoe
,__proto__
,constructor
.
# prototype
在JavaScript中,每一个对象(除了null)在创建的时候都会与之关联另一个对象,这另一个对象就是我们所说的原型,每一个对象都会从原型"继承"属性.这里的继承并不是真继承.因为继承意味着复制操作,然而JavaScript默认并不会复制对象的属性.这里只是在两个对象之间建立一个关联.这样,一个对象就能通过委托访问另一个对象的方法或属性.
所有函数都有prototype
属性,称之为原型属性(也叫显式原型),而普通对象是没有prototype
的. 虽然函数也是对象,但是我们这里的普通对象显然是不包含函数的.
console.log(({}).prototype) // undefined
console.log((function(){}).prototype) // {constructor: ƒ}
其实,这个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
Person
是个函数,通过构造调用的方式生成了p
和p2
两个对象.而Person
的原型对象上又定义了一个say
方法,从运行结果看,p
和p2
两个实例对象都可以调用say
方法输出Hello World
,并且p.say === p2.say
的结果为true
也证明了它们是共享访问.
下面我们来看关系图:
# proto
在JavaScript中,所有对象(除了null)都会有一个内部属性__proto__
(也叫隐式原型),该属性指向被构造调用函数的原型对象
function Person(){}
let p = new Person()
console.log(p.__proto__ === Person.prototype)
由此,我们又可以在关系图上面再添加点东西:从实例指向原型对象
上面也说到了,所有对象(除了null)都有__proto__
,那么Person.__proto__
又指向什么呢?我们在上面的代码后面添加如下
console.log(Person.__proto__ === Function.prototype) // true
这是因为函数也是一种对象,可以认为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
可以得出一个结论:
let o = new F()
o.__proto__ === F.prototype
或者:
o.__proto__ === o.constructor.prototype // 这个原因在后面的constructor中会提到
注意: 使用__proto__
是有争议的,因此不鼓励使用它,现在更推荐使用Object.getPrototypeOf/Reflect.getPrototypeOf
.参考地址:MDN
# constructor
默认情况下,每个原型对象都会自动获得一个constructor
属性,指向其关联的构造函数.
function Person(){}
console.log(Person.prototype.constructor === Person)
let p = new Person()
// 使用 getPrototypeOf 方法还能获取对象的原型
console.log(Object.getPrototypeOf(p) === Person.prototype) // true
我们再来看下面代码
function Person(){}
let p = new Person()
console.log(p.constructor === Person) // true
实例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没有原型
我们在上面的代码后面继续添加如下代码:
Object.prototype.say = function(){
console.log('Hi Universe')
}
Object.prototype.shout = function(){
console.log('Hello Universe')
}
p.say() // Hello World
p.shout() // Hello Universe
实例对象p
自身没有say
和shout
方法,向原型查找,在Person原型对象中找到了say
,就不再继续向上查找,而shout
方法不在Person原型对象中,因此继续向上查找.最终在Object原型对象中找到了shout
,发出了Hello Universe
的呐喊
此时我们的关系图变成了如下:
# 补充
function Person(){}
console.log(Person.__proto__ === Function.prototype) // true
console.log(Object.__proto__ === Function.prototype) // true
Person
和Object
,它们一个是自定义函数,另外一个是内置构造函数,都算是函数实例,因此也是由Function
构造生成.
最后,嘿嘿嘿,给大家出一道题目,看看大家知不知道为什么
console.log(Function instanceof Object)
console.log(Object instanceof Function)
总结: 有关原型和原型链的内容就暂时讲到这里了,相信大家对这几个东西应该都有所了解了.确实,这块内容中各种指来指去,关系图中也是箭头满天飞,还需要大家好好消化一下.