摘要:最后判斷有無根節(jié)點,無則表示首次掛載,添加鉤子函數(shù),返回總結(jié)實例初始化掛載方法屬性初始化掛載過程在版本,生成函數(shù)對作處理,執(zhí)行中定義了通過實例化的回調(diào)執(zhí)行執(zhí)行,即調(diào)用了真實渲染成對象。
vue 入口
從vue的構(gòu)建過程可以知道,web環(huán)境下,入口文件在 src/platforms/web/entry-runtime-with-compiler.js(以Runtime + Compiler模式構(gòu)建,vue直接運行在瀏覽器進行編譯工作)
import Vue from "./runtime/index"
下一步,找到./runtime/index,發(fā)現(xiàn):
import Vue from "core/index"
下一步,找到core/index,發(fā)現(xiàn):
import Vue from "./instance/index"
按照這個思路找,最后發(fā)現(xiàn):Vue是在"core/index"下定義的
import { initMixin } from "./init" import { stateMixin } from "./state" import { renderMixin } from "./render" import { eventsMixin } from "./events" import { lifecycleMixin } from "./lifecycle" import { warn } from "../util/index" 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) } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) export default Vue
引入方法,用function定義了Vue類,再以Vue為參數(shù),調(diào)用了5個方法,最后導出了vue。
可以進入這5個文件查看相關方法,主要就是在Vue原型上掛載方法,可以看到,Vue 是把這5個方法按功能放入不同的模塊中,這很利于代碼的維護和管理
initGlobalAPI回到core/index.js, 看到除了引入已經(jīng)在原型上掛載方法后的 Vue 外,還導入initGlobalAPI 、 isServerRendering、FunctionalRenderContext,執(zhí)行initGlobalAPI(Vue),在vue.prototype上掛載$isServer、$ssrContext、FunctionalRenderContext,在vue 上掛載 version 屬性,
看到initGlobalAPI的定義,主要是往vue.config、vue.util等上掛載全局靜態(tài)屬性和靜態(tài)方法(可直接通過Vue調(diào)用,而不是實例調(diào)用),再把builtInComponents 內(nèi)置組件擴展到Vue.options.components下。此處大致了解下它是做什么的即可,后面用到再做具體分析。
new Vue()一般我們用vue都采用模板語法來聲明:
{{ message }}
var app = new Vue({ el: "#app", data: { message: "Hello Vue!" } })
當new Vue()時,vue做了哪些處理?
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實例化,否則報錯。實例化vue后,執(zhí)行了this._init(),該方法在通過initMixin(Vue)掛載在Vue原型上的,找到定義文件core/instance/init.js 查看該方法。
_init()一開始在this對象上定義_uid、_isVue,判斷options._isComponent,此次先不考慮options._isComponent為true的情況,走else,合并options,接著安裝proxy, 初始化生命周期,初始化事件、初始化渲染、初始化data、鉤子函數(shù)等,最后判斷有vm.$options.el則執(zhí)行vm.$mount(),即是把el渲染成最終的DOM。
初始化data 數(shù)據(jù)綁定_init()中通過initState()來綁定數(shù)據(jù)到vm上,看下initState的定義:
export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
獲取options,初始化props、methods、data、計算屬性、watch綁定到vm上,先來看下initData()是如何把綁定data的:
先判斷data是不是function類型,是則調(diào)用getData,返回data的自調(diào)用,不是則直接返回data,并將data賦值到vm._data上
對data、props、methods,作個校驗,防止出現(xiàn)重復的key,因為它們最終都會掛載到vm上,都是通過vm.key來調(diào)用
通過proxy(vm, `_data`, key)把每個key都掛載在vm上
export 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) } const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop }
proxy() 定義了一個get/set函數(shù),再通過Object.defineProperty定義修改屬性(不了解Object.defineProperty()的同學可以先看下文檔,通過Object.defineProperty()定義的屬性,通過描述符的設置可以進行更精準的控制對象屬性),將對target的key訪問加了一層get/set,即當訪問vm.key時,實際上是調(diào)用了sharedPropertyDefinition.get,返回this._data.key,這樣就實現(xiàn)了通過vm.key來調(diào)用vm._data上的屬性
最后,observe(data, true /* asRootData */) 觀察者,對數(shù)據(jù)作響應式處理,這也是vue的核心之一,此處先不分析
$mount() 實例掛載Vue的核心思想之一是數(shù)據(jù)驅(qū)動,在vue下,我們不會直接操作DOM,而是通過js修改數(shù)據(jù),所有邏輯只需要考慮對數(shù)據(jù)的修改,最后再把數(shù)據(jù)渲染成DOM。其中,$mount()就是負責把數(shù)據(jù)掛載到vm,再渲染成最終DOM。
接下來將會分析下 vue 是如何把javaScript對象渲染成dom元素的,和之前一樣,主要分析主線代碼
預處理還是從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) ··· }
首先將原先原型上的$mount方法緩存起來,再重新定義$mount:
先判斷 el ,el 不能是 body, html ,因為渲染出來的 DOM 最后是會替換掉el的
判斷render方法, 有的話直接調(diào)用mount.call(this, el, hydrating)
沒有render方法時:
判斷有沒有template ,有則用compileToFunctions將其編譯成render方法
沒有template時,則查看有沒有el,有轉(zhuǎn)換成template,再用compileToFunctions將其編譯成render方法
將render掛載到options下
最后調(diào)用 mount.call(this, el, hydrating),即是調(diào)用原先原型上的mount方法
我們發(fā)現(xiàn)這一系列調(diào)用都是為了生成render函數(shù),說明在vue中,所有的組件渲染最終都需要render方法(不管是單文件.vue還是el/template),vue 文檔里也提到:
Vue 選項中的 render 函數(shù)若存在,則 Vue 構(gòu)造函數(shù)不會從 template 選項或通過 el 選項指定的掛載元素中提取出的 HTML 模板編譯渲染函數(shù)。原先原型上的mount方法
找到原先原型上的mount方法,在src/platform/web/runtime/index.js中:
// 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方法,這么設計使得這個方法可以被 runtime only和runtime+compiler 版本共同使用
$mount 第一個參數(shù)el, 表示掛載的元素,在瀏覽器環(huán)境會通過query(el)獲取到dom對象,第二個參數(shù)和服務端渲染相關,不進行深入分析,此處不傳。接著調(diào)用mountComponent()
看下query(),比較簡單,當el 是string時,找到該選擇器返回dom對象,否則新創(chuàng)建個div dom對象,el是dom對象直接返回el.
mountComponentmountComponent定義在src/core/instance/lifecycle.js中,傳入vm,el,
將el緩存在vm.$el上
判斷有沒有render方法,沒有則直接把createEmptyVNode作為render函數(shù)
開發(fā)環(huán)境警告(沒有Render但有el/template不能使用runtime-only版本、render和template必須要有一個)
掛載beforeMount鉤子
定義 updateComponent , 渲染相關
updateComponent = () => { vm._update(vm._render(), hydrating) }
new Watcher() 實例化一個渲染watcher,簡單看下定義,
this.getter = expOrFn
把updateComponent掛載到this.getter上
this.value = this.lazy ? undefined : this.get()
get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) {...} return value }
執(zhí)行this.get(),則執(zhí)行了this.getter,即updateComponent,所以new Watcher()時會執(zhí)行updateComponent,也就會執(zhí)行到vm._update、vm._render方法。
因為之后不止初始化時需要渲染頁面,數(shù)據(jù)發(fā)生變化時也是要更新到dom上的,實例watcher可以實現(xiàn)對數(shù)據(jù)進行監(jiān)聽以及隨后的更新dom處理,watcher會在初始化執(zhí)行回調(diào),也會在數(shù)據(jù)變化時執(zhí)行回調(diào),此處先簡單介紹為什么要使用watcher,不深入分析watcher實現(xiàn)原理。
最后判斷有無根節(jié)點,無則表示首次掛載,添加mounted鉤子函數(shù) ,返回vm
總結(jié)實例初始化:new Vue()->掛載方法屬性->this._init->初始化data->$mount
掛載過程:(在complier版本,生成render函數(shù))對el作處理,執(zhí)行mountComponent,mountComponent中定義了updateComponent,通過實例化watcher的回調(diào)執(zhí)行updateComponent,執(zhí)行updateComponent,即調(diào)用了vm._update、vm._render真實渲染成dom對象。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/100853.html
摘要:上一篇文章我們寫到從入口文件一步步找到的構(gòu)造函數(shù),現(xiàn)在我們要去看看實例化經(jīng)歷的過程的構(gòu)造函數(shù)我們知道的構(gòu)造函數(shù)在中不明白的可以去看上一篇文章源碼學習筆記一。 上一篇文章我們寫到從入口文件一步步找到Vue的構(gòu)造函數(shù),現(xiàn)在我們要去看看Vue實例化經(jīng)歷的過程 Vue的構(gòu)造函數(shù) 我們知道Vue的構(gòu)造函數(shù)在src/core/instance/index.js中,不明白的可以去看上一篇文章 Vue...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之掛載組件由這篇文章從模 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理從模板到的簡要流程今天的計劃是, 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基...
摘要:圖在中應用三數(shù)據(jù)渲染過程數(shù)據(jù)綁定實現(xiàn)邏輯本節(jié)正式分析從到數(shù)據(jù)渲染到頁面的過程,在中定義了一個的構(gòu)造函數(shù)。一、概述 vue已是目前國內(nèi)前端web端三分天下之一,也是工作中主要技術棧之一。在日常使用中知其然也好奇著所以然,因此嘗試閱讀vue源碼并進行總結(jié)。本文旨在梳理初始化頁面時data中的數(shù)據(jù)是如何渲染到頁面上的。本文將帶著這個疑問一點點追究vue的思路??傮w來說vue模版渲染大致流程如圖1所...
摘要:作者王聰本篇目的是介紹實例化到掛載到的整體路線,一些細節(jié)會被省略。從源碼中找到構(gòu)造函數(shù)的聲明,是一個很簡潔的工廠模式聲明的一個構(gòu)造函數(shù)。內(nèi)部做了邏輯判斷構(gòu)造函數(shù)調(diào)用必須有關鍵字。代表的是當前實例也就是構(gòu)造函數(shù)被調(diào)用后的指向。 作者:王聰本篇目的是介紹vue實例化到掛載到dom的整體路線,一些細節(jié)會被省略。 從new Vue()開始 所有的一切都是從 new Vue()開始的,所以從這個...
閱讀 898·2021-09-22 15:18
閱讀 1264·2021-09-09 09:33
閱讀 2822·2019-08-30 10:56
閱讀 1266·2019-08-29 16:30
閱讀 1558·2019-08-29 13:02
閱讀 1518·2019-08-26 13:55
閱讀 1703·2019-08-26 13:41
閱讀 2018·2019-08-26 11:56