vue 面試匯總(更新中...)

1.說說對雙向綁定的理解

1.1卫旱、雙向綁定的原理是什么

我們都知道?Vue?是數(shù)據(jù)雙向綁定的框架,雙向綁定由三個重要部分構(gòu)成

數(shù)據(jù)層(Model):應(yīng)用的數(shù)據(jù)及業(yè)務(wù)邏輯

視圖層(View):應(yīng)用的展示效果,各類UI組件

業(yè)務(wù)邏輯層(ViewModel):框架封裝的核心,它負責(zé)將數(shù)據(jù)與視圖關(guān)聯(lián)起來

而上面的這個分層的架構(gòu)方案蹋肮,可以用一個專業(yè)術(shù)語進行稱呼:MVVM這里的控制層的核心功能便是 “數(shù)據(jù)雙向綁定” 待侵。自然丢早,我們只需弄懂它是什么,便可以進一步了解數(shù)據(jù)綁定的原理

理解ViewModel

它的主要職責(zé)就是:

數(shù)據(jù)變化后更新視圖

視圖變化后更新數(shù)據(jù)

當(dāng)然秧倾,它還有兩個主要部分組成

監(jiān)聽器(Observer):對所有數(shù)據(jù)的屬性進行監(jiān)聽

解析器(Compiler):對每個元素節(jié)點的指令進行掃描跟解析,根據(jù)指令模板替換數(shù)據(jù),以及綁定相應(yīng)的更新函數(shù)

1.2怨酝、實現(xiàn)雙向綁定

我們還是以Vue為例,先來看看Vue中的雙向綁定流程是什么的

new Vue()首先執(zhí)行初始化那先,對data執(zhí)行響應(yīng)化處理农猬,這個過程發(fā)生Observe中

同時對模板執(zhí)行編譯,找到其中動態(tài)綁定的數(shù)據(jù)售淡,從data中獲取并初始化視圖斤葱,這個過程發(fā)生在Compile中

同時定義?個更新函數(shù)和Watcher,將來對應(yīng)數(shù)據(jù)變化時Watcher會調(diào)用更新函數(shù)

由于data的某個key在?個視圖中可能出現(xiàn)多次揖闸,所以每個key都需要?個管家Dep來管理多個Watcher

將來data中數(shù)據(jù)?旦發(fā)生變化揍堕,會首先找到對應(yīng)的Dep,通知所有Watcher執(zhí)行更新函數(shù)


2.單頁應(yīng)用與多頁應(yīng)用的區(qū)別

單頁應(yīng)用 ---SPA(single-page application)汤纸,翻譯過來就是單頁應(yīng)用SPA是一種網(wǎng)絡(luò)應(yīng)用程序或網(wǎng)站的模型衩茸,它通過動態(tài)重寫當(dāng)前頁面來與用戶交互,這種方法避免了頁面之間切換打斷用戶體驗在單頁應(yīng)用中贮泞,所有必要的代碼(HTML楞慈、JavaScript和CSS)都通過單個頁面的加載而檢索,或者根據(jù)需要(通常是為響應(yīng)用戶操作)動態(tài)裝載適當(dāng)?shù)馁Y源并添加到頁面頁面在任何時間點都不會重新加載啃擦,也不會將控制轉(zhuǎn)移到其他頁面舉個例子來講就是一個杯子囊蓝,早上裝的牛奶,中午裝的是開水令蛉,晚上裝的是茶聚霜,我們發(fā)現(xiàn),變的始終是杯子里的內(nèi)容言询,而杯子始終是那個杯子


單頁面應(yīng)用(SPA) | 多頁面應(yīng)用(MPA)?

組成 | 一個主頁面和多個頁面片段 | 多個主頁面 |

刷新方式 | 局部刷新 | 整頁刷新 |

url模式 | 哈希模式 | 歷史模式 |

SEO搜索引擎優(yōu)化 | 難實現(xiàn)俯萎,可使用SSR方式改善 | 容易實現(xiàn) |

數(shù)據(jù)傳遞 | 容易 | 通過url、cookie运杭、localStorage等傳遞 |

頁面切換 | 速度快夫啊,用戶體驗良好 | 切換加載資源,速度慢辆憔,用戶體驗差 |

?維護成本 | 相對容易 | 相對復(fù)雜 |

1.1單頁應(yīng)用優(yōu)缺點

優(yōu)點:

具有桌面應(yīng)用的即時性撇眯、網(wǎng)站的可移植性和可訪問性

用戶體驗好报嵌、快,內(nèi)容的改變不需要重新加載整個頁面

良好的前后端分離熊榛,分工更明確

缺點:

不利于搜索引擎的抓取

首次渲染速度相對較慢

3.v-show與v-if的區(qū)別

控制手段不同

編譯過程不同

編譯條件不同

控制手段:v-show隱藏則是為該元素添加css--display:none锚国,dom元素依舊還在。v-if顯示隱藏是將dom元素整個添加或刪除

編譯過程:v-if切換有一個局部編譯/卸載的過程玄坦,切換過程中合適地銷毀和重建內(nèi)部的事件監(jiān)聽和子組件血筑;v-show只是簡單的基于css切換

編譯條件:v-if是真正的條件渲染,它會確保在切換過程中條件塊內(nèi)的事件監(jiān)聽器和子組件適當(dāng)?shù)乇讳N毀和重建煎楣。只有渲染條件為假時豺总,并不做操作,直到為真才渲染

v-show?由false變?yōu)閠rue的時候不會觸發(fā)組件的生命周期

v-if由false變?yōu)閠rue的時候择懂,觸發(fā)組件的beforeCreate喻喳、create、beforeMount困曙、mounted鉤子表伦,由true變?yōu)閒alse的時候觸發(fā)組件的beforeDestory、destoryed方法

性能消耗:v-if有更高的切換消耗慷丽;v-show有更高的初始渲染消耗蹦哼;

4.vue掛載都干了什么

1.1 分析

首先找到vue的構(gòu)造函數(shù)

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

functionVue(options){if(process.env.NODE_ENV!=='production'&&!(thisinstanceofVue)){warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options)}

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

vue構(gòu)建函數(shù)調(diào)用_init方法翔怎,但我們發(fā)現(xiàn)本文件中并沒有此方法窃诉,但仔細可以看到文件下方定定義了很多初始化方法

initMixin(Vue);// 定義 _initstateMixin(Vue);// 定義 $set $get $delete $watch 等eventsMixin(Vue);// 定義事件? $on? $once $off $emitlifecycleMixin(Vue);// 定義 _update? $forceUpdate? $destroyrenderMixin(Vue);// 定義 _render 返回虛擬dom

首先可以看initMixin方法杨耙,發(fā)現(xiàn)該方法在Vue原型上定義了_init方法

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

