vue Observer的实现

243 阅读3分钟

Observer需要实现的功能

例如vue里面的data,修改data中的数据的时候,能够触发视图更新

先实现最简单的值为常量的情况

例如

const data = {
  name: '魁拔',
  title: '十万火急'
}

定义observe函数:遍历data的所有属性,并对属性进行处理

function observe (data) {
  Object.keys.forEach(key => {
    defineReactive(data, key, data[key])
  })
}

实现defineReactive:利用Object.defineProperty()对对象属性进行重写

function defineReactive (data, key, val) {
  Object.defineProperty(data, key, {
    get () {
      // 这里不能return data[key],会一直触发get,造成死循环
      return val
    },
    set (newVal) {
      // 同理这里使用data[key]去判断也会出现死循环
      if (val === newVal) reutrn
      val = newVal
      console.log('更新视图')
    }
  })
}

注意:这里的val,为常量的时候,可以看做是一个中间变量。为引用类型时是对data[key]的值的一个引用,此时调用val并不会执行data[key]的get方法 此时,执行以下代码就会打印“更新视图”

observe(data)
data.title = '战神崛起'

深度监听data

data下的属性值也可能是一个对象,需要做以下修改

function observe (data) {
  if (typeof data !== 'object' || data === null) return
  Object.keys(data).forEach(key => {
    defineReactive(data, key, data[key])
  })
}

function defineReactive (data, key, val) {
  observe(val)
  Object.defineProperty(data, key, {
    get () {
      return val
    },
    set (newVal) {
      if (val === newVal) reutrn
      val = newVal
      console.log('更新视图')
    }
  })
}

这个例子也就能触发“视图更新”

const data = {
  name: '魁拔',
  title: '十万火急',
  exp: {
    boxOffice: 100
  }
}
observe(data)
data.exp.boxOffice = 200

传给observe函数的是一个数组

需要遍历数组,把数组的每一项再传给observe

function observe (data) {
  if (typeof data !== 'object' || data === null) return
  if (Array.isArray(data)) {
    for (let i = 0; i < data.length; i += 1) {
      observe(data[i])
    }
  } else {
    Object.keys(data).forEach(key => {
      defineReactive(data, key, data[key])
    })
  }
}

这个例子也就能触发“视图更新”

const data = {
  name: '魁拔',
  title: '十万火急',
  version: [1, 2, { n: 100 }]
}
observe(data)
data.version[2].n = 200

push,pop,shift,unshift,splice,sort,reverse也能触发视图更新

修改代码

const arrayProto = Array.prototype
// 复制一份新的原型,避免对数组的原型造成污染
const proto = Object.create(arrayProto)
// 改写数组的7个方法
;['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
  proto[method] = function (...args) {
    // Array.prototype[method]
    const result = arrayProto[method].call(this, ...args)
    console.log('更新视图')
    return result
  }
})
function observe (data) {
  if (typeof data !== 'object' || data === null) return
  if (Array.isArray(data)) {
    // 给数组设置新的原型
    // data.__proto__ = proto
    Object.setPrototypeOf(data, proto)
    for (let i = 0; i < data.length; i += 1) {
      observe(data[i])
    }
  } else {
    Object.keys(data).forEach(key => {
      defineReactive(data, key, data[key])
    })
  }
}
function defineReactive (data, key, val) {
  observe(val)
  Object.defineProperty(data, key, {
    get () {
      return val
    },
    set (newVal) {
      if (val === newVal) reutrn
      val = newVal
      console.log('更新视图')
    }
  })
}

这个例子也就能触发“视图更新”

const data = {
  name: '魁拔',
  title: '十万火急',
  version: [1, 2]
}
observe(data)
data.version.push(3)

数组方法中如果增加了元素也是对象的话,也需要进行监听

增加元素的方法有push、unshift、splice,所以最后的代码为:

const arrayProto = Array.prototype
const proto = Object.create(arrayProto)
// 改写数组的7个方法
;['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
  proto[method] = function (...args) {
    // Array.prototype[method]
    const result = arrayProto[method].call(this, ...args)
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    observeArray(inserted)
    console.log('更新视图')
    return result
  }
})

function observe (data) {
  if (typeof data !== 'object' || data === null) return
  if (Array.isArray(data)) {
    // data.__proto__ = proto
    Object.setPrototypeOf(data, proto)
    observeArray(data)
  } else {
    Object.keys(data).forEach(key => {
      defineReactive(data, key, data[key])
    })
  }
}

function observeArray (data) {
  for (let i = 0; i < data.length; i += 1) {
    observe(data[i])
  }
}

function defineReactive (data, key, val) {
  observe(val)
  Object.defineProperty(data, key, {
    get () {
      return val
    },
    set (newVal) {
      if (val === newVal) reutrn
      val = newVal
      console.log('更新视图')
    }
  })
}
const data = {
  name: '魁拔',
  title: '十万火急',
  version: [1, 2]
}
observe(data)
data.version.push({ n: 100 }) // 添加对象,对象也需要监听
data.version[2].n = 200 // 更改添加对象的属性