vue基礎知識四:Vue實例掛載的過程

在這里插入圖片描述
一、思考

我們都聽過知其然知其所以然這句話

那么不知道大家是否思考過new Vue()這個過程中究竟做了些什么?

過程中是如何完成數據的綁定,又是如何將數據渲染到視圖的等等

一、分析

首先找到vue的構造函數

源碼位置: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)
}

options是用戶傳遞過來的配置項,如data、methods等常用的方法

vue構建函數調用_init方法,但我們發現本文件中并沒有此方法,但仔細可以看到文件下方定定義了很多初始化方法

initMixin(Vue);     // 定義 _init
stateMixin(Vue);    // 定義 $set $get $delete $watch 等
eventsMixin(Vue);   // 定義事件  $on  $once $off $emit
lifecycleMixin(Vue);// 定義 _update  $forceUpdate  $destroy
renderMixin(Vue);   // 定義 _render 返回虛擬dom

首先可以看initMixin方法,發現該方法在Vue原型上定義了_init方法

源碼位置:src\core\instance\init.js

Vue.prototype._init = function (options?: Object) {const vm: Component = this// a uidvm._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 observedvm._isVue = true// merge options// 合并屬性,判斷初始化的是否是組件,這里合并主要是 mixins 或 extends 的方法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 { // 合并vue屬性vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),options || {},vm)}/* istanbul ignore else */if (process.env.NODE_ENV !== 'production') {// 初始化proxy攔截器initProxy(vm)} else {vm._renderProxy = vm}// expose real selfvm._self = vm// 初始化組件生命周期標志位initLifecycle(vm)// 初始化組件事件偵聽initEvents(vm)// 初始化渲染方法initRender(vm)callHook(vm, 'beforeCreate')// 初始化依賴注入內容,在初始化data、props之前initInjections(vm) // resolve injections before data/props// 初始化props/data/method/watch/methodsinitState(vm)initProvide(vm) // resolve provide after data/propscallHook(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)}}

仔細閱讀上面的代碼,我們得到以下結論:

  • 在調用beforeCreate之前,數據初始化并未完成,像data、props這些屬性無法訪問到
  • 到了created的時候,數據已經初始化完成,能夠訪問data、props這些屬性,但這時候并未完成dom的掛載,因此無法訪問到dom元素
  • 掛載方法是調用vm.$mount方法

initState方法是完成props/data/method/watch/methods的初始化

源碼位置:src\core\instance\state.js

export function initState (vm: Component) {// 初始化組件的watcher列表vm._watchers = []const opts = vm.$options// 初始化propsif (opts.props) initProps(vm, opts.props)// 初始化methods方法if (opts.methods) initMethods(vm, opts.methods)if (opts.data) {// 初始化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)}
}

我們和這里主要看初始化data的方法為initData,它與initState在同一文件上

function initData (vm: Component) {let data = vm.$options.data// 獲取到組件上的datadata = 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)}// proxy data on instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (i--) {const key = keys[i]if (process.env.NODE_ENV !== 'production') {// 屬性名不能與方法名重復if (methods && hasOwn(methods, key)) {warn(`Method "${key}" has already been defined as a data property.`,vm)}}// 屬性名不能與state名稱重復if (props && hasOwn(props, key)) {process.env.NODE_ENV !== 'production' && warn(`The data property "${key}" is already declared as a prop. ` +`Use prop default value instead.`,vm)} else if (!isReserved(key)) { // 驗證key值的合法性// 將_data中的數據掛載到組件vm上,這樣就可以通過this.xxx訪問到組件上的數據proxy(vm, `_data`, key)}}// observe data// 響應式監聽data是數據的變化observe(data, true /* asRootData */)
}

仔細閱讀上面的代碼,我們可以得到以下結論:

  • 初始化順序:props、methods、data
  • data定義的時候可選擇函數形式或者對象形式(組件只能為函數形式)

關于數據響應式在這就不展開詳細說明

上文提到掛載方法是調用vm.$mount方法

源碼位置:

Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean
): Component {// 獲取或查詢元素el = el && query(el)/* istanbul ignore if */// vue 不允許直接掛載到body或頁面文檔上if (el === document.body || el === document.documentElement) {process.env.NODE_ENV !== 'production' && warn(`Do not mount Vue to <html> or <body> - mount to normal elements instead.`)return this}const options = this.$options// resolve template/el and convert to render functionif (!options.render) {let template = options.template// 存在template模板,解析vue模板文件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')}/***  1.將temmplate解析ast tree*  2.將ast tree轉換成render語法字符串*  3.生成render方法*/const { render, staticRenderFns } = compileToFunctions(template, {outputSourceRange: process.env.NODE_ENV !== 'production',shouldDecodeNewlines,shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments}, this)options.render = renderoptions.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)
}

