前言
最近这段时间在学习Vue数据驱动的实现原理,看过网上很多大牛写的关于Vue数据驱动的文章,发现讲的都很抽象,不适合小白阅读,今天我就从小白的角度出发,详细的分析一下Vue数据驱动的实现原理,希望大家看过以后可以更好的理解Vue数据驱动。
在正式开始之前,先给大家讲一个小故事。大家都知道侦察兵吧--监听目标敌人的变化,及时将敌情上报,方便我方做出正确的战略响应,可以说是十分重要了。那既然侦查兵这么重要,在编程界,我们该怎么成为一个合格的侦查兵呢?Vue数据驱动的实现方式,可以告诉你答案。这里可以先透露一下,Vue数据驱动的实现是建立在观察者模式的基础之上的,看过这篇文章以后,你不仅可以学到Vue数据驱动的实现原理,而且可以熟练的掌握编程中经常用到的观察者模式,成为一个编程界合格的侦察兵,将不在是梦想!
通过这篇文章你将学到什么?
1.Vue数据驱动的实现原理
2.观察者模式
3.Vue中computed与watch的区别
响应式编程
Vue框架的一个核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不需要直接操作DOM,而可以通过修改预先设定好的数据达到修改视图的目的。 怎么去理解这句话呢?相信用过Vue框架的人都知道,在定义一个Vue组件的时候,我们会给一个组件定义data、props、computed、watch属性,在需要改变页面视图时,我们只需要更改data、props、computed、watch中对应的变量值就可以了,Vue框架侦听变量的变化,并对页面视图做出修改,这样可以让我们很从容的应对大量的视图更新。今天这篇文章要讲的就是Vue实现数据驱动的原理,我们先来看一张vue官方教程对数据驱动的总结图。 这张图向我们展示了 Vue数据驱动的中心思想,这张图可以分为三个部分--渲染部分(黄色)、watcher订阅者(蓝色)、Data发布者(紫色),下面我们来简单介绍一下每个模块的作用
Render渲染函数
图中的黄色部分是Vue的渲染方法,想了解Vue具体渲染过程的可以查看juejin.cn/post/691679…, 现在我们只需知道,渲染函数可以将Vue模版转换为DOM,显示页面视图就可以了。
Data发布者
图中的紫色部分为Data发布者,在执行Vue渲染函数的过程当中,不可避免的要从Data对象中取值,这样就会触发当前变量的getter方法,在getter方法中会收集当前变量的订阅者,也就是Watcher。
Watcher观察者
当Data对象中的变量有变化时,会触发当前对象中的setter方法,setter方法会通知getter方法收集的订阅者当前变量发生的变化,重新触发渲染函数,更新页面视图。
简言之,数据驱动的中心思想,是通过重写Data中数据的get和set属性方法,让数据在被渲染时把所有用到自己的订阅者存放在自己的订阅者列表中,当数据发生变化时将该变化通知到所有订阅了自己的订阅者,达到重新渲染的目的。 下面我们来总结一下数据驱动的整个过程:
第一步:执行initData()函数初始化Data对象中的变量。
第二步:给Data对象中的变量对添加getter和setter属性,getter属性负责添加变量的订阅者,setter通知订阅者。
第三步:执行渲染函数,将template模版和Data数据转换为真实的DOM元素。
第四步:在执行渲染函数的过程当中,需要获取Data中的数据,这时会触发当前变量的getter方法,getter方法会收集当前变量变化时的订阅者(也就是渲染函数,变量值变化时重新渲染)
第五步:更新视图,呈现完整的前端页面给用户。
第六步:如果此时为了响应用户需求,修改了Data对象中某个变量的值,会触发该变量的setter方法,setter方法会把该变化通知给渲染函数(观察者),并重新触发页面渲染。
第七步:更新视图,呈现更新后的前端页面给用户。
通过对Vue数据驱动整个过程的解析可以发现,整个Vue数据驱动的实现是通过观察者模式实现的,下面我们来简单了解一下观察者模式,加深一下对Vue数据驱动实现的理解,也方便后续的源码分析。
观察者模式
观察者模式的原理
对于观察者模式的字面理解是--A对象(观察者)需要对B对象(被观察者)的每个变化都做出响应。举个例子,人们就根据天气的变化做出不同的反应,下雨天,人们会打雨伞、穿雨衣;飓风天气,大家会选择呆在家里面。这个例子中天气可以作为被观察者,而我们人类就是观察者,会根据天气的变化做出不同的响应,可以看到在观察者模式里,有两个重要的对象,一个是被观察者(也称为发布者),一个是观察者(也称为订阅者),观察者模式也可以称为发布-订阅模式。
通过上面的例子我们知道观察者模式有两个重要的元素--发布者与订阅者。发布者(也就是天气)负责在天气变化时,将这种变化通知给人类,而订阅者(人类、动物等)则在收到这种变化后,做出具体的响应。这个过程当中,还有一个很重要的问题,那就是天气变化时,需要将这种变化通知给谁呢?人类和动物需要,建筑物、河流不需要。这就需要提前在天气(发布者)那里提前注册需要好订阅者,好让发布者知道该去通知谁。
观察者模式的编程实现
在了解了观察者模式的原理以后,我们来看一下在编程中怎么实现观察者模式。结合我们刚才的原理分析,我们知道需要两个接口,一个Subject接口来定义发布者,发布者需要具备三个基础方法--注册订阅者、删除订阅者和通知订阅者。一个是Observer接口来定义观察者,观察者需要具备响应变化的能力,我们可以根据自己的需求去实现不同的观察者,来应对千变万化的需求。
总结一下观察者模式的三要素:
1.发布者(被观察者)
2.订阅者(观察者)
3.发布和订阅的时机
学习了观察模式以后,我们能够更好的了解Vue数据驱动的原理,下面就让我们来揭开Vue数据驱动的神秘面纱,一睹为快!
vue如何实现响应式
通过上面章节对Vue数据驱动的流程的分析,结合观察者模式,我们可以将整个数据驱动的实现大致可以分为三个步骤:
1.生成响应式对象,在这个过程当中会为data中的每个变量创建发布者(Dep)和订阅者(Watcher)。
2.依赖收集,发布者收集订阅者的过程。
3.派发更新,当被发布者监控的变量有变化时,发布者会通知订阅者做出反应。
生成响应式对象
所谓的响应式对象,就是给data对象中的每一个变量添加getter和setter属性,当我们访问某个变量的时候会触发getter方法,当我们对该属性做修改的时候会触发setter方法。这样当我们第一次访问data中的变量时,就可以收集依赖data对象的观察者,当我们再次访问它时,就可以通知观察者及时做出响应。
Vue是通过ES5的Object.defineProperty()方法给变量添加getter和setter方法的,先来看一下它的语法:
Object.defineProperty(obj, prop, descriptor)
obj 是要在其上定义属性的对象;
prop 是要定义或修改的属性的名称;
descriptor 是将被定义或修改的属性描述符。
比较核心的是descriptor,它有很多可选键值,具体的可以去参阅Object.defineProperty的文档developer.mozilla.org/zh-CN/docs/… 。这里我们最关心的是get和set,get是一个给属性提供的getter方法,当我们访问了该属性的时候会触发getter方法;set是一个给属性提供的setter方法,当我们对该属性做修改的时候会触发setter方法。 一旦对象拥有了getter和setter,我们可以简单地把这个对象称为响应式对象。那么Vue把哪些对象变成了响应式对象了呢,我们从源码层面来分析。
我们知道,在Vue框架进行初始化的时候,会执行initState()函数
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
可以看到,在初始化阶段,Vue对props、methods、data、computed和wathcer等属性做了初始化操作。由此可知props、methods、data、computed和wathcer都是响应式对象。
下面我们以data对象的初始化为🌰,来介绍一下Vue生成响应式对象的整个过程,我们知道data对象的初始化会首先执行initData()函数。
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
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)
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)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, 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, true /* asRootData */) // 第二步
}
可以看到,生成响应式对象的整个过程主要做了两件事:
第一,通过proxy()方法把每一个值vm._data.xxx都代理到vm.xxx上,这也是我们通过vm.xxx直接访问vm._data.xxx变量的原因。
第二,通过defineReactive()方法将data中的变量变成响应式的。defineReactive()是真正为数据添加get和set属性方法的方法,它将 data中的数据定义一个响应式对象,并给该对象设置get和set属性方法,其中get方法是对依赖进行收集,set方法是当数据改变时通知 Watcher派发更新。
下面我们来看一下具体实现:
第一,通过proxy把每一个值vm._data.xxx都代理到vm.xxx上,这也是我们通过vm.xxx直接访问vm._data.xxx变量的原因。结合源码来看,proxy方法的实现很简单,通过Object.defineProperty把target[sourceKey][key]的读写变成了对target[key] 的读写。当我们调用proxy的时候,target为vm,也就是this对象、sourceKey为_data、key为具体的变量xxx。翻译过来,当我们访问this.xxx时就是访问this._data.xxx。
proxy(vm, `_data`, key) // 第一步,调用proxy()函数
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)
}
第二,通过defineReactive()方法将data中的变量变成响应式的。可以看到data中的每一个变量都会为其创建一个Observer对象(见注释一),下面我们来看一下Observer的实现。
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value) // 注释一,可以看到data中的每一个变量都会为其创建一个Observer对象,实现将其转化为响应式对象的目的
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
Observer的构造函数逻辑很简单,会对value的类型进行判断,可以看到,无论value是数组与否,最终的逻辑都会走到defineReactive()函数(见注释一),下面我们来看一下defineReactive()。
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys) // 注释一,如果value是数组,调用this.observeArray()
this.observeArray(value)
} else {
this.walk(value) // 注释一,知道value不再是数组,开始调用walk()
}
}
/**
* Walk through each property 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]) // 注释一,最终调用defineReactive()
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) { // 注释一,递归调用observe()
observe(items[i])
}
}
}
/**
* Define a property.
*/
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
defineReactive函数最开始初始化Dep对象的实例,首先实例化Dep对象(注释一),Dep对象就是data中变量的订阅者,实现对data中变量观察者的注册和变化通知。后面我们详细介绍Dep对象的实现,接着我们看到defineReactive通过defineProperty为data中的变量都添加了getter和setter方法,将变量变成了响应式对象,到此data中的变量变成了响应式对象。
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new 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) {
val = obj[key]
}
let childOb = !shallow && 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 = !shallow && observe(newVal)
dep.notify()
}
})
}
vue中的发布者-Dep
我们先来看下Dep类的实现,可以看到,Dep类里面定义了一个subs数组,用来存储当前变量的观察者:
Dep类重要的三个方法介绍:
1.removeSub()删除依赖
2.addSub()添加依赖
3.notify()通知依赖
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this) // Dep.target是当前全局唯一的订阅者,这是因为同一时间只允许一个订阅者被处理。
}
}
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Dep.target = null
const targetStack = []
export function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
vue中的观察者-Watcher
下面我们来看一下Watcher类,也就是变量的观察者:
Watcher类重要的两个方法介绍:
1.update()触发watcher的回调函数
2.get()执行watcher的回调函数
3.触发和执行回调函数之间有一些watcher的排序和优化逻辑
export default class Watcher {
// ...
get () {
pushTarget(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
}
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)
}
}
}
update () {
if (this.computed) {
if (this.dep.subs.length === 0) {
this.dirty = true
} else {
// In activated mode, we want to proactively perform the computation
// but only notify our subscribers when the value has indeed changed.
this.getAndInvoke(() => {
this.dep.notify()
})
}
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
}
依赖收集--发布时机
此处涉及到了两个重要的部分——依赖到底是什么?依赖要存放在哪里?
依赖就是Watcher类(观察者),Dep类就是我们的发布者(被观察者),存储依赖。
触发响应式对象getter和setter方法的时机就是发布和订阅的时机。分别对应依赖收集和更新派发
依赖收集的原理是:当视图被渲染时,会触发渲染中所使用到的数据的get属性方法,通过get方法进行依赖收集。
Vue渲染的过程当中会执行mountComponent()函数,可以看到,在执行updateComponent()函数的过程当中会创建一个watcher,传给Watcher的回调函数为-updateComponent()(见注释一),updateComponent()函数会执行组件的渲染生成前端页面,根据函数的功能,所以我们称这类wather为渲染watcher,也就是说当该类watcher被触发时会重新执行页面的渲染过程(见注释二)。
updateComponent = () => {
vm._update(vm._render(), hydrating) // 注释二,执行页面渲染
}
new Watcher(vm, updateComponent, noop, { // 注释一
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
在执行页面的渲染过程当中,不可避免的要从data中取变量,这时就会触发变量(此时已经是响应式对象)的getter()函数,可以看到getter函数的逻辑就是将当前的watcher放入了Dep的subs数组里面,实现了对该变量的订阅,也就会整个依赖收集的过程。
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
}
派发更新
1.当我们修改data对象中变量的值时,就会触发该变量(此时已经是响应式对象)的setter()函数,可以看到setter()方法首先判断新值是否与旧值相等,相等直接返回,否则会执行dep.notify()进行依赖通知(见注释一)。
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 = !shallow && observe(newVal)
dep.notify() // 注释一
}
2.notify主要是遍历subs也就是watcher队列,然后调⽤每⼀个watcher的update()⽅法(见注释一)。
class Dep {
// ...
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() // 注释一
}
}
}
3.update对于watcher的不同状态,会执⾏不同的逻辑,computed和sync状态后面再说,在⼀般组件数据更新的场景,会⾛到最后⼀个queueWatcher(this)(见注释一)。
class Watcher {
// ...
update () {
/* istanbul ignore else */
if (this.computed) {
// A computed property watcher has two modes: lazy and activated.
// It initializes as lazy by default, and only becomes activated when
// it is depended on by at least one subscriber, which is typically
// another computed property or a component's render function.
if (this.dep.subs.length === 0) {
// In lazy mode, we don't want to perform computations until necessary,
// so we simply mark the watcher as dirty. The actual computation is
// performed just-in-time in this.evaluate() when the computed property
// is accessed.
this.dirty = true
} else {
// In activated mode, we want to proactively perform the computation
// but only notify our subscribers when the value has indeed changed.
this.getAndInvoke(() => {
this.dep.notify()
})
}
} else if (this.sync) {
this.run()
} else {
queueWatcher(this) // 注释一
}
}
}
4.queueWatcher首先获取watcher的id,然后利用id进行去重,保证相同的watcher只能添加一次。flushing的初始值为false,此时会将watcher添加到queue中。最后通过wating属性保证对nextTick(flushSchedulerQueue)的调⽤逻辑只有⼀次,nextTick()的原理后面再说,⽬前可以理解它是在下一个事件循环去执行传给它的回调函数,这里也就是异步的去执⾏flushSchedulerQueue(),目的是保证不是每一次更新都去执行回调,优化性能。
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else { // 在执行回调函数的过程中,又插入新的watcher
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
nextTick(flushSchedulerQueue)
}
}
}
5.flushSchedulerQueue()首先设置flushing属性为true。其次对queue中的watcher进行排序,排序理由如下:
1.组件的更新由⽗到⼦;因为⽗组件的创建过程是先于⼦的,所以watcher的创建也是先⽗后⼦,执⾏顺序也应该保持先⽗后⼦。
2.⽤户的⾃定义watcher要优先于渲染watcher执⾏;因为⽤户⾃定义watcher是在渲染watcher之前创建的。
3.如果⼀个组件在⽗组件的watcher执⾏期间被销毁,那么它对应的watcher执⾏都可以被跳过,所以⽗组件的watcher应该先执⾏。
接着就是遍历queue调用watcher的run()函数。这里注意遍历判断条件是取queue.length的,也就是说在遍历的时候每次都会对queue.length求值,因为在 watcher.run() 的时候,很可能⽤户会再次添加新的watcher,这样会再次执⾏到上面的queueWatcher(),而此时flushing属性为true,然后在queue队列中从后往前找,根据watcher的id属性找到它正确的位置插入。
let flushing = false
let index = 0
/**
* Flush both queues and run the watchers.
*/
function flushSchedulerQueue () {
flushing = true
let watcher, id
queue.sort((a, b) => a.id - b.id)
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
watcher.run() // 执行watcher的回调
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? `in watcher with expression "${watcher.expression}"`
: `in a component render function.`
),
watcher.vm
)
break
}
}
}
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
if (devtools && config.devtools) {
devtools.emit('flush')
}
}
6.watcher的run()方法调用了this.get()(见注释一),也就触发了this.getter(),this.getter()最终执行了vm._update(vm._render())进行重新渲染,至此整个更新派发的过程就完成了。
run () {
if (this.active) {
this.getAndInvoke(this.cb)
}
}
getAndInvoke (cb: Function) {
const value = this.get() // 注释一,此处的this指的是watcher,watcher的get函数最终会调用渲染函数执行组件的渲染,实现页面的更新
//...
}
新的watcher--computed和watch属性
我们都知道在vue中,不仅data对象中的变量是响应式的,watch和computed对象中的变量同样是响应式的,其实watch和computed的响应式实现原理也是基于观察者模式的,计算属性的watcher(简称计算watcher)监听了data对象中属性的变化,而渲染watcher监听了计算属性本身的变化,所以当计算属性中监听的值变化时,计算属性本身的值会重新计算更新,而计算属性更新以后又会重新触发页面渲染,相当于是双重监听。
而watch属性也很简单,就是watch属性的watcher(简称用户watcher)监听了data中属性的变化,其本身就是data中属性的监听者,当其监听data中的属性发生变化时就会触发对象的回调函数,其跟渲染watcher是平级的,可以理解为渲染watcher监听到data中属性变化时会触发页面更新,而用户watcher监听到data中的属性发生变化时会执行用户自身定义好的回调函数。
由于篇幅有限,本文不再对watch和computed的具体实现进行分析,有兴趣的同学可以戳caibaojian.com/vue-analysi… ,自行了解。
总结
Vue响应式编程就是基于观察者模式实现的,明白这一点就可以很轻松的搞明白它的原理了!