谈谈我对javascript,原型、原型链的理解。

正文

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__)
复制代码

image.png

那么__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__的到底是个啥?

catch-head.webp

如何识别该对象的构造函数?

可以仔细看下以上的每一个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, 
复制代码

image.png

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)
复制代码

image.png

上面代码中,分别在Test函数中声明了属性为fruits,值为apple,Test.prototype里面声明了属性fruits,值为bananan,Object.prototype中声明了属性fruits,值为orange。可以看到目前实例对象test.fruits输出为构造函数,也就是Test函数内部声明的fruits的值。

WHY?

可以看到对象testnew 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)
复制代码

image.png

当注释掉Test函数中的属性值时,我们发现test.fruits输出了banana,此时,就可以看出new操作符的作用了。因为在使用new的时候,new操作符帮我们在内部使用了Test.prototype创建了新的对象,也就是说,test里面实际上包含着Test.prototype里面的所有属性、方法,我们也可以得出结论了:当访问对象中的某个属性时,对象本身如果不存在的话,会通过原型链,继续查找。

查找规则

手动敲黑板

有个比较重要的点,是需要记住的。查找属性的原则是就近原则。什么意思呢?当对象自身存在访问的属性值时,直接返回该属性值,查找停止。当自身不存在该属性值时,继续在原型链查找,一旦找到,返回该属性值,不在继续查找。

这也是为什么,第一次输出的是apple,不是banana、orange,注释掉Testthis.fruits = ‘apple’之后,查找到的结果是banana而不是orange,并不是因为bananaorange便宜或者好吃。当然,当把Test.prototype.fruits = 'banana'注释掉之后,返回的是orange,这是必然的。

以上为个人观点、总结,不喜勿喷,如果存在错误的地方,请在评论区留言,我会在看到的第一时间修正。

猜你喜欢

转载自juejin.im/post/7077120616326758437