86.說說vue生命周期,發(fā)送請求在生命周期的哪個階段,為什么不可以是beforeMount,mounted中
回答:
1、vue的生命周期
1)、生命周期是什么? Vue 實例有一個完整的生命周期,也就是從開始創(chuàng)建、初始化數(shù)據(jù)、編譯模版、掛載 Dom -> 渲染、更新 -> 渲染、卸載等一系列過程,我們稱這是 Vue 的生命周期。
2)、各個生命周期階段及其鉤子函數(shù)
vue的生命周期核心經歷了四個階段,在四個階段的前后分別有兩個鉤子函數(shù)。
第一階段:數(shù)據(jù)掛載階段:把配置項data中的屬性,賦給了vue對象本身,并做了數(shù)據(jù)劫持。
該階段前后的兩個鉤子函數(shù):beforeCreate和created
第二階段:模板渲染階段:把vue對象的數(shù)據(jù)渲染到模板上。
該階段前后的兩個鉤子函數(shù):beforeMount和mounted
第三階段:組件更新階段:當數(shù)據(jù)發(fā)送變化時,組件會進行重新渲染,所以,準確的說是,組件重新渲染階段。
該階段前后的兩個鉤子函數(shù):beforeUpdate和updated
第四階段:組件銷毀階段:組件銷毀。
該階段前后的兩個鉤子函數(shù):beforeDestroy和destroyed
視情況可以補充:
當使用keep-alive包裹組件時,會有組件激活和停用之說,這兩個鉤子函數(shù)分別是:activited和deactivated
2、發(fā)送請求在生命周期的哪個階段,為什么不可以是beforeMount,mounted中。
(如果組件的初始數(shù)據(jù)來自后端)發(fā)送請求建議在鉤子函數(shù)created里,這個鉤子函數(shù)里是最快,也能有助于一致性。
為什么?
1)、為什么不能放在beforeCreate里?
因為:
一般來說,數(shù)據(jù)從后端返回回來后,都會賦給vue中的屬性(data掛載的),在beforeCreate鉤子函數(shù)里,data的數(shù)據(jù)還沒有掛載到vue對象本身上,所以,不能在這個鉤子函數(shù)里用。而created鉤子函數(shù)里,數(shù)據(jù)已經掛載到vue對象了。
2)、為什么不可以是beforeMount,mounted中
因為:
第一,在這兩個鉤子函數(shù)里,發(fā)送請求有點晚,會增加頁面loading的時間;
第二,vue的SSR不支持beforeMount 、mounted 鉤子函數(shù),所以,放在 created 中有助于一致性
87.兄弟和父子組件,在不能使用vuex的情況下有多少種方案,請列舉出來?
回答:
1、父子之間傳值:
1)、父到子:props,子到父:$emit
2)、$ref、$parent
3)、事件總線(event-bus)
4)、集中管理($root)
2、兄弟之間傳值:
1)、事件總線(event-bus)
2)、集中管理($root)
3)、先 子到父(用事件),再 父到子(用props)
88.v-if和v-for可以同時使用嗎
回答:
可以使用,但是,在循環(huán)時,通過v-if只能拿到少部分數(shù)據(jù)時,建議不要使用。
原因: v-for比v-if優(yōu)先級高,如果遍歷的數(shù)組元素個數(shù)比較多,但是滿足v-if條件比較少的情況下。會浪費性能。而且,每次刷新頁面時,都會執(zhí)行這樣性能不高的代碼。
解決:可以在computed里循環(huán)數(shù)組,通過filter的方式,過濾出需要的數(shù)據(jù)。v-for直接循環(huán)計算屬性的結果(不用v-if)。而computed是有緩存的,所以,在原始數(shù)據(jù)沒有變化時,不會多次過濾數(shù)據(jù),這樣,就提高了效率。
89.vue-cli怎么配置跨域
回答:
使用反向代理,vue-cli3+的項目里,新建(編輯)vue.config.js文件,(增加)配置代碼如下:
module.exports = {
devServer:{
//設置代理
proxy: { //代理是從指定的target后面開始匹配的,不是任意位置;配置pathRewrite可以做替換
'/api': { //這個是自定義的,當axios訪問以/api開頭的請求時,會代理到 target + /api
target: 'http://localhost:3001', //最終要訪問的服務器地址
changeOrigin: true, //創(chuàng)建虛擬服務器
pathRewrite: {
'^/api': '' //重寫接口,去掉/api, 在代理過程中是否替換掉/api/路徑
}
}
}
}
}
90.v-bind是用來干什么的
回答:
v-bind指令是把標簽的屬性處理成動態(tài)的。分別可以把屬性名和屬性值處理成vue里的屬性,常間的是屬性值處理成動態(tài)的。
格式如下:
1、屬性值動態(tài)綁定:v-bind:html屬性="數(shù)據(jù)" 簡寫 :html屬性="數(shù)據(jù)"`
示例:
<img v-bind:src="imgstr"></div>
new Vue({
data:{
imgstr:'./imgs/1.jpg'
}
})
2、 屬性名動態(tài)綁定:v-bind:[屬性名]="數(shù)據(jù)"
此時,屬性值也是動態(tài)的
示例:
<div v-bind:[attr]="idname" >我是div</div>
new Vue({
el: "#app",
data:{
attr:"class",
idname:"div01"
}
})
91.說說對插槽的理解
回答:
1、插槽的作用:
插槽是用來處理組件的內容的。插槽決定了組件的內容放在組件模板的何處。插槽使用的是vue官方提供的組件<slot>來完成的。
2、vue中的插槽分為:
1)、單個插槽
在組件中只有一個插槽時,插槽不用起名字。默認的名字是:default
示例:
//1、定義組件
let book = {
template: `
<div>
<p>我是上p</p>
<!--這是插槽,該組件的內容將會渲染在此處 -->
<slot></slot>
<p>我是下p</p>
</div>
`,
}
//2、父組件的模板
<div id="box">
<book>
<!--放在組件book標簽的內容,最終會渲染在book標簽的<slot>處。-->
<img src="imgs/2.jpg" />
</book>
</div>
2)、具名插槽
但組件中的插槽多于一個時,就需要給組件起名字,用名字來區(qū)分不同的插槽。用官方組件slot的name屬性給插槽起名字
格式:
<slot name="插槽的名字"></slot>
示例:
//1、組件:
let book = {
template: `
<div>
<p>我是上p</p>
<!--具名插槽,該插槽起名為s1-->
<slot name="s1"></slot>
<p>我是中p</p>
<!--具名插槽,該插槽起名為s2-->
<slot name="s2"></slot>
<p>我是下p</p>
</div>
`,
}
//2、父組件模板里:
<div id="box">
<book>
<!--以下內容插在name為s1的插槽處-->
<template v-slot:s1>
<img src="imgs/2.jpg" />
</template>
<!--以下內容插在name為s2的插槽處-->
<template v-slot:s2>
<div>我是div</div>
</template>
</book>
</div>
92.$nextTick理解 和定時器有什么區(qū)別 都是延時執(zhí)行
回答:
1、$nextTick理解:
vue更新DOM時,使用的是異步更新隊列。目的是提高性能,避免無效的重復的DOM更新。即:vue中更新數(shù)據(jù)后,并不會立即更新DOM,而是把數(shù)據(jù)引起的DOM更新放入到異步更新隊列里。等待下次事件循環(huán)(tick),并在兩個tick之間進行UI渲染。
按照這個思路,程序員就不能在更改數(shù)據(jù)后,立即獲取更新后的DOM,也不知道什么時候DOM能夠更新?;诖?,vue提供了$nextTick函數(shù)。程序員只需要把 ”操作更新后DOM的代碼“ 放入到$nextTick的回調函數(shù)里。由$nextTick內部,在更新完DOM后,調用回調函數(shù)。
示例:
this.msg = "hello"
this.$nextTick(()=>{
操作“this.msg影響的DOM”的代碼。
})
2、$nextTick理解和定時器有什么區(qū)別
相同:都是延遲加載,都使用事件隊列
不同:
1)、定時器是下一個隊列的隊首。
2)、$nextTick()是放在當前隊列的最后一個。$nextTick()的回調函數(shù)執(zhí)行要先于定時器。
93.event-bus是怎么用?
event-bus是事件總線,是借助一個全局的vue對象,來完成事件的綁定和事件的觸發(fā)。
當:我們需要把A組件的數(shù)據(jù)傳給B組件時,在A、B兩個組件里都引入全局的vue對象。然后,在B組件里綁定事件,在A組件里觸發(fā)事件,就可以把A組件的數(shù)據(jù)傳給B組件了。
示例:
//1、全局的vue對象: bus.js
export default new Vue();//vue 對象具有 $on 和 $emit 方法
//2、B組件的代碼
import bus from "./bus.js"
export default {
………………
data(){
return {
bookmsg:""
}
},
created(){
// 綁定事件(用全局變量bus綁定一個事件)
bus.$on("eclick",str=>{
this.bookmsg = str;
})
}
}
//3、A組件的代碼
import bus from "./bus.js"
export default {
………………
data(){
return {
msg:"要傳給B組件的數(shù)據(jù)"
}
},
methods:{
chuan(){
// 觸發(fā)事件(用全部變量bus觸發(fā)事件)
bus.$emit("eclick",this.msg);
}
}
}
94.mounted與created的區(qū)別
回答:
mounted和created都是vue對象生命周期的鉤子函數(shù),執(zhí)行時機不同。
1、created 是在 data配置項的數(shù)據(jù)掛載到vue對象本身后,會調用的鉤子函數(shù)。
此時,
1)、用 this. 的方式可以拿到data里的數(shù)據(jù)。
2)、但是數(shù)據(jù)還沒有渲染到模板上。所以,訪問dom時,內容還是原始的模板內容。
2、mounted是在組件的模板初次渲染完畢后會調用的鉤子函數(shù)。
此時,
data里的數(shù)據(jù)已經渲染到模板上了。所以,訪問dom時,已經和頁面上看到的效果一樣了。
95.v-model 原理 是什么
回答:
1、v-model指令的作用
vue中的v-model指令是完成雙向綁定的,用在表單元素上。雙向綁定就是 M會影響V。V也會影響M。即:能將頁面上輸入的值同步更新到相關綁定的data屬性,也會在更新data綁定屬性時候,更新頁面上輸入控件的值。
2、v-model的原理
v-model指令是一個語法糖,是屬性綁定和事件的語法糖。vue會根據(jù)不同的表單元素使用不同的屬性和事件。
如下:
- text 和 textarea 元素使用 value property 和 input 事件;
- checkbox 和 radio 使用 checked property 和 change 事件;
- select 字段將 value 作為 prop 并將 change 作為事件。
以文本框為例剖析原理,以下是代碼:
<!-- V -->
<div id="app">
<!--文本框使用value屬性和input事件-->
<input type="text" v-bind:value="msg" @input="changeMsg" >
</div>
//M:
let vm = new Vue({
el: "#app",
data: {
msg:"hi"
},
methods: {
changeMsg(e){
this.msg = e.target.value;
}
}
})
而,使用v-model來完成以上功能的代碼如下:
<!-- V-->
<div id="app">
<!-- 此處不需要使用value屬性和input事件,直接使用v-mdoel指令即可 -->
<input type="text" v-model="msg" >
</div>
// M:model
let vm = new Vue({
el: "#app",
data: {
msg:"hi"
}
})
96.vue 的組件中的 data 配置為什么必須是函數(shù)(每個組件都是 vue 的實例)
vue 為了保證每個實例上的 data 數(shù)據(jù)的獨立性,規(guī)定了必須使用函數(shù),而不是對象。因為使用對象的話,每個實例(組件)上使用的 data 數(shù)據(jù)是相互影響的,這當然就不是我們想要的了。對象是對于內存地址的引用,直接定義個對象的話組件之間都會使用這個對象,這樣會造成組件之間數(shù)據(jù)相互影響。而函數(shù)具有內部作用域,可以解決這個問題。
97.vue 中 computed 和 watch 的區(qū)別
- watch 和 computed 都是以 Vue 的依賴追蹤機制為基礎的,當某一個依賴型數(shù)據(jù)(依賴型數(shù)據(jù):簡單理解即放在 data 等對象下的實例數(shù)據(jù))發(fā)生變化的時候,所有依賴這個數(shù)據(jù)的相關數(shù)據(jù)會自動發(fā)生變化,即自動調用相關的函數(shù),來實現(xiàn)數(shù)據(jù)的變動。當依賴的值變化時,在 watch 中,是可以做一些復雜的操作的,而 computed 中的依賴,僅僅是一個值依賴于另一個值,是值上的依賴。
- 應用場景:computed:用于處理復雜的邏輯運算;一個數(shù)據(jù)受一個或多個數(shù)據(jù)影響;用來處理 watch 和 methods 無法處理的,或處理起來不方便的情況。例如處理模板中的復雜表達式、購物車里面的商品數(shù)量和總金額之間的變化關系等。 watch:用來處理當一個屬性發(fā)生變化時,需要執(zhí)行某些具體的業(yè)務邏輯操作,或要在數(shù)據(jù)變化時執(zhí)行異步或開銷較大的操作;一個數(shù)據(jù)改變影響多個數(shù)據(jù)。例如用來監(jiān)控路由、inpurt 輸入框值的特殊處理等。
- 區(qū)別:
- computed
- 初始化顯示或者相關的 data、props 等屬性數(shù)據(jù)發(fā)生變化的時候調用;
- 計算屬性不在 data 中,它是基于 data 或 props 中的數(shù)據(jù)通過計算得到的一個新值,這個新值根據(jù)已知值的變化而變化;
- 在 computed 屬性對象中定義計算屬性的方法,和取 data 對象里的數(shù)據(jù)屬性一樣,以屬性訪問的形式調用;
- 如果 computed 屬性值是函數(shù),那么默認會走 get 方法,必須要有一個返回值,函數(shù)的返回值就是屬性的屬性值;
- computed 屬性值默認會緩存計算結果,在重復的調用中,只要依賴數(shù)據(jù)不變,直接取緩存中的計算結果,只有依賴型數(shù)據(jù)發(fā)生改變,computed 才會重新計算;
- 在 computed 中的,屬性都有一個 get 和一個 set 方法,當數(shù)據(jù)變化時,調用 set 方法。
- watch
- 主要用來監(jiān)聽某些特定數(shù)據(jù)的變化,從而進行某些具體的業(yè)務邏輯操作,可以看作是 computed 和 methods 的結合體;
- 可以監(jiān)聽的數(shù)據(jù)來源:data,props,computed 內的數(shù)據(jù);
- watch 支持異步;
- 不支持緩存,監(jiān)聽的數(shù)據(jù)改變,直接會觸發(fā)相應的操作;
- 監(jiān)聽函數(shù)有兩個參數(shù),第一個參數(shù)是最新的值,第二個參數(shù)是輸入之前的值,順序一定是新值,舊值。
98.vue 的生命周期?網絡請求為什么要掛載在 mounted 中?
- vue2 的生命周期
- 初始化階段:
- beforeCreate
- created
- 掛載階段
- beforeMount
- mounted
- 更新階段
- beforeUpdate
- updated
- 卸載階段
- beforeDestroy
- destroyed
- 緩存組件相關
- activated
- deactivated
- 處理錯誤相關
- errorCaptured
- vue3 的生命周期在 vue2 的基礎上新增了:
- renderTracked:跟蹤虛擬 DOM 重新渲染時調用。鉤子接收 debugger event 作為參數(shù)。此事件告訴你哪個操作跟蹤了組件以及該操作的目標對象和鍵。
- renderTriggered:當虛擬 DOM 重新渲染被觸發(fā)時調用。和 renderTracked 類似,接收 debugger event 作為參數(shù)。此事件告訴你是什么操作觸發(fā)了重新渲染,以及該操作的目標對象和鍵。一共 13 個
- vue3 的組合 api 的生命周期移除了 beforeCreate 和 created,因為創(chuàng)建時的事件可以在 setup 里面直接調用。其他的 11 個生命周期前面全部加上 on比如:mounted -> onMounted, beforeDestroy -> onDeforeDestroy
- 網絡請求為什么要掛載在 mounted 中?在 Created 生命周期里 Data 才生成,而請求返回的數(shù)據(jù)需要掛載在 data 上,所以 Created 里是可以初始化請求的,但是 Created 的這時候 DOM 還沒有初始化;Mounted 生命周期里 DOM 才初始化渲染完成。然而,請求是異步的,所以不會堵塞頁面渲染的主線程。所以請求放在 created 和 mounted 里面都是可行的。如果我們的請求不需要獲取/借助/依賴/改變 DOM,這時請求可以放在 Created。反之則可以放在 Mounted 里。這樣做會更加的安全,也能保證頁面不會閃爍。
99.vue 的指令,在項目中封裝了那些常用指令?
在 vue 中我們可以使用 Vue.directive()方法注冊全局指令。也可以只用 directives 選項注冊局部指令。
- 輸入框防抖指令 v-debounce
const debounce = {
inserted: function (el, binding) {
let timer;
el.addEventListener("keyup", () => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
binding.value();
}, 1000);
});
},
};
export default debounce;
- 復制粘貼指令 v-copy
const copy = {
bind(el, { value }) {
el.$value = value;
el.handler = () => {
if (!el.$value) {
// 值為空的時候,給出提示。可根據(jù)項目UI仔細設計
console.log("無復制內容");
return;
}
// 動態(tài)創(chuàng)建 textarea 標簽
const textarea = document.createElement("textarea");
// 將該 textarea 設為 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);
// 選中值并復制
textarea.select();
const result = document.execCommand("Copy");
if (result) {
console.log("復制成功"); // 可根據(jù)項目UI仔細設計
}
document.body.removeChild(textarea);
};
// 綁定點擊事件,就是所謂的一鍵 copy 啦
el.addEventListener("click", el.handler);
},
// 當傳進來的值更新的時候觸發(fā)
componentUpdated(el, { value }) {
el.$value = value;
},
// 指令與元素解綁的時候,移除事件綁定
unbind(el) {
el.removeEventListener("click", el.handler);
},
};
export default copy;
- 長按指令 v-longpress
const longpress = {
bind: function (el, binding, vNode) {
if (typeof binding.value !== "function") {
throw "callback must be a function";
}
// 定義變量
let pressTimer = null;
// 創(chuàng)建計時器( 2秒后執(zhí)行函數(shù) )
let start = (e) => {
if (e.type === "click" && e.button !== 0) {
return;
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
handler();
}, 2000);
}
};
// 取消計時器
let cancel = (e) => {
if (pressTimer !== null) {
clearTimeout(pressTimer);
pressTimer = null;
}
};
// 運行函數(shù)
const handler = (e) => {
binding.value(e);
};
// 添加事件監(jiān)聽器
el.addEventListener("mousedown", start);
el.addEventListener("touchstart", start);
// 取消計時器
el.addEventListener("click", cancel);
el.addEventListener("mouseout", cancel);
el.addEventListener("touchend", cancel);
el.addEventListener("touchcancel", cancel);
},
// 當傳進來的值更新的時候觸發(fā)
componentUpdated(el, { value }) {
el.$value = value;
},
// 指令與元素解綁的時候,移除事件綁定
unbind(el) {
el.removeEventListener("click", el.handler);
},
};
export default longpress;
- 禁止表情及特殊字符 v-emoji
- let findEle = (parent, type) => {
return parent.tagName.toLowerCase() === type
? parent
: parent.querySelector(type);
};
const trigger = (el, type) => {
const e = document.createEvent("HTMLEvents");
e.initEvent(type, true, true);
el.dispatchEvent(e);
};
const emoji = {
bind: function (el, binding, vnode) {
// 正則規(guī)則可根據(jù)需求自定義
var regRule = /[^u4E00-u9FA5|d|a-zA-Z|rns,.?!,。?!…—&$=()-+/*{}[]]|s/g;
let $inp = findEle(el, "input");
el.$inp = $inp;
$inp.handle = function () {
let val = $inp.value;
$inp.value = val.replace(regRule, "");
trigger($inp, "input");
};
$inp.addEventListener("keyup", $inp.handle);
},
unbind: function (el) {
el.$inp.removeEventListener("keyup", el.$inp.handle);
},
};
export default emoji; - 圖片懶加載 v-LazyLoad
const LazyLoad = {
// install方法
install(Vue, options) {
const defaultSrc = options.default;
Vue.directive("lazy", {
bind(el, binding) {
LazyLoad.init(el, binding.value, defaultSrc);
},
inserted(el) {
if (IntersectionObserver) {
LazyLoad.observe(el);
} else {
LazyLoad.listenerScroll(el);
}
},
});
},
// 初始化
init(el, val, def) {
el.setAttribute("data-src", val);
el.setAttribute("src", def);
},
// 利用IntersectionObserver監(jiān)聽el
observe(el) {
var io = new IntersectionObserver((entries) => {
const realSrc = el.dataset.src;
if (entries[0].isIntersecting) {
if (realSrc) {
el.src = realSrc;
el.removeAttribute("data-src");
}
}
});
io.observe(el);
},
// 監(jiān)聽scroll事件
listenerScroll(el) {
const handler = LazyLoad.throttle(LazyLoad.load, 300);
LazyLoad.load(el);
window.addEventListener("scroll", () => {
handler(el);
});
},
// 加載真實圖片
load(el) {
const windowHeight = document.documentElement.clientHeight;
const elTop = el.getBoundingClientRect().top;
const elBtm = el.getBoundingClientRect().bottom;
const realSrc = el.dataset.src;
if (elTop - windowHeight < 0 && elBtm > 0) {
if (realSrc) {
el.src = realSrc;
el.removeAttribute("data-src");
}
}
},
// 節(jié)流
throttle(fn, delay) {
let timer;
let prevTime;
return function (...args) {
const currTime = Date.now();
const context = 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);
};
},
};
export default LazyLoad;
- 權限校驗指令 v-premission
- function checkArray(key) {
let arr = ["1", "2", "3", "4"];
let index = arr.indexOf(key);
if (index > -1) {
return true; // 有權限
} else {
return false; // 無權限
}
}
const permission = {
inserted: function (el, binding) {
let permission = binding.value; // 獲取到 v-permission的值
if (permission) {
let hasPermission = checkArray(permission);
if (!hasPermission) {
// 沒有權限 移除Dom元素
el.parentNode && el.parentNode.removeChild(el);
}
}
},
};
export default permission;
- 實現(xiàn)頁面水印 v-waterMarker
function addWaterMarker(str, parentNode, font, textColor) {
// 水印文字,父元素,字體,文字顏色
var can = document.createElement("canvas");
parentNode.appendChild(can);
can.width = 200;
can.height = 150;
can.style.display = "none";
var cans = can.getContext("2d");
cans.rotate((-20 * Math.PI) / 180);
cans.font = font || "16px Microsoft JhengHei";
cans.fillStyle = textColor || "rgba(180, 180, 180, 0.3)";
cans.textAlign = "left";
cans.textBaseline = "Middle";
cans.fillText(str, can.width / 10, can.height / 2);
parentNode.style.backgroundImage = "url(" + can.toDataURL("image/png") + ")";
}
const waterMarker = {
bind: function (el, binding) {
addWaterMarker(
binding.value.text,
el,
binding.value.font,
binding.value.textColor
);
},
};
export default waterMarker;
- 拖拽指令 v-draggable
const draggable = {
inserted: function (el) {
el.style.cursor = "move";
el.onmousedown = function (e) {
let disx = e.pageX - el.offsetLeft;
let disy = e.pageY - el.offsetTop;
document.onmousemove = function (e) {
let x = e.pageX - disx;
let y = e.pageY - disy;
let maxX =
document.body.clientWidth -
parseInt(window.getComputedStyle(el).width);
let maxY =
document.body.clientHeight -
parseInt(window.getComputedStyle(el).height);
if (x < 0) {
x = 0;
} else if (x > maxX) {
x = maxX;
}
if (y < 0) {
y = 0;
} else if (y > maxY) {
y = maxY;
}
el.style.left = x + "px";
el.style.top = y + "px";
};
document.onmouseup = function () {
document.onmousemove = document.onmouseup = null;
};
};
},
};
export default draggable;
100.vue 的移動端適配怎么做的,rem 怎么用的
vue 的移動端適配我們可以參考 vant-ui 組件庫給我們提供的方案。使用 amfe-flexible(用于自動定義跟字體大小)插件和 postcss-pxtorem(用于將 px 自動轉成 rem)插件在 main.ts 里面 import "amfe-flexible"在根目錄新建 .postcssrc.js 文件
module.exports = {
plugins: {
"postcss-pxtorem": {
rootValue: 37.5,
propList: ["*"],
},
},
};
rem 是相對于跟字體的倍數(shù),如果我們整個項目都是用 rem 作為單位,那么當我們做移動端的響應式的時候只需要去改變跟字體的大小就能做到適配。
101.后臺管理系統(tǒng)用戶驗證權限
- 登錄用戶填寫完賬號和密碼后向服務端驗證是否正確,登錄成功后,服務端會返回一個 token(該 token 的是一個能唯一標示用戶身份的一個 key),之后我們將 token 存儲在本地 localstorage 之中,這樣下次打開頁面或者刷新頁面的時候能記住用戶的登錄狀態(tài),不用再去登錄頁面重新登錄了。為了保證安全性,后臺所有 token 有效期(Expires/Max-Age)都是 Session,就是當瀏覽器關閉了就丟失了。重新打開瀏覽器都需要重新登錄驗證,后端也會在每周固定一個時間點重新刷新 token,讓后臺用戶全部重新登錄一次,確保后臺用戶不會因為電腦遺失或者其它原因被人隨意使用賬號。
- 攔截路由進行判斷頁面會先從 localstorage 中查看是否存有 token,沒有,就走一遍上一部分的流程重新登錄,如果有 token,就會把這個 token 返給后端去拉取 user_info,保證用戶信息是最新的。當然如果是做了單點登錄得的的話,用戶信息存儲在本地也是可以的。當你一臺電腦登錄時,另一臺會被提下線,所以總會重新登錄獲取最新的內容。
- 權限控制前端會有一份路由表,它表示了每一個路由可訪問的權限。當用戶登錄之后,通過 token 獲取用戶的 role ,動態(tài)根據(jù)用戶的 role 算出其對應有權限的路由,再通過 router.addRoutes 動態(tài)掛載路由。但這些控制都只是頁面級的,說白了前端再怎么做權限控制都不是絕對安全的,后端的權限驗證是逃不掉的。前端控制頁面級的權限,不同權限的用戶顯示不同的側邊欄和限制其所能進入的頁面(也做了少許按鈕級別的權限控制),后端則會驗證每一個涉及請求的操作,驗證其是否有該操作的權限,每一個后臺的請求不管是 get 還是 post 都會讓前端在請求 header 里面攜帶用戶的 token,后端會根據(jù)該 token 來驗證用戶是否有權限執(zhí)行該操作。若沒有權限則拋出一個對應的狀態(tài)碼,前端檢測到該狀態(tài)碼,做出相對應的操作。
- 利用 vuex 管理路由表,根據(jù) vuex 中可訪問的路由渲染側邊欄組件。
- 創(chuàng)建 vue 實例的時候將 vue-router 掛載,但這個時候 vue-router 掛載一些登錄或者不用權限的公用的頁面。
- 當用戶登錄后,獲取用 role,將 role 和路由表每個頁面的需要的權限作比較,生成最終用戶可訪問的路由表。
- 調用 router.addRoutes(store.getters.addRouters)添加用戶可訪問的路由。
- 使用 vuex 管理路由表,根據(jù) vuex 中可訪問的路由渲染側邊欄組件。
102.vuex 做數(shù)據(jù)請求刷新頁面,數(shù)據(jù)可能會發(fā)生丟失這個問題怎么解決
因為 store 里的數(shù)據(jù)是保存在運行內存中的,當頁面刷新時,頁面會重新加載 vue 實例,store 里面的數(shù)據(jù)就會被重新賦值初始化。所以我們可以在修改 store 的數(shù)據(jù)同時將數(shù)據(jù)再存一份到本地存儲(localStorage 或者 sessionStorage),本地存儲的內容是存在瀏覽器里面的,不會因為刷新而丟失。我們也可以用過比如 vuex-persistedstate 這樣的第三方包來幫助我們做 vuex 的持久化數(shù)據(jù)。
103.vue2 和 vue3 兩者的具體的區(qū)別有哪些,請一一例舉出來
- 源碼使用 ts 重寫現(xiàn)如今 typescript 異常火爆,它的崛起是有原因的,因為對于規(guī)模很大的項目,沒有類型聲明,后期維護和代碼的閱讀都是頭疼的事情,所以廣大碼農迫切的需要 vue 能完美支持 ts。vue2 用的是 Facebook 的 Flow 做類型檢查,但是因為某些情況下推斷有問題,所以 vue3 使用 typescript 進行了源碼的重寫。一個是為了更好的類型檢查,另一個是擁抱 ts。
- 使用 proxy 代替 defineProperty我們知道 vue2.x 雙向綁定的核心是 Object.defineProperty(),所以導致 vue 對數(shù)組對象的深層監(jiān)聽無法實現(xiàn)。所以 vue3 使用 proxy 對雙向綁定進行了重寫。Proxy 可以對整體進行監(jiān)聽,不需要關心里面有什么屬性,而且 Proxy 的配置項有 13 種,可以做更細致的事情,這是之前的 defineProperty 無法達到的。
- Diff 算法的提升vue3 在 vue2 的 diff 算法的基礎上增加了靜態(tài)標記,元素提升和事件緩存等優(yōu)化。使得速度更快。
- 打包體積變化vue2 官方說的運行時打包師 23k,但這只是沒安裝依賴的時候,隨著依賴包和框架特性的增多,有時候不必要的,未使用的代碼文件都被打包了進去,所以后期項目大了,打包文件會特別多還很大。在 Vue 3 中,我們通過將大多數(shù)全局 API 和內部幫助程序移動到 Javascript 的 module.exports 屬性上實現(xiàn)這一點。這允許現(xiàn)代模式下的 module bundler 能夠靜態(tài)地分析模塊依賴關系,并刪除與未使用的 module.exports 屬性相關的代碼。模板編譯器還生成了對樹抖動友好的代碼,只有在模板中實際使用某個特性時,該代碼才導入該特性的幫助程序。盡管增加了許多新特性,但 Vue 3 被壓縮后的基線大小約為 10 KB,不到 Vue 2 的一半。
- 其他 Api 和功能的改動
- Global API
- 模板指令
- 組件
- 渲染函數(shù)
- vue-cli 從 v4.5.0 開始提供 Vue 3 預設
- Vue Router 4.0 提供了 Vue 3 支持,并有許多突破性的變化
- Vuex 4.0 提供了 Vue 3 支持,其 API 與 2.x 基本相同
- 組件基本結構
在創(chuàng)建組件的時候我們不必在寫唯一的根元素。移除了 vue2 中的 filters。
- 生命周期的區(qū)別
新增了 renderTracked 和 renderTriggered 兩個生命周期。
- 增加了組合 api
我們可以使用 setup 函數(shù)來使用類似 react 的自定義 hooks 的功能,主要解決邏輯關注點分離的問題。
104.vue 操作虛擬 DOM 有什么優(yōu)異的地方?他不是還多做了一層虛擬 DOM,為什么比原生操作 DOM 還快
我們有必要先了解下模板轉換成視圖的過程整個過程:
- Vue.js 通過編譯將 template 模板轉換成渲染函數(shù)(render ) ,執(zhí)行渲染函數(shù)就可以得到一個虛擬節(jié)點樹。
- 在對 Model 進行操作的時候,會觸發(fā)對應 Dep 中的 Watcher 對象。Watcher 對象會調用對應的 update 來修改視圖。這個過程主要是將新舊虛擬節(jié)點進行差異對比,然后根據(jù)對比結果進行 DOM 操作來更新視圖。
簡單點講,在 Vue 的底層實現(xiàn)上,Vue 將模板編譯成虛擬 DOM 渲染函數(shù)。結合 Vue 自帶的響應系統(tǒng),在狀態(tài)改變時,Vue 能夠智能地計算出重新渲染組件的最小代價并應到 DOM 操作上。
?那么 vue 操作虛擬 DOM 有什么優(yōu)異的地方呢?
- 具備跨平臺的優(yōu)勢由于 Virtual DOM 是以 JavaScript 對象為基礎而不依賴真實平臺環(huán)境,所以使它具有了跨平臺的能力,比如說瀏覽器平臺、Weex、Node 等。
- 操作 DOM 慢,js 運行效率高。我們可以將 DOM 對比操作放在 JS 層,提高效率。因為 DOM 操作的執(zhí)行速度遠不如 Javascript 的運算速度快,因此,把大量的 DOM 操作搬運到 Javascript 中,運用 patching 算法來計算出真正需要更新的節(jié)點,最大限度地減少 DOM 操作,從而顯著提高性能。Virtual DOM 本質上就是在 JS 和 DOM 之間做了一個緩存。可以類比 CPU 和硬盤,既然硬盤這么慢,我們就在它們之間加個緩存:既然 DOM 這么慢,我們就在它們 JS 和 DOM 之間加個緩存。CPU(JS)只操作內存(Virtual DOM),最后的時候再把變更寫入硬盤(DOM)
- 提升渲染性能Virtual DOM 的優(yōu)勢不在于單次的操作,而是在大量、頻繁的數(shù)據(jù)更新下,能夠對視圖進行合理、高效的更新。為了實現(xiàn)高效的 DOM 操作,一套高效的虛擬 DOM diff 算法顯得很有必要。我們通過 patch 的核心—-diff 算法,找出本次 DOM 需要更新的節(jié)點來更新,其他的不更新。比如修改某個 model 100 次,從 1 加到 100,那么有了 Virtual DOM 的緩存之后,只會把最后一次修改 patch 到 view 上。那 diff 算法的實現(xiàn)過程是怎樣的?
那么為什么比原生操作 DOM 還快呢?首先我們每次操作 dom 的時候,都會去執(zhí)行瀏覽器的那 5 個步驟,尤其是當大量循環(huán)的時候,每次循環(huán)完都不知道后面還要不要修改,所以每次都要去重復這個過程,引發(fā)不必要的渲染。但是在實際開發(fā)過程中,我們會發(fā)現(xiàn)虛擬 dom 并沒有比真實 dom 更快。這個問題尤雨溪在知乎上面有過回答:這是一個性能 vs. 可維護性的取舍??蚣艿囊饬x在于為你掩蓋底層的 DOM 操作,讓你用更聲明式的方式來描述你的目的,從而讓你的代碼更容易維護。沒有任何框架可以比純手動的優(yōu)化 DOM 操作更快,因為框架的 DOM 操作層需要應對任何上層 API 可能產生的操作,它的實現(xiàn)必須是普適的。針對任何一個 benchmark,我都可以寫出比任何框架更快的手動優(yōu)化,但是那有什么意義呢?在構建一個實際應用的時候,你難道為每一個地方都去做手動優(yōu)化嗎?出于可維護性的考慮,這顯然不可能。框架給你的保證是,你在不需要手動優(yōu)化的情況下,我依然可以給你提供過得去的性能。
105.token 過期你是如何來進行處理,有沒有弄過 token 續(xù)期
在開發(fā)中,我們經常會遇到使用 token,token 的作用是要驗證用戶是否處于登錄狀態(tài),所以要請求一些只有登錄狀態(tài)才能查看的資源的時候,我們需要攜帶 token。
一般的后端接口設置的 token 是有時效的,超時后就會失效,失效之后的處理策略一般會做兩種處理:
- 一種是直接跳轉到登錄頁面,重新登錄。
- 另外一種如果返回 token 失效的信息,自動去刷新 token,然后繼續(xù)完成未完成的請求操作。
106.vue底層實現(xiàn)原理
- 使用 Object.defineProperty 劫持 data上的數(shù)據(jù)。
- Vue2.0通過設定對象屬性的 setter/getter 方法來監(jiān)聽數(shù)據(jù)的變化,通過getter進行依賴收集,而每個setter方法就是一個觀察者,在數(shù)據(jù)變更的時候通知訂閱者更新視圖。
107.Vue的生命周期,created與mounted的區(qū)別
1、created
表示組件實例已經完全創(chuàng)建,data數(shù)據(jù)已經被 Object.defineProperty 劫持完成,屬性也綁定成功,但真實dom還沒有生成,$el還不可用。
2、mounted
el選項所對應的視圖節(jié)點已經被新創(chuàng)建的 vm.$el 替換,并掛載到實例上去了。此時響應式數(shù)據(jù)都已經完成了渲染。
108.用vue寫了商城,從列表頁點進詳情頁,從詳情頁退出來的時候,怎么保持進入詳情頁之前的頁面卷動值。
使用 <keep-alive>對列表頁面進行包裹,被包裹的列表頁面就有了activated、deactivated這兩個生命周期。在離開列表頁面時,在deactivated中記錄頁面的滾動條位置。再次進入列表頁面時,在activated中使用 this.$el.scrollTop 把頁面滾動到離開時所記錄的位置。
109.說說你對vue的理解
- vue是數(shù)據(jù)驅動的MVVM框架,相比傳統(tǒng)的DOM庫,vue有一層虛擬DOM。每當數(shù)據(jù)發(fā)生更新時,會觸發(fā)虛擬DOM進行diff運算,找出最小的變化節(jié)點,大大地節(jié)省了DOM操作性能。
- vue是組件化的,在單頁面應用程序中,每一個組件相當于是一塊積木,搭建起龐大的應用系統(tǒng)。組件,可以理解成更大粒度的“HTML元素”,有利于快速開發(fā)、組件的高效復用。
- vue有一整套指令,大大地降低了開發(fā)者的開發(fā)難度,提升了開發(fā)效率。
- 雖然vue有諸多優(yōu)點,但仍然有一些缺陷,比如復雜的業(yè)務頁面通常過長,data、methods、computed、watch對應的數(shù)據(jù)和邏輯交織在一起,難以維護。
110.說說對虛擬DOM的理解
- 在vue中,虛擬DOM本質上就是一個固定格式的JSON數(shù)據(jù),它用于描述真實的DOM結構,并使用各種不同flag標記出動態(tài)的DOM節(jié)點。
- 虛擬DOM數(shù)據(jù)保存在應用程序對應的內存結構中,擁有更快的數(shù)據(jù)交換速度。
- 每當有數(shù)據(jù)發(fā)生變化時,就會生成新的虛擬DOM,進一步發(fā)生diff運算,找出最小臟節(jié)點,減少不必要的DOM開銷,這也是vue擁有更好的性能的根本原因。
111.說說provide的用法
- 在父級組件中,使用provide選項向vue組件樹中“提供數(shù)據(jù)”,其語法是:provide:{a: 1}
- 在后代子級組件中,使用 inject選項從vue組件中“取出數(shù)據(jù)”,其語法是:inject: ['a']
112.說一下element ui遇到過的坑
- 表單設置觸發(fā)事件為blur,但是ctrl+A全選以后再刪除時又觸發(fā)了change事件,并提示一個原始報錯
- 解決方案:trigger設置成trigger: ['blur', 'change']
- 使用el-dialog 遮罩層把顯示內容遮住了
- 原因:Dialog 的外層布局的 position 值為 fixed, absolute, relative 三者之一時,就會出現(xiàn)被蒙板遮住的情況。
- 解決方法:v-bind:modal-append-to-body="false"
- 使用el-select 不能繼承父元素的寬度
- 原因:el-select 本身是 inline-block
- 解決辦法:手動設置el-select的寬度
113.怎么修改element ui動態(tài)組件的樣式
要修改elementUI組件的樣式,可以采用以下兩種方式
1.全局樣式
通過選擇權重覆蓋elementUI組件的樣式,如修改復選框為圓角:
<style>
.edit-item .el-checkbox__inner {
border-radius: 50%;
}
</style>
但這種方式為全局樣式,會影響頁面中所有復選框,如果不希望影響其它頁面的樣式,可以采用第二中方式
2.局部樣式
<style scoped>
.edit-item .el-checkbox__inner {
border-radius: 50%;
}
</style>
但如果僅僅是設置了scoped屬性,樣式無法生效,原因是以上樣式會被編譯成屬性選擇器,而elementUI組件內部的結構卻無法添加該html屬性,以上樣式被編譯成如下代碼:
.edit-item[data-v-6558bc58] .el-checkbox__inner[data-v-6558bc58] {
border-radius: 50%;
}
解決方案也很簡單,只需在選擇器中要添加>>>即可
<style scoped>
.edit-item >>> .el-checkbox__inner {
border-radius: 50%;
}
</style>
如果是sass或less編寫的樣式,還可以使用/deep/
<style scoped lang="scss">
.edit-item /deep/ .el-checkbox__inner {
border-radius: 50%;
}
</style>
以上寫法樣式都會編譯成以下樣式:
.edit-item[data-v-6558bc58] .el-checkbox__inner{}
- 所以elementUI中的樣式就能成功覆蓋
114.vue和react中的key值主要用來干什么
key是虛擬DOM對象的標識,在更新顯示時key起著極其重要的作用,vue和react都是采用diff算法來對比新舊虛擬節(jié)點,而key的作用是為了在執(zhí)行 diff算法 的時候,更快更準確地找到對應的虛擬節(jié)點,從而提高diff速度。
115.route和router區(qū)別
route 和 router 是vue-router中經常會操作的兩個對象, route表示當前的路由信息對象,包含了當前 URL 解析得到的信息,包含當前的路徑、參數(shù)、query對象等,一般用于獲取跳轉時傳入的參數(shù)。router對象是全局路由的實例,是router構造方法的實例,一般用戶路由跳轉,如router.push()、router.replace() 等方法
116.vue和react相對于傳統(tǒng)的有什么好處,性能優(yōu)點
- 組件化開發(fā),開發(fā)效率更高
- React與Vue都鼓勵使用組件化開發(fā)。這本質上是建議你將你的應用分拆成一個個功能明確的模塊,每個模塊之間可以通過特定的方式進行關聯(lián)。這樣可以更好的管理功能模塊與復用,在團隊項目中也能更好的分工協(xié)作
- VirtualDOM,性能更高
- 對真實DOM的操作很慢,所以Vue和React都實現(xiàn)了虛擬DOM,用戶對數(shù)據(jù)的修改首先反映到虛擬DOM上,而不是直接操作真實DOM,然后在虛擬DOM環(huán)節(jié)進行優(yōu)化,比如只修改差異項,對虛擬 DOM 進行頻繁修改時進行合并,然后一次性修改真實 DOM 中需要改的部分,最后在真實 DOM 中進行排版與重繪,減少過多DOM節(jié)點排版與重繪損耗等
- 數(shù)據(jù)和結構的分離
- 雙向數(shù)據(jù)綁定
- Vue可以通過v-model指令實現(xiàn),react可通過單向綁定+事件來實現(xiàn)
- 強大的周邊生態(tài)
- Vue和React都有著強大的生態(tài),比如路由、狀態(tài)管理、腳手架等,用戶可以根據(jù)需求自行選擇,不需要重復造輪子
117.虛擬DOM實現(xiàn)原理
我們先來看看瀏覽器渲染一個頁面的過和,大概需要以下5個步驟
- 解析HTML元素,構建DOM樹
- 解析CSS,生成頁面CSS規(guī)則樹(Style Rules)
- 將DOM樹和CSS規(guī)則樹進行關聯(lián),生成render樹
- 布局(layout/reflow):瀏覽器設定Render樹中的每個節(jié)點在屏幕上的位置與尺寸
- 繪制Render樹:繪制頁面像素信息到屏幕上
眾所周知,一個頁面在瀏覽器中最大的開銷就是DOM節(jié)點操作,頁面的性能問題大多數(shù)是DOM操作引起的,當我們用原生js 或jquery這樣的庫去操作DOM時,瀏覽器會從構建DOM樹開始執(zhí)行完以上整個流程,所以頻繁操作DOM會引起不需要的計算、重排與重繪,從而導致頁面卡頓,影響用戶體驗
所以減少DOM的操作能達到性能優(yōu)化的目的,事實上,虛擬DOM就是這么做的,虛擬DOM(VirtualDOM) 的實現(xiàn)原理主要包括以下 3 部分:
- 用 JavaScript 對象模擬真實 DOM 樹,對真實 DOM 進行抽象
- diff 算法 — 比較新舊兩個虛擬DOM,得到差異對象
- pach 算法 — 將差異對象應用到真實 DOM 樹
Virtual DOM 本質上就是一個javascript對象,數(shù)據(jù)的修改會生成一個新的虛擬DOM(一個新的javascript對象),然后與舊的虛擬DOM進行對比,得到兩個對象的差異項,最后只更新差異對象中的內容到真實DOM,這樣能做到最少限度地修改真實DOM,從而實現(xiàn)性能優(yōu)化
118.如何實現(xiàn)角色權限分配
在開發(fā)中后臺應用過程中或多或少都會涉及到一個問題:權限,簡單地說就是讓不同的用戶在系統(tǒng)中擁有不同的操作能力。
但在實際應用中我們一般不直接將權限賦予在用戶身上,因為這樣操作對有大量用戶的系統(tǒng)來說過于繁瑣,所以我們一般基于RBAC(Role-Based Access Control)權限模型,引入角色的概念,通過角色的媒介過渡,先將權限賦予在角色上,再關聯(lián)相應的用戶,對應的用戶就繼承了角色的權限
用戶與角色,角色與權限都是多對多的關系
引入角色媒介的優(yōu)點:
- 實現(xiàn)了用戶與權限的解耦
- 提高了權限配置的效率
- 降低了后期維護的成本
119.雙向數(shù)據(jù)綁定和單向數(shù)據(jù)流的優(yōu)缺點
所謂數(shù)據(jù)綁定,就是指View層和Model層之間的映射關系。
- 單向數(shù)據(jù)綁定:Model的更新會觸發(fā)View的更新,而View的更新不會觸發(fā)Model的更新,它們的作用是單向的。
- 優(yōu)點:所有狀態(tài)變化都可以被記錄、跟蹤,狀態(tài)變化通過手動調用觸發(fā),源頭易追溯。
- 缺點:會有很多類似的樣板代碼,代碼量會相應的上升。
- 雙向數(shù)據(jù)綁定:Model的更新會觸發(fā)View的更新,View的更新也會觸發(fā)Model的更新,它們的作用是相互的。
- 優(yōu)點:在操作表單時使用v-model方便簡單,可以省略繁瑣或重復的onChange事件去處理每個表單數(shù)據(jù)的變化(減少代碼量)。
- 缺點:屬于暗箱操作,無法很好的追蹤雙向綁定的數(shù)據(jù)的變化。
120.Vue是如何實現(xiàn)雙向綁定的?
Vue的雙向數(shù)據(jù)綁定是通過數(shù)據(jù)劫持結合發(fā)布者訂閱者模式來實現(xiàn)的 要實現(xiàn)這種雙向數(shù)據(jù)綁定,必要的條件有:
- 實現(xiàn)一個數(shù)據(jù)監(jiān)聽器Observer,能夠對數(shù)據(jù)對象的所有屬性進行監(jiān)聽,如有變動可拿到最新值并通知訂閱者
- 實現(xiàn)一個指令解析器Compile,對每個元素節(jié)點的指令進行掃描和解析,根據(jù)指令模板替換數(shù)據(jù),以及綁定相應的更新函數(shù)
- 實現(xiàn)一個Watcher,作為連接Observer和Compile的橋梁,能夠訂閱并收到每個屬性變動的通知,執(zhí)行指令綁定的相應回調函數(shù),從而更新視圖
- MVVM入口函數(shù),整合以上三者
補充回答
在new Vue的時候,在 Observer 中通過 Object.defineProperty() 達到數(shù)據(jù)劫持,代理所有數(shù)據(jù)的 getter 和 setter 屬性,在每次觸發(fā) setter 的時候,都會通過 Dep 來通知 Watcher,Watcher 作為Observer數(shù)據(jù)監(jiān)聽器與Compile模板解析器之間的橋梁,當 Observer 監(jiān)聽到數(shù)據(jù)發(fā)生改變的時候,通過 Updater 來通知 Compile 更新視圖,而 Compile 通過 Watcher 訂閱對應數(shù)據(jù),綁定更新函數(shù),通過 Dep 來添加訂閱者,達到雙向綁定。
121.Proxy與Object.defineProperty的優(yōu)劣對比?
Proxy的優(yōu)勢如下
- Proxy可以直接監(jiān)聽整個對象而非屬性。
- Proxy可以直接監(jiān)聽數(shù)組的變化。
- Proxy有13中攔截方法,如ownKeys、deleteProperty、has 等是 Object.defineProperty 不具備的。
- Proxy返回的是一個新對象,我們可以只操作新的對象達到目的,而Object.defineProperty只能遍歷對象屬性直接修改;
- Proxy做為新標準將受到瀏覽器產商重點持續(xù)的性能優(yōu)化,也就是傳說中的新標準的性能紅利。
Object.defineProperty 的優(yōu)勢如下
- 兼容性好,支持 IE9,而 Proxy 的存在瀏覽器兼容性問題,而且無法用 polyfill 磨平。
Object.defineProperty 不足在于:
- Object.defineProperty 只能劫持對象的屬性,因此我們需要對每個對象的每個屬性進行遍歷。
- Object.defineProperty不能監(jiān)聽數(shù)組。是通過重寫數(shù)據(jù)的那7個可以改變數(shù)據(jù)的方法來對數(shù)組進行監(jiān)聽的。
- Object.defineProperty 也不能對 es6 新產生的 Map,Set 這些數(shù)據(jù)結構做出監(jiān)聽。
- Object.defineProperty也不能監(jiān)聽新增和刪除操作,通過 Vue.set()和 Vue.delete來實現(xiàn)響應式的。
122.你是如何理解Vue的響應式系統(tǒng)的?
就是能夠自動追蹤數(shù)據(jù)的變化,而不必手動觸發(fā)視圖更新。Vue2.X通過Object.defineProperty()做數(shù)據(jù)劫持,而Vue3通過Proxy做數(shù)據(jù)代理,從而捕捉到對數(shù)據(jù)的get和set。
- 什么叫數(shù)據(jù)響應式?
簡單說就是用戶更改數(shù)據(jù)(Data)時,視圖可以自動刷新,頁面UI能夠響應數(shù)據(jù)變化。
- 為什么要了解Vue數(shù)據(jù)響應式?
因為這是Vue的立身之本呀,連尤雨溪都在給放文檔上這樣說,“Vue 最獨特的特性之一,是其非侵入性的響應式系統(tǒng)?!?/p>
Vue就是通過getter 和setter來對數(shù)據(jù)進行操作,通過get()和set()函數(shù)可以生成一個虛擬屬性,來直接調用函數(shù)操作數(shù)據(jù)。這一步是被封裝起來的,我們是看不到的。
此外,還需要一個重要的媒介API,那就是Object.defineProperty,因為對象被定義完成后,想要更改或添加屬性(像是get和set這樣的屬性),只能通過這個Object.defineProperty來添加。接著需要實現(xiàn)對數(shù)據(jù)屬性的讀寫進行監(jiān)控。能夠監(jiān)控就意味著能讓vm(一般生成的實例)能夠知道數(shù)據(jù)有變化,從而觸發(fā)render(data)函數(shù),頁面UI就會做出響應。
123.既然Vue通過數(shù)據(jù)劫持可以精準探測數(shù)據(jù)變化,為什么還需要虛擬DOM進行diff檢測差異?
1.減少對真實DOM的操作
Diff 算法探討的就是虛擬 DOM 樹發(fā)生變化后,生成 DOM 樹更新補丁的方式。對比新舊兩株虛擬 DOM 樹的變更差異,將更新補丁作用于真實 DOM,以最小成本完成視圖更新。
具體流程:
- 真實 DOM 與虛擬 DOM 之間存在一個映射關系。這個映射關系依靠初始化時的 JSX 建立完成;
- 當虛擬 DOM 發(fā)生變化后,就會根據(jù)差距計算生成 patch,這個 patch 是一個結構化的數(shù)據(jù),內容包含了增加、更新、移除等;
- 最后再根據(jù) patch 去更新真實的 DOM,反饋到用戶的界面上。
這樣一個生成補丁、更新差異的過程統(tǒng)稱為 diff 算法。
這里涉及3個要點:
- 更新時機:更新發(fā)生在setState、Hooks 調用等操作以后
- 遍歷算法:采用深度優(yōu)先遍歷,從根節(jié)點出發(fā),沿著左子樹方向進行縱向遍歷,直到找到葉子節(jié)點為止。然后回溯到前一個節(jié)點,進行右子樹節(jié)點的遍歷,直到遍歷完所有可達節(jié)點
- 優(yōu)化策略:樹、組件及元素三個層面進行復雜度的優(yōu)化
-
- 元素比對主要發(fā)生在同層級中,通過標記節(jié)點操作生成補丁。節(jié)點操作包含了插入、移動、刪除等。其中節(jié)點重新排序同時涉及插入、移動、刪除三個操作,所以效率消耗最大,此時策略三起到了至關重要的作用。通過標記 key 的方式,React 可以直接移動 DOM 節(jié)點,降低內耗
- 在組件比對的過程中:如果組件是同一類型則進行樹比對;如果不是則直接放入補丁中。只要父組件類型不同,就會被重新渲染。這也就是為什么shouldComponentUpdate、PureComponent 及 React.memo 可以提高性能的原因
- 這一策略需要進行樹比對,即對樹進行分層比較。樹比對的處理手法是非常“暴力”的,即兩棵樹只對同一層次的節(jié)點進行比較,如果發(fā)現(xiàn)節(jié)點已經不存在了,則該節(jié)點及其子節(jié)點會被完全刪除掉,不會用于進一步的比較,這就提升了比對效率
- 忽略節(jié)點跨層級操作場景,提升比對效率。
- 如果組件的 class 一致,則默認為相似的樹結構,否則默認為不同的樹結構
- 同一層級的子節(jié)點,可以通過標記 key 的方式進行列表對比
通過diff算法能準確的獲取到具體的節(jié)點 進行針對操作?。?!
124.Vue為什么沒有類似于React中shouldComponentUpdate的生命周期?
因為 Vue 的響應式系統(tǒng)已經在初次渲染時收集了渲染依賴的數(shù)據(jù)項,通過自動的方式就能夠得到相當不錯的性能。不過,在一些場景下,手動控制刷新檢查還是能夠進一步提升渲染性能的
vue 的機制,其實就是個依賴關系管理機制。不管是計算屬性,watcher,以及 renderer,站在外部看,模型都是一樣的,即初始化的時候通過一些方法分析出來這個模塊依賴誰,等被依賴的數(shù)據(jù)更新了,這些模塊就重新運算一遍產出物(實際上不盡然,比如計算屬性有惰性機制)。
具體到 renderer,哪些數(shù)據(jù)的變更會引起 dom 變更,在模板編譯的時候已經確定了,并且寫死在生成的代碼里了。
而 react 是沒有這種自動機制的,它去執(zhí)行 render 唯一的原因就是你主動讓他 render。那你什么時候讓它 render 呢?工程上一般是使用一個數(shù)據(jù)流工具,數(shù)據(jù)有變化的時候發(fā)出一個事件,一股腦把數(shù)據(jù)推過來,不區(qū)分哪個字段有變更(你區(qū)分了也沒用,renderer 根本不認)。如果這個數(shù)據(jù)流模型是多個組件共用的,那必然是在 render 之前有個 hook 給我們個機會告訴組件“這次沒你的事兒”,有利于性能優(yōu)化。
那么,我們有沒有可能不增加代碼靜態(tài)分析環(huán)節(jié),搞清楚 react renderer 到底依賴哪些數(shù)據(jù),繼而把這個判斷自動做掉呢?依我看不太可能,因為我們不能保證運行期跑一遍 render,它就會一次性訪問它所有可能訪問的數(shù)據(jù)。
125.Vue中的key到底有什么用?
1、key的作用主要是為了搞笑的更新虛擬dom,其原理是vue在patch過程中通過key可以精準判斷兩個節(jié)點是否是同一個,從而避免頻繁更新不同元素,使得整個patch過程更加高效,減少dom操作量,提高性能。2、另外,若不設置key還可能在列表更新時候引發(fā)一些隱藏的bug。3、vue中在使用相同標簽名元素的過渡切換時,也會使用到key屬性,其目的也是為了讓vue可以區(qū)分它們,否則vue只會替換其內部屬性而不會觸發(fā)過渡效果。
126.不能用index做key
看了上面的介紹其實也很容易明白為什么不能用index作為key。
1、影響性能:當用index作為key的時候,刪除節(jié)點后面的所有節(jié)點都會導致重新渲染,因為index變化了,可以也就變化了
有人說,當你的列表數(shù)據(jù)沒有變化的時候可以用index作為key。也就是說列表不會觸發(fā)更新元素,只有靜態(tài)展示。這種說法你怎么看呢?之所以說到這個問題,是在vue官方群里面一群人應為這個問題討論半天。我弱弱回復一句,任何情況下都不要用index作為key。結果遭到炮轟,哎!(除非前端寫死的list,且無操作不會引起key變化,只要是后端數(shù)據(jù),前端怎么能保證數(shù)據(jù)不變呢)。關于這個問題,我有這樣三點想法:
1、代碼的規(guī)范性2、類比typescript,為什么變量要加類型,除了規(guī)范,也方便定位錯誤3、列表的順序穩(wěn)定其實是難以保證的
127.vue項目中用jsx語法
JSX就是Javascript和XML結合的一種格式。React發(fā)明了JSX,利用HTML語法來創(chuàng)建虛擬DOM。當遇到<,JSX就當HTML解析,遇到{就當JavaScript解析.
我為什么要在vue中用JSX?
是使用的的模板語法,Vue的模板實際上就是編譯成了 render 函數(shù),同樣支持 JSX 語法。在 Vue 官網中,提供 createElement 函數(shù)中使用模板中的功能。文章來源:http://www.zghlxwxcb.cn/news/detail-694527.html
<script>
export default {
name: "item",
props:{
id:{
type:Number,
default:1
}
},
render(){
const hText=`
<h${this.id}>${this.$slots.default[0].text}</h${this.id}>
`
return <div domPropsInnerHTML={hText}></div>
}
}
</script>
但是極少數(shù)的 VUE項目用JSX語法 JSX語法 根植在react上面 在vue上還是 template舒服!?文章來源地址http://www.zghlxwxcb.cn/news/detail-694527.html
到了這里,關于【直接收藏】前端 VUE 高階面試題(三)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!