入理解Vue響應式原理

   最近一段時間在閱讀Vue源碼缨伊,從它的核心原理入手蜂大,開始了源碼的學習婴程,而其核心原理就是其數(shù)據(jù)的響應式鲤氢,講到Vue的響應式原理飘痛,我們可以從它的兼容性說起晚胡,Vue不支持IE8以下版本的瀏覽器灵奖,因為Vue是基于 [Object.defineProperty](https://funteas.com/go/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FObject%2FdefineProperty) 來實現(xiàn)數(shù)據(jù)響應的,而 Object.defineProperty 是 ES5 中一個無法 shim 的特性估盘,這也就是為什么 Vue 不支持 IE8 以及更低版本瀏覽器的原因瓷患;Vue通過Object.defineProperty的 **getter/setter** 對收集的依賴項進行監(jiān)聽,在屬性被訪問和修改時通知變化,進而更新視圖數(shù)據(jù);

受現(xiàn)代JavaScript 的限制 (以及廢棄 Object.observe)遣妥,Vue不能檢測到對象屬性的添加或刪除擅编。由于 Vue 會在初始化實例時對屬性執(zhí)行 getter/setter 轉(zhuǎn)化過程,所以屬性必須在 data 對象上存在才能讓Vue轉(zhuǎn)換它,這樣才能讓它是響應的爱态。 <a id=“more”></a>

vue響應式

我們這里是根據(jù)Vue2.3源碼進行分析,Vue數(shù)據(jù)響應式變化主要涉及 Observer, Watcher , Dep 這三個主要的類谭贪;因此要弄清Vue響應式變化需要明白這個三個類之間是如何運作聯(lián)系的;以及它們的原理锦担,負責的邏輯操作俭识。那么我們從一個簡單的Vue實例的代碼來分析Vue的響應式原理

var vue = new Vue({
el: "#app",
data: {
name: 'Junga'
},
created () {
this.helloWorld()
},
methods: {
helloWorld: function() {
console.log('my name is' + this.name)
}
}
...
})


# [](https://funteas.com/go/?target=%23Vue%25E5%2588%259D%25E5%25A7%258B%25E5%258C%2596%25E5%25AE%259E%25E4%25BE%258B "Vue初始化實例")Vue初始化實例

根據(jù)Vue的[生命周期](https://funteas.com/go/?target=https%3A%2F%2Fcn.vuejs.org%2Fv2%2Fguide%2Finstance.html%23%E5%AE%9E%E4%BE%8B%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%92%A9%E5%AD%90)我們知道,Vue首先會進行init初始化操作洞渔;源碼在[src/core/instance/init.js](https://funteas.com/go/?target=https%3A%2F%2Fgithub.com%2Fhuangzhuangjia%2FVue-learn%2Fblob%2Fmaster%2Fcore%2Finstance%2Finit.js)中

/初始化生命周期/
initLifecycle(vm)
/初始化事件/
initEvents(vm)Object.defineProperty
/初始化render/
initRender(vm)
/調(diào)用beforeCreate鉤子函數(shù)并且觸發(fā)beforeCreate鉤子事件/
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
/初始化props套媚、methods、data痘煤、computed與watch/
initState(vm)
initProvide(vm) // resolve provide after data/props
/調(diào)用created鉤子函數(shù)并且觸發(fā)created鉤子事件/
callHook(vm, 'created')


以上代碼可以看到 **initState(vm)** 是用來初始化props,methods,data,computed和watch;

[src/core/instance/state.js](https://funteas.com/go/?target=https%3A%2F%2Fgithub.com%2Fhuangzhuangjia%2FVue-learn%2Fblob%2Fmaster%2Fcore%2Finstance%2Fstate.js)

/初始化props、methods猿规、data衷快、computed與watch/
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.options /*初始化props*/ if (opts.props) initProps(vm, opts.props) /*初始化方法*/ if (opts.methods) initMethods(vm, opts.methods) /*初始化data*/ if (opts.data) { initData(vm) } else { /*該組件沒有data的時候綁定一個空對象*/ observe(vm._data = {}, true /* asRootData */) } /*初始化computed*/ if (opts.computed) initComputed(vm, opts.computed) /*初始化watchers*/ if (opts.watch) initWatch(vm, opts.watch) } ... /*初始化data*/ function initData (vm: Component) { /*得到data數(shù)據(jù)*/ let data = vm.options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}defi
...
//遍歷data中的數(shù)據(jù)
while (i--) {
/保證data中的key不與props中的key重復,props優(yōu)先姨俩,如果有沖突會產(chǎn)生warning/
if (props && hasOwn(props, keys[i])) {
process.env.NODE_ENV !== 'production' && warn(
The data property "${keys[i]}" is already declared as a prop. +
Use prop default value instead.,
vm
)
} else if (!isReserved(keys[i])) {
/判斷是否是保留字段/
/這里是我們前面講過的代理蘸拔,將data上面的屬性代理到了vm實例上/
proxy(vm, _data, keys[i])
}
}
// observe data
/這里通過observe實例化Observe對象,開始對數(shù)據(jù)進行綁定环葵,asRootData用來根數(shù)據(jù)调窍,用來計算實例化根數(shù)據(jù)的個數(shù),下面會進行遞歸observe進行對深層對象的綁定张遭。則asRootData為非true/
observe(data, true /* asRootData */)
}


## [](https://funteas.com/go/?target=%231%25E3%2580%2581initData "1邓萨、initData")1、initData

現(xiàn)在我們重點分析下**initData**菊卷,這里主要做了兩件事缔恳,一是將_data上面的數(shù)據(jù)代理到vm上,二是通過執(zhí)行 observe(data, true / *asRootData* /)將所有data變成可觀察的洁闰,即對data定義的每個屬性進行g(shù)etter/setter操作歉甚,這里就是Vue實現(xiàn)響應式的基礎;**observe**的實現(xiàn)如下 [src/core/observer/index.js](https://funteas.com/go/?target=https%3A%2F%2Fgithub.com%2Fhuangzhuangjia%2FVue-learn%2Fblob%2Fmaster%2Fcore%2Fobserver%2Findex.js)

/嘗試創(chuàng)建一個Observer實例(ob)扑眉,如果成功創(chuàng)建Observer實例則返回新的Observer實例纸泄,如果已有Observer實例則返回現(xiàn)有的Observer實例。/
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value)) {
return
}
let ob: Observer | void
/這里用ob這個屬性來判斷是否已經(jīng)有Observer實例腰素,如果沒有Observer實例則會新建一個Observer實例并賦值給ob這個屬性聘裁,如果已有Observer實例則直接返回該Observer實例,這里可以看Observer實例化的代碼def(value, 'ob', this)/
if (hasOwn(value, 'ob') && value.ob instanceof Observer) {
ob = value.ob
} else if (
/這里的判斷是為了確保value是單純的對象弓千,而不是函數(shù)或者是Regexp等情況咧虎。而且該對象在shouldConvert的時候才會進行Observer。這是一個標識位计呈,避免重復對value進行Observer
/
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
/
如果是根數(shù)據(jù)則計數(shù)砰诵,后面Observer中的observe的asRootData非true
/
ob.vmCount++
}
return ob
}


