js原型及原型链

329 阅读4分钟

函数原型及原型链

一、函数的 prototype 属性

  1. 每个函数都有一个prototype属性,可称为显式原型对象,它默认指向一个 Object 空对象
  2. 原型对象中有一个属性 constructor,它返回创建实例对象的 Object 构造函数的引用。注意,此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串
  3. 给原型对象添加属性或者方法,一般都是方法,函数的所有实例对象自动拥有原型中的属性、方法

注意 prototype是函数才有的属性,具体原因,可以看看阮一峰大神的这篇文章Javascript继承机制的设计思想,里面介绍了prototype的设计由来

二、对象的__proto__属性

  1. __proto__ 是每个对象都有的属性,可称为隐式原型对象,并且都指向创建该对象的函数的prototype。

它不是一个规范属性,该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它。__proto__属性已在ECMAScript 6语言规范中标准化,用于确保Web浏览器的兼容性,因此它未来将被支持。它已被不推荐使用, 现在更推荐使用Object.getPrototypeOf/Reflect.getPrototypeOf和Object.setPrototypeOf/Reflect.setPrototypeOf 参考MDN文档

  1. 所有的函数都是由Function函数创建的,所以函数的__proto__指向Function的prototype

  2. Function也是函数,因此它也由Function创建的,也就是说它自己创建了自己!所有Function__proto__指向的就是Functionprototype

  3. Object函数也是Function函数创建的,因此Object__proto__也是指向Functionprototype

  4. prototype也是一个对象,它是Object函数创建的,所以prototype__proto__指向Objectprototype

  5. 但是Object.prototype却是一个特例,它的__proto__指向的是null

三、原型链

访问一个对象的属性时, 先在自身属性中查找,找到返回 如果没有,再沿着__proto__这条链条向上查找,找到返回 如果最终没找到,返回 undefined

别名:隐式原型链 作用:查找对象的属性(方法)

    function Person(name) {
        this.name = name;
    }
    Function.prototype.runing = function() {
        console.log('人会走路');
    }
    const person = new Person("洋仔");
    // person.running();
    person.hasOwnProperty("name");

instanceof 是如何判断的

instanceof是原型链继承中的重要环节,对instanceof的理解有利于理解原型链继承的机制。

以 A instanceof B为例

  1. instanceof的普通的用法,检测B.prototype是否存在于参数A的原型链上。
  2. 继承中判断实例是否属于它的父类

简单理解就是沿着A的__proto__跟B的prototype寻找,如果能找到同一引用,返回true,否则返回false。

四、vue2.x 中数组响应式实现思路,修改原型链的结构

vue2.x响应式原理

vue2中Object.defineProperty响应式只对对象有效,对数组无效,所以对数组做额外处理。我们知道,会改变数组本身的方法只有7个:sort, push, pop, slice, splice, shift, unshift,所以可以通过重写这些方法来达到数组响应式

解决方案:

  1. 找到数组原型
  2. 覆盖那些能够修改数组的更新方法,让他们在修改数组同时,还可以通知更新
  3. 将得到的新的原型设置到数组实例原型上
  4. 对数组内部元素实现响应式
   
   // 1. 替换数组原型中7个方法
    let ARRAY_METHOD = [
      'push',
      'pop',
      'shift',
      'unshift',
      'reverse',
      'sort',
      'splice',
    ]

    // 思路, 原型式继承: 修改原型链的结构
    // let arr = []
    // 继承关系: arr -> Array.prototype -> Object.prototype -> ...
    // 继承关系: arr -> 改写的方法 -> Array.prototype -> Object.prototype -> ...

    /// 克隆原数组原型
    let array_methods = Object.create( Array.prototype )

    // 2. 在克隆的原型上,覆盖那些能够修改数组的更新方法,让他们在修改数组同时,还可以通知更新
    ARRAY_METHOD.forEach( method => {
      array_methods[ method ] = function () {
        // 调用原来的方法
        console.log( '调用的是拦截的 ' + method + ' 方法' )

        // 将数据进行响应式化
        for( let i = 0; i < arguments.length; i++ ) {
          reactify( arguments[ i ] )
        }

        let res = Array.prototype[ method ].apply( this, arguments )
        // Array.prototype[ method ].call( this, ...arguments ) // 类比
        return res
      }
    } )

    // 对象响应化 简化后的版本
    function defineReactive( target, key, value, enumerable ) {
      // 函数内部就是一个局部作用域, 这个 value 就只在函数内使用的变量 ( 闭包 )

      if ( typeof value === 'object' && value != null && !Array.isArray( value ) ) {
        // 是非数组的引用类型
        reactify( value ) // 递归
      }

      Object.defineProperty( target, key, {
        configurable: true,
        enumerable: !!enumerable,

        get () {
          console.log( `读取 ${key} 属性` ); // 额外
          return value;
        },
        set ( newVal ) {
          console.log( `设置 ${key} 属性为: ${newVal}` ); // 额外
          value = newVal;
        }
      } )
    }

    // 将对象 o 响应式化
    function reactify( o ) {
      let keys = Object.keys( o )

      for ( let i = 0; i < keys.length; i++ ) {
        let key = keys[ i ] // 属性名
        let value = o[ key ]
        if ( Array.isArray( value ) ) {
          // 3. 将得到的新的原型设置到数组实例原型上
          value.__proto__ = array_methods // 数组就响应式了
          // 4. 对数组内的元素,同样进行响应化
          for ( let j = 0; j < value.length; j++ ) {
            reactify( value[ j ] ) // 递归
          }
        } else {
          // 对象或值类型
          defineReactive( o, key, value, true )
        }
      }
    }