数据响应原理(三)-数组的响应式处理

202 阅读3分钟

由于数组无法处理,所以要单独处理数组

知识点


复制Array.prototype里的push,pop,shift,unshift,splice,sort,reverse方法, 重写此方法 利用Object.setPrototypeOf

setPrototypeOf()

Object.setPrototypeOf() 方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null

create()

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。


流程

  1. 入参-对象
  2. observer-是否数组
  3. 将-对象-原型指向,新的数组增强方法,再通过observer类的observeArray方法,对数组逐一进行observe
  4. observeArray循环需要改写的7个方法
  5. 备份原方法,通过工具函数定义新方法
  6. 定义新方法
  • 恢复原有方法功能
  • 将类似数组(参数) 转换为数组
  • 取出ob
  • 处理3个冲洗插入值的方法(push,unshift,splice)将方法插入的值,再次执行observeArray,以防依旧有数组

源码

// 入口
import observe from './observe'
const obj = {
  a: {
    b: {
      m:5
    },
    c5
  },
  n4,
  g: [33,22,55]
}
observe(obj)
obj.a.c = 10
obj.g.push([33,44])
console.log(obj.g'--------pushed-------')

// array.js
import { def } from "./utils"
const arrayPrototype = Array.prototype
// 以Array.prototype 为原型创建对象
export const arrayMethods = Object.create(arrayPrototype)
// 要被改写的7个方法,vue只有此7个列表方法数据会更新 单独改一个值不会出发数据更新
const methodsNeedTochange = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
console.log(methodsNeedTochange, '------------')
methodsNeedTochange.forEach(methodName => {
  // 备份原有方法
  const original = arrayPrototype[methodName];
  // 定义新方法
  def(arrayMethods, methodName, function () {
     // 恢复原来的功能,才能使用 原有方法
    const result = original.apply(thisarguments); 
    
    // 把类数组对象变为数组
    const args = [...arguments];
    // 把这个数组身上的__ob__取出来,__ob__已经被添加了,为什么已经被添加了?因为数组肯定不是最高层,比如obj.g属性是数组,obj不能是数组,第一次遍历obj这个对象的第一层的时候,已经给g属性(就是这个数组)添加了__ob__属性。
    const ob = this.__ob__;
    // 有三种方法push\unshift\splice能够插入新项,现在要把插入的新项也要变为observe的
    let inserted = [];
    switch (methodName) {
      case 'push':
      case 'unshift':
          inserted = args;
          break;
      case 'splice':
          // splice格式是splice(下标, 数量, 插入的新项)
          inserted = args.slice(2);
          break;
     }
     // 判断有没有要插入的新项,让新项也变为响应的 
    if (inserted) {
       // 因为是整个数组已经 实例化过了 拥有了walk,和observeArray 才可以调用
      ob.observeArray(inserted);
    }
    
    // pop等方法 要返回值
    return result;
  }, false)
})

// observer.js

import { def } from './utils'
import defineReactive from './defineReactive'
import { arrayMethods } from './array'
import observe from './observe'
export default class Observer {
  constructor(value) {
    // 给实例增加了一个不可枚举的__ob__
    def(value, '__ob__'thisfalse)
    console.log('Observer构造器', value)
    // Observer类的目的是:将一个正常的object转换为每个层级的属性都是响应式(可以被侦测的)的object
    if (Array.isArray(value)) {
      // 将数组的原型,指向arrayMethods
      Object.setPrototypeOf(value, arrayMethods)
      // 数组逐项observe,非数组,子元素已经在defineReactive,中处理了子项目,而数组,则需单独处理
      this.observeArray(value);
    } else {
      this.walk(value)
    }
  }
  // 遍历
  walk(value) {
    for (let i in value) {
      defineReactive(value, i)
    }
  }
  observeArray(arr) {
    for (let i = 0, l = arr.length; i < l; i++) {
      // 逐个进行observe
      observe(arr[i])
    }
  }
 }

// observe.js
import Observer from './observer'
export default function observe(value) {
  // 如果不是对象 返回
  if (typeof value != 'object'return
  // 定义ob
  let ob;
  if ('__ob__' in value) {
    ob = value.__ob__;
  } else {
    ob = new Observer(value)
  }
  return ob
}

// utils.js
export function def(obj, key, value, enumerable) {
  Object.defineProperty(obj, key, {
    configurabletrue,
    enumerable,
    writabletrue,
    value
});
}


// defineReactive.js
import observe from './observe'
export default function defineReactive(obj, key, val) {
  if (arguments.length == 2) {
    val = obj[key]
  }
  // 子元素observe,形成多文件互相调用成为递归
  let childOb = observe(val)
  console.log('-------------defineReactive', obj, key)
  Object.defineProperty(obj, key, {
      configurabletrue,
      enumerabletrue,
      // getter
      get() {
        console.log('-----------------访问属性', key, ':', val)
        return val
      },
      // setter
      set(newData) {
        if (val === newData) {
          return false
        }
        console.log('-----------------设置属性', key, ':', val)
        val = newData
        // 当设置了新值,这个值页要被observe,防止新值又是个对象
        childOb = observe(newData)
      } 
  });
}