摘要:巴拉巴拉省略大法,去除無(wú)關(guān)代碼巴拉巴拉省略大法,去除無(wú)關(guān)代碼核心就這一句話。文章鏈接源碼分析系列源碼分析系列之環(huán)境搭建源碼分析系列之入口文件分析源碼分析系列之響應(yīng)式數(shù)據(jù)一
前言
接著上一篇的初始化部分,我們細(xì)看initData中做了什么。
正文 initDatafunction initData (vm: Component) { let data = vm.$options.data // 獲得傳入的data.此處為{a:1, b:2} data = vm._data = typeof data === "function" ? getData(data, vm) : data || {} // 如果data不是純對(duì)象,則打印警告信息 if (!isPlainObject(data)) { data = {} process.env.NODE_ENV !== "production" && warn( "data functions should return an object: " + "https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function", vm ) } // 獲得data中所有的key,即a,b const keys = Object.keys(data) // 獲得組件中定義的props與methods const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i--) { // data中的第i個(gè)k const key = keys[i] // 如果methods中定義過(guò)相同的key,報(bào)錯(cuò) if (process.env.NODE_ENV !== "production") { if (methods && hasOwn(methods, key)) { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } // 如果props中定義過(guò)相同的key,報(bào)錯(cuò) if (props && hasOwn(props, key)) { process.env.NODE_ENV !== "production" && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) // 如果key不是保留關(guān)鍵字,即不是_或者$開(kāi)頭的key。 } else if (!isReserved(key)) { // 將屬性代理到實(shí)例的_data屬性上。 // 例如vm.a = 1實(shí)際上會(huì)被處理為vm._data.a = 1; // vm.a 將返回 vm._data.a; proxy(vm, `_data`, key) } } // 讓data變?yōu)轫憫?yīng)式數(shù)據(jù),即數(shù)據(jù)改變時(shí)候,UI也能跟著變。 observe(data, true /* asRootData */) }observe
export function observe (value: any, asRootData: ?boolean): Observer | void { // 如果設(shè)置的參數(shù)value不是一個(gè)對(duì)象或者是一個(gè)虛擬dom。則直接返回。 if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void // 如果這個(gè)value存在觀察者實(shí)例,則賦給返回值 if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( observerState.shouldConvert && // 需要轉(zhuǎn)化 !isServerRendering() && // 非服務(wù)端渲染 (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue // 是數(shù)組或者對(duì)象且可以擴(kuò)展屬性,且不是vue組件實(shí)例 ) { // 根據(jù)給定的值創(chuàng)建一個(gè)觀察者實(shí)例 ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } // 返回觀察者實(shí)例 return ob }new Observer(value)
export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that has this object as root $data constructor (value: any) { // 要被觀察的值 this.value = value // new 一個(gè)用來(lái)收集依賴的對(duì)象 this.dep = new Dep() this.vmCount = 0 // 將該Observer實(shí)例,賦值給value的__ob__屬性。 def(value, "__ob__", this) if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment // 如果是數(shù)組的話,需要對(duì)數(shù)組的方法做特殊處理, // 并且依次的讓數(shù)組的每個(gè)對(duì)象元素變?yōu)榭捎^察的 augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { // 否則是對(duì)象的話,依次遍歷每個(gè)屬性,并設(shè)置其 // getter與setter,支持后續(xù)的響應(yīng)式變化。 this.walk(value) } } /** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. */ walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } /** * Observe a list of Array items. */ observeArray (items: Arrayobserve 與 Observer簡(jiǎn)單說(shuō)明) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
observe使一個(gè)對(duì)象變?yōu)轫憫?yīng)式對(duì)象,在其值改變時(shí)候,可觸發(fā)ui改變。其做法是為每一個(gè)value(非原始類型的)設(shè)置一個(gè)__ob__,即Observer對(duì)象實(shí)例。如果value是一個(gè)對(duì)象,會(huì)遍歷它的每一個(gè)key,然后為其設(shè)置setter/getter,如果是數(shù)組會(huì)對(duì)數(shù)組元素依次的遞歸observe。
假設(shè)輸入為data = {a: 1, b: 2} 經(jīng)過(guò)observe后,會(huì)變成 data = {a: 1, b:2, __ob__: ObserverInstance, a的 getter,setter,b的getter, b的setter}。 其中ObserverInstance.dep用來(lái)收集對(duì)這個(gè)對(duì)象變更感興趣的watcher, 其中ObserverInstance.value指向這個(gè)對(duì)象data;defineReactive
為了以最簡(jiǎn)單的方式說(shuō)明流程,這里我們只看對(duì)象的處理情況,到了這里,我們的邏輯進(jìn)入walk函數(shù)內(nèi)部,遍歷對(duì)象的每個(gè)屬性,即a,b;分別定義為響應(yīng)式屬性。
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { // 這里的Dep還是Watcher收集器,用來(lái)收集對(duì)該變量感興趣的watcher const dep = new Dep() // 獲取屬性描述符,不熟悉的同學(xué)請(qǐng)查閱es6的文檔 const property = Object.getOwnPropertyDescriptor(obj, key) // 如果屬性描述符顯示該對(duì)象不可配置, // 即無(wú)法設(shè)置getter,setter,也就無(wú)法處理為響應(yīng)式屬性了,那直接返回。 // 一般我們定義的data里很少設(shè)置屬性描述符,默認(rèn)property => undefined if (property && property.configurable === false) { return } // cater for pre-defined getter/setters const getter = property && property.get const setter = property && property.set // 如果該obj的key,對(duì)應(yīng)的val還是對(duì)象,則使其也變成可觀察的對(duì)象 // 這里是一個(gè)遞歸處理,shallow標(biāo)識(shí)是否深度處理該值。 // 類似深拷貝,淺拷貝中是否深拷貝的邏輯。 let childOb = !shallow && observe(val) // 重點(diǎn),設(shè)置getter,setter,這里我們定義data.a的getter,setter, Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { // 這里一般對(duì)象的屬性是沒(méi)有g(shù)etter的,直接返回val const value = getter ? getter.call(obj) : val // 在特定的時(shí)機(jī)進(jìn)行依賴收集。通過(guò)Dep.target // 這里在后期vue處理模板template的時(shí)候,會(huì)生成render函數(shù), // render函數(shù)執(zhí)行生成虛擬dom時(shí)候會(huì)去讀取vm實(shí)例的屬性,比如vm.a這時(shí)候會(huì)觸發(fā)這個(gè)getter, // 此時(shí)的Dep.target為一個(gè)watcher, // 內(nèi)容為vm._update(vm._render)這個(gè)函數(shù),用來(lái)更新視圖用 // 將該函數(shù)添加到defineReactive內(nèi)部定義的dep中。 // 接下來(lái)我們看后面的set部分 if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { // 當(dāng)我們觸發(fā)點(diǎn)擊事件時(shí)候,this.a += 1; // 此時(shí)newVal是value值加1,所以代碼會(huì)繼續(xù)走 const 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 (process.env.NODE_ENV !== "production" && customSetter) { customSetter() } // 這里設(shè)置了newVal為val if (setter) { setter.call(obj, newVal) } else { val = newVal } // 如果新值是一個(gè)對(duì)象,會(huì)繼續(xù)被當(dāng)做可觀察的對(duì)象。 childOb = !shallow && observe(newVal) // dep.notify就是挨個(gè)通知Dep中收集的watcher去搞事情。 // get函數(shù)內(nèi)我提到,dep中被加入了一個(gè)watcher, // 其watcher實(shí)際作用就是觸發(fā)視圖更新,get時(shí)候,被收集了, // set時(shí)候就會(huì)觸發(fā)ui的更新。 dep.notify() } }) }initData的完結(jié)
至此initData完結(jié),上文中defineReactive的getter和setter的設(shè)定,在前期到不會(huì)觸發(fā),這里只是把規(guī)矩定下,真正用到的時(shí)候還需要跟模板結(jié)合。這個(gè)章節(jié)我們主要分析一下initData對(duì)data的處理。接下來(lái)我們看下模板的地方。
首先我們需要把代碼跳出來(lái)看這里,不要問(wèn)我為什么知道看這里,因?yàn)槲沂强赐暌槐楹笥袀€(gè)印象,現(xiàn)在只不過(guò)在梳理流程。
this.init -> initState -> initData this.init -> initState -> this.init this.init -> vm.$mount(vm.options.el)
這里我們?cè)儋N一下init內(nèi)部函數(shù)的代碼,讓大家對(duì)大概的方位有個(gè)了解
Vue.prototype._init = function (options?: Object) { // ***************************省略頂部代碼 initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, "created") /* istanbul ignore if */ if (process.env.NODE_ENV !== "production" && config.performance && mark) { vm._name = formatComponentName(vm, false) mark(endTag) measure(`vue ${vm._name} init`, startTag, endTag) } if (vm.$options.el) { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! vm.$mount(vm.$options.el) } }vm.$mount
接下來(lái)我們簡(jiǎn)述一下vm.$mount內(nèi)部的代碼
const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { // 根據(jù)id查找dom元素 el = el && query(el) // 巴拉巴拉省略魔法 // 如果沒(méi)有render函數(shù)則生成render函數(shù) const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns return mount.call(this, el, hydrating) }compileToFunctions生成的render函數(shù)
compileToFunctions是怎么根據(jù)模板生成html的,這不本章節(jié)的重點(diǎn),后面我會(huì)多帶帶去寫(xiě)compileToFunctions的過(guò)程,本章節(jié)重點(diǎn)是render函數(shù)的結(jié)果,我以上一章節(jié)的html為例,這是render函數(shù)的結(jié)果。
(function anonymous() { with (this) { return _c("div", { attrs: { "id": "demo" } }, [_c("div", [_c("p", [_v("a:" + _s(a))]), _v(" "), _c("p", [_v("b: " + _s(b))]), _v(" "), _c("p", [_v("a+b: " + _s(total))]), _v(" "), _c("button", { on: { "click": addA } }, [_v("a+1")])])]) } })
其中_c就是vue封裝的createElement,用來(lái)生成虛擬dom。_s就是toString方法,等等。這里我們可以看到其中有些參數(shù)是變量,a,b,total。這與我們?cè)趈s中定義的一致。接下來(lái)我們看下這個(gè)渲染函數(shù)執(zhí)行的地方。
mount.call首先還是接上面的代碼。mount.call(this, el, hydrating)。
Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }mountComponent
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el // 巴拉巴拉省略大法,去除無(wú)關(guān)代碼 callHook(vm, "beforeMount") // 巴拉巴拉省略大法,去除無(wú)關(guān)代碼 let updateComponent /* istanbul ignore if */ updateComponent = () => { vm._update(vm._render(), hydrating) } // 核心就這一句話。new 一個(gè)Watcher,上文多次提到的家伙。 vm._watcher = new Watcher(vm, updateComponent, noop) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, "mounted") } return vm }new Watcher()
上個(gè)代碼片段核心就這一句話。
vm._watcher = new Watcher(vm, updateComponent, noop)。
重點(diǎn)是這個(gè)Watcher第二個(gè)參數(shù),updateComponent,很重要。
new Watcher我們著重看下構(gòu)造函數(shù)內(nèi)部的代碼即可,如下是精簡(jiǎn)過(guò)后的代碼
export default class Watcher { vm: Component; expression: string; cb: Function; id: number; deep: boolean; user: boolean; lazy: boolean; sync: boolean; dirty: boolean; active: boolean; deps: Array總結(jié); newDeps: Array ; depIds: ISet; newDepIds: ISet; getter: Function; value: any; constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: Object ) { this.vm = vm vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync } else { this.deep = this.user = this.lazy = this.sync = false } // 此時(shí)expOrFn為上述的updateComponent this.getter = expOrFn // 由于此時(shí)lazy是falsly值,觸發(fā)get this.value = this.lazy ? undefined : this.get() } get () { // 該函數(shù)將Dep.target置為當(dāng)前watcher。 pushTarget(this) let value const vm = this.vm try { // 調(diào)用getter實(shí)際是調(diào)用updateComponent // 由于updateComponent會(huì)調(diào)用options.render,所以會(huì)觸發(fā) // vm._render函數(shù),而vm._render函數(shù)中的核心則是 // vnode = render.call(vm, vm.$createElement) // 在 compileToFunctions生成的render函數(shù) 一節(jié)我們已經(jīng)看到了一個(gè)rendre函數(shù)大概的面貌 // 此時(shí)render函數(shù)中有時(shí)候會(huì)取讀取vm.a的值。有時(shí)會(huì)取讀取vm.b的值。 // 當(dāng)讀取vm.a或者b的時(shí)候,就會(huì)觸發(fā)對(duì)應(yīng)屬性的getter // 然后會(huì)將當(dāng)前的Watcher加入屬性對(duì)應(yīng)的dep中。 // 聯(lián)系不起來(lái)的同學(xué)可以往回看,defineReactive中的dep收集的就是當(dāng)前watcher了。 // 當(dāng)用戶點(diǎn)擊頁(yè)面的a+1按鈕時(shí),則會(huì)觸發(fā)this.a += 1。 // 則會(huì)觸發(fā)defineReactive(obj, a, {get,set})中的set, // set中會(huì)調(diào)用dep.notify。其實(shí)就是讓dep收集到的watcher挨個(gè)執(zhí)行 // 下述中的run方法. // 而run方法又觸發(fā)了當(dāng)前的這個(gè)get方法,執(zhí)行到getter.call的時(shí)候,界面就更新了。 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 } run () { if (this.active) { // 關(guān)鍵一句就是獲取值,實(shí)際上這里的獲取值就是 // get -> this.get -> updateComponent -> 虛擬節(jié)點(diǎn)中重新獲取 // 界面中需要的a,b的值。 const 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 const 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) } } } }
此時(shí)一個(gè)大概的關(guān)于data是如何影響view的流程基本跑通了。以界面數(shù)據(jù)a為例,
核心思想就是defineReactive(data, a, {get,set})去設(shè)置屬性a的getter,setter。
getter將會(huì)在vue執(zhí)行render函數(shù)生成虛擬dom的時(shí)候,將界面更新的watcher放入a的dep中。
當(dāng)鼠標(biāo)單擊界面上的a+1按鈕觸發(fā)this.a += 1時(shí)候,會(huì)觸發(fā)a的setter函數(shù),此時(shí)會(huì)將getter時(shí)收集的依賴————更新界面的watcher————觸發(fā)。watcher執(zhí)行自身的run方法,即更新界面。
至此data -> view這一層算是通了,至于input中的v-model實(shí)際上是input + onInput事件的語(yǔ)法糖,監(jiān)聽(tīng)input,值改變時(shí)候通過(guò)事件修改vm.a的值,進(jìn)一步觸發(fā)————更新界面的watcher,使界面更新。
文章鏈接vue源碼分析系列
vue源碼分析系列之debug環(huán)境搭建
vue源碼分析系列之入口文件分析
vue源碼分析系列之響應(yīng)式數(shù)據(jù)(一)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/101647.html
摘要:代碼初始化部分一個(gè)的時(shí)候做了什么當(dāng)我們一個(gè)時(shí),實(shí)際上執(zhí)行了的構(gòu)造函數(shù),這個(gè)構(gòu)造函數(shù)內(nèi)部掛載了很多方法,可以在我的上一篇文章中看到。合并構(gòu)造函數(shù)上掛載的與當(dāng)前傳入的非生產(chǎn)環(huán)境,包裝實(shí)例本身,在后期渲染時(shí)候,做一些校驗(yàn)提示輸出。 概述 在使用vue的時(shí)候,data,computed,watch是一些經(jīng)常用到的概念,那么他們是怎么實(shí)現(xiàn)的呢,讓我們從一個(gè)小demo開(kāi)始分析一下它的流程。 dem...
摘要:執(zhí)行當(dāng)時(shí)傳入的回調(diào),并將新值與舊值一并傳入。文章鏈接源碼分析系列源碼分析系列之環(huán)境搭建源碼分析系列之入口文件分析源碼分析系列之響應(yīng)式數(shù)據(jù)一源碼分析系列之響應(yīng)式數(shù)據(jù)二源碼分析系列之響應(yīng)式數(shù)據(jù)三 前言 上一節(jié)著重講述了initComputed中的代碼,以及數(shù)據(jù)是如何從computed中到視圖層的,以及data修改后如何作用于computed。這一節(jié)主要記錄initWatcher中的內(nèi)容。 ...
摘要:目標(biāo)是為了可以調(diào)試版本的,也就是下的源碼,所以主要是的開(kāi)啟。結(jié)語(yǔ)至此就可以開(kāi)心的研究源碼啦。文章鏈接源碼分析系列源碼分析系列之入口文件分析源碼分析系列之響應(yīng)式數(shù)據(jù)一源碼分析系列之響應(yīng)式數(shù)據(jù)二 概述 為了探究vue的本質(zhì),所以想debug一下源碼,但是怎么開(kāi)始是個(gè)問(wèn)題,于是有了這樣一篇記錄。目標(biāo)是為了可以調(diào)試es6版本的,也就是src下的源碼,所以主要是sourceMap的開(kāi)啟。原文來(lái)自...
摘要:中引入了中的中引入了中的中,定義了的構(gòu)造函數(shù)中的原型上掛載了方法,用來(lái)做初始化原型上掛載的屬性描述符,返回原型上掛載的屬性描述符返回原型上掛載與方法,用來(lái)為對(duì)象新增刪除響應(yīng)式屬性原型上掛載方法原型上掛載事件相關(guān)的方法。 入口尋找 入口platforms/web/entry-runtime-with-compiler中import了./runtime/index導(dǎo)出的vue。 ./r...
摘要:本系列文章旨在化繁為簡(jiǎn),通讀源碼,描述背后的實(shí)現(xiàn)邏輯。記錄方式主要是代碼注釋文章鏈接源碼分析系列之環(huán)境搭建源碼分析系列之入口文件分析源碼分析系列之響應(yīng)式數(shù)據(jù)一源碼分析系列之響應(yīng)式數(shù)據(jù)二 概述 在使用vue的時(shí)候,會(huì)遇到很多神奇的地方,比如 修改vue實(shí)例中data對(duì)象的屬性值,會(huì)觸發(fā)dom值的改變;改變dom中的輸入,會(huì)觸發(fā)data對(duì)應(yīng)屬性的改變,即雙向數(shù)據(jù)綁定。 通過(guò)watch可以...
閱讀 1025·2021-11-22 13:52
閱讀 936·2019-08-30 15:44
閱讀 580·2019-08-30 15:43
閱讀 2436·2019-08-30 12:52
閱讀 3484·2019-08-29 16:16
閱讀 645·2019-08-29 13:05
閱讀 2951·2019-08-26 18:36
閱讀 2006·2019-08-26 13:46