observifyArray

by jin5354

HTML

<script src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js"></script>

Babel + JSX

// more detail go to 
// https://github.com/jin5354/leaf-observable

// Dep 类,保存数据源的所有订阅,并在接收到数据源的变动通知后,触发所有订阅
class Dep {
  constructor() {
    this.subs = []
  }

  addSub(sub) {  // 添加订阅
    this.subs.push(sub)
  }

  notify(newValue, oldValue) {
    this.subs.forEach(sub => {
      sub.update(newValue, oldValue) // 触发订阅
    })
  }
}

const globalDep = new Dep()

// Watcher 类,每个 Watcher 为一个订阅源
class Watcher {
  constructor(callback) {
    this.callback = callback
  }

  update(newValue, oldValue) {
    this.callback(newValue, oldValue)  // 被触发后执行回调
  }
}

// 使一个对象响应化
function observify(value) {
  if(!isObject(value)) {
    return
  }
  if(Array.isArray(value)) {  // 由于性能问题,我们不再对数组的每个 key 执行 Object.defineReactive
    observifyArray(value)
    for(let i = 0; i < value.length; i++) {
      observify(value[i])
    }
  }else {
    Object.keys(value).forEach((key) => {
      defineReactive(value, key, value[key])  // 遍历每个键使其响应化
    })
  }
}

function isObject(value) {
  return typeof value === 'object' && value !== null
}

//为对象的一个键应用 Object.defineProperty
function defineReactive(obj, key, value) {
  observify(value) // 递归
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      return value
    },
    set: (newValue) => {
      if(newValue === value) {
        return
      }else {
        let oldValue = value
        value = newValue
        observify(newValue)
        globalDep.notify(newValue, oldValue) // 变动时通知 Dep
      }
    }
  })
}

// 数组响应化
function observifyArray(arr) {
  const aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'] //需要变异的函数名列表
  let arrayAugmentations = Object.create(Array.prototype) // 创建一个 __proto__ 到 Array.prototype 的 arrayAugmentations 对象
  aryMethods.forEach(method => {    // 在 arrayAugmentations 对象上将需要变异的函数重写
    arrayAugmentations[method] = function(...arg) {
      let oldValue = _.cloneDeep(this)
      Array.prototype[method].apply(this, arg)  // 执行默认操作
     ...