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