4

[深入02] 原型链

 2 years ago
source link: https://segmentfault.com/a/1190000040532486
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

[深入02] 原型链

发布于 30 分钟前
  • 2021/07/21更新
  • 2021/07/22更新

[[深入01] 执行上下文](https://juejin.im/post/684490...
[[深入02] 原型链](https://juejin.im/post/684490...
[[深入03] 继承](https://juejin.im/post/684490...
[[深入04] 事件循环](https://juejin.im/post/684490...
[[深入05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490...
[[深入06] 隐式转换 和 运算符](https://juejin.im/post/684490...
[[深入07] 浏览器缓存机制(http缓存机制)](https://juejin.im/post/684490...
[[深入08] 前端安全](https://juejin.im/post/684490...
[[深入09] 深浅拷贝](https://juejin.im/post/684490...
[[深入10] Debounce Throttle](https://juejin.im/post/684490...
[[深入11] 前端路由](https://juejin.im/post/684490...
[[深入12] 前端模块化](https://juejin.im/post/684490...
[[深入13] 观察者模式 发布订阅模式 双向数据绑定](https://juejin.im/post/684490...
[[深入14] canvas](https://juejin.im/post/684490...
[[深入15] webSocket](https://juejin.im/post/684490...
[[深入16] webpack](https://juejin.im/post/684490...
[[深入17] http 和 https](https://juejin.im/post/684490...
[[深入18] CSS-interview](https://juejin.im/post/684490...
[[深入19] 手写Promise](https://juejin.im/post/684490...
[[深入20] 手写函数](https://juejin.im/post/684490...

[[react] Hooks](https://juejin.im/post/684490...

[[部署01] Nginx](https://juejin.im/post/684490...
[[部署02] Docker 部署vue项目](https://juejin.im/post/684490...
[[部署03] gitlab-CI](https://juejin.im/post/684490...

[[源码-webpack01-前置知识] AST抽象语法树](https://juejin.im/post/684490...
[[源码-webpack02-前置知识] Tapable](https://juejin.im/post/684490...
[[源码-webpack03] 手写webpack - compiler简单编译流程](https://juejin.im/post/684490...
[[源码] Redux React-Redux01](https://juejin.im/post/684490...
[[源码] axios ](https://juejin.im/post/684490...
[[源码] vuex ](https://juejin.im/post/684490...
[[源码-vue01] data响应式 和 初始化渲染 ](https://juejin.im/post/684490...

构造函数的缺点

  • 通过构造函数生成实例对象,属性和方法都生成在实例上,多个实例之间属性和方法不能共享

prototype属性

  • javaScript的继承机制的设计思想:原型对象的所有属性和方法都能被实例对象所共享
  • 所有的函数都有一个prototype属性,指向一个对象
  • 对于构造函数来说:在构造函数生成实例的时候,构造函数的prototype属性会成为实例对象的原型
  • 原型对象上的属性不是实例对象自身的属性,只要修改原型对象,变动就会立刻反应到所有实例对象上
  • 如何实例对象和原型对象有同名的属性和方法,则实例对象读取该属性时,会读取自身的属性,而不会读取原型上的属性
  • 原型对象的作用:定义所有实例共享的属性和方法
  • js规定,所有对象都有原型对象
  • 所有对象都可以成为其他对象的原型,原型对象也是对象,也有自己的原型,形成一个链条
  • 一层层上溯,最终都会上溯到Object.prototype,即Object构造函数的prototype属性
  • 即所有对象都继承了 Object.prototype 对象上的属性和方法,这就是所有对象都具有valueOf和toString的原因
  • Object.prototype的原型是null,null没有任何属性和方法,也没有自己的原型,原型链终止
  • null是为了防止死链

覆盖 overriding

  • 读取对象的属性时,自身和原型上有同名的属性和方法,优先读取自身属性,这叫做覆盖
  • 读取对对象的属性时,自身没有会到原型上找,原型没有会到原型的原型上找,直到Object.prototype,还是没有返回undefined
  • 一级级向上,在整个原型链上寻找某个属性,对性能是有影响的。
  • overriding:覆盖的意思

    function A(){} //---------------------- 定义构造函数A
    A.prototype = new Array() // ---------- 将A.prototype指向实例数组,那么A的实例就能继承数组原型链上的属性和方法
    A.prototype.constructor = A // -------- 修改prototype的同时,也要修改constructor防止意外
    const a = new A()
    a.push(1)
    a instanceof Array // true
    

constructor

  • prototype对象有一个constructor属性,默认指向prototype属性所在的构造函数
  • 因为constructor属性在prototype对象上,所以constructor属性被所有实例继承
    • (1) constructor的作用是,可以得知一个实例对象,到底是由哪个构造函数产生的
    • (2) constructor的另一个作用:可以用一个实例新建另一个实例
  • 注意:<font color=red>constructor表示原型对象和构造函数之间的关连关系,如果修改原型对象,一般会同时修改constructor属性,防止引用的时候报错,=> 因为修改了prototype之后,A.prototype.constructor指向的已经不是A了,而是最新赋值给prototype对象的对象指向的那个constructor</font>
  • 修改原型对象要一同修改构造函数,防止引用报错!!!!!!!
  • name属性

    • constructor.name => 构造函数名
    <script>
    function A(){}
    const a = new A()
    console.log(A.prototype.constructor === A) // true
    console.log(a.constructor === A.prototype.constructor) // true,constructor属性在prototype对象上,所以能被实例继承并访问
    console.log(a.constructor === A) // true,constructor的作用是可以确定实例由哪个构造函数产生
    console.log(a.constructor === RegExp) // false
    console.log(a.hasOwnProperty('constructor'), "a.hasOwnProperty('constructor')") // constructor不是实例自身属性,继承的
    console.log(new a.constructor(), 'constructor的另一个作用: 一个实例可以借助constructor新建另一个实例')
    const b = new a.constructor()
    console.log( b instanceof A) // true , 即b是A的其中一个实例
    </script>
    constructor
    constructor表示原型对象和构造函数之间的关联关系,修改prototype,需要同时修改constructor,防止引用出错
    
    <script>
    function A () {
      console.log('A构造函数')
    }
    console.log(A.prototype, '修改之前的A.prototype')
    console.log(A.prototype.constructor, '修改之前的A.prototype.constructor') // A
    console.log(A.prototype.constructor.name, '修改之前的A.prototype.constructor.name => 修改前的constrctor名称')
    A.prototype = {name: 'woow_wu7'}
    console.log(A.prototype.constructor, '修改之后的A.prototype.constructor') // Object
    console.log(A.prototype.constructor.name, '修改之后的A.prototype.constructor.name => 修改后的constrctor名称')
    A.prototype.constructor = A
    console.log(A.prototype.constructor, '修改prototype后,要重新指定A.prototype.constructor=A,防止引用出错')
    </script>

instanceof

  • 返回一个布尔值,表示对象是否为某个构造函数的实例
  • instanceof左边是实例对象,右边是构造函数
  • <font color=red> instanceof会检查右边构造函数的原型对象prototype,是否在左边对象的原型链上</font>
  • <font color=red> 由于instanceof检查整个原型链,因此同一个实例对象,可能对多个构造函数返回true</font>
  • 有一个特殊情况:如果左边对象的原型链上只有null(即左边对象的原型对象是null),这时instanceof判断就会失真
  • instanceof的一个用处就是判断值类型,(但是只能用于判断对象,不能用于判断原始类型的值)
  • 对于undefined和null,instanceOf运算符总是返回false
  • <font color=red>利用instanceof可以巧妙的解决,调用构造函数时忘记加new命令的问题</font>

    instanceof
    
    function A(){}
    const a = new A()
    console.log(a instanceof A, 'instanceof的原理是检查右边构造函数的原型对象是否在左边对象的原型链上')
    console.log(A.prototype.isPrototypeOf(a), 'instanceof等价于这样')
    
    
    var d = new Date();
    d instanceof Date
    // true
    // d既是Date的实例,也是Object的实例
    d instanceof Object 
    // true
    // 因为instanceof检查的是右边构造函数的实例,是否在左边实例对象的原型链上,检查整个原型链
    // 所以同一个实例,可能对多个构造函数返回ture
    
    
    
    var obj = Object.create(null); // 以null为原型对象创建实例
    typeof obj // "object"
    Object.create(null) instanceof Object // false
    
    installof可以解决调用构造函数时,忘记加new命令的情况
    
    function A() {
    if(!(this instanceof A)) { // 如果this不是A的实例,说明不是new命令调用的,那么执行new
      return new A()
    }
    else {
      this.name = 'woow_wu7'
    }
    }
    const a = new A()
    console.log(a)

    Object.getPrototypeOf

  • 返回参数对象的原型对象
  • Object.getPrototypeOf 是获取原型对象的标准方法
  • 注意:<font color=red>参数是对象,也可以是函数,因为函数也是对象,在es6中可以判断类的继承,Object.getPrototypeOf(ColorPoint) === Point
    // true,ColorPoint是一个类,即是一个函数</font>

  • 获取参数对象的原型对象,是获取原型对象的标准方法

    Object.getPrototypeOf(Object.prototype) === null // true
    Object.getPrototypeOf(Function.prototype) === Object.prototype // true
    Object.getPrototypeOf({}) === Object.prototype // true

  • 将第一个参数对象的原型设置为第二个参数对象 Object.setPrototypeOf(现有对象,原型对象)
  • 返回值:返回第一个参数对象
  • 参数:第一个参数是现有对象,第二个参数是原型对象

    const a = {}
    const b = {name: 'woow_wu7'}
    const res = Object.setPrototypeOf(a, b) // 将b设置成a的原型,注意返回值是a
    Object.getPrototypeOf(a) === b // true
    console.log(a.name)
    console.log(res) // a, 返回值是a
  • new命令可以用Object.setPrototypeOf来模拟

    var F = function () {
    this.foo = 'bar';
    };
    
    var f = new F();
    // 等同于
    var f = Object.setPrototypeOf({}, F.prototype);
    // 返回值f是第一个参数 {}
    // Object.setPrototypeOf({}, F.prototype) => 相当于 {}.__proto__ = F.prototype
    F.call(f);

    Object.create

  • 生成实例对象

    • 通过new命令执行构造函数的方式生成(构造函数其实就是普通的函数,只是用new命令调用时,this指向了实例,并且首字母大写来区分,不大写也行,但约定俗成)
    • 通过一个对象生成,有时候只能拿到一个对象,要生成实例对象。Object.create,该实例完全继承原型对象的属性和方法
  • <font color=red>Object.create() 以参数对象为原型返回实例对象,该实例完全继承原型对象的属性和方法</font>

    手动实现一个 Object.create
    
    Object._create = function(obj) {
    function F(){}
    F.prototype = obj // 新建一个构造函数,将构造函数的prototype指向传入的对象,执行构造函数,即实例的原型指向了传入的对象
    return new F()
    }
  • 如果想要生成一个不继承任何属性和方法的对象,可以使用 Object.create(null)
  • <font color=red>如果Object.create()的参数为空,或者不是对象就会报错</font>

Object.prototype.isPrototypeOf

  • 实例对象的 isPrototypeOf 属性用于判断该对象是否是参数对象的原型

    Object.prototype.__proto__

  • 实例对象的原型对象
  • 根据语言标准,只有浏览器需要部署__proto__,其他环境中没有__proto__属性
  • 不建议使用__proto__
  • 而是用标准的Object.getPrototypeOf读取原型Object.setPrototypeOf(现有对象,原型对象)来设置原型

获取原型对象的方法比较

- `obj.__proto__` // 只有浏览器才有,不建议使用
- obj.constructor.prototype // 手动修改原型时,可能会失真
- Object.getPrototypeOf() // 推荐的获取方法
直接修改原型对象时,需要同时修改constructor防止失真


function A(){}
const a = new A()

function B() {}
B.prototype = a
const b = new B()

b.constructor.prototype === a // false
// 因为:b.constructor === a.constructor === A.prototype.constructor === A
// 所以:b.constructor.prototype === A.prototype
// 结论:当直接修改prototype属性时,一定要修改constructor属性 !!!!!!!!!!!!!!!!!!(重要)


// 如果是下面这样则:
function A(){}
const a = new A()
function B() {}
B.prototype = a
B.prototype.constructor = B // 修改了prototype,同时修改constructor则引用不会出错
const b = new B()
b.constructor.prototype === a // true

Object.prototype.hasOwnProperty

  • 返回一个布尔值,表示是否是对象自身的属性,不包含原型链上的属性

    Date.hasOwnProperty('length') // true
    Date.hasOwnProperty('toString') // false

in运算符

  • in运算符返回一个布尔值,表示属性在对象中是否存在,不区分自身属性还是继承的属性
  • 注意:<font color=red>in 运算符不区分自身属性和继承属性</font>

    例子:
    function X(){}
    X.prototype.name = 'woow_wu7';
    let x = new X()
    'name' in X // true
    // 因为:in 运算符返回一个布尔值,表示属性是否在对象中存在,不区分自身还是继承
    // 所以:'name' in X => 返回 true

    for in 和 for of

  • for in 可以遍历对象和数组
  • for of 只能遍历数组

  • 用于数组,i表示:key
  • 用于对象,i表示:key
  • 用于对象时,for...in会遍历自身属性和继承的属性,
    for of
  • 用于数组:i表示 value

    const objP = {sex: 'man'}
    const obj = {name: 'woow_wu7', age: 20, address: 'hangzhou'};
    Object.setPrototypeOf(obj, objP)
    for(let i in obj) {
    console.log(i, 'for in 循环 => 用于对象,会遍历自身和继承的属性') // name,age,address,sex
    if (obj.hasOwnProperty(i)) {
    console.log(i, '如果只希望遍历自身属性,可以用Object.prototype.hanOwnProperty(属性名)来过滤')// name,age,address
    }
    }

  • 我的简书:https://www.jianshu.com/p/1a2...

  • constructor

    • constructor表示构造函数和原型对象之间的关联关系,如果修改了原型对象,需要一起修改构造函数,防止引用出错。
      -(每一个构造函数都有一个prototype属性,prototype的constructor指向prototype所在的构造函数)
  • instanceof

    • 原理:instanceof是检查(右边构造函数的prototype属性)是否在(左边对象)的原型链上
    • instanceof失效的情况:

      • 如果一个对象的__proto__属性指向null,则instanceof就会失效
      • 因为右边是构造函数的prototype => 终点是Object.prototype,是否在左边对象的原型链上
      • Object.prototype.__prototo__ === null
      • Object.prototype instanceof Ojbect // false
        // Object.create(null) instanceof Object // false,因为创建的实例没有任何属性和方法,也没有原型

2021/07/24更新

  • null没有任何属性和方法
  • 如何生成一个没有任何属性和方法的对象

    • Object.create(null)
  • 如何模拟一个Object.create
  • 修改prototype属性时,一定要同时修改constructor属性,防止引用出错,不然会指向被赋值对象的构造函数的prototype上的constructor
    20210724165301.jpg

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK