day-101-one-hundred-and-one-20230628-自定義指令的玩法和作用-對(duì)象新增屬性不能響應(yīng)的問題-Vue組件中的data屬性-Vue生命周期-$nextTick-computed和watch
常見面試題
- 面試題:自定義指令的玩法和作用
- 面試題:Vue怎么用 vm.$set() 解決對(duì)象新增屬性不能響應(yīng)的問題 ?
- 面試題:Vue 組件中的 data 為什么必須是函數(shù)?
- 面試題:談?wù)勀銓?duì) Vue2 生命周期的理解?
- 面試題:簡(jiǎn)單說一下 $nextTick 的作用及實(shí)現(xiàn)原理?
- 面試題:computed 和 watch 的區(qū)別和運(yùn)用的場(chǎng)景?
自定義指令的玩法和作用
- 面試題:自定義指令的玩法和作用
- 在我之前的項(xiàng)目中,有些需求我是基于
Vue.directive()
創(chuàng)建自定義指令
完成的。我覺得這也算是一種封裝技巧,把一些要實(shí)現(xiàn)的功能,封裝成為自定義指令,以后基于v-xxx進(jìn)行調(diào)用,用起來也很方便! - 比如我之前封裝過:
- v-power 實(shí)現(xiàn)權(quán)限的校驗(yàn)。
- 在自定義指令內(nèi)部,獲取登錄者具備的權(quán)限標(biāo)識(shí),根據(jù)
傳遞進(jìn)來
的需要判斷的標(biāo)識(shí)
,驗(yàn)證當(dāng)前登錄者
是否具備相應(yīng)的權(quán)限,從而控制元素的渲染和銷毀。
- 在自定義指令內(nèi)部,獲取登錄者具備的權(quán)限標(biāo)識(shí),根據(jù)
- v-debounce/throttle 實(shí)現(xiàn)函數(shù)的防抖和節(jié)流。
- …
- 在ElementUI組件庫中,也提供了兩個(gè)自定義指令:v-InfiniteScroll處理無限滾動(dòng)、v-loading處理加載效果。
- v-power 實(shí)現(xiàn)權(quán)限的校驗(yàn)。
- 在Vue2和Vue3中,自定義指令的玩法是有一些區(qū)別的:
- 在vue2中,基于Vue.directive()創(chuàng)建自定義指令。
-
在其配置項(xiàng)中,包含這樣幾個(gè)鉤子函數(shù):
// 創(chuàng)建自定義指令。一般在全局環(huán)境中配置。 // v-jinyu Vue.directive("jinyu", { bind() {}, inserted() {}, update() {}, componentUpdated() {}, unbind() {}, });
- bind:把自定義指令綁定給某個(gè)元素后就會(huì)觸發(fā)。一般是組件第一次渲染的時(shí)候。
- inserted:被綁定元素插入父節(jié)點(diǎn)時(shí)調(diào)用 (僅保證父節(jié)點(diǎn)存在,但不一定已被插入文檔中)。
- 就保證父節(jié)點(diǎn)已經(jīng)存在了,但自身或子組件不一定已經(jīng)在頁面中有DOM結(jié)構(gòu)了。
- update/componentUpdated:組件更新后觸發(fā)(建議使用componentUpdated)。
- unbind:指令與元素解綁時(shí)調(diào)用(一般是組件銷毀的時(shí)候)。
-
而在這些鉤子函數(shù)中,都可以接收到四個(gè)參數(shù):
- el:指令所綁定的元素,可以用來直接操作DOM。
- binding:一個(gè)對(duì)象,包含很多信息。
- name:指令名字(沒有v-)。
- value:指令綁定的值。
- oldValue:
指令綁定的原來的值
。在componentUpdated鉤子函數(shù)中使用。 - expression:表達(dá)式。
- arg:參數(shù)。
- modifiers:修飾符。
- vnode/oldVnode:編譯的虛擬DOM對(duì)象。
-
我們會(huì)在指定的鉤子函數(shù)中,基于接收到的信息,完成項(xiàng)目中的需求!
-
- 但是在Vue3中,自定義指令的語法有些許的變化:
- 因?yàn)椴辉倬邆銿ue這個(gè)類,所以基于app.directive創(chuàng)建全局自定義指令。
- 相應(yīng)的鉤子函數(shù),調(diào)整為和組件鉤子函數(shù)相似的名字,讓語義化更明顯。
- created 代替了 bind
- beforeMount / mounted 代替了 inserted
- beforeUpdate / updated 代替了 componentUpdated
- beforeUnmount / unmounted 代替了 unbind
- 還有binging接收的信息中,新增了一個(gè)instance,當(dāng)把指令綁定給組件,可以獲取組件的實(shí)例。
- …
- 在vue2中,基于Vue.directive()創(chuàng)建自定義指令。
- 總之,我覺得自定義指令還是很常用的,尤其是針對(duì)于視圖中的每個(gè)元素/組件,需要做什么特殊的處理,用自定義指令來進(jìn)行封裝處理,在開發(fā)和使用上,會(huì)更加便捷一些。
- 自定義指令一般都是全局指令,以便全局都可以使用。
-
fang/f20230628/day0628/src/main.js
import Vue from 'vue' import './global' import App from './App.vue' new Vue({ render: h => h(App) }).$mount('#app')
-
fang/f20230628/day0628/src/global.js
import Vue from "vue"; import { Button, Tag, Loading, Message } from "element-ui"; import "element-ui/lib/theme-chalk/index.css"; Vue.use(Button).use(Tag).use(Loading.directive); Vue.prototype.$message = Message; Vue.config.productionTip = false; /* // 創(chuàng)建自定義指令。一般在全局環(huán)境中配置。 // v-jinyu Vue.directive("jinyu", { bind() {}, inserted() {}, update() {}, componentUpdated() {}, unbind() {}, }); */ // 創(chuàng)建自定義指令。一般在全局環(huán)境中配置。 // v-jinyu Vue.directive("jinyu", { bind(el, binding, vnode) { console.log(`el-->`, el); console.log(`binding-->`, binding); console.log(`vnode-->`, vnode); }, });
-
fang/f20230628/day0628/src/App.vue
<template> <div id="app"> <!-- <Demo /> --> <Demo1 /> <!-- <Demo2 /> <Demo2 /> <Demo2 /> --> <!-- <Demo3 /> --> <!-- <Demo4 /> --> <!-- <Demo5 /> --> <!-- <Demo6 /> --> <!-- <Demo7 /> --> </div> </template> <script> // import Demo from './views/Demo1.vue' import Demo1 from './views/Demo1.vue' import Demo2 from './views/Demo2.vue' import Demo3 from './views/Demo3.vue' import Demo4 from './views/Demo4.vue' import Demo5 from './views/Demo5.vue' import Demo6 from './views/Demo6.vue' import Demo7 from './views/Demo7.vue' export default { name: 'App', components: { Demo1, Demo2, Demo3, Demo4, Demo5, Demo6, Demo7, } } </script> <style lang="less"> @import './assets/reset.min.css'; html, body, #app { height: 100%; font-size: 14px; overflow: hidden; } </style>
-
fang/f20230628/day0628/src/views/Demo1.vue
<template> <div class="demo-box"> <button v-jinyu:aa.stop.trim="1 + 1">自定義指令</button> </div> </template> <script> export default { } </script> <style lang="less" scoped> .demo-box { box-sizing: border-box; } </style>
-
- 自定義指令一般都是全局指令,以便全局都可以使用。
- 在我之前的項(xiàng)目中,有些需求我是基于
對(duì)象新增屬性不能響應(yīng)的問題
- 面試題:Vue怎么用
vm.$set()
解決對(duì)象新增屬性
不能響應(yīng)的問題 ?-
在Vue既定的響應(yīng)式策略中,當(dāng)
new Vue()
或第一次渲染組件
的時(shí)候,只會(huì)把初始化data()時(shí)
中現(xiàn)有的狀態(tài)
進(jìn)行數(shù)據(jù)劫持
,對(duì)于后期新增進(jìn)來的成員
,默認(rèn)將不會(huì)進(jìn)行數(shù)據(jù)劫持了!- 所以我之前開發(fā)的時(shí)候,都會(huì)把
需要的狀態(tài)信息
提前寫入到data中,讓其變?yōu)轫憫?yīng)式的。 - 只是
新增的成員
不會(huì)再做響應(yīng)式處理
。- 但是對(duì)于已有成員
新增或者修改的值
,Vue2內(nèi)部依然會(huì)調(diào)用observe()方法
進(jìn)行數(shù)據(jù)劫持
。
- 但是對(duì)于已有成員
- 所以我之前開發(fā)的時(shí)候,都會(huì)把
-
所以不瞞你說,
this.$set()
我雖然知道是什么回事,但是項(xiàng)目中基本上很少使用。 -
但是我之前研究Vue2響應(yīng)式原理的時(shí)候,看過
$set()
的源碼,它最主要的目的是:給對(duì)象新增的成員做響應(yīng)式的數(shù)據(jù)劫持,并且通知視圖更新!- 在
/node_modules/vue/dist/vue.js
中的function set(target, key, val)
;
-
但是其內(nèi)部有很多特殊處理:
Vue.prototype.$set = set 實(shí)例.$set(target, key, val)
- $set()在
Vue.prototype
上。
- 要求傳遞進(jìn)來的target是一個(gè)對(duì)象類型值,而且不能是只讀的(比如:props),也不能是Vue的實(shí)例對(duì)象。
- 如果target是一個(gè)數(shù)組,key是其一個(gè)索引項(xiàng)。
- target是響應(yīng)式的:基于重寫的splice方法新增/修改索引項(xiàng)的信息,完成后通知視圖更新,對(duì)新修改/新增的值,基于ovserve進(jìn)行響應(yīng)式處理。
- 如果其不是響應(yīng)式的:則直接基于內(nèi)置的splice實(shí)現(xiàn)此索引項(xiàng)的新增/修改即可。
- 如果target是對(duì)象,核心在于target對(duì)象是否是經(jīng)過響應(yīng)式處理的:
-
如果沒有經(jīng)過處理:則基于$set的所有操作,都僅僅是在新增值或修改值,不會(huì)做任何其余的處理。例如:讓視圖更新,或者對(duì)新增的值進(jìn)行響應(yīng)式處理等。
-
如果是經(jīng)過處理的:
-
key:如果是target現(xiàn)有的成員(本身也是響應(yīng)式的),那么在修改值的時(shí)候,會(huì)觸發(fā)key本身的setter劫持函數(shù),實(shí)現(xiàn)數(shù)據(jù)更改、新數(shù)據(jù)的響應(yīng)式處理、通知視圖更新等。
-
key如果在target中不存在,則給新增的成員,基于defineReactive()進(jìn)行數(shù)據(jù)劫持、并且讓視圖更新。
-
具體的操作:
- 如果key是target中現(xiàn)在的一個(gè)成員,則直接修改成員值:
- 如果target是響應(yīng)式的(key也是響應(yīng)式):則基于target[key]=value操作,針對(duì)觸發(fā)key的set劫持函數(shù),讓視圖進(jìn)行更新。
- 如果target不是響應(yīng)式的:則僅是修改成員值,但是后續(xù)不會(huì)做任何的操作。比如:讓視圖更新,或者新值的響應(yīng)式處理等,都不會(huì)做。
- 如果操作的target對(duì)象本身不是響應(yīng)式的,則也是直接修改/新增值,后續(xù)不會(huì)做任何的操作。
- 只有target對(duì)象本身是響應(yīng)式的,key這個(gè)成員,之前在對(duì)象中并不存在,此時(shí)才會(huì)給新增的對(duì)象進(jìn)行數(shù)據(jù)劫持,并且讓視圖更新!
- 如果key是target中現(xiàn)在的一個(gè)成員,則直接修改成員值:
function set(target, key, val) { //要求傳遞進(jìn)來的target是一個(gè)對(duì)象類型值。 if ((isUndef(target) || isPrimitive(target))) { warn$2("Cannot set reactive property on undefined, null, or primitive value: ".concat(target)); } //要求傳遞進(jìn)來的對(duì)象不能是只讀的! if (isReadonly(target)) { warn$2("Set operation on key \"".concat(key, "\" failed: target is readonly.")); return; } //如果target是一個(gè)數(shù)組,key是其一個(gè)索引項(xiàng)。 var ob = target.__ob__; if (isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key); target.splice(key, 1, val); // when mocking for SSR, array methods are not hijacked if (ob && !ob.shallow && ob.mock) { observe(val, false, true); } return val; } //如果key是target中現(xiàn)在的一個(gè)成員,則直接修改成員值,但是后續(xù)不會(huì)做任何的操作。 if (key in target && !(key in Object.prototype)) { target[key] = val; return val; } //要求傳遞進(jìn)來的target是不能是Vue的實(shí)例對(duì)象。 if (target._isVue || (ob && ob.vmCount)) { warn$2('Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.'); return val; } //如果操作的target對(duì)象本身不是響應(yīng)式的,則也是直接修改/新增值,后續(xù)不會(huì)做任何的操作。 if (!ob) { target[key] = val; return val; } //只有target對(duì)象本身是響應(yīng)式的,key這個(gè)成員,之前在對(duì)象中并不存在,此時(shí)才會(huì)給新增的對(duì)象進(jìn)行數(shù)據(jù)劫持,并且讓視圖更新。 defineReactive(ob.value, key, val, undefined, ob.shallow, ob.mock); { ob.dep.notify({ type: "add" /* TriggerOpTypes.ADD */, target: target, key: key, newValue: val, oldValue: undefined }); } return val; }
-
-
- $set()在
- 在
-
其實(shí)vue.prototype上也還有和 s e t 類似的方法: set類似的方法: set類似的方法:delete,主要目的是:刪除data中數(shù)組/對(duì)象的某個(gè)成員,可以讓視圖更新!
function del(target, key) { if ((isUndef(target) || isPrimitive(target))) { warn$2("Cannot delete reactive property on undefined, null, or primitive value: ".concat(target)); } if (isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1); return; } var ob = target.__ob__; if (target._isVue || (ob && ob.vmCount)) { warn$2('Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.'); return; } if (isReadonly(target)) { warn$2("Delete operation on key \"".concat(key, "\" failed: target is readonly.")); return; } if (!hasOwn(target, key)) { return; } delete target[key]; if (!ob) { return; } { ob.dep.notify({ type: "delete" /* TriggerOpTypes.DELETE */, target: target, key: key }); } }
-
總結(jié):真實(shí)項(xiàng)目中, s e t / set/ set/delete雖然有用,但是我一般很少用,個(gè)人覺得不如把需要的狀態(tài)都事先寫在data中,這樣方便開發(fā)調(diào)試!
-
Vue組件中的data屬性
-
面試題:Vue 組件中的 data 為什么必須是函數(shù)?
- 例子:
-
如果是函數(shù):
<script> export default { data() { return { num: 0, }; }, }; </script>
<template> <div class="demo-box"> <div class="text">{{num}}</div> <el-button type="primary" size="small" @click="handle">按鈕</el-button> </div> </template> <script> export default { data() { return { num: 0, }; }, methods: { handle() { this.num += 10; }, }, }; </script> <style lang="less" scoped> .demo-box { box-sizing: border-box; margin: 10px auto; padding: 10px; width: 200px; background: lightblue; } </style>
-
如果是對(duì)象:
<script> export default { data: { num: 0, }, }; </script>
<template> <div class="demo-box"> <div class="text">{{ num }}</div> <el-button type="primary" size="small" @click="handle">按鈕</el-button> </div> </template> <script> export default { data: { num: 0, }, methods: { handle() { this.num += 10; }, }, }; </script> <style lang="less" scoped> .demo-box { box-sizing: border-box; margin: 10px auto; padding: 10px; width: 200px; background: lightblue; } </style>
-
- 例子:
-
如果給單文件組件中的data寫成對(duì)象格式-而非函數(shù),直接會(huì)報(bào)錯(cuò)。
[Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions.
-
為什么Vue內(nèi)部,要求組件中的data必須是函數(shù),而不能是對(duì)象呢?
-
因?yàn)槿绻麑懗蓪?duì)象,那么對(duì)象中的狀態(tài)數(shù)據(jù)是共享的,此時(shí)如果一個(gè)組件被調(diào)用多次,即便創(chuàng)建多個(gè)實(shí)例,但是這樣狀態(tài)是共享的,在某個(gè)被調(diào)用的組件中修改狀態(tài),也會(huì)影響其它被調(diào)用組件中的信息!
-
而把data寫成一個(gè)函數(shù),是因?yàn)楹瘮?shù)執(zhí)行會(huì)產(chǎn)生閉包,讓狀態(tài)是組件內(nèi)部私有的,而不是共享的,以此來避免沖突!
-
源碼中是在initState()–>initData(vm);
function initState(vm) { var opts = vm.$options; if (opts.props) initProps$1(vm, opts.props); // Composition API initSetup(vm); if (opts.methods) initMethods(vm, opts.methods); if (opts.data) { initData(vm); } else { var ob = observe((vm._data = {})); ob && ob.vmCount++; } if (opts.computed) initComputed$1(vm, opts.computed); if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); } }
function initData(vm) { var data = vm.$options.data; data = vm._data = isFunction(data) ? getData(data, vm) : data || {}; if (!isPlainObject(data)) { data = {}; warn$2('data functions should return an object:\n' + 'https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm); } // proxy data on instance var keys = Object.keys(data); var props = vm.$options.props; var methods = vm.$options.methods; var i = keys.length; while (i--) { var key = keys[i]; { if (methods && hasOwn(methods, key)) { warn$2("Method \"".concat(key, "\" has already been defined as a data property."), vm); } } if (props && hasOwn(props, key)) { warn$2("The data property \"".concat(key, "\" is already declared as a prop. ") + "Use prop default value instead.", vm); } else if (!isReserved(key)) { proxy(vm, "_data", key); } } // observe data var ob = observe(data); ob && ob.vmCount++; }
-
Vue生命周期
- 面試題:談?wù)勀銓?duì) Vue 生命周期的理解?
- Vue2和Vue3的鉤子函數(shù)還是有一些區(qū)別的。
- 在Vue2中,提供了好幾個(gè)鉤子函數(shù),但是在我之前的項(xiàng)目開發(fā)中,常用的就那幾個(gè):
- created:當(dāng)把要初始化的信息都處理好(例如屬性、狀態(tài)、監(jiān)聽器、計(jì)算屬性、還有一些內(nèi)置的私有屬性等),當(dāng)
應(yīng)該掛載到實(shí)例上的信息
都掛載完畢了,此時(shí)Vue內(nèi)部會(huì)基于callHook函數(shù),觸發(fā)created鉤子函數(shù)執(zhí)行!- 我一般會(huì)在這里向服務(wù)器發(fā)送異步數(shù)據(jù)請(qǐng)求,這樣在組件第一次渲染期間,就在請(qǐng)求數(shù)據(jù),等待第一次渲染完畢,可能數(shù)據(jù)已經(jīng)獲取到了,這樣可以盡快地把真實(shí)數(shù)據(jù)渲染到視圖中!
- 當(dāng)然也可以放在beforeMount鉤子函數(shù)中處理,和created是一樣的!
- mounted:會(huì)在組件第一次渲染完畢后觸發(fā),此時(shí)在這個(gè)鉤子中,就可以基于ref獲取真實(shí)的DOM(或者
子組件的實(shí)例
),這樣可以干很多事情,例如:- 手動(dòng)基于addEventListener給真實(shí)DOM進(jìn)行事件綁定(一般是某些第三方插件中,需要我們自己手動(dòng)綁定-如元素拖拽插件)。
- 還可以自己創(chuàng)建定時(shí)器或者監(jiān)聽器去實(shí)現(xiàn)一些需求,例如:
- 我之前實(shí)現(xiàn)一個(gè)
觸底加載更多數(shù)據(jù)
的功能,就是基于創(chuàng)建IntersectionObserver()監(jiān)聽器
實(shí)現(xiàn)的 - 之前也設(shè)置過定時(shí)器,在第一次渲染完畢后,延遲一段時(shí)間去處理一些事情
- …
- 我之前實(shí)現(xiàn)一個(gè)
- 用的一些第三方插件(比如:echarts/swiper/IScroll等),也需要在視圖第一次渲染完畢有對(duì)應(yīng)的DOM元素后,進(jìn)行初始化處理,實(shí)現(xiàn)相關(guān)的效果!
- 總之mounted鉤子函數(shù)還是很常用的!
- updated:在組件每一次更新完畢后觸發(fā),如果想在更新完畢后做什么事,可以寫在這個(gè)鉤子函數(shù)中。
- 只不過,不論那個(gè)狀態(tài)改變,只要組件更新了,updated都會(huì)被觸發(fā)。如果需求就是:不論誰改變,都要做這個(gè)事情,寫在updated中沒有任何問題!
- 但如果是改變不同狀態(tài)要做不同的事情,我往往會(huì)基于 n e x t T i c k 來代替 u p d a t e d , nextTick來代替updated, nextTick來代替updated,nextTick也是在組件更新完畢后觸發(fā),而且是晚于updated的。
- beforeDestory/destoryed:組件銷毀之前/之后觸發(fā)的鉤子函數(shù)。
- 我一般會(huì)在這里,手動(dòng)清除一些內(nèi)容,比如:設(shè)置的定時(shí)器、監(jiān)聽器、手動(dòng)基于addEventListener做的事件綁定等等,這些東西不會(huì)隨著組件銷毀而釋放,需要手動(dòng)清除,來優(yōu)化內(nèi)存空間。
- 之前搞管理系統(tǒng)的項(xiàng)目,有的表單頁表單內(nèi)容特別多,用戶可能一次性寫不完,我做了個(gè)信息草稿箱的功能:在組件銷毀之前,把用戶已經(jīng)填寫(但是還沒有填完、沒有提交)的信息,存儲(chǔ)到本地 localStorage 中,當(dāng)用戶關(guān)掉頁面,下一次再重新進(jìn)來的時(shí)候,把之前存儲(chǔ)的信息直接放在對(duì)應(yīng)的表單中,這樣可以讓用戶不再重新編寫!
- 還有一些其它的鉤子函數(shù),比如:beforeCreate、beforeMount、beforeUpdate等,只不過我在項(xiàng)目中沒有用過!
- 項(xiàng)目中配合
<keep-alive>組件
進(jìn)行緩存,讓組件還具備了activated、deactivated兩個(gè)鉤子! - 當(dāng)我們使用 vue-router 的時(shí)候,在組件中有一些組件內(nèi)的,導(dǎo)航守衛(wèi)鉤子函數(shù),例如:beforeRouteEnter、beforeRouteLeave、beforeRouteUpdate等,只不過我也很少用!
- 項(xiàng)目中配合
- created:當(dāng)把要初始化的信息都處理好(例如屬性、狀態(tài)、監(jiān)聽器、計(jì)算屬性、還有一些內(nèi)置的私有屬性等),當(dāng)
- 在Vue3中,全面使用函數(shù)式編程,在vue中提供了相應(yīng)的鉤子函數(shù),需要處理的事情和Vue2依然差不多,只不過鉤子函數(shù)的名字改了!
- 沒有 created/beforeCreate 了,被 setup 聚合函數(shù)代替,所以我會(huì)把發(fā)送數(shù)據(jù)請(qǐng)求的事情,放在 onBeforeMount 鉤子函數(shù)中!
-
onMounted
代替了mounted
-
onUpdated
代替了updated
,nextTick函數(shù)
代替了$nextTick
- 這些變化還不大,主要是組件銷毀:
-
onBeforeUnmount
代替了beforeDestory
-
onUnmounted
代替了destoryed
-
- 在Vue2中,提供了好幾個(gè)鉤子函數(shù),但是在我之前的項(xiàng)目開發(fā)中,常用的就那幾個(gè):
- 以上就是我常用的鉤子函數(shù)和處理的一些事情,當(dāng)然還有很多之前做過的需求,這里就不一一列舉了!
- Vue2和Vue3的鉤子函數(shù)還是有一些區(qū)別的。
生命周期的細(xì)節(jié)
- 在Vue中修改狀態(tài)值,會(huì)觸發(fā)對(duì)應(yīng)setter劫持函數(shù),在此函數(shù)中:
- 同步修改狀態(tài)值-并且立即基于observe對(duì)新的值進(jìn)行響應(yīng)式處理。
- 異步通知視圖更新-每一次更改狀態(tài),并沒有立即進(jìn)行視圖更新,而是把更新的操作,放在更新隊(duì)列中,等待同步操作處理完畢,再把更新隊(duì)列中的任務(wù)統(tǒng)一批處理一次,這樣可以有效減少視圖更新編譯的次數(shù),節(jié)約性能,讓視圖渲染速度更快。
$nextTick
- 面試題:簡(jiǎn)單說一下 $nextTick 的作用及實(shí)現(xiàn)原理?
-
我之前研究過Vue視圖更新的原理:
- 在Vue中,修改狀態(tài)讓視圖更新,其中修改狀態(tài)值是同步的,但是讓視圖更新的操作是異步的。
- 其實(shí)就是Vue內(nèi)部實(shí)現(xiàn)了一個(gè)更新隊(duì)列,每次讓視圖更新,都不會(huì)立即更新,而是把其放在更新隊(duì)列中;
- 而讓更新隊(duì)列執(zhí)行的操作是一個(gè)異步任務(wù)。支持Promise的瀏覽器,它是一個(gè)異步微任務(wù),因?yàn)槭腔赑romise處理的。不支持的瀏覽器,它是一個(gè)異步宏任務(wù),因?yàn)槭腔诙〞r(shí)器處理的。
- 當(dāng)同步代碼執(zhí)行完畢后,會(huì)通知異步任務(wù)執(zhí)行,讓所有的更新操作統(tǒng)一批處理,這樣視圖只需要更新一次。
- 這樣設(shè)計(jì)的目的是:
- 連續(xù)修改多個(gè)狀態(tài)值,視圖只需要更新一次,節(jié)省性能,加快視圖的渲染。
- 也可以讓代碼執(zhí)行的順序更加清晰。
- 還可以避免無效的更新操作。每一次加入更新隊(duì)列的時(shí)候,內(nèi)部都會(huì)去重。
- 在Vue中,修改狀態(tài)讓視圖更新,其中修改狀態(tài)值是同步的,但是讓視圖更新的操作是異步的。
-
但是這樣也導(dǎo)致一些問題:如果我們想在狀態(tài)更改、視圖更新完畢后,做一些事情,只能寫在updated鉤子函數(shù)中,但是不論進(jìn)行何種操作,只要讓視圖更新,updated都會(huì)執(zhí)行;如果只想這幾個(gè)狀態(tài)更改,單獨(dú)做一些事情,此時(shí)就需要使用Vue中提供的$nextTick函數(shù)了!
-
$nextTick之所以有這樣的效果,其內(nèi)部是利用EventLoop事件循環(huán)機(jī)制:
- 當(dāng)執(zhí)行$nextTick(callback)的時(shí)候,會(huì)把callback放在一個(gè)callbacks集合中。
- 而異步微任務(wù)(IE是異步宏任務(wù))中有一個(gè)任務(wù),就是用來通知callbacks集合中的函數(shù)執(zhí)行的。
- 等待同步操作完畢(或者上一個(gè)異步任務(wù)執(zhí)行完畢-例如:視圖更新),會(huì)把callbacks中的函數(shù)都按照順序執(zhí)行。
-
在我之前的項(xiàng)目開發(fā)中,我經(jīng)常使用$nextTick代替updated;
-
$nextTick在沒有傳遞callback函數(shù)的時(shí)候,會(huì)返回一個(gè)promise實(shí)例,我們可以基于await等待實(shí)例成功后,再執(zhí)行我們要干的事情,效果和傳遞callback一樣,只不過我用的最多的還是callback方式。
-
computed和watch
- 面試題:computed 和 watch 的區(qū)別和運(yùn)用的場(chǎng)景?
- 在我之前的項(xiàng)目開發(fā)中,computed和watch都很常用。
- computed:計(jì)算屬性。
-
用法:
computed:{ ratio(){} } computed:{ ratio:{ get(){}, //等價(jià)于上面的函數(shù) set(){} //手動(dòng)修改計(jì)算屬性值的時(shí)候,觸發(fā)這個(gè)函數(shù) } }
-
依賴于其它狀態(tài)/屬性值,計(jì)算出一個(gè)新值:
- 創(chuàng)建的計(jì)算屬性會(huì)被掛載到實(shí)例上,所以不能和data/methods/props中的字段沖突。
- 而且也進(jìn)行了數(shù)據(jù)劫持。
- set劫持函數(shù):在我們沒有設(shè)置set函數(shù)的時(shí)候,手動(dòng)修改計(jì)算屬性的值,會(huì)報(bào)錯(cuò)。如果我們自己設(shè)置了set函數(shù),則手動(dòng)修改值的時(shí)候,執(zhí)行我們自己設(shè)置的set函數(shù)!
- get劫持函數(shù):獲取計(jì)算屬性值,只不過這個(gè)值可能用的是緩存的舊值,也可能是重新執(zhí)行函數(shù)算出來的新值。
-
計(jì)算屬性具備一個(gè)特點(diǎn):計(jì)算緩存。
- 第一次獲取計(jì)算屬性,會(huì)把對(duì)應(yīng)的函數(shù)執(zhí)行,計(jì)算出結(jié)果。
- 把此結(jié)果緩存起來。
- 把函數(shù)中用到的狀態(tài)與屬性,建立起依賴追蹤:所謂依賴追蹤,就是在函數(shù)執(zhí)行的時(shí)候,用到那些狀態(tài)/屬性,都會(huì)觸發(fā)其對(duì)應(yīng)的getter函數(shù),在getter函數(shù)中,設(shè)立監(jiān)聽機(jī)制(目的是監(jiān)聽其是否變化)!
- 第二次及以后再獲取計(jì)算屬性值的時(shí)候,首先看依賴的狀態(tài)是否發(fā)生過改變。
- 如果沒有變:直接獲取之前緩存的結(jié)果來使用。
- 如果其中至少有一個(gè)依賴的狀態(tài)/屬性有變化,都需要把函數(shù)重新執(zhí)行,重新計(jì)算新的值出來…
- 第一次獲取計(jì)算屬性,會(huì)把對(duì)應(yīng)的函數(shù)執(zhí)行,計(jì)算出結(jié)果。
-
想比較于methods中的方法來講,computed計(jì)算屬性,因?yàn)槠溆?jì)算緩存/依賴追蹤的機(jī)制,可以避免函數(shù)每一次都重新執(zhí)行,減少非必要的消耗,提高頁面渲染的速度!
-
- watch: 監(jiān)聽器
- 從initWatch(vm, watch)中開始。
-
監(jiān)聽器的語法:文章來源:http://www.zghlxwxcb.cn/news/detail-507160.html
watch: { x(newVal, oldVal) { console.log(`x發(fā)生改變`, newVal, oldVal); // ... }, y: { handler(newVal, oldVal) { console.log(`y發(fā)生改變`, newVal, oldVal); //... }, immediate: true, //讓組件第一次渲染就立即執(zhí)行一次。 deep: true, //開啟深度監(jiān)聽:如果監(jiān)聽的狀態(tài)是個(gè)對(duì)象,修改對(duì)象中的內(nèi)容,也可以觸發(fā)監(jiān)聽器。 }, z: [ // 當(dāng)z發(fā)生改變,數(shù)組中的每一項(xiàng)規(guī)則都會(huì)執(zhí)行。 function () {}, { handler(newVal, oldVal) { console.log(`y發(fā)生改變`, newVal, oldVal); //... }, immediate: true, //讓組件第一次渲染就立即執(zhí)行一次。 deep: true, //開啟深度監(jiān)聽:如果監(jiān)聽的狀態(tài)是個(gè)對(duì)象,修改對(duì)象中的內(nèi)容,也可以觸發(fā)監(jiān)聽器。 }, ], h:'init',//當(dāng)h改變的時(shí)候,去實(shí)例上找init方法執(zhí)行。 },
-
監(jiān)聽
現(xiàn)有的狀態(tài)
/現(xiàn)有的屬性
,當(dāng)被監(jiān)聽的狀態(tài)
/被監(jiān)聽的屬性
發(fā)生改變,會(huì)把指定的handler方法執(zhí)行。在Vue內(nèi)部,就是基于創(chuàng)建Watcher類的實(shí)例來完成的。文章來源地址http://www.zghlxwxcb.cn/news/detail-507160.html
- computed:計(jì)算屬性。
- 無論是computed還是watch,其實(shí)它們的底層都是建立了監(jiān)聽機(jī)制。都是當(dāng)依賴或監(jiān)聽的狀態(tài)發(fā)生改變,對(duì)應(yīng)的函數(shù)才會(huì)執(zhí)行,從性能來講其實(shí)是差不多的!
- 只不過,computed會(huì)自動(dòng)對(duì)函數(shù)中用到的所有狀態(tài),都自動(dòng)建立起依賴追蹤,而watch需要我們一個(gè)個(gè)地去進(jìn)行監(jiān)聽。從便捷度來講,如果需要監(jiān)聽多個(gè)狀態(tài),做相同的事情,用computed會(huì)更方便一些。
- 如果是依賴別的狀態(tài)算出新的值,computed更符合語義化一些。而watch的語義,就是監(jiān)聽某個(gè)狀態(tài)變化,做點(diǎn)啥事,但是不一定需要算出新的值!從這一點(diǎn)出發(fā),到底用computed還是watch,可以看是否需要算新的值出來!
- watch有一個(gè)computed做不到的能力:在watch設(shè)定的監(jiān)聽函數(shù)中,是可以發(fā)送異步數(shù)據(jù)請(qǐng)求的,而computed中是不搞異步操作的!
- 在項(xiàng)目中,我就是根據(jù)computed和watch的多方面特征,來選擇用誰更合適的,如果兩個(gè)都合適,我個(gè)人習(xí)慣于用computed計(jì)算屬性!
- 接下來還可以舉一些具體應(yīng)用的例子…
- 在我之前的項(xiàng)目開發(fā)中,computed和watch都很常用。
進(jìn)階參考
到了這里,關(guān)于20230628----重返學(xué)習(xí)-自定義指令的玩法和作用-對(duì)象新增屬性不能響應(yīng)的問題-Vue組件中的data屬性-Vue生命周的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!