Vue 2.3.4源码分析之双向绑定原理

本文深入探讨Vue如何通过defineReactive和Object.defineProperty实现数据监听,以及解决数组方法变动监听问题。通过代理模式或直接在数组对象上新增方法来重写原生数组操作。当数据变化时,依赖收集的Dep通知Watcher执行视图渲染,整个过程基于观察者模式。
摘要由CSDN通过智能技术生成

    要想实现双向绑定需要做到两点:1.如何监听data对象是否改变;2.对象变化后如何去更新视图

    一、如何监听data对象是否改变

     在Vue监听data对象是否改变主要通过defineReactive方法来做到的,就是利用Object.defineProperty的get和set方法。如下代码所示:

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: Function
) {
  const dep = new Dep()//每个监听的对象都新建一个dep对象,dep相当于观察者模式中的被观察对象,watcher相当于观察者对象


  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set

  let childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()//这里是进行订阅操作
        if (childOb) {
          childOb.dep.depend()
        }
        if (Array.isArray(value)) {
          dependArray(value)
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = observe(newVal)
      dep.notify()//通知所有订阅了该dep的观察者watcher对象去更新视图,watcher会调用updateComponent方法去更新视图
    }
  })
}

    但是Object.defineProperty也不是没有缺点,就是它无法监听数组的push,shift等方法。那么如何监听数组的这一些方法呢?

    我们知道数组对象本身没有push这一些方法,都是继承至Array.prototype对象的,所以我们想到可以重写这一些方法,但不能直接修改Array.prototype对象上的方法,否则将影响所有数组的使用。那Vue是如何做到的呢?分为两种情况:

    1.如果浏览器支持__proto__熟悉的话,vue采用的是代理模式实现的,实现方式如下:

    首先Vue创建了一个继承至Array.prototype的arrayMethods代理对象,在该代理对象中重写了相关的数组原生方法,

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

/**
 * Intercept mutating methods and emit events
 */
;[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator () {
    // avoid leaking arguments:
    // http://jsperf.com/closure-with-arguments
    let i = arguments.length
    const args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
        inserted = args
        break
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})

    然后再将数组对象继承至arrayMethods对象

function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

    2.如果浏览器不支持__proto__属性,就只需在数组对象上新增了push等方法,这样当数组调用push等方法时将不再使用Array.prototype的方法,而是这一些新增的方法。

function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

    二、对象变化后如何去进行视图渲染

    首先对象变化,会触发对于的defineReactive中的set方法,在set方法中会触发dep.notify(),该方法会触发所有订阅了该dep的watcher去只执行视图渲染,可以参考http://blog.csdn.net/zhoulu001/article/details/79372555

   总结如下,其实vue的双向绑定主要基于观察者模式实现的。Dep相当于被观察对象,Watcher相关于观察者对象,在页面首次渲染后,defineReactive中的get方法就让watcher进行了对dep的订阅,一旦data数据变化,就调用dep.notify()去通知所有的订阅了该dep的watcher,去执行渲染操作。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值