1 nextTick原理
1 特点
异步更新渲染试图,内部实现兼容了各个浏览器的微任务和宏任务方法。可单独调用,也存在于queueWatcher方法中。
2 结合源码,详细分析
-
nextTick方法的实现,将所有方法都放在callbacks数组中,根据pending = false执行timefunc,timeFunc中异步执行pending=false- pending初始值是false,当异步执行flushCallbacks后又会重新赋值为false,阻止了同步一直调用刷新视图
// ulit/next-tick.js function nextTick (cb?: Function, ctx?: Object) { let _resolve // 把所有需要更新的方法都存放于callbacks数组中 callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) // pending初始值是false,当异步执行flushCallbacks后又会重新赋值为false,阻止了同步一直调用刷新视图 if (!pending) { pending = true timerFunc() } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } } -
timeFunc中兼容了各个浏览器的异步任务,包括promise、mutatitionObserve、setImmediate、setTimeout,来异步调用flashCallback,将pending=false,遍历执行所有的任务function flushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } // Here we have async deferring wrappers using microtasks. // In 2.5 we used (macro) tasks (in combination with microtasks). // However, it has subtle problems when state is changed right before repaint // (e.g. #6813, out-in transitions). // Also, using (macro) tasks in event handler would cause some weird behaviors // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109). // So we now use microtasks everywhere, again. // A major drawback of this tradeoff is that there are some scenarios // where microtasks have too high a priority and fire in between supposedly // sequential events (e.g. #4521, #6690, which have workarounds) // or even between bubbling of the same event (#6566). let timerFunc // The nextTick behavior leverages the microtask queue, which can be accessed // via either native Promise.then or MutationObserver. // MutationObserver has wider support, however it is seriously bugged in // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It // completely stops working after triggering a few times... so, if native // Promise is available, we will use it: /* istanbul ignore next, $flow-disable-line */ // 如果promise支持的话。则直接用promise,微任务 if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) // In problematic UIWebViews, Promise.then doesn't completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn't being flushed, until the browser // needs to do some other work, e.g. handle a timer. Therefore we can // "force" the microtask queue to be flushed by adding an empty timer. if (isIOS) setTimeout(noop) } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( // MutationObserver是H5自带的属性,IE11才兼容 isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // Use MutationObserver where native Promise is not available, // e.g. PhantomJS, iOS7, Android 4.4 // (#6466 MutationObserver is unreliable in IE11) let counter = 1 const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { // setImmediate IE自带,chorme已支持 // Fallback to setImmediate. // Technically it leverages the (macro) task queue, // but it is still a better choice than setTimeout. timerFunc = () => { setImmediate(flushCallbacks) } } else { // Fallback to setTimeout. // 宏任务支持 timerFunc = () => { setTimeout(flushCallbacks, 0) } } export function nextTick (cb?: Function, ctx?: Object) { let _resolve // 把所有需要更新的方法都存放于callbacks数组中 callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) // pending初始值是false,当异步执行flushCallbacks后又会重新赋值为false,阻止了同步一直调用刷新视图 if (!pending) { pending = true timerFunc() } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
3 总结
2 computed原理
1 特点
computed:计算属性,同步执行,具有缓存作用,只有绑定的依赖发生变化时 ,才会更新,重新求值。
2 结合源码,详细分析
-
初始化initComputed方法中,获取
computed中方法函数getter,实例化new Watcher后将参数带入new Watcher({vm, getter,noop,{lazy: true}}),// state.js const computedWatcherOptions = { lazy: true } // 在initState中初始化initComputed function initComputed (vm: Component, computed: Object) { // $flow-disable-line const watchers = vm._computedWatchers = Object.create(null) // computed properties are just getters during SSR const isSSR = isServerRendering() for (const key in computed) { const userDef = computed[key] // getter是一个函数,如果computed中的key, computed: {getName(){return xx}} 不是一个函数,则创建返回函数 const getter = typeof userDef === 'function' ? userDef : userDef.get if (process.env.NODE_ENV !== 'production' && getter == null) { warn( `Getter is missing for computed property "${key}".`, vm ) } if (!isSSR) { // create internal watcher for the computed property. // 实例化Watcher,这个是计算属性的watcher, 主要还是getter函数 watchers[key] = new Watcher( vm, // vue实例 getter || noop, // 传入getter noop, // 空函数 computedWatcherOptions // 默认{lazy: true} ) } -
在
Watcher类中,将判断传入options中的lazy,赋值给dirty,dirty主要用于后续判断是否立即执行计算函数,即存在缓存的关键。其中还有lazy=true是不执行操作,即返回undefined,跟watch有区别,因为是computed是根据defineProtoity去定义的,即当模板调用时才会去计算属性。class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, // 没有传则用noop代替空函数 options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync this.before = options.before } 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 // dirty从this.lazy中取值 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 if (typeof expOrFn === 'function') { // expOrFn 可以看作是从computed传过来的computed中函数 this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = noop 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 ) } } // computed属性,lazy默认为true,则不处理 this.value = this.lazy ? undefined : this.get() } -
重新定义了
defineComputed,如果是在key不是在vm上。// 重新定义了defineComputed,key不是在vm上的 if (!(key in vm)) { defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== 'production') { 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) } } } -
defineComputed函数中,执行了createComputedGetter,返回value,再通过defineProperty去创建监听数据,也就是说可以通过{{name}}直接在模板中使用。createComputeGetter中根据dirty=true是直接执行watcher.evaluate,将dirty设置为false,然后返回value,当dirty=flase是直接返回return watcher.value,起到缓存的作用。当数据重新变化的时候,
update函数中lazy:true会重新将dirty赋值为truefunction defineComputed ( target: any, key: string, userDef: Object | Function ) { const shouldCache = !isServerRendering() // const sharedPropertyDefinition = { // enumerable: true, // configurable: true, // get: noop, // set: noop // } if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : createGetterInvoker(userDef) sharedPropertyDefinition.set = noop } else { // ev sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : createGetterInvoker(userDef.get) : noop sharedPropertyDefinition.set = userDef.set || noop } if (process.env.NODE_ENV !== 'production' && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( `Computed property "${key}" was assigned to but it has no setter.`, this ) } } Object.defineProperty(target, key, sharedPropertyDefinition) } // createComputeGetter function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { if (watcher.dirty) { // 将dirty设置为false,返回value,核心就是起到缓存的作用 watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value } } }
3 watch原理
1 特点
这里的watch指的是用户自定义的watch即
watch: {
name: function(newval,oldval) {
console.log(newval)
},
deep: true
}
在name发生改变时,执行该函数,存在options, 常见的有deep: true深度遍历,immediate: true立即执行,默认是当数据改变时再执行。
2 结合源码,详细分析
-
creteWatch中执行vm.$watch(key, func, options)function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object ) { if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === 'string') { handler = vm[handler] } // watch: { // name: function(newval, oldval) { // console.log(newval) // } // } // 这里就是用户定义的watch方法, expOrFn: 'name'; handler: function return vm.$watch(expOrFn, handler, options) } -
在
$watch中,创建一个watcher,如果options.immediate=true,则直接执行watch中的方法关键:创建一个watcher,expOrFn: string, cb: function
Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { const vm: Component = this if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } options = options || {} options.user = true // 关键,创建一个watcher,expOrFn: string, cb: function const watcher = new Watcher(vm, expOrFn, cb, options) // immediate 直接执行cb 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() } } } -
Watcher中,因为expOrFn是字符串,parsePath(expOrFn)转成函数,lazy=flase,则执行this.get(),this.deep=true,则深度遍历。export default class Watcher { constructor ( vm: Component, expOrFn: string | Function, // computed是一个fn,watch是一个str cb: Function, // 没有传则用noop代替空函数 options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync this.before = options.before } else { // watch中如果没有options:{deep: true},都是false 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 if (typeof expOrFn === 'function') { // expOrFn 可以看作是从computed传过来的computed中函数 this.getter = expOrFn } else { this.getter = parsePath(expOrFn) // 字符串转成函数 msg -> function(){return msg} if (!this.getter) { this.getter = noop 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 ) } } // computed属性,lazy默认为true,则不处理 this.value = this.lazy ? undefined : this.get() } /** * Evaluate the getter, and re-collect dependencies. */ 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 }