浅谈js原型

123 阅读6分钟
/**
前言
  首先我们明确两点
  1. __proto__ 和 constructor是对象独有的
  2. prototype 属性是函数独有的
原型
  prototype
  *在最新的ES规范里面,prototype被定义为,给其他对象提供共享属性的对象
  *也就是说,prototype自己也是对象,只是用以承担某个职能罢了
  因此prototype 描述的是两个对象之间的某种关系(其中一个 为另一个提供属性访问权限)

constructor与prototype联系
  *每个函数都有一个prototype属性,它默认指向一个Object空对象(即是原型对象)
  *原型对象有一个属性constructor 它指向函数对象
  *给原型对象添加属性(一般是方法)
    ** 作用:函数的所有实例对象自动拥有原型中的属性(方法)
  下面例子说明下:
  function Demo() {}
  console.log(Demo.prototype.constructor === Demo) // true
  console.log(Demo.prototype)
  可以看出Demo函数对象的prototype原型是右边这个对象,那么Demo.prototype 原型上有个constructor属性,这个属性正好指向Demo函数本身
  所以你可以理解成
  A的显示原型是B则有
  A.prototype === B
  B.constructor === A
  我觉得这样的好处是你可以找到我 我可以找到你

__proto__和prototype关系
  再次强调:
  __proto__ 和constructor 是对象独有, prototype属性是函数独有
  显示原型和隐式原型
  * 每个函数fun都独有一个prototype 及显示原型(属性)
  * 每个实例对象都有一个 __proto__ 及隐式原型(属性)
  * 对象的隐式原型的值 === 其构造函数的显示原型的值
  怎么理解呢?? 我们通过内存结构图来看
  function Demo() {}
  Demo.prototype.say= () => { // 给原型添加say方法
    console.log('hello world')
  }
  console.log(Demo.prototype.say)
  let fn = new Demo()
  fn.say() // 怎么找到say方法的??
  console.log(fn.__proto__ === Demo.prototype) // true
  如图 '__proto__和prototype关系'
  我们从图中可以看到,Demo函数的原型跟它构造函数(Demo)创建的实例fn.__proto__指向同一个对象
  那么fn 是怎么找到say方法呢
  更加具体的说就是通过隐式原型__proto__找到的,分析如下
  *js引擎执行到fn.say()整行代码时,解析器去栈中查找fn变量
  *发现fn变量是引用类型,就去堆内存中查找地址0x234的实体,查到后,发现并没有say属性,所以接着去__proto__属性对应的原型
  *接着找到内存地址为0x345对应的实体,发现该实体中有say属性,同样的操作去找地址为0x789的实体,最后执行该函数

  那么是不是可以更加准备的说明,实例是通过隐式原型__proto__查找需要调用的属性的,那么我们通过接下来的代码去验证下
  function Demo() {}
  Demo.prototype.say = () => {
    console.log('hello world')
  }
  Demo.prototyope.name = 'old name'
  let fn = new Demo()
  fn.say() // 怎么找打say方法呢??
  console.log(fn.name) 
  console.log('为修改前', fn.__proto__ === Demo.prototype) // true
  console.log('----接下来修改fn的__proto__')
  fn.__proto = {
    say: () => {
      console.log('hello 隐式原型')
    },
    name: 'new name'
  }
  console.log('修改实例中的隐式原型', fn.__proto__ === Demo.prototype) // true 
  console.log(fn.name)
  fn.say()
  console.log('重新创建一个Demo构造函数实例')
  let demo1 = new Demo()
  consoel.log(Demo.prototype === demo1.__proto__)
  demo1.say()
  如图'打印结果'
  首先说明的是:
  通过查询相关的文档,ES6之前不能直接操作隐式原型,也不推荐做
  通过修改fn的隐式原型,让他指向一个新的对象, 那么fn.proto 不等于Demo.prototype, 这个例子也能证明一点,实例对象调用属性时,实例对象不具有改属性时,是通过隐式原型去找的该属性的,找不到的话,在它的隐式原型对象 的 隐式原型对象 上找
  这也就是我们常说的,在原型上添加属性或者方法,实例可以共享,原因就在于我们并不推荐去修改实例的__proto__ 属性,这样子也就是会有以下结果
  function Dmeo() {
    // 内部语句 this.prototype = {}
  }
  let fn = new Demo() // 内部语句 fn.__proto__ = Demo.prototype
  // 实例化一个对象 隐式原型会默认赋值 fn.__proto__ = Demo.prototype
  // 定义函数时,显示原型也会默认添加 Demo.prototype = new Object()
  这里需要知道的是, __proto__ 是对象所独有的,并且 __proto__是一个对象指向另一个对象,也就是他的原型对象,我们也可以理解为父类对象,他的作用就是当你访问一个对象属性时候,如果该对象内部不存在这个属性,那么就回去它的__proto__ 属性所指对象(父类对象)所指向的父类的上去查找,以此类推,直到找到null ,这个查找过程就是原型链

总结
  *那什么是原型呢?你可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。
  *函数的prototype属性:在定义函数时自动添加prototype,默认是一个空Object对象
  *对象的__proto__属性:创建一个对象实例时,默认值是构造函数的prototype属性值,也就是上面所说的
  *实例的构造函数属性(constructor)指向构造函数
  *一般而言,可以直接操作显式原型,不能直接操作隐式原型(ES6)

补充
  Object和Function的鸡和蛋的问题
  最后总结: 先有 Object.prototype(原型链顶端),Function.prototype 继承Object.prototype而产生,最后 Function和Object和其它构造函数 继承 Function.prototype而产生

  MDN的推荐
  使用__proto__是有争议的也不鼓励使用它,因为它从未包含在ES规范里,但是浏览器都实现了它,__proto__ 属性已经在ES6语言规范中标准化,用于确保Web浏览器兼容性,未来会被支持,但是不推荐使用,现在推荐使用Object.getPrototypeOf/Reflect.getPrototypeOf 和Object.setPrototypeOf/Reflect.setPrototypeOf(尽管如此,设置对象[[Prototype]]是一个缓慢的操作,如果性能是一个问题,应该是避免)
  proto 属性也可以在对象文字定义中使用对象[[Prototype]]来创建,作为Object.create() 的一个替代。


prototype chain 原型链
  最新ES规范给出定义
    a prototype may have a non-null implicit reference to its prototype, and so on; this is called the prototype chain.
  既然prototype只是恰好作为另一个对象的隐式引用普通对象,那么他也是对象,也符合对象的基本特征
  *每个对象都可以有一个原型__proto__ 这个原型还可以有它自己的原型,以此类推
  *形成一个原型链,查找特定属性时候,我们先去这个对象里去找
  *如果没有的话就去他的原型对象里边去
  *如果还是没有再去原型对象里去寻找
  这个操作被委托在原型链上,这就是我们说的原型链

  结论
  __proto__ 是原型链查询中实际用到的,它总是指向prototype
  prototype 是函数所独有的  在定义构造函数时自动创建,他总是被proto所指
  所有的对象都有__proto__,函数这个特殊对象处理具有__proto__属性,还有特有的原型属性prototype。prototype对象默认有两个属性,constructor和__proto__属性,
  prototype属性可以给函数和对象添加可共享的(继承)的方法,属性,而__proto__是查找某函数或者对象的原型链方式,constructor这个属性包含了一个指针,指回构造函数

参考
  https://juejin.cn/post/6844903984335945736
  https://github.com/creeperyang/blog/issues/9
  https://github.com/mqyqingfeng/blog/issues/2


 */