详解Vue数据驱动原理

前言

Vue区别于传统的JS库,例如JQuery,其中一个最大的特点就是不用手动去操作DOM,只需要对数据进行变更之后,视图也会随之更新。 比如你想修改div#app里的内容:

/// JQuery
<div id="app"></div>
<script>
 $('#app').text('lxb')
</script>
<template>
	<div id="app">{{ message }}</div>
  <button @click="change">点击修改message</button>
</template>
<script>
export default {
	data () {
  	return {
    	message: 'lxb'
    }
  },
  methods: {
  	change () {
    	this.message = 'lxb1' // 触发视图更新
    }
	}
}
</script>

在代码层面上的最大区别就是,JQuery直接对DOM进行了操作,而Vue则对数据进行了操作,接下来我们通过分析源码来进一步分析,Vue是如何做到数据驱动的,而数据驱动主要分成两个部分依赖收集和派发更新。

数据驱动

// _init方法中
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm) // 重点分析
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')

在Vue初始化会执行_init方法,并调用initState方法. initState相关代码在src/core/instance/state.js下

export function initState (vm: Component) {
 vm._watchers = []
 const opts = vm.$options
 if (opts.props) initProps(vm, opts.props) // 初始化Props
 if (opts.methods) initMethods(vm, opts.methods) // 初始化方法
 if (opts.data) {
  initData(vm) // 初始化data
 } else {
  observe(vm._data = {}, true /* asRootData */)
 }
 if (opts.computed) initComputed(vm, opts.computed) // 初始化computed
 if (opts.watch && opts.watch !== nativeWatch) { // 初始化watch
  initWatch(vm, opts.watch)
 }
}

我们具体看看initData是如何定义的。

function initData (vm: Component) {
 let data = vm.$options.data
 data = vm._data = typeof data === 'function' // 把data挂载到了vm._data上
  ? getData(data, vm) // 执行 data.call(vm)
  : data || {}
 if (!isPlainObject(data)) {
  data = {} // 这也是为什么 data函数需要返回一个object不然就会报这个警告
  process.env.NODE_ENV !== 'production' && warn(
   'data functions should return an object:\n' +
   'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
   vm
  )
 }
 // proxy data on instance
 const keys = Object.keys(data) // 取到data中所有的key值所组成的数组
 const props = vm.$options.props
 const methods = vm.$options.methods
 let i = keys.length
 while (i--) {
  const key = keys[i]
  if (process.env.NODE_ENV !== 'production') {
   if (methods && hasOwn(methods, key)) { // 避免方法名与data的key重复
    warn(
     `Method "${key}" has already been defined as a data property.`,
     vm
    )
   }
  }
  if (props && hasOwn(props, key)) { // 避免props的key与data的key重复
   process.env.NODE_ENV !== 'production' && warn(
    `The data property "${key}" is already declared as a prop. ` +
    `Use prop default value instead.`,
    vm
   )
  } else if (!isReserved(key)) { // 判断是不是保留字段
   proxy(vm, `_data`, key) // 代理
  }
 }
 // observe data
 observe(data, true /* asRootData */) // 响应式处理
}

其中有两个重要的函数分别是proxy跟observe,在往下阅读之前,如果还有不明白Object.defineProperty作用的同学,可以点击这里进行了解,依赖收集跟派发更新都需要依靠这个函数进行实现。

proxy

proxy分别传入vm,'_data',data中的key值,定义如下:

const sharedPropertyDefinition = {
 enumerable: true,
 configurable: true,
 get: noop,
 set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) {
 sharedPropertyDefinition.get = function proxyGetter () {
  return this[sourceKey][key]
 }
 sharedPropertyDefinition.set = function proxySetter (val) {
  this[sourceKey][key] = val
 }
 Object.defineProperty(target, key, sharedPropertyDefinition)
}

proxy函数的逻辑很简单,就是对vm._data上的数据进行代理,vm._data上保存的就是data数据。通过代理的之后我们就可以直接通过this.xxx访问到data上的数据,实际上访问的就是this._data.xxx。

observe