這里 **new Observer(value)** 就是實現(xiàn)響應式的核心方法之一了征唬,通過它將data轉(zhuǎn)變可以成觀察的,而這里正是我們開頭說的茁彭,用了 **Object.defineProperty** 實現(xiàn)了data的 **getter/setter** 操作总寒,通過 **Watcher** 來觀察數(shù)據(jù)的變化,進而更新到視圖中理肺。

## [](https://funteas.com/go/?target=%232%25E3%2580%2581Observer "2摄闸、Observer")2、Observer

Observer類是將每個目標對象(即data)的鍵值轉(zhuǎn)換成getter/setter形式妹萨,用于進行依賴收集以及調(diào)度更新年枕。

[src/core/observer/index.js](https://funteas.com/go/?target=https%3A%2F%2Fgithub.com%2Fhuangzhuangjia%2FVue-learn%2Fblob%2Fmaster%2Fcore%2Fobserver%2Findex.js)

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
/* 將Observer實例綁定到data的ob屬性上面去,之前說過observe的時候會先檢測是否已經(jīng)有ob對象存放Observer實例了乎完,def方法定義可以參考/src/core/util/lang.js/
def(value, 'ob', this)
if (Array.isArray(value)) {
/
如果是數(shù)組熏兄,將修改后可以截獲響應的數(shù)組方法替換掉該數(shù)組的原型中的原生方法,達到監(jiān)聽數(shù)組數(shù)據(jù)變化響應的效果树姨。這里如果當前瀏覽器支持proto屬性摩桶,則直接覆蓋當前數(shù)組對象原型上的原生數(shù)組方法,如果不支持該屬性帽揪,則直接覆蓋數(shù)組對象的原型硝清。/
const augment = hasProto
? protoAugment /
直接覆蓋原型的方法來修改目標對象/
: copyAugment /
定義(覆蓋)目標對象或數(shù)組的某一個方法/
augment(value, arrayMethods, arrayKeys)
/
如果是數(shù)組則需要遍歷數(shù)組的每一個成員進行observe/
this.observeArray(value)
} else {
/
如果是對象則直接walk進行綁定/
this.walk(value)
},
walk (obj: Object) {
const keys = Object.keys(obj)
/
walk方法會遍歷對象的每一個屬性進行defineReactive綁定*/
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
}


