摘要:數據驅動一個核心思想是數據驅動。發生了什么從入口代碼開始分析,我們先來分析背后發生了哪些事情。函數最后判斷為根節點的時候設置為,表示這個實例已經掛載了,同時執行鉤子函數。這里注意表示實例的父虛擬,所以它為則表示當前是根的實例。
數據驅動
Vue.js 一個核心思想是數據驅動。所謂數據驅動,是指視圖是由數據驅動生成的,我們對視圖的修改,不會直接操作 DOM,而是通過修改數據。它相比我們傳統的前端開發,如使用 jQuery 等前端庫直接修改 DOM,大大簡化了代碼量。特別是當交互復雜的時候,只關心數據的修改會讓代碼的邏輯變的非常清晰,因為 DOM 變成了數據的映射,我們所有的邏輯都是對數據的修改,而不用碰觸 DOM,這樣的代碼非常利于維護。
在 Vue.js 中我們可以采用簡潔的模板語法來聲明式的將數據渲染為 DOM:
{{ message }}var app = new Vue({ el: "#app", data: { message: "Hello Vue!" } })
最終它會在頁面上渲染出 Hello Vue。接下來,我們會從源碼角度來分析 Vue 是如何實現的,分析過程會以主線代碼為主,重要的分支邏輯會放在之后多帶帶分析。數據驅動還有一部分是數據更新驅動視圖變化,這一塊內容我們也會在之后的章節分析,這一章我們的目標是弄清楚模板和數據如何渲染成最終的 DOM。
new Vue 發生了什么從入口代碼開始分析,我們先來分析 new Vue 背后發生了哪些事情。我們都知道,new 關鍵字在 Javascript 語言中代表實例化是一個對象,而 Vue 實際上是一個類,類在 Javascript 中是用 Function 來實現的,來看一下源碼,在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 只能通過 new 關鍵字初始化,然后會調用 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: 關于vue源碼的調試技巧
在webpack的vue工程中有以下配置文件
webpack.base.conf.js
resolve: { extensions: [".js", ".vue", ".json"], alias: { "vue$": "vue/dist/vue.esm.js", "@": resolve("src"), } },
指向的真實vue源碼是,node_modules 里的vue工程下的路徑vue/dist/vue.esm.js。需要調試的代碼插入debugger來斷點
Vue 實例掛載的實現Vue 中我們是通過 $mount 實例方法去掛載 vm 的,$mount 方法在多個文件中都有定義,如 src/platform/web/entry-runtime-with-compiler.js、src/platform/web/runtime/index.js、src/platform/weex/runtime/index.js。因為 $mount 這個方法的實現是和平臺、構建方式都相關的。接下來我們重點分析帶 compiler 版本的 $monut 實現,因為拋開 webpack 的 vue-loader,我們在純前端瀏覽器環境分析 Vue 的工作原理,有助于我們對原理理解的深入。
先來看一下 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 方法,再重新定義該方法,我們先來分析這段代碼。首先,它對 el 做了限制,Vue 不能掛載在 body、html 這樣的根節點上。接下來的是很關鍵的邏輯 —— 如果沒有定義 render 方法,則會把 el 或者 template 字符串轉換成 render 方法。這里我們要牢記,在 Vue 2.0 版本中,所有 Vue 的組件的渲染最終都需要 render 方法,無論我們是用單文件 .vue 方式開發組件,還是寫了 el 或者 template 屬性,最終都會轉換成 render 方法,那么這個過程是 Vue 的一個“在線編譯”的過程,它是調用 compileToFunctions 方法實現的,編譯過程我們之后會介紹。最后,調用原先原型上的 $mount 方法掛載。
原先原型上的 $mount 方法在 src/platform/web/runtime/index.js 中定義,之所以這么設計完全是為了復用,因為它是可以被 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 方法實際上會去調用 mountComponent 方法,這個方法定義在 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 核心就是先調用 vm._render 方法先生成虛擬 Node,再實例化一個渲染Watcher,在它的回調函數中會調用 updateComponent 方法,最終調用 vm._update 更新 DOM。
Watcher 在這里起到兩個作用,一個是初始化的時候會執行回調函數,另一個是當 vm 實例中的監測的數據發生變化的時候執行回調函數,這塊兒我們會在之后的章節中介紹。
函數最后判斷為根節點的時候設置 vm._isMounted 為 true, 表示這個實例已經掛載了,同時執行 mounted 鉤子函數。 這里注意 vm.$vnode 表示 Vue 實例的父虛擬 Node,所以它為 Null 則表示當前是根 Vue 的實例。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/100083.html
摘要:五六月份推薦集合查看最新的請點擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...
摘要:五六月份推薦集合查看最新的請點擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...
摘要:有一點要注意的是,暴露的方法最好不要依賴,因為它可能經常會發生變化,是不穩定的。 從入口開始 我們之前提到過 Vue.js 構建過程,在 web 應用下,我們來分析 Runtime + Compiler 構建出來的 Vue.js,它的入口是 src/platforms/web/entry-runtime-with-compiler.js: 摘選entry-runtime-with-co...
摘要:特意對前端學習資源做一個匯總,方便自己學習查閱參考,和好友們共同進步。 特意對前端學習資源做一個匯總,方便自己學習查閱參考,和好友們共同進步。 本以為自己收藏的站點多,可以很快搞定,沒想到一入匯總深似海。還有很多不足&遺漏的地方,歡迎補充。有錯誤的地方,還請斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應和斧正,會及時更新,平時業務工作時也會不定期更...
閱讀 1800·2021-11-22 09:34
閱讀 3083·2019-08-30 15:55
閱讀 663·2019-08-30 15:53
閱讀 2054·2019-08-30 15:52
閱讀 3000·2019-08-29 18:32
閱讀 1989·2019-08-29 17:15
閱讀 2392·2019-08-29 13:14
閱讀 3557·2019-08-28 18:05