淺談vue的第一個(gè)commit分析
為什么寫這篇vue的分析文章?
對(duì)于天資愚鈍的前端(我)來說,閱讀源碼是件不容易的事情,畢竟有時(shí)候看源碼分析的文章都看不懂。每次看到大佬們用了1~2年的vue就能掌握原理,甚至精通源碼,再看看自己用了好幾年都還在基本的使用階段,心中總是羞愧不已。如果一直滿足于基本的業(yè)務(wù)開發(fā),怕是得在初級(jí)水平一直待下去了吧。所以希望在學(xué)習(xí)源碼的同時(shí)記錄知識(shí)點(diǎn),可以讓自己的理解和記憶更加深刻,也方便將來查閱。
目錄結(jié)構(gòu)
本文以vue的第一次 commit a879ec06 作為分析版本
├── build│ └── build.js// `rollup` 打包配置├── dist │ └── vue.js ├── package.json├── src // vue源碼目錄│ ├── compiler// 將vue-template轉(zhuǎn)化為render函數(shù)│ │ ├── codegen.js // 遞歸ast提取指令,分類attr,style,class,并生成render函數(shù)│ │ ├── html-parser.js // 通過正則匹配將html字符串轉(zhuǎn)化為ast│ │ ├── index.js // compile主入口│ │ └── text-parser.js // 編譯{{}}│ ├── config.js // 對(duì)于vue的全局配置文件│ ├── index.js// 主入口│ ├── index.umd.js // 未知(應(yīng)該是umd格式的主入口)│ ├── instance// vue實(shí)例函數(shù)│ │ └── index.js // 包含了vue實(shí)例的初始化,compile,data代理,methods代理,watch數(shù)據(jù),執(zhí)行渲染│ ├── observer// 數(shù)據(jù)訂閱發(fā)布的實(shí)現(xiàn)│ │ ├── array.js // 實(shí)現(xiàn)array變異方法,$set $remove 實(shí)現(xiàn)│ │ ├── batcher.js // watch執(zhí)行隊(duì)列的收集,執(zhí)行│ │ ├── dep.js // 訂閱中心實(shí)現(xiàn)│ │ ├── index.js // 數(shù)據(jù)劫持的實(shí)現(xiàn),收集訂閱者│ │ └── watcher.js // watch實(shí)現(xiàn),訂閱者│ ├── util // 工具函數(shù)│ │ ├── component.js│ │ ├── debug.js│ │ ├── dom.js│ │ ├── env.js // nexttick實(shí)現(xiàn)│ │ ├── index.js│ │ ├── lang.js│ │ └── options.js│ └── vdom│ ├── dom.js // dom操作的封裝│ ├── h.js// 節(jié)點(diǎn)數(shù)據(jù)分析(元素節(jié)點(diǎn),文本節(jié)點(diǎn))│ ├── index.js // vdom主入口│ ├── modules // 不同屬性處理函數(shù)│ │ ├── attrs.js // 普通attr屬性處理│ │ ├── class.js // class處理│ │ ├── events.js // event處理│ │ ├── props.js // props處理│ │ └── style.js // style處理│ ├── patch.js // node樹的渲染,包括節(jié)點(diǎn)的加減更新處理,及對(duì)應(yīng)attr的處理│ └── vnode.js // 返回最終的節(jié)點(diǎn)數(shù)據(jù)└── webpack.config.js // webpack配置
從template到html的過程分析
我們的代碼是從new Vue()開始的,Vue的構(gòu)造函數(shù)如下:
constructor (options) { // options就是我們對(duì)于vue的配置 this.$options = options this._data = options.data // 獲取元素html,即template const el = this._el = document.querySelector(options.el) // 編譯模板 -> render函數(shù) const render = compile(getOuterHTML(el)) this._el.innerHTML = ’’ // 實(shí)例代理data數(shù)據(jù) Object.keys(options.data).forEach(key => this._proxy(key)) // 將method的this指向?qū)嵗?if (options.methods) { Object.keys(options.methods).forEach(key => { this[key] = options.methods[key].bind(this) }) } // 數(shù)據(jù)觀察 this._ob = observe(options.data) this._watchers = [] // watch數(shù)據(jù)及更新 this._watcher = new Watcher(this, render, this._update) // 渲染函數(shù) this._update(this._watcher.value)}
當(dāng)我們初始化項(xiàng)目的時(shí)候,即會(huì)執(zhí)行構(gòu)造函數(shù),該函數(shù)向我們展示了vue初始化的主線:編譯template字符串 => 代理data數(shù)據(jù)/methods的this綁定 => 數(shù)據(jù)觀察 => 建立watch及更新渲染
1. 編譯template字符串
const render = compile(getOuterHTML(el))
其中compile的實(shí)現(xiàn)如下:
export function compile (html) { html = html.trim() // 對(duì)編譯結(jié)果緩存 const hit = cache[html] // parse函數(shù)在parse-html中定義,其作用是把我們獲取的html字符串通過正則匹配轉(zhuǎn)化為ast,輸出如下 {tag: ’div’, attrs: {}, children: []} return hit || (cache[html] = generate(parse(html)))}
接下來看看generate函數(shù),ast通過genElement的轉(zhuǎn)化生成了構(gòu)建節(jié)點(diǎn)html的函數(shù),在genElement將對(duì)if for 等進(jìn)行判斷并轉(zhuǎn)化( 指令的具體處理將在后面做分析,先關(guān)注主流程代碼),最后都會(huì)執(zhí)行g(shù)enData函數(shù)
// 生成節(jié)點(diǎn)主函數(shù)export function generate (ast) { const code = genElement(ast) // 執(zhí)行code代碼,并將this作為code的global對(duì)象。所以我們?cè)趖emplate中的變量將指向?yàn)閷?shí)例的屬性 {{name}} -> this.name return new Function (`with (this) { return $[code]}`)}// 解析單個(gè)節(jié)點(diǎn) -> genDatafunction genElement (el, key) { let exp // 指令的實(shí)現(xiàn),實(shí)際就是在模板編譯時(shí)實(shí)現(xiàn)的 if (exp = getAttr(el, ’v-for’)) { return genFor(el, exp) } else if (exp = getAttr(el, ’v-if’)) { return genIf(el, exp) } else if (el.tag === ’template’) { return genChildren(el) } else { // 分別為 tag 自身屬性 子節(jié)點(diǎn)數(shù)據(jù) return `__h__(’${ el.tag }’, ${ genData(el, key) }, ${ genChildren(el) })` }}
我們可以看看在genData中都做了什么。上面的parse函數(shù)將html字符串轉(zhuǎn)化為ast,而在genData中則將節(jié)點(diǎn)的attrs數(shù)據(jù)進(jìn)一步處理,例如class -> renderClass style class props attr 分類。在這里可以看到 bind 指令的實(shí)現(xiàn),即通過正則匹配 : 和 bind,如果匹配則把相應(yīng)的 value值轉(zhuǎn)化為 (value)的形式,而不匹配的則通過JSON.stringify()轉(zhuǎn)化為字符串(’value’)。最后輸出attrs的(key-value),在這里得到的對(duì)象是字符串形式的,例如(value)等也僅僅是將變量名,而在generate中通過new Function進(jìn)一步通過(this.value)得到變量值。
function genData (el, key) { // 沒有屬性返回空對(duì)象 if (!el.attrs.length) { return ’{}’ } // key let data = key ? `{key:${ key },` : `{` // class處理 if (el.attrsMap[’:class’] || el.attrsMap[’class’]) { data += `class: _renderClass(${ el.attrsMap[’:class’] }, '${ el.attrsMap[’class’] || ’’ }'),` } // attrs let attrs = `attrs:{` let props = `props:{` let hasAttrs = false let hasProps = false for (let i = 0, l = el.attrs.length; i < l; i++) { let attr = el.attrs[i] let name = attr.name // bind屬性 if (bindRE.test(name)) { name = name.replace(bindRE, ’’) if (name === ’class’) { continue // style處理 } else if (name === ’style’) { data += `style: ${ attr.value },` // props屬性處理 } else if (mustUsePropsRE.test(name)) { hasProps = true props += `'${ name }': (${ attr.value }),` // 其他屬性 } else { hasAttrs = true attrs += `'${ name }': (${ attr.value }),` } // on指令,未實(shí)現(xiàn) } else if (onRE.test(name)) { name = name.replace(onRE, ’’) // 普通屬性 } else if (name !== ’class’) { hasAttrs = true attrs += `'${ name }': (${ JSON.stringify(attr.value) }),` } } if (hasAttrs) { data += attrs.slice(0, -1) + ’},’ } if (hasProps) { data += props.slice(0, -1) + ’},’ } return data.replace(/,$/, ’’) + ’}’}
而對(duì)于genChildren,我們可以猜到就是對(duì)ast中的children進(jìn)行遍歷調(diào)用genElement,實(shí)際上在這里還包括了對(duì)文本節(jié)點(diǎn)的處理。
// 遍歷子節(jié)點(diǎn) -> genNodefunction genChildren (el) { if (!el.children.length) { return ’undefined’ } // 對(duì)children扁平化處理 return ’__flatten__([’ + el.children.map(genNode).join(’,’) + ’])’}function genNode (node) { if (node.tag) { return genElement(node) } else { return genText(node) }}// 解析{{}}function genText (text) { if (text === ’ ’) { return ’' '’ } else { const exp = parseText(text) if (exp) { return ’String(’ + escapeNewlines(exp) + ’)’ } else { return escapeNewlines(JSON.stringify(text)) } }}
genText處理了text及換行,在parseText函數(shù)中利用正則解析{{}},輸出字符串(value)形式的字符串。
現(xiàn)在我們?cè)倏纯確_h__(’${ el.tag }’, ${ genData(el, key) }, ${ genChildren(el) })中__h__函數(shù)
// h 函數(shù)利用上面得到的節(jié)點(diǎn)數(shù)據(jù)得到 vNode對(duì)象 => 虛擬domexport default function h (tag, b, c) { var data = {}, children, text, i if (arguments.length === 3) { data = b if (isArray(c)) { children = c } else if (isPrimitive(c)) { text = c } } else if (arguments.length === 2) { if (isArray(b)) { children = b } else if (isPrimitive(b)) { text = b } else { data = b } } if (isArray(children)) { // 子節(jié)點(diǎn)遞歸處理 for (i = 0; i < children.length; ++i) { if (isPrimitive(children[i])) children[i] = VNode(undefined, undefined, undefined, children[i]) } } // svg處理 if (tag === ’svg’) { addNS(data, children) } // 子節(jié)點(diǎn)為文本節(jié)點(diǎn) return VNode(tag, data, children, text, undefined)}
到此為止,我們分析了const render = compile(getOuterHTML(el)),從el的html字符串到render函數(shù)都是怎么處理的。
2. 代理data數(shù)據(jù)/methods的this綁定
// 實(shí)例代理data數(shù)據(jù)Object.keys(options.data).forEach(key => this._proxy(key))// 將method的this指向?qū)嵗齣f (options.methods) { Object.keys(options.methods).forEach(key => { this[key] = options.methods[key].bind(this) })}
實(shí)例代理data數(shù)據(jù)的實(shí)現(xiàn)比較簡(jiǎn)單,就是利用了對(duì)象的setter和getter,讀取this數(shù)據(jù)時(shí)返回data數(shù)據(jù),在設(shè)置this數(shù)據(jù)時(shí)同步設(shè)置data數(shù)據(jù)
_proxy (key) { if (!isReserved(key)) { // need to store ref to self here // because these getter/setters might // be called by child scopes via // prototype inheritance. var self = this Object.defineProperty(self, key, { configurable: true, enumerable: true, get: function proxyGetter () { return self._data[key] }, set: function proxySetter (val) { self._data[key] = val } }) }}
3. Obaerve的實(shí)現(xiàn)
Observe的實(shí)現(xiàn)原理在很多地方都有分析,主要是利用了Object.defineProperty()來建立對(duì)數(shù)據(jù)更改的訂閱,在很多地方也稱之為數(shù)據(jù)劫持。下面我們來學(xué)習(xí)從零開始建立這樣一個(gè)數(shù)據(jù)的訂閱發(fā)布體系。
從簡(jiǎn)單處開始,我們希望有個(gè)函數(shù)可以幫我們監(jiān)聽數(shù)據(jù)的改變,每當(dāng)數(shù)據(jù)改變時(shí)執(zhí)行特定回調(diào)函數(shù)
function observe(data, callback) { if (!data || typeof data !== ’object’) { return } // 遍歷key Object.keys(data).forEach((key) => { let value = data[key]; // 遞歸遍歷監(jiān)聽深度變化 observe(value, callback); // 監(jiān)聽單個(gè)可以的變化 Object.defineProperty(data, key, { configurable: true, enumerable: true, get() { return value; }, set(val) { if (val === value) { return } value = val; // 監(jiān)聽新的數(shù)據(jù) observe(value, callback);// 數(shù)據(jù)改變的回調(diào) callback(); } }); });}// 使用observe函數(shù)監(jiān)聽dataconst data = {};observe(data, () => { console.log(’data修改’);})
上面我們實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的observe函數(shù),只要我們將編譯函數(shù)作為callback傳入,那么每次數(shù)據(jù)更改時(shí)都會(huì)觸發(fā)回調(diào)函數(shù)。但是我們現(xiàn)在不能為單獨(dú)的key設(shè)置監(jiān)聽及回調(diào)函數(shù),只能監(jiān)聽整個(gè)對(duì)象的變化執(zhí)行回調(diào)。下面我們對(duì)函數(shù)進(jìn)行改進(jìn),達(dá)到為某個(gè)key設(shè)置監(jiān)聽及回調(diào)。同時(shí)建立調(diào)度中心,讓整個(gè)訂閱發(fā)布模式更加清晰。
// 首先是訂閱中心class Dep { constructor() { this.subs = []; // 訂閱者數(shù)組 } addSub(sub) { // 添加訂閱者 this.subs.push(sub); } notify() { // 發(fā)布通知 this.subs.forEach((sub) => { sub.update(); }); }}// 當(dāng)前訂閱者,在getter中標(biāo)記Dep.target = null;// 訂閱者class Watch { constructor(express, cb) { this.cb = cb; if (typeof express === ’function’) { this.expressFn = express; } else { this.expressFn = () => { return new Function(express)(); } } this.get(); } get() { // 利用Dep.target存當(dāng)前訂閱者 Dep.target = this; // 執(zhí)行表達(dá)式 -> 觸發(fā)getter -> 在getter中添加訂閱者 this.expressFn(); // 及時(shí)置空 Dep.taget = null; } update() { // 更新 this.cb(); } addDep(dep) { // 添加訂閱 dep.addSub(this); }}// 觀察者 建立觀察class Observe { constructor(data) { if (!data || typeof data !== ’object’) { return } // 遍歷key Object.keys(data).forEach((key) => { // key => dep 對(duì)應(yīng) const dep = new Dep(); let value = data[key]; // 遞歸遍歷監(jiān)聽深度變化 const observe = new Observe(value); // 監(jiān)聽單個(gè)可以的變化 Object.defineProperty(data, key, { configurable: true, enumerable: true, get() { if (Dep.target) { const watch = Dep.target; watch.addDep(dep); } return value; }, set(val) { if (val === value) { return } value = val; // 監(jiān)聽新的數(shù)據(jù) new Observe(value); // 數(shù)據(jù)改變的回調(diào) dep.notify(); } }); }); }}// 監(jiān)聽數(shù)據(jù)中某個(gè)key的更改const data = { name: ’xiaoming’, age: 26};const observe = new Observe(data);const watch = new Watch(’data.age’, () => { console.log(’age update’);});data.age = 22
現(xiàn)在我們實(shí)現(xiàn)了訂閱中心,訂閱者,觀察者。觀察者監(jiān)測(cè)數(shù)據(jù)的更新,訂閱者通過訂閱中心訂閱數(shù)據(jù)的更新,當(dāng)數(shù)據(jù)更新時(shí),觀察者會(huì)告訴訂閱中心,訂閱中心再逐個(gè)通知所有的訂閱者執(zhí)行更新函數(shù)。到現(xiàn)在為止,我們可以大概猜出vue的實(shí)現(xiàn)原理:
建立觀察者觀察data數(shù)據(jù)的更改 (new Observe) 在編譯的時(shí)候,當(dāng)某個(gè)代碼片段或節(jié)點(diǎn)依賴data數(shù)據(jù),為該節(jié)點(diǎn)建議訂閱者,訂閱data中某些數(shù)據(jù)的更新(new Watch) 當(dāng)dada數(shù)據(jù)更新時(shí),通過訂閱中心通知數(shù)據(jù)更新,執(zhí)行節(jié)點(diǎn)更新函數(shù),新建或更新節(jié)點(diǎn)(dep.notify())上面是我們對(duì)vue實(shí)現(xiàn)原理訂閱發(fā)布模式的基本實(shí)現(xiàn),及編譯到更新過程的猜想,現(xiàn)在我們接著分析vue源碼的實(shí)現(xiàn):在實(shí)例的初始化中
// ...// 為數(shù)據(jù)建立數(shù)據(jù)觀察this._ob = observe(options.data)this._watchers = []// 添加訂閱者 執(zhí)行render 會(huì)觸發(fā) getter 訂閱者訂閱更新,數(shù)據(jù)改變觸發(fā) setter 訂閱中心通知訂閱者執(zhí)行 updatethis._watcher = new Watcher(this, render, this._update)// ...
vue中數(shù)據(jù)觀察的實(shí)現(xiàn)
// observe函數(shù)export function observe (value, vm) { if (!value || typeof value !== ’object’) { return } if ( hasOwn(value, ’__ob__’) && value.__ob__ instanceof Observer ) { ob = value.__ob__ } else if ( shouldConvert && (isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { // 為數(shù)據(jù)建立觀察者 ob = new Observer(value) } // 存儲(chǔ)關(guān)聯(lián)的vm if (ob && vm) { ob.addVm(vm) } return ob}// => Observe 函數(shù)export function Observer (value) { this.value = value // 在數(shù)組變異方法中有用 this.dep = new Dep() // observer實(shí)例存在__ob__中 def(value, ’__ob__’, this) if (isArray(value)) { var augment = hasProto ? protoAugment : copyAugment // 數(shù)組遍歷,添加變異的數(shù)組方法 augment(value, arrayMethods, arrayKeys) // 對(duì)數(shù)組的每個(gè)選項(xiàng)調(diào)用observe函數(shù) this.observeArray(value) } else { // walk -> convert -> defineReactive -> setter/getter this.walk(value) }}// => walkObserver.prototype.walk = function (obj) { var keys = Object.keys(obj) for (var i = 0, l = keys.length; i < l; i++) { this.convert(keys[i], obj[keys[i]]) }}// => convertObserver.prototype.convert = function (key, val) { defineReactive(this.value, key, val)}// 重點(diǎn)看看defineReactiveexport function defineReactive (obj, key, val) { // key對(duì)應(yīng)的的訂閱中心 var dep = new Dep() var property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // 兼容原有setter/getter // cater for pre-defined getter/setters var getter = property && property.get var setter = property && property.set // 實(shí)現(xiàn)遞歸監(jiān)聽屬性 val = obj[key] // 深度優(yōu)先遍歷 先為子屬性設(shè)置 reactive var childOb = observe(val) // 設(shè)置 getter/setter Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { var value = getter ? getter.call(obj) : val // Dep.target 為當(dāng)前 watch 實(shí)例 if (Dep.target) { // dep 為 obj[key] 對(duì)應(yīng)的調(diào)度中心 dep.depend 將當(dāng)前 wtcher 實(shí)例添加到調(diào)度中心 dep.depend() if (childOb) { // childOb.dep 為 obj[key] 值 val 對(duì)應(yīng)的 observer 實(shí)例的 dep // 實(shí)現(xiàn)array的變異方法和$set方法訂閱 childOb.dep.depend() } // TODO: 此處作用未知? if (isArray(value)) { for (var e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() } } } return value }, set: function reactiveSetter (newVal) { var value = getter ? getter.call(obj) : val // 通過 getter 獲取 val 判斷是否改變 if (newVal === value) { return } if (setter) { setter.call(obj, newVal) } else { val = newVal } // 為新值設(shè)置 reactive childOb = observe(newVal) // 通知key對(duì)應(yīng)的訂閱中心更新 dep.notify() } })}
訂閱中心的實(shí)現(xiàn)
let uid = 0export default function Dep () { this.id = uid++ // 訂閱調(diào)度中心的watch數(shù)組 this.subs = []}// 當(dāng)前watch實(shí)例Dep.target = null// 添加訂閱者Dep.prototype.addSub = function (sub) { this.subs.push(sub)}// 移除訂閱者Dep.prototype.removeSub = function (sub) { this.subs.$remove(sub)}// 訂閱Dep.prototype.depend = function () { // Dep.target.addDep(this) => this.addSub(Dep.target) => this.subs.push(Dep.target) Dep.target.addDep(this)}// 通知更新Dep.prototype.notify = function () { // stablize the subscriber list first var subs = this.subs.slice() for (var i = 0, l = subs.length; i < l; i++) { // subs[i].update() => watch.update() subs[i].update() }}
訂閱者的實(shí)現(xiàn)
export default function Watcher (vm, expOrFn, cb, options) { // mix in options if (options) { extend(this, options) } var isFn = typeof expOrFn === ’function’ this.vm = vm // vm 的 _watchers 包含了所有 watch vm._watchers.push(this) this.expression = expOrFn this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.lazy // for lazy watchers // deps 一個(gè) watch 實(shí)例可以對(duì)應(yīng)多個(gè) dep this.deps = [] this.newDeps = [] this.depIds = Object.create(null) this.newDepIds = null this.prevError = null // for async error stacks // parse expression for getter/setter if (isFn) { this.getter = expOrFn this.setter = undefined } else { warn(’vue-lite only supports watching functions.’) } this.value = this.lazy ? undefined : this.get() this.queued = this.shallow = false}Watcher.prototype.get = function () { this.beforeGet() var scope = this.scope || this.vm var value try { // 執(zhí)行 expOrFn,此時(shí)會(huì)觸發(fā) getter => dep.depend() 將watch實(shí)例添加到對(duì)應(yīng) obj[key] 的 dep value = this.getter.call(scope, scope) } if (this.deep) { // 深度watch // 觸發(fā)每個(gè)key的getter watch實(shí)例將對(duì)應(yīng)多個(gè)dep traverse(value) } // ... this.afterGet() return value}// 觸發(fā)getter,實(shí)現(xiàn)訂閱Watcher.prototype.beforeGet = function () { Dep.target = this this.newDepIds = Object.create(null) this.newDeps.length = 0}// 添加訂閱Watcher.prototype.addDep = function (dep) { var id = dep.id if (!this.newDepIds[id]) { // 將新出現(xiàn)的dep添加到newDeps中 this.newDepIds[id] = true this.newDeps.push(dep) // 如果已在調(diào)度中心,不再重復(fù)添加 if (!this.depIds[id]) { // 將watch添加到調(diào)度中心的數(shù)組中 dep.addSub(this) } }}Watcher.prototype.afterGet = function () { // 切除key的getter聯(lián)系 Dep.target = null var i = this.deps.length while (i--) { var dep = this.deps[i] if (!this.newDepIds[dep.id]) { // 移除不在expOrFn表達(dá)式中關(guān)聯(lián)的dep中watch的訂閱 dep.removeSub(this) } } this.depIds = this.newDepIds var tmp = this.deps this.deps = this.newDeps // TODO: 既然newDeps最終會(huì)被置空,這邊賦值的意義在于? this.newDeps = tmp}// 訂閱中心通知消息更新Watcher.prototype.update = function (shallow) { if (this.lazy) { this.dirty = true } else if (this.sync || !config.async) { this.run() } else { // if queued, only overwrite shallow with non-shallow, // but not the other way around. this.shallow = this.queued ? shallow ? this.shallow : false : !!shallow this.queued = true // record before-push error stack in debug mode /* istanbul ignore if */ if (process.env.NODE_ENV !== ’production’ && config.debug) { this.prevError = new Error(’[vue] async stack trace’) } // 添加到待執(zhí)行池 pushWatcher(this) }}// 執(zhí)行更新回調(diào)Watcher.prototype.run = function () { if (this.active) { var value = this.get() if ( ((isObject(value) || this.deep) && !this.shallow) ) { // set new value var oldValue = this.value this.value = value var prevError = this.prevError // ... this.cb.call(this.vm, value, oldValue) } this.queued = this.shallow = false }}Watcher.prototype.depend = function () { var i = this.deps.length while (i--) { this.deps[i].depend() }}
wtach回調(diào)執(zhí)行隊(duì)列
在上面我們可以發(fā)現(xiàn),watch在收到信息更新執(zhí)行update時(shí)。如果非同步情況下會(huì)執(zhí)行pushWatcher(this)將實(shí)例推入執(zhí)行池中,那么在何時(shí)會(huì)執(zhí)行回調(diào)函數(shù),如何執(zhí)行呢?我們一起看看pushWatcher的實(shí)現(xiàn)。
// batch.jsvar queueIndexvar queue = []var userQueue = []var has = {}var circular = {}var waiting = falsevar internalQueueDepleted = false// 重置執(zhí)行池function resetBatcherState () { queue = [] userQueue = [] // has 避免重復(fù) has = {} circular = {} waiting = internalQueueDepleted = false}// 執(zhí)行執(zhí)行隊(duì)列function flushBatcherQueue () { runBatcherQueue(queue) internalQueueDepleted = true runBatcherQueue(userQueue) resetBatcherState()}// 批量執(zhí)行function runBatcherQueue (queue) { for (queueIndex = 0; queueIndex < queue.length; queueIndex++) { var watcher = queue[queueIndex] var id = watcher.id // 執(zhí)行后置為null has[id] = null watcher.run() // in dev build, check and stop circular updates. if (process.env.NODE_ENV !== ’production’ && has[id] != null) { circular[id] = (circular[id] || 0) + 1 if (circular[id] > config._maxUpdateCount) { warn( ’You may have an infinite update loop for watcher ’ + ’with expression '’ + watcher.expression + ’'’, watcher.vm ) break } } }}// 添加到執(zhí)行池export function pushWatcher (watcher) { var id = watcher.id if (has[id] == null) { if (internalQueueDepleted && !watcher.user) { // an internal watcher triggered by a user watcher... // let’s run it immediately after current user watcher is done. userQueue.splice(queueIndex + 1, 0, watcher) } else { // push watcher into appropriate queue var q = watcher.user ? userQueue : queue has[id] = q.length q.push(watcher) // queue the flush if (!waiting) { waiting = true // 在nextick中執(zhí)行 nextTick(flushBatcherQueue) } } }}
4. patch實(shí)現(xiàn)
上面便是vue中數(shù)據(jù)驅(qū)動(dòng)的實(shí)現(xiàn)原理,下面我們接著回到主流程中,在執(zhí)行完watch后,便執(zhí)行this._update(this._watcher.value)開始節(jié)點(diǎn)渲染
// _update => createPatchFunction => patch => patchVnode => (dom api)// vtree是通過compile函數(shù)編譯的render函數(shù)執(zhí)行的結(jié)果,返回了當(dāng)前表示當(dāng)前dom結(jié)構(gòu)的對(duì)象(虛擬節(jié)點(diǎn)樹)_update (vtree) { if (!this._tree) { // 第一次渲染 patch(this._el, vtree) } else { patch(this._tree, vtree) } this._tree = vtree}// 在處理節(jié)點(diǎn)時(shí),需要針對(duì)class,props,style,attrs,events做不同處理// 在這里注入針對(duì)不同屬性的處理函數(shù)const patch = createPatchFunction([ _class, // makes it easy to toggle classes props, style, attrs, events])// => createPatchFunction返回patch函數(shù),patch函數(shù)通過對(duì)比虛擬節(jié)點(diǎn)的差異,對(duì)節(jié)點(diǎn)進(jìn)行增刪更新// 最后調(diào)用原生的dom api更新htmlreturn function patch (oldVnode, vnode) { var i, elm, parent var insertedVnodeQueue = [] // pre hook for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]() if (isUndef(oldVnode.sel)) { oldVnode = emptyNodeAt(oldVnode) } if (sameVnode(oldVnode, vnode)) { // someNode can patch patchVnode(oldVnode, vnode, insertedVnodeQueue) } else { // 正常的不復(fù)用 remove insert elm = oldVnode.elm parent = api.parentNode(elm) createElm(vnode, insertedVnodeQueue) if (parent !== null) { api.insertBefore(parent, vnode.elm, api.nextSibling(elm)) removeVnodes(parent, [oldVnode], 0, 0) } } for (i = 0; i < insertedVnodeQueue.length; ++i) { insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]) } // hook post for (i = 0; i < cbs.post.length; ++i) cbs.post[i]() return vnode}
結(jié)尾
以上分析了vue從template 到節(jié)點(diǎn)渲染的大致實(shí)現(xiàn),當(dāng)然也有某些地方?jīng)]有全面分析的地方,其中template解析為ast主要通過正則匹配實(shí)現(xiàn),及節(jié)點(diǎn)渲染及更新的patch過程主要通過節(jié)點(diǎn)操作對(duì)比來實(shí)現(xiàn)。但是我們對(duì)編譯template字符串 => 代理data數(shù)據(jù)/methods的this綁定 => 數(shù)據(jù)觀察 => 建立watch及更新渲染的大致流程有了個(gè)比較完整的認(rèn)知。
到此這篇關(guān)于淺談vue的第一個(gè)commit分析的文章就介紹到這了,更多相關(guān)vue commit內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
1. 完美解決vue 中多個(gè)echarts圖表自適應(yīng)的問題2. Java GZip 基于內(nèi)存實(shí)現(xiàn)壓縮和解壓的方法3. 利用CSS制作3D動(dòng)畫4. 存儲(chǔ)于xml中需要的HTML轉(zhuǎn)義代碼5. Springboot 全局日期格式化處理的實(shí)現(xiàn)6. jsp+servlet簡(jiǎn)單實(shí)現(xiàn)上傳文件功能(保存目錄改進(jìn))7. Python的文本常量與字符串模板之string庫(kù)8. .Net加密神器Eazfuscator.NET?2023.2?最新版使用教程9. SpringBoot+TestNG單元測(cè)試的實(shí)現(xiàn)10. JAMon(Java Application Monitor)備忘記