閱讀上面代碼,我們能得到以下結論:

  • 不要將根元素放到body或者html上
  • 可以在對象中定義template/render或者直接使用template、el表示元素選擇器
  • 最終都會解析成render函數,調用compileToFunctions,會將template解析成render函數對template的解析步驟大致分為以下幾步:
  • 將html文檔片段解析成ast描述符
  • 將ast描述符解析成字符串
  • 生成render函數

生成render函數,掛載到vm上后,會再次調用mount方法

源碼位置:src\platforms\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)
}

調用mountComponent渲染組件

export function mountComponent (vm: Component,el: ?Element,hydrating?: boolean
): Component {vm.$el = el// 如果沒有獲取解析的render函數,則會拋出警告// render是解析模板文件生成的if (!vm.$options.render) {vm.$options.render = createEmptyVNodeif (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 {// 沒有獲取到vue的模板文件warn('Failed to mount component: template or render function not defined.',vm)}}}// 執行beforeMount鉤子callHook(vm, 'beforeMount')let updateComponent/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {updateComponent = () => {const name = vm._nameconst id = vm._uidconst 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 = () => {// 實際調?是在lifeCycleMixin中定義的_update和renderMixin中定義的_rendervm._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 && !vm._isDestroyed) {// 數據更新引發的組件更新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 hookif (vm.$vnode == null) {vm._isMounted = truecallHook(vm, 'mounted')}return vm
}

閱讀上面代碼,我們得到以下結論:

  • 會觸發beforeCreate鉤子
  • 定義updateComponent渲染頁面視圖的方法
  • 監聽組件數據,一旦發生變化,觸發beforeUpdate生命鉤子

updateComponent方法主要執行在vue初始化時聲明的render,update方法

render的作用主要是生成vnode

源碼位置:src\core\instance\render.js

// 定義vue 原型上的render方法
Vue.prototype._render = function (): VNode {const vm: Component = this// render函數來自于組件的optionconst { render, _parentVnode } = vm.$optionsif (_parentVnode) {vm.$scopedSlots = normalizeScopedSlots(_parentVnode.data.scopedSlots,vm.$slots,vm.$scopedSlots)}// set parent vnode. this allows render functions to have access// to the data on the placeholder node.vm.$vnode = _parentVnode// render selflet vnodetry {// There's no need to maintain a stack because all render fns are called// separately from one another. Nested component's render fns are called// when parent component is patched.currentRenderingInstance = vm// 調用render方法,自己的獨特的render方法, 傳入createElement參數,生成vNodevnode = render.call(vm._renderProxy, vm.$createElement)} catch (e) {handleError(e, vm, `render`)// return error render result,// or previous vnode to prevent render error causing blank component/* istanbul ignore else */if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {try {vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)} catch (e) {handleError(e, vm, `renderError`)vnode = vm._vnode}} else {vnode = vm._vnode}} finally {currentRenderingInstance = null}// if the returned array contains only a single node, allow itif (Array.isArray(vnode) && vnode.length === 1) {vnode = vnode[0]}// return empty vnode in case the render function errored outif (!(vnode instanceof VNode)) {if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {warn('Multiple root nodes returned from render function. Render function ' +'should return a single root node.',vm)}vnode = createEmptyVNode()}// set parentvnode.parent = _parentVnodereturn vnode
}

_update主要功能是調用patch,將vnode轉換為真實DOM,并且更新到頁面中

