摘要:通知某屬性改變,遍歷所有的訂閱者,就是實例,然后調(diào)用實例的方法些方法最核心的部分就是通過調(diào)用給的每個屬性添加和方法。
vue生命周期圖示:
Vue.js最顯著的功能就是響應(yīng)式系統(tǒng),它是一個典型的MVVM框架,Model只是普通的Javascript對象,修改它則View會自動更新,這種設(shè)計讓狀態(tài)管理變得非常簡單而直觀。
如何追蹤變化?我們先看一個簡單的例子:
count:{{times}}
運行后,我們可以從頁面中看到,count后面的times每隔一秒遞增1,視圖一直在更新,在代碼中僅僅是通過setInterval方法每隔一秒來修改vm.times的值,并沒有任何dom操作,那么vue是怎么實現(xiàn)這個過程呢,我們通過一張圖來看下:
圖中的Model就是data方法返回的{times:1},View是最終在瀏覽器中顯示的DOM,模型通過Observer,Dep,Watcher,Directive等一系列對象的關(guān)聯(lián),最終和視圖建立起關(guān)系。總的來說,vue在些做了3件事:
通過Observer對data做監(jiān)聽,并提供了訂閱某個數(shù)據(jù)項變化的能力。
把template編譯成一段document fragment,然后解析其中的Directive,得到每一個Directive所依賴的數(shù)據(jù)項和update方法。
通過Watcher把上述2部分結(jié)合起來,即把Directive中的數(shù)據(jù)依賴通過Watcher訂閱在對應(yīng)數(shù)據(jù)的Observer的Dep上,當(dāng)數(shù)據(jù)變化時,就會觸發(fā)Observer的Dep上的notify方法通知對應(yīng)的Watcher的update,進(jìn)而觸發(fā)Directive的update方法來更新dom視圖,最后達(dá)到模型和視圖關(guān)聯(lián)起來。
Observer我們來看下vue是如何給data對象添加Observer的,我們知道,vue的實例創(chuàng)建的過程會有一個生命周期,其中有一個過程就是調(diào)用vm.initData方法處理data選項,代碼如下:
src/core/instance/state.js function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === "function" ? getData(data, vm) : data || {} 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 ) } // proxy data on instance const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i--) { const key = keys[i] if (process.env.NODE_ENV !== "production") { if (methods && hasOwn(methods, key)) { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } 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 ) } else if (!isReserved(key)) { proxy(vm, `_data`, key) } } // observe data observe(data, true /* asRootData */) }
我們要注意下proxy方法,它的功能是遍歷data的key,把data上的屬性代理到vm實例上,proxy源碼如下:
const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop } function proxy (target: Object, sourceKey: string, key: string) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) }
方法主要通過Object.defineProperty的getter和setter方法實現(xiàn)了代理,在 前面的例子中,我們調(diào)用vm.times就相當(dāng)于訪問了vm._data.times.
在initData的最后,我們調(diào)用了observer(data,this)來對data做監(jiān)聽,observer的源碼定義如下:
src/core/observer/index.js /** *嘗試創(chuàng)建一個值的觀察者實例, *如果成功觀察到新的觀察者, *或現(xiàn)有的觀察者,如果該值已經(jīng)有一個。 */ export function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( observerState.shouldConvert && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob }
它會首先判斷value是否已經(jīng)添加了_ob_屬性,它是一個Observer對象的實例,如果是就直接用,否則在value滿足一些條件(數(shù)組或?qū)ο螅蓴U(kuò)展,非vue組件等)的情況下創(chuàng)建一個Observer對象,下面看下Observer這個類的源碼:
class Observer { value: any; dep: Dep; vmCount: number; // 將這個對象作為根$data的vm的數(shù)量。 constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, "__ob__", this) if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { this.walk(value) } } /** *遍歷每個屬性并將其轉(zhuǎn)換為擁有g(shù)etter / setter。這個方法應(yīng)該只在什么時候調(diào)用。當(dāng)obj是對象。 */ walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } /** 觀察數(shù)組項的列表。 */ observeArray (items: Array) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
這個構(gòu)造函數(shù)主要做了這么幾件事,首先創(chuàng)建了一個Dep對象實例,然后把自身this添加到value的_ob_屬性上,最后對value的類型進(jìn)行判斷,如果是數(shù)組則觀察數(shù)組,否則觀察單個元素。obsersverArray方法就是對數(shù)組進(jìn)行遍歷,遞歸調(diào)用observer方法,最終都會調(diào)用walk方法觀察單個元素。walk方法就是對obj的key進(jìn)行遍歷。然后調(diào)用了defineReactive,把要觀察的data對象的每個屬性都賦予getter和setter方法,這樣一旦屬性被訪問或者更新,我們就可以追蹤到這些變化。源碼如下:
/** * 在對象上定義一個反應(yīng)性屬性 setter,getter。 */ export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // 滿足預(yù)定義的getter / setter const getter = property && property.get const setter = property && property.set let childOb = !shallow && observe(val) // 在這里添加setter,getter。 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { 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() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) // 通知data某屬性改變,遍歷所有的訂閱者,就是watcher實例,然后調(diào)用watcher實例的update方法 dep.notify() } }) }
些方法最核心的部分就是通過調(diào)用Object.defineProperty給data的每個屬性添加getter和setter方法。當(dāng)data的某個屬性被訪問時,則會調(diào)用getter方法,判斷當(dāng)Dep.target不為空時調(diào)用dep.depend和childOb.dep.depend方法做依賴收集,如果訪問的屬性是一個數(shù)組則會遍歷這個數(shù)組收集數(shù)組元素的依賴,當(dāng)改變data的屬性時,則會調(diào)用setter方法,這時調(diào)用dep.notify方法進(jìn)行通知。其中用到的Dep類是一個簡單的觀察者模式的實現(xiàn),然后我們看下源碼:
src/core/observer/dep.js class Dep { static target: ?Watcher; id: number; subs: Arraywatcher; constructor () { this.id = uid++ this.subs = [] // 用來存儲所有訂閱它的watcher } addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } depend () { if (Dep.target) { // Dep.target表示當(dāng)前正在計算的watcher,是全局唯一的,同一時間只能有一個watcher被計算 // 把當(dāng)前Dep的實例添加到當(dāng)前正在計算的watcher依賴中 Dep.target.addDep(this) } } // 遍歷所有的的訂閱watcher,調(diào)用它們的update方法。 notify () { // 首先穩(wěn)定用戶列表。 const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
src/core/observer/watcher.js let uid = 0 /** * 觀察者解析表達(dá)式,收集依賴項, *當(dāng)表達(dá)式值發(fā)生變化時觸發(fā)回調(diào)。 這用于$watch() api和指令。 */ export default class Watcher { 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 } this.cb = cb this.id = ++uid // uid 為批處理 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") { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} 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 ) } } this.value = this.lazy ? undefined : this.get() } /** * 評估getter并重新收集依賴項。 */ 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 { // “觸摸”每個屬性,所以它們都被跟蹤。 // 對深度觀察的依賴。 if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value } /** * Add a dependency to this directive.把dep添加到watcher實例的依賴中,同時通過 dep.addsup(this)把watcher實例添加到dep的訂閱者中。 */ addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } } /** * Clean up for dependency collection. */ cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 } /** *用戶界面。時將調(diào)用一個依賴的變化。 */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { // 調(diào)用,把watcher實例推入隊列中,延遲this.run調(diào)用的時機(jī)。 queueWatcher(this) } } /** * 調(diào)度器的工作界面。會被調(diào)度器調(diào)用。 * 再次對watcher進(jìn)行求值,重新收集依賴,接下來判斷求值結(jié)果和之前value的關(guān)系,如果不變,則什么也不做 * 此方法是directive實例創(chuàng)建watcher時傳入的,它對應(yīng)相關(guān)指令的update方法來真實更新dom。這樣就完成了數(shù)據(jù)更新到對應(yīng)視圖的變化過程。 * watcher把observer和directive關(guān)聯(lián)起來,實現(xiàn)了數(shù)據(jù)一旦更新,視圖就自動變化的效果。利用object.defineProperty實現(xiàn)了數(shù)據(jù)和視圖的綁定 */ run () { if (this.active) { const value = this.get() if ( value !== this.value || // 深入觀察和觀察對象/陣列甚至應(yīng)該開火。當(dāng)值相等時,因為值可以。有突變。 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) } } } } /** * 評估觀察者的價值。 這只會被稱為懶惰的觀察者。 */ evaluate () { this.value = this.get() // 對watcher進(jìn)行求值,同時收集依賴 this.dirty = false // 不會再對watcher求值,也不會再訪問計算屬性的getter方法了 } /** * 要看這個觀察者收集的所有數(shù)據(jù)。 */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** * Remove self from all dependencies" subscriber list. */ teardown () { if (this.active) { // remove self from vm"s watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } } }
Dep實例在初始化watcher時,會傳入指令的expression.在前面的例子中expression是times.get方法的功能是對當(dāng)前watcher進(jìn)行求值,收集依賴關(guān)系,設(shè)置Dep.target為當(dāng)前watcher的實例,this.getter.call(vm,vm),這個方法相當(dāng)于獲取vm.times,這樣就觸發(fā)了對象的getter.我們之前給data添加Observer時,通過上面defineReactive/Object.defineProperty給data對象的每一個屬性添加getter和setter了.
src/core/observer/index.js function defineReactive (obj,key,val,customSetter,shallow) { // 在這里添加setter,getter。 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value } }
當(dāng)獲取vm.times時,會執(zhí)行到get方法體內(nèi),由于我們在之前已經(jīng)設(shè)置了Dep.target為當(dāng)前Watcher實例,所以接下來就調(diào)用dep.depend()完成收集,它實際上是執(zhí)行了Dep.target.addDep(this),相當(dāng)于執(zhí)行了Watcher實例的addDep方法,把Dep添加到Watcher實例的依賴中。
src/observer/watcher.js addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } }
addDep是把dep添加到Watcher實例的依賴中,同時又通過dep.addSup(this)把Watcher實例添加到dep的訂閱者中
src/observer/dep.js addSub (sub: Watcher) { this.subs.push(sub) }
至此,指令完成了依賴收集,并且通過Watcher完成了對數(shù)據(jù)變化的訂閱。
我們再看下,當(dāng)data發(fā)生變化時,視圖是如何自動更新的,在前面的例子中,我們setInterval每隔一秒執(zhí)行一次vm.times++,數(shù)據(jù)改變會觸發(fā)對象的setter,執(zhí)行set方法體的代碼。
src/core/observer/index.js function defineReactive (obj,key,val,customSetter,shallow) { // 在這里添加setter,getter。 Object.defineProperty(obj, key, { enumerable: true, configurable: true, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val if (newVal === value || (newVal !== newVal && value !== value)) { return } if (process.env.NODE_ENV !== "production" && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) // 通知data某屬性改變,遍歷所有的訂閱者,就是watcher實例,然后調(diào)用watcher實例的update方法 dep.notify() } }
src/observer/watcher.js update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this)// 調(diào)用,把watcher實例推入隊列中,延遲this.run調(diào)用的時機(jī)。 } }
src/core/observer/scheduler.js /** * 把一個觀察者watcher推入觀察者隊列。 *將跳過具有重復(fù)id的作業(yè),除非它是。 *當(dāng)隊列被刷新時被推。 * 通過nextTick在下一個事件循環(huán)周期處理watcher隊列,是一種優(yōu)化手段。因為如果同時觀察的數(shù)據(jù)多次變化,比如同步執(zhí)行3次vm.time++,同時調(diào)用就會觸發(fā)3次dom操作 * 而推入隊列中等待下一個事件循環(huán)周期再操作隊列里的watcher,因為是同一個watcher,它只會調(diào)用一次watcher.run,從而只觸發(fā)一次dom操作。 */ export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { has[id] = true if (!flushing) { queue.push(watcher) } else { // 如果已經(jīng)刷新,則根據(jù)其id將監(jiān)視器拼接起來。 // 如果已經(jīng)超過了它的id,它將會立即運行。 let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } // 隊列的沖 if (!waiting) { waiting = true nextTick(flushSchedulerQueue) } } }
function flushSchedulerQueue () { flushing = true let watcher, id // 在刷新前排序隊列。 // 這確保: // 1。組件由父元素更新為子元素。(因為父母總是在孩子面前創(chuàng)建) // 2。組件的用戶觀察者在它的呈現(xiàn)觀察者之前運行(因為用戶觀察者是在渲染觀察者之前創(chuàng)建的 // 3。如果組件在父組件的監(jiān)視程序運行期間被銷毀, 它的觀察者可以跳過。 queue.sort((a, b) => a.id - b.id) // 不要緩存長度,因為可能會有更多的觀察者被推。 // 當(dāng)我們運行現(xiàn)有的觀察者時。遍歷queue中watcher的run方法 for (index = 0; index < queue.length; index++) { watcher = queue[index] id = watcher.id 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] > MAX_UPDATE_COUNT) { warn( "You may have an infinite update loop " + ( watcher.user ? `in watcher with expression "${watcher.expression}"` : `in a component render function.` ), watcher.vm ) break } } } // keep copies of post queues before resetting state const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() resetSchedulerState() // call component updated and activated hooks callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue) // devtool hook /* istanbul ignore if */ if (devtools && config.devtools) { devtools.emit("flush") } }
遍歷queue中Watcher的run方法,
src/core/observer/watcher.js run () { if (this.active) { const value = this.get() if ( value !== this.value || // 深入觀察和觀察對象/陣列甚至應(yīng)該開火。當(dāng)值相等時,因為值可以。有突變。 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) } } } }
run方法再次對Watcher求值,重新收集依賴,接下來判斷求值結(jié)果和之前value的關(guān)系,如果不變則什么也不做,如果變了則調(diào)用this.cb.call(this.vm,value,oldValue)方法,這個方法是Directive實例創(chuàng)建watcher時傳入的,它對應(yīng)相關(guān)指令的update方法來真實更新 DOM,這樣就完成了數(shù)據(jù)更新到對應(yīng)視圖的變化過程。Watcher巧妙的把Observer和Directive關(guān)聯(lián)起來,實現(xiàn)了數(shù)據(jù)一旦更新,視圖就會自動變化的效果,vue利用了Object.defineProperty這個核心技術(shù)實現(xiàn)了數(shù)據(jù)和視圖的綁定。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/93030.html
摘要:問題為什么修改即可觸發(fā)更新和的關(guān)聯(lián)關(guān)系官方介紹的官網(wǎng)文檔,對響應(yīng)式屬性的原理有一個介紹。因此本文在源碼層面,對響應(yīng)式原理進(jìn)行梳理,對關(guān)鍵步驟進(jìn)行解析。 描述 ?我們通過一個簡單的 Vue應(yīng)用 來演示 Vue的響應(yīng)式屬性: html: {{message}} js: let vm = new Vue({ el: #ap...
摘要:所以我今后打算把每一個內(nèi)容分成白話版和源碼版。有什么錯誤的地方,感謝大家能夠指出響應(yīng)式系統(tǒng)我們都知道,只要在實例中聲明過的數(shù)據(jù),那么這個數(shù)據(jù)就是響應(yīng)式的。什么是響應(yīng)式,也即是說,數(shù)據(jù)發(fā)生改變的時候,視圖會重新渲染,匹配更新為最新的值。 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 V...
摘要:在讀取訪問器屬性時,就會調(diào)用函數(shù),該函數(shù)負(fù)責(zé)返回有效的值在寫入訪問器屬性時,會調(diào)用函數(shù)并傳入新值,該函數(shù)負(fù)責(zé)決定如何處理數(shù)據(jù),但是這兩個函數(shù)不一定非要同時存在。 前言 Vue最明顯的特性之一便是它的響應(yīng)式系統(tǒng),其數(shù)據(jù)模型即是普通的 JavaScript 對象。而當(dāng)你讀取或?qū)懭胨鼈儠r,視圖便會進(jìn)行響應(yīng)操作。文章簡要闡述下其實現(xiàn)原理,如有錯誤,還請不吝指正。個人博客鏈接:hiybm.cn ...
寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關(guān)注公眾號也可以吧 【Vue原理】Props - 源碼版 今天記錄 Props 源碼流程,哎,這東西,就算是研究過了,也真是會隨著時間慢慢忘記的。 幸好我做...
摘要:對象用戶看到的對象用戶看到的是這個對象即是實際使用的對象實際使用的對象復(fù)制更新相應(yīng)的代碼實現(xiàn)對象代理響應(yīng)式原理前提官網(wǎng)說過,限于現(xiàn)代瀏覽器限制,無法監(jiān)測通過這種方式添加的屬性,所以,他的響應(yīng)式是建立在實例化對象的時候,預(yù)定義屬性的基礎(chǔ)上的。 1. Vue 對象 1.1 用戶看到的對象 var app = new Vue({ el: #app , /* * 用...
摘要:原型方法通過原型方法方法來掛載實例。當(dāng)響應(yīng)式屬性發(fā)生變化時,會通知依賴列表中的對象進(jìn)行更新。此時,對象執(zhí)行方法,重新渲染節(jié)點。在執(zhí)行過程中,如果需要讀取響應(yīng)式屬性,則會觸發(fā)響應(yīng)式屬性的。總結(jié)響應(yīng)式屬性的原理 vue實例 初始化 完成以后,接下來就要進(jìn)行 掛載。 vue實例掛載,即為將vue實例對應(yīng)的 template模板,渲染成 Dom節(jié)點。 原型方法 - $mount ? 通過原...
閱讀 3462·2021-11-22 12:00
閱讀 679·2019-08-29 13:24
閱讀 2911·2019-08-29 11:31
閱讀 2599·2019-08-26 14:00
閱讀 3200·2019-08-26 11:42
閱讀 2481·2019-08-23 18:31
閱讀 806·2019-08-23 18:27
閱讀 2854·2019-08-23 16:58