oberse定义在src/core/oberse/index.js下,关于数据驱动的文件都存放在src/core/observe这个目录中:

export function observe (value: any, asRootData: ?boolean): Observer | void {
 if (!isObject(value) || value instanceof VNode) { // 判断是否是对象或者是VNode
  return
 }
 let ob: Observer | void
 // 是否拥有__ob__属性 有的话证明已经监听过了,直接返回该属性
 if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
  ob = value.__ob__
 } else if (
  shouldObserve && // 能否被观察
  !isServerRendering() && // 是否是服务端渲染
  (Array.isArray(value) || isPlainObject(value)) && // 是否是数组、对象、能否被扩展、是否是Vue函数
  Object.isExtensible(value) &&
  !value._isVue
 ) {
  ob = new Observer(value) // 对value进行观察
 }
 if (asRootData && ob) {
  ob.vmCount++
 }
 return ob
}

observe函数会对传入的value进行判断,在我们初始化过程会走到new Observer(value),其他情况可以看上面的注释。

Observer类

export class Observer {
 value: any; // 观察的数据
 dep: Dep; // dep实例用于 派发更新
 vmCount: number; // number of vms that have this object as root $data
 constructor (value: any) {
  this.value = value
  this.dep = new Dep()
  this.vmCount = 0
  // 把__ob__变成不可枚举的,因为没有必要改变watcher本身
  def(value, '__ob__', this) 会执行 value._ob_ = this(watcher实例)操作
  if (Array.isArray(value)) { // 当value是数组
   if (hasProto) {
    protoAugment(value, arrayMethods) // 重写Array.prototype的相关方法
   } else {
    copyAugment(value, arrayMethods, arrayKeys) // 重写Array.prototype的相关方法
   }
   this.observeArray(value)
  } else {
   this.walk(value) // 当value为对象
  }
 }

 /**
  * Walk through all properties and convert them into
  * getter/setters. This method should only be called when
  * value type is Object.
  */
 walk (obj: Object) {
  const keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i++) {
   defineReactive(obj, keys[i]) // 对数据进行响应式处理
  }
 }

 /**
  * Observe a list of Array items.
  */
 observeArray (items: Array<any>) {
  for (let i = 0, l = items.length; i < l; i++) {
   observe(items[i]) // 遍历value数组的每一项并调用observe函数,进行响应式处理
  }
 }
}

Observe类要做的事情通过查看源码也是清晰明了,对数据进行响应式处理,并对数组的原型方法进行重写!defineReactive函数就是实现依赖收集和派发更新的核心函数了,实现代码如下。

依赖收集

defineReactive

export function defineReactive (
 obj: Object, // data数据
 key: string, // data中对应的key值
 val: any, // 给data[key] 赋值 可选
 customSetter?: ?Function, // 自定义setter 可选
 shallow?: boolean // 是否对data[key]为对象的值进行observe递归 可选
) {
 const dep = new Dep() // Dep实例 **每一个key对应一个Dep实例**

 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
 if ((!getter || setter) && arguments.length === 2) { // 没有getter或者有setter,并且传入的参数有两个
  val = obj[key]
 }

 let childOb = !shallow && observe(val) // 根据shallow,递归遍历val对象,相当于val当做data传入
 Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
   const value = getter ? getter.call(obj) : val
   if (Dep.target) { // 当前的全部的Watcher实例
    dep.depend() // 把当前的Dep.target加入到dep.subs数组中
    if (childOb) { // 如果val是对象,
     childOb.dep.depend() // 会在value._ob_的dep.subs数组中加入Dep.target, 忘记ob实例属性的同学可往回翻一番
     if (Array.isArray(value)) {
      dependArray(value) // 定义如下,逻辑也比较简单
     }
    }
   }
   return value
  },
  set: function reactiveSetter (newVal) {
   // ....
  }
 })
}

function dependArray (value: Array<any>) {
 for (let e, i = 0, l = value.length; i < l; i++) {
  e = value[i]
  e && e.__ob__ && e.__ob__.dep.depend() // 如果e是响应式数据,则往e._ob_.dep.subs数组中加入Dep.target
  if (Array.isArray(e)) {
   dependArray(e) // 递归遍历
  }
 }
}