源碼位置:src\core\instance\lifecycle.js

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {const vm: Component = thisconst prevEl = vm.$elconst prevVnode = vm._vnode// 設置當前激活的作用域const restoreActiveInstance = setActiveInstance(vm)vm._vnode = vnode// Vue.prototype.__patch__ is injected in entry points// based on the rendering backend used.if (!prevVnode) {// initial render// 執行具體的掛載邏輯vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)} else {// updatesvm.$el = vm.__patch__(prevVnode, vnode)}restoreActiveInstance()// update __vue__ referenceif (prevEl) {prevEl.__vue__ = null}if (vm.$el) {vm.$el.__vue__ = vm}// if parent is an HOC, update its $el as wellif (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {vm.$parent.$el = vm.$el}// updated hook is called by the scheduler to ensure that children are// updated in a parent's updated hook.}

三、結論

new Vue的時候調用會調用_init方法

  • 定義 s e t 、 set、 setget 、 d e l e t e 、 delete、 deletewatch 等方法
  • 定義 o n 、 on、 onoff、 e m i t 、 emit、 emitoff等事件
  • 定義 _update、 f o r c e U p d a t e 、 forceUpdate、 forceUpdatedestroy生命周期

調用$mount進行頁面的掛載

掛載的時候主要是通過mountComponent方法

定義updateComponent更新函數

執行render生成虛擬DOM

_update將虛擬DOM生成真實DOM結構,并且渲染到頁面中

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/38911.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/38911.shtml
英文地址,請注明出處:http://en.pswp.cn/news/38911.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

一生一芯4——使用星火應用商店在ubuntu下載QQ、微信、百度網盤

星火應用商店可以非常方便的完成一些應用的下載&#xff0c;下面是官方網址 http://spark-app.store/download 我使用的是intel處理器&#xff0c;無需下載依賴項&#xff0c;直接點擊軟件本體 我這里下載amd64,根據自己的處理器下載對應版本 sudo apt install ./spark-stor…

做視頻_Style

Video 1> 風格2> 技巧3> 借鑒 &#x1f517; B站視頻 1> 風格 記錄分享生活&#xff0c;工作&#xff0c;學習方面的總結&#xff1b; 4個段位&#xff1a; 實用 -> 簡潔 -> 清晰流暢 -> 生動有趣 2> 技巧 1> 大視頻分段錄制&#xff0c;最后合并…

pytorch入門-神經網絡

神經網絡的基本骨架 import torch from torch import nn #nn模塊是PyTorch中用于構建神經網絡模型的核心模塊。它提供了各種類和函數&#xff0c;可以幫助你定義和訓練神經網絡。class Tudui(nn.Module):def __init__(self):super(Tudui, self).__init__() #調用 super(Tudui,…

數據結構入門指南:二叉樹

目錄 文章目錄 前言 1. 樹的概念及結構 1.1 樹的概念 1.2 樹的基礎概念 1.3 樹的表示 1.4 樹的應用 2. 二叉樹 2.1 二叉樹的概念 2.2 二叉樹的遍歷 前言 在計算機科學中&#xff0c;數據結構是解決問題的關鍵。而二叉樹作為最基本、最常用的數據結構之一&#xff0c;不僅在算法…

java對大文件分片上傳

這里記錄一下&#xff0c;Java對大文件的切分&#xff0c;和后端接口分片上傳的實現邏輯 正常&#xff0c;前后端分離的項目其實是前端去切分文件&#xff0c;后端接口接收到切分后的分片文件去合并&#xff0c;這里都用java來記錄一下。特別說明&#xff1a;我這里用的是zip包…

vue+java實現在線播放mp4視頻

java: 讀取本地視頻文件的流然后給response的輸出流 File file new File("/Users/zhangqingtian/Documents/水庫/Floodforecast/static/" videoName);BufferedInputStream inputStream new BufferedInputStream(new FileInputStream(file));response.setContentT…

ReactDOM模塊react-dom/client沒有默認導出報錯解決辦法

import ReactDOM 模塊“"E:/Dpandata/Shbank/rt-pro/node_modules/.pnpm/registry.npmmirror.comtypesreact-dom18.2.7/node_modules/types/react-dom/client"”沒有默認導出。 解決辦法 只需要在tsconfig.json里面添加配置 "esModuleInterop": true 即…

【C++】queue容器

1.queue容器基本概念 2.queue常用接口 #include <iostream> using namespace std;//隊列queue #include<queue>//創建Person類 class Person { public:Person(string name, int age){this->m_Name name;this->m_Age age;}string m_Name; //姓名int m_Age; …

mysql創建新用戶并授權

目錄 前言登錄到mysql創建用戶用戶授權更改用戶密碼參考 前言 略 登錄到mysql shell> mysql -h127.0.0.1 -P3306 -uroot -p******創建用戶 mysql> CREATE USER abc% IDENTIFIED BY 123456;用戶授權 mysql> GRANT all privileges ON ruoyi.* TO abc%;用戶ruoyi擁有…

優維低代碼實踐:自定義模板

優維低代碼技術專欄&#xff0c;是一個全新的、技術為主的專欄&#xff0c;由優維技術委員會成員執筆&#xff0c;基于優維7年低代碼技術研發及運維成果&#xff0c;主要介紹低代碼相關的技術原理及架構邏輯&#xff0c;目的是給廣大運維人提供一個技術交流與學習的平臺。 優維…

禾賽科技Q2營收交付雙新高,國產激光雷達從量變到質變

隨著2022年激光雷達元年、2023年城市智能輔助駕駛&#xff08;NOA&#xff09;元年相繼到來&#xff0c;激光雷達產業迎來爆發期。 今年以來&#xff0c;自動駕駛公司、汽車制造商以及移動出行公司等各路人馬積極推動城市級別的智能輔助駕駛全面落地&#xff0c;北京、上海、深…

通過css設置filter 屬性,使整個頁面呈現灰度效果,讓整個網頁變灰

通過css設置filter 屬性設置頁面整體置灰 效果圖: 通過設置 filter 屬性為 grayscale(100%)&#xff0c;頁面中的所有元素都會被應用灰色濾鏡效果&#xff0c;使整個頁面呈現灰度效果。 <style type"text/css"> html { filter: grayscale(100%); -webkit-f…

git pull 某一個文件或文件夾

QQ: 2967732156 背景&#xff1a; 在使用Oracle VM VirtualBox&#xff0c;進行Linux開發時&#xff0c;隨著使用內存越來越少&#xff0c;空間已不足拉取整個代碼庫。 Ubuntu1604內存夠&#xff0c;Ubuntu18.04內存不夠。 思路&#xff1a; 第一步&#xff1a;從問題本身…

TB/TM-商品詳情原數據(APP)

一、接口參數說明&#xff1a; item_get_app-獲得TB/TMapp商品詳情原數據&#xff0c;點擊更多API調試&#xff0c;請移步注冊API賬號點擊獲取測試key和secret 公共參數 請求地址: https://api-gw.onebound.cn/taobao/item_get_app 名稱類型必須描述keyString是調用key&…

考研 408 | 【計算機網絡】 應用層

導圖 網絡應用模型 客戶/服務器&#xff08;c/s&#xff09;模型 P2P模型 DNS 域名 域名服務器 域名解析過程 文件傳輸協議FTP FTP服務器和用戶端 FTP工作原理 電子郵件 電子郵件的信息格式 組成結構 郵件服務器的功能&#xff1a; 1.發送&接收郵件 2.給發件人報告郵…

使用windows Api簡單驗證ISO9660文件格式,以及裝載和卸載鏡像文件

使用IIsoImageManager接口簡單驗證ISO鏡像文件正確性,使用AttachVirtualDisk裝載ISO鏡像文件,和使用DetachVirtualDisk卸載,(只支持windows 8及以上系統) 導讀 IIsoImageManager 驗證ISO文件正確性AttachVirtualDisk 裝載鏡像文件DetachVirtualDisk 卸載鏡像文件其他相關函…

《游戲編程模式》學習筆記(四) 觀察者模式 Observer Pattern

定義 觀察者模式定義了對象間的一種一對多的依賴關系&#xff0c;當一個對象的狀態發生改變時&#xff0c;所有依賴于它的對象都得到通知并被自動更新。 這是定義&#xff0c;看不懂就看不懂吧&#xff0c;我接下來舉個例子慢慢說 為什么我們需要觀察者模式 我們看一個很簡…

PAT (Advanced Level) 甲級 1004 Counting Leaves

點此查看所有題目集 A family hierarchy is usually presented by a pedigree tree. Your job is to count those family members who have no child. Input Specification: Each input file contains one test case. Each case starts with a line containing 0<N<100, …

如何在iPhone手機上修改手機定位和模擬導航?

如何在iPhone手機上修改手機定位和模擬導航&#xff1f; English Location Simulator&#xff08;定位模擬工具&#xff09; 是一款功能強大的 macOS 應用&#xff0c;專為 iPhone 用戶設計&#xff0c;旨在修改手機定位并提供逼真的模擬導航體驗。無論是為了保護隱私、測試位…

Angular 獨立組件入門

Angular 獨立組件入門 如果你正在學習 Angular&#xff0c;那么你可能已經聽說過獨立組件&#xff08;Component&#xff09;。顧名思義&#xff0c;獨立組件就是可以獨立使用和管理的組件&#xff0c;它們能夠被包含在其他組件中或被其他組件引用。 在本文中&#xff0c;我們…