<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Vue 的這些技巧你真的都掌握了嗎?

          共 28572字,需瀏覽 58分鐘

           ·

          2021-11-19 13:35

          大廠技術(shù)??高級(jí)前端??Node進(jìn)階

          點(diǎn)擊上方?程序員成長(zhǎng)指北,關(guān)注公眾號(hào)

          回復(fù)1,加入高級(jí)Node交流群


          前言

          文章目的昭然若揭?????,整理匯總 Vue 框架中重要的特性、框架的原理。

          那 "前車之鑒" 從何而來?

          是的,我又要講小故事了,但這次是故事的續(xù)集。

          故事第 1 集:CSS預(yù)處理器,你還是只會(huì)嵌套么 ?[2]
          故事第 2 集:【自適應(yīng)】px 轉(zhuǎn) rem,你還在手算么?[3]

          為什么說是續(xù)集,因?yàn)檫@些都是同一大佬問的,在此感謝大佬,天降素材??。

          故事續(xù)集

          大佬:有看過 Vue 源碼么?

          我:嗯嗯,看過。

          大佬:那大概講一講 nextTick 的底層實(shí)現(xiàn) ?

          我:停頓了大概10s,說了句忘了。(理不直氣還壯)

          大佬:噢噢,沒事。(內(nèi)心大概已經(jīng)放棄對(duì)我知識(shí)面的挖掘)

          因?yàn)槭且曨l面試,強(qiáng)裝自信的尷尬從屏幕中溢出,這大概就是普通且自信???♂??裝X失敗案例引以為戒,能寫出續(xù)集的面試結(jié)果不提也罷。

          這次面試打擊還是蠻大的,考察內(nèi)容全面且細(xì)節(jié)。面試后一直在整理 Vue 相關(guān)的知識(shí)點(diǎn),所以不會(huì)將nextTick實(shí)現(xiàn)單獨(dú)成文,只是收錄在下方試題中。前車之鑒可以為鑒,大家可以把本篇文章當(dāng)測(cè)驗(yàn),考察自己是否對(duì)這些知識(shí)點(diǎn)熟練于心。

          萬(wàn)字長(zhǎng)文,持續(xù)更新,若有遺漏知識(shí)點(diǎn),后續(xù)會(huì)補(bǔ)充。

          題目

          Vue 的優(yōu)缺點(diǎn)

          優(yōu)點(diǎn)

          1. 創(chuàng)建單頁(yè)面應(yīng)用的輕量級(jí)Web應(yīng)用框架
          2. 簡(jiǎn)單易用
          3. 雙向數(shù)據(jù)綁定
          4. 組件化的思想
          5. 虛擬DOM
          6. 數(shù)據(jù)驅(qū)動(dòng)視圖

          缺點(diǎn)

          不支持IE8(現(xiàn)階段只能勉強(qiáng)湊出這么半點(diǎn)??)

          SPA 的理解

          SPA是Single-Page-Application的縮寫,翻譯過來就是單頁(yè)應(yīng)用。在WEB頁(yè)面初始化時(shí)一同加載Html、Javascript、Css。一旦頁(yè)面加載完成,SPA不會(huì)因?yàn)橛脩舨僮鞫M(jìn)行頁(yè)面重新加載或跳轉(zhuǎn),取而代之的是利用路由機(jī)制實(shí)現(xiàn)Html內(nèi)容的變換。

          優(yōu)點(diǎn)

          1. 良好的用戶體驗(yàn),內(nèi)容更改無需重載頁(yè)面。
          2. 基于上面一點(diǎn),SPA相對(duì)服務(wù)端壓力更小。
          3. 前后端職責(zé)分離,架構(gòu)清晰。

          缺點(diǎn)

          1. 由于單頁(yè)WEB應(yīng)用,需在加載渲染頁(yè)面時(shí)請(qǐng)求JavaScript、Css文件,所以耗時(shí)更多。
          2. 由于前端渲染,搜索引擎不會(huì)解析JS,只能抓取首頁(yè)未渲染的模板,不利于SEO。
          3. 由于單頁(yè)應(yīng)用需在一個(gè)頁(yè)面顯示所有的內(nèi)容,默認(rèn)不支持瀏覽器的前進(jìn)后退。

          缺點(diǎn)3,想必有人和我有同樣的疑問。

          通過資料查閱,其實(shí)是前端路由機(jī)制解決了單頁(yè)應(yīng)用無法前進(jìn)后退的問題。Hash模式中Hash變化會(huì)被瀏覽器記錄(onhashchange事件),History模式利用 H5 新增的pushStatereplaceState方法可改變?yōu)g覽器歷史記錄棧。

          new Vue(options) 都做了些什么

          如下 Vue 構(gòu)造函數(shù)所示,主要執(zhí)行了 this._init(options)方法,該方法在initMixin函數(shù)中注冊(cè)。

          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')
          ??}
          ??//?Vue.prototype._init?方法
          ??this._init(options)
          }

          //?_init?方法在?initMixin?注冊(cè)
          initMixin(Vue)
          stateMixin(Vue)
          eventsMixin(Vue)
          lifecycleMixin(Vue)
          renderMixin(Vue)

          export?default?Vue
          復(fù)制代碼

          查看initMixin方法的實(shí)現(xiàn),其他函數(shù)具體實(shí)現(xiàn)可自行查看,這里就不貼出了。

          let?uid?=?0
          export?function?initMixin()?{
          ??Vue.prototype._init?=?function(options)?{
          ????const?vm?=?this
          ????vm._uid?=?uid++
          ????vm._isVue?=?true
          ???
          ????//?處理組件配置項(xiàng)
          ????if?(options?&&?options._isComponent)?{
          ???????/**
          ???????*?如果是子組件,走當(dāng)前?if?分支
          ???????*?函數(shù)作用是性能優(yōu)化:將原型鏈上的方法都放到vm.$options中,減少原型鏈上的訪問
          ???????*/
          ???
          ??????initInternalComponent(vm,?options)
          ????}?else?{
          ??????/**
          ???????*?如果是根組件,走當(dāng)前?else?分支
          ???????*?合并?Vue?的全局配置到根組件中,如?Vue.component?注冊(cè)的全局組件合并到根組件的?components?的選項(xiàng)中
          ???????*?子組件的選項(xiàng)合并發(fā)生在兩個(gè)地方
          ???????*?1.?Vue.component?方法注冊(cè)的全局組件在注冊(cè)時(shí)做了選項(xiàng)合并
          ???????*?2.?{?component:?{xx}?}?方法注冊(cè)的局部組件在執(zhí)行編譯器生成的?render?函數(shù)時(shí)做了選項(xiàng)合并
          ???????*/
          ??
          ??????vm.$options?=?mergeOptions(
          ????????resolveConstructorOptions(vm.constructor),
          ????????options?||?{},
          ????????vm
          ??????)
          ????}
          ??
          ????if?(process.env.NODE_ENV?!==?'production')?{
          ??????initProxy(vm)
          ????}?else?{
          ??????vm._renderProxy?=?vm
          ????}

          ????vm._self?=?vm
          ????/**
          ????*?初始化組件實(shí)例關(guān)系屬性,如:$parent $root $children $refs
          ????*/

          ????initLifecycle(vm)
          ????/**
          ????*?初始化自定義事件
          ????*?@click
          ="handleClick">
          ????*?組件上注冊(cè)的事件,監(jiān)聽者不是父組件,而是子組件本身
          ????*/
          ????initEvents(vm)
          ????/**
          ????*?解析組件插槽信息,得到vm.$slot,處理渲染函數(shù),得到 vm.$createElement 方法,即 h 函數(shù)。
          ????*/

          ????initRender(vm)
          ????/**
          ????*?執(zhí)行?beforeCreate?生命周期函數(shù)
          ????*/

          ????callHook(vm,?'beforeCreate')
          ????/**
          ????*?解析?inject?配置項(xiàng),得到?result[key]?=?val?的配置對(duì)象,做響應(yīng)式處理且代理到?vm?實(shí)力上
          ????*/

          ????initInjections(vm)?
          ????/**
          ????*?響應(yīng)式處理核心,處理?props、methods、data、computed、watch
          ????*/

          ????initState(vm)
          ????/**
          ????*?解析?provide?對(duì)象,并掛載到?vm?實(shí)例上
          ????*/

          ????initProvide(vm)?
          ????/**
          ????*?執(zhí)行?created?生命周期函數(shù)
          ????*/

          ????callHook(vm,?'created')

          ????//?如果?el?選項(xiàng),自動(dòng)執(zhí)行$mount
          ????if?(vm.$options.el)?{
          ??????vm.$mount(vm.$options.el)
          ????}
          ??}
          }
          復(fù)制代碼

          MVVM 的理解

          MVVM是Model-View-ViewModel的縮寫。Model 代表數(shù)據(jù)層,可定義修改數(shù)據(jù)、編寫業(yè)務(wù)邏輯。View 代表視圖層,負(fù)責(zé)將數(shù)據(jù)渲染成頁(yè)面。ViewModel 負(fù)責(zé)監(jiān)聽數(shù)據(jù)層數(shù)據(jù)變化,控制視圖層行為交互,簡(jiǎn)單講,就是同步數(shù)據(jù)層和視圖層的對(duì)象。ViewModel 通過雙向綁定把 View 和 Model 層連接起來,且同步工作無需人為干涉,使開發(fā)人員只關(guān)注業(yè)務(wù)邏輯,無需頻繁操作DOM,不需關(guān)注數(shù)據(jù)狀態(tài)的同步問題。

          mvvm.png

          如何實(shí)現(xiàn) v-model

          v-model指令用于實(shí)現(xiàn)inputselect等表單元素的雙向綁定,是個(gè)語(yǔ)法糖。

          原生 input 元素若是text/textarea類型,使用 value 屬性和 input 事件。
          原生 input 元素若是radio/checkbox類型,使用 checked屬性和 change 事件。
          原生 select 元素,使用 value 屬性和 change 事件。

          input 元素上使用 v-model 等價(jià)于

          <input?:value="message"?@input="message?=?$event.target.value"?/>
          復(fù)制代碼

          實(shí)現(xiàn)自定義組件的 v-model

          自定義組件的v-model使用prop值為valueinput事件。若是radio/checkbox類型,需要使用model來解決原生 DOM 使用的是 checked 屬性 和 change 事件,如下所示。

          //?父組件
          <template>
          ??<base-checkbox?v-model="baseCheck"?/>
          template>
          復(fù)制代碼
          //?子組件
          <template>
          ??<input?type="checkbox"?:checked="checked"?@change="$emit('change',?$event.target.checked)"?/>
          template>
          <script>
          export?default?{
          ??model:?{
          ????prop:?'checked',
          ????event:?'change'
          ??},
          ??prop:?{
          ????checked:?Boolean
          ??}
          }
          script
          >
          復(fù)制代碼

          如何理解 Vue 單向數(shù)據(jù)流

          Vue 官方文檔 Prop 菜單下的有個(gè)名為單項(xiàng)數(shù)據(jù)流的子菜單。

          image.png

          我們經(jīng)常說 Vue 的雙向綁定,其實(shí)是在單向綁定的基礎(chǔ)上給元素添加 input/change 事件,來動(dòng)態(tài)修改視圖。Vue 組件間傳遞數(shù)據(jù)仍然是單項(xiàng)的,即父組件傳遞到子組件。子組件內(nèi)部可以定義依賴 props 中的值,但無權(quán)修改父組件傳遞的數(shù)據(jù),這樣做防止子組件意外變更父組件的狀態(tài),導(dǎo)致應(yīng)用數(shù)據(jù)流向難以理解。

          如果在子組件內(nèi)部直接更改prop,會(huì)遇到警告處理。

          2 種定義依賴 props 中的值

          1. 通過 data 定義屬性并將 prop 作為初始值。
          <script>
          export?default?{
          ??props:?['initialNumber'],
          ??data()?{
          ????return?{
          ??????number:?this.initailNumber
          ????}
          ??}
          }
          script
          >
          復(fù)制代碼
          1. 用 computed 計(jì)算屬性去定義依賴 prop 的值。若頁(yè)面會(huì)更改當(dāng)前值,得分 get 和 set 方法。
          <script>
          export?default?{
          ??props:?['size'],
          ??computed:?{
          ????normalizedSize()?{
          ??????return?this.size.trim().toLowerCase()
          ????}
          ??}
          }
          sciprt>

          復(fù)制代碼

          Vue 響應(yīng)式原理

          核心源碼位置:vue/src/core/observer/index.js

          響應(yīng)式原理3個(gè)步驟:數(shù)據(jù)劫持、依賴收集、派發(fā)更新。

          數(shù)據(jù)分為兩類:對(duì)象、數(shù)組。

          對(duì)象

          遍歷對(duì)象,通過Object.defineProperty為每個(gè)屬性添加 getter 和 setter,進(jìn)行數(shù)據(jù)劫持。getter 函數(shù)用于在數(shù)據(jù)讀取時(shí)進(jìn)行依賴收集,在對(duì)應(yīng)的 dep 中存儲(chǔ)所有的 watcher;setter 則是數(shù)據(jù)更新后通知所有的 watcher 進(jìn)行更新。

          核心源碼

          function?defineReactive(obj,?key,?val,?shallow)?{
          ??//?實(shí)例化一個(gè)?dep,?一個(gè)?key?對(duì)應(yīng)一個(gè)?dep
          ??const?dep?=?new?Dep()
          ?
          ??//?獲取屬性描述符
          ??const?getter?=?property?&&?property.get
          ??const?setter?=?property?&&?property.set
          ??if?((!getter?||?setter)?&&?arguments.length?===?2)?{
          ????val?=?obj[key]
          ??}

          ??//?通過遞歸的方式處理?val?為對(duì)象的情況,即處理嵌套對(duì)象
          ??let?childOb?=?!shallow?&&?observe(val)
          ??
          ??Object.defineProperty(obj,?key,?{
          ????enumerable:?true,
          ????configurable:?true,
          ????//?攔截obj.key,進(jìn)行依賴收集
          ????get:?function?reactiveGetter?()?{
          ??????const?value?=?getter???getter.call(obj)?:?val
          ??????//?Dep.target?是當(dāng)前組件渲染的?watcher
          ??????if?(Dep.target)?{
          ????????//?將?dep?添加到?watcher?中
          ????????dep.depend()
          ????????if?(childOb)?{
          ??????????//?嵌套對(duì)象依賴收集
          ??????????childOb.dep.depend()
          ??????????//?響應(yīng)式處理?value?值為數(shù)組的情況
          ??????????if?(Array.isArray(value))?{
          ????????????dependArray(value)
          ??????????}
          ????????}
          ??????}
          ??????return?value
          ????},
          ????set:?function?reactiveSetter?(newVal)?{
          ??????//?獲取舊值
          ??????const?value?=?getter???getter.call(obj)?:?val
          ??????//?判斷新舊值是否一致
          ??????if?(newVal?===?value?||?(newVal?!==?newVal?&&?value?!==?value))?{
          ????????return
          ??????}
          ??????if?(process.env.NODE_ENV?!==?'production'?&&?customSetter)?{
          ????????customSetter()
          ??????}

          ??????if?(getter?&&?!setter)?return
          ??????//?如果是新值,用新值替換舊值
          ??????if?(setter)?{
          ????????setter.call(obj,?newVal)
          ??????}?else?{
          ????????val?=?newVal
          ??????}
          ??????//?新值做響應(yīng)式處理
          ??????childOb?=?!shallow?&&?observe(newVal)
          ??????//?當(dāng)響應(yīng)式數(shù)據(jù)更新,依賴通知更新
          ??????dep.notify()
          ????}
          ??})
          }
          復(fù)制代碼

          數(shù)組

          用數(shù)組增強(qiáng)的方式,覆蓋原屬性上默認(rèn)的數(shù)組方法,保證在新增或刪除數(shù)據(jù)時(shí),通過 dep 通知所有的 watcher 進(jìn)行更新。

          核心源碼

          const?arrayProto?=?Array.prototype
          //?基于數(shù)組原型對(duì)象創(chuàng)建一個(gè)新的對(duì)象
          export?const?arrayMethods?=?Object.create(arrayProto)

          const?methodsToPatch?=?[
          ??'push',
          ??'pop',
          ??'shift',
          ??'unshift',
          ??'splice',
          ??'sort',
          ??'reverse'
          ]

          methodsToPatch.forEach(function?(method)?{
          ??const?original?=?arrayProto[method]
          ??//?分別在?arrayMethods?對(duì)象上定義7個(gè)方法
          ??def(arrayMethods,?method,?function?mutator?(...args)?{
          ????//?先執(zhí)行原生的方法
          ????const?result?=?original.apply(this,?args)
          ????const?ob?=?this.__ob__
          ????let?inserted
          ????switch?(method)?{
          ??????case?'push':
          ??????case?'unshift':
          ????????inserted?=?args
          ????????break
          ??????case?'splice':
          ????????inserted?=?args.slice(2)
          ????????break
          ????}
          ????//?針對(duì)新增元素進(jìn)行響應(yīng)式處理
          ????if?(inserted)?ob.observeArray(inserted)
          ????//?數(shù)據(jù)無論是新增還是刪除都進(jìn)行派發(fā)更新
          ????ob.dep.notify()
          ????return?result
          ??})
          })
          復(fù)制代碼

          手寫觀察者模式

          當(dāng)對(duì)象間存在一對(duì)多的關(guān)系,使用觀察者模式。比如:當(dāng)一個(gè)對(duì)象被修改,會(huì)自動(dòng)通知依賴它的對(duì)象。

          let?uid?=?0
          class?Dep?{
          ??constructor()?{
          ????this.id?=?uid++
          ????//?存儲(chǔ)所有的?watcher
          ????this.subs?=?[]
          ??}
          ??addSub(sub)?{
          ????this.subs.push(sub)
          ??}
          ??removeSub(sub)?{
          ????if(this.subs.length)?{
          ??????const?index?=?this.subs.indexOf(sub)
          ??????if(index?>?-1)?return?this.subs.splice(index,?1)
          ????}
          ??}
          ??notify()?{
          ????this.subs.forEach(sub?=>?{
          ??????sub.update()
          ????})
          ??}
          }

          class?Watcher?{
          ??constructor(name)?{
          ????this.name?=?name
          ??}
          ??update()?{
          ????console.log('更新')
          ??}
          }
          復(fù)制代碼

          手寫發(fā)布訂閱模式

          與觀察者模式相似,區(qū)別在于發(fā)布者和訂閱者是解耦的,由中間的調(diào)度中心去與發(fā)布者和訂閱者通信。

          Vue響應(yīng)式原理個(gè)人更傾向于發(fā)布訂閱模式。其中 Observer 是發(fā)布者,Watcher 是訂閱者,Dep 是調(diào)度中心。

          vue中數(shù)據(jù)綁定原理的設(shè)計(jì)模式到底觀察者還是發(fā)布訂閱?[4],知乎有相關(guān)爭(zhēng)論,感興趣的可以看下。

          class?EventEmitter?{
          ??constructor()?{
          ????this.events?=?{}
          ??}
          ??on(type,?cb)?{
          ????if(!this.events[type])?this.events[type]?=?[]
          ????this.events[type].push(cb)
          ??}
          ??emit(type,?...args)?{
          ????if(this.events[type])?{
          ??????this.events[type].forEach(cb?=>?{
          ????????cb(...args)
          ??????})
          ????}
          ??}
          ??off(type,?cb)?{
          ????if(this.events[type])?{
          ??????const?index?=?this.events[type].indexOf(cb)
          ??????if(index?>?-1)?this.events[type].splice(index,?1)
          ????}
          ??}
          }
          復(fù)制代碼

          關(guān)于 Vue.observable 的了解

          Vue.observable 可使對(duì)象可響應(yīng)。返回的對(duì)象可直接用于渲染函數(shù)計(jì)算屬性內(nèi),并且在發(fā)生變更時(shí)觸發(fā)相應(yīng)的更新。也可以作為最小化的跨組件狀態(tài)存儲(chǔ)器。

          Vue 2.x 中傳入的對(duì)象和返回的對(duì)象是同一個(gè)對(duì)象。
          Vue 3.x 則不是一個(gè)對(duì)象,源對(duì)象不具備響應(yīng)式功能。

          適用的場(chǎng)景:在項(xiàng)目中沒有大量的非父子組件通信時(shí),可以使用 Vue.observable 去替代 eventBusvuex方案。

          用法如下

          //?store.js
          import?Vue?from?'vue'
          export?const?state?=?Vue.observable({
          ??count:?1
          })
          export?const?mutations?=?{
          ??setCount(count)?{
          ????state.count?=?count
          ??}
          }?

          //?vue?文件