Vue.prototype._init=function(options?:Object){constvm:Component=this// a uidvm._uid=uid++letstartTag,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')// 初始化依賴注入內(nèi)容珊膜,在初始化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)}}

仔細閱讀上面的代碼宣脉,我們得到以下結(jié)論:

在調(diào)用beforeCreate之前车柠,數(shù)據(jù)初始化并未完成,像data塑猖、props這些屬性無法訪問到

到了created的時候竹祷,數(shù)據(jù)已經(jīng)初始化完成,能夠訪問data羊苟、props這些屬性塑陵,但這時候并未完成dom的掛載,因此無法訪問到dom元素

掛載方法是調(diào)用vm.$mount方法

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

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

exportfunctioninitState(vm:Component){// 初始化組件的watcher列表vm._watchers=[]constopts=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在同一文件上

functioninitData(vm:Component){letdata=vm.$options.data// 獲取到組件上的datadata=vm._data=typeofdata==='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 instanceconstkeys=Object.keys(data)constprops=vm.$options.propsconstmethods=vm.$options.methodsleti=keys.lengthwhile(i--){constkey=keys[i]if(process.env.NODE_ENV!=='production'){// 屬性名不能與方法名重復(fù)if(methods&&hasOwn(methods,key)){warn(`Method "${key}" has already been defined as a data property.`,vm)}}// 屬性名不能與state名稱重復(fù)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)}elseif(!isReserved(key)){// 驗證key值的合法性// 將_data中的數(shù)據(jù)掛載到組件vm上,這樣就可以通過this.xxx訪問到組件上的數(shù)據(jù)proxy(vm,`_data`,key)}}// observe data// 響應(yīng)式監(jiān)聽data是數(shù)據(jù)的變化observe(data,true/* asRootData */)}

仔細閱讀上面的代碼令花,我們可以得到以下結(jié)論:

初始化順序:props阻桅、methods、data

data定義的時候可選擇函數(shù)形式或者對象形式(組件只能為函數(shù)形式)

關(guān)于數(shù)據(jù)響應(yīng)式在這就不展開詳細說明

上文提到掛載方法是調(diào)用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.`)returnthis}constoptions=this.$options// resolve template/el and convert to render functionif(!options.render){lettemplate=options.template// 存在template模板兼都,解析vue模板文件if(template){if(typeoftemplate==='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)}}}elseif(template.nodeType){template=template.innerHTML}else{if(process.env.NODE_ENV!=='production'){warn('invalid template option:'+template,this)}returnthis}}elseif(el){// 通過選擇器獲取元素內(nèi)容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轉(zhuǎn)換成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')}}}returnmount.call(this,el,hydrating)}

閱讀上面代碼嫂沉,我們能得到以下結(jié)論:

不要將根元素放到body或者html上

可以在對象中定義template/render或者直接使用template、el表示元素選擇器

最終都會解析成render函數(shù)扮碧,調(diào)用compileToFunctions趟章,會將template解析成render函數(shù)

對template的解析步驟大致分為以下幾步:

將html文檔片段解析成ast描述符

將ast描述符解析成字符串

生成render函數(shù)

生成render函數(shù),掛載到vm上后慎王,會再次調(diào)用mount方法

源碼位置:src\platforms\web\runtime\index.js

// public mount methodVue.prototype.$mount=function(el?:string|Element,hydrating?:boolean):Component{el=el&&inBrowser?query(el):undefined// 渲染組件returnmountComponent(this,el,hydrating)}

調(diào)用mountComponent渲染組件

exportfunctionmountComponent(vm:Component,el: ?Element,hydrating?:boolean):Component{vm.$el=el// 如果沒有獲取解析的render函數(shù)尤揣,則會拋出警告// 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)}}}// 執(zhí)行beforeMount鉤子callHook(vm,'beforeMount')letupdateComponent/* istanbul ignore if */if(process.env.NODE_ENV!=='production'&&config.performance&&mark){updateComponent=()=>{constname=vm._nameconstid=vm._uidconststartTag=`vue-perf-start:${id}`constendTag=`vue-perf-end:${id}`mark(startTag)constvnode=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{// 定義更新函數(shù)updateComponent=()=>{// 實際調(diào)?是在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// 監(jiān)聽當(dāng)前組件狀態(tài),當(dāng)有數(shù)據(jù)變化時柬祠,更新組件newWatcher(vm,updateComponent,noop,{before(){if(vm._isMounted&&!vm._isDestroyed){// 數(shù)據(jù)更新引發(fā)的組件更新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')}returnvm}

閱讀上面代碼北戏,我們得到以下結(jié)論:

會觸發(fā)boforeCreate鉤子

定義updateComponent渲染頁面視圖的方法

監(jiān)聽組件數(shù)據(jù),一旦發(fā)生變化漫蛔,觸發(fā)beforeUpdate生命鉤子

updateComponent方法主要執(zhí)行在vue初始化時聲明的render嗜愈,update方法

render的作用主要是生成vnode

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

// 定義vue 原型上的render方法Vue.prototype._render=function():VNode{constvm:Component=this// render函數(shù)來自于組件的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 selfletvnodetry{// 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// 調(diào)用render方法,自己的獨特的render方法莽龟, 傳入createElement參數(shù)蠕嫁,生成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(!(vnodeinstanceofVNode)){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=_parentVnodereturnvnode}

_update主要功能是調(diào)用patch,將vnode轉(zhuǎn)換為真實DOM毯盈,并且更新到頁面中

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

Vue.prototype._update=function(vnode:VNode,hydrating?:boolean){constvm:Component=thisconstprevEl=vm.$elconstprevVnode=vm._vnode// 設(shè)置當(dāng)前激活的作用域constrestoreActiveInstance=setActiveInstance(vm)vm._vnode=vnode// Vue.prototype.__patch__ is injected in entry points// based on the rendering backend used.if(!prevVnode){// initial render// 執(zhí)行具體的掛載邏輯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的時候調(diào)用會調(diào)用_init方法

定義?$set幕袱、?$get?炸渡、$delete、$watch?等方法

定義?$on、$off涛浙、$emit贬养、$off?等事件

定義?_update图呢、$forceUpdate赖钞、$destroy生命周期

調(diào)用$mount進行頁面的掛載

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

定義updateComponent更新函數(shù)

執(zhí)行render生成虛擬DOM

_update將虛擬DOM生成真實DOM結(jié)構(gòu),并且渲染到頁面中

5.vue 生命周期

1.1生命周期是什么

生命周期(Life Cycle)的概念應(yīng)用很廣泛宋欺,特別是在政治轰豆、經(jīng)濟、環(huán)境齿诞、技術(shù)酸休、社會等諸多領(lǐng)域經(jīng)常出現(xiàn),其基本涵義可以通俗地理解為“從搖籃到墳?zāi)埂保–radle-to-Grave)的整個過程在Vue中實例從創(chuàng)建到銷毀的過程就是生命周期祷杈,即指從創(chuàng)建斑司、初始化數(shù)據(jù)、編譯模板吠式、掛載Dom→渲染陡厘、更新→渲染抽米、卸載等一系列過程我們可以把組件比喻成工廠里面的一條流水線,每個工人(生命周期)站在各自的崗位糙置,當(dāng)任務(wù)流轉(zhuǎn)到工人身邊的時候云茸,工人就開始工作PS:在Vue生命周期鉤子會自動綁定?this?上下文到實例中,因此你可以訪問數(shù)據(jù)谤饭,對?property?和方法進行運算這意味著你不能使用箭頭函數(shù)來定義一個生命周期方法?(例如?created: () => this.fetchTodos())

1.2生命周期有哪些

Vue生命周期總共可以分為8個階段:創(chuàng)建前后, 載入前后,更新前后,銷毀前銷毀后标捺,以及一些特殊場景的生命周期

生命周期描述

beforeCreate組件實例被創(chuàng)建之初

created組件實例已經(jīng)完全創(chuàng)建

beforeMount組件掛載之前

mounted組件掛載到實例上去之后

beforeUpdate組件數(shù)據(jù)發(fā)生變化,更新之前

updated數(shù)據(jù)數(shù)據(jù)更新之后

beforeDestroy組件實例銷毀之前

destroyed組件實例銷毀之后

activatedkeep-alive 緩存的組件激活時

deactivatedkeep-alive 緩存的組件停用時調(diào)用

errorCaptured捕獲一個來自子孫組件的錯誤時被調(diào)用

1.3生命周期整體流程

Vue生命周期流程圖

具體分析

beforeCreate -> created

初始化vue實例揉抵,進行數(shù)據(jù)觀測

created

完成數(shù)據(jù)觀測亡容,屬性與方法的運算,watch冤今、event事件回調(diào)的配置

可調(diào)用methods中的方法闺兢,訪問和修改data數(shù)據(jù)觸發(fā)響應(yīng)式渲染dom,可通過computed和watch完成數(shù)據(jù)計算

此時vm.$el?并沒有被創(chuàng)建

created -> beforeMount

判斷是否存在el選項戏罢,若不存在則停止編譯屋谭,直到調(diào)用vm.$mount(el)才會繼續(xù)編譯

優(yōu)先級:render?>?template?>?outerHTML

vm.el獲取到的是掛載DOM的

beforeMount

在此階段可獲取到vm.el

此階段vm.el雖已完成DOM初始化,但并未掛載在el選項上

beforeMount -> mounted

此階段vm.el完成掛載龟糕,vm.$el生成的DOM替換了el選項所對應(yīng)的DOM

mounted

vm.el已完成DOM的掛載與渲染桐磁,此刻打印vm.$el,發(fā)現(xiàn)之前的掛載點及內(nèi)容已被替換成新的DOM

beforeUpdate

更新的數(shù)據(jù)必須是被渲染在模板上的(el讲岁、template我擂、render之一)

此時view層還未更新

若在beforeUpdate中再次修改數(shù)據(jù),不會再次觸發(fā)更新方法

updated

完成view層的更新

若在updated中再次修改數(shù)據(jù)缓艳,會再次觸發(fā)更新方法(beforeUpdate校摩、updated)

beforeDestroy

實例被銷毀前調(diào)用,此時實例屬性與方法仍可訪問

destroyed

完全銷毀一個實例郎任⊙砗模可清理它與其它實例的連接备籽,解綁它的全部指令及事件監(jiān)聽器

并不能清除DOM舶治,僅僅銷毀實例

使用場景分析

生命周期描述

beforeCreate執(zhí)行時組件實例還未創(chuàng)建,通常用于插件開發(fā)中執(zhí)行一些初始化任務(wù)

created組件初始化完畢车猬,各種數(shù)據(jù)可以使用霉猛,常用于異步數(shù)據(jù)獲取

beforeMount未執(zhí)行渲染、更新珠闰,dom未創(chuàng)建

mounted初始化結(jié)束惜浅,dom已創(chuàng)建,可用于獲取訪問數(shù)據(jù)和dom元素

beforeUpdate更新前伏嗜,可用于獲取更新前各種狀態(tài)

updated更新后坛悉,所有狀態(tài)已是最新

beforeDestroy銷毀前伐厌,可用于一些定時器或訂閱的取消

destroyed組件已銷毀,作用同上

1.4題外話:數(shù)據(jù)請求在created和mouted的區(qū)別

created是在組件實例一旦創(chuàng)建完成的時候立刻調(diào)用裸影,這時候頁面dom節(jié)點并未生成mounted是在頁面dom節(jié)點渲染完畢之后就立刻執(zhí)行的觸發(fā)時機上created是比mounted要更早的兩者相同點:都能拿到實例對象的屬性和方法討論這個問題本質(zhì)就是觸發(fā)的時機挣轨,放在mounted請求有可能導(dǎo)致頁面閃動(頁面dom結(jié)構(gòu)已經(jīng)生成),但如果在頁面加載前完成則不會出現(xiàn)此情況建議:放在create生命周期當(dāng)中

6.v-if 和v-for

1.1優(yōu)先級

v-if與v-for都是vue模板系統(tǒng)中的指令

在vue模板編譯的時候轩猩,會將指令系統(tǒng)轉(zhuǎn)化成可執(zhí)行的render函數(shù)

示例

編寫一個p標簽卷扮,同時使用v-if與?v-for

<divid="app"><pv-if="isShow"v-for="item in items">{{ item.title }}</p></div>

創(chuàng)建vue實例,存放isShow與items數(shù)據(jù)

constapp=newVue({el:"#app",data(){return{items:[{title:"foo"},{title:"baz"}]}},computed:{isShow(){returnthis.items&&this.items.length>0}}})

模板指令的代碼都會生成在render函數(shù)中均践,通過app.$options.render就能得到渲染函數(shù)

?anonymous(){with(this){return_c('div',{attrs:{"id":"app"}},_l((items),function(item){return(isShow)?_c('p',[_v("\n"+_s(item.title)+"\n")]):_e()}),0)}}

_l是vue的列表渲染函數(shù)晤锹,函數(shù)內(nèi)部都會進行一次if判斷

初步得到結(jié)論:v-for優(yōu)先級是比v-if高

再將v-for與v-if置于不同標簽

<divid="app"><templatev-if="isShow"><pv-for="item in items">{{item.title}}</p></template></div>

再輸出下render函數(shù)

?anonymous(){with(this){return_c('div',{attrs:{"id":"app"}},[(isShow)?[_v("\n"),_l((items),function(item){return_c('p',[_v(_s(item.title))])})]:_e()],2)}}

這時候我們可以看到,v-for與v-if作用在不同標簽時候鞭铆,是先進行判斷,再進行列表的渲染

我們再在查看下vue源碼

源碼位置:?\vue-dev\src\compiler\codegen\index.js

exportfunctiongenElement(el:ASTElement,state:CodegenState):string{if(el.parent){el.pre=el.pre||el.parent.pre}if(el.staticRoot&&!el.staticProcessed){returngenStatic(el,state)}elseif(el.once&&!el.onceProcessed){returngenOnce(el,state)}elseif(el.for&&!el.forProcessed){returngenFor(el,state)}elseif(el.if&&!el.ifProcessed){returngenIf(el,state)}elseif(el.tag==='template'&&!el.slotTarget&&!state.pre){returngenChildren(el,state)||'void 0'}elseif(el.tag==='slot'){returngenSlot(el,state)}else{// component or element...}

在進行if判斷的時候焦影,v-for是比v-if先進行判斷

最終結(jié)論:v-for優(yōu)先級比v-if高

1.2注意事項

永遠不要把?v-if?和?v-for?同時用在同一個元素上衔彻,帶來性能方面的浪費(每次渲染都會先循環(huán)再進行條件判斷)

如果避免出現(xiàn)這種情況,則在外層嵌套template(頁面渲染不生成dom節(jié)點)偷办,在這一層進行v-if判斷艰额,然后在內(nèi)部進行v-for循環(huán)

<templatev-if="isShow"><pv-for="item in items"></template>

如果條件出現(xiàn)在循環(huán)內(nèi)部,可通過計算屬性computed提前過濾掉那些不需要顯示的項

computed:{items:function(){returnthis.list.filter(function(item){returnitem.isShow})}}

7.spa首屏加載慢

一椒涯、什么是首屏加載

首屏?xí)r間(First Contentful Paint)柄沮,指的是瀏覽器從響應(yīng)用戶輸入網(wǎng)址地址,到首屏內(nèi)容渲染完成的時間废岂,此時整個網(wǎng)頁不一定要全部渲染完成祖搓,但需要展示當(dāng)前視窗需要的內(nèi)容

首屏加載可以說是用戶體驗中最重要的環(huán)節(jié)

關(guān)于計算首屏?xí)r間

利用performance.timing提供的數(shù)據(jù):

通過DOMContentLoad或者performance來計算出首屏?xí)r間

// 方案一:document.addEventListener('DOMContentLoaded',(event)=>{console.log('first contentful painting');});

// 方案二:performance.getEntriesByName("first-contentful-paint")[0].startTime// performance.getEntriesByName("first-contentful-paint")[0]// 會返回一個 PerformancePaintTiming的實例,

結(jié)構(gòu)如下:{name:"first-contentful-paint",entryType:"paint",startTime:507.80000002123415,duration:0,};

二湖苞、加載慢的原因

在頁面渲染的過程拯欧,導(dǎo)致加載速度慢的因素可能如下:

網(wǎng)絡(luò)延時問題

資源文件體積是否過大

資源是否重復(fù)發(fā)送請求去加載了

加載腳本的時候,渲染內(nèi)容堵塞了

三财骨、解決方案

常見的幾種SPA首屏優(yōu)化方式

減小入口文件積

靜態(tài)資源本地緩存

UI框架按需加載

圖片資源的壓縮

組件重復(fù)打包

開啟GZip壓縮

使用SSR

8.為什么組件data必須是函數(shù)不能是對象

一镐作、實例和組件定義data的區(qū)別

vue實例的時候定義data屬性既可以是一個對象,也可以是一個函數(shù)

constapp=newVue({el:"#app",// 對象格式data:{foo:"foo"},// 函數(shù)格式data(){return{foo:"foo"}}})

組件中定義data屬性隆箩,只能是一個函數(shù)

如果為組件data直接定義為一個對象

Vue.component('component1',{template:`<div>組件</div>`,data:{foo:"foo"}})

則會得到警告信息

警告說明:返回的data應(yīng)該是一個函數(shù)在每一個組件實例中

二该贾、組件data定義函數(shù)與對象的區(qū)別

上面講到組件data必須是一個函數(shù),不知道大家有沒有思考過這是為什么呢捌臊?

在我們定義好一個組件的時候杨蛋,vue最終都會通過Vue.extend()構(gòu)成組件實例

這里我們模仿組件構(gòu)造函數(shù),定義data屬性,采用對象的形式

functionComponent(){}Component.prototype.data={count:0}

創(chuàng)建兩個組件實例

const componentA = new Component()

const componentB = new Component()

修改componentA組件data屬性的值逞力,componentB中的值也發(fā)生了改變

console.log(componentB.data.count)// 0componentA.data.count=1console.log(componentB.data.count)// 1

產(chǎn)生這樣的原因這是兩者共用了同一個內(nèi)存地址曙寡,componentA修改的內(nèi)容,同樣對componentB產(chǎn)生了影響

如果我們采用函數(shù)的形式寇荧,則不會出現(xiàn)這種情況(函數(shù)返回的對象內(nèi)存地址并不相同)

functionComponent(){this.data=this.data()}Component.prototype.data=function(){return{count:0}}

修改componentA組件data屬性的值卵皂,componentB中的值不受影響

console.log(componentB.data.count)// 0componentA.data.count=1console.log(componentB.data.count)// 0

vue組件可能會有很多個實例,采用函數(shù)返回一個全新data形式砚亭,使每個實例對象的數(shù)據(jù)不會受到其他實例對象數(shù)據(jù)的污染

三灯变、原理分析

首先可以看看vue初始化data的代碼,data的定義可以是函數(shù)也可以是對象

源碼位置:/vue-dev/src/core/instance/state.js

functioninitData(vm:Component){letdata=vm.$options.datadata=vm._data=typeofdata==='function'?getData(data,vm):data||{}...}

data既能是object也能是function捅膘,那為什么還會出現(xiàn)上文警告呢添祸?

別急,繼續(xù)看下文

組件在創(chuàng)建的時候寻仗,會進行選項的合并

源碼位置:/vue-dev/src/core/util/options.js

自定義組件會進入mergeOptions進行選項合并

Vue.prototype._init=function(options?:Object){...// merge optionsif(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)}...}

定義data會進行數(shù)據(jù)校驗

源碼位置:/vue-dev/src/core/instance/init.js

這時候vm實例為undefined刃泌,進入if判斷,若data類型不是function署尤,則出現(xiàn)警告提示

strats.data=function(parentVal:any,childVal:any,vm?:Component): ?Function{if(!vm){if(childVal&&typeofchildVal!=="function"){process.env.NODE_ENV!=="production"&&warn('The "data" option should be a function '+"that returns a per-instance value in component "+"definitions.",vm);returnparentVal;}returnmergeDataOrFn(parentVal,childVal);}returnmergeDataOrFn(parentVal,childVal,vm);};

四耙替、結(jié)論

根實例對象data可以是對象也可以是函數(shù)(根實例是單例),不會產(chǎn)生數(shù)據(jù)污染情況

組件實例對象data必須為函數(shù)曹体,目的是為了防止多個組件實例對象之間共用一個data俗扇,產(chǎn)生數(shù)據(jù)污染。采用函數(shù)的形式箕别,initData時會將其作為工廠函數(shù)都會返回全新data對象

9.NextTick是什么

官方對其的定義

在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)铜幽。在修改數(shù)據(jù)之后立即使用這個方法,獲取更新后的 DOM

什么意思呢串稀?

我們可以理解成除抛,Vue?在更新?DOM?時是異步執(zhí)行的。當(dāng)數(shù)據(jù)發(fā)生變化母截,Vue將開啟一個異步更新隊列到忽,視圖需要等隊列中所有數(shù)據(jù)變化完成之后,再統(tǒng)一進行更新

舉例一下

Html結(jié)構(gòu)

<divid="app">{{ message }}</div>

構(gòu)建一個vue實例

constvm=newVue({el:'#app',data:{message:'原始值'}})

修改message

this.message='修改后的值1'this.message='修改后的值2'this.message='修改后的值3'

這時候想獲取頁面最新的DOM節(jié)點清寇,卻發(fā)現(xiàn)獲取到的是舊值

console.log(vm.$el.textContent)// 原始值

這是因為message數(shù)據(jù)在發(fā)現(xiàn)變化的時候喘漏,vue并不會立刻去更新Dom,而是將修改數(shù)據(jù)的操作放在了一個異步操作隊列中

如果我們一直修改相同數(shù)據(jù)颗管,異步操作隊列還會進行去重

等待同一事件循環(huán)中的所有數(shù)據(jù)變化完成之后陷遮,會將隊列中的事件拿來進行處理,進行DOM的更新

為什么要有nexttick

舉個例子

{{num}}for(leti=0;i<100000;i++){num=i}

如果沒有?nextTick?更新機制垦江,那么?num?每次更新值都會觸發(fā)視圖更新(上面這段代碼也就是會更新10萬次視圖),有了nextTick機制,只需要更新一次比吭,所以nextTick本質(zhì)是一種優(yōu)化策略

二绽族、使用場景

如果想要在修改數(shù)據(jù)后立刻得到更新后的DOM結(jié)構(gòu),可以使用Vue.nextTick()

第一個參數(shù)為:回調(diào)函數(shù)(可以獲取最近的DOM結(jié)構(gòu))

第二個參數(shù)為:執(zhí)行函數(shù)上下文

// 修改數(shù)據(jù)vm.message='修改后的值'// DOM 還沒有更新console.log(vm.$el.textContent)// 原始的值Vue.nextTick(function(){// DOM 更新了console.log(vm.$el.textContent)// 修改后的值})

組件內(nèi)使用?vm.$nextTick()?實例方法只需要通過this.$nextTick()衩藤,并且回調(diào)函數(shù)中的?this?將自動綁定到當(dāng)前的?Vue?實例上

this.message='修改后的值'console.log(this.$el.textContent)// => '原始的值'this.$nextTick(function(){console.log(this.$el.textContent)// => '修改后的值'})

$nextTick()?會返回一個?Promise?對象吧慢,可以是用async/await完成相同作用的事情

this.message='修改后的值'console.log(this.$el.textContent)// => '原始的值'awaitthis.$nextTick()console.log(this.$el.textContent)// => '修改后的值'

三、實現(xiàn)原理

源碼位置:/src/core/util/next-tick.js

callbacks也就是異步操作隊列

callbacks新增回調(diào)函數(shù)后又執(zhí)行了timerFunc函數(shù)赏表,pending是用來標識同一個時間只能執(zhí)行一次

exportfunctionnextTick(cb?:Function,ctx?:Object){let_resolve;// cb 回調(diào)函數(shù)會經(jīng)統(tǒng)一處理壓入 callbacks 數(shù)組callbacks.push(()=>{if(cb){// 給 cb 回調(diào)函數(shù)執(zhí)行加上了 try-catch 錯誤處理try{cb.call(ctx);}catch(e){handleError(e,ctx,'nextTick');}}elseif(_resolve){_resolve(ctx);}});// 執(zhí)行異步延遲函數(shù) timerFuncif(!pending){pending=true;timerFunc();}// 當(dāng) nextTick 沒有傳入函數(shù)參數(shù)的時候检诗,返回一個 Promise 化的調(diào)用if(!cb&&typeofPromise!=='undefined'){returnnewPromise(resolve=>{_resolve=resolve;});}}

timerFunc函數(shù)定義,這里是根據(jù)當(dāng)前環(huán)境支持什么方法則確定調(diào)用哪個瓢剿,分別有:

Promise.then逢慌、MutationObserver、setImmediate间狂、setTimeout

通過上面任意一種方法攻泼,進行降級操作

exportletisUsingMicroTask=falseif(typeofPromise!=='undefined'&&isNative(Promise)){//判斷1:是否原生支持Promiseconstp=Promise.resolve()timerFunc=()=>{p.then(flushCallbacks)if(isIOS)setTimeout(noop)}isUsingMicroTask=true}elseif(!isIE&&typeofMutationObserver!=='undefined'&&(isNative(MutationObserver)||MutationObserver.toString()==='[object MutationObserverConstructor]')){//判斷2:是否原生支持MutationObserverletcounter=1constobserver=newMutationObserver(flushCallbacks)consttextNode=document.createTextNode(String(counter))observer.observe(textNode,{characterData:true})timerFunc=()=>{counter=(counter+1)%2textNode.data=String(counter)}isUsingMicroTask=true}elseif(typeofsetImmediate!=='undefined'&&isNative(setImmediate)){//判斷3:是否原生支持setImmediatetimerFunc=()=>{setImmediate(flushCallbacks)}}else{//判斷4:上面都不行,直接用setTimeouttimerFunc=()=>{setTimeout(flushCallbacks,0)}}

無論是微任務(wù)還是宏任務(wù)鉴象,都會放到flushCallbacks使用

這里將callbacks里面的函數(shù)復(fù)制一份忙菠,同時callbacks置空

依次執(zhí)行callbacks里面的函數(shù)

functionflushCallbacks(){pending=falseconstcopies=callbacks.slice(0)callbacks.length=0for(leti=0;i<copies.length;i++){copies[i]()}}

小結(jié):

把回調(diào)函數(shù)放入callbacks等待執(zhí)行

將執(zhí)行函數(shù)放到微任務(wù)或者宏任務(wù)中

事件循環(huán)到了微任務(wù)或者宏任務(wù),執(zhí)行函數(shù)依次執(zhí)行callbacks中的回調(diào)

10.修飾符是什么

在程序世界里纺弊,修飾符是用于限定類型以及類型成員的聲明的一種符號

在Vue中牛欢,修飾符處理了許多DOM事件的細節(jié),讓我們不再需要花大量的時間去處理這些煩惱的事情淆游,而能有更多的精力專注于程序的邏輯處理

vue中修飾符分為以下五種:

表單修飾符

事件修飾符

鼠標按鍵修飾符

鍵值修飾符

v-bind修飾符

1.修飾符的作用

表單修飾符

在我們填寫表單的時候用得最多的是input標簽氢惋,指令用得最多的是v-model

關(guān)于表單的修飾符有如下:

lazy

trim

number

lazy

在我們填完信息,光標離開標簽的時候稽犁,才會將值賦予給value焰望,也就是在change事件之后再進行信息同步

<inputtype="text"v-model.lazy="value"><p>{{value}}</p>

trim

自動過濾用戶輸入的首空格字符,而中間的空格不會過濾

<inputtype="text"v-model.trim="value">

number

自動將用戶的輸入值轉(zhuǎn)為數(shù)值類型已亥,但如果這個值無法被parseFloat解析熊赖,則會返回原來的值

<inputv-model.number="age"type="number">

事件修飾符

事件修飾符是對事件捕獲以及目標進行了處理,有如下修飾符:

stop

prevent

self

once

capture

passive

native

stop

阻止了事件冒泡虑椎,相當(dāng)于調(diào)用了event.stopPropagation方法

<div@click="shout(2)"><button@click.stop="shout(1)">ok</button></div>//只輸出1

prevent

阻止了事件的默認行為震鹉,相當(dāng)于調(diào)用了event.preventDefault方法

<formv-on:submit.prevent="onSubmit"></form>

self

只當(dāng)在?event.target?是當(dāng)前元素自身時觸發(fā)處理函數(shù)

<divv-on:click.self="doThat">...</div>

使用修飾符時,順序很重要捆姜;相應(yīng)的代碼會以同樣的順序產(chǎn)生传趾。因此,用?v-on:click.prevent.self?會阻止所有的點擊泥技,而?v-on:click.self.prevent?只會阻止對元素自身的點擊

once

綁定了事件以后只能觸發(fā)一次浆兰,第二次就不會觸發(fā)

<button@click.once="shout(1)">ok

capture

使事件觸發(fā)從包含這個元素的頂層開始往下觸發(fā)

<div@click.capture="shout(1)">? ? obj1<div @click.capture="shout(2)">? ? obj2<div @click="shout(3)">? ? obj3<div @click="shout(4)">? ? obj4// 輸出結(jié)構(gòu): 1 2 4 3

passive

在移動端,當(dāng)我們在監(jiān)聽元素滾動事件的時候,會一直觸發(fā)onscroll事件會讓我們的網(wǎng)頁變卡簸呈,因此我們使用這個修飾符的時候榕订,相當(dāng)于給onscroll事件整了一個.lazy修飾符

<!--滾動事件的默認行為(即滾動行為)將會立即觸發(fā)--><!--而不會等待`onScroll`完成--><!--這其中包含`event.preventDefault()`的情況--><divv-on:scroll.passive="onScroll">...</div>

不要把?.passive?和?.prevent?一起使用,因為?.prevent?將會被忽略,同時瀏覽器可能會向你展示一個警告蜕便。

passive?會告訴瀏覽器你不想阻止事件的默認行為

native

讓組件變成像html內(nèi)置標簽?zāi)菢颖O(jiān)聽根元素的原生事件劫恒,否則組件上使用?v-on?只會監(jiān)聽自定義事件

<my-componentv-on:click.native="doSomething"></my-component>

使用.native修飾符來操作普通HTML標簽是會令事件失效的

鼠標按鈕修飾符

鼠標按鈕修飾符針對的就是左鍵、右鍵轿腺、中鍵點擊两嘴,有如下:

left 左鍵點擊

right 右鍵點擊

middle 中鍵點擊

<button@click.left="shout(1)">ok</button><button @click.right="shout(1)">ok</button><button @click.middle="shout(1)">ok

鍵盤修飾符

鍵盤修飾符是用來修飾鍵盤事件(onkeyup,onkeydown)的族壳,有如下:

keyCode存在很多憔辫,但vue為我們提供了別名,分為以下兩種:

普通鍵(enter决侈、tab螺垢、delete、space赖歌、esc枉圃、up...)

系統(tǒng)修飾鍵(ctrl、alt庐冯、meta孽亲、shift...)

// 只有按鍵為keyCode的時候才觸發(fā)<inputtype="text"@keyup.keyCode="shout()">

還可以通過以下方式自定義一些全局的鍵盤碼別名

Vue.config.keyCodes.f2=113

v-bind修飾符

v-bind修飾符主要是為屬性進行操作,用來分別有如下:

async

prop

camel

async

能對props進行一個雙向綁定

//父組件<comp:myMessage.sync="bar"></comp>//子組件this.$emit('update:myMessage',params);

以上這種方法相當(dāng)于以下的簡寫

//父親組件<comp:myMessage="bar"@update:myMessage="func"></comp>func(e){this.bar=e;}//子組件jsfunc2(){this.$emit('update:myMessage',params);}

使用async需要注意以下兩點:

使用sync的時候展父,子組件傳遞的事件名格式必須為update:value返劲,其中value必須與子組件中props中聲明的名稱完全一致

注意帶有?.sync?修飾符的?v-bind?不能和表達式一起使用

將?v-bind.sync?用在一個字面量的對象上,例如?v-bind.sync=”{ title: doc.title }”栖茉,是無法正常工作的

props

設(shè)置自定義標簽屬性篮绿,避免暴露數(shù)據(jù),防止污染HTML結(jié)構(gòu)

<inputid="uid"title="title1"value="1":index.prop="index">

camel

將命名變?yōu)轳劮迕榔鐚?view-Box屬性名轉(zhuǎn)換為?viewBox

<svg:viewBox="viewBox"></svg>

2.應(yīng)用場景

根據(jù)每一個修飾符的功能亲配,我們可以得到以下修飾符的應(yīng)用場景:

.stop:阻止事件冒泡

.native:綁定原生事件

.once:事件只執(zhí)行一次

.self :將事件綁定在自身身上,相當(dāng)于阻止事件冒泡

.prevent:阻止默認事件

.caption:用于事件捕獲

.once:只觸發(fā)一次

.keyCode:監(jiān)聽特定鍵盤按下

.right:右鍵

11.自定義指令

1.什么是指令

開始之前我們先學(xué)習(xí)一下指令系統(tǒng)這個詞

指令系統(tǒng)是計算機硬件的語言系統(tǒng)惶凝,也叫機器語言吼虎,它是系統(tǒng)程序員看到的計算機的主要屬性。因此指令系統(tǒng)表征了計算機的基本功能決定了機器所要求的能力

在vue中提供了一套為數(shù)據(jù)驅(qū)動視圖更為方便的操作苍鲜,這些操作被稱為指令系統(tǒng)

我們看到的v-?開頭的行內(nèi)屬性思灰,都是指令,不同的指令可以完成或?qū)崿F(xiàn)不同的功能

除了核心功能默認內(nèi)置的指令 (v-model?和?v-show)混滔,Vue?也允許注冊自定義指令

指令使用的幾種方式:

//會實例化一個指令洒疚,但這個指令沒有參數(shù) `v-xxx`// -- 將值傳到指令中`v-xxx="value"`// -- 將字符串傳入到指令中歹颓,如`v-html="'<p>內(nèi)容</p>'"``v-xxx="'string'"`// -- 傳參數(shù)(`arg`),如`v-bind:class="className"``v-xxx:arg="value"`// -- 使用修飾符(`modifier`)`v-xxx:arg.modifier="value"`

2.如何實現(xiàn)

注冊一個自定義指令有全局注冊與局部注冊

全局注冊注冊主要是用過Vue.directive方法進行注冊

Vue.directive第一個參數(shù)是指令的名字(不需要寫上v-前綴)拳亿,第二個參數(shù)可以是對象數(shù)據(jù)晴股,也可以是一個指令函數(shù)

// 注冊一個全局自定義指令 `v-focus`Vue.directive('focus',{// 當(dāng)被綁定的元素插入到 DOM 中時……inserted:function(el){// 聚焦元素el.focus()// 頁面加載完成之后自動讓輸入框獲取到焦點的小功能}})

局部注冊通過在組件options選項中設(shè)置directive屬性

directives:{focus:{// 指令的定義inserted:function(el){el.focus()// 頁面加載完成之后自動讓輸入框獲取到焦點的小功能}}}

然后你可以在模板中任何元素上使用新的?v-focus?property愿伴,如下:

<inputv-focus/>

自定義指令也像組件那樣存在鉤子函數(shù):

bind:只調(diào)用一次肺魁,指令第一次綁定到元素時調(diào)用。在這里可以進行一次性的初始化設(shè)置

inserted:被綁定元素插入父節(jié)點時調(diào)用 (僅保證父節(jié)點存在隔节,但不一定已被插入文檔中)

update:所在組件的?VNode?更新時調(diào)用鹅经,但是可能發(fā)生在其子?VNode?更新之前。指令的值可能發(fā)生了改變怎诫,也可能沒有瘾晃。但是你可以通過比較更新前后的值來忽略不必要的模板更新

componentUpdated:指令所在組件的?VNode?及其子?VNode?全部更新后調(diào)用

unbind:只調(diào)用一次,指令與元素解綁時調(diào)用

所有的鉤子函數(shù)的參數(shù)都有以下:

el:指令所綁定的元素幻妓,可以用來直接操作?DOM

binding:一個對象蹦误,包含以下?property:

name:指令名,不包括?v-?前綴肉津。

value:指令的綁定值强胰,例如:v-my-directive="1 + 1"?中,綁定值為?2妹沙。

oldValue:指令綁定的前一個值偶洋,僅在?update?和?componentUpdated?鉤子中可用。無論值是否改變都可用距糖。

expression:字符串形式的指令表達式玄窝。例如?v-my-directive="1 + 1"?中,表達式為?"1 + 1"悍引。

arg:傳給指令的參數(shù)恩脂,可選。例如?v-my-directive:foo?中趣斤,參數(shù)為?"foo"俩块。

modifiers:一個包含修飾符的對象。例如:v-my-directive.foo.bar?中唬渗,修飾符對象為?{ foo: true, bar: true }

vnode:Vue?編譯生成的虛擬節(jié)點

oldVnode:上一個虛擬節(jié)點典阵,僅在?update?和?componentUpdated?鉤子中可用

除了?el?之外,其它參數(shù)都應(yīng)該是只讀的镊逝,切勿進行修改壮啊。如果需要在鉤子之間共享數(shù)據(jù),建議通過元素的?dataset?來進行

舉個例子:

<divv-demo="{ color: 'white', text: 'hello!' }"></div><script>Vue.directive('demo',function(el,binding){console.log(binding.value.color)// "white"console.log(binding.value.text)// "hello!"})</script>

3.應(yīng)用場景

使用自定義組件組件可以滿足我們?nèi)粘R恍﹫鼍俺潘猓@里給出幾個自定義組件的案例:

防抖

圖片懶加載

一鍵 Copy的功能

輸入框防抖

防抖這種情況設(shè)置一個v-throttle自定義指令來實現(xiàn)

舉個例子:

// 1.設(shè)置v-throttle自定義指令Vue.directive('throttle',{bind:(el,binding)=>{letthrottleTime=binding.value;// 防抖時間if(!throttleTime){// 用戶若不設(shè)置防抖時間歹啼,則默認2sthrottleTime=2000;}letcbFun;el.addEventListener('click',event=>{if(!cbFun){// 第一次執(zhí)行cbFun=setTimeout(()=>{cbFun=null;},throttleTime);}else{event&&event.stopImmediatePropagation();}},true);},});// 2.為button標簽設(shè)置v-throttle自定義指令<button@click="sayHello"v-throttle>提交</button>

圖片懶加載

設(shè)置一個v-lazy自定義組件完成圖片懶加載

constLazyLoad={// install方法install(Vue,options){// 代替圖片的loading圖letdefaultSrc=options.default;Vue.directive('lazy',{bind(el,binding){LazyLoad.init(el,binding.value,defaultSrc);},inserted(el){// 兼容處理if('IntersectionObserver'inwindow){LazyLoad.observe(el);}else{LazyLoad.listenerScroll(el);}},})},// 初始化init(el,val,def){// data-src 儲存真實srcel.setAttribute('data-src',val);// 設(shè)置src為loading圖el.setAttribute('src',def);},// 利用IntersectionObserver監(jiān)聽elobserve(el){letio=newIntersectionObserver(entries=>{letrealSrc=el.dataset.src;if(entries[0].isIntersecting){if(realSrc){el.src=realSrc;el.removeAttribute('data-src');}}});io.observe(el);},// 監(jiān)聽scroll事件listenerScroll(el){lethandler=LazyLoad.throttle(LazyLoad.load,300);LazyLoad.load(el);window.addEventListener('scroll',()=>{handler(el);});},// 加載真實圖片load(el){letwindowHeight=document.documentElement.clientHeightletelTop=el.getBoundingClientRect().top;letelBtm=el.getBoundingClientRect().bottom;letrealSrc=el.dataset.src;if(elTop-windowHeight<0&&elBtm>0){if(realSrc){el.src=realSrc;el.removeAttribute('data-src');}}},// 節(jié)流throttle(fn,delay){lettimer;letprevTime;returnfunction(...args){letcurrTime=Date.now();letcontext=this;if(!prevTime)prevTime=currTime;clearTimeout(timer);if(currTime-prevTime>delay){prevTime=currTime;fn.apply(context,args);clearTimeout(timer);return;}timer=setTimeout(function(){prevTime=Date.now();timer=null;fn.apply(context,args);},delay);}}}exportdefaultLazyLoad;

一鍵 Copy的功能

import{Message}from'ant-design-vue';constvCopy={///*? ? bind 鉤子函數(shù)玄渗,第一次綁定時調(diào)用,可以在這里做初始化設(shè)置? ? el: 作用的 dom 對象? ? value: 傳給指令的值狸眼,也就是我們要 copy 的值? */bind(el,{value}){el.$value=value;// 用一個全局屬性來存?zhèn)鬟M來的值藤树,因為這個值在別的鉤子函數(shù)里還會用到el.handler=()=>{if(!el.$value){// 值為空的時候,給出提示拓萌,我這里的提示是用的 ant-design-vue 的提示岁钓,你們隨意Message.warning('無復(fù)制內(nèi)容');return;}// 動態(tài)創(chuàng)建 textarea 標簽consttextarea=document.createElement('textarea');// 將該 textarea 設(shè)為 readonly 防止 iOS 下自動喚起鍵盤,同時將 textarea 移出可視區(qū)域textarea.readOnly='readonly';textarea.style.position='absolute';textarea.style.left='-9999px';// 將要 copy 的值賦給 textarea 標簽的 value 屬性textarea.value=el.$value;// 將 textarea 插入到 body 中document.body.appendChild(textarea);// 選中值并復(fù)制textarea.select();// textarea.setSelectionRange(0, textarea.value.length);constresult=document.execCommand('Copy');if(result){Message.success('復(fù)制成功');}document.body.removeChild(textarea);};// 綁定點擊事件微王,就是所謂的一鍵 copy 啦el.addEventListener('click',el.handler);},// 當(dāng)傳進來的值更新的時候觸發(fā)componentUpdated(el,{value}){el.$value=value;},// 指令與元素解綁的時候屡限,移除事件綁定unbind(el){el.removeEventListener('click',el.handler);},};exportdefaultvCopy;

12.過濾器

一、是什么

過濾器(filter)是輸送介質(zhì)管道上不可缺少的一種裝置

大白話炕倘,就是把一些不必要的東西過濾掉

過濾器實質(zhì)不改變原始數(shù)據(jù)钧大,只是對數(shù)據(jù)進行加工處理后返回過濾后的數(shù)據(jù)再進行調(diào)用處理,我們也可以理解其為一個純函數(shù)

Vue?允許你自定義過濾器罩旋,可被用于一些常見的文本格式化

ps:?Vue3中已廢棄filter

二啊央、如何用

vue中的過濾器可以用在兩個地方:雙花括號插值和?v-bind?表達式,過濾器應(yīng)該被添加在?JavaScript?表達式的尾部涨醋,由“管道”符號指示:

<!--在雙花括號中-->{{message|capitalize}}<!--在`v-bind`中--><divv-bind:id="rawId | formatId"></div>

定義filter

在組件的選項中定義本地的過濾器

filters:{capitalize:function(value){if(!value)return''value=value.toString()returnvalue.charAt(0).toUpperCase()+value.slice(1)}}

定義全局過濾器:

Vue.filter('capitalize',function(value){if(!value)return''value=value.toString()returnvalue.charAt(0).toUpperCase()+value.slice(1)})newVue({// ...})

注意:當(dāng)全局過濾器和局部過濾器重名時瓜饥,會采用局部過濾器

過濾器函數(shù)總接收表達式的值 (之前的操作鏈的結(jié)果) 作為第一個參數(shù)。在上述例子中东帅,capitalize?過濾器函數(shù)將會收到?message?的值作為第一個參數(shù)

過濾器可以串聯(lián):

{{ message | filterA | filterB }}

在這個例子中压固,filterA?被定義為接收單個參數(shù)的過濾器函數(shù),表達式?message?的值將作為參數(shù)傳入到函數(shù)中靠闭。然后繼續(xù)調(diào)用同樣被定義為接收單個參數(shù)的過濾器函數(shù)?filterB帐我,將?filterA?的結(jié)果傳遞到?filterB?中。

過濾器是?JavaScript?函數(shù)愧膀,因此可以接收參數(shù):

{{ message | filterA('arg1', arg2) }}

這里拦键,filterA?被定義為接收三個參數(shù)的過濾器函數(shù)。

其中?message?的值作為第一個參數(shù)檩淋,普通字符串?'arg1'?作為第二個參數(shù)芬为,表達式?arg2?的值作為第三個參數(shù)

舉個例子:

<divid="app"><p>{{ msg | msgFormat('瘋狂','--')}}</p></div><script>// 定義一個 Vue 全局的過濾器,名字叫做? msgFormatVue.filter('msgFormat',function(msg,arg,arg2){// 字符串的? replace 方法蟀悦,第一個參數(shù)媚朦,除了可寫一個 字符串之外,還可以定義一個正則returnmsg.replace(/單純/g,arg+arg2)})</script>

小結(jié):

部過濾器優(yōu)先于全局過濾器被調(diào)用

一個表達式可以使用多個過濾器日戈。過濾器之間需要用管道符“|”隔開询张。其執(zhí)行順序從左往右

三、應(yīng)用場景

平時開發(fā)中浙炼,需要用到過濾器的地方有很多份氧,比如單位轉(zhuǎn)換唯袄、數(shù)字打點、文本格式化蜗帜、時間格式化之類的等

比如我們要實現(xiàn)將30000 => 30,000恋拷,這時候我們就需要使用過濾器

Vue.filter('toThousandFilter',function(value){if(!value)return''value=value.toString()return.replace(str.indexOf('.')>-1?/(\d)(?=(\d{3})+\.)/g:/(\d)(?=(?:\d{3})+$)/g,'$1,')})

四、原理分析

使用過濾器

{{message|capitalize}}

在模板編譯階段過濾器表達式將會被編譯為過濾器函數(shù)厅缺,主要是用過parseFilters蔬顾,我們放到最后講

_s(_f('filterFormat')(message))

首先分析一下_f:

_f 函數(shù)全名是:resolveFilter,這個函數(shù)的作用是從this.$options.filters中找出注冊的過濾器并返回

// 變?yōu)閠his.$options.filters['filterFormat'](message)// message為參數(shù)

關(guān)于resolveFilter

import{indentity,resolveAsset}from'core/util/index'exportfunctionresolveFilter(id){returnresolveAsset(this.$options,'filters',id,true)||identity}

內(nèi)部直接調(diào)用resolveAsset店归,將option對象阎抒,類型酪我,過濾器id消痛,以及一個觸發(fā)警告的標志作為參數(shù)傳遞,如果找到都哭,則返回過濾器秩伞;

resolveAsset的代碼如下:

exportfunctionresolveAsset(options,type,id,warnMissing){// 因為我們找的是過濾器,所以在 resolveFilter函數(shù)中調(diào)用時 type 的值直接給的 'filters',實際這個函數(shù)還可以拿到其他很多東西if(typeofid!=='string'){// 判斷傳遞的過濾器id 是不是字符串欺矫,不是則直接返回return}constassets=options[type]// 將我們注冊的所有過濾器保存在變量中// 接下來的邏輯便是判斷id是否在assets中存在纱新,即進行匹配if(hasOwn(assets,id))returnassets[id]// 如找到,直接返回過濾器// 沒有找到穆趴,代碼繼續(xù)執(zhí)行constcamelizedId=camelize(id)// 萬一你是駝峰的呢if(hasOwn(assets,camelizedId))returnassets[camelizedId]// 沒找到脸爱,繼續(xù)執(zhí)行constPascalCaseId=capitalize(camelizedId)// 萬一你是首字母大寫的駝峰呢if(hasOwn(assets,PascalCaseId))returnassets[PascalCaseId]// 如果還是沒找到,則檢查原型鏈(即訪問屬性)constresult=assets[id]||assets[camelizedId]||assets[PascalCaseId]// 如果依然沒找到未妹,則在非生產(chǎn)環(huán)境的控制臺打印警告if(process.env.NODE_ENV!=='production'&&warnMissing&&!result){warn('Failed to resolve '+type.slice(0,-1)+': '+id,options)}// 無論是否找到簿废,都返回查找結(jié)果returnresult}

下面再來分析一下_s:

_s?函數(shù)的全稱是?toString,過濾器處理后的結(jié)果會當(dāng)作參數(shù)傳遞給?toString函數(shù),最終?toString函數(shù)執(zhí)行后的結(jié)果會保存到Vnode中的text屬性中络它,渲染到視圖中

functiontoString(value){returnvalue==null?'':typeofvalue==='object'?JSON.stringify(value,null,2)// JSON.stringify()第三個參數(shù)可用來控制字符串里面的間距:String(value)}

最后族檬,在分析下parseFilters,在模板編譯階段使用該函數(shù)階段將模板過濾器解析為過濾器函數(shù)調(diào)用表達式

functionparseFilters(filter){letfilters=filter.split('|')letexpression=filters.shift().trim()// shift()刪除數(shù)組第一個元素并將其返回化戳,該方法會更改原數(shù)組letiif(filters){for(i=0;i<filters.length;i++){experssion=warpFilter(expression,filters[i].trim())// 這里傳進去的expression實際上是管道符號前面的字符串单料,即過濾器的第一個參數(shù)}}returnexpression}// warpFilter函數(shù)實現(xiàn)functionwarpFilter(exp,filter){// 首先判斷過濾器是否有其他參數(shù)consti=filter.indexof('(')if(i<0){// 不含其他參數(shù),直接進行過濾器表達式字符串的拼接return`_f("${filter}")(${exp})`}else{constname=filter.slice(0,i)// 過濾器名稱constargs=filter.slice(i+1)// 參數(shù)点楼,但還多了 ‘)’return`_f('${name}')(${exp},${args}`// 注意這一步少給了一個 ')'}}

小結(jié):

在編譯階段通過parseFilters將過濾器編譯成函數(shù)調(diào)用(串聯(lián)過濾器則是一個嵌套的函數(shù)調(diào)用扫尖,前一個過濾器執(zhí)行的結(jié)果是后一個過濾器函數(shù)的參數(shù))

編譯后通過調(diào)用resolveFilter函數(shù)找到對應(yīng)過濾器并返回結(jié)果

執(zhí)行結(jié)果作為參數(shù)傳遞給toString函數(shù),而toString執(zhí)行后掠廓,其結(jié)果會保存在Vnode的text屬性中换怖,渲染到視圖

上述均轉(zhuǎn)自: https://github.com/febobo/web-interview

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市却盘,隨后出現(xiàn)的幾起案子狰域,更是在濱河造成了極大的恐慌媳拴,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兆览,死亡現(xiàn)場離奇詭異屈溉,居然都是意外死亡,警方通過查閱死者的電腦和手機抬探,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門子巾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人小压,你說我怎么就攤上這事线梗。” “怎么了怠益?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵仪搔,是天一觀的道長。 經(jīng)常有香客問我蜻牢,道長烤咧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任抢呆,我火速辦了婚禮煮嫌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抱虐。我一直安慰自己昌阿,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布恳邀。 她就那樣靜靜地躺著懦冰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪轩娶。 梳的紋絲不亂的頭發(fā)上儿奶,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音鳄抒,去河邊找鬼闯捎。 笑死,一個胖子當(dāng)著我的面吹牛许溅,可吹牛的內(nèi)容都是我干的瓤鼻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼贤重,長吁一口氣:“原來是場噩夢啊……” “哼茬祷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起并蝗,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤祭犯,失蹤者是張志新(化名)和其女友劉穎秸妥,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沃粗,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡粥惧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了最盅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片突雪。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖涡贱,靈堂內(nèi)的尸體忽然破棺而出咏删,到底是詐尸還是另有隱情,我是刑警寧澤问词,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布督函,位于F島的核電站,受9級特大地震影響戏售,放射性物質(zhì)發(fā)生泄漏侨核。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一灌灾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧悲柱,春花似錦锋喜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拐辽,卻和暖如春育韩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蛇更。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工瞻赶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人派任。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓砸逊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親掌逛。 傳聞我的和親對象是個殘疾皇子师逸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容