vue中watch和computed為什么能監(jiān)聽(tīng)到數(shù)據(jù)的改變以及不同之處
先來(lái)個(gè)流程圖,水平有限,湊活看吧-_-||

首先在創(chuàng)建一個(gè)Vue應(yīng)用時(shí):
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
Vue構(gòu)造函數(shù)源碼:
//創(chuàng)建Vue構(gòu)造函數(shù)
function Vue (options) {
if (!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
//_init方法,會(huì)初始化data,watch,computed等
Vue.prototype._init = function (options) {
var vm = this;
// a uid
vm._uid = uid$3++;
......
// expose real self
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm);
......
};
在initState方法中會(huì)初始化data、watch和computed,并調(diào)用observe函數(shù)監(jiān)聽(tīng)data(Object.defineProperty):
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); }
if (opts.methods) { initMethods(vm, opts.methods); }
if (opts.data) {
initData(vm);//initData中也會(huì)調(diào)用observe方法
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { initComputed(vm, opts.computed); }
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
1、observe
observe在initState 時(shí)被調(diào)用,為vue實(shí)例的data屬性值創(chuàng)建getter、setter函數(shù),在setter中dep.depend會(huì)把watcher實(shí)例添加到Dep實(shí)例的subs屬性中,在getter中會(huì)調(diào)用dep.notify,調(diào)用watcher的update方法。
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
* 該函數(shù)在initState中有調(diào)用
*/
function observe (value, asRootData) {
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
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);
}
if (asRootData && ob) {
ob.vmCount++;
}
re * Observer class that is attached to each observed
* object. Once attached, the observer converts the target
* object's property keys into getter/setters that
* collect dependencies and dispatch updates.
*/
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
};
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
};
/**
* Define a reactive property on an Object.
*/
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
//Dep.target 全局變量指向的就是指向當(dāng)前正在解析生成的 Watcher
//會(huì)執(zhí)行到dep.addSub,將Watcher添加到Dep對(duì)象的Watcher數(shù)組中
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var 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 (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();//如果數(shù)據(jù)被重新賦值了, 調(diào)用 Dep 的 notify 方法, 通知所有的 Watcher
} }); }
2、Dep
Watcher的update方法是在new Dep的notify的方法中被調(diào)用的
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
var Dep = function Dep () {
this.id = uid++;
this.subs = [];
};
//設(shè)置某個(gè)Watcher的依賴
//這里添加Dep.target,用來(lái)判斷是不是Watcher的構(gòu)造函數(shù)調(diào)用
//也就是其this.get調(diào)用
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
};
//在該方法中會(huì)觸發(fā)subs的update方法
Dep.prototype.notify = function notify () {
// stabilize the subscriber list first
var subs = this.subs.slice();
if (!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(function (a, b) { return a.id - b.id; });
}
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
3、watch
初始化watch,函數(shù)中會(huì)調(diào)用createWatcher,createWatcher會(huì)調(diào)用$watch,$watch調(diào)用new Watcher實(shí)例。
function initWatch (vm, watch) {
for (var key in watch) {
var handler = watch[key];
if (Array.isArray(handler)) {
for (var i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i]);
}
} else {
createWatcher(vm, key, handler);
}
}
}
function createWatcher (
vm,
expOrFn,
handler,
options
) {
if (isPlainObject(handler)) {
options = handler;
handler = handler.handler;
}
if (typeof handler === 'string') {
handler = vm[handler];
}
return vm.$watch(expOrFn, handler, options)
}
Vue.prototype.$watch = function (
expOrFn,
cb,
options
) {
var vm = this;
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {};
options.user = true;
var watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) {
try {
cb.call(vm, watcher.value);
} catch (error) {
handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
}
}
return function unwatchFn () {
watcher.teardown();
}
};
}
2、computed
初始化computed,調(diào)用new Watcher(),并通過(guò)defineComputed函數(shù)將計(jì)算屬性掛載到vue實(shí)例上,使計(jì)算屬性可以在模板中使用
var computedWatcherOptions = { lazy: true }
function initComputed (vm, computed) {
// $flow-disable-line
var watchers = vm._computedWatchers = Object.create(null);
// computed properties are just getters during SSR
var isSSR = isServerRendering();
for (var key in computed) {
var userDef = computed[key];
var getter = typeof userDef === 'function' ? userDef : userDef.get;
//getter也就是computed的函數(shù)
if (getter == null) {
warn(
("Getter is missing for computed property \"" + key + "\"."),
vm
);
}
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
}
//組件定義的計(jì)算屬性已在
//組件原型。我們只需要定義定義的計(jì)算屬性
//在這里實(shí)例化。
if (!(key in vm)) {
defineComputed(vm, key, userDef);
} else {
if (key in vm.$data) {
warn(("The computed property \"" + key + "\" is already defined in data."), vm);
} else if (vm.$options.props && key in vm.$options.props) {
warn(("The computed property \"" + key + "\" is already defined as a prop."), vm);
}
}
}
}
function defineComputed (
target,
key,
userDef
) {
var shouldCache = !isServerRendering();//true
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef);
sharedPropertyDefinition.set = noop;
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop;
sharedPropertyDefinition.set = userDef.set || noop;
}
if (sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
("Computed property \"" + key + "\" was assigned to but it has no setter."),
this
);
};
}
Object.defineProperty(target, key, sharedPropertyDefinition);
}
//computed的getter函數(shù),在模板獲取對(duì)應(yīng)computed數(shù)據(jù)時(shí)會(huì)調(diào)用
function createComputedGetter (key) {
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {//true
watcher.evaluate();//該方法會(huì)調(diào)用watcher.get方法,也就是computed對(duì)應(yīng)的函數(shù)
}
if (Dep.target) {
watcher.depend();
}
return watcher.value
}
}
}
通過(guò)以上代碼可以看到watch和computed都是通過(guò)new Watcher實(shí)例實(shí)現(xiàn)數(shù)據(jù)的監(jiān)聽(tīng)的,但是computed的options中l(wèi)azy為true,這個(gè)參數(shù)導(dǎo)致它們走的是兩條不同路線。
computed:模板獲取數(shù)據(jù)時(shí),觸發(fā)其getter函數(shù),最終調(diào)用watcher.get,也就是調(diào)用對(duì)應(yīng)回調(diào)函數(shù)。
watch:模板獲取數(shù)據(jù)時(shí),觸發(fā)其getter函數(shù),將watcher添加到對(duì)應(yīng)的Dep.subs中,在之后setter被調(diào)用時(shí),Dep.notify通知所有watcher進(jìn)行update,最終調(diào)用watcher.cb,也就是調(diào)用對(duì)應(yīng)回調(diào)函數(shù)。
3、Watcher
構(gòu)造函數(shù)在是watch時(shí),會(huì)最后調(diào)用this.get,會(huì)觸發(fā)屬性的getter函數(shù),將該Watcher添加到Dep的subs中,用于通知數(shù)據(jù)變動(dòng)時(shí)調(diào)用。
調(diào)用Watcher實(shí)例的update方法會(huì)觸發(fā)其run方法,run方法中會(huì)調(diào)用觸發(fā)函數(shù)。其depend方法會(huì)調(diào)用new Dep的depend方法,dep的depend會(huì)調(diào)用Watcher的addDep方法,最終會(huì)把該watcher實(shí)例添加到Dep的subs屬性中
/**
*觀察者解析表達(dá)式,收集依賴項(xiàng),
*并在表達(dá)式值更改時(shí)激發(fā)回調(diào)。
*這用于$watch()api和指令。
*/
var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
this.vm = vm;
......
this.cb = cb;//觸發(fā)函數(shù)
this.id = ++uid$2; // uid for batching
this.active = true;
this.dirty = this.lazy; // for lazy watchers
......
this.value = this.lazy ? undefined ? this.get();//computed會(huì)返回undefined,而watch會(huì)執(zhí)行Watcher.get
};
/**
* Scheduler job interface.
* Will be called by the scheduler.
* 該方法會(huì)執(zhí)行觸發(fā)函數(shù)
*/
Watcher.prototype.run = function run () {
if (this.active) {
var 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.
isObject(value) ||
this.deep
) {
// set new value
var oldValue = this.value;
this.value = value;
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 getter, and re-collect dependencies.
*/
Watcher.prototype.get = function get () {
pushTarget(this);
var value;
var 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
};
/**
* Subscriber interface.
* Will be called when a dependency changes.
* 在方法中調(diào)用Watcher的run方法
*/
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);//該方法最終也會(huì)調(diào)用run方法
}
};
/**
* Depend on all deps collected by this watcher.會(huì)調(diào)用new Dep的depend方法,dep的depend會(huì)調(diào)用Watcher的addDep方法
*/
Watcher.prototype.depend = function depend () {
var i = this.deps.length;
while (i--) {
this.deps[i].depend();
}
};
/**
* Add a dependency to this directive.
*/
Watcher.prototype.addDep = function addDep (dep) {
var id = dep.id;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
dep.addSub(this);
}
}
};
總結(jié)
以上所述是小編給大家介紹的vue中watch和computed為什么能監(jiān)聽(tīng)到數(shù)據(jù)的改變以及不同之處,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
如果你覺(jué)得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!
- 如何理解Vue中computed和watch的區(qū)別
- Vue中computed和watch有哪些區(qū)別
- 詳解Vue中的watch和computed
- vue中watch和computed的區(qū)別與使用方法
- Vue中computed及watch區(qū)別實(shí)例解析
- Vue中watch、computed、updated三者的區(qū)別及用法
- Vue的data、computed、watch源碼淺談
- Vue中computed、methods與watch的區(qū)別總結(jié)
- vue計(jì)算屬性computed、事件、監(jiān)聽(tīng)器watch的使用講解
- vue Watch和Computed的使用總結(jié)
相關(guān)文章
Vue使用JSEncrypt實(shí)現(xiàn)rsa加密及掛載方法
這篇文章主要介紹了Vue使用JSEncrypt實(shí)現(xiàn)rsa加密及掛載方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02
VUE中filters過(guò)濾器的兩種用法實(shí)例
vue中過(guò)濾器的作用可被用于一些常見(jiàn)的文本格式化,也就是修飾文本,但是文本內(nèi)容不會(huì)改變,下面這篇文章主要給大家介紹了關(guān)于VUE中filters過(guò)濾器的兩種用法,需要的朋友可以參考下2022-04-04
vue自定義穿梭框支持遠(yuǎn)程滾動(dòng)加載的實(shí)現(xiàn)方法
這篇文章主要介紹了vue自定義穿梭框支持遠(yuǎn)程滾動(dòng)加載,iview是全局注入,基本使用原先的類名進(jìn)行二次創(chuàng)建公共組件,修改基礎(chǔ)js實(shí)現(xiàn)邏輯,本文結(jié)合實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08
Vue路由監(jiān)聽(tīng)實(shí)現(xiàn)同頁(yè)面動(dòng)態(tài)加載的示例
本文主要介紹了Vue基于路由監(jiān)聽(tīng)實(shí)現(xiàn)同頁(yè)面動(dòng)態(tài)加載的示例,重點(diǎn)在于切換路由的時(shí)候,重新拉取列表數(shù)據(jù),接下來(lái)看看實(shí)現(xiàn)方法吧2021-05-05
可控制緩存銷毀的?keepAlive?組件實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了可控制緩存銷毀的?keepAlive?組件實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
vue數(shù)組中不滿足條件跳出循環(huán)問(wèn)題
這篇文章主要介紹了vue數(shù)組中不滿足條件跳出循環(huán)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
vue項(xiàng)目實(shí)現(xiàn)github在線預(yù)覽功能
這篇文章主要介紹了vue項(xiàng)目實(shí)現(xiàn)github在線預(yù)覽功能,本文通過(guò)提問(wèn)兩個(gè)問(wèn)題帶領(lǐng)大家一起學(xué)習(xí)整個(gè)過(guò)程,需要的朋友可以參考下2018-06-06
使用vue-aplayer插件時(shí)出現(xiàn)的問(wèn)題的解決
這篇文章主要介紹了使用vue-aplayer插件時(shí)出現(xiàn)的問(wèn)題的解決,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
解決vue create 創(chuàng)建項(xiàng)目只有兩個(gè)文件問(wèn)題
這篇文章主要介紹了解決vue create 創(chuàng)建項(xiàng)目只有兩個(gè)文件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02
vue3實(shí)現(xiàn)移動(dòng)端滑動(dòng)模塊
這篇文章主要為大家詳細(xì)介紹了vue3實(shí)現(xiàn)移動(dòng)端滑動(dòng)模塊,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-09-09

