看了部分Vue源碼分析或運行機制的文章,接收到這些信息:javascript
- 使用
Object.defineProperty
實現響應監聽,- 使用
Dep
,Wacther
實現依賴收集追蹤- 使用
Virtual Dom
、高效diff
算法實現最小化更新對整個流程仍是沒有理解,或者寫了流程,也是做者本身實現的一套,不是Vue的源碼。爲了熟悉Vue真正的流程花了點時間看了Vue的源碼,經過debugger(瀏覽器版本)本身梳理了一遍。vue
如下代碼來源自Vue,只作了刪減,保留核心。java
new Vue(options)
的執行流程,debugger的主體流程以下:react
beforeCreate Hook
initSate()
git
initData() -- oberve
created Hook
vm.$mount(vm.$options.el)
github
根據template解析出render function
聲明 update function
算法
updateComponent = function () { vm._update(vm._render(), hydrating); };
new Watcher(vm, updateComponent, noop,{before...}
mounted Hook
不考慮updateComponent
和Hooks
剩下:promise
initData() -- oberve
new Watcher(vm, updateComponent, noop,{before...}
這兩個步驟實現了依賴的收集與通知、以及初始化,我在github上的項目learnVue截取了Vue核心代碼進行流程學習,如下是我簡化後的代碼:瀏覽器
import {Observer} from './observer'; import Watcher from "./watcher"; let noop =function(){}; let vm={ _watchers:[], data :{ name:'jack', age:12 } // render:new Function("with(this){return _c('div',{attrs:{\"id\":\"app\"}},[_v(_s(name))])}") } new Observer(vm.data); new Watcher(vm,function(){ //update function var name=vm.data.name; var age=vm.data.age; console.log(name,age) },noop);
下面代碼,控制檯會正常輸出lili
,10
:app
setTimeout(()=>{ vm.data.name='lucy'; vm.data.age=10; vm.data.name='lili'; },1000)
什麼優化都不作Object.defineProperty
定義set
便可實現同步。後果是任一屬性更新都會同步dom,形成性能浪費。Vue使用訂閱/觀察模式作了第一步優化。
打開vue項目下observer文件夾,能夠看到如下幾個文件:
index.js(observer) //植入響應式鉤子
dep.js //依賴管理
watcher.js //觀察者,同步UI
index.js
核心代碼:
const dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = val if (Dep.target) { dep.depend() } return value } })
調用dep.js
中的方法
depend () { if (Dep.target) { Dep.target.addDep(this) } }
那Dep.target
又是什麼呢?這就是new Watcher
的做用了。
watcher.js:
constructor( vm, expOrFn, cb ) { this.vm = vm this.getter = expOrFn this.value = this.get() //初始化 } get() { pushTarget(this) //推送 Dep.target let value const vm = this.vm value = this.getter.call(vm, vm) //觸發 get鉤子 popTarget() //還原 Dep.target this.cleanupDeps() return value }
在Watcher.get()
函數中進行依賴收集,畫了一下運行流程(綠色和藍色線條):
經過這個流程咱們能夠看到,Watcher 就是負責把內存的值同步到UI的。
同步更新會致使大量的重繪,從而致使UI性能問題。Vue採用異步更新策略作了第二步優化,把一個批次的修改一次更新給UI。來看下index.js中set
方法。
set: function reactiveSetter(newVal) { const value = val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } val = newVal dep.notify() }
dep.js
notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() //Watcher.update } }
watcher.js
/** * Subscriber interface. * Will be called when a dependency changes. */ update() { /* istanbul ignore else */ queueWatcher(this) }
queueWatcher
將Watcher
給了queueWatcher //scheduler.js
調度中心,異步的實現,優先promise
,降級setTimeout
。
export const nextTick = (function () { const callbacks = [] let pending = false let timerFunc function nextTickHandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } // 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 if */ if (typeof Promise !== 'undefined') { var p = Promise.resolve() var logError = err => { console.error(err) } timerFunc = () => { p.then(nextTickHandler).catch(logError) } }else { // fallback to setTimeout /* istanbul ignore next */ timerFunc = () => { setTimeout(nextTickHandler, 0) } } return function queueNextTick (cb, ctx) { let _resolve callbacks.push(() => { if (cb) cb.call(ctx) if (_resolve) _resolve(ctx) }) if (!pending) { pending = true timerFunc() } if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } } })()
流程圖,黃色線條:
暫時只研究了內存 => UI
,UI => 內存
之後再研究。框架的彎彎繞繞但沒有一行冗餘代碼,Vue賊6