一文彻底理解js原生语法prototype,__proto__和constructor

2022-04-15 407
目录
  • 1 前言
  • 2 前置知识点
    • 2.1 数据类型
    • 2.2 判断是否是自身属性(hasOwnProperty)
  • 3 一点小思考
    • 3.1 修改 constructor
      • 3.1.1 instanceof
    • 3.1.2 isPrototypeOf
      • 3.2 修改__proto__|prototype
      • 4 修改和获取原型对象的方式
        • 4.1 修改
          • 4.1.1 Object.create
          • 4.1.2 Object.setPrototypeOf
        • 4.2 获取
          • 4.2.1 Object.getPrototypeOf
      • 5 js 内置原生构造函数
        • 5.1 js 继承的最顶端是什么
          • 5.2 js 继承的二等公民(Function)
            • 5.3 js 继承的三等公民(内置的其他构造函数)
            • 6 用户定义的特定公民构造函数
              • 7. 总结
                • 8 最后

                  1 前言

                  写了几篇vue的源码注释(并不算解析…), 感觉到了对原型的理解是不够的, 在js中, 原型是非常重要的, 只要你想在js这座山上往上爬, 它就会嘲笑你, 你把我搞会了么? 如果没有, 它就给你加个十倍重力. 如果搞懂了, 那肯定是能月薪过万, 赢取白富美, 走向人生巅峰的啦~~~

                  这篇文章讲的都是我自己的理解, 应该是原创的(我有99%把握, 除非是我之前看过文章记到脑子里了, 没法给到引用了, 联系我可以加上), 但是如果有人借鉴我的这篇文章, 希望给到一个这篇文章的链接. 其实我只是想我的文章能有更多的阅读量, 我想月薪过万, 赢取白富美, 走向人生巅峰~~~

                  2 前置知识点

                  2.1 数据类型

                  js共有7种数据类型

                  从可不可以读取属性, 可以分为两类

                  • 可以读取属性:
                    • 自身可以有属性: object
                    • 自身不可以有属性: string,number,boolean,symbol
                  • 不可以读取属性: null,undefined

                  null,undefined类型, 读取和设置属性都是非法的, 直接报错.

                  只有object能有自有属性, 可以读取属性和设置属性

                  string,number,boolean,symbol类型可以读取属性, 其实是先构造成包装对象, 再读取属性, 设置属性也是一样, 可以理解设置到了会立即销毁的包装对象上, 就是可以设置, 但是没有任何实质效果.

                  2.2 判断是否是自身属性(hasOwnProperty)

                  hasOwnProperty方法是继承来的, 用来判断该对象自身上是否有这个属性, 有就行, 不管是什么值

                  const obj = { a: 1 }
                  const o = Object.create(obj)
                  o.b = 1
                  o.c = void 0
                  console.log('a', o.a, o.hasOwnProperty('a')) // 可以读取到值, 继承而来, 但不是自身属性
                  console.log('b', o.b, o.hasOwnProperty('b')) // 可以读取到值, 自身属性
                  console.log('c', o.c, o.hasOwnProperty('c')) // 读取到undefined, 自身属性
                  console.log('d', o.d, o.hasOwnProperty('d')) // 读取到undefined, 不是自身属性, 也没有继承到这个属性
                  

                  一文彻底理解js原生语法prototype,__proto__和constructor

                  3 一点小思考

                  程序就是数据结构与算法, 好的程序最好是使用最小的内存存储数据, 使用最快的时间完成运行得到结果.

                  复用数据可以达到减少内存使用的目的, 例如a和b需要完成一样的功能, 就可以复用同一个方法(属性).

                  那么就需要解决一个问题, 这个复用的方法存在哪里, a和b怎样能找到它.

                  在js中的解决方案是, a和b都由函数(这里先叫Function吧)构造而来, 复用的方法存放在函数身上(prototype属性里).

                  (因为只有构造函数身上需要存放复用的方法, 所以prototype只有可构造的函数上才有, 箭头函数不是用来构造的, 它就没有, 其它对象, 如果连函数都不是, 就更不会有这个属性了)

                  那么需要给a,b 和Function建立起联系, 因为a,b需要到Function身上找它们可以用的复用方法

                  在js中的实现是通过constructor属性,即a.constructor, b.constructor可以找到Function

                  所以通过a.constructor.prototype可以找到它可以复用的方法的存放地址, 为了快速找到js提供了一种快捷方法a.__proto__一步到位找到, 即a.constructor.prototype和a.__proto__找到的是同一个对象, 当然它俩是全等的啦.

                  // 它俩都不是自有属性, 我也不知道怎么从这俩属性上找到原型对象的了, 肯定是魔法.....
                  const obj = {}
                  console.log(obj.hasOwnProperty('__proto__')) // false
                  console.log(obj.hasOwnProperty('constructor')) // false
                  

                  (所以, 如果手动修改了constructor,prototype,__proto__的指向, 那么你得清楚你在干什么)

                  (我不知道js的设计者是不是这样想的, 哈哈, 我就这样认为, 这样好理解多了)

                  (这个过程称之为继承, 而且是一个链式过程, 即可以a.constructor.prototype.constructor.prototype.constructor.prototype这样查找, 直到找到最顶端, 这个过程可以由a.__proto__.__proto__.__proto__加速, 这个就叫做原型链, js的继承只有这一种实现方式.)

                  (上面只是引导思考过程, 其实查找原型对象并不会通过a.constructor.prototype去找, 而是直接通过__proto__查找)

                  3.1 修改 constructor

                  const Dog = function () {}
                  const dog = new Dog()
                  
                  dog.constructor = 0
                  
                  console.log(dog.hasOwnProperty('constructor')) // true
                  console.log(dog.constructor) // 0
                  
                  console.log(dog.__proto__.constructor) // [Function: Dog]
                  

                  总结, 修改了这个属性, 增加了找到构造它的构造函数的难度, 不能直接获取了, 需要到原型对象上去读取.

                  如果它自身的这个属性和原型上的这个属性都被修改了, 那么也只是找不到它的构造函数了而已, 不会有别的影响.

                  3.1.1 instanceof

                  instanceof关心的是原型链, 跟constructor没有关系

                  印证上面的点, 修改constructor属性, 除了让实例找不到构造它的构造函数, 没有别的影响了. 如果需要通过实例找到它的构造函数, 就需要维护好它俩的关系.

                  // 语法是
                  // a instanceof b
                  // 这个操作符是判断 a 的原型链上是否有  b.prototype, 因为要判断 b.prototype 所以 b 必需是一个 可构造的函数, 否则会报错
                  const fn = function () {}
                  const o = Object.create(fn.prototype)
                  // 此时 o 的原型链上有 fn.prototype, 因为 o.__proto__ === fn.prototype
                  console.log(o instanceof fn) // true
                  const emptyObj = {}
                  fn.prototype = emptyObj
                  // 此时 o 的原型链上已经没有 fn.prototype 了, 因为此时 o.__proto__ 已经不再和 fn.prototype 相等了
                  console.log(o instanceof fn) // false
                  o.__proto__ = emptyObj
                  // 修正了 o.__proto__ 就好了
                  console.log(o instanceof fn) // true
                  

                  3.1.2 isPrototypeOf

                  现在有个新的api, 实现的功能和instanceof一致, 但是更加语义化一些, 直接判断对象是否在另一个对象的原型链上

                  const fn = function () {}
                  const o = Object.create(fn.prototype)
                  console.log(fn.prototype.isPrototypeOf(o)) // true
                  

                  3.2 修改__proto__|prototype

                  先说一个总结, 在构造实例的时候, 会将这个实例的__proto__指向此时的构造函数的prototype, 然后实例实际是继承的是__proto__.(为什么强调此时, 因为构造函数的prototype可能会被修改指向, 修改之后只会影响修改之后构造的实例, 修改之前构造的实例还会使用修改之前的prototype)

                  所以, 就可以理解到修改__proto__和prototype会有哪些影响了

                  1.修改__proto__的指向

                  只会影响它自己的继承

                  const Dog = function () {}
                  const dog = new Dog()
                  const d = new Dog()
                  Dog.prototype.name = 'Dog'
                  dog.__proto__ = {
                    name: '__proto__',
                  }
                  console.log(d.name) // Dog
                  console.log(dog.name) // __proto__
                  

                  2.修改__proto__的属性

                  会影响这一波段构造的实例

                  const Dog = function () {}
                  const dog = new Dog()
                  const d = new Dog()
                  Dog.prototype.name = 'Dog'
                  console.log(d.name) // Dog
                  console.log(dog.name) // Dog
                  Dog.prototype = {
                    name: 'after',
                  }
                  const dog1 = new Dog()
                  const d1 = new Dog()
                  console.log(d1.name) // after
                  console.log(dog1.name) // after
                  dog1.__proto__.name = '__proto__'
                  // 可以看到只影响了当前这一段构造的实例, 之前和之后的都不会被影响到, 因为这一段内的是同一个 Dog.prototype , 它们的 __proto__ 都是指向它的
                  console.log(d1.name) // __proto__
                  console.log(dog1.name) // __proto__
                  Dog.prototype = {
                    name: 'new',
                  }
                  const dog2 = new Dog()
                  const d2 = new Dog()
                  console.log(d2.name) // new
                  console.log(dog2.name) // new
                  

                  3.修改prototype的指向

                  会影响这一波段构造的实例

                  4.修改prototype的属性

                  会影响这一波段构造的实例, 同修改 __proto__的属性

                  4 修改和获取原型对象的方式

                  4.1 修改

                  上面已经讲了修改prototype和__proto__

                  4.1.1 Object.create

                  const obj = {
                    name: 'objName',
                  }
                  
                  const o = Object.create(obj)
                  // 它相当于 o.__proto__ = obj, 但是推荐使用`Object.create`
                  
                  console.log(o.name) // objName
                  console.log(o.__proto__ === obj) // true
                  

                  4.1.2 Object.setPrototypeOf

                  const obj = {
                    name: 'objName',
                  }
                  
                  const o = {}
                  
                  Object.setPrototypeOf(o, obj)
                  // 它相当于 o.__proto__ = obj, 但是推荐使用`Object.setPrototypeOf`
                  const proto = Object.getPrototypeOf(o)
                  console.log(proto === obj && proto === o.__proto__) // true
                  const obj1 = {}
                  o.__proto__ = obj1
                  const proto1 = Object.getPrototypeOf(o)
                  console.log(proto1 === obj1 && proto1 === o.__proto__) // true
                  

                  总结, 在什么时候使用Object.create, 在什么时候使用Object.setPrototypeOf呢, 首先它俩都是标准api, 都是建议使用的, 在创建对象的时候就要指定原型时使用Object.create, 需要动态修改原型对象时, 使用Object.setPrototypeOf

                  4.2 获取

                  之前已经讲了, 通过 constructor.prototype和__proto__获取了

                  4.2.1 Object.getPrototypeOf

                  const obj = {
                    name: 'objName',
                  }
                  
                  const o = {}
                  
                  Object.setPrototypeOf(o, obj)
                  
                  const proto = Object.getPrototypeOf(o)
                  console.log(proto === obj && proto === o.__proto__) // true
                  

                  5 js 内置原生构造函数

                  这些原生的构造函数的prototype属性是不可写, 不可枚举, 不可配置的

                  console.log(Object.getOwnPropertyDescriptor(Object, 'prototype'))
                  // {
                  //   value: [Object: null prototype] {},
                  //   writable: false,
                  //   enumerable: false,
                  //   configurable: false
                  // }
                  

                  5.1 js 继承的最顶端是什么

                  null, 必须是这家伙, 不然只能无限套娃了

                  然后其它所有对象都是从Object构造而来, 所以所有的对象都可以继承到Object.prototype.

                  const obj = {}
                  const o = new Object()
                  

                  5.2 js 继承的二等公民(Function)

                  在上面的小思考中, 说到, js对象都是函数构造而来, 所以包括Object也是由Function构造来的, 甚至它自己都是由自己构造而来

                  console.log(Object.constructor === Function) // true
                  // 这就离谱了, 第一个Function是从哪里来的呢????
                  console.log(Function.constructor === Function) // true
                  

                  我再来一点小理解, 可能是在js内部做了小处理, 第一个Function是凭空变出来的…. 然后这个Function构造出了Object, 然后这个Object构造出了第一个原型对象Object.prototype, 然后再去修改一些引用关系.

                  其实最复杂的是Object和Function的关系

                  console.log(Object.__proto__ === Function.prototype) // true
                  console.log(Function.constructor === Function) // true
                  console.log(Function.__proto__ === Function.prototype) // true
                  console.log(Function.prototype.__proto__ === Object.prototype) // true
                  

                  5.3 js 继承的三等公民(内置的其他构造函数)

                  const arr = [
                    String,
                    Array,
                    Boolean,
                    Number,
                    Date,
                    RegExp,
                    Error,
                    Promise,
                    Map,
                    Set,
                    Symbol,
                    Proxy,
                  ]
                  // 都是由Function构造而来
                  

                  6 用户定义的特定公民构造函数

                  这个才是重点, 根据上面的理解, 我会再开一篇文章写一下我理解的js的继承, 这里就先留个坑

                  7. 总结

                  这篇文章跟网上大多讲constructor,prototype,__proto__的文章都有所不同, 我的立足点是从给定的一个可以读取属性的值开始, 在js中, 除了null和undefined, 其它所有的值都可以成为立足点. 从这个立足点开始, 它的__proto__属性记录了它的原型对象, 这个原型对象是构造它时, 它的构造函数的prototype属性的值.

                  const a = 1
                  console.log(a.__proto__.constructor) // [Function: Number]
                  

                  读取一个值的属性的值时, 如果它自身有这个属性, 那么直接返回这个属性的值, 否则就会到它的__proto__对象上去找, 一直递归下去, 直到找到顶部null, 找到就返回它的值, 没找到就返回undefined

                  这篇文章有三个理解点,让我茅塞顿开, 都是在我试验了好久突然得到的结论

                  1. 以一个值为立足点开始分析
                  2. 在构造实例的时候, 会将这个实例__proto__指向此时的构造函数的prototype
                  3. 查找原型对象时, 以__proto__为准

                  8 最后

                  到此这篇关于js原生语法prototype,__proto__和constructor的文章就介绍到这了,更多相关js原生语法prototype,__proto__和constructor内容请搜索NICE源码以前的文章或继续浏览下面的相关文章希望大家以后多多支持NICE源码!

                  免责声明:
                  1、本网站所有发布的源码、软件和资料均为收集各大资源网站整理而来;仅限用于学习和研究目的,您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 不得使用于非法商业用途,不得违反国家法律。否则后果自负!

                  2、本站信息来自网络,版权争议与本站无关。一切关于该资源商业行为与www.niceym.com无关。
                  如果您喜欢该程序,请支持正版源码、软件,购买注册,得到更好的正版服务。
                  如有侵犯你版权的,请邮件与我们联系处理(邮箱:skknet@qq.com),本站将立即改正。

                  NICE源码网 JavaScript 一文彻底理解js原生语法prototype,__proto__和constructor https://www.niceym.com/18389.html

                  常见问题
                  • 请您联系网站客服QQ,79024519,将第一时间处理您的问题,咨询请提供会员ID以及资源编号!
                  查看详情
                  • 本站发布的压缩包如遇需要解压密码请尝试本站域名:www.niceym.com (通常为小写,如不对可试试大写) 或者:niceym.com 如遇无法解压,请您提供资源编号发送给本站客服!
                  查看详情