摘要:源碼學(xué)習(xí)從學(xué)習(xí)的內(nèi)部做了什么源碼版本為接前文。本文將從這個簡單的例子追本溯源,看看究竟做了什么。也是組件相關(guān),因此剩下四個是我們關(guān)心的。至此的響應(yīng)式雛形基本完成。里面執(zhí)行了首次渲染。
[email protected]源碼學(xué)習(xí)---從hello world學(xué)習(xí)vue的內(nèi)部做了什么
源碼版本為2.0.0
接前文。
前文講到下面五個函數(shù)擴展了Vue的原型
initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)
我畫了一個圖,是執(zhí)行這幾個mixin之后,Vue原型掛載的方法
一個簡單的例子window.app = new Vue({ data: { msg: "hello world", }, render (h) { return h("p", this.msg) } }).$mount("#root") setTimeout(() => { app.msg = "hi world" }, 2000)
毫無疑問屏幕上會先渲染hello world,隔兩秒后變?yōu)閔i world。
本文將從這個簡單的例子追本溯源,看看Vue究竟做了什么。
我們沿著執(zhí)行順序一步一步的看,上文已經(jīng)找到了Vue的構(gòu)造函數(shù)如下:
function Vue (options) { if (process.env.NODE_ENV !== "production" && !(this instanceof Vue)) { warn("Vue is a constructor and should be called with the `new` keyword") } this._init(options) }
所以執(zhí)行new Vue()的時候,實例(vm)會首先執(zhí)行初始化方法vm._init(),_init方法如下:
Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ // a flag to avoid this being observed vm._isVue = true // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { // console.log(resolveConstructorOptions(vm)) vm.$options = mergeOptions( resolveConstructorOptions(vm), options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== "production") { initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm initLifecycle(vm) initEvents(vm) callHook(vm, "beforeCreate") initState(vm) callHook(vm, "created") initRender(vm) }
由于本文是初步探索Vue,所以并沒有涉及到組件這個概念,但是我拷貝過來的代碼中會經(jīng)常出現(xiàn)與組件邏輯相關(guān)的代碼,直接略過即可。
執(zhí)行初始化操作首先給實例添加了幾個私有屬性,然后merge了options,vm.$options最終變?yōu)檫@樣
vm.$options = { components: [..], directives: [], filters: [], vm: vm, data: {}, render: function() {} }
真正重要的操作是下面的幾個init函數(shù)
initLifecycle(vm) 初始化生命周期 initEvents(vm) 初始化事件系統(tǒng)(這里面做的是父子組件通信的工作,所以這篇文章暫時略過) callHook(vm, "beforeCreate") 執(zhí)行beforeCreate鉤子 initState(vm) 初始化狀態(tài)(包括data、computed、methods、watch) callHook(vm, "created") 執(zhí)行created鉤子 initRender(vm) 渲染頁面
從上面可以看到,created鉤子執(zhí)行的時機是在數(shù)據(jù)被observe之后(此時數(shù)據(jù)還沒有收集依賴)??匆幌耤allHook函數(shù):
export function callHook (vm: Component, hook: string) { const handlers = vm.$options[hook] if (handlers) { for (let i = 0, j = handlers.length; i < j; i++) { handlers[i].call(vm) } } vm.$emit("hook:" + hook) }
handle中的this綁定了vm
下面依次分析幾個初始化函數(shù)做的工作
initLifecycleexport function initLifecycle (vm: Component) { const options = vm.$options // locate first non-abstract parent let parent = options.parent if (parent && !options.abstract) { while (parent.$options.abstract && parent.$parent) { parent = parent.$parent } parent.$children.push(vm) } vm.$parent = parent vm.$root = parent ? parent.$root : vm vm.$children = [] vm.$refs = {} vm._watcher = null vm._inactive = false vm._isMounted = false vm._isDestroyed = false vm._isBeingDestroyed = false }
這里沒什么好說的,vm._watcher和vm._isMounted后面會用到
initEvents這里做的是父子組件通信的相關(guān)工作,不在本篇的討論范圍內(nèi)。
initStateexport function initState (vm: Component) { vm._watchers = [] initProps(vm) initData(vm) initComputed(vm) initMethods(vm) initWatch(vm) }
initProps也是組件相關(guān),因此剩下四個是我們關(guān)心的。核心initData完成了數(shù)據(jù)的observe
1) initDatafunction initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === "function" ? data.call(vm) : data || {} if (!isPlainObject(data)) { data = {} process.env.NODE_ENV !== "production" && warn( "data functions should return an object.", vm ) } // proxy data on instance const keys = Object.keys(data) const props = vm.$options.props let i = keys.length while (i--) { // data中的字段不能和props中的重復(fù) if (props && hasOwn(props, keys[i])) { process.env.NODE_ENV !== "production" && warn( `The data property "${keys[i]}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else { // 代理 proxy(vm, keys[i]) } } // observe data observe(data) data.__ob__ && data.__ob__.vmCount++ }
首先代理data里面的字段:
在vue中通常這樣訪問一個值
this.msg 而不是 this._data.msg
正是因為proxy(vm, keys[i])已經(jīng)對key值做了代理,如下:
function proxy (vm: Component, key: string) { if (!isReserved(key)) { Object.defineProperty(vm, key, { configurable: true, enumerable: true, get: function proxyGetter () { // 訪問vm[key]返回的事實上是vm._data[key] return vm._data[key] }, set: function proxySetter (val) { // 設(shè)置vm[key]事實上給vm._data[key]賦值 vm._data[key] = val } }) } }
接下來就是對數(shù)據(jù)observe(本文暫不考慮數(shù)組),數(shù)據(jù)的observe可以說是Vue的核心,網(wǎng)上很多文章已經(jīng)介紹的十分詳細,這里我把observe簡化一下如下:
export function observe (value) { if (!isObject(value)) { return } let ob = new Observer(value) return ob } export class Observer { constructor (value) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, "__ob__", this) this.walk(value) } walk (obj) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } } export function defineReactive (obj, key, val) { const dep = new Dep() let childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, // 取值時給數(shù)據(jù)添加依賴 get: function reactiveGetter () { const value = val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() } } return value }, // 賦值時通知數(shù)據(jù)依賴更新 set: function reactiveSetter (newVal) { const value = val if (newVal === value) { return } val = newVal childOb = observe(newVal) dep.notify() } }) }
整個響應(yīng)式系統(tǒng)的核心在于defineReactive這個函數(shù),利用了一個閉包把數(shù)據(jù)的依賴收集起來,下文我們會看到Dep.target事實上是一個個watcher。
這里有個需要注意的地方:
if (childOb) { childOb.dep.depend() }
為什么閉包里的dep已經(jīng)收集過了依賴,這里還要加上這句代碼?先看一個例子
data: { name: { first: "zhang" } }
假如數(shù)據(jù)是這樣,我們這樣改變數(shù)據(jù)
this.name.last = "san"
想一下這樣會出發(fā)依賴更新嗎?事實上是不會的,因為last并沒有被監(jiān)聽。Vue給我們指明了正確的姿勢是:
this.$set("name", "last", "san")
來看一下set的源碼(為方便,我已把數(shù)組相關(guān)的代碼刪掉)
export function set (obj: Array| Object, key: any, val: any) { if (hasOwn(obj, key)) { obj[key] = val return } const ob = obj.__ob__ if (!ob) { obj[key] = val return } // 對新增的屬性進行監(jiān)聽 defineReactive(ob.value, key, val) ob.dep.notify() return val }
想一下,this.name變化時講道理是應(yīng)該通知閉包內(nèi)name的依賴更新,但是由于新增屬性并不會觸發(fā)defineReactive,而this.name.__ob__的依賴和name屬性的依賴是相同的,所以this.name.__ob__.notify()可達到相同的效果,這也是上面childOb.dep.depend()的原因。同理del也是如此:
export function del (obj: Object, key: string) { const ob = obj.__ob__ if (!hasOwn(obj, key)) { return } delete obj[key] if (!ob) { return } ob.dep.notify() }2)initWatch
function initWatch (vm: Component) { const watch = vm.$options.watch if (watch) { for (const key in watch) { const handler = watch[key] if (Array.isArray(handler)) { for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { createWatcher(vm, key, handler) } } } } function createWatcher (vm: Component, key: string, handler: any) { let options if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === "string") { handler = vm[handler] } vm.$watch(key, handler, options) }
可以看出來initWatch最終調(diào)用的是$watch
Vue.prototype.$watch = function ( expOrFn: string | Function, cb: Function, options?: Object ): Function { const vm: Component = this options = options || {} options.user = true const watcher = new Watcher(vm, expOrFn, cb, options) if (options.immediate) { cb.call(vm, watcher.value) } return function unwatchFn () { watcher.teardown() } }
最終實例化了一個Watcher,watcher可以分為兩種,一種是用戶定義的(我們在實例化Vue是傳入的watch選項),一種是Vue內(nèi)部自己實例化的,后文會看到。
watcher的代碼如下:
export default class Watcher { constructor (vm, expOrFn, cb, options) { this.vm = vm vm._watchers.push(this) // options this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync this.expression = expOrFn.toString() 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() // 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() } /** * Evaluate the getter, and re-collect dependencies. */ get () { pushTarget(this) const value = this.getter.call(this.vm, this.vm) // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() return value } /** * Add a dependency to this directive. */ addDep (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 } /** * Subscriber interface. * Will be called when a dependency changes. */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } /** * Scheduler job interface. * Will be called by the scheduler. */ run () { if (this.active) { 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) { process.env.NODE_ENV !== "production" && warn( `Error in watcher "${this.expression}"`, this.vm ) /* istanbul ignore else */ if (config.errorHandler) { config.errorHandler.call(null, e, this.vm) } else { throw e } } } else { this.cb.call(this.vm, value, oldValue) } } } } /** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */ evaluate () { this.value = this.get() this.dirty = false } /** * Depend on all deps collected by this watcher. */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** * Remove self from all dependencies" subcriber 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 or is performing a v-for // re-render (the watcher list is then filtered by v-for). if (!this.vm._isBeingDestroyed && !this.vm._vForRemoving) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } } }
代碼蠻長的,慢慢看
watcher實例有一個getter方法,我們上文提到過watcher有兩種,當(dāng)watcher是用戶創(chuàng)建時,此時的expOrFn就是一個expression,例如name或者name.first,此時它會被parsePath格式化為一個取值函數(shù)
const bailRE = /[^w.$]/ export function parsePath (path: string): any { if (bailRE.test(path)) { return } else { const segments = path.split(".") // obj為vue實例時 輸出的便是 return function (obj) { for (let i = 0; i < segments.length; i++) { if (!obj) return obj = obj[segments[i]] } return obj } } }
格式化完getter函數(shù)之后緊接著執(zhí)行g(shù)et方法,數(shù)據(jù)的依賴正是在watcher的get方法執(zhí)行時收集的,可以說get是連接observer和watcher的橋梁
get () { pushTarget(this) const value = this.getter.call(this.vm, this.vm) // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() return value }
get方法里面執(zhí)行了getter,前面已經(jīng)說過getter是一個取值函數(shù),這不禁令我們聯(lián)想到了數(shù)據(jù)的監(jiān)聽,當(dāng)取值時假如Dep.target存在那么就可以收集依賴了,想想就激動。既然這樣,pushTarget和popTarget必然是定義Dep.target
Dep.target = null const targetStack = [] export function pushTarget (_target: Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } export function popTarget () { Dep.target = targetStack.pop() }
如我們所想,pushTarget和popTarget定義了全局唯一的Dep.target(即調(diào)用get的watcher)。這里是需要思考的,源碼的寫法顯然表明當(dāng)getter函數(shù)調(diào)用時可能會觸發(fā)其他watcher的get方法,事實上當(dāng)我們watch一個計算屬性或者渲染一個計算屬性時便會出現(xiàn)這種情況,我們本篇暫不討論。
getter執(zhí)行后,data相應(yīng)閉包中的dep會執(zhí)行dep.depend(),最終watcher會被添加到dep的訂閱subs中,但data中的數(shù)據(jù)改變時,相應(yīng)閉包中dep會notify它的subs(即watcher)依次update,最終調(diào)用watcher的run方法實現(xiàn)更新,看一下run方法:
run () { if (this.active) { 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) { process.env.NODE_ENV !== "production" && warn( `Error in watcher "${this.expression}"`, this.vm ) /* istanbul ignore else */ if (config.errorHandler) { config.errorHandler.call(null, e, this.vm) } else { throw e } } } else { this.cb.call(this.vm, value, oldValue) } } } }
run方法執(zhí)行的時候會首先執(zhí)行g(shù)et方法,然后比較新的value的舊的value,如果不相同就執(zhí)行watcher.cb。至此Vue的響應(yīng)式雛形基本完成。
3)initComputed先看代碼(簡化了)
function initComputed (vm) { const computed = vm.$options.computed if (computed) { for (const key in computed) { const userDef = computed[key] computedSharedDefinition.get = makeComputedGetter(userDef, vm) Object.defineProperty(vm, key, computedSharedDefinition) } } } function makeComputedGetter (getter, owner) { const watcher = new Watcher(owner, getter, noop, { lazy: true }) return function computedGetter () { if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value } }
從代碼可以看到,計算屬性的值就是與之相關(guān)watcher的value。注意這里options的lazy為true,這表明創(chuàng)建watcher(稱為a)的時候并不會執(zhí)行g(shù)et方法,也就是不會收集依賴。只有當(dāng)我們?nèi)∮嬎銓傩缘闹档臅r候才會收集依賴,那么什么時候會取計算屬性的值呢?比如watch計算屬性或者把計算屬性寫進render函數(shù)中。因為此get是惰性的,所以依賴于其他watcher(稱為b)的喚醒,當(dāng)執(zhí)行完watcher.evaluate()之后,會把a添加到計算屬性依賴數(shù)據(jù)dep的subs中,當(dāng)執(zhí)行完watcher.depend()之后,會把這個b添加到計算屬性依賴數(shù)據(jù)dep的subs中。當(dāng)依賴數(shù)據(jù)變化時,a和b(至少有這兩個)watcher均會update,并且a的update是靠前的,因為其id在前面,所以當(dāng)b進行update時獲取到的計算屬性為更新后的。
這里比較繞,多想想吧。
initMethodsfunction initMethods (vm: Component) { const methods = vm.$options.methods if (methods) { for (const key in methods) { if (methods[key] != null) { vm[key] = bind(methods[key], vm) } else if (process.env.NODE_ENV !== "production") { warn(`Method "${key}" is undefined in options.`, vm) } } } }
這個沒什么好說的,就是將方法掛載到實例上。
initRenderinitRender里面執(zhí)行了首次渲染。
在進行下面的內(nèi)容之前我們先說明一下實例的_render方法,這個方法是根據(jù)render函數(shù)返回虛擬dom,什么是所謂的虛擬dom,看下Vue文檔的解釋:
它所包含的信息會告訴 Vue 頁面上需要渲染什么樣的節(jié)點,及其子節(jié)點。我們把這樣的節(jié)點描述為“虛擬節(jié)點 (Virtual Node)”,也常簡寫它為“VNode”?!疤摂M DOM”是我們對由 Vue 組件樹建立起來的整個 VNode 樹的稱呼。
至于vnode的生成原理不在本文的討論范圍。
進入正題,看下initRender的代碼:
export function initRender (vm: Component) { // 對于組件適用 其在父樹的占位 vm.$vnode = null // the placeholder node in parent tree // 虛擬dom vm._vnode = null // the root of the child tree vm._staticTrees = null vm._renderContext = vm.$options._parentVnode && vm.$options._parentVnode.context vm.$slots = resolveSlots(vm.$options._renderChildren, vm._renderContext) // bind the public createElement fn to this instance // so that we get proper render context inside it. // 這就是render函數(shù)里面我們傳遞的那個參數(shù) // 它的作用是生成vnode(虛擬dom) vm.$createElement = bind(createElement, vm) if (vm.$options.el) { vm.$mount(vm.$options.el) } }
initRender執(zhí)行了實例的$mount,而$mount實際上是調(diào)用的內(nèi)部方法_mount,現(xiàn)在來看_mount(簡化了)
Vue.prototype._mount = function (el, hydrating) { const vm = this vm.$el = el callHook(vm, "beforeMount") vm._watcher = new Watcher(vm, () => { vm._update(vm._render(), hydrating) }, noop) hydrating = false // root instance, call mounted on self // mounted is called for child components in its inserted hook // 假如vm是根實例 那么其$root屬性就是其自身 if (vm.$root === vm) { vm._isMounted = true callHook(vm, "mounted") } return vm }
_mount給我們提供了beforeMount和mounted兩個鉤子,可想而知實例化watcher的時候已經(jīng)生成了虛擬dom,并且根據(jù)虛擬dom創(chuàng)建了真實dom并掛載到了頁面上。
上文我們已經(jīng)講過watcher的創(chuàng)建過程,所以可知vm._watcher的getter函數(shù)即為
() => { vm._update(vm._render(), hydrating) }
并且此watcher的get并非為惰性get,所以watcher實例化之后便會立即執(zhí)行g(shù)et方法,事實上是執(zhí)行vm._render(),并將獲得的vnode作為參數(shù)傳給vm._update執(zhí)行。思考一下_render()函數(shù)執(zhí)行時會發(fā)生什么,顯然會獲取data的值,此時便會觸發(fā)get攔截器,從而將
vm._watcher添加至對應(yīng)dep的subs中。
vm._update代碼如下(簡化了):
Vue.prototype._update = function (vnode, hydrating) { const vm = this if (vm._isMounted) { callHook(vm, "beforeUpdate") } const prevVnode = vm._vnode vm._vnode = vnode if (!prevVnode) { // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. // 如果之前的虛擬dom不存在 說明是首次掛載 vm.$el = vm.__patch__(vm.$el, vnode, hydrating) } else { // 之前的虛擬dom存在 需要先對新舊虛擬dom對比 然后差異化更新 vm.$el = vm.__patch__(prevVnode, vnode) } if (vm._isMounted) { callHook(vm, "updated") } }
可以看到_update的主要作用就是根據(jù)vnode形成真實dom節(jié)點。當(dāng)data數(shù)據(jù)改變時,對應(yīng)的dep會通知subs即vm._watcher進行update,update方法中會再次執(zhí)行vm._watcher.get(),從而調(diào)用vm._update進行試圖的更新。
這里有個地方值得我們思考,更新后的視圖可能不再依賴于上次的數(shù)據(jù)了,什么意思呢
更新前{{this.a}}
更新后{{this.b}}
也就是說需要清除掉a數(shù)據(jù)中watcher的依賴。看下Vue中的實現(xiàn)
dep.depend并沒有我們想的那么簡單,如下
depend () { if (Dep.target) { Dep.target.addDep(this) } } addSub (sub: Watcher) { this.subs.push(sub) }
相應(yīng)的watcher的addDep如下,他會把本次更新依賴的dep的id存起來,如果更新前的id列表不存在新的dep的id,說明視圖更新后依賴于這個dep,于是將vm._watcher添加到此dep的subs中
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 dep.addSub(this) } } }
假如之前dep的id列表存在存在某些id,這些id不存在與更新后dep的id列表,表明更新后的視圖不在依賴于這些id對應(yīng)的dep,那么需要將vm._watcher從這些dep中移除,這部分工作是在cleanupDeps中完成的,如下:
cleanupDeps () { let i = this.deps.length // console.log(i) 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 }結(jié)語
這篇文章只是對Vue內(nèi)部實現(xiàn)機制的簡單探索,很多地方?jīng)]有涉及到,比如組件機制、模板的編譯、虛擬dom樹的創(chuàng)建等等,希望這些能在以后慢慢搞清楚。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/90376.html
摘要:源碼版本為原文地址和有必要了解這兩個概念的區(qū)別。點開目錄下的,發(fā)現(xiàn)確實是導(dǎo)出了一個構(gòu)造函數(shù)。再回過頭看,它給構(gòu)造函數(shù)擴展了一些方法具體的邏輯后文看。 前言 網(wǎng)上vue的源碼分析也蠻多的,不過很多都是1.0版本的并且大多都是在講數(shù)據(jù)的observe,索性自己看看源碼,雖然很難但是希望能學(xué)到點東西。 源碼版本為2.0.0 原文地址 runtime和runtime-with-compiler...
摘要:今年的月日,的版本正式發(fā)布了,其中核心代碼都進行了重寫,于是就專門花時間,對的源碼進行了學(xué)習(xí)。本篇文章就是源碼學(xué)習(xí)的總結(jié)。實現(xiàn)了并且將靜態(tài)子樹進行了提取,減少界面重繪時的對比。的最新源碼可以去獲得。 Vue2.0介紹 從去年9月份了解到Vue后,就被他簡潔的API所吸引。1.0版本正式發(fā)布后,就在業(yè)務(wù)中開始使用,將原先jQuery的功能逐步的進行遷移。 今年的10月1日,Vue的2...
摘要:定義一個組件如下打印如下再回過頭看,可以發(fā)現(xiàn)他做的工作就是擴展一個構(gòu)造函數(shù),并將這個構(gòu)造函數(shù)添加到現(xiàn)在我們已經(jīng)可以回答最開始的問題的組件是什么的組件其實就是擴展的構(gòu)造函數(shù),并且在適當(dāng)?shù)臅r候?qū)嵗癁閷嵗? [email protected]源碼學(xué)習(xí)---組件究竟是什么 本篇文章從最簡單的情況入手,不考慮prop和組件間通信。 Vue.component vue文檔告訴我們可以使用Vue.component(...
摘要:觀察員由模板解析指令創(chuàng)建的觀察員負責(zé)模板中的更新視圖操作。觀察員種類目前了解情況來看主要分三類視圖指令的計算屬性的用戶自定義的 介紹 關(guān)于 Vue.js 的原理一直以來都是一個話題。經(jīng)過幾天的源碼學(xué)習(xí)和資料介紹,我將一些個人理解的經(jīng)驗給寫下來,希望能夠與大家共勉。 附上GITHUB源碼地址, 如果有任何不解 可以在 文章下面提出或者寫下issue, 方便大家回答和學(xué)習(xí), 有興趣可以St...
摘要:毫無疑問,設(shè)計模式于己于他人于系統(tǒng)都是多贏的設(shè)計模式使代碼編寫真正工程化設(shè)計模小書前端掘金這是一本關(guān)于的小書。 JavaScript 常見設(shè)計模式解析 - 掘金設(shè)計模式(Design pattern)是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計經(jīng)驗的總結(jié)。使用設(shè)計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。毫無疑問,設(shè)計模式于己于他人于系統(tǒng)都是多贏的;設(shè)計...
閱讀 994·2021-11-04 16:08
閱讀 2973·2021-09-13 10:37
閱讀 507·2019-08-30 15:56
閱讀 1959·2019-08-30 15:55
閱讀 2239·2019-08-30 15:53
閱讀 2081·2019-08-30 13:13
閱讀 2922·2019-08-30 12:51
閱讀 1542·2019-08-29 16:06