正文
javascript原型、原型链相关的话题,也是老生常谈的一个话题了,一直也对这块的理解不够深刻,这篇文章主要也是记录下自己对于原型、原型链这块知识的理解、整理,方便后续查看。
名称认知
prototype
-> 原型、原型对象。
__proto__
-> 原型的链接点。
什么是prototype?
首先需要知道的是,prototype
是函数里面的一个属性,本质上是一个对象。如何验证原型是函数里面的一个属性呢?
function Test () {
// Do Someing
}
console.log(Test.prototype) // { constructor: ƒ Test () {}, [[Prototype]]: Object }
console.log(Test.prototype.constructor) // ƒ Test () {}
复制代码
由此验证prototype是函数里面的一个属性,本质上是一个对象的论点。故我们又称prototype
为:原型对象。
什么是__proto__
?
__proto__
是对象内置的一个属性。上文提到过,函数里面的prototype
是一个对象,我们可以通过prototype
来论证这一论点。
function Test () {
// Do Someing
}
console.log(Test.prototype.__proto__)
复制代码
那么__proto__
是如何作为原型链中的链接点,让原型链整个链条可以链接起来呢?
__proto__
里面保存着构造该对象函数的prototype
。
function Test () {
// Do Someing
}
const test = new Test()
console.log(test.__proto__) // { constructor: ƒ Test () {}, [[Prototype]]: Object }
consle.log(test.__proto__ === Test.prototype) // true
复制代码
致此,以上提出观点均已论证。
提问:既然对象都有自己的__proto__
属性,那么函数Test.prototype.__proto__
保存的又是谁的prototype
呢?
function Test () {
// Do Someing
}
const test = new Test()
console.log(test.__proto__) // { constructor: ƒ Test () {}, [[Prototype]]: Object }
consle.log(test.__proto__ === Test.prototype) // true
console.log(Test.prototype.__proto__) // {constructor: ƒ Object(), __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
复制代码
可以看到,最后一个log输出的就是Test.prototype.__proto__
,里面保存着构造Test.prototype.__proto__
的构造函数的prototype
。所以,构造出Test.prototype.__proto__
的到底是个啥?
如何识别该对象的构造函数?
可以仔细看下以上的每一个log,里面都有一个属性:constructor
。
constructor
,在js中,他是每一个对象、函数的DNA存储的地方,DNA鉴定(查看constructor
)找到自己的生父。在非人为干扰,或者第三者插足的情况下,都是可以精准找到的。当然,天生天养,跳出阴阳外,不在五行中的除外。(null
:还有这样的存在?是孙悟空吗?, 我:…, 说的就是你)。
…扯远了,关于null
的特殊性如果感兴趣的话,可自行查找相关资料了解之。
既然,我们已经知道了,如何找出自己的构造函数,那么Test.prototype.__proto__
输出的内容,就显而易见了,Test.prototype.__proto__
输出的constructor
里面保存着的是Object.prototype
,很明显这是一个对象,所以,继续还是看看Object.prototype.__proto__
里面的是个什么磨破豆腐吧。
function Test () {
// Do Someing
}
const test = new Test()
console.log(test.__proto__) // { constructor: ƒ Test () {}, [[Prototype]]: Object }
consle.log(test.__proto__ === Test.prototype) // true
console.log(Test.prototype.__proto__) // {constructor: ƒ Object(), __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
// 此处输出的为:Object.prototype,因为Test.prototype.__proto__里面保存着的是构造出Test.prototype.__proto__的构造函数的prototype
console.log(Test.prototype.__proto__.__proto__) // null,
复制代码
em….,输出了个null
, 也就是说,这条链子已经到头了,而且,链子的最顶点是由null
作为完结点。
得出结论:原型链,是以对象为基准,以__proto__
为链接点,(__proto__
保存着构造该对象的函数的prototype
),最终形成的链式结构。原型链的完结点为:Object.prototype
关于原型链查找
上面对prototype
、__proto__
以及原型链,进行了论证、总结。接下来我们就看看,具体一点的东西。
function Test () {
this.fruits = 'apple'
}
let test = new Test()
Test.prototype.fruits = 'banana'
Test.prototype.__proto__.fruits = 'orange'
console.log(test)
console.log(test.fruits)
复制代码
上面代码中,分别在Test
函数中声明了属性为fruits
,值为apple
,Test.prototype
里面声明了属性fruits
,值为bananan
,Object.prototype
中声明了属性fruits
,值为orange
。可以看到目前实例对象test.fruits
输出为构造函数,也就是Test
函数内部声明的fruits
的值。
WHY?
可以看到对象test
是new Test()
的返回值,其中new
之后发生了什么?为什么要使用new
?
let test = new Test()
// let this = Object.create(Test.prototype) // 以构造函数的prototype为值,创建新对象,并赋值给this
// this.fruits = 'apple' // 在新对象中添加构造函数Test里面存在的属性
// return this // 默认返回this对象
复制代码
当然,这里new
之后的返回值,与构造函数Test
还是有很大关系的,上文中的Test
函数就大概是这样的一个流程。
言归正传,实例对象test
,通过new
操作符构造出来之后,准确的得到了Test
构造函数中的属性值fruits = 'apple'
,因此,log中的test.fruits
输出为:apple
。
function Test () {
// this.fruits = 'apple'
}
let test = new Test()
Test.prototype.fruits = 'banana'
Test.prototype.__proto__.fruits = 'orange'
console.log(test)
console.log(test.fruits)
复制代码
当注释掉Test
函数中的属性值时,我们发现test.fruits
输出了banana
,此时,就可以看出new
操作符的作用了。因为在使用new
的时候,new
操作符帮我们在内部使用了Test.prototype
创建了新的对象,也就是说,test
里面实际上包含着Test.prototype
里面的所有属性、方法,我们也可以得出结论了:当访问对象中的某个属性时,对象本身如果不存在的话,会通过原型链,继续查找。
查找规则
手动敲黑板
有个比较重要的点,是需要记住的。查找属性的原则是就近原则。什么意思呢?当对象自身存在访问的属性值时,直接返回该属性值,查找停止。当自身不存在该属性值时,继续在原型链查找,一旦找到,返回该属性值,不在继续查找。
这也是为什么,第一次输出的是apple
,不是banana、orange
,注释掉Test
的this.fruits = ‘apple’
之后,查找到的结果是banana
而不是orange
,并不是因为banana
比orange
便宜或者好吃。当然,当把Test.prototype.fruits = 'banana'
注释掉之后,返回的是orange
,这是必然的。
以上为个人观点、总结,不喜勿喷,如果存在错误的地方,请在评论区留言,我会在看到的第一时间修正。