?給大家推薦一個(gè)實(shí)用面試題庫
1、前端面試題庫 (面試必備) ? ? ? ? ? ?推薦:★★★★★
地址:web前端面試題庫文章來源地址http://www.zghlxwxcb.cn/news/detail-726406.html
很喜歡‘萬變不離其宗’
這句話,希望在不斷的思考和總結(jié)中找到Vue
中的宗
,來解答面試官拋出的各種Vue
問題,一起加油~
一、MVVM原理
在Vue2
官方文檔中沒有找到Vue
是MVVM
的直接證據(jù),但文檔有提到:雖然沒有完全遵循MVVM模型
,但是 Vue 的設(shè)計(jì)也受到了它的啟發(fā),因此在文檔中經(jīng)常會(huì)使用vm
(ViewModel 的縮寫) 這個(gè)變量名表示 Vue 實(shí)例。
為了感受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)用,是加載單個(gè)HTML頁面并在用戶與應(yīng)用程序交互時(shí)動(dòng)態(tài)更新該頁面的Web應(yīng)用程序。我們開發(fā)的Vue
項(xiàng)目大多是借助個(gè)官方的CLI
腳手架,快速搭建項(xiàng)目,直接通過new Vue
構(gòu)建一個(gè)實(shí)例,并將el:'#app'
掛載參數(shù)傳入,最后通過npm run build
的方式打包后生成一個(gè)index.html
,稱這種只有一個(gè)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="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",
// ...
},
通過執(zhí)行npm run build的時(shí)候,會(huì)進(jìn)行scripts/build.js文件的執(zhí)行,npm run build:ssr和npm run build:weex的時(shí)候,將ssr和weex作為參數(shù)傳入,按照參數(shù)構(gòu)建出不一樣的vue.js打包文件。
所以說,vue
中的package.json
文件就是構(gòu)建的入口,具體構(gòu)建流程可以參考vue2入口:構(gòu)建入口。
五、對(duì)import Vue from "vue"的理解
在使用腳手架開發(fā)項(xiàng)目時(shí),會(huì)有一行代碼import Vue from "vue"
,那么這個(gè)Vue
指的是什么。
答:一個(gè)構(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ā)中引入的Vue
其實(shí)就是這個(gè)構(gòu)造函數(shù),而且這個(gè)構(gòu)造函數(shù)只能通過new Vue
的方式進(jìn)行使用,否則會(huì)在控制臺(tái)打印警告信息。定義完后,還會(huì)通過initMixin(Vue)
、stateMixin(Vue)
、eventsMixin(Vue)
、lifecycleMixin(Vue)
和renderMixin(Vue)
的方式為Vue
原型中混入方法。我們通過import Vue from "Vue"
引入的本質(zhì)上就是一個(gè)原型上掛在了好多方法的構(gòu)造函數(shù)。
六、對(duì)new Vue的理解
// main.js文件
import Vue from "vue";
var app = new Vue({
el: '#app',
data() {
return {
msg: 'hello Vue~'
}
},
template: `<div>{{msg}}</div>`,
})
console.log(app);
new Vue
就是對(duì)構(gòu)造函數(shù)Vue
進(jìn)行實(shí)例化,執(zhí)行結(jié)果如下:
可以看出實(shí)例化后的實(shí)例中包含了很多屬性,用來對(duì)當(dāng)前app
進(jìn)行描述,當(dāng)然復(fù)雜的Vue
項(xiàng)目這個(gè)app
將會(huì)是一個(gè)樹結(jié)構(gòu),通過$parent
和$children
維護(hù)父子關(guān)系。
new Vue
的過程中還會(huì)執(zhí)行this._init
方法進(jìn)行初始化處理。
七、編譯
虛擬DOM
的生成必須通過render
函數(shù)實(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
}
})
主要完成的功能是:
- 通過
const ast = parse(template.trim(), options)
將template
轉(zhuǎn)換成ast
樹 - 通過
optimize(ast, options)
對(duì)ast
進(jìn)行優(yōu)化 - 通過
const code = generate(ast, options)
將優(yōu)化后的ast
轉(zhuǎn)換成包含render
字符串的code
對(duì)象,最終render
字符串通過new Function
轉(zhuǎn)換為可執(zhí)行的render
函數(shù)
模板編譯的真實(shí)入口
可以參考vue2從template到render:模板編譯入口parse
可以參考vue2從template到render:ASToptimize
可以參考vue2從template到render:optimizegenerate
可以參考vue2從template到render:code
八、虛擬DOM
先看瀏覽器對(duì)HTML
的理解:
<div>
<h1>My title</h1>
Some text content
<!-- TODO: Add tagline -->
</div>
當(dāng)瀏覽器讀到這些代碼時(shí),它會(huì)建立一個(gè)DOM樹來保持追蹤所有內(nèi)容,如同你會(huì)畫一張家譜樹來追蹤家庭成員的發(fā)展一樣。 上述 HTML 對(duì)應(yīng)的 DOM 節(jié)點(diǎn)樹如下圖所示:
?每個(gè)元素都是一個(gè)節(jié)點(diǎn)。每段文字也是一個(gè)節(jié)點(diǎn)。甚至注釋也都是節(jié)點(diǎn)。一個(gè)節(jié)點(diǎn)就是頁面的一個(gè)部分。就像家譜樹一樣,每個(gè)節(jié)點(diǎn)都可以有孩子節(jié)點(diǎn) (也就是說每個(gè)部分可以包含其它的一些部分)。
再看Vue
對(duì)HTML template
的理解
Vue 通過建立一個(gè)虛擬 DOM?來追蹤自己要如何改變真實(shí) DOM。因?yàn)樗男畔?huì)告訴 Vue 頁面上需要渲染什么樣的節(jié)點(diǎn),包括及其子節(jié)點(diǎn)的描述信息。我們把這樣的節(jié)點(diǎn)描述為“虛擬節(jié)點(diǎn) (virtual node)”,也常簡寫它為“VNode”?!疤摂M DOM”是我們對(duì)由 Vue 組件樹建立起來的整個(gè) VNode 樹的稱呼。
簡言之,瀏覽器對(duì)HTML的理解是DOM樹,Vue對(duì)HTML
的理解是虛擬DOM,最后在patch
階段通過DOM操作的api將其渲染成真實(shí)的DOM節(jié)點(diǎn)。
九、模板或者組件渲染
Vue
中的編譯會(huì)執(zhí)行到邏輯vm._update(vm._render(), hydrating)
,其中的vm._render
執(zhí)行會(huì)獲取到vNode
,vm._update
就會(huì)對(duì)vNode
進(jìn)行patch
的處理,又分為模板渲染和組件渲染。
十、數(shù)據(jù)響應(yīng)式處理
Vue
的數(shù)據(jù)響應(yīng)式處理的核心是Object.defineProperty
,在遞歸響應(yīng)式處理對(duì)象的過程中,為每一個(gè)屬性定義了一個(gè)發(fā)布者dep
,當(dāng)進(jìn)行_render
函數(shù)執(zhí)行時(shí)會(huì)訪問到當(dāng)前值,在get
中通過dep.depend
進(jìn)行當(dāng)前Watcher
的收集,當(dāng)數(shù)據(jù)發(fā)生變化時(shí)會(huì)在set
中通過dep.notify
進(jìn)行Watcher
的更新。
數(shù)據(jù)響應(yīng)式處理以及發(fā)布訂閱者模式的關(guān)系請(qǐng)參考vue2從數(shù)據(jù)變化到視圖變化:發(fā)布訂閱模式
十一、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;
}
}
});
以上例子執(zhí)行的結(jié)果是:
name-1的年齡是
當(dāng)點(diǎn)擊后依然是:
name-2的年齡是
可以看出點(diǎn)擊后,obj
的name
屬性變化得到了視圖更新,而age
屬性并未進(jìn)行變化。
name
屬性響應(yīng)式的過程中鎖定了一個(gè)發(fā)布者dep
,在當(dāng)前視圖渲染時(shí)在發(fā)布者dep
的subs
中做了記錄,一旦其發(fā)生改變,就會(huì)觸發(fā)set
方法中的dep.notify
,繼而執(zhí)行視圖的重新渲染。然而,age
屬性并未進(jìn)行響應(yīng)式的處理,當(dāng)其改變時(shí)就不能進(jìn)行視圖渲染。
十二、組件注冊(cè)
組件的使用是先注冊(cè)后使用,又分為:
- 全局注冊(cè):可以直接在頁面中使用
- 局部注冊(cè):使用時(shí)需要通過
import xxx from xxx
的方式引入,并且在當(dāng)前組件的選項(xiàng)components
中增加局部組件的名稱。
十三、異步組件
Vue單頁面應(yīng)用中一個(gè)頁面只有一個(gè)<div id="app"></div>
承載所有節(jié)點(diǎn),因此復(fù)雜項(xiàng)目可能會(huì)出現(xiàn)首屏加載白屏等問題,Vue異步組件就很好的處理了這問題。
十四、this.$nextTick
因?yàn)橥ㄟ^new
實(shí)例化構(gòu)造函數(shù)Vue
的時(shí)候會(huì)執(zhí)行初始化方法this._init
,其中涉及到的方法大多都是同步執(zhí)行。nextTick
在vue中是一個(gè)很重要的方法,在new Vue
實(shí)例化的同步過程中將一些需要異步處理的函數(shù)推到異步隊(duì)列中去,可以等new Vue
所有的同步任務(wù)執(zhí)行完后,再執(zhí)行異步隊(duì)列中的函數(shù)。
nextTick
的實(shí)現(xiàn)可以參考?vue2從數(shù)據(jù)變化到視圖變化:nextTick,
十五、keep-alive內(nèi)置組件
vue
中支持組件化,并且也有用于緩存的內(nèi)置組件keep-alive
可直接使用,使用場景為路由組件
和動(dòng)態(tài)組件
。
-
activated
表示進(jìn)入組件的生命周期,deactivated
表示離開組件的生命周期 -
include
表示匹配到的才緩存,exclude
表示匹配到的都不緩存 -
max
表示最多可以緩存多少組件
keep-alive
的具體實(shí)現(xiàn)請(qǐng)參考?vue中的keep-alive(源碼分析)
十六、生命周期
vue
中的生命周期有哪些?
答案:11
個(gè),分別為beforeCreate
、created
、beforeMount
、mounted
、beforeUpdate
、updated
、activated
、deactivated
、beforeDestroy
、destroyed
和errorCaptured
。
具體實(shí)現(xiàn)請(qǐng)參考?vue生命周期
十七、v-show和v-if的區(qū)別
先看v-if
和v-show
的使用場景:
(1)v-if
更多的使用在需要考慮白屏?xí)r間或者切換次數(shù)很少的場景
(2)v-show
更多使用在transition
控制的動(dòng)畫或者需要非常頻繁地切換的場景
再從底層實(shí)現(xiàn)思路上分析:
(1)v-if
條件為false
時(shí),會(huì)生成空的占位注釋節(jié)點(diǎn),那么在考慮首頁白屏?xí)r間時(shí),選用v-if
比較合適。條件從false
變化為true
的話會(huì)從空的注釋節(jié)點(diǎn)變成真實(shí)節(jié)點(diǎn),條件再變?yōu)?code>false時(shí)真實(shí)節(jié)點(diǎn)又會(huì)變成注釋節(jié)點(diǎn),如果切換次數(shù)比較多,那么開銷會(huì)比較大,頻繁切換場景不建議使用v-if
。
(2)v-show
條件為false
時(shí),會(huì)生成真實(shí)的節(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)和真實(shí)節(jié)點(diǎn)的開銷要小很多,這種場景就建議使用v-show
。
可以通過vue中v-if和v-show的區(qū)別(源碼分析)了解v-if
和v-show
詳細(xì)過程。
十八、v-for中key的作用
在v-for
進(jìn)行循環(huán)展示過程中,當(dāng)數(shù)據(jù)發(fā)生變化進(jìn)行渲染的過程中,會(huì)進(jìn)行新舊節(jié)點(diǎn)列表的比對(duì)。首先新舊vnode
列表首先通過首首
、尾尾
、首尾
和尾首
的方式進(jìn)行比對(duì),如果key
相同則采取原地復(fù)用的策略進(jìn)行節(jié)點(diǎn)的移動(dòng)。
如果首尾兩兩比對(duì)的方式找不到對(duì)應(yīng)關(guān)系,繼續(xù)通過key
和vnode
的對(duì)應(yīng)關(guān)系進(jìn)行尋找。
如果key
和vnode
對(duì)應(yīng)關(guān)系中找不到,繼續(xù)通過sameVnode
的方式在未比對(duì)的節(jié)點(diǎn)中進(jìn)行尋找。
如果都找不到,則將其按照新vnode
進(jìn)行createElm
的方式進(jìn)行創(chuàng)建,這種方式是比節(jié)點(diǎn)移動(dòng)的方式計(jì)算量更大。
最后將舊的vnode
列表中沒有進(jìn)行匹配的vnode
中的vnode.elm
在父節(jié)點(diǎn)中移除。
簡單總結(jié)就是,新的vnode
列表在舊的vnode
列表中去尋找具有相同的key
的節(jié)點(diǎn)進(jìn)行原地復(fù)用,如果找不到則通過創(chuàng)建的方式createElm
去創(chuàng)建一個(gè),如果舊的vnode
列表中沒有進(jìn)行匹配則在父節(jié)點(diǎn)中移除其vnode.elm
。這就是原地復(fù)用邏輯的大體實(shí)現(xiàn)。
具體key
和diff
算法的關(guān)系可以參考vue2從數(shù)據(jù)變化到視圖變化:diff算法圖解
十九、v-for和v-if能同時(shí)使用嗎
答案是:用了也能出來預(yù)期的效果,但是會(huì)有性能浪費(fèi)。
同時(shí)包含v-for
和v-if
的template
模板在編輯階段會(huì)執(zhí)行v-for
比v-if
優(yōu)先級(jí)更高的編譯流程;在生成vnode
的階段,會(huì)包含屬性isComment
為true
的空白占位vnode
;在patch
階段,會(huì)生成真實(shí)的占位節(jié)點(diǎn)。雖然一個(gè)空的占位節(jié)點(diǎn)無妨,但是如果數(shù)據(jù)量比較大的話,也是一個(gè)性能問題。
當(dāng)然,可以在獲取到數(shù)據(jù)(一般是在beforeCreate
或者created
階段)時(shí)進(jìn)行過濾處理,也可以通過計(jì)算屬性對(duì)其進(jìn)行處理。
可以通過v-for和v-if可以一起使用嗎?了解v-for
和v-if
的詳細(xì)過程。
二十、vue中的data為什么是函數(shù)
答案是:是不是一定是函數(shù),得看場景。并且,也無需擔(dān)心什么時(shí)候該將data
寫為函數(shù)還是對(duì)象,因?yàn)?code>vue內(nèi)部已經(jīng)做了處理,并在控制臺(tái)輸出錯(cuò)誤信息。
場景一:new Vue({data: ...})
這種場景主要為項(xiàng)目入口或者多個(gè)html
頁面各實(shí)例化一個(gè)Vue
時(shí),這里的data
即可用對(duì)象的形式,也可用工廠函數(shù)返回對(duì)象的形式。因?yàn)?,這里的data
只會(huì)出現(xiàn)一次,不存在重復(fù)引用而引起的數(shù)據(jù)污染問題。
場景二:組件場景中的選項(xiàng)
在生成組件vnode
的過程中,組件會(huì)在生成構(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ā)現(xiàn)子組件的數(shù)據(jù)不是函數(shù),即typeof childVal !== 'function'
成立,進(jìn)而在開發(fā)環(huán)境會(huì)在控制臺(tái)輸出警告并且直接返回parentVal
,說明這里壓根就沒有把childVal
中的任何data
信息合并到options
中去。
可以通過vue中的data為什么是函數(shù)?了解詳細(xì)過程。
二十一、this.$watch
使用場景:用來監(jiān)聽數(shù)據(jù)的變化,當(dāng)數(shù)據(jù)發(fā)生變化的時(shí)候,可以做一些業(yè)務(wù)邏輯的處理。
配置參數(shù):
-
deep
:監(jiān)聽數(shù)據(jù)的深層變化 -
immediate
:立即觸發(fā)回調(diào)函數(shù)
實(shí)現(xiàn)思路:?Vue
構(gòu)造函數(shù)定義完成以后,在執(zhí)行stateMixin(Vue)
時(shí)為Vue.prototype
上定義$watch
。該方法通過const watcher = new Watcher(vm, expOrFn, cb, options)
進(jìn)行Watcher
的實(shí)例化,將options
中的user
屬性設(shè)置為true
。并且,$watch
邏輯結(jié)束的會(huì)返回函數(shù)function unwatchFn () { watcher.teardown() }
,用來取消偵聽的函數(shù)。
可以通過watch
選項(xiàng)和$watch
方法的區(qū)別vue中的watch和$watch監(jiān)聽的事件,執(zhí)行幾次?來了解詳細(xì)過程。
二十二、計(jì)算屬性和偵聽屬性的區(qū)別
相同點(diǎn):?兩者都是Watcher
實(shí)例化過程中的產(chǎn)物
計(jì)算屬性:
- 使用場景:模板內(nèi)的表達(dá)式主要用于簡單運(yùn)算,對(duì)于復(fù)雜的計(jì)算邏輯可以用計(jì)算屬性
- 計(jì)算屬性是基于它們的響應(yīng)式依賴進(jìn)行緩存的,當(dāng)依賴的數(shù)據(jù)未發(fā)生變化時(shí),多次調(diào)用無需重復(fù)執(zhí)行函數(shù)
- 計(jì)算屬性計(jì)算結(jié)果依賴于
data
中的值 - 同步操作,不支持異步
偵聽屬性:
- 使用場景:當(dāng)需要在數(shù)據(jù)變化時(shí)執(zhí)行異步或開銷較大的操作時(shí),可以用偵聽屬性
- 可配置參數(shù):可以通過配置
immediate
和deep
來控制立即執(zhí)行和深度監(jiān)聽的行為 - 偵聽屬性偵聽的是
data
中定義的
計(jì)算屬性請(qǐng)參考vue2從數(shù)據(jù)變化到視圖變化:計(jì)算屬性
偵聽屬性請(qǐng)參考vue2從數(shù)據(jù)變化到視圖變化:偵聽器
二十三、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>`
});
普通input:input
中的v-model
,最終通過target.addEventListener
處理成在節(jié)點(diǎn)上監(jiān)聽input
事件function($event){msg=$event.target.value}}
的形式,當(dāng)input
值變化時(shí)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!'
};
},
});
組件:v-model
在組件中則通過給點(diǎn)擊事件綁定原生事件,當(dāng)觸發(fā)到$emit
的時(shí)候,再進(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)容的能力,就像在子組件里面放了個(gè)插槽,讓父組件往插槽內(nèi)塞入父組件中的楔子;并且,父組件在子組件中嵌入的楔子也可以訪問子組件中的數(shù)據(jù)。v-slot
的產(chǎn)生讓組件的應(yīng)用更加靈活。
1、具名插槽
let baseLayout = {
template: `<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>`,
data() {
return {
url: ""
};
}
};
new Vue({
el: "#app",
template: `<base-layout>
<template v-slot:header>
<h1>title-txt</h1>
</template>
<p>paragraph-1-txt</p>
<p>paragraph-2-txt</p>
<template v-slot:footer>
<p>foot-txt</p>
</template>
</base-layout>`,
components: {
baseLayout
}
});
引入的組件baseLayout
中的template
被添加了屬性v-slot:header
和v-slot:footer
,子組件中定義了對(duì)應(yīng)的插槽被添加了屬性name="header"
和name="footer"
,未被進(jìn)行插槽標(biāo)識(shí)的內(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
}
});
當(dāng)前例子中作用域插槽通過v-bind:userData="childData"
的方式,將childData
作為參數(shù),父組件中通過v-slot:user="slotProps"
的方式進(jìn)行接收,為父組件使用子組件中的數(shù)據(jù)提供了可能。
v-slot
的底層實(shí)現(xiàn)請(qǐng)參考vue中的v-slot(源碼分析)
二十五、Vue.filters
filters
類似于管道流可以將上一個(gè)過濾函數(shù)的結(jié)果作為下一個(gè)過濾函數(shù)的第一個(gè)參數(shù),又可以在其中傳遞參數(shù)讓過濾器更靈活。
// main.js文件
import Vue from "vue";
Vue.filter("filterEmpty", function(val) {
return val || "";
});
Vue.filter("filterA", function(val) {
return val + "平時(shí)周末的";
});
Vue.filter("filterB", function(val, info, fn) {
return val + 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] || "未知";
}
}
});
其中我們對(duì)msg
通過filterEmpty
、filterA
和filterB('愛好是', transformHobby('chess'))}
進(jìn)行三層過濾。
Vue.filters
的底層實(shí)現(xiàn)請(qǐng)查看vue中的filters(源碼分析)
二十六、Vue.use
- 作用:
Vue.use
被用來安裝Vue.js插件,例如vue-router
、vuex
、element-ui
。 -
install
方法:如果插件是一個(gè)對(duì)象,必須提供?install
?方法。如果插件是一個(gè)函數(shù),它會(huì)被作為install
方法。install
方法調(diào)用時(shí),會(huì)將Vue
作為參數(shù)傳入。 - 調(diào)用時(shí)機(jī):該方法需要在調(diào)用?
new Vue()
?之前被調(diào)用。 - 特點(diǎn):當(dāng) install 方法被同一個(gè)插件多次調(diào)用,插件將只會(huì)被安裝一次。
二十七、Vue.extend和選項(xiàng)extends
1、Vue.extend
Vue.extend
使用基礎(chǔ)Vue
構(gòu)造器創(chuàng)建一個(gè)“子類”,參數(shù)是一個(gè)包含組件選項(xiàng)的對(duì)象,實(shí)例化的過程中可以修改其中的選項(xiàng),為實(shí)現(xiàn)功能的繼承提供了思路。
new Vue({
el: "#app",
template: `<div><div id="person1"></div><div id="person2"></div></div>`,
mounted() {
// 定義子類構(gòu)造函數(shù)
var Profile = Vue.extend({
template: '<p @click="showInfo">{{name}} 喜歡 {{fruit}}</p>',
data: function () {
return {
name: '張三',
fruit: '蘋果'
}
},
methods: {
showInfo() {
console.log(`${this.name}喜歡${this.fruit}`)
}
}
})
// 實(shí)例化1,掛載到`#person1`上
new Profile().$mount('#person1')
// 實(shí)例化2,并修改其`data`選項(xiàng),掛載到`#person2`上
new Profile({
data: function () {
return {
name: '李四',
fruit: '香蕉'
}
},
}).$mount('#person2')
},
});
在當(dāng)前例子中,通過Vue.extend
構(gòu)建了子類構(gòu)造函數(shù)Profile
,可以通過new Profile
的方式實(shí)例化無數(shù)個(gè)vm
實(shí)例。我們定義初始的template
、data
和methods
供vm
進(jìn)行使用,如果有變化,在實(shí)例的過程中傳入新的選項(xiàng)參數(shù)即可,比如例子中實(shí)例化第二個(gè)vm
的時(shí)候就對(duì)data
進(jìn)行了調(diào)整。
2、選項(xiàng)extends
extends
允許聲明擴(kuò)展另一個(gè)組件 (可以是一個(gè)簡單的選項(xiàng)對(duì)象或構(gòu)造函數(shù)),而無需使用?Vue.extend
。這主要是為了便于擴(kuò)展單文件組件,以實(shí)現(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: '編輯表單'
}
}
}
new Vue({
el: "#app",
template: `<div>
<create></create>
<edit></edit>
</div>`,
components: {
create,
edit,
}
});
當(dāng)前極簡demo中定義了公共的表單common
,然后又在新增表單組件create
和編輯表單組件edit
中擴(kuò)展了common
。
二十八、Vue.mixin和選項(xiàng)mixins
全局混入和局部混入視情況而定,主要區(qū)別在全局混入是通過Vue.mixin
的方式將選項(xiàng)混入到了Vue.options
中,在所有獲取子組件構(gòu)建函數(shù)的時(shí)候都將其進(jìn)行了合并,是一種影響全部組件的混入策略。
而局部混入是將選項(xiàng)通過配置mixins
選項(xiàng)的方式合并到當(dāng)前的子組件中,只有配置了mixins
選項(xiàng)的組件才會(huì)受到混入影響,是一種局部的混入策略。
二十九、Vue.directive和directives
1、使用場景
主要用于對(duì)于DOM的操作,比如:文本框聚焦,節(jié)點(diǎn)位置控制、防抖節(jié)流、權(quán)限管理、復(fù)制操作等功能
2、鉤子函數(shù)
-
bind
:只調(diào)用一次,指令第一次綁定到元素時(shí)調(diào)用。在這里可以進(jìn)行一次性的初始化設(shè)置。 -
inserted
:被綁定元素插入父節(jié)點(diǎn)時(shí)調(diào)用 (僅保證父節(jié)點(diǎn)存在,但不一定已被插入文檔中)。 -
update
:所在組件的 VNode 更新時(shí)調(diào)用,但是可能發(fā)生在其子 VNode 更新之前。指令的值可能發(fā)生了改變,也可能沒有。但是你可以通過比較更新前后的值來忽略不必要的模板更新。 -
componentUpdated
:指令所在組件的 VNode?及其子 VNode?全部更新后調(diào)用。 -
unbind
:只調(diào)用一次,指令與元素解綁時(shí)調(diào)用。
3、鉤子函數(shù)參數(shù)
-
el
:指令所綁定的元素,可以用來直接操作 DOM。 -
binding
:一個(gè)對(duì)象,包含以下 property:-
name
:指令名,不包括?v-
?前綴。 -
value
:指令的綁定值,例如:v-my-directive="1 + 1"
?中,綁定值為?2
。 -
oldValue
:指令綁定的前一個(gè)值,僅在?update
?和?componentUpdated
?鉤子中可用。無論值是否改變都可用。 -
expression
:字符串形式的指令表達(dá)式。例如?v-my-directive="1 + 1"
?中,表達(dá)式為?"1 + 1"
。 -
arg
:傳給指令的參數(shù),可選。例如?v-my-directive:foo
?中,參數(shù)為?"foo"
。 -
modifiers
:一個(gè)包含修飾符的對(duì)象。例如:v-my-directive.foo.bar
?中,修飾符對(duì)象為?{ foo: true, bar: true }
。
-
-
vnode
:Vue 編譯生成的虛擬節(jié)點(diǎn)。 -
oldVnode
:上一個(gè)虛擬節(jié)點(diǎn),僅在?update
?和?componentUpdated
?鉤子中可用。
4、動(dòng)態(tài)指令參數(shù)
指令的參數(shù)可以是動(dòng)態(tài)的。例如,在?v-mydirective:[argument]="value"
?中,argument
?參數(shù)可以根據(jù)組件實(shí)例數(shù)據(jù)進(jìn)行更新!這使得自定義指令可以在應(yīng)用中被靈活使用。
三十、vue中的原生事件
vue
中可以通過@
或者v-on
的方式綁定事件,也可為其添加修飾符。
new Vue({
el: '#app',
template: `<div @click='divClick'><a @clickt='aClick' href=''>點(diǎn)擊</a></div>`,
methods: {
divClick() {
console.log('divClick')
},
aClick() {
console.log('aClick')
},
}
})
以上例子如果點(diǎn)擊a
會(huì)觸發(fā)其默認(rèn)行為,如果href
不為空還會(huì)進(jìn)行跳轉(zhuǎn)。除此之外,點(diǎn)擊還會(huì)繼續(xù)觸發(fā)div
上綁定的點(diǎn)擊事件。
如果通過@click.stop.prevent='aClick'
的方式為a
標(biāo)簽的點(diǎn)擊事件添加修飾符stop
和prevent
,那么就不會(huì)觸發(fā)其a
的默認(rèn)行為,即使href
不為空也不會(huì)進(jìn)行跳轉(zhuǎn),同時(shí),div
上的點(diǎn)擊事件也不會(huì)進(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)擊")])])
}
其中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)的渲染后會(huì)執(zhí)行invokeCreateHooks(vnode, insertedVnodeQueue)
邏輯,其中,針對(duì)attrs
會(huì)將其設(shè)置為$el
的真實(shí)屬性,當(dāng)前例子中會(huì)為a
標(biāo)簽設(shè)置herf
屬性。針對(duì)on
會(huì)通過target.addEventListener
的方式將其處理過的事件綁定到$el
上,當(dāng)前例子中會(huì)分別對(duì)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">
(2).number
如果想自動(dòng)將用戶的輸入值轉(zhuǎn)為數(shù)值類型,可以給?v-model
?添加?number
?修飾符:
<input v-model.number="age" type="number">
(3).trim
如果要自動(dòng)過濾用戶輸入的首尾空白字符,可以給?v-model
?添加?trim
?修飾符:
<input v-model.trim="msg">
2、事件修飾符
(1).stop
阻止單擊事件繼續(xù)傳播。
<!--這里只會(huì)觸發(fā)a-->
<div @click="divClick"><a v-on:click.stop="aClick">點(diǎn)擊</a></div>
(2).prevent
阻止標(biāo)簽的默認(rèn)行為。
<a v-on:click.prevent="aClick">點(diǎn)擊</a>
(3).capture
事件先在有.capture
修飾符的節(jié)點(diǎn)上觸發(fā),然后在其包裹的內(nèi)部節(jié)點(diǎn)中觸發(fā)。
<!--這里先執(zhí)行divClick事件,然后再執(zhí)行aClick事件-->
<div @click="divClick"><a v-on:click="aClick">點(diǎn)擊</a></div>
(4).self
只當(dāng)在 event.target 是當(dāng)前元素自身時(shí)觸發(fā)處理函數(shù),即事件不是從內(nèi)部元素觸發(fā)的。
<!--在a標(biāo)簽上點(diǎn)擊時(shí)只會(huì)觸發(fā)aClick事件,只有點(diǎn)擊phrase的時(shí)候才會(huì)觸發(fā)divClick事件-->
<div @click.self="divClick">phrase<a v-on:click="aClick">點(diǎn)擊</a></div>
(5).once
不像其它只能對(duì)原生的 DOM 事件起作用的修飾符,.once
?修飾符還能被用到自定義的組件事件上,表示當(dāng)前事件只觸發(fā)一次。
<a v-on:click.once="aClick">點(diǎn)擊</a>
(6).passive
.passive
?修飾符尤其能夠提升移動(dòng)端的性能
<!-- 滾動(dòng)事件的默認(rèn)行為 (即滾動(dòng)行為) 將會(huì)立即觸發(fā) -->
<!-- 而不會(huì)等待 `onScroll` 完成 -->
<!-- 這其中包含 `event.preventDefault()` 的情況 -->
<div v-on:scroll.passive="onScroll">...</div>
3、其他修飾符
除了表單和事件的修飾符,Vue
還提供了很多其他修飾符,在使用的時(shí)候可以查閱文檔。
小結(jié)
Vue
中提供了很多好用的功能和api
,那么修飾符的出現(xiàn)就為功能和api
提供了更為豐富的擴(kuò)展屬性和更大的靈活度。
三十二、vue-router
vue
路由是單頁面中視圖切換的方案,有三種mode
:
- hash,#后的僅僅作為參數(shù),不屬于url部分
- history,路徑作為請(qǐng)求url請(qǐng)求資源鏈接,如果找不到會(huì)出現(xiàn)404錯(cuò)誤
- abstract,服務(wù)端渲染場景 hash場景下,會(huì)出現(xiàn)
url
鏈接,再修改其view-router中對(duì)應(yīng)的值。
了解vue-router
的底層實(shí)現(xiàn)請(qǐng)參考vue2視圖切換:vue-router
三十三、vuex
vuex
是狀態(tài)管理倉庫,一般使用的場景為:多個(gè)視圖依賴于同一狀態(tài),來自不同視圖的行為需要變更同一狀態(tài)。其管理的狀態(tài)是響應(yīng)式的,修改也只能顯式提交mutation
的方式修改。vuex
有state
、getter
、mutation
、action
和module
五個(gè)核心,并且通過module
實(shí)現(xiàn)了vuex
樹的管理。
了解vuex
的底層實(shí)現(xiàn)請(qǐng)參考vue2狀態(tài)管理:vuex
三十四、eventBus
使用場景:兄弟組件傳參
const eventBus = new Vue();
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)
})
},
}
new Vue({
el: '#app',
components: {
A,
B,
},
template: `<div><A></A><B></B></div>`,
})
在當(dāng)前例子中,A
組件和B
組件稱為兄弟組件,A
組件通過事件總線eventBus
中的$emit
分發(fā)事件,B
組件則通過$on
來監(jiān)聽事件。
實(shí)現(xiàn)原理:eventsMixin
export function eventsMixin (Vue: Class<Component>) {
const hookRE = /^hook:/
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
if (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 lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
function on () {
vm.$off(event, on)
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// all
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$off(event[i], fn)
}
return vm
}
// specific event
const cbs = vm._events[event]
if (!cbs) {
return vm
}
if (!fn) {
vm._events[event] = null
return vm
}
// specific handler
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
if (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) : cbs
const 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
}
}
在Vue
構(gòu)造函數(shù)定義完執(zhí)行的eventsMixin
函數(shù)中,在Vue.prototype
上分別定義了$on
、$emit
、$off
和$once
的方法易實(shí)現(xiàn)對(duì)事件的綁定、分發(fā)、取消和只執(zhí)行一次的方法。eventBus
就是利用了當(dāng)new Vue
實(shí)例化后實(shí)例上的$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++;
}
}
}
new Vue({
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);
},
}
})
在當(dāng)前例子中,通過ref='childRef'
的方式在當(dāng)前組件中定義一個(gè)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')
}
},
}
new Vue({
el: '#app',
components: {
A
},
template: `<A :childData='parentData' @emitChildData='getChildData'></A>`,
data() {
return {
parentData: 'data from parent'
}
},
methods: {
getChildData(v) {
console.log(v);
}
}
})
從當(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
}
})
}
其中data
部分有attrs
和on
來描述屬性和方法。
在通過createComponent
創(chuàng)建組件vnode
的過程中,會(huì)通過const propsData = extractPropsFromVNodeData(data, Ctor, tag)
的方式獲取props
,通過const listeners = data.on
的方式獲取listeners
,最后將其作為參數(shù)通過new VNode(options)
的方式實(shí)例化組件vnode
。
2、子組件渲染
在通過const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance )
創(chuàng)建組件實(shí)例的過程中,會(huì)執(zhí)行到組件繼承自Vue
的._init
方法,通過initEvents
將事件處理后存儲(chǔ)到vm._events
中,通過initProps
將childData
賦值到子組件A
的vm
實(shí)例上,并進(jìn)行響應(yīng)式處理,讓其可以通過vm.childData
的方式訪問,并且數(shù)據(jù)發(fā)生變化時(shí)視圖也可以發(fā)生改變。
組件模板編譯后對(duì)應(yīng)的render
函數(shù)是:
with(this) {
return _c('div', {
on: {
"click": emitData
}
}, [_v(_s(childData))])
}
在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
時(shí),會(huì)通過var cbs = vm._events[event]
取出_events
中的事件進(jìn)行執(zhí)行。
至此,父組件中的傳遞的數(shù)據(jù)就在子組件中可以通過this.xxx
的方式獲得,也可以通過this.$emit
的方式將子組件中的數(shù)據(jù)傳遞給父組件。
prop
數(shù)據(jù)發(fā)生改變引起視圖變化的底層邏輯請(qǐng)參考vue2從數(shù)據(jù)變化到視圖變化:props引起視圖變化詳解
三十七、$attrs和$listeners
使用場景:?父子組件非props
屬性和非native
方法傳遞
// main.js文件
import Vue from "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')
}
}
new Vue({
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);
},
}
})
當(dāng)前例子中,new Vue
的template
模板中有attrData
、propData
、click.native
和onFun
在進(jìn)行傳遞。實(shí)際運(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'
的方式實(shí)現(xiàn)了屬性和方法的跨層級(jí)傳遞。
同時(shí)通過this.$emit
的方法觸發(fā)了根節(jié)點(diǎn)中onFun
事件。
關(guān)于例子中的inheritAttrs: false
,默認(rèn)情況下父作用域的不被認(rèn)作props
的attribute
綁定將會(huì)“回退”且作為普通的HTML
屬性應(yīng)用在子組件的根元素上。當(dāng)撰寫包裹一個(gè)目標(biāo)元素或另一個(gè)組件的組件時(shí),這可能不會(huì)總是符合預(yù)期行為。通過設(shè)置inheritAttrs
到false
,這些默認(rèn)行為將會(huì)被去掉。
三十八、$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)前實(shí)例的$parent
this.$parent.changeParentData(); // 調(diào)用當(dāng)前父級(jí)中的方法`changeParentData`
},
changeChildData() {
this.childRandom = Math.random();
}
}
}
const B = {
template: `<div>b-component</div>`,
}
new Vue({
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--'); // 獲取第一個(gè)子組件中的childRandom
},
methods: {
changeParentData() {
this.parentCount++;
},
changeChildrenData() {
console.log(this.$children); // 此時(shí)有兩個(gè)子組件
this.$children[0].changeChildData(); // 調(diào)起第一個(gè)子組件中的'changeChildData'方法
}
}
})
在當(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)建請(qǐng)參考雜談:??????/parent/children的底層邏輯
三十九、inject和provide
使用場景:嵌套組件多層級(jí)傳參
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
},
}
})
例子中在new Vue
的時(shí)候通過provide
提供了兩個(gè)數(shù)據(jù)來源parentData1
和parentData2
,然后跨了一個(gè)A
組件在B
組件中通過inject
注入了這兩個(gè)數(shù)據(jù)。
1、initProvide
在執(zhí)行組件內(nèi)部的this._init
初始化方法時(shí),會(huì)執(zhí)行到initProvide
邏輯:
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
如果在當(dāng)前vm.$options
中存在provide
,會(huì)將其執(zhí)行結(jié)果賦值給vm._provided
。
2、initInjections
function initInjections (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)
}
}
function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
const 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__') continue
const provideKey = inject[key].from
let source = vm
while (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].default
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault
} else if (process.env.NODE_ENV !== 'production') {
warn(`Injection "${key}" not found`, vm)
}
}
}
return result
}
}
如果當(dāng)前組件中有選項(xiàng)inject
,會(huì)以while
循環(huán)的方式不斷在source = source.$parent
中尋找_provided
,然后獲取到祖先組件中提供的數(shù)據(jù)源,這是實(shí)現(xiàn)祖先組件向所有子孫后代注入依賴的核心。
四十、Vue項(xiàng)目能做的性能優(yōu)化
1、v-if
和v-show
- 頻繁切換時(shí)使用
v-show
,利用其緩存特性 - 首屏渲染時(shí)使用
v-if
,如果為false
則不進(jìn)行渲染
2、v-for
的key
- 列表變化時(shí),循環(huán)時(shí)使用唯一不變的
key
,借助其本地復(fù)用策略 - 列表只進(jìn)行一次渲染時(shí),
key
可以采用循環(huán)的index
3、偵聽器和計(jì)算屬性
- 偵聽器
watch
用于數(shù)據(jù)變化時(shí)引起其他行為 - 多使用
compouter
計(jì)算屬性顧名思義就是新計(jì)算而來的屬性,如果依賴的數(shù)據(jù)未發(fā)生變化,不會(huì)觸發(fā)重新計(jì)算
4、合理使用生命周期
- 在
destroyed
階段進(jìn)行綁定事件或者定時(shí)器的銷毀 - 使用動(dòng)態(tài)組件的時(shí)候通過
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)行時(shí)版本vue.runtime.esm.js
,相比完整版體積要小大約30%
10、渲染方式
- 服務(wù)端渲染,如果是需要
SEO
的網(wǎng)站可以采用服務(wù)端渲染的方式 - 前端渲染,一些企業(yè)內(nèi)部使用的后端管理系統(tǒng)可以采用前端渲染的方式
11、字體圖標(biāo)的使用
- 有些圖片圖標(biāo)盡可能使用字體圖標(biāo)
四十一、Vue項(xiàng)目白屏問題
- 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升級(jí)成http2、如果是SSR項(xiàng)目考慮服務(wù)端渲染有沒有可優(yōu)化的點(diǎn)、請(qǐng)求頭是否帶了多余信息等思路
內(nèi)容有些多,大體可以歸類為從服務(wù)端拿到資源的速度、資源的體積和渲染是否阻塞的角度去作答。
四十二、從0到1構(gòu)建一個(gè)Vue項(xiàng)目需要注意什么
- 架子:選用合適的初始化腳手架(
vue-cli2.0
或者vue-cli3.0
) - 請(qǐng)求:數(shù)據(jù)
axios
請(qǐng)求的配置 - 登錄:登錄注冊(cè)系統(tǒng)
- 路由:路由管理頁面
- 數(shù)據(jù):
vuex
全局?jǐn)?shù)據(jù)管理 - 權(quán)限:權(quán)限管理系統(tǒng)
- 埋點(diǎn):埋點(diǎn)系統(tǒng)
- 插件:第三方插件的選取以及引入方式
- 錯(cuò)誤:錯(cuò)誤頁面
- 入口:前端資源直接當(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。然而,也可以將同一個(gè)組件渲染為服務(wù)器端的 HTML 字符串,將它們直接發(fā)送到瀏覽器,最后將這些靜態(tài)標(biāo)記"激活"為客戶端上完全可交互的應(yīng)用程序。
2、為什么使用服務(wù)端渲染(SSR)?
與傳統(tǒng) SPA (單頁應(yīng)用程序 (Single-Page Application)) 相比,服務(wù)器端渲染 (SSR) 的優(yōu)勢(shì)主要在于:
- 更好的 SEO,由于搜索引擎爬蟲抓取工具可以直接查看完全渲染的頁面。
- 更快的內(nèi)容到達(dá)時(shí)間 (time-to-content),特別是對(duì)于緩慢的網(wǎng)絡(luò)情況或運(yùn)行緩慢的設(shè)備。
3、使用服務(wù)器端渲染 (SSR) 時(shí)需要考慮的問題?
使用服務(wù)器端渲染 (SSR) 時(shí)還需要有一些權(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)用程序,顯然會(huì)比僅僅提供靜態(tài)文件的 server 更加大量占用 CPU 資源 (CPU-intensive - CPU 密集),因此如果你預(yù)料在高流量環(huán)境 (high traffic) 下使用,請(qǐng)準(zhǔn)備相應(yīng)的服務(wù)器負(fù)載,并明智地采用緩存策略。
四十四、scoped
在Vue
項(xiàng)目開發(fā)的項(xiàng)目中如果樣式中未使用scoped
,組件間的樣式會(huì)出現(xiàn)覆蓋的問題。
反例:
// app.vue文件
<template>
<div>
<h3 class="title">app-txt</h3>
<child></child>
</div>
</template>
<script>
import child from "@/components/child";
export default {
components: { child },
};
</script>
<style>
.title {
color: red;
}
</style>
// child.vue文件
<template>
<h3 class="title">child-txt</h3>
</template>
<style>
.title {
color: green;
}
</style>
父組件和子組件的樣式顏色都為green
,子組件中的樣式覆蓋了父組件的樣式。
正例:
<template>
<h3 class="title">child-txt</h3>
</template>
<style scoped>
.title {
color: green;
}
</style>
此時(shí),父組件中顏色為red
,子組件中顏色為green
。
主要原因:
?例子中的DOM節(jié)點(diǎn)和CSS層疊樣式中都被添加了data-v-xxx
來表示唯一,所以scoped
是給當(dāng)前組件的節(jié)點(diǎn)和樣式唯一標(biāo)識(shí)為data-v-xxx
,避免了樣式覆蓋。文章來源:http://www.zghlxwxcb.cn/news/detail-726406.html
?給大家推薦一個(gè)實(shí)用面試題庫
1、前端面試題庫 (面試必備) ? ? ? ? ? ?推薦:★★★★★
地址:web前端面試題庫
到了這里,關(guān)于vue核心面試題匯總【查缺補(bǔ)漏】的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!