代码中多次用到了Dep类和Dep.target,理解清楚了它们的作用,我们就离Vue数据驱动的原理更近一步了,相关的代码如下:

Dep

let uid = 0
/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
 static target: ?Watcher;
 id: number;
 subs: Array<Watcher>; 

 constructor () {
  this.id = uid++ // 每一个dep都有一个唯一的ID
  this.subs = [] // 存放watcher实例的数组
 }

 addSub (sub: Watcher) {
  this.subs.push(sub) // 往this.subs加入watcher
 }

 removeSub (sub: Watcher) {
  remove(this.subs, sub) // 删除this.subs对应的watcher
 }

 depend () {
  if (Dep.target) {
   // watcher.addDep(this) actually
   Dep.target.addDep(this) // 在watcher类中查看
  }
 }

 notify () {
  // stabilize the subscriber list first
  const subs = this.subs.slice()
  if (process.env.NODE_ENV !== 'production' && !config.async) {
   // subs aren't sorted in scheduler if not running async
   // we need to sort them now to make sure they fire in correct
   // order
   subs.sort((a, b) => a.id - b.id) // 根据watcher的id进行排序
  }
  for (let i = 0, l = subs.length; i < l; i++) {
   subs[i].update() // 遍历subs数组中的每一个watcher执行update方法
  }
 }
}

// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null // Dep.target 代表当前全局的watcher
const targetStack = []

export function pushTarget (target: ?Watcher) {
 targetStack.push(target)
 Dep.target = target // 赋值
}

export function popTarget () {
 targetStack.pop()
 Dep.target = targetStack[targetStack.length - 1] // 赋值
}

Dep的定义还是非常清晰的,代码注释如上,很明显Dep跟Watcher就跟捆绑销售一样,互相依赖。我们在分析denfineReactive的时候,在对数据进行响应式操作的时候,通过Object.defineProperty重写了getter函数。

Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
   const value = getter ? getter.call(obj) : val
   if (Dep.target) { // 当前的全部的Watcher实例
    dep.depend() // 把当前的Dep.target加入到dep.subs数组中
    // ..
   }
   return value
  },

其中的dep.depend()实际上就是执行了Dep.target.addDep(this),this指向Dep实例,而Dep.target是一个Watcher实例,即执行watcher.addDep(this)函数。我们接下来在看看这个函数做了什么:

class Watcher {
	addDep (dep: Dep) {
   const id = dep.id
   if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id)
    this.newDeps.push(dep) //
    if (!this.depIds.has(id)) {
     dep.addSub(this) // 会把watcher插入到dep.subs数组中
    }
   }
 }
}

可以通过下图以便理解data、Dep、Watcher的关系:

回到代码中,其中dep.addSub(this)就是会把当前的wathcer实例插入到dep.subs的数组中,为之后的派发更新做好准备,这样依赖收集就完成了。但是到现在为止,我们只分析了依赖收集是怎么实现的,但是依赖收集的时机又是在什么时候呢?什么时候会触发getter函数进而实现依赖收集的?在进行依赖收集的时候,Dep.tagrget对应wathcer又是什么呢?

Watcher大致可以分为三类: * 渲染Watcher: 每一个实例对应唯一的一个(有且只有一个) * computed Watcher: 每一个实例可以有多个,由computed属性生成的(computed有多少个keyy,实例就有多少个computedWatcher) * user Watcher: 每一个实例可以有多个,由watch属性生成的(同computed一样,userWatcher的数量由key数量决定) 为避免混淆,我们接下来说的Watcher都是渲染Watcher。我们知道在Vue初始化的过程中,在执行mountComponent函数的时候,会执行new Watcher(vm, updateComponent, {}, true),这里的Watcher就是渲染Watcher

