網(wǎng)站建設(shè)策劃書競價網(wǎng)絡(luò)推廣
一、MVVM原理
在Vue2官方文檔中沒有找到Vue是MVVM的直接證據(jù),但文檔有提到:雖然沒有完全遵循MVVM模型,但是 Vue 的設(shè)計也受到了它的啟發(fā),因此在文檔中經(jīng)常會使用vm(ViewModel 的縮寫) 這個變量名表示 Vue 實例。
為了感受MVVM模型的啟發(fā),我簡單列舉下其概念。
MVVM是Model-View-ViewModel的簡寫,由三部分構(gòu)成:
Model: 模型持有所有的數(shù)據(jù)、狀態(tài)和程序邏輯
View: 負(fù)責(zé)界面的布局和顯示
ViewModel:負(fù)責(zé)模型和界面之間的交互,是Model和View的橋梁
大廠面試題分享 面試題庫
前端后端面試題庫 (面試必備) 推薦:★★★★★
地址:前端面試題庫
二、SPA單頁面應(yīng)用
單頁Web應(yīng)用(single page web application,SPA),就是只有一張Web頁面的應(yīng)用,是加載單個HTML頁面并在用戶與應(yīng)用程序交互時動態(tài)更新該頁面的Web應(yīng)用程序。我們開發(fā)的Vue項目大多是借助個官方的CLI腳手架,快速搭建項目,直接通過new Vue構(gòu)建一個實例,并將el:'#app'掛載參數(shù)傳入,最后通過npm run build的方式打包后生成一個index.html,稱這種只有一個HTML的頁面為單頁面應(yīng)用。
當(dāng)然,vue也可以像jq一樣引入,作為多頁面應(yīng)用的基礎(chǔ)框架。
三、Vue的特點(diǎn)
清晰的官方文檔和好用的api,比較容易上手。
是一套用于構(gòu)建用戶界面的漸進(jìn)式框架,將注意力集中保持在核心庫,而將其他功能如路由和全局狀態(tài)管理交給相關(guān)的庫。
使用 Virtual DOM。
提供了響應(yīng)式 (Reactive) 和組件化 (Composable) 的視圖組件。
四、Vue的構(gòu)建入口
vue使用過程中可以采用以下兩種方式:
在vue腳手架中直接使用,參考文檔:https://cn.vuejs.org/v2/guide/installation.html
或者在html文件的頭部通過靜態(tài)文件的方式引入: <script src="" target="_blank">https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
那么問題來了,使用的或者引入的到底是什么?
答:引入的是已經(jīng)打包好的vue.js文件,通過rollup構(gòu)建打包所得。
構(gòu)建入口在哪里?
答:在vue源碼的package.json文件中:
"scripts":{// ..."build":"node scripts/build.js","build:ssr":"npm run build -- web-runtime-cjs,web-server-renderer","build:weex":"npm run build -- weex",// ...},復(fù)制代碼
通過執(zhí)行npm run build的時候,會進(jìn)行scripts/build.js文件的執(zhí)行,npm run build:ssr和npm run build:weex的時候,將ssr和weex作為參數(shù)傳入,按照參數(shù)構(gòu)建出不一樣的vue.js打包文件。
所以說,vue中的package.json文件就是構(gòu)建的入口,具體構(gòu)建流程可以參考vue2入口:構(gòu)建入口。
五、對import Vue from "vue"的理解
在使用腳手架開發(fā)項目時,會有一行代碼import Vue from "vue",那么這個Vue指的是什么。
答:一個構(gòu)造函數(shù)。
function Vue (options) {if (process.env.NODE_ENV !== 'production' &&!(this instanceof Vue)) {warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
復(fù)制代碼
我們開發(fā)中引入的Vue其實就是這個構(gòu)造函數(shù),而且這個構(gòu)造函數(shù)只能通過new Vue的方式進(jìn)行使用,否則會在控制臺打印警告信息。定義完后,還會通過initMixin(Vue)、stateMixin(Vue)、eventsMixin(Vue)、lifecycleMixin(Vue)和renderMixin(Vue)的方式為Vue原型中混入方法。我們通過import Vue from "Vue"引入的本質(zhì)上就是一個原型上掛在了好多方法的構(gòu)造函數(shù)。
六、對new Vue的理解
// main.js文件importVuefrom"vue";
var app = newVue({el: '#app',data() {return {msg: 'hello Vue~'}},template: `<div>{{msg}}</div>`,
})console.log(app);
復(fù)制代碼
new Vue就是對構(gòu)造函數(shù)Vue進(jìn)行實例化,執(zhí)行結(jié)果如下:

可以看出實例化后的實例中包含了很多屬性,用來對當(dāng)前app進(jìn)行描述,當(dāng)然復(fù)雜的Vue項目這個app將會是一個樹結(jié)構(gòu),通過$parent和$children維護(hù)父子關(guān)系。
new Vue的過程中還會執(zhí)行this._init方法進(jìn)行初始化處理。
七、編譯
虛擬DOM的生成必須通過render函數(shù)實現(xiàn),render函數(shù)的產(chǎn)生是在編譯階段完成,核心代碼如下:
export const createCompiler = createCompilerCreator(function baseCompile (template: string,options: CompilerOptions
): CompiledResult {const ast = parse(template.trim(), options)if (options.optimize !== false) {optimize(ast, options)}const code = generate(ast, options)return {ast,render: code.render,staticRenderFns: code.staticRenderFns}
})
復(fù)制代碼
主要完成的功能是:
通過const ast = parse(template.trim(), options)將template轉(zhuǎn)換成ast樹
通過optimize(ast, options)對ast進(jìn)行優(yōu)化
通過const code = generate(ast, options)將優(yōu)化后的ast轉(zhuǎn)換成包含render字符串的code對象,最終render字符串通過new Function轉(zhuǎn)換為可執(zhí)行的render函數(shù)
八、虛擬DOM
先看瀏覽器對HTML的理解:
<div><h1>My title</h1> Some text content <!-- TODO: Add tagline --></div>復(fù)制代碼
當(dāng)瀏覽器讀到這些代碼時,它會建立一個DOM樹來保持追蹤所有內(nèi)容,如同你會畫一張家譜樹來追蹤家庭成員的發(fā)展一樣。 上述 HTML 對應(yīng)的 DOM 節(jié)點(diǎn)樹如下圖所示:

每個元素都是一個節(jié)點(diǎn)。每段文字也是一個節(jié)點(diǎn)。甚至注釋也都是節(jié)點(diǎn)。一個節(jié)點(diǎn)就是頁面的一個部分。就像家譜樹一樣,每個節(jié)點(diǎn)都可以有孩子節(jié)點(diǎn) (也就是說每個部分可以包含其它的一些部分)。
再看Vue對HTML template的理解
Vue 通過建立一個虛擬 DOM 來追蹤自己要如何改變真實 DOM。因為它所包含的信息會告訴 Vue 頁面上需要渲染什么樣的節(jié)點(diǎn),包括及其子節(jié)點(diǎn)的描述信息。我們把這樣的節(jié)點(diǎn)描述為“虛擬節(jié)點(diǎn) (virtual node)”,也常簡寫它為“VNode”。“虛擬 DOM”是我們對由 Vue 組件樹建立起來的整個 VNode 樹的稱呼。
簡言之,瀏覽器對HTML的理解是DOM樹,Vue對HTML的理解是虛擬DOM,最后在patch階段通過DOM操作的api將其渲染成真實的DOM節(jié)點(diǎn)。
九、模板或者組件渲染
Vue中的編譯會執(zhí)行到邏輯vm._update(vm._render(), hydrating),其中的vm._render執(zhí)行會獲取到vNode,vm._update就會對vNode進(jìn)行patch的處理,又分為模板渲染和組件渲染。
十、數(shù)據(jù)響應(yīng)式處理
Vue的數(shù)據(jù)響應(yīng)式處理的核心是Object.defineProperty,在遞歸響應(yīng)式處理對象的過程中,為每一個屬性定義了一個發(fā)布者dep,當(dāng)進(jìn)行_render函數(shù)執(zhí)行時會訪問到當(dāng)前值,在get中通過dep.depend進(jìn)行當(dāng)前Watcher的收集,當(dāng)數(shù)據(jù)發(fā)生變化時會在set中通過dep.notify進(jìn)行Watcher的更新。
十一、this.$set
const app = new Vue({el: "#app",data() {return {obj: {name: "name-1"}};},template: `<div @click="change">{{obj.name}}的年齡是{{obj.age}}</div>`,methods: {change() {this.obj.name = 'name-2';this.obj.age = 30;}}
});復(fù)制代碼
以上例子執(zhí)行的結(jié)果是:
name-1的年齡是
當(dāng)點(diǎn)擊后依然是:
name-2的年齡是
可以看出點(diǎn)擊后,obj的name屬性變化得到了視圖更新,而age屬性并未進(jìn)行變化。
name屬性響應(yīng)式的過程中鎖定了一個發(fā)布者dep,在當(dāng)前視圖渲染時在發(fā)布者dep的subs中做了記錄,一旦其發(fā)生改變,就會觸發(fā)set方法中的dep.notify,繼而執(zhí)行視圖的重新渲染。然而,age屬性并未進(jìn)行響應(yīng)式的處理,當(dāng)其改變時就不能進(jìn)行視圖渲染。
此時就需要通過this.$set的方式對其進(jìn)行手動響應(yīng)式的處理。
十二、組件注冊
組件的使用是先注冊后使用,又分為:
全局注冊:可以直接在頁面中使用
局部注冊:使用時需要通過import xxx from xxx的方式引入,并且在當(dāng)前組件的選項components中增加局部組件的名稱。
十三、異步組件
Vue單頁面應(yīng)用中一個頁面只有一個<div id="app"></div>承載所有節(jié)點(diǎn),因此復(fù)雜項目可能會出現(xiàn)首屏加載白屏等問題,Vue異步組件就很好的處理了這問題。
十四、this.$nextTick
因為通過new實例化構(gòu)造函數(shù)Vue的時候會執(zhí)行初始化方法this._init,其中涉及到的方法大多都是同步執(zhí)行。nextTick在vue中是一個很重要的方法,在new Vue實例化的同步過程中將一些需要異步處理的函數(shù)推到異步隊列中去,可以等new Vue所有的同步任務(wù)執(zhí)行完后,再執(zhí)行異步隊列中的函數(shù)。
十五、keep-alive內(nèi)置組件
vue中支持組件化,并且也有用于緩存的內(nèi)置組件keep-alive可直接使用,使用場景為路由組件和動態(tài)組件。
activated表示進(jìn)入組件的生命周期,deactivated表示離開組件的生命周期
include表示匹配到的才緩存,exclude表示匹配到的都不緩存
max表示最多可以緩存多少組件
十六、生命周期
vue中的生命周期有哪些?
答案:11個,分別為beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、activated、deactivated、beforeDestroy、destroyed和errorCaptured。
十七、v-show和v-if的區(qū)別
先看v-if和v-show的使用場景:
(1)v-if更多的使用在需要考慮白屏?xí)r間或者切換次數(shù)很少的場景
(2)v-show更多使用在transition控制的動畫或者需要非常頻繁地切換的場景
再從底層實現(xiàn)思路上分析:
(1)v-if條件為false時,會生成空的占位注釋節(jié)點(diǎn),那么在考慮首頁白屏?xí)r間時,選用v-if比較合適。條件從false變化為true的話會從空的注釋節(jié)點(diǎn)變成真實節(jié)點(diǎn),條件再變?yōu)閒alse時真實節(jié)點(diǎn)又會變成注釋節(jié)點(diǎn),如果切換次數(shù)比較多,那么開銷會比較大,頻繁切換場景不建議使用v-if。
(2)v-show條件為false時,會生成真實的節(jié)點(diǎn),只是為當(dāng)前節(jié)點(diǎn)增加了display:none來控制其隱藏,相比v-if生成空的注釋節(jié)點(diǎn)其首次渲染開銷是比較大的,所以不建議用在考慮首屏白屏?xí)r間的場景。如果我們頻繁切換v-show的值,從display:none到display:block之間的切換比起空的注釋節(jié)點(diǎn)和真實節(jié)點(diǎn)的開銷要小很多,這種場景就建議使用v-show。
十八、v-for中key的作用
在v-for進(jìn)行循環(huán)展示過程中,當(dāng)數(shù)據(jù)發(fā)生變化進(jìn)行渲染的過程中,會進(jìn)行新舊節(jié)點(diǎn)列表的比對。首先新舊vnode列表首先通過首首、尾尾、首尾和尾首的方式進(jìn)行比對,如果key相同則采取原地復(fù)用的策略進(jìn)行節(jié)點(diǎn)的移動。
如果首尾兩兩比對的方式找不到對應(yīng)關(guān)系,繼續(xù)通過key和vnode的對應(yīng)關(guān)系進(jìn)行尋找。
如果key和vnode對應(yīng)關(guān)系中找不到,繼續(xù)通過sameVnode的方式在未比對的節(jié)點(diǎn)中進(jìn)行尋找。
如果都找不到,則將其按照新vnode進(jìn)行createElm的方式進(jìn)行創(chuàng)建,這種方式是比節(jié)點(diǎn)移動的方式計算量更大。
最后將舊的vnode列表中沒有進(jìn)行匹配的vnode中的vnode.elm在父節(jié)點(diǎn)中移除。
簡單總結(jié)就是,新的vnode列表在舊的vnode列表中去尋找具有相同的key的節(jié)點(diǎn)進(jìn)行原地復(fù)用,如果找不到則通過創(chuàng)建的方式createElm去創(chuàng)建一個,如果舊的vnode列表中沒有進(jìn)行匹配則在父節(jié)點(diǎn)中移除其vnode.elm。這就是原地復(fù)用邏輯的大體實現(xiàn)。
十九、v-for和v-if能同時使用嗎
答案是:用了也能出來預(yù)期的效果,但是會有性能浪費(fèi)。
同時包含v-for和v-if的template模板在編輯階段會執(zhí)行v-for比v-if優(yōu)先級更高的編譯流程;在生成vnode的階段,會包含屬性isComment為true的空白占位vnode;在patch階段,會生成真實的占位節(jié)點(diǎn)。雖然一個空的占位節(jié)點(diǎn)無妨,但是如果數(shù)據(jù)量比較大的話,也是一個性能問題。
當(dāng)然,可以在獲取到數(shù)據(jù)(一般是在beforeCreate或者created階段)時進(jìn)行過濾處理,也可以通過計算屬性對其進(jìn)行處理。
二十、vue中的data為什么是函數(shù)
答案是:是不是一定是函數(shù),得看場景。并且,也無需擔(dān)心什么時候該將data寫為函數(shù)還是對象,因為vue內(nèi)部已經(jīng)做了處理,并在控制臺輸出錯誤信息。
場景一:new Vue({data: ...})
這種場景主要為項目入口或者多個html頁面各實例化一個Vue時,這里的data即可用對象的形式,也可用工廠函數(shù)返回對象的形式。因為,這里的data只會出現(xiàn)一次,不存在重復(fù)引用而引起的數(shù)據(jù)污染問題。
場景二:組件場景中的選項
在生成組件vnode的過程中,組件會在生成構(gòu)造函數(shù)的過程中執(zhí)行合并策略:
// data合并策略
strats.data = function (parentVal,childVal,vm
) {if (!vm) {if (childVal && typeof childVal !== 'function') {process.env.NODE_ENV !== 'production' && warn('The "data" option should be a function ' +'that returns a per-instance value in component ' +'definitions.',vm);return parentVal}return mergeDataOrFn(parentVal, childVal)}return mergeDataOrFn(parentVal, childVal, vm)
};
復(fù)制代碼
如果合并過程中發(fā)現(xiàn)子組件的數(shù)據(jù)不是函數(shù),即typeof childVal !== 'function'成立,進(jìn)而在開發(fā)環(huán)境會在控制臺輸出警告并且直接返回parentVal,說明這里壓根就沒有把childVal中的任何data信息合并到options中去。
二十一、this.$watch
使用場景:用來監(jiān)聽數(shù)據(jù)的變化,當(dāng)數(shù)據(jù)發(fā)生變化的時候,可以做一些業(yè)務(wù)邏輯的處理。
配置參數(shù):
deep:監(jiān)聽數(shù)據(jù)的深層變化
immediate:立即觸發(fā)回調(diào)函數(shù)
實現(xiàn)思路: Vue構(gòu)造函數(shù)定義完成以后,在執(zhí)行stateMixin(Vue)時為Vue.prototype上定義$watch。該方法通過const watcher = new Watcher(vm, expOrFn, cb, options)進(jìn)行Watcher的實例化,將options中的user屬性設(shè)置為true。并且,$watch邏輯結(jié)束的會返回函數(shù)function unwatchFn () { watcher.teardown() },用來取消偵聽的函數(shù)。
二十二、計算屬性和偵聽屬性的區(qū)別
相同點(diǎn): 兩者都是Watcher實例化過程中的產(chǎn)物
計算屬性:
使用場景:模板內(nèi)的表達(dá)式主要用于簡單運(yùn)算,對于復(fù)雜的計算邏輯可以用計算屬性
計算屬性是基于它們的響應(yīng)式依賴進(jìn)行緩存的,當(dāng)依賴的數(shù)據(jù)未發(fā)生變化時,多次調(diào)用無需重復(fù)執(zhí)行函數(shù)
計算屬性計算結(jié)果依賴于data中的值
同步操作,不支持異步
偵聽屬性:
使用場景:當(dāng)需要在數(shù)據(jù)變化時執(zhí)行異步或開銷較大的操作時,可以用偵聽屬性
可配置參數(shù):可以通過配置immediate和deep來控制立即執(zhí)行和深度監(jiān)聽的行為
偵聽屬性偵聽的是data中定義的
二十三、v-model
// main.js
new Vue({el: "#app",data() {return {msg: ""};},template: `<div><input v-model="msg" placeholder="edit me"><p>msg is: {{ msg }}</p></div>`
});
復(fù)制代碼
普通input:input中的v-model,最終通過target.addEventListener處理成在節(jié)點(diǎn)上監(jiān)聽input事件function($event){msg=$event.target.value}}的形式,當(dāng)input值變化時msg也跟著改變。
// main.js
const inputBox = {template: `<input @input="$emit('input', $event.target.value)">`,
};new Vue({el: "#app",template: `<div><input-box v-model="msg"></input-box><p>{{msg}}</p></div>`,components: {inputBox},data() {return {msg: 'hello world!'};},
});復(fù)制代碼
組件:v-model在組件中則通過給點(diǎn)擊事件綁定原生事件,當(dāng)觸發(fā)到$emit的時候,再進(jìn)行回調(diào)函數(shù)?unction input($$v) {msg=$$v}的執(zhí)行,進(jìn)而達(dá)到子組件修改父組件中數(shù)據(jù)msg的目的。
二十四、v-slot
v-slot產(chǎn)生的主要目的是,在組件的使用過程中可以讓父組件有修改子組件內(nèi)容的能力,就像在子組件里面放了個插槽,讓父組件往插槽內(nèi)塞入父組件中的楔子;并且,父組件在子組件中嵌入的楔子也可以訪問子組件中的數(shù)據(jù)。v-slot的產(chǎn)生讓組件的應(yīng)用更加靈活。
1、具名插槽
let baseLayout = {template: `<divclass="container"><header><slotname="header"></slot></header><main><slot></slot></main><footer><slotname="footer"></slot></footer></div>`,data() {return {url: ""};}
};new Vue({el: "#app",template: `<base-layout><templatev-slot:header><h1>title-txt</h1></template><p>paragraph-1-txt</p><p>paragraph-2-txt</p><templatev-slot:footer><p>foot-txt</p></template></base-layout>`,components: {baseLayout}
});
復(fù)制代碼
引入的組件baseLayout中的template被添加了屬性v-slot:header和v-slot:footer,子組件中定義了對應(yīng)的插槽被添加了屬性name="header"和name="footer",未被進(jìn)行插槽標(biāo)識的內(nèi)容被插入到了匿名的<slot></slot>中。
2、作用域插槽
let currentUser = {template: `<span><slot name="user" v-bind:userData="childData">{{childData.firstName}}</slot></span>`,data() {return {childData: {firstName: "first",lastName: "last"}};}
};new Vue({el: "#app",template: `<current-user><template v-slot:user="slotProps">{{slotProps.userData.lastName}}</template></current-user>`,components: {currentUser}
});復(fù)制代碼
當(dāng)前例子中作用域插槽通過v-bind:userData="childData"的方式,將childData作為參數(shù),父組件中通過v-slot:user="slotProps"的方式進(jìn)行接收,為父組件使用子組件中的數(shù)據(jù)提供了可能。
二十五、Vue.filters
filters類似于管道流可以將上一個過濾函數(shù)的結(jié)果作為下一個過濾函數(shù)的第一個參數(shù),又可以在其中傳遞參數(shù)讓過濾器更靈活。
// main.js文件import Vue from "vue";Vue.filter("filterEmpty", function(val) {returnval || "";
});Vue.filter("filterA", function(val) {returnval + "平時周末的";
});Vue.filter("filterB", function(val, info, fn) {returnval + info + fn;
});new Vue({el: "#app",template: `<div>{{msg | filterEmpty | filterA | filterB('愛好是', transformHobby('chess'))}}</div>`,data() {return {msg: "張三"};},methods: {transformHobby(type) {const map = {bike: "騎行",chess: "象棋",game: "游戲",swimming: "游泳"};return map[type] || "未知";}}
});
復(fù)制代碼
其中我們對msg通過filterEmpty、filterA和filterB('愛好是', transformHobby('chess'))}進(jìn)行三層過濾。
二十六、Vue.use
作用:Vue.use被用來安裝Vue.js插件,例如vue-router、vuex、element-ui。
install方法:如果插件是一個對象,必須提供 install 方法。如果插件是一個函數(shù),它會被作為install方法。install方法調(diào)用時,會將Vue作為參數(shù)傳入。
調(diào)用時機(jī):該方法需要在調(diào)用 new Vue() 之前被調(diào)用。
特點(diǎn):當(dāng) install 方法被同一個插件多次調(diào)用,插件將只會被安裝一次。
二十七、Vue.extend和選項extends
1、Vue.extend
Vue.extend使用基礎(chǔ)Vue構(gòu)造器創(chuàng)建一個“子類”,參數(shù)是一個包含組件選項的對象,實例化的過程中可以修改其中的選項,為實現(xiàn)功能的繼承提供了思路。
newVue({el: "#app",template: `<div><div id="person1"></div><div id="person2"></div></div>`,mounted() {// 定義子類構(gòu)造函數(shù)varProfile = Vue.extend({template: '<p @click="showInfo">{{name}} 喜歡 {{fruit}}</p>',data: function () {return {name: '張三',fruit: '蘋果'}},methods: {showInfo() {console.log(`${this.name}喜歡${this.fruit}`)}}})// 實例化1,掛載到`#person1`上newProfile().$mount('#person1')// 實例化2,并修改其`data`選項,掛載到`#person2`上newProfile({data: function () {return {name: '李四',fruit: '香蕉'}},}).$mount('#person2')},
});
復(fù)制代碼
在當(dāng)前例子中,通過Vue.extend構(gòu)建了子類構(gòu)造函數(shù)Profile,可以通過new Profile的方式實例化無數(shù)個vm實例。我們定義初始的template、data和methods供vm進(jìn)行使用,如果有變化,在實例的過程中傳入新的選項參數(shù)即可,比如例子中實例化第二個vm的時候就對data進(jìn)行了調(diào)整。
2、選項extends
extends允許聲明擴(kuò)展另一個組件 (可以是一個簡單的選項對象或構(gòu)造函數(shù)),而無需使用 Vue.extend。這主要是為了便于擴(kuò)展單文件組件,以實現(xiàn)組件繼承的目的。
const common = {template: `<div>{{name}}</div>`,data() {return {name: '表單'}}
}const create = {extends: common,data() {return {name: '新增表單'}}
}const edit = {extends: common,data() {return {name: '編輯表單'}}
}newVue({el: "#app",template: `<div><create></create><edit></edit></div>`,components: {create,edit,}
});
復(fù)制代碼
當(dāng)前極簡demo中定義了公共的表單common,然后又在新增表單組件create和編輯表單組件edit中擴(kuò)展了common。
二十八、Vue.mixin和選項mixins
全局混入和局部混入視情況而定,主要區(qū)別在全局混入是通過Vue.mixin的方式將選項混入到了Vue.options中,在所有獲取子組件構(gòu)建函數(shù)的時候都將其進(jìn)行了合并,是一種影響全部組件的混入策略。
而局部混入是將選項通過配置mixins選項的方式合并到當(dāng)前的子組件中,只有配置了mixins選項的組件才會受到混入影響,是一種局部的混入策略。
二十九、Vue.directive和directives
1、使用場景
主要用于對于DOM的操作,比如:文本框聚焦,節(jié)點(diǎn)位置控制、防抖節(jié)流、權(quán)限管理、復(fù)制操作等功能
2、鉤子函數(shù)
bind:只調(diào)用一次,指令第一次綁定到元素時調(diào)用。在這里可以進(jìn)行一次性的初始化設(shè)置。
inserted:被綁定元素插入父節(jié)點(diǎn)時調(diào)用 (僅保證父節(jié)點(diǎn)存在,但不一定已被插入文檔中)。
update:所在組件的 VNode 更新時調(diào)用,但是可能發(fā)生在其子 VNode 更新之前。指令的值可能發(fā)生了改變,也可能沒有。但是你可以通過比較更新前后的值來忽略不必要的模板更新。
componentUpdated:指令所在組件的 VNode 及其子 VNode 全部更新后調(diào)用。
unbind:只調(diào)用一次,指令與元素解綁時調(diào)用。
3、鉤子函數(shù)參數(shù)
el:指令所綁定的元素,可以用來直接操作 DOM。
binding:一個對象,包含以下 property:
name:指令名,不包括 v- 前綴。
value:指令的綁定值,例如:v-my-directive="1 + 1" 中,綁定值為 2。
oldValue:指令綁定的前一個值,僅在 update 和 componentUpdated 鉤子中可用。無論值是否改變都可用。
expression:字符串形式的指令表達(dá)式。例如 v-my-directive="1 + 1" 中,表達(dá)式為 "1 + 1"。
arg:傳給指令的參數(shù),可選。例如 v-my-directive:foo 中,參數(shù)為 "foo"。
modifiers:一個包含修飾符的對象。例如:v-my-directive.foo.bar 中,修飾符對象為 { foo: true, bar: true }。
vnode:Vue 編譯生成的虛擬節(jié)點(diǎn)。
oldVnode:上一個虛擬節(jié)點(diǎn),僅在 update 和 componentUpdated 鉤子中可用。
4、動態(tài)指令參數(shù)
指令的參數(shù)可以是動態(tài)的。例如,在 v-mydirective:[argument]="value" 中,argument 參數(shù)可以根據(jù)組件實例數(shù)據(jù)進(jìn)行更新!這使得自定義指令可以在應(yīng)用中被靈活使用。
三十、vue中的原生事件
vue中可以通過@或者v-on的方式綁定事件,也可為其添加修飾符。
newVue({el: '#app',template: `<div @click='divClick'><a @clickt='aClick' href=''>點(diǎn)擊</a></div>`,methods: {divClick() {console.log('divClick')},aClick() {console.log('aClick')},}
})
復(fù)制代碼
以上例子如果點(diǎn)擊a會觸發(fā)其默認(rèn)行為,如果href不為空還會進(jìn)行跳轉(zhuǎn)。除此之外,點(diǎn)擊還會繼續(xù)觸發(fā)div上綁定的點(diǎn)擊事件。
如果通過@click.stop.prevent='aClick'的方式為a標(biāo)簽的點(diǎn)擊事件添加修飾符stop和prevent,那么就不會觸發(fā)其a的默認(rèn)行為,即使href不為空也不會進(jìn)行跳轉(zhuǎn),同時,div上的點(diǎn)擊事件也不會進(jìn)行觸發(fā)。
模板的渲染一般分為編譯生成render函數(shù)、render函數(shù)執(zhí)行生成vNode和patch進(jìn)行渲染。下面按照這步驟進(jìn)行簡單分析。
1、render
通過編譯生成的render函數(shù):
with(this) {return _c('div', {on: {"click": divClick}}, [_c('a', {attrs: {"href": "http://www.baidu.com"},on: {"click": function ($event) {$event.stopPropagation();$event.preventDefault();return aClick($event)}}}, [_v("點(diǎn)擊")])])
}
復(fù)制代碼
其中div的on作為div事件描述。a標(biāo)簽的attrs作為屬性描述,on作為事件描述,在描述中.stop被編譯成了$event.stopPropagation()來阻止事件冒泡,.prevent被編譯成了$event.preventDefault()用來阻止a標(biāo)簽的默認(rèn)行為。
2、vNode
通過執(zhí)行Vue.prototype._render將render函數(shù)轉(zhuǎn)換成vNode。
3、patch
patch的過程中,當(dāng)完成$el節(jié)點(diǎn)的渲染后會執(zhí)行invokeCreateHooks(vnode, insertedVnodeQueue)邏輯,其中,針對attrs會將其設(shè)置為$el的真實屬性,當(dāng)前例子中會為a標(biāo)簽設(shè)置herf屬性。針對on會通過target.addEventListener的方式將其處理過的事件綁定到$el上,當(dāng)前例子中會分別對div和a中的click進(jìn)行處理,再通過addEventListener的方式進(jìn)行綁定。
小結(jié)
vue中的事件,從編譯生成render再通過Vue.prototype._render函數(shù)執(zhí)行render到生成vNode,主要是通過on作為描述。在patch渲染階段,將on描述的事件進(jìn)行處理再通過addEventListener的方式綁定到$el上。
三十一、常用修飾符
1、表單修飾符
(1).lazy
在默認(rèn)情況下,v-model 在每次 input 事件觸發(fā)后將輸入框的值與數(shù)據(jù)進(jìn)行同步 ,可以添加 lazy 修飾符,從而轉(zhuǎn)為在 change 事件之后進(jìn)行同步:
<input v-model.lazy="msg">
復(fù)制代碼
(2).number
如果想自動將用戶的輸入值轉(zhuǎn)為數(shù)值類型,可以給 v-model 添加 number 修飾符:
<input v-model.number="age" type="number">
復(fù)制代碼
(3).trim
如果要自動過濾用戶輸入的首尾空白字符,可以給 v-model 添加 trim 修飾符:
<input v-model.trim="msg">
復(fù)制代碼
2、事件修飾符
(1).stop
阻止單擊事件繼續(xù)傳播。
<!--這里只會觸發(fā)a--><div @click="divClick"><av-on:click.stop="aClick">點(diǎn)擊</a></div>復(fù)制代碼
(2).prevent
阻止標(biāo)簽的默認(rèn)行為。
<a href="http://www.baidu.com" v-on:click.prevent="aClick">點(diǎn)擊</a>
復(fù)制代碼
(3).capture
事件先在有.capture修飾符的節(jié)點(diǎn)上觸發(fā),然后在其包裹的內(nèi)部節(jié)點(diǎn)中觸發(fā)。
<!--這里先執(zhí)行divClick事件,然后再執(zhí)行aClick事件--><div @click="divClick"><av-on:click="aClick">點(diǎn)擊</a></div>復(fù)制代碼
(4).self
只當(dāng)在 event.target 是當(dāng)前元素自身時觸發(fā)處理函數(shù),即事件不是從內(nèi)部元素觸發(fā)的。
<!--在a標(biāo)簽上點(diǎn)擊時只會觸發(fā)aClick事件,只有點(diǎn)擊phrase的時候才會觸發(fā)divClick事件--><div @click.self="divClick">phrase<av-on:click="aClick">點(diǎn)擊</a></div>復(fù)制代碼
(5).once
不像其它只能對原生的 DOM 事件起作用的修飾符,.once 修飾符還能被用到自定義的組件事件上,表示當(dāng)前事件只觸發(fā)一次。
<a v-on:click.once="aClick">點(diǎn)擊</a>
復(fù)制代碼
(6).passive
.passive 修飾符尤其能夠提升移動端的性能
<!-- 滾動事件的默認(rèn)行為 (即滾動行為) 將會立即觸發(fā) --><!-- 而不會等待 `onScroll` 完成 --><!-- 這其中包含 `event.preventDefault()` 的情況 --><divv-on:scroll.passive="onScroll">...</div>復(fù)制代碼
3、其他修飾符
除了表單和事件的修飾符,Vue還提供了很多其他修飾符,在使用的時候可以查閱文檔。
小結(jié)
Vue中提供了很多好用的功能和api,那么修飾符的出現(xiàn)就為功能和api提供了更為豐富的擴(kuò)展屬性和更大的靈活度。
三十二、vue-router
vue路由是單頁面中視圖切換的方案,有三種mode:
hash,#后的僅僅作為參數(shù),不屬于url部分
history,路徑作為請求url請求資源鏈接,如果找不到會出現(xiàn)404錯誤
abstract,服務(wù)端渲染場景 hash場景下,會出現(xiàn)url鏈接,再修改其view-router中對應(yīng)的值。
三十三、vuex
vuex是狀態(tài)管理倉庫,一般使用的場景為:多個視圖依賴于同一狀態(tài),來自不同視圖的行為需要變更同一狀態(tài)。其管理的狀態(tài)是響應(yīng)式的,修改也只能顯式提交mutation的方式修改。vuex有state、getter、mutation、action和module五個核心,并且通過module實現(xiàn)了vuex樹的管理。
三十四、eventBus
使用場景:兄弟組件傳參
const eventBus = newVue();const A = {template: `<div @click="send">component-a</div>`,methods: {send() {eventBus.$emit('sendData', 'data from A')}},
}const B = {template: `<div>component-b</div>`,created() {eventBus.$on('sendData', (args) => {console.log(args)})},
}newVue({el: '#app',components: {A,B,},template: `<div><A></A><B></B></div>`,
})
復(fù)制代碼
在當(dāng)前例子中,A組件和B組件稱為兄弟組件,A組件通過事件總線eventBus中的$emit分發(fā)事件,B組件則通過$on來監(jiān)聽事件。
實現(xiàn)原理:eventsMixin
exportfunctioneventsMixin (Vue: Class<Component>) {const hookRE = /^hook:/Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {constvm: Component = thisif (Array.isArray(event)) {for (let i = 0, l = event.length; i < l; i++) {vm.$on(event[i], fn)}} else {(vm._events[event] || (vm._events[event] = [])).push(fn)// optimize hook:event cost by using a boolean flag marked at registration// instead of a hash lookupif (hookRE.test(event)) {vm._hasHookEvent = true}}return vm}Vue.prototype.$once = function (event: string, fn: Function): Component {constvm: Component = thisfunctionon () {vm.$off(event, on)fn.apply(vm, arguments)}on.fn = fnvm.$on(event, on)return vm}Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {constvm: Component = this// allif (!arguments.length) {vm._events = Object.create(null)return vm}// array of eventsif (Array.isArray(event)) {for (let i = 0, l = event.length; i < l; i++) {vm.$off(event[i], fn)}return vm}// specific eventconst cbs = vm._events[event]if (!cbs) {return vm}if (!fn) {vm._events[event] = nullreturn vm}// specific handlerlet cblet i = cbs.lengthwhile (i--) {cb = cbs[i]if (cb === fn || cb.fn === fn) {cbs.splice(i, 1)break}}return vm}Vue.prototype.$emit = function (event: string): Component {constvm: Component = thisif (process.env.NODE_ENV !== 'production') {const lowerCaseEvent = event.toLowerCase()if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {tip(`Event "${lowerCaseEvent}" is emitted in component ` +`${formatComponentName(vm)} but the handler is registered for "${event}". ` +`Note that HTML attributes are case-insensitive and you cannot use ` +`v-on to listen to camelCase events when using in-DOM templates. ` +`You should probably use "${hyphenate(event)}" instead of "${event}".`)}}let cbs = vm._events[event]if (cbs) {cbs = cbs.length > 1 ? toArray(cbs) : cbsconst args = toArray(arguments, 1)const info = `event handler for "${event}"`for (let i = 0, l = cbs.length; i < l; i++) {invokeWithErrorHandling(cbs[i], vm, args, vm, info)}}return vm}
}
復(fù)制代碼
在Vue構(gòu)造函數(shù)定義完執(zhí)行的eventsMixin函數(shù)中,在Vue.prototype上分別定義了$on、$emit、$off和$once的方法易實現(xiàn)對事件的綁定、分發(fā)、取消和只執(zhí)行一次的方法。eventBus就是利用了當(dāng)new Vue實例化后實例上的$on、$emit、$off和$once進(jìn)行數(shù)據(jù)傳遞。
三十五、ref
使用場景: 父組件獲取子組件數(shù)據(jù)或者執(zhí)行子組件方法
const A = {template: `<div>{{childData.age}}</div>`,data() {return {childData: {name: 'qb',age: 30},}},methods: {increaseAge() {this.childData.age++;}}
}newVue({el: '#app',components: {A,},template: `<A ref='childRef' @click.native='changeChildData'></A>`,methods: {changeChildData() {// 執(zhí)行子組件的方法this.$refs.childRef.increaseAge()// 獲取子組件的數(shù)據(jù)console.log(this.$refs.childRef.childData);},}
})
復(fù)制代碼
在當(dāng)前例子中,通過ref='childRef'的方式在當(dāng)前組件中定義一個ref,可以通過this.$refs.childRef的方式獲取到子組件A??梢酝ㄟ^this.$refs.childRef.increaseAge()的方式執(zhí)行子組件中age增加的方法,也可以通過this.$refs.childRef.childData的方式獲取到子組件中的數(shù)據(jù)。
三十六、props
使用場景: 父子傳參
const A = {template: `<div @click='emitData'>{{childData}}</div>`,props: ['childData'],methods: {emitData() {this.$emit('emitChildData', 'data from child')}},
}newVue({el: '#app',components: {A},template: `<A :childData='parentData' @emitChildData='getChildData'></A>`,data() {return {parentData: 'data from parent'}},methods: {getChildData(v) {console.log(v);}}
})
復(fù)制代碼
從當(dāng)前例子中可以看出,數(shù)據(jù)父傳子是通過:childData='parentData'的方式,數(shù)據(jù)子傳父是通過this.$emit('emitChildData', 'data from child')的方式,然后,父組件通過@emitChildData='getChildData'的方式進(jìn)行獲取。
大廠面試題分享 面試題庫
前端后端面試題庫 (面試必備) 推薦:★★★★★
地址:前端面試題庫
1、父組件render函數(shù)
new Vue中傳入的模板template經(jīng)過遍歷生成的render函數(shù)如下:
with(this) {return _c('A', {attrs: {"childData": parentData},on: {"emitChildData": getChildData}})
}
復(fù)制代碼
其中data部分有attrs和on來描述屬性和方法。
在通過createComponent創(chuàng)建組件vnode的過程中,會通過const propsData = extractPropsFromVNodeData(data, Ctor, tag)的方式獲取props,通過const listeners = data.on的方式獲取listeners,最后將其作為參數(shù)通過new VNode(options)的方式實例化組件vnode。
2、子組件渲染
在通過const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance )創(chuàng)建組件實例的過程中,會執(zhí)行到組件繼承自Vue的._init方法,通過initEvents將事件處理后存儲到vm._events中,通過initProps將childData賦值到子組件A的vm實例上,并進(jìn)行響應(yīng)式處理,讓其可以通過vm.childData的方式訪問,并且數(shù)據(jù)發(fā)生變化時視圖也可以發(fā)生改變。
組件模板編譯后對應(yīng)的render函數(shù)是:
with(this) {return _c('div', {on: {"click": emitData}}, [_v(_s(childData))])
}
復(fù)制代碼
在createElm完成節(jié)點(diǎn)的創(chuàng)建后,在invokeCreateHooks(vnode, insertedVnodeQueue)階段,給DOM原生節(jié)點(diǎn)節(jié)點(diǎn)綁定emitData。
3、this.$emit
在點(diǎn)擊執(zhí)行this.$emit時,會通過var cbs = vm._events[event]取出_events中的事件進(jìn)行執(zhí)行。
至此,父組件中的傳遞的數(shù)據(jù)就在子組件中可以通過this.xxx的方式獲得,也可以通過this.$emit的方式將子組件中的數(shù)據(jù)傳遞給父組件。
三十七、$attrs和$listeners
使用場景: 父子組件非props屬性和非native方法傳遞
// main.js文件importVuefrom"vue";const B = {template: `<div @click="emitData">{{ formParentData }}</div>`,data() {return {formParentData: ''}},inheritAttrs: false,created() {this.formParentData = this.$attrs;console.log(this.$attrs, '--------------a-component-$attrs')console.log(this.$listeners, '--------------b-component-$listeners')},methods: {emitData() {this.$emit('onFun', 'form B component')}},
}const A = {template: `<B v-bind='$attrs' v-on='$listeners'></B>`,components: {B,},props: ['propData'],inheritAttrs: false,created() {console.log(this.$attrs, '--------------b-component-$attrs')console.log(this.$listeners, '--------------b-component-$listeners')}
}newVue({el: '#app',components: {A,},template: `<A :attrData='parentData' :propData='parentData' @click.native="nativeFun" @onFun="onFun"></A>`,data() {return {parentData: 'msg'}},methods: {nativeFun() {console.log('方法A');},onFun(v) {console.log('方法B', v);},}
})
復(fù)制代碼
當(dāng)前例子中,new Vue的template模板中有attrData、propData、click.native和onFun在進(jìn)行傳遞。實際運(yùn)行后,在A組件中this.$attrs為{attrData: 'msg'},this.$listeners為{onFun:f(...)}。在A組件中通過v-bind='$attrs'和v-on='$listeners'的方式繼續(xù)進(jìn)行屬性和方法的傳遞,在B組件中就可以獲取到A組件中傳入的$attrs和$listeners。
當(dāng)前例子中完成了非props屬性和非native方法的傳遞,并且通過v-bind='$attrs'和v-on='$listeners'的方式實現(xiàn)了屬性和方法的跨層級傳遞。
同時通過this.$emit的方法觸發(fā)了根節(jié)點(diǎn)中onFun事件。
關(guān)于例子中的inheritAttrs: false,默認(rèn)情況下父作用域的不被認(rèn)作props的attribute綁定將會“回退”且作為普通的HTML屬性應(yīng)用在子組件的根元素上。當(dāng)撰寫包裹一個目標(biāo)元素或另一個組件的組件時,這可能不會總是符合預(yù)期行為。通過設(shè)置inheritAttrs到false,這些默認(rèn)行為將會被去掉。
三十八、$parent和$children
使用場景: 利用父子關(guān)系進(jìn)行數(shù)據(jù)的獲取或者方法的調(diào)用
const A = {template: `<div @click="changeParentData">{{childRandom}}</div>`,data() {return {childRandom: Math.random()}},mounted() {console.log(this.$parent.parentCount, '--child-created--'); // 獲取父組件中的parentCount},methods: {changeParentData() {console.log(this.$parent); // 打印當(dāng)前實例的$parentthis.$parent.changeParentData(); // 調(diào)用當(dāng)前父級中的方法`changeParentData`},changeChildData() {this.childRandom = Math.random();}}
}
const B = {template: `<div>b-component</div>`,
}newVue({el: '#app',components: {A,B,},template: `<div><A></A><B></B><p>{{parentCount}}</p><button @click="changeChildrenData">修改子組件數(shù)據(jù)</button></div>`,data() {return {parentCount: 1}},mounted() {console.log(this.$children[0].childRandom, '--parent-created--'); // 獲取第一個子組件中的childRandom},methods: {changeParentData() {this.parentCount++;},changeChildrenData() {console.log(this.$children); // 此時有兩個子組件this.$children[0].changeChildData(); // 調(diào)起第一個子組件中的'changeChildData'方法}}
})
復(fù)制代碼
在當(dāng)前例子中,父組件可以通過this.$children獲取所有的子組件,這里有A組件和B組件,可以通過this.$children[0].childRandom的方式獲取子組件A中的數(shù)據(jù),也可以通過this.$children[0].changeChildData()的方式調(diào)起子組件A中的方法。
子組件可以通過this.$parent的方式獲取父組件,可以通過this.$parent.parentCount獲取父組件中的數(shù)據(jù),也可以通過this.$parent.changeParentData()的方式修改父組件中的數(shù)據(jù)。
Vue中$parent和$children父子關(guān)系的底層構(gòu)建請參考雜談:parent/parent/children的底層邏輯
三十九、inject和provide
使用場景:嵌套組件多層級傳參
const B = {template: `<div>{{parentData1}}{{parentData2}}</div>`,inject: ['parentData1', 'parentData2'],
}const A = {template: `<B></B>`,components: {B,},
}new Vue({el: '#app',components: {A,},template: `<A></A>`,provide: {parentData1: {name: 'name-2',age: 30},parentData2: {name: 'name-2',age: 29},}
})
復(fù)制代碼
例子中在new Vue的時候通過provide提供了兩個數(shù)據(jù)來源parentData1和parentData2,然后跨了一個A組件在B組件中通過inject注入了這兩個數(shù)據(jù)。
1、initProvide
在執(zhí)行組件內(nèi)部的this._init初始化方法時,會執(zhí)行到initProvide邏輯:
export function initProvide (vm: Component) {const provide = vm.$options.provideif (provide) {vm._provided = typeof provide === 'function'? provide.call(vm): provide}
}
復(fù)制代碼
如果在當(dāng)前vm.$options中存在provide,會將其執(zhí)行結(jié)果賦值給vm._provided。
2、initInjections
functioninitInjections (vm: Component) {const result = resolveInject(vm.$options.inject, vm)if (result) {toggleObserving(false)Object.keys(result).forEach(key => {/* istanbul ignore else */if (process.env.NODE_ENV !== 'production') {defineReactive(vm, key, result[key], () => {warn(`Avoid mutating an injected value directly since the changes will be ` +`overwritten whenever the provided component re-renders. ` +`injection being mutated: "${key}"`,vm)})} else {defineReactive(vm, key, result[key])}})toggleObserving(true)}
}
functionresolveInject (inject: any, vm: Component): ?Object {if (inject) {// inject is :any because flow is not smart enough to figure out cachedconst result = Object.create(null)const keys = hasSymbol? Reflect.ownKeys(inject): Object.keys(inject)for (let i = 0; i < keys.length; i++) {const key = keys[i]// #6574 in case the inject object is observed...if (key === '__ob__') continueconst provideKey = inject[key].fromlet source = vmwhile (source) {if (source._provided && hasOwn(source._provided, provideKey)) {result[key] = source._provided[provideKey]break}source = source.$parent}if (!source) {if ('default'in inject[key]) {const provideDefault = inject[key].defaultresult[key] = typeof provideDefault === 'function'? provideDefault.call(vm): provideDefault} elseif (process.env.NODE_ENV !== 'production') {warn(`Injection "${key}" not found`, vm)}}}return result}
}
復(fù)制代碼
如果當(dāng)前組件中有選項inject,會以while循環(huán)的方式不斷在source = source.$parent中尋找_provided,然后獲取到祖先組件中提供的數(shù)據(jù)源,這是實現(xiàn)祖先組件向所有子孫后代注入依賴的核心。
四十、Vue項目能做的性能優(yōu)化
1、v-if和v-show
頻繁切換時使用v-show,利用其緩存特性
首屏渲染時使用v-if,如果為false則不進(jìn)行渲染
2、v-for的key
列表變化時,循環(huán)時使用唯一不變的key,借助其本地復(fù)用策略
列表只進(jìn)行一次渲染時,key可以采用循環(huán)的index
3、偵聽器和計算屬性
偵聽器watch用于數(shù)據(jù)變化時引起其他行為
多使用compouter計算屬性顧名思義就是新計算而來的屬性,如果依賴的數(shù)據(jù)未發(fā)生變化,不會觸發(fā)重新計算
4、合理使用生命周期
在destroyed階段進(jìn)行綁定事件或者定時器的銷毀
使用動態(tài)組件的時候通過keep-alive包裹進(jìn)行緩存處理,相關(guān)的操作可以在actived階段激活
5、數(shù)據(jù)響應(yīng)式處理
不需要響應(yīng)式處理的數(shù)據(jù)可以通過Object.freeze處理,或者直接通過this.xxx = xxx的方式進(jìn)行定義
需要響應(yīng)式處理的屬性可以通過this.$set的方式處理,而不是JSON.parse(JSON.stringify(XXX))的方式
6、路由加載方式
頁面組件可以采用異步加載的方式
7、插件引入
第三方插件可以采用按需加載的方式,比如element-ui。
8、減少代碼量
采用mixin的方式抽離公共方法
抽離公共組件
定義公共方法至公共js中
抽離公共css
9、編譯方式
如果線上需要template的編譯,可以采用完成版vue.esm.js
如果線上無需template的編譯,可采用運(yùn)行時版本vue.runtime.esm.js,相比完整版體積要小大約30%
10、渲染方式
服務(wù)端渲染,如果是需要SEO的網(wǎng)站可以采用服務(wù)端渲染的方式
前端渲染,一些企業(yè)內(nèi)部使用的后端管理系統(tǒng)可以采用前端渲染的方式
11、字體圖標(biāo)的使用
有些圖片圖標(biāo)盡可能使用字體圖標(biāo)
四十一、Vue項目白屏問題
1、開啟gzip壓縮減小文件體積。
2、webpack設(shè)置productionSourceMap:false,不在線上環(huán)境打包.map文件。
3、路由懶加載
4、異步組件的使用
5、靜態(tài)資源使用cdn鏈接引入
6、采用ssr服務(wù)端渲染方案
7、骨架屏或者loading效果填充空白間隙
8、首次不渲染的隱藏采用v-if
9、注重代碼規(guī)范:抽取公共組件,公共js,公共css樣式,減小代碼體積。刪除無用代碼,減少非必要注釋。防止寫出死循環(huán)等等
10、刪除輔助開發(fā)的console.log
11、非Vue角度思考:非重要文件采用異步加載方式、css樣式采用媒體查詢、采用域名分片技術(shù)、http1升級成http2、如果是SSR項目考慮服務(wù)端渲染有沒有可優(yōu)化的點(diǎn)、請求頭是否帶了多余信息等思路
內(nèi)容有些多,大體可以歸類為從服務(wù)端拿到資源的速度、資源的體積和渲染是否阻塞的角度去作答。
四十二、從0到1構(gòu)建一個Vue項目需要注意什么
架子:選用合適的初始化腳手架(vue-cli2.0或者vue-cli3.0)
請求:數(shù)據(jù)axios請求的配置
登錄:登錄注冊系統(tǒng)
路由:路由管理頁面
數(shù)據(jù):vuex全局?jǐn)?shù)據(jù)管理
權(quán)限:權(quán)限管理系統(tǒng)
埋點(diǎn):埋點(diǎn)系統(tǒng)
插件:第三方插件的選取以及引入方式
錯誤:錯誤頁面
入口:前端資源直接當(dāng)靜態(tài)資源,或者服務(wù)端模板拉取
SEO:如果考慮SEO建議采用SSR方案
組件:基礎(chǔ)組件/業(yè)務(wù)組件
樣式:樣式預(yù)處理起,公共樣式抽取
方法:公共方法抽離
四十三、SSR
1、什么是服務(wù)端渲染(SSR)?
Vue.js 是構(gòu)建客戶端應(yīng)用程序的框架。默認(rèn)情況下,可以在瀏覽器中輸出 Vue 組件,進(jìn)行生成 DOM 和操作 DOM。然而,也可以將同一個組件渲染為服務(wù)器端的 HTML 字符串,將它們直接發(fā)送到瀏覽器,最后將這些靜態(tài)標(biāo)記"激活"為客戶端上完全可交互的應(yīng)用程序。
2、為什么使用服務(wù)端渲染(SSR)?
與傳統(tǒng) SPA (單頁應(yīng)用程序 (Single-Page Application)) 相比,服務(wù)器端渲染 (SSR) 的優(yōu)勢主要在于:
更好的 SEO,由于搜索引擎爬蟲抓取工具可以直接查看完全渲染的頁面。
更快的內(nèi)容到達(dá)時間 (time-to-content),特別是對于緩慢的網(wǎng)絡(luò)情況或運(yùn)行緩慢的設(shè)備。
3、使用服務(wù)器端渲染 (SSR) 時需要考慮的問題?
使用服務(wù)器端渲染 (SSR) 時還需要有一些權(quán)衡之處
開發(fā)條件所限。瀏覽器特定的代碼,只能在某些生命周期鉤子函數(shù) (lifecycle hook) 中使用;一些外部擴(kuò)展庫 (external library) 可能需要特殊處理,才能在服務(wù)器渲染應(yīng)用程序中運(yùn)行。
涉及構(gòu)建設(shè)置和部署的更多要求。與可以部署在任何靜態(tài)文件服務(wù)器上的完全靜態(tài)單頁面應(yīng)用程序 (SPA) 不同,服務(wù)器渲染應(yīng)用程序,需要處于 Node.js server 運(yùn)行環(huán)境。
更多的服務(wù)器端負(fù)載。在 Node.js 中渲染完整的應(yīng)用程序,顯然會比僅僅提供靜態(tài)文件的 server 更加大量占用 CPU 資源 (CPU-intensive - CPU 密集),因此如果你預(yù)料在高流量環(huán)境 (high traffic) 下使用,請準(zhǔn)備相應(yīng)的服務(wù)器負(fù)載,并明智地采用緩存策略。
四十四、scoped
在Vue項目開發(fā)的項目中如果樣式中未使用scoped,組件間的樣式會出現(xiàn)覆蓋的問題。
反例:
// app.vue文件
<template><div><h3class="title">app-txt</h3><child></child></div></template><script>import child from"@/components/child";
exportdefault {components: { child },
};
</script><style>.title {color: red;
}
</style>復(fù)制代碼
// child.vue文件
<template><h3class="title">child-txt</h3></template><style>.title {color: green;}
</style>復(fù)制代碼
父組件和子組件的樣式顏色都為green,子組件中的樣式覆蓋了父組件的樣式。
正例:
<template><h3class="title">child-txt</h3></template><stylescoped>.title {color: green;}
</style>復(fù)制代碼
此時,父組件中顏色為red,子組件中顏色為green。
主要原因:


例子中的DOM節(jié)點(diǎn)和CSS層疊樣式中都被添加了data-v-xxx來表示唯一,所以scoped是給當(dāng)前組件的節(jié)點(diǎn)和樣式唯一標(biāo)識為data-v-xxx,避免了樣式覆蓋。
大廠面試題分享 面試題庫
后端面試題庫 (面試必備) 推薦:★★★★★
地址:前端面試題庫