入口開(kāi)始解讀Vue源碼系列(三)——initMixin

作者:muwoo
轉(zhuǎn)發(fā)鏈接:
https://github.com/muwoo/blogs/blob/master/src/Vue/3.md目錄
入口開(kāi)始解讀Vue源碼系列(一)——造物創(chuàng)世
入口開(kāi)始解讀Vue源碼系列(二)——new Vue 的故事
入口開(kāi)始解讀Vue源碼(三)—— initMixin 本篇
入口開(kāi)始解讀Vue源碼系列(四)——實(shí)現(xiàn)一個(gè)基礎(chǔ)的 Vue 雙向綁定
入口開(kāi)始解讀Vue源碼系列(五)——$mount 內(nèi)部實(shí)現(xiàn)
入口開(kāi)始解讀Vue源碼系列(六)$mount 實(shí)現(xiàn)compile parse生成AST
入口開(kāi)始解讀Vue源碼系列(七)$mount 實(shí)現(xiàn)compile optimize標(biāo)記
入口開(kāi)始解讀Vue源碼(八)—— $mount 內(nèi)部實(shí)現(xiàn) --- compile generate 生成render函數(shù)
入口開(kāi)始解讀Vue源碼(九)—— $mount 內(nèi)部實(shí)現(xiàn) --- render函數(shù) --> VNode
入口開(kāi)始解讀Vue源碼(十)—— $mount 內(nèi)部實(shí)現(xiàn) --- patch
前言
這邊文章我們主要去解讀 initMixin后續(xù)的執(zhí)行過(guò)程,上篇我們已經(jīng)可以看到,接下來(lái)主要會(huì)發(fā)生這幾個(gè)操作過(guò)程:
initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, beforeCreate) initInjections(vm) initState(vm) initProvide(vm) callHook(vm, created)在我們一個(gè)個(gè)了解之前,首先我們需要了解一下響應(yīng)式數(shù)據(jù)原理,也就是我們常說(shuō)的:訂閱-發(fā)布 模式。