class Wachter {
	get () {
   pushTarget(this) // Dep.target = this
   let value
   const vm = this.vm
   try {
    value = this.getter.call(vm, vm) // 更新视图
   } catch (e) {
    if (this.user) {
     handleError(e, vm, `getter for watcher "${this.expression}"`)
    } else {
     throw e
    }
   } finally {
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    if (this.deep) {
     traverse(value)
    }
    popTarget()
    this.cleanupDeps()
   }
   return value
  }
}

new Watcher对于渲染watcher而言,会直接执行this.get()方法,然后执行pushTarget(this),所以当前的Dep.target为渲染watcher(用于更新视图)。 而在我们执行this.getter的时候,会调用render函数,此时会读取vm实例上的data数据,这个时候就触发了getter函数了,从而进行了依赖收集,这就是依赖收集的时机,比如

{{ message }} // 会读取vm._data.message, 触发getters函数

派发更新

我们继续来看defineReactive函数里

export function defineReactive (
 obj: Object,
 key: string,
 val: any,
 customSetter?: ?Function,
 shallow?: boolean
) {
 const dep = new Dep()
	// ..
 Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
   // ..
  },
  set: function reactiveSetter (newVal) {
   /* 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()
   }
   // #7981: for accessor properties without setter
   if (getter && !setter) return
   if (setter) {
    setter.call(obj, newVal)https://cn.vuejs.org//images/data.png
   } else {
    val = newVal
   }
   childOb = !shallow && observe(newVal)
   dep.notify() // 遍历dep.subs数组,取出所有的wathcer执行update操作
  }
 })
}

当我们修改数据的时候,会触发setter函数,这个时候会执行dep.notify,dep.subs中所有的watcher都会执行update方法,对于渲染Watcher而言,就是执行this.get()方法,及更新视图。这样一来,就实现了数据驱动。 到这里,Vue的数据驱动原理我们就分析完了,如果还对这个流程不大清楚的,可以结合参考官方给的图解:

总结

  1. 通过Object.defineProperty函数改写了数据的getter和setter函数,来实现依赖收集和派发更新。
  2. 一个key值对应一个Dep实例,一个Dep实例可以包含多个Watcher,一个Wathcer也可以包含多个Dep。
  3. Dep用于依赖的收集与管理,并通知对应的Watcher执行相应的操作。
  4. 依赖收集的时机是在执行render方法的时候,读取vm上的数据,触发getter函数。而派发更新即在变更数据的时候,触发setter函数,通过dep.notify(),通知到所收集的watcher,执行相应操作。

以上就是详解Vue数据驱动原理的详细内容,更多关于Vue数据驱动原理的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解VueJS 数据驱动和依赖追踪分析

    之前关于 Vue 数据绑定原理的一点分析,最近需要回顾,就顺便发到随笔上了 在之前实现一个自己的Mvvm中,用 setter 来观测model,将界面上所有的 viewModel 绑定到 model 上. 当model改变,更新所有的viewModel,将新值渲染到界面上 .同时监听界面上通过v-model 绑定的所有 input,并通过 addEventListener事件将新值更新到 model 上,以此来完成双向绑定 . 但是那段程序除了用来理解 defineProperty,其它一文不值

  • Vue数据驱动模拟实现3

    一.前言 在"模拟Vue之数据驱动2"中,我们实现了个Observer构造函数,通过它可以达到监听已有数据data中的所有属性. 但,倘若我们想在某个对象中,新增某个属性呢? 如下: 那么岂不是,新增的infor属性,以及它的对象属性,没有得到监听. 此时,应该怎么处理呢? 通过走读Vue源码,发现他是采用另增属性方法$set实现的. 就是说,如果我们采用常规方法为对象增加属性(如上),我们没法得知并监控它,所以,我们为每个对象扩展一个$set方法,用于另增属性使用,即可,如下: da

  • Vue数据驱动模拟实现1

    一.前言 Vue有一核心就是数据驱动(Data Driven),允许我们采用简洁的模板语法来声明式的将数据渲染进DOM,且数据与DOM是绑定在一起的,这样当我们改变Vue实例的数据时,对应的DOM元素也就会改变了. 如下: <!DOCTYPE html> <head> <meta charset="utf-8"> </head> <body> <div id="test"> {{name}} &

  • Vue数据驱动模拟实现5

    一.前言 在"模拟Vue之数据驱动4"中,我们实现了push.pop等数组变异方法. 但是,在随笔末尾我们提到,当pop.sort这些方法触发后,该怎么办呢?因为其实,它们并没有往数组中新增属性呢. 而且,当数据改动后,如果我们在变动数据处,就立即更改数据也未免性能不够,此时,走读Vue源码,发现他用了一个很巧妙的方法,就是职责链模式.当某个数据有所变动时,它会向上传递,通俗点就是冒泡至根结点,这样我们也可以在自己代码中使用事件代理咯,哇卡哇卡. 示意图如下所示: 好了,说了这么多,我

  • Vue数据驱动模拟实现4

    一.前言 在"模拟Vue之数据驱动3"中,我们实现了为每个对象扩展一个$set方法,用于新增属性使用,这样就可以监听新增的属性了. 当然,数组也是对象,也可以通过$set方法实现新增属性. 但是,对于数组而言,通常我们是通过push之类的方法吧. PS:Vue中明确指出push.pop.shift.unshift.splice.sort.reverse方法为变异方法,可以通过它们监听属性变化,触发视图更新(详情见here) 下面,我们就一起来实现这些Array的变异方法吧. 注:我们将

  • 浅谈vuejs实现数据驱动视图原理

    什么是数据驱动 数据驱动是vuejs最大的特点.在vuejs中,所谓的数据驱动就是当数据发生变化的时候,用户界面发生相应的变化,开发者不需要手动的去修改dom. 比如说我们点击一个button,需要元素的文本进行是和否的切换.在jquery刀耕火种的年代中,对于页面的修改我们一般是这样的一个流程,我们对button绑定事件,然后获取文案对应的元素dom对象,然后根据切换修改该dom对象的文案值. 而对于vuejs实现这个功能的流程,只需要在button元素上指明事件,同时声明对应文案的属性,点击

  • Vue数据驱动表单渲染,轻松搞定form表单

    form-create 具有动态渲染.数据收集.校验和提交功能的表单生成器,支持双向数据绑定.事件扩展以及自定义组件,可快速生成包含有省市区三级联动.时间选择.日期选择等17种功能组件. Github| 文档 form-create 是基于 Vue开发的开源项目,可快速生成 iviewUI 的表单元素.目的是节省开发人员在表单页面上耗费的时间,从而更专注于功能开发.使用 form-creae 可快速.便捷的生成日常开发中所需的各种表单. 下面向大家介绍一下 form-create 使用方法和生成

  • Vue数据驱动模拟实现2

    一.前言 在随笔"模拟Vue之数据驱动1"结尾处,我们说到如果监听的属性是个对象呢?那么这个对象中的其他属性岂不就是监听不了了吗? 如下: 倘若user中的name.age属性变化,如何知道它们变化了呢? 今儿,就来解决这一问题. 通过走读Vue源码,发现他是利用Observer构造函数为每个对象创建一个Observer对象,来监听数据的,如果数据中的属性又是一个对象,那么就又通过Observer来监听嘛. 其实,核心思想就是树的先序遍历(关于树,可参考here).如我们将上述Demo

  • 详解Vue数据驱动原理

    前言 Vue区别于传统的JS库,例如JQuery,其中一个最大的特点就是不用手动去操作DOM,只需要对数据进行变更之后,视图也会随之更新. 比如你想修改div#app里的内容: /// JQuery <div id="app"></div> <script> $('#app').text('lxb') </script> <template> <div id="app">{{ message }

  • 详解Vue中的MVVM原理和实现方法

    下面由我阿巴阿巴的详细走一遍Vue中MVVM原理的实现,这篇文章大家可以学习到: 1.Vue数据双向绑定核心代码模块以及实现原理 2.订阅者-发布者模式是如何做到让数据驱动视图.视图驱动数据再驱动视图 3.如何对元素节点上的指令进行解析并且关联订阅者实现视图更新 一.思路整理 实现的流程图: 我们要实现一个类MVVM简单版本的Vue框架,就需要实现一下几点: 1.实现一个数据监听Observer,对数据对象的所有属性进行监听,数据发生变化可以获取到最新值通知订阅者. 2.实现一个解析器Compi

  • 详解Vue的异步更新实现原理

    最近面试总是会被问到这么一个问题:在使用vue的时候,将for循环中声明的变量i从1增加到100,然后将i展示到页面上,页面上的i是从1跳到100,还是会怎样?答案当然是只会显示100,并不会有跳转的过程. 怎么可以让页面上有从1到100显示的过程呢,就是用setTimeout或者Promise.then等方法去模拟. 讲道理,如果不在vue里,单独运行这段程序的话,输出一定是从1到100,但是为什么在vue中就不一样了呢? for(let i=1; i<=100; i++){ console.

  • 详解vue的数据binding绑定原理

    自从angular火了以后,各种mvc框架喷涌而出,angular虽然比较火,但是他的坑还是蛮多的,还有许多性能问题被人们吐槽.比如坑爹的脏检查机制,数据binding是受人喜爱的,脏检查就有点-性能低下了.有时候改了一个地方,脏循环要循环多次来保证数据是不是真的变了和是否停止变化了.这样性能就很低了.于是人们开始钻研新的双向数据binding的方法.尤大的vue binding就是本人蛮喜欢的一种实现方式,本文跟随尤大的一个例子来详解vue的数据binding的原理. 数据binding,一般

  • 详解vue 组件的实现原理

    组件机制的设计,可以让开发者把一个复杂的应用分割成一个个功能独立组件,降低开发的难度的同时,也提供了极好的复用性和可维护性.本文我们一起从源码的角度,了解一下组件的底层实现原理. 组件注册时做了什么? 在Vue中使用组件,要做的第一步就是注册.Vue提供了全局注册和局部注册两种方式. 全局注册方式如下: Vue.component('my-component-name', { /* ... */ }) 局部注册方式如下: var ComponentA = { /* ... */ } new Vu

  • 详解vue的hash跳转原理

    在new vueRouter的时候我们可以传入一个mode属性,他可以接收三个值:hash/history/abstract hash和history的区别 history的路径更美观一点 比如http://yoursite.com/user/id,history是基于pushState()来完成 URL 跳转而无须重新加载页面. 但是强制刷新还是会有问题(服务端来解决这个问题),所以history模式需要后端人员配合使用. hash的路径会带有#,比如http://yoursite.com#/

  • 详解vue computed的缓存实现原理

    目录 初始化 computed 依赖收集 派发更新 总结一下 本文围绕下面这个例子,讲解一下computed初始化及更新时的流程,来看看计算属性是怎么实现的缓存,及依赖是怎么被收集的. <div id="app"> <span @click="change">{{sum}}</span> </div> <script src="./vue2.6.js"></script> &

  • 详解Vue中的watch和computed

    前言 对于使用Vue的前端而言,watch.computed和methods三个属性相信是不陌生的,是日常开发中经常使用的属性.但是对于它们的区别及使用场景,又是否清楚,本文我将跟大家一起通过源码来分析这三者的背后实现原理,更进一步地理解它们所代表的含义. 在继续阅读本文之前,希望你已经具备了一定的Vue使用经验,如果想学习Vue相关知识,请移步至官网. Watch 我们先来找到watch的初始化的代码,/src/core/instance/state.js export function in

  • 详解Vue Cli浏览器兼容性实践

    浏览器市场占有率 在处理浏览器兼容性问题之前,我们先来看一下现在的浏览器市场份额是怎样的,

  • 详解vue高级特性

    Vue为我们提供了很多高级特性,学习和掌握它们有助于提高你的代码水平. 一.watch进阶 从我们刚开始学习Vue的时候,对于侦听属性,都是简单地如下面一般使用: watch:{ a(){ //doSomething } } 实际上,Vue对watch提供了很多进阶用法. handler函数 以对象和handler函数的方式来定义一个监听属性,handler就是处理监听变动时的函数: watch:{ a:{ handler:'doSomething' } }, methods:{ doSomet

随机推荐