1.  首先將Observer實例綁定到data的**ob**屬性上面去,防止重復綁定转晰;
2.  若data為數(shù)組芦拿,先實現(xiàn)對應的[變異方法](https://funteas.com/go/?target=https%3A%2F%2Fcn.vuejs.org%2Fv2%2Fguide%2Flist.html%23%E5%8F%98%E5%BC%82%E6%96%B9%E6%B3%95)(這里變異方法是指Vue重寫了數(shù)組的7種原生方法,這里不做贅述查邢,后續(xù)再說明)防嗡,再將數(shù)組的每個成員進行observe,使之成響應式數(shù)據(jù)侠坎;
3.  否則執(zhí)行walk()方法蚁趁,遍歷data所有的數(shù)據(jù),進行g(shù)etter/setter綁定实胸,這里的核心方法就是 **defineReative(obj, keys[i], obj[keys[i]])**

export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: Function
) {
/在閉包中定義一個dep對象/
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
/如果之前該對象已經(jīng)預設了getter以及setter函數(shù)則將其取出來他嫡,新定義的getter/setter中會將其執(zhí)行,保證不會覆蓋之前已經(jīng)定義的getter/setter庐完。/
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
/對象的子對象遞歸進行observe并返回子節(jié)點的Observer對象/
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
/如果原本對象擁有g(shù)etter方法則執(zhí)行/
const value = getter ? getter.call(obj) : val
if (Dep.target) {
/進行依賴收集/
dep.depend()
if (childOb) {
/子對象進行依賴收集钢属,其實就是將同一個watcher觀察者實例放進了兩個depend中,一個是正在本身閉包中的depend门躯,另一個是子元素的depend/
childOb.dep.depend()
}
if (Array.isArray(value)) {
/是數(shù)組則需要對每一個成員都進行依賴收集淆党,如果數(shù)組的成員還是數(shù)組,則遞歸。/
dependArray(value)
}
}
return value
},
set: function reactiveSetter (newVal) {
/通過getter方法獲取當前值染乌,與新值進行比較山孔,一致則不需要執(zhí)行下面的操作/
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方法則執(zhí)行setter/
setter.call(obj, newVal)
} else {
val = newVal
}
/
新的值需要重新進行observe,保證數(shù)據(jù)響應式/
childOb = observe(newVal)
/
dep對象通知所有的觀察者*/
dep.notify()
}
})
}


其中g(shù)etter方法:

1.  先為每個data聲明一個 **Dep** 實例對象荷憋,被用于getter時執(zhí)行dep.depend()進行收集相關的依賴;
2.  根據(jù)Dep.target來判斷是否收集依賴台颠,還是普通取值。Dep.target是在什么時候勒庄,如何收集的后面再說明串前,先簡單了解它的作用,

那么問題來了实蔽,我們?yōu)樯兑占嚓P依賴呢荡碾?

new Vue({
template:
<div> <span>text1:</span> {{text1}} <span>text2:</span> {{text2}} <div>,
data: {
text1: 'text1',
text2: 'text2',
text3: 'text3'
}
});


我們可以從以上代碼看出,data中text3并沒有被模板實際用到局装,為了提高代碼執(zhí)行效率坛吁,我們沒有必要對其進行響應式處理,因此贼邓,依賴收集簡單點理解就是收集只在實際頁面中用到的data數(shù)據(jù)阶冈,然后打上標記闷尿,這里就是標記為Dep.target塑径。

在setter方法中:

1.  獲取新的值并且進行observe,保證數(shù)據(jù)響應式填具;
2.  通過dep對象通知所有觀察者去更新數(shù)據(jù)统舀,從而達到響應式效果。

在Observer類中劳景,我們可以看到在getter時誉简,dep會收集相關依賴,即收集依賴的watcher盟广,然后在setter操作時候通過dep去通知watcher,此時watcher就執(zhí)行變化闷串,我們用一張圖描述這三者之間的關系: ![關系圖](http://upload-images.jianshu.io/upload_images/7902592-416a7361b407cb15?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

從圖我們可以簡單理解:Dep可以看做是書店,Watcher就是書店訂閱者筋量,而Observer就是書店的書烹吵,訂閱者在書店訂閱書籍,就可以添加訂閱者信息桨武,一旦有新書就會通過書店給訂閱者發(fā)送消息肋拔。

## [](https://funteas.com/go/?target=%233%25E3%2580%2581Watcher "3、Watcher")3呀酸、Watcher

Watcher是一個觀察者對象凉蜂。依賴收集以后Watcher對象會被保存在Dep的subs中,數(shù)據(jù)變動的時候Dep會通知Watcher實例,然后由Watcher實例回調(diào)cb進行視圖的更新窿吩。

[src/core/observer/watcher.js](https://funteas.com/go/?target=https%3A%2F%2Fgithub.com%2Fhuangzhuangjia%2FVue-learn%2Fblob%2Fmaster%2Fcore%2Fobserver%2Fwatcher.js)

export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
this.vm = vm
/_watchers存放訂閱者實例/
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
/把表達式expOrFn解析成getter/
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
process.env.NODE_ENV !== 'production' && warn(
Failed watching path: "${expOrFn}" +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
/**

  • Evaluate the getter, and re-collect dependencies.
    /
    /
    獲得getter的值并且重新進行依賴收集/
    get () {
    /
    將自身watcher觀察者實例設置給Dep.target茎杂,用以依賴收集。/
    pushTarget(this)
    let value
    const vm = this.vm
    /
    執(zhí)行了getter操作爆存,看似執(zhí)行了渲染操作蛉顽,其實是執(zhí)行了依賴收集。
    在將Dep.target設置為自生觀察者實例以后先较,執(zhí)行g(shù)etter操作携冤。
    譬如說現(xiàn)在的的data中可能有a、b闲勺、c三個數(shù)據(jù)曾棕,getter渲染需要依賴a跟c,
    那么在執(zhí)行g(shù)etter的時候就會觸發(fā)a跟c兩個數(shù)據(jù)的getter函數(shù)菜循,
    在getter函數(shù)中即可判斷Dep.target是否存在然后完成依賴收集翘地,
    將該觀察者對象放入閉包中的Dep的subs中去。/
    if (this.user) {
    try {
    value = this.getter.call(vm, vm)
    } catch (e) {
    handleError(e, vm, getter for watcher "${this.expression}")
    }
    } else {
    value = this.getter.call(vm, vm)
    }
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    /
    如果存在deep癌幕,則觸發(fā)每個深層對象的依賴衙耕,追蹤其變化/
    if (this.deep) {
    /
    遞歸每一個對象或者數(shù)組,觸發(fā)它們的getter勺远,使得對象或數(shù)組的每一個成員都被依賴收集橙喘,形成一個“深(deep)”依賴關系/
    traverse(value)
    }
    /
    將觀察者實例從target棧中取出并設置給Dep.target/
    popTarget()
    this.cleanupDeps()
    return value
    }
    /
    *
  • Add a dependency to this directive.
    /
    /
    添加一個依賴關系到Deps集合中/
    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)
    }
    }
    }
    /
    *
  • Clean up for dependency collection.
    /
    /
    清理依賴收集/
    cleanupDeps () {
    /
    移除所有觀察者對象/
    ...
    }
    /
    *
  • Subscriber interface.
  • Will be called when a dependency changes.
    /
    /

    調(diào)度者接口,當依賴發(fā)生改變的時候進行回調(diào)胶逢。
    /
    update () {
    /
    istanbul ignore else /
    if (this.lazy) {
    this.dirty = true
    } else if (this.sync) {
    /
    同步則執(zhí)行run直接渲染視圖/
    this.run()
    } else {
    /
    異步推送到觀察者隊列中厅瞎,下一個tick時調(diào)用。/
    queueWatcher(this)
    }
    }
    /
    *
  • Scheduler job interface.
  • Will be called by the scheduler.
    /
    /

    調(diào)度者工作接口初坠,將被調(diào)度者回調(diào)和簸。
    /
    run () {
    if (this.active) {
    /
    get操作在獲取value本身也會執(zhí)行g(shù)etter從而調(diào)用update更新視圖 /
    const value = this.get()
    if (
    value !== this.value ||
    // Deep watchers and watchers on Object/Arrays should fire even
    // when the value is the same, because the value may
    // have mutated.
    /

    即便值相同,擁有Deep屬性的觀察者以及在對象/數(shù)組上的觀察者應該被觸發(fā)更新碟刺,因為它們的值可能發(fā)生改變锁保。
    /
    isObject(value) ||
    this.deep
    ) {
    // set new value
    const oldValue = this.value
    /
    設置新的值/
    this.value = value
    /
    觸發(fā)回調(diào)/
    if (this.user) {
    try {
    this.cb.call(this.vm, value, oldValue)
    } catch (e) {
    handleError(e, this.vm, callback for watcher "${this.expression}")
    }
    } else {
    this.cb.call(this.vm, value, oldValue)
    }
    }
    }
    }
    /
    *
  • Evaluate the value of the watcher.
  • This only gets called for lazy watchers.
    /
    /
    獲取觀察者的值/
    evaluate () {
    this.value = this.get()
    this.dirty = false
    }
    /
    *
  • Depend on all deps collected by this watcher.
    /
    /
    收集該watcher的所有deps依賴/
    depend () {
    let i = this.deps.length
    while (i--) {
    this.deps[i].depend()
    }
    }
    /
    *
  • Remove self from all dependencies' subscriber list.
    /
    /
    將自身從所有依賴收集訂閱列表刪除*/
    teardown () {
    ...
    }
    }

## [](https://funteas.com/go/?target=%234%25E3%2580%2581Dep "4、Dep")4半沽、Dep

被Observer的data在觸發(fā) **getter** 時爽柒,**Dep** 就會收集依賴的 **Watcher** ,其實 **Dep** 就像剛才說的是一個書店抄囚,可以接受多個訂閱者的訂閱霉赡,當有新書時即在data變動時,就會通過 **Dep** 給 **Watcher** 發(fā)通知進行更新幔托。

[src/core/observer/dep.js](https://funteas.com/go/?target=https%3A%2F%2Fgithub.com%2Fhuangzhuangjia%2FVue-learn%2Fblob%2Fmaster%2Fcore%2Fobserver%2Fdep.js)

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)
}
/依賴收集穴亏,當存在Dep.target的時候添加觀察者對象/
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
/通知所有訂閱者/
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}


