简单实现vue的响应式

class Vue {
  constructor(options) {
    this.$options = options
    this.$data = options.data
    this.initData()
    this.initComputed()
    this.initWatch()
  }

  initData() {
    let data = this.$data
    const keys = Object.keys(data)

    // 数据代理
    for (const key of keys) {
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get: function proxyGetter() {
          return data[key]
        },
        set: function proxySetter(val) {
          data[key] = val
        }
      })
    }

    // 数据劫持
    observe(data)
  }

  initComputed() {
    const computed = this.$options.computed
    if (!computed) return

    const keys = Object.keys(computed)
    for (const key of keys) {
      const watcher = new Watcher(this, computed[key], function() {}, {
        lazy: true
      })
      
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get: function computedGetter() {
          if (watcher.dirty) {
            watcher.get() // 触发 get
            watcher.dirty = false
          }
          if (Dep.target) {
            // 1号watcher收集到的dep,把这些dep一个个拿出来通知他们收集,现在仍然在台上的2号watcher
            for (let j = 0; j < watcher.deps.length; j++) {
              watcher.deps[j].depend()
            }
          }
          return watcher.value
        },
        set: function computedSetter() {
          throw new Error('请不要给计算属性赋值')
        }
      })
    }
  }

  initWatch() {
    const watch = this.$options.watch
    if (!watch) return

    const keys = Object.keys(watch)

    for (const key of keys) {
      new Watcher(this, key, watch[key])
    }
  }

  $watch(key, cb) {
    new Watcher(this, key, cb)
  }

  $set(target, key, value) {
    defineReactive(target, key, value)
    target.__ob__.dep.notify()
  }
}

// 1.观察data
function observe(data) {
  const type = Object.prototype.toString.call(data)
  if (type !== '[object Object]' && type !== '[object Array]') return
  if (data.__ob__) {
    return data.__ob__
  }
  return new Observe(data)
}

// 1.劫持data
function defineReactive(data, key, value) {
  const childOb = observe(data[key])
  const dep = new Dep() // 每个属性存一个 Dep 实例,收集 watch
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      // console.log(`获取data的${key}`)
      dep.depend() // 存 watch 的回调
      childOb && childOb.dep.depend() // 收集
      return value
    },
    set: function reactiveSetter(val) {
      if (value === val) return
      // console.log(`data的${key}发生了改变`)
      dep.notify() // 触发 watch 的回调
      value = val
    }
  })
}

// 1.观察data
class Observe {
  constructor(data) {
    this.dep = new Dep() // 存一个 Dep 实例,后面用来实现$set
    if (Array.isArray(data)) {
      data.__proto__ = ArrayMethods
      observeArray(data) // 观察数组
    } else {
      this.walk(data)
    }
    Object.defineProperty(data, '__ob__', { // 把 Observe 实例挂载到对象的 __ob__ 属性上
      value: this,
      enumerable: false,
      configurable: true,
      writable: true
    })
  }

  walk(data) {
    const keys = Object.keys(data)

    for (const key of keys) {
      defineReactive(data, key, data[key])
    }
  }

  observeArray(data) {
    for (const item of data) {
      observe(item)
    }
  }
}

// 2.实现watch
const targetStack = []
class Dep {
  constructor() {
    this.subs = []
  }

  addSub(watcher) {
    this.subs.push(watcher)
  }

  depend() { // push 某个回调
    Dep.target && Dep.target.addDep(this)
  }

  notify() { // 依次执行回调
    this.subs.forEach((watch) => {
      watch.update()
    })
  }
}

// 2.实现watch
let watchId = 0
const watchQueue = []
class Watcher {
  constructor(vm, exp, cb, options = {}) {
    this.dirty = this.lazy = !!options.lazy // 打缓存标记
    this.vm = vm
    this.exp = exp
    this.cb = cb
    this.id = ++watchId
    this.deps = []
    !this.lazy && this.get()
  }

  
  addDep(dep) {
    // dep实例有可能被收集过,如果收集过,则直接返回
    if (this.deps.indexOf(dep) !== -1) return
    this.deps.push(dep)
    dep.addSub(this)
  }
 
  get() {
    targetStack.push(this)
    Dep.target = this
    if (typeof this.exp === 'function') { // computed
      this.value = this.exp.call(this.vm)
    } else {
      this.value = this.vm[this.exp] // 这里访问一次就是触发 defineReactive 函数中的 get 方法执行 dep.depend()
    }

    targetStack.pop()
    if (targetStack.length) {
      // 将栈顶的watcher拿出来放到“舞台”
      Dep.target = targetStack[targetStack.length - 1]
    } else {
      Dep.target = null
    }
    // Dep.target = null
  }

  update() {
    if (this.lazy) {
      this.dirty = true
    } else {
      this.run()
    }
  }

  run() {
    if (watchQueue.includes(this.id)) return // 已经存在队列中
    watchQueue.push(this.id)
    const index = watchQueue.length - 1
    Promise.resolve().then(() => {
      this.get()
      this.cb.call(this.vm)
      watchQueue.splice(index, 1)
    })
  }
}

// 3.对数组原生方法的拦截
const ArrayMethods = {}
ArrayMethods.__proto__ = Array.prototype
const methods = [
  'push',
  'pop'
]

methods.forEach(method => {
  ArrayMethods[method] = function(...args) {
    const res = Array.prototype[method].apply(this, args)
    if (method === 'push') { // 如果 push 的是 object or array
      this.__ob__.observeArray(args)
    }
    this.__ob__.dep.notify()
    return res
  }
})
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值