задний план
Меня всегда интересовал vuewatchа такжеcomputedполупоняты, немного знают (например:watchа такжеcomputedСуть в томnew Watcher,computedКэш есть, он будет выполняться только при вызове, и он будет запускаться снова только при изменении зависимых данных...), а дальше такого нет.
Я также читал много статей, написанных большими парнями, там указан большой кусок исходного кода, который действительно заставляет меня, новичка, смотреть на него, и, естественно, я не хочу его читать. Недавно я снова начал изучать исходный код vue, и тогда я действительно понял принцип их реализации.
data() {
return {
msg: 'hello guys',
info: {age:'18'},
name: 'FinGet'
}
}
watcher
watcherчто это такое? слушатель? это классclass!
class Watcher{
constructor(vm,exprOrFn,callback,options,isRenderWatcher){
}
}
-
vmvue-экземпляр -
exprOrFnЭто может быть строка или функция обратного вызова (оглянитесь назад, если вы немного запутались, сейчас это не имеет значения) -
optionsРазличные элементы конфигурации (что настраивать, оглянуться назад) -
isRenderWatcherтак или иначеоказыватьWathcer
initState
Инициализация Vue выполнитinitStateметоды, из которых наиболее известныinitData,то естьObject.definePropertyПерехват данных.
export function initState(vm) {
const opts = vm.$options;
// vue 的数据来源 属性 方法 数据 计算属性 watch
if(opts.props) {
initProps(vm);
}
if(opts.methods) {
initMethod(vm);
}
if(opts.data) {
initData(vm);
}
if(opts.computed){
initComputed(vm);
}
if(opts.watch) {
initWatch(vm, opts.watch);
}
}
В захвате данных,Watcherхороший другDepПоявился,Depпросто поставитьWatcherспасти.
function defineReactive(data, key, val) {
let dep = new Dep();
Object.defineProperty(data, key, {
get(){
if(Dep.target) {
dep.depend(); // 收集依赖
}
return val;
},
set(newVal) {
if(newVal === val) return;
val = newVal;
dep.notify(); // 通知执行
}
})
}
когда
initDataкогда,Dep.targetНичего, так я собрал одиночество.targetпривязан кDepВ этом классе, а не в экземпляре.
но когда$mountПосле этого все было иначе. Что касается$mountчто выполняется вcompile,generate,render,patch,diffЭто не то, чему посвящена эта статья, это не важно, обходите это стороной!
Вам нужно знать только одно: будет выполнен следующий код
new Watcher(vm, updateComponent, () => {}, {}, true); // true 表示他是一个渲染watcher
updateComponentПросто обновите, независимо от конкретной реализации, теперь это страница, которая будет обновлятьсяПерезвоните, он будет присутствоватьWatcherизgetterсередина. Он соответствует первомуexprOrFnпараметр.
Эй, на этот раз все по-другому:
- Отрисовка страницы — это вызов данных, которые вы определили (не суетитесь, это определено, а не вызывается), и оно пойдет.
get. - над
new WatcherПри вызове метода для помещения этого экземпляра вDep.targetвверх, иди сноваgetВы можете собирать зависимости в
pushTarget(watcher) {
Dep.target = watcher;
}
Эти две вещи просто собираются вместе, затемdep.depend()Просто работай.
Так что тут можно понять одно, все
dataДанные, определенные в , будут собирать рендер всякий раз, когда он вызывается.watcher, то есть изменения данных, выполнитьsetсерединаdep.notifyбудет оказыватьwatcher
На следующем рисунке показано определениеmsg,info,nameтри данных, все они имеютоказыватьWatcher:
Зоркие друзья должны были видетьmsgЕсть дваwatcher, один определяется пользователемwatch, другой также определяется пользователемwatch. Ах, конечно нет,vueЭто сделано для дедупликации, повторов не будетwatcher, как и следовало ожидать, другойcomputed watcher;
пользовательские часы
Обычно мы используем такие часы:
watch: {
msg(newVal, oldVal){
console.log('my watch',newVal, oldVal)
}
// or
msg: {
handler(newVal, oldVal) {
console.log('my watch',newVal, oldVal)
},
immediate: true
}
}
Здесь будет выполнятьсяinitWatch, после одной операции извлекаетсяexprOrFn(На этот раз это строка (msg)),handler,options, что то же самое, чтоWatcherОн подходит необъяснимо, а затем называет это логически.vm.$watchметод.
Vue.prototype.$watch = function(exprOrFn, cb, options = {}) {
options.user = true; // 标记为用户watcher
// 核心就是创建个watcher
const watcher = new Watcher(this, exprOrFn, cb, options);
if(options.immediate){
cb.call(vm,watcher.value)
}
}
Да ладно, я не могу не посмотреть на этот код (я изначально вставил длинный абзац, но сказал на просторечии, поэтому удалил те, которые имеют мало отношения к этому абзацу):
class Watcher{
constructor(vm,exprOrFn,callback,options,isRenderWatcher){
this.vm = vm;
this.callback = callback;
this.options = options;
if(options) {
this.user = !!options.user;
}
this.id = id ++;
if (typeof exprOrFn == 'function') {
this.getter = exprOrFn; // 将传过来的回调函数 放到getter属性上
} else {
// 当exprOrFn 是个字符串的时候,就需要去取值(返回一个取值函数 闭包)
this.getter = parsePath(exprOrFn);
// 如果getter不存在 会赋值一个 函数
if (!this.getter) {
this.getter = (() => {});
}
}
this.value = this.get();
}
get(){
pushTarget(this); // 把当前watcher 存入dep中
let result = this.getter.call(this.vm, this.vm); // 渲染watcher的执行 这里会走到observe的get方法,然后存下这个watcher
popTarget(); // 再置空 当执行到这一步的时候 所以的依赖收集都完成了,都是同一个watcher
return result;
}
}
// 这个就是拿来把msg的值取到,取到的就是oldVal
function parsePath(path) {
if (!path) {
return
}
var segments = path.split('.');
return function(obj) {
for (var i = 0; i < segments.length; i++) {
if (!obj) { return }
obj = obj[segments[i]];
}
return obj
}
}
Как вы видете,new Watcherбудет выполнятьgetметод, когдаРендер-наблюдательОтобразит страницу, выполнитupdateComponent, когда он являетсяНаблюдатель за пользователямизаключается в выполненииparsePathметод в методе возврата, а затем получить значениеthis.valueто естьoldVal.
Эй, эй, теперь, когда значение принято, оно снова исчезлоmsgизgetвнутри, на этот разdep.depend()снова работает,Наблюдатель за пользователямихранится в нем.
когдаmsgПри смене еще какие-то грубые операции в процессе, это не важно, а в итоге одна будет выполненаrunметод, вызовите функцию обратного вызова, поместитеnewValueа такжеoldValueПройти:
// dep
notify() {
this.subs.forEach(watcher => watcher.update())
}
// watch
update(){
if (this.lazy) {
// 计算属性 需要更新
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
// 异步更新 多次修改同一个值 只更新一次
queueWatcher(this);
}
// queueWatcher(this);
// this.get()
}
run(){
let oldValue = this.value;
// 再执行一次就拿到了现在的值,会去重哈,watcher不会重复添加
let newValue = this.get();
this.value = newValue;
if(this.user && oldValue != newValue) {
// 是用户watcher, 就调用callback 也就是 handler
this.callback(newValue, oldValue)
}
}
computed
computed: {
c_msg() {
return this.msg + 'computed'
}
// or
c_msg: {
get() {
return this.msg + 'computed'
},
set() {}
}
},
computedКаковы особенности:
- будет выполняться при вызове
- с кешем
- Пересчитывать при изменении зависимостей
Выполняется при вызове, как я узнаю, что он звонит? эй эй,Object.definePropertyРазве это не то, что он делает, это не совпадение.
Когда зависимые данные изменятся, они будут пересчитаны, поэтому вам нужно собрать зависимости. Еще та логика, позвониthis.msg -> get -> dep.depend().
function initComputed(vm) {
let computed = vm.$options.computed;
const watchers = vm._computedWatchers = {};
for(let key in computed) {
const userDef = computed[key];
// 获取get方法
const getter = typeof userDef === 'function' ? userDef : userDef.get;
// 创建计算属性watcher lazy就是第一次不调用
watchers[key] = new Watcher(vm, userDef, () => {}, { lazy: true });
defineComputed(vm, key, userDef)
}
}
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: () => {},
set: () => {}
}
function defineComputed(target, key, userDef) {
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = createComputedGetter(key)
} else {
sharedPropertyDefinition.get = createComputedGetter(userDef.get);
sharedPropertyDefinition.set = userDef.set;
}
// 使用defineProperty定义 这样才能做到使用才计算
Object.defineProperty(target, key, sharedPropertyDefinition)
}
Следующий абзац является наиболее важным, просто взгляните на вышеизложенное, и вышеприведенное должно поставитьgetспособ узнать, использоватьObject.definePropertyСвяжите это.
class Watcher{
constructor(vm,exprOrFn,callback,options,isRenderWatcher){
...
this.dirty = this.lazy;
// lazy 第一次不执行
this.value = this.lazy ? undefined : this.get();
...
}
update(){
if (this.lazy) {
// 计算属性 需要更新
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this); // 这就是个陪衬 现在不管它
}
}
evaluate() {
this.value = this.get();
this.dirty = false;
}
}
Кэш здесь, выполнитьgetМетод получит возвращаемое значениеthis.valueэто кэшированное значение, вНаблюдатель за пользователями, этоoldValue, Когда я написал это, мое восхищение Богом Ю Да усилилось. 🐂🍺плюс!
function createComputedGetter(key) {
return function computedGetter() {
// this 指向vue 实例
const watcher = this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) { // 如果dirty为true
watcher.evaluate();// 计算出新值,并将dirty 更新为false
}
// 如果依赖的值不发生变化,则返回上次计算的结果
return watcher.value
}
}
}
watcherизupdateКогда это было вызвано? То есть вызов обновления данныхdep.notify(),dirtyдолжен статьtrue, но вычисляемое свойство по-прежнему не может быть вычислено немедленно, его все равно нужно вычислить при вызове, поэтому вupdateтолько что изменилdirtyположение дел! Затем он будет пересчитан при следующем вызове.
Полагаться на изменение данных -> после обновления уведомления -> сбросить флаг грязных данных -> значение обновления (пересчитать) при чтении страницы (выполнение наблюдателя рендеринга).
class Dep {
constructor() {
this.id = id ++;
this.subs = [];
}
addSub(watcher) {
this.subs.push(watcher);
}
depend() {
Dep.target.addDep(this);
}
notify() {
this.subs.forEach(watcher => watcher.update())
}
}
Суммировать
-
watchа такжеcomputedПо сутиWatcher, хранятся вDep, при изменении данных выполнитьdep.notifyпоставить текущий соответствующийDepхранится в экземпляреWatcherВсеrunТеперь это сделанооказыватьWatcherСтраница обновляется; - Каждые данные имеют свои
Dep, если он вызывается в шаблоне, он должен иметь渲染Watcher; -
initDataкогда, не так лиWatcherМожно собрать; - не нашел,оказывать
Watcherа такжеComputedсередина,exprOrFnфункции,ПользовательWatcherвсе строки.
Код в статье является сокращенной версией, и есть много деталей, которые не упомянуты. Это не важно и не важно для данной статьи. Для более глубокого понимания вы можете прочитать исходный код.