# [](https://funteas.com/go/?target=%23%25E6%2580%25BB%25E7%25BB%2593 "總結(jié)")總結(jié)

其實在 **Vue** 中初始化渲染時蜂挪,視圖上綁定的數(shù)據(jù)就會實例化一個 **Watcher**,依賴收集就是是通過屬性的 **getter** 函數(shù)完成的嗓化,文章一開始講到的 **Observer** 棠涮、**Watcher** 、**Dep** 都與依賴收集相關刺覆。其中 **Observer** 與 **Dep** 是一對一的關系严肪, **Dep** 與 **Watcher** 是多對多的關系,**Dep** 則是 **Observer** 和 **Watcher** 之間的紐帶谦屑。依賴收集完成后驳糯,當屬性變化會執(zhí)行被 **Observer** 對象的 **dep.notify()** 方法,這個方法會遍歷訂閱者(Watcher)列表向其發(fā)送消息氢橙, **Watcher** 會執(zhí)行 **run** 方法去更新視圖酝枢,我們再來看一張圖總結(jié)一下: ![關系圖](http://upload-images.jianshu.io/upload_images/7902592-8ba8c8054297dcc3?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

1.  在 **Vue** 中模板編譯過程中的指令或者數(shù)據(jù)綁定都會實例化一個 **Watcher** 實例,實例化過程中會觸發(fā) **get()**將自身指向 **Dep.target**;
2.  data在 **Observer** 時執(zhí)行 **getter** 會觸發(fā) **dep.depend()** 進行依賴收集;依賴收集的結(jié)果:1悍手、data在 **Observer** 時閉包的dep實例的subs添加觀察它的 **Watcher** 實例帘睦;2. **Watcher** 的deps中添加觀察對象 **Observer** 時的閉包dep;
3.  當data中被 **Observer** 的某個對象值變化后坦康,觸發(fā)subs中觀察它的watcher執(zhí)行 **update()** 方法竣付,最后實際上是調(diào)用watcher的回調(diào)函數(shù)cb,進而更新視圖滞欠。

# [](https://funteas.com/go/?target=%23%25E5%258F%2582%25E8%2580%2583 "參考")參考

</article>

[Vue](https://funteas.com/tag/Vue)

[收藏](https://funteas.com/topic/5a809f5847dc830a0e4690c2#myModal)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末古胆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子仑撞,更是在濱河造成了極大的恐慌赤兴,老刑警劉巖妖滔,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隧哮,死亡現(xiàn)場離奇詭異,居然都是意外死亡座舍,警方通過查閱死者的電腦和手機沮翔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來曲秉,“玉大人采蚀,你說我怎么就攤上這事〕卸” “怎么了榆鼠?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長亥鸠。 經(jīng)常有香客問我妆够,道長识啦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任神妹,我火速辦了婚禮颓哮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鸵荠。我一直安慰自己冕茅,他們只是感情好,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布蛹找。 她就那樣靜靜地躺著姨伤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪庸疾。 梳的紋絲不亂的頭發(fā)上姜挺,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音彼硫,去河邊找鬼炊豪。 笑死,一個胖子當著我的面吹牛拧篮,可吹牛的內(nèi)容都是我干的词渤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼串绩,長吁一口氣:“原來是場噩夢啊……” “哼缺虐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起礁凡,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤高氮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后顷牌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剪芍,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年窟蓝,在試婚紗的時候發(fā)現(xiàn)自己被綠了罪裹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡运挫,死狀恐怖状共,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谁帕,我是刑警寧澤峡继,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站匈挖,受9級特大地震影響碾牌,放射性物質(zhì)發(fā)生泄漏颠猴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一小染、第九天 我趴在偏房一處隱蔽的房頂上張望翘瓮。 院中可真熱鬧,春花似錦裤翩、人聲如沸资盅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呵扛。三九已至,卻和暖如春筐带,著一層夾襖步出監(jiān)牢的瞬間今穿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工伦籍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蓝晒,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓帖鸦,卻偏偏與公主長得像芝薇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子作儿,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容