摘要:數(shù)據(jù)驅(qū)動(dòng)一個(gè)核心思想是數(shù)據(jù)驅(qū)動(dòng)。發(fā)生了什么從入口代碼開始分析,我們先來(lái)分析背后發(fā)生了哪些事情。函數(shù)最后判斷為根節(jié)點(diǎn)的時(shí)候設(shè)置為,表示這個(gè)實(shí)例已經(jīng)掛載了,同時(shí)執(zhí)行鉤子函數(shù)。這里注意表示實(shí)例的父虛擬,所以它為則表示當(dāng)前是根的實(shí)例。
數(shù)據(jù)驅(qū)動(dòng)
Vue.js 一個(gè)核心思想是數(shù)據(jù)驅(qū)動(dòng)。所謂數(shù)據(jù)驅(qū)動(dòng),是指視圖是由數(shù)據(jù)驅(qū)動(dòng)生成的,我們對(duì)視圖的修改,不會(huì)直接操作 DOM,而是通過(guò)修改數(shù)據(jù)。它相比我們傳統(tǒng)的前端開發(fā),如使用 jQuery 等前端庫(kù)直接修改 DOM,大大簡(jiǎn)化了代碼量。特別是當(dāng)交互復(fù)雜的時(shí)候,只關(guān)心數(shù)據(jù)的修改會(huì)讓代碼的邏輯變的非常清晰,因?yàn)?DOM 變成了數(shù)據(jù)的映射,我們所有的邏輯都是對(duì)數(shù)據(jù)的修改,而不用碰觸 DOM,這樣的代碼非常利于維護(hù)。
在 Vue.js 中我們可以采用簡(jiǎn)潔的模板語(yǔ)法來(lái)聲明式的將數(shù)據(jù)渲染為 DOM:
{{ message }}var app = new Vue({ el: "#app", data: { message: "Hello Vue!" } })
最終它會(huì)在頁(yè)面上渲染出 Hello Vue。接下來(lái),我們會(huì)從源碼角度來(lái)分析 Vue 是如何實(shí)現(xiàn)的,分析過(guò)程會(huì)以主線代碼為主,重要的分支邏輯會(huì)放在之后多帶帶分析。數(shù)據(jù)驅(qū)動(dòng)還有一部分是數(shù)據(jù)更新驅(qū)動(dòng)視圖變化,這一塊內(nèi)容我們也會(huì)在之后的章節(jié)分析,這一章我們的目標(biāo)是弄清楚模板和數(shù)據(jù)如何渲染成最終的 DOM。
new Vue 發(fā)生了什么從入口代碼開始分析,我們先來(lái)分析 new Vue 背后發(fā)生了哪些事情。我們都知道,new 關(guān)鍵字在 Javascript 語(yǔ)言中代表實(shí)例化是一個(gè)對(duì)象,而 Vue 實(shí)際上是一個(gè)類,類在 Javascript 中是用 Function 來(lái)實(shí)現(xiàn)的,來(lái)看一下源碼,在src/core/instance/index.js 中。
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) }
可以看到 Vue 只能通過(guò) new 關(guān)鍵字初始化,然后會(huì)調(diào)用 this._init 方法, 該方法在 src/core/instance/init.js 中定義
Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ if (process.env.NODE_ENV !== "production" && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } // 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 { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), 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) initRender(vm) callHook(vm, "beforeCreate") initInjections(vm) // resolve injections before data/props 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) } }
Vue 初始化主要就干了幾件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等等
tips: 關(guān)于vue源碼的調(diào)試技巧
在webpack的vue工程中有以下配置文件
webpack.base.conf.js
resolve: { extensions: [".js", ".vue", ".json"], alias: { "vue$": "vue/dist/vue.esm.js", "@": resolve("src"), } },
指向的真實(shí)vue源碼是,node_modules 里的vue工程下的路徑vue/dist/vue.esm.js。需要調(diào)試的代碼插入debugger來(lái)斷點(diǎn)
Vue 實(shí)例掛載的實(shí)現(xiàn)Vue 中我們是通過(guò) $mount 實(shí)例方法去掛載 vm 的,$mount 方法在多個(gè)文件中都有定義,如 src/platform/web/entry-runtime-with-compiler.js、src/platform/web/runtime/index.js、src/platform/weex/runtime/index.js。因?yàn)?$mount 這個(gè)方法的實(shí)現(xiàn)是和平臺(tái)、構(gòu)建方式都相關(guān)的。接下來(lái)我們重點(diǎn)分析帶 compiler 版本的 $monut 實(shí)現(xiàn),因?yàn)閽侀_ webpack 的 vue-loader,我們?cè)诩兦岸藶g覽器環(huán)境分析 Vue 的工作原理,有助于我們對(duì)原理理解的深入。
先來(lái)看一下 src/platform/web/entry-runtime-with-compiler.js 文件中定義:
const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) /* istanbul ignore if */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== "production" && warn( `Do not mount Vue to or - mount to normal elements instead.` ) return this } const options = this.$options // resolve template/el and convert to render function if (!options.render) { let template = options.template if (template) { if (typeof template === "string") { if (template.charAt(0) === "#") { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== "production" && !template) { warn( `Template element not found or is empty: ${options.template}`, this ) } } } else if (template.nodeType) { template = template.innerHTML } else { if (process.env.NODE_ENV !== "production") { warn("invalid template option:" + template, this) } return this } } else if (el) { template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== "production" && config.performance && mark) { mark("compile") } const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== "production" && config.performance && mark) { mark("compile end") measure(`vue ${this._name} compile`, "compile", "compile end") } } } return mount.call(this, el, hydrating) }
這段代碼首先緩存了原型上的 $mount 方法,再重新定義該方法,我們先來(lái)分析這段代碼。首先,它對(duì) el 做了限制,Vue 不能掛載在 body、html 這樣的根節(jié)點(diǎn)上。接下來(lái)的是很關(guān)鍵的邏輯 —— 如果沒(méi)有定義 render 方法,則會(huì)把 el 或者 template 字符串轉(zhuǎn)換成 render 方法。這里我們要牢記,在 Vue 2.0 版本中,所有 Vue 的組件的渲染最終都需要 render 方法,無(wú)論我們是用單文件 .vue 方式開發(fā)組件,還是寫了 el 或者 template 屬性,最終都會(huì)轉(zhuǎn)換成 render 方法,那么這個(gè)過(guò)程是 Vue 的一個(gè)“在線編譯”的過(guò)程,它是調(diào)用 compileToFunctions 方法實(shí)現(xiàn)的,編譯過(guò)程我們之后會(huì)介紹。最后,調(diào)用原先原型上的 $mount 方法掛載。
原先原型上的 $mount 方法在 src/platform/web/runtime/index.js 中定義,之所以這么設(shè)計(jì)完全是為了復(fù)用,因?yàn)樗强梢员?runtime only 版本的 Vue 直接使用的。
// public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
$mount 方法實(shí)際上會(huì)去調(diào)用 mountComponent 方法,這個(gè)方法定義在 src/core/instance/lifecycle.js 文件中:
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el if (!vm.$options.render) { vm.$options.render = createEmptyVNode if (process.env.NODE_ENV !== "production") { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== "#") || vm.$options.el || el) { warn( "You are using the runtime-only build of Vue where the template " + "compiler is not available. Either pre-compile the templates into " + "render functions, or use the compiler-included build.", vm ) } else { warn( "Failed to mount component: template or render function not defined.", vm ) } } } callHook(vm, "beforeMount") let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== "production" && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}` const endTag = `vue-perf-end:${id}` mark(startTag) const vnode = vm._render() mark(endTag) measure(`vue ${name} render`, startTag, endTag) mark(startTag) vm._update(vnode, hydrating) mark(endTag) measure(`vue ${name} patch`, startTag, endTag) } } else { updateComponent = () => { vm._update(vm._render(), hydrating) } } // we set this to vm._watcher inside the watcher"s constructor // since the watcher"s initial patch may call $forceUpdate (e.g. inside child // component"s mounted hook), which relies on vm._watcher being already defined new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted) { callHook(vm, "beforeUpdate") } } }, true /* isRenderWatcher */) 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 }
從上面的代碼可以看到,mountComponent 核心就是先調(diào)用 vm._render 方法先生成虛擬 Node,再實(shí)例化一個(gè)渲染W(wǎng)atcher,在它的回調(diào)函數(shù)中會(huì)調(diào)用 updateComponent 方法,最終調(diào)用 vm._update 更新 DOM。
Watcher 在這里起到兩個(gè)作用,一個(gè)是初始化的時(shí)候會(huì)執(zhí)行回調(diào)函數(shù),另一個(gè)是當(dāng) vm 實(shí)例中的監(jiān)測(cè)的數(shù)據(jù)發(fā)生變化的時(shí)候執(zhí)行回調(diào)函數(shù),這塊兒我們會(huì)在之后的章節(jié)中介紹。
函數(shù)最后判斷為根節(jié)點(diǎn)的時(shí)候設(shè)置 vm._isMounted 為 true, 表示這個(gè)實(shí)例已經(jīng)掛載了,同時(shí)執(zhí)行 mounted 鉤子函數(shù)。 這里注意 vm.$vnode 表示 Vue 實(shí)例的父虛擬 Node,所以它為 Null 則表示當(dāng)前是根 Vue 的實(shí)例。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/100083.html
摘要:五六月份推薦集合查看最新的請(qǐng)點(diǎn)擊集前端最近很火的框架資源定時(shí)更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語(yǔ)。葉上初陽(yáng)乾宿雨,水面清圓,一一風(fēng)荷舉。家住吳門,久作長(zhǎng)安旅。五月漁郎相憶否。小楫輕舟,夢(mèng)入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請(qǐng)::點(diǎn)擊::集web前端最近很火的vue2框架資源;定時(shí)更新,歡迎 Star 一下。 蘇...
摘要:五六月份推薦集合查看最新的請(qǐng)點(diǎn)擊集前端最近很火的框架資源定時(shí)更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語(yǔ)。葉上初陽(yáng)乾宿雨,水面清圓,一一風(fēng)荷舉。家住吳門,久作長(zhǎng)安旅。五月漁郎相憶否。小楫輕舟,夢(mèng)入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請(qǐng)::點(diǎn)擊::集web前端最近很火的vue2框架資源;定時(shí)更新,歡迎 Star 一下。 蘇...
摘要:有一點(diǎn)要注意的是,暴露的方法最好不要依賴,因?yàn)樗赡芙?jīng)常會(huì)發(fā)生變化,是不穩(wěn)定的。 從入口開始 我們之前提到過(guò) Vue.js 構(gòu)建過(guò)程,在 web 應(yīng)用下,我們來(lái)分析 Runtime + Compiler 構(gòu)建出來(lái)的 Vue.js,它的入口是 src/platforms/web/entry-runtime-with-compiler.js: 摘選entry-runtime-with-co...
摘要:特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒(méi)想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請(qǐng)斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...
閱讀 1815·2021-11-22 09:34
閱讀 3100·2019-08-30 15:55
閱讀 678·2019-08-30 15:53
閱讀 2068·2019-08-30 15:52
閱讀 3010·2019-08-29 18:32
閱讀 2002·2019-08-29 17:15
閱讀 2406·2019-08-29 13:14
閱讀 3566·2019-08-28 18:05