先從 Observer 開(kāi)始談起
Observer
export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that has this object as root $data 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) } } /** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. */ walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } /** * Observe a list of Array items. */ observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }當(dāng)我們調(diào)用new Observer(value)的時(shí)候,會(huì)去執(zhí)行this.walk(value)這個(gè)方法,其功能主要是遍歷value的屬性,通過(guò)Object.defineProperty方法來(lái)定義響應(yīng)式數(shù)據(jù)。那么是如何實(shí)現(xiàn)響應(yīng)通知的呢?來(lái)看一下它的具體代碼:
defineReactive
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() ... Object.defineProperty(obj, key, { ... get: function reactiveGetter () { ... dep.depend() ... return value }, set: function reactiveSetter (newVal) { ... dep.notify() } }) }這里我們省略了部分代碼,主要注意Dep 這個(gè)東西,也就是我們常說(shuō)的訂閱器,這里主要做了2件事情:
dep.depend()dep.notify()Dep
通過(guò)上面的圖,我們知道,Dom 上通過(guò)指令或者雙大括號(hào)綁定的數(shù)據(jù),會(huì)為數(shù)據(jù)進(jìn)行添加觀察者watcher,當(dāng)實(shí)例化Watcher的時(shí)候 會(huì)觸發(fā)屬性的getter方法,此時(shí)會(huì)調(diào)用dep.depend()。我們看一下depend方法:
depend () { if (Dep.target) { Dep.target.addDep(this) } }Dep.target 是什么東西呢?其實(shí)在進(jìn)行Watcher實(shí)例化的時(shí)候,會(huì)調(diào)用內(nèi)部的get函數(shù),開(kāi)始為他初始化
get () { pushTarget(this) ... }Watcher
其中pushTarget 方法就是為Dep.target綁定此watcher實(shí)例,所以Dep.target.addDep(this)也就是執(zhí)行此實(shí)例中的addDep方法
addDep (dep: Dep) { ... dep.addSub(this) }這樣便為我們的dep實(shí)例添加了一個(gè)watcher實(shí)例。
接著當(dāng)我們更新data的時(shí)候,會(huì)觸發(fā)他的set方法,執(zhí)行dep.notify()函數(shù):
notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }這里也就是遍歷dep中收集到的watcher實(shí)例,進(jìn)行update()。也就是進(jìn)行數(shù)據(jù)更新操作。這也就是簡(jiǎn)單的數(shù)據(jù)響應(yīng)式,其實(shí)還需要注意的是: 當(dāng)數(shù)據(jù)的getter觸發(fā)后,會(huì)收集依賴,但也不是所有的觸發(fā)方式都會(huì)收集依賴,只有通過(guò)watcher觸發(fā)的getter會(huì)收集依賴:if (Dep.target) { dep.depend() },而所謂的被收集的依賴就是當(dāng)前watcher,DOM中的數(shù)據(jù)必須通過(guò)watcher來(lái)綁定,只通過(guò)watcher來(lái)讀取。
書(shū)接上文,我們一個(gè)個(gè)了解一下后面的執(zhí)行過(guò)程。
1. initLifecycle
export function initLifecycle (vm: Component) { const options = vm.$options /** * 這里判斷是否存在父示例,如果存在,則通過(guò) while 循環(huán),建立所有組建的父子關(guān)系 */ let parent = options.parent if (parent && !options.abstract) { while (parent.$options.abstract && parent.$parent) { parent = parent.$parent } parent.$children.push(vm) } /** * 為組件實(shí)例掛載相應(yīng)屬性,并初始化 */ vm.$parent = parent vm.$root = parent ? parent.$root : vm vm.$children = [] vm.$refs = {} vm._watcher = null vm._inactive = null vm._directInactive = false vm._isMounted = false vm._isDestroyed = false vm._isBeingDestroyed = false }2. initEvents
export function initEvents (vm: Component) { /** * 創(chuàng)建事件對(duì)象,用于存儲(chǔ)事件 */ vm._events = Object.create(null) /** * 這里應(yīng)該是系統(tǒng)事件標(biāo)識(shí)位 */ vm._hasHookEvent = false // init parent attached events // _parentListeners其實(shí)是父組件模板中寫(xiě)的v-on // 所以下面這段就是將父組件模板中注冊(cè)的事件放到當(dāng)前組件實(shí)例的listeners里面 const listeners = vm.$options._parentListeners if (listeners) { updateComponentListeners(vm, listeners) } }3. initRender
export function initRender (vm: Component) { vm._vnode = null // the root of the child tree const options = vm.$options const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree const renderContext = parentVnode && parentVnode.context // 處理組件slot,返回slot插槽對(duì)象 vm.$slots = resolveSlots(options._renderChildren, renderContext) vm.$scopedSlots = emptyObject // bind the createElement fn to this instance // so that we get proper render context inside it. // args order: tag, data, children, normalizationType, alwaysNormalize // internal version is used by render functions compiled from templates vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // normalization is always applied for the public version, used in // user-written render functions. vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) /** * 定義v2.4中新增的$attrs及$listeners屬性,需要為其綁定響應(yīng)式數(shù)據(jù)更新 */ // $attrs & $listeners are exposed for easier HOC creation. // they need to be reactive so that HOCs using them are always updated const parentData = parentVnode && parentVnode.data /* istanbul ignore else */ if (process.env.NODE_ENV !== production) { defineReactive(vm, $attrs, parentData && parentData.attrs || emptyObject, () => { !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm) }, true) defineReactive(vm, $listeners, options._parentListeners || emptyObject, () => { !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm) }, true) } else { defineReactive(vm, $attrs, parentData && parentData.attrs || emptyObject, null, true) defineReactive(vm, $listeners, options._parentListeners || emptyObject, null, true) } }可以看到,initRender函數(shù)主要是為我們的組件實(shí)例,初始化一些渲染屬性,比如$slots和$createElement等。
4. callHook
export function callHook (vm: Component, hook: string) { const handlers = vm.$options[hook] if (handlers) { for (let i = 0, j = handlers.length; i < j; i++) { try { handlers[i].call(vm) } catch (e) { handleError(e, vm, `${hook} hook`) } } } if (vm._hasHookEvent) { vm.$emit(hook: + hook) } }這里是調(diào)用鉤子函數(shù)的方法,也就是觸發(fā)之前我們options中定義的相應(yīng)的生命周期函數(shù)。進(jìn)行到此處便開(kāi)始調(diào)用了beforeCreate鉤子函數(shù)
5. initInjections 和 initProvide
可以先來(lái)看一下官網(wǎng)的描述:
提示:provide 和 inject 綁定并不是可響應(yīng)的。這是刻意為之的。然而,如果你傳入了一個(gè)可監(jiān)聽(tīng)的對(duì)象,那么其對(duì)象的屬性還是可響應(yīng)的。 然后我們來(lái)看一下initInjections 和 initProvide的執(zhí)行順序:
initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props也就是說(shuō)我們先初始化initInjections
export function initInjections (vm: Component) { // 因?yàn)椴](méi)有```vm._provided```此時(shí) ```result``` 返回的是個(gè) null,也就沒(méi)有進(jìn)行```defineReactive``` const result = resolveInject(vm.$options.inject, vm) if (result) { observerState.shouldConvert = false Object.keys(result).forEach(key => { /* istanbul ignore else */ if (process.env.NODE_ENV !== production) { defineReactive(vm, key, result[key], () => { warn( `Avoid mutating an injected value directly since the changes will be ` + `overwritten whenever the provided component re-renders. ` + `injection being mutated: "${key}"`, vm ) }) } else { defineReactive(vm, key, result[key]) } }) observerState.shouldConvert = true } }接著定義我們的initProvide
export function initProvide (vm: Component) { const provide = vm.$options.provide if (provide) { vm._provided = typeof provide === function ? provide.call(vm) : provide } }6. 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) } }這里的主要工作主要是定義的數(shù)據(jù)進(jìn)行defineReactive,可以簡(jiǎn)單看一下initData的過(guò)程:
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:\n + https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function, vm ) } // 代理數(shù)據(jù),當(dāng)訪問(wèn)```this.xxx```的時(shí)候,代理到```this.data.xxx``` ... proxy(vm, `_data`, key) ... // observe data observe(data, true /* asRootData */) }推薦Vue學(xué)習(xí)資料文章:
《入口開(kāi)始解讀Vue源碼系列(一)——造物創(chuàng)世》
《入口開(kāi)始解讀Vue源碼系列(二)——new Vue 的故事》
《學(xué)會(huì)使用Vue JSX,一車?yán)细蓩尪际悄愕?/a>》
《「速圍」尤雨溪詳細(xì)介紹 Vue 3 的最新進(jìn)展》
《細(xì)聊single-spa + vue來(lái)實(shí)現(xiàn)前端微服務(wù)項(xiàng)目》
《9個(gè)優(yōu)秀的 VUE 開(kāi)源項(xiàng)目》
《細(xì)聊Single-Spa + Vue Cli 微前端落地指南「實(shí)踐」》
《一文帶你搞懂vue/react應(yīng)用中實(shí)現(xiàn)ssr(服務(wù)端渲染)》
《Vue+CSS3 實(shí)現(xiàn)圖片滑塊效果》
《教你Vue3 Compiler 優(yōu)化細(xì)節(jié),如何手寫(xiě)高性能渲染函數(shù)(上)》
《教你Vue3 Compiler 優(yōu)化細(xì)節(jié),如何手寫(xiě)高性能渲染函數(shù)(下)》
《vue實(shí)現(xiàn)一個(gè)6個(gè)輸入框的驗(yàn)證碼輸入組件》
《Vue常見(jiàn)的面試知識(shí)點(diǎn)匯總(上)「附答案」》
《Vue常見(jiàn)的面試知識(shí)點(diǎn)匯總(下)「附答案」》
《讓Jenkins自動(dòng)部署你的Vue項(xiàng)目「實(shí)踐」》
《20個(gè)免費(fèi)的設(shè)計(jì)資源 UI套件背景圖標(biāo)CSS框架》
《Deno將停止使用TypeScript,并公布五項(xiàng)具體理由》
《Vue原來(lái)可以這樣寫(xiě)開(kāi)發(fā)效率杠杠的》
《用vue簡(jiǎn)單寫(xiě)一個(gè)音樂(lè)播放組件「附源碼」》
《為什么Vue3.0不再使用defineProperty實(shí)現(xiàn)數(shù)據(jù)監(jiān)聽(tīng)?》
《「干貨」學(xué)會(huì)這些Vue小技巧,可以早點(diǎn)下班和女神約會(huì)》
《細(xì)品30張腦圖帶你從零開(kāi)始學(xué)Vue》
《Vue后臺(tái)項(xiàng)目中遇到的技術(shù)難點(diǎn)以及解決方案》
《手把手教你Electron + Vue實(shí)戰(zhàn)教程(五)》
《手把手教你Electron + Vue實(shí)戰(zhàn)教程(四)》
《手把手教你Electron + Vue實(shí)戰(zhàn)教程(三)》
《手把手教你Electron + Vue實(shí)戰(zhàn)教程(二)》
《手把手教你Electron + Vue實(shí)戰(zhàn)教程(一)》
《如何寫(xiě)出優(yōu)秀后臺(tái)管理系統(tǒng)?11個(gè)經(jīng)典模版拿去不謝「干貨」》
《手把手教你實(shí)現(xiàn)一個(gè)Vue自定義指令懶加載》
《基于 Vue 和高德地圖實(shí)現(xiàn)地圖組件「實(shí)踐」》
《一個(gè)由 Vue 作者尤雨溪開(kāi)發(fā)的 web 開(kāi)發(fā)工具—vite》
《1.1萬(wàn)字深入細(xì)品Vue3.0源碼響應(yīng)式系統(tǒng)筆記「上」》
《1.1萬(wàn)字深入細(xì)品Vue3.0源碼響應(yīng)式系統(tǒng)筆記「下」》
《「實(shí)踐」Vue 數(shù)據(jù)更新7 種情況匯總及延伸解決總結(jié)》
《尤大大細(xì)說(shuō)Vue3 的誕生之路「譯」》
《提高10倍打包速度工具Snowpack 2.0正式發(fā)布,再也不需要打包器》
《大廠Code Review總結(jié)Vue開(kāi)發(fā)規(guī)范經(jīng)驗(yàn)「值得學(xué)習(xí)」》
《Vue3 插件開(kāi)發(fā)詳解嘗鮮版「值得收藏」》
《Vue 3.x 如何有驚無(wú)險(xiǎn)地快速入門「進(jìn)階篇」》
《帶你了解 vue-next(Vue 3.0)之 爐火純青「實(shí)踐」》
《「干貨」Vue+高德地圖實(shí)現(xiàn)頁(yè)面點(diǎn)擊繪制多邊形及多邊形切割拆分》
《「干貨」Vue+Element前端導(dǎo)入導(dǎo)出Excel》
《細(xì)品pdf.js實(shí)踐解決含水印、電子簽章問(wèn)題「Vue篇」》
《基于vue + element的后臺(tái)管理系統(tǒng)解決方案》
《Vue仿蘑菇街商城項(xiàng)目(vue+koa+mongodb)》
《基于 electron-vue 開(kāi)發(fā)的音樂(lè)播放器「實(shí)踐」》
《「實(shí)踐」Vue項(xiàng)目中標(biāo)配編輯器插件Vue-Quill-Editor》
《基于 Vue 技術(shù)棧的微前端方案實(shí)踐》
《「干貨」Deno TCP Echo Server 是怎么運(yùn)行的?》
《「干貨」了不起的 Deno 實(shí)戰(zhàn)教程》
《Deno 正式發(fā)布,徹底弄明白和 node 的區(qū)別》
《「實(shí)踐」基于Apify+node+react/vue搭建一個(gè)有點(diǎn)意思的爬蟲(chóng)平臺(tái)》
《「實(shí)踐」深入對(duì)比 Vue 3.0 Composition API 和 React Hooks》
《前端網(wǎng)紅框架的插件機(jī)制全梳理(axios、koa、redux、vuex)》
《深入Vue 必學(xué)高階組件 HOC「進(jìn)階篇」》
《深入學(xué)習(xí)Vue的data、computed、watch來(lái)實(shí)現(xiàn)最精簡(jiǎn)響應(yīng)式系統(tǒng)》
《10個(gè)實(shí)例小練習(xí),快速入門熟練 Vue3 核心新特性(一)》
《10個(gè)實(shí)例小練習(xí),快速入門熟練 Vue3 核心新特性(二)》
《教你部署搭建一個(gè)Vue-cli4+Webpack移動(dòng)端框架「實(shí)踐」》
《詳解Vue3中 router 帶來(lái)了哪些變化?》
《Vue項(xiàng)目部署及性能優(yōu)化指導(dǎo)篇「實(shí)踐」》
《Vue高性能渲染大數(shù)據(jù)Tree組件「實(shí)踐」》
《尤大大細(xì)品VuePress搭建技術(shù)網(wǎng)站與個(gè)人博客「實(shí)踐」》
《10個(gè)Vue開(kāi)發(fā)技巧「實(shí)踐」》
《是什么導(dǎo)致尤大大選擇放棄Webpack?【vite 原理解析】》
《帶你了解 vue-next(Vue 3.0)之 小試牛刀【實(shí)踐】》
《帶你了解 vue-next(Vue 3.0)之 初入茅廬【實(shí)踐】》
《實(shí)踐Vue 3.0做JSX(TSX)風(fēng)格的組件開(kāi)發(fā)》
《一篇文章教你并列比較React.js和Vue.js的語(yǔ)法【實(shí)踐】》
《手拉手帶你開(kāi)啟Vue3世界的鬼斧神工【實(shí)踐】》
《深入淺出通過(guò)vue-cli3構(gòu)建一個(gè)SSR應(yīng)用程序【實(shí)踐】》
《怎樣為你的 Vue.js 單頁(yè)應(yīng)用提速》
《聊聊昨晚尤雨溪現(xiàn)場(chǎng)針對(duì)Vue3.0 Beta版本新特性知識(shí)點(diǎn)匯總》
《【新消息】Vue 3.0 Beta 版本發(fā)布,你還學(xué)的動(dòng)么?》
《Vue真是太好了 壹萬(wàn)多字的Vue知識(shí)點(diǎn) 超詳細(xì)!》
《Vue + Koa從零打造一個(gè)H5頁(yè)面可視化編輯器——Quark-h5》
《深入淺出Vue3 跟著尤雨溪學(xué) TypeScript 之 Ref 【實(shí)踐】》
《手把手教你深入淺出vue-cli3升級(jí)vue-cli4的方法》
《Vue 3.0 Beta 和React 開(kāi)發(fā)者分別杠上了》
《手把手教你用vue drag chart 實(shí)現(xiàn)一個(gè)可以拖動(dòng) / 縮放的圖表組件》
《Vue3 嘗鮮》
《2020 年,Vue 受歡迎程度是否會(huì)超過(guò) React?》
《使用vue實(shí)現(xiàn)HTML頁(yè)面生成圖片》
《實(shí)現(xiàn)全棧收銀系統(tǒng)(Node+Vue)(上)》
《實(shí)現(xiàn)全棧收銀系統(tǒng)(Node+Vue)(下)》
《Vue合理配置WebSocket并實(shí)現(xiàn)群聊》
《多年vue項(xiàng)目實(shí)戰(zhàn)經(jīng)驗(yàn)匯總》
《Vue插件總結(jié)【前端開(kāi)發(fā)必備】》
《Vue 開(kāi)發(fā)必須知道的 36 個(gè)技巧【近1W字】》
《構(gòu)建大型 Vue.js 項(xiàng)目的10條建議》
《手把手教你Vue解析pdf(base64)轉(zhuǎn)圖片【實(shí)踐】》
《使用vue+node搭建前端異常監(jiān)控系統(tǒng)》
《推薦 8 個(gè)漂亮的 vue.js 進(jìn)度條組件》
《基于Vue實(shí)現(xiàn)拖拽升級(jí)(九宮格拖拽)》
《手摸手,帶你用vue擼后臺(tái) 系列二(登錄權(quán)限篇)》
《手摸手,帶你用vue擼后臺(tái) 系列三(實(shí)戰(zhàn)篇)》
《前端框架用vue還是react?清晰對(duì)比兩者差異》
《淺析 React / Vue 跨端渲染原理與實(shí)現(xiàn)》
《10個(gè)Vue開(kāi)發(fā)技巧助力成為更好的工程師》
《手把手教你Vue之父子組件間通信實(shí)踐講解【props、$ref 、$emit】》
《1W字長(zhǎng)文+多圖,帶你了解vue的雙向數(shù)據(jù)綁定源碼實(shí)現(xiàn)》
《深入淺出Vue3 的響應(yīng)式和以前的區(qū)別到底在哪里?【實(shí)踐】》
《干貨滿滿!如何優(yōu)雅簡(jiǎn)潔地實(shí)現(xiàn)時(shí)鐘翻牌器(支持JS/Vue/React)》
《基于Vue/VueRouter/Vuex/Axios登錄路由和接口級(jí)攔截原理與實(shí)現(xiàn)》
《手把手教你D3.js 實(shí)現(xiàn)數(shù)據(jù)可視化極速上手到Vue應(yīng)用》
《吃透 Vue 項(xiàng)目開(kāi)發(fā)實(shí)踐|16個(gè)方面深入前端工程化開(kāi)發(fā)技巧【上】》
《吃透 Vue 項(xiàng)目開(kāi)發(fā)實(shí)踐|16個(gè)方面深入前端工程化開(kāi)發(fā)技巧【中】》
《吃透 Vue 項(xiàng)目開(kāi)發(fā)實(shí)踐|16個(gè)方面深入前端工程化開(kāi)發(fā)技巧【下】》
《Vue3.0權(quán)限管理實(shí)現(xiàn)流程【實(shí)踐】》
《后臺(tái)管理系統(tǒng),前端Vue根據(jù)角色動(dòng)態(tài)設(shè)置菜單欄和路由》
作者:muwoo
轉(zhuǎn)發(fā)鏈接:
https://github.com/muwoo/blogs/blob/master/src/Vue/3.md掃描二維碼推送至手機(jī)訪問(wèn)。
版權(quán)聲明:本文由財(cái)神資訊-領(lǐng)先的體育資訊互動(dòng)媒體轉(zhuǎn)載發(fā)布,如需刪除請(qǐng)聯(lián)系。