如果你是一個已經在學習前端開發的初學者亦或者是一名在代碼界縱橫多年的程序員,那你一定知道現在最火的前端框架之一Vue.js。它相比於React與Angular上手更加容易,或許這也是很多初學者選擇vue的原因之一。
我們看到很多招聘上都寫着掌握vue開發項目,那麼面試都會問什麼呢?別急,下面是我給大家整理了一份比較全面的Vue面試高頻考題解析小冊。
對於MVVM的理解
MVVM 是 Model-View-ViewModel 的縮寫
Model: 代表數據模型,也可以在Model中定義數據修改和操作的業務邏輯。我們可以把Model稱爲數據層,因爲它僅僅關注數據本身,不關心任何行爲
View: 用戶操作界面。當ViewModel對Model進行更新的時候,會通過數據綁定更新到View
ViewModel: 業務邏輯層,View需要什麼數據,ViewModel要提供這個數據;View有某些操作,ViewModel就要響應這些操作,所以可以說它是Model for View.
總結: MVVM模式簡化了界面與業務的依賴,解決了數據頻繁更新。MVVM 在使用當中,利用雙向綁定技術,使得 Model 變化時,ViewModel 會自動更新,而 ViewModel 變化時,View 也會自動變化。
開發中常用的指令有哪些
-
v-model
:一般用在表達輸入,很輕鬆的實現表單控件和數據的雙向綁定 -
v-html
: 更新元素的 innerHTML -
v-show
與v-if
: 條件渲染, 注意二者區別
使用了v-if的時候,如果值爲false,那麼頁面將不會有這個html標籤生成。
v-show則是不管值爲true還是false,html元素都會存在,只是CSS中的display顯示或隱藏
-
v-on : click
: 可以簡寫爲@click,@綁定一個事件。如果事件觸發了,就可以指定事件的處理函數 -
v-for
:基於源數據多次渲染元素或模板塊 -
v-bind
: 當表達式的值改變時,將其產生的連帶影響,響應式地作用於 DOM
語法:v-bind:title="msg"
簡寫::title="msg"
請詳細說下你對vue生命週期的理解
vue生命週期總共分爲8個階段: 創建前/後,載入前/後,更新前/後, 銷燬前/後。
-
beforeCreate (創建前)vue實例的掛載元素
$el
和數據對象data
都是undefined
, 還未初始化 -
created (創建後) 完成了
data
數據初始化,el
還未初始化 -
beforeMount (載入前) vue實例的
$el
和data
都初始化了, 相關的render函數首次被調用。實例已完成以下的配置:編譯模板,把data裏面的數據和模板生成html。注意此時還沒有掛載html到頁面上。 -
mounted (載入後) 在
el
被新創建的vm.$el
替換,並掛載到實例上去之後調用。實例已完成以下的配置:用上面編譯好的html
內容替換el
屬性指向的DOM
對象。完成模板中的html渲染到html頁面中。此過程中進行ajax交互 - beforeUpdate (更新前) 在數據更新之前調用,發生在虛擬DOM重新渲染和打補丁之前。可以在該鉤子中進一步地更改狀態,不會觸發附加的重渲染過程。
- updated (更新後) 在由於數據更改導致的虛擬DOM重新渲染和打補丁之後調用。調用時,組件DOM已經更新,所以可以執行依賴於DOM的操作。然而在大多數情況下,應該避免在此期間更改狀態,因爲這可能會導致更新無限循環。該鉤子在服務器端渲染期間不被調用。
- beforeDestroy (銷燬前) 在實例銷燬之前調用。實例仍然完全可用。
- destroyed (銷燬後) 在實例銷燬之後調用。調用後,所有的事件監聽器會被移除,所有的子實例也會被銷燬。該鉤子在服務器端渲染期間不被調用。
Vue的雙向數據綁定原理是什麼
vue.js 是採用數據劫持結合發佈者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,在數據變動時發佈消息給訂閱者,觸發相應的監聽回調。
具體實現步驟,感興趣的可以看看:
- 當把一個普通 Javascript 對象傳給 Vue 實例來作爲它的 data 選項時,Vue 將遍歷它的屬性,用 Object.defineProperty 都加上 setter和getter 這樣的話,給這個對象的某個值賦值,就會觸發setter,那麼就能監聽到了數據變化
- compile解析模板指令,將模板中的變量替換成數據,然後初始化渲染頁面視圖,並將每個指令對應的節點綁定更新函數,添加監聽數據的訂閱者,一旦數據有變動,收到通知,更新視圖
- Watcher訂閱者是Observer和Compile之間通信的橋樑,主要做的事情是:
1、在自身實例化時往屬性訂閱器(dep)裏面添加自己
2、自身必須有一個update()方法
3、待屬性變動dep.notice()通知時,能調用自身的update()方法,並觸發Compile中綁定的回調,則功成身退。
- MVVM作爲數據綁定的入口,整合Observer、Compile和Watcher三者,通過Observer來監聽自己的model數據變化,通過Compile來解析編譯模板指令,最終利用Watcher搭起Observer和Compile之間的通信橋樑,達到數據變化 -> 視圖更新;視圖交互變化(input) -> 數據model變更的雙向綁定效果
//vue實現數據雙向綁定的原理就是用Object.defineproperty()重新定義(set方法)對象設置屬性值和(get方法)獲取屬性值的操縱來實現的。
//Object.property()方法的解釋:Object.property(參數1,參數2,參數3) 返回值爲該對象obj
//其中參數1爲該對象(obj),參數2爲要定義或修改的對象的屬性名,參數3爲屬性描述符,屬性描述符是一個對象,主要有兩種形式:數據描述符和存取描述符。這兩種對象只能選擇一種使用,不能混合使用。而get和set屬於存取描述符對象的屬性。
//這個方法會直接在一個對象上定義一個新屬性或者修改對象上的現有屬性,並返回該對象。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="myapp">
<input v-model="message" /><br>
<span v-bind="message"></span>
</div>
<script type="text/javascript">
var model = {
message: ""
};
var models = myapp.querySelectorAll("[v-model=message]");
for (var i = 0; i < models.length; i++) {
models[i].onkeyup = function() {
model[this.getAttribute("v-model")] = this.value;
}
}
// 觀察者模式 / 鉤子函數
// defineProperty 來定義一個對象的某個屬性
Object.defineProperty(model, "message", {
set: function(newValue) {
var binds = myapp.querySelectorAll("[v-bind=message]");
for (var i = 0; i < binds.length; i++) {
binds[i].innerHTML = newValue;
};
var models = myapp.querySelectorAll("[v-model=message]");
for (var i = 0; i < models.length; i++) {
models[i].value = newValue;
};
this.value = newValue;
},
get: function() {
return this.value;
}
})
</script>
</body>
</html>
Proxy 相比於 defineProperty 的優勢
Object.defineProperty() 的問題主要有三個:
- 不能監聽數組的變化
- 必須遍歷對象的每個屬性
- 必須深層遍歷嵌套的對象
Proxy 在 ES2015 規範中被正式加入,它有以下幾個特點:
- 針對對象:針對整個對象,而不是對象的某個屬性,所以也就不需要對 keys 進行遍歷。這解決了上述 Object.defineProperty() 第二個問題
- 支持數組:Proxy 不需要對數組的方法進行重載,省去了衆多 hack,減少代碼量等於減少了維護成本,而且標準的就是最好的。
除了上述兩點之外,Proxy 還擁有以下優勢:
- Proxy 的第二個參數可以有 13 種攔截方法,這比起 Object.defineProperty() 要更加豐富
- Proxy 作爲新標準受到瀏覽器廠商的重點關注和性能優化,相比之下 Object.defineProperty() 是一個已有的老方法。
vue-router 有哪幾種導航守衛?
- 全局守衛
- 路由獨享守衛
- 路由組件內的守衛
1.全局守衛
vue-router全局有三個守衛:
- router.beforeEach 全局前置守衛 進入路由之前
- router.beforeResolve 全局解析守衛(2.5.0+) 在beforeRouteEnter調用之後調用
- router.afterEach 全局後置鉤子 進入路由之後
使用方法:
// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next) => {
next();
});
router.beforeResolve((to, from, next) => {
next();
});
router.afterEach((to, from) => {
console.log('afterEach 全局後置鉤子');
});
2.路由獨享守衛
如果你不想全局配置守衛的話,你可以爲某些路由單獨配置守衛:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// 參數用法什麼的都一樣,調用順序在全局前置守衛後面,所以不會被全局守衛覆蓋
// ...
}
}
]
})
3.路由組件內的守衛
- beforeRouteEnter 進入路由前, 在路由獨享守衛後調用 不能 獲取組件實例
this
,組件實例還沒被創建 - beforeRouteUpdate (2.2) 路由複用同一個組件時, 在當前路由改變,但是該組件被複用時調用 可以訪問組件實例
this
- beforeRouteLeave 離開當前路由時, 導航離開該組件的對應路由時調用,可以訪問組件實例
this
Vue的路由實現:hash模式 和 history模式
hash模式:
在瀏覽器中符號“#”,#以及#後面的字符稱之爲hash,用window.location.hash讀取;
特點:hash雖然在URL中,但不被包括在HTTP請求中;用來指導瀏覽器動作,對服務端安全無用,hash不會重加載頁面。
hash 模式下,僅 hash 符號之前的內容會被包含在請求中,如 http://www.xiaogangzai.com
,因此對於後端來說,即使沒有做到對路由的全覆蓋,也不會返回 404 錯誤。
history模式:
history採用HTML5的新特性;且提供了兩個新方法:pushState(),replaceState()可以對瀏覽器歷史記錄棧進行修改,以及popState事件的監聽到狀態變更。
history 模式下,前端的 URL 必須和實際向後端發起請求的 URL 一致,如 http://www.xxx.com/items/id
。後端如果缺少對 /items/id 的路由處理,將返回 404 錯誤。Vue-Router 官網裏如此描述:“不過這種模式要玩好,還需要後臺配置支持……所以呢,你要在服務端增加一個覆蓋所有情況的候選資源:如果 URL 匹配不到任何靜態資源,則應該返回同一個 index.html 頁面,這個頁面就是你 app 依賴的頁面。”
組件之間的傳值通信
組件之間通訊分爲三種: 父傳子、子傳父、兄弟組件之間的通訊
1. 父組件給子組件傳值
使用props
,父組件可以使用props
向子組件傳遞數據。
父組件vue模板father.vue:
<template>
<child :msg="message"></child>
</template>
<script>
import child from './child.vue';
export default {
components: {
child
},
data () {
return {
message: 'father message';
}
}
}
</script>
子組件vue模板child.vue:
<template>
<div>{{msg}}</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
}
}
</script>
2. 子組件向父組件通信
父組件向子組件傳遞事件方法,子組件通過$emit觸發事件,回調給父組件。
父組件vue模板father.vue:
<template>
<child @msgFunc="func"></child>
</template>
<script>
import child from './child.vue';
export default {
components: {
child
},
methods: {
func (msg) {
console.log(msg);
}
}
}
</script>
子組件vue模板child.vue:
<template>
<button @click="handleClick">點我</button>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
},
methods () {
handleClick () {
//........
this.$emit('msgFunc');
}
}
}
</script>
3. 非父子, 兄弟組件之間通信
vue2中廢棄了$dispatch和$broadcast廣播和分發事件的方法。父子組件中可以用props和$emit()。如何實現非父子組件間的通信,可以通過實例一個vue實例Bus作爲媒介,要相互通信的兄弟組件之中,都引入Bus,然後通過分別調用Bus事件觸發和監聽來實現通信和參數傳遞。
Bus.js可以是這樣:
import Vue from 'vue'
export default new Vue()
在需要通信的組件都引入Bus.js:
<template>
<button @click="toBus">子組件傳給兄弟組件</button>
</template>
<script>
import Bus from '../common/js/bus.js'
export default{
methods: {
toBus () {
Bus.$emit('on', '來自兄弟組件')
}
}
}
</script>
另一個組件也import Bus.js 在鉤子函數中監聽on事件
import Bus from '../common/js/bus.js'
export default {
data() {
return {
message: ''
}
},
mounted() {
Bus.$on('on', (msg) => {
this.message = msg
})
}
}
Vue與Angular以及React的區別?
版本在不斷更新,以下的區別有可能不是很正確。而且工作中只用到vue,對angular和react不怎麼熟
Vue與AngularJS的區別
- Angular採用TypeScript開發, 而Vue可以使用javascript也可以使用TypeScript
- AngularJS依賴對數據做髒檢查,所以Watcher越多越慢;Vue.js使用基於依賴追蹤的觀察並且使用異步隊列更新,所有的數據都是獨立觸發的。
- AngularJS社區完善, Vue的學習成本較小
Vue與React的區別
- vue組件分爲全局註冊和局部註冊,在react中都是通過import相應組件,然後模版中引用;
- props是可以動態變化的,子組件也實時更新,在react中官方建議props要像純函數那樣,輸入輸出一致對應,而且不太建議通過props來更改視圖;
- 子組件一般要顯示地調用props選項來聲明它期待獲得的數據。而在react中不必需,另兩者都有props校驗機制;
- 每個Vue實例都實現了事件接口,方便父子組件通信,小型項目中不需要引入狀態管理機制,而react必需自己實現;
- 使用插槽分發內容,使得可以混合父組件的內容與子組件自己的模板;
- 多了指令系統,讓模版可以實現更豐富的功能,而React只能使用JSX語法;
- Vue增加的語法糖computed和watch,而在React中需要自己寫一套邏輯來實現;
- react的思路是all in js,通過js來生成html,所以設計了jsx,還有通過js來操作css,社區的styled-component、jss等;而 vue是把html,css,js組合到一起,用各自的處理方式,vue有單文件組件,可以把html、css、js寫到一個文件中,html提供了模板引擎來處理。
- react做的事情很少,很多都交給社區去做,vue很多東西都是內置的,寫起來確實方便一些, 比如 redux的combineReducer就對應vuex的modules, 比如reselect就對應vuex的getter和vue組件的computed, vuex的mutation是直接改變的原始數據,而redux的reducer是返回一個全新的state,所以redux結合immutable來優化性能,vue不需要。
- react是整體的思路的就是函數式,所以推崇純組件,數據不可變,單向數據流,當然需要雙向的地方也可以做到,比如結合redux-form,組件的橫向拆分一般是通過高階組件。而vue是數據可變的,雙向綁定,聲明式的寫法,vue組件的橫向拆分很多情況下用mixin。
vuex是什麼?怎麼使用?哪種功能場景使用它?
- vuex 就是一個倉庫,倉庫裏放了很多對象。其中 state 就是數據源存放地,對應於一般 vue 對象裏面的 data
- state 裏面存放的數據是響應式的,vue 組件從 store 讀取數據,若是 store 中的數據發生改變,依賴這相數據的組件也會發生更新
- 它通過 mapState 把全局的 state 和 getters 映射到當前組件的 computed 計算屬性
vuex的使用藉助官方提供的一張圖來說明:
Vuex有5種屬性: 分別是 state、getter、mutation、action、module;
state
Vuex 使用單一狀態樹,即每個應用將僅僅包含一個store 實例,但單一狀態樹和模塊化並不衝突。存放的數據狀態,不可以直接修改裏面的數據。
mutations
mutations定義的方法動態修改Vuex 的 store 中的狀態或數據。
getters
類似vue的計算屬性,主要用來過濾一些數據。
action
actions可以理解爲通過將mutations裏面處裏數據的方法變成可異步的處理數據的方法,簡單的說就是異步操作數據。view 層通過 store.dispath 來分發 action。
vuex 一般用於中大型 web 單頁應用中對應用的狀態進行管理,對於一些組件間關係較爲簡單的小型應用,使用 vuex 的必要性不是很大,因爲完全可以用組件 prop 屬性或者事件來完成父子組件之間的通信,vuex 更多地用於解決跨組件通信以及作爲數據中心集中式存儲數據。
- 使用Vuex解決非父子組件之間通信問題
vuex 是通過將 state 作爲數據中心、各個組件共享 state 實現跨組件通信的,此時的數據完全獨立於組件,因此將組件間共享的數據置於 State 中能有效解決多層級組件嵌套的跨組件通信問題。
- vuex 作爲數據存儲中心
vuex 的 State 在單頁應用的開發中本身具有一個“數據庫”的作用,可以將組件中用到的數據存儲在 State 中,並在 Action 中封裝數據讀寫的邏輯。這時候存在一個問題,一般什麼樣的數據會放在 State 中呢? 目前主要有兩種數據會使用 vuex 進行管理:
1、組件之間全局共享的數據
2、通過後端異步請求的數據
比如做加入購物車、登錄狀態等都可以使用Vuex來管理數據狀態。
一般面試官問到這裏vue基本知識就差不多了, 如果更深入的研究就是和你探討關於vue的底層源碼;或者是具體在項目中遇到的問題,下面列舉幾個項目中可能遇到的問題:
- 開發時,改變數組或者對象的數據,但是頁面沒有更新如何解決?
- vue彈窗後如何禁止滾動條滾動?
- 如何在 vue 項目里正確地引用 jquery 和 jquery-ui的插件
公衆號技術棧路線
大家好,我是koala,公衆號「程序員成長指北」作者,這篇文章是【JS必知必會系列】的高階函數講解。目前在做一個node後端工程師進階路線,加入我們一起學習吧!
加入我們