對MVVM的理解?
MVVM是Model-View-ViewModel的縮寫,Model代表資料模型負責業務邏輯和資料封裝,View代表UI元件負責介面和顯示,ViewModel監聽模型資料的改變和控制檢視行為,處理使用者互動,簡單來說就是通過雙向資料繫結把View層和Model層連線起來。在MVVM架構下,View和Model沒有直接聯絡,而是通過ViewModel進行互動,我們只關注業務邏輯,不需要手動操作DOM,不需要關注View和Model的同步工作。
vue等單頁面應用及優缺點
vue核心是一個響應的資料繫結系統,mvvm,資料驅動,元件化,輕量,簡潔,高效,快速,模組友好。
缺點:不支援低版本瀏覽器,最低到IE9,不利於SEO的優化,首頁載入時間較長,不可以使用瀏覽器的導航按鈕需要自行實現前進後退。
什麼是RESTful API,然後怎麼使用?
RESTful是一個api的標準,無狀態請求。請求的路由地址是固定的。 restful:給使用者一個url,根據method不同在後端做不同處理:比如post 建立資料,get 獲取資料,put和patch修改資料,delete刪除資料
vue-cli中src目錄下每個檔案的用途?
1.vue-cli名字改為@vue/cli,所以全域性安裝了舊版的要通過npm install vue-cli -g
解除安裝。
安裝新版vue-cli
npm install -g @vue/cli
2.建立一個專案
vue create hello-world
3.assets資料夾是放靜態資源;components是放元件;router是定義路由相關的配置;view檢視;app.vue是一個應用主元件;main.js是入口檔案
v-model的原理
<input v-model="msg" />
複製程式碼
相當於
<input v-bind:value="msg" v-on:input="msg=$event.target.value" />
複製程式碼
v-if和v-show的區別
v-show只是在display: none和display: block之間切換,只需要切換CSS,DOM還是一直保留著,v-show在初始渲染時有更高的開銷,但是切換開銷很小,更適合頻繁切換的場景
v-if涉及到vue底層的編譯,當屬性初始為false時元件不會被渲染,直到條件為true,並且切換條件時會觸發銷燬/掛載元件,切換時開銷更高,更適合不經常切換的場景
route和router的區別
route是路由資訊物件,包括path,params,hash,query,fullPath,matched,name等路由資訊引數。
router是路由例項物件,包括了路由的跳轉方法,鉤子函式。
怎麼定義vue-router的動態路由?怎麼獲取傳過來的值
在router目錄下的index.js檔案,對path屬性加上:id,
使用router物件的params.id獲取
active-class是哪個元件的屬性?巢狀路由怎麼定義
active-class是vue-router模組中router-link元件的屬性
使用children定義巢狀路由
對keep-alive的瞭解
keep-alive是一個內建元件,可使被包含的元件保留狀態或避免重新渲染,有include(包含的元件快取)和exclude(排除的元件不快取)兩個屬性。
vue常用修飾符
.prevent: 提交時間不再過載頁面
.stop:阻止單擊事件冒泡
.self:當事件發生在該元素本身而不是子元素的時候觸發
.capture:事件偵聽,事件發生的時候會呼叫
元件中data什麼時候可以適用物件
元件複用時所有元件例項都會共享data,如果data是物件就會造成一個元件修改data以後會影響到其他所有元件,所以需要將data寫成函式,每次用到就呼叫一次函式獲得新的資料
當我們使用new Vue()的方式的時候,無論我們將data設定為物件還是函式都是可以的,因為new Vue()的方式是生成一個根元件,該元件不會複用,也就不存在共享data的情況
vue-cli如何新增自定義指令?
1.建立區域性指令
directives:{
// 指令名稱
dir1: {
inserted(el){
// 第一個引數是當前使用指令的DOM
el.style.width = '200px';
el.style.height = '200px';
el.style.background = '#000'
}
}
}
複製程式碼
2.全域性指令
Vue.directive('dir2', {
inserted(el){
console.log(el)
}
})
複製程式碼
3.指令的使用
<div v-dir1></div>
<div v-dir2></div>
複製程式碼
vue如何自定義一個過濾器?
<input type="text" v-model="msg" />
{{msg | capitalize}}
data(){
return{
msg: ''
}
},
filters: {
capitalize: function(value){
if(!value) return "";
value = value.toString();
return value.charAt(0).toUpperCase()+value.slice(1)
}
}
複製程式碼
computed和watch區別
computed是計算屬性,依賴其他屬性計算值,並且computed的值有快取,只有當計算值變化才會返回內容
watch監聽到值的變化就會執行回撥,在回撥中可以進行一些邏輯操作。
一般來說需要依賴別的屬性來動態獲得值的時候可以使用computed,對於監聽到值的變化需要做一些複雜業務邏輯的情況可以使用watch
另外computed和watch還支援物件的寫法
data: {
firstName: 'Chen',
lastName: 'Miao',
fullName: 'Chen Miao'
},
watch: {
firstName: function(val){
this.fullName = val+ ' '+this.lastName
},
lastName: function(val){
this.fullName = this.firstName+ ' '+val
}
},
computed: {
anoFullName: function(){
return this.firstName+' '+this.lastName
}
}
複製程式碼
extend能做什麼?
作用是擴充套件元件生成一個構造器,通常與$mount一起使用。
// 建立元件構造器
let Component = Vue.extend({
template: '<div>test</div>'
})
// 掛載到#app上
new Component().$mount('#app')
// 擴充套件已有元件
let SuperComponent = Vue.extend(Component)
new SuperComponent({
created(){
console.log(1)
}
})
new SuperComponent().$mount('#app')
複製程式碼
mixin和mixins區別
mixin用於全域性混入,會影響到每個元件例項,通常外掛都是這樣做初始化的。
Vue.mixin({
beforeCreate(){
// 會影響到每個元件的beforeCreate鉤子函式
}
})
複製程式碼
mixins最常用的擴充套件元件的方式。如果多個元件有相同的業務邏輯,就可將這些邏輯剝離出來,通過mixins混入程式碼。需要注意:mixins混入的鉤子函式會先於元件內的鉤子函式執行,並且在遇到同名選項的時候也會有選擇性的進行合併。
如何使用vue.nextTick()?
nextTick可以使我們在下次DOM更新迴圈結束之後執行延遲迴調,用於獲得更新後的DOM
data:function(){
return {
message: '沒有更新'
}
},
methods: {
updateMessage: function(){
this.message='更新完成'
console.log(this.$el.textContent) // '沒有更新'
this.$nextTick(function(){
console.log(this.$el.textContent)// '更新完成'
})
}
}
複製程式碼
transition 過渡的實現原理
<transition name="fade1">
<router-view></router-view>
</transition>
複製程式碼
類名介紹:
- v-enter:定義進入過渡的開始狀態
- v-enter-active:定義進入過渡生效時的狀態
- v-enter:定義進入過渡的結束狀態
- v-leave:定義離開過渡的開始狀態
- v-leave-active:定義離開過渡生效時的狀態
- v-leave-to:定義離開過渡的結束狀態
簡單說一下元件通訊
父子通訊
1.props和emit
父元件通過props傳遞資料給子元件,子元件通過emit傳送事件傳遞給父元件。
// 父元件
<div>
<child :data="child" @send="getFromChild"></child>
</div>
data(){
return{
toChild: '大兒子',
fromChild: ''
}
},
methods: {
getFromChild(val){
this.fromChild=val
}
}
// 子元件
<div @click="toParent">{{data}}</div>
props:[data],
methods: {
toParent(){
this.$emit('send', '給父親')
}
}
複製程式碼
2.v-model
v-model其實是props,emit的語法糖,v-model預設會解析成名為value的prop和名為input的事件。
// 父元件
<children v-model="msg"></children>
<p>{{msg}}</p>
data(){
return{
msg:'model'
}
}
// 子元件
<input :value="value" @input="toInput" />
props: ['value'],
methods: {
toInput(e){
this.$emit('input', e.target.value)
}
}
複製程式碼
3.在父元件使用$children
訪問子元件,在子元件中使用$parent
訪問父元件
// 父元件
<child />
data(){
return {
msg: '父元件資料'
}
},
methods: {
test(){
console.log('我是父元件的方法,被執行')
}
},
mounted(){
console.log(this.$children[0].child_msg); // 執行子元件方法
}
// 子元件
<div>{{$parent.msg}}</div>
data(){
return{
child_msg: '子元件資料'
}
},
mounted(){
// 子元件執行父元件方法
this.$parent.test();
}
複製程式碼
$listeners
和$attrs
$attrs
--繼承所有父元件屬性(除了prop傳遞的屬性)
inheritAttrs--預設值true,繼承所有父元件屬性(除props),為true會將attrs中的屬性當做html的data屬性渲染到dom根節點上
$listeners
--屬性,包含了作用在這個元件上所有監聽器,v-on="$listeners"將所有事件監聽器指向這個元件的某個特定子元素
// 父元件
<children :child1="child1" :child2="child2" @test1="onTest1"
@test2="onTest2"></children>
data(){
return {
child1: 'childOne',
child2: 'childTwo'
}
},
methods: {
onTest1(){
console.log('test1 running')
},
onTest2(){
console.log('test2 running')
}
}
// 子元件
<p>{{child1}}</p>
<child v-bind="$attrs" v-on="$listeners"></child>
props: ['child1'],
mounted(){
this.$emit('test1')
}
// 孫元件
<p>{{child2}</p>
<p>{{$attrs}}</p>
props: ['child2'],
mounted(){
this.$emit('test2')
}
複製程式碼
.sync方式
在vue1.x中是對prop進行雙向繫結,在vue2只允許單向資料流,也是一個語法糖
// 父元件
<child :count.sync="num" />
data(){
return {
num: 0
}
}
// 子元件
<div @click="handleAdd">add</div>
data(){
return {
counter: this.count
}
},
props: ["count"],
methods: {
handleAdd(){
this.$emit('update:count', ++this.counter)
}
}
複製程式碼
兄弟元件通訊
可以通過查詢父元件中的子元件實現,
this.$parent.$children
在$children
中可以通過元件name查詢到需要的元件例項,然後進行通訊
跨多層次元件通訊
可以使用provide/inject,雖然文件中不推薦直接使用在業務中。
假設有父元件A,然後有一個跨多層次的子元件B
// 父元件A
export default{
provide: {
data: 1
}
}
// 子元件B
export default{
inject: ['data'],
mounted(){
// 無論跨幾層都能獲取父元件的data屬性
console.log(this.data); // 1
}
}
複製程式碼
任意元件
可以用Vuex或Event Bus解決
eventBus的使用
1.新建一個bus.js檔案
import Vue from 'vue';
export default new Vue();
複製程式碼
2.使用它
<div @click="addCart">新增</div>
import Bus from 'bus.js';
export default{
methods: {
addCart(event){
Bus.$emit('getTarget', event.target)
}
}
}
// 另一元件
export default{
created(){
Bus.$on('getTarget', target =>{
console.log(target)
})
}
}
複製程式碼
vue-router路由
單頁面應用SPA的核心之一是:更新檢視而不重新請求頁面
普通路由
router.push('home')
router.push({path: 'home')
命名路由
const router=new VueRouter({
routes: [{
path: '/user',
name: 'user',
component: User
}]
})
複製程式碼
<router-link :to="{name: 'user'}"></router-link>
複製程式碼
router.push({
name: 'user'
})
複製程式碼
動態路由匹配
<div>{{$route.params.id}}</div>
複製程式碼
const router = new VueRouter({
routes: [{
path: '/user/:id',
component: User
}]
})
複製程式碼
router.push({name:'user',params: {id: 123})
巢狀路由
<h2>{{$route.params.id}}</h2>
<router-view></router-view>
複製程式碼
const router = new VueRouter({
routes: [{
path: '/user/:id',
children: [{
// 當/user/:id
path: '',
component: UserHome
},{
// 當/user/:id/profile
path: 'profile',
component: UserProfile
}]
}]
})
複製程式碼
引數的路由
router.push({path:'register',query:{plan:'private'})
程式設計式導航
router.push()
引數:
1.字串
router.push('home')
2.物件
router.push({path: 'home'})
3.命名的路由
router.push({ name: 'user', params: { userId: 123 } })
4.帶查詢引數,變成/register?plan=private
router.push({ path: 'register', query: { plan: 'private' } })
router.replace()
不會向history新增新紀錄,替換當前的history記錄
點選<router-link :to="..." replace>
等同於呼叫router.replace(...)
router.go(n)
在歷史記錄中向前或向後退多少步
// 前進一步,等同history.forward()
router.go(1)
// 後退一步
router.go(-1)
// 前進3步記錄
router.go(3)
複製程式碼
命名檢視
<router-view></router-view>
<router-view name="a"></router-view>
<router-view name="b"></router-view>
複製程式碼
const router = new VueRouter({
routes: [{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}]
})
複製程式碼
vue路由的鉤子函式
導航鉤子主要用來攔截導航,讓它完成跳轉或取消。
router.beforEach((to,from,next) => {})
鉤子是非同步執行解析的,每個鉤子方法接收三個引數:
to: Route即將進入的目標路由物件
from: Route當前導航正要離開的路由
next: Function,呼叫該方法來resolve這個鉤子,執行效果看引數
-
next():進行下一個鉤子
-
next(false):中斷當前的導航
-
next('/')或next({path: '/'}):跳轉到另一地址
請詳細說下你對vue生命週期的理解
生命週期共分為8個階段建立前/後,載入前/後,更新前/後,銷燬前/後
建立前/後:在beforeCreated階段,vue例項的掛載元素el和資料物件data都為undefined,還未初始化。created階段,vue例項的資料物件data有了,el還沒有。
載入前後:在beforeMount階段,vue例項的el和data都初始化了,但還是掛載之前為虛擬的dom節點,data.message還未替換。在mounted階段,vue例項掛載完成,data.message成功渲染
更新前/後:當data變化時,會觸發beforeUpdated和updated方法
銷燬前/後:beforeDestroy在例項銷燬前呼叫,例項仍然完全可用。destroy在例項銷燬之後呼叫,呼叫後所有事件監聽器會被移除,所有子例項也會被銷燬。
生命週期的作用?
生命週期中有多個事件鉤子,讓我們在控制整個Vue例項的過程中更容易形成好的邏輯。
vuex是什麼?怎麼使用?哪種功能場景使用它?
1.vuex是vue生態系統中的狀態管理,用來管理vue中的所有元件狀態。
2.使用
import Vue from vue;
import Vuex from vuex;
Vue.use(Vuex);
const store = new Vuex.store({
state: {
count: 0
},
getters: {
addTen: state => {
return state.count+10;
}
},
mutations: {
increment(state){
state.count++
}
}
})
複製程式碼
store.commit('increment');
console.log(this.$store.state.count);
複製程式碼
vuex中有
①state狀態,還有mapState對映狀態
computed: mapState({
count: state => state.count
})
複製程式碼
getter相當於store的計算屬性,主要用來過濾一些資料
getters: {
addTen: state => {
return state.count+10;
}
}
store.getters.addTen
複製程式碼
mapGetters是將store中的getter對映到區域性計算屬性中
computed: {
...mapGetters([
'addTen'
])
}
複製程式碼
③Mutation 改變vuex中store狀態唯一方法就是提交mutation,可傳入額外引數,是一個同步函式。 在元件中提交Mutation
import {mapMutations} from 'vuex'
export default{
methods: {
...mapMutations([
'increment'
]),
...mapMutaions({
add: 'increment'
})
}
}
複製程式碼
④Action 類似mutation,但是是非同步的,view層通過store.dispath分發action
actions:{
increment(context){
context.commit('increment')
}
}
複製程式碼
⑤module 當應用比較複雜,可以將store分割成模組
3.常用的場景有:單頁應用中,元件之間的狀態,音樂播放、登入狀態、加入購物車等等
談談你對vue的雙向資料繫結原理的理解
vue.js是採用資料劫持結合釋出者-訂閱者模式的方式,通過 Object.definePorperty() 來劫持各個屬性的setter,getter,在資料變動時釋出訊息給訂閱者,觸發相應的監聽回撥。
MVVM作為資料繫結的入口,整合Observer,Compile和Watcher三者,通過Observer來監聽自己的model資料變化,通過Compile來解析編譯模板指定(解析{{}}),最終利用Watcher搭起Observer和Compile之間的通訊橋樑,達到資料變化->檢視更新;檢視互動變化input->資料model變更的雙向繫結效果
實現簡單的雙向繫結
<input type="text" id="inp" />
<div id="show"></div>
<script type="text/javascript">
var inp = document.getElementById('inp');
var show = document.getElementById('show');
var obj = {};
function watch(obj, key, callback){
var val = obj[key];
Object.defineProperty(obj, key, {
get: function(){
return val;
},
set: function(newVal){
callback(newVal, this)
}
})
}
watch(obj, "input", function(val){
show.innerHTML = val
})
inp.addEventListener('keyup', function(e){
obj.input = e.target.value
})
</script>
複製程式碼
另一種方式實現vue的響應式原理
Proxy在目標物件之前架設一層“攔截”,外界對該物件的訪問都必須先通過這層攔截,因此提供一種機制,可以對外界的訪問進行過濾和改寫。
<input type="text" id="txt" />
<div id="show"></div>
<script type="text/javascript">
var inp = document.getElementById('txt');
var show = document.getElementById('show')
var obj = {}
var objKey = 'text'; // 將鍵儲存起來
// Object.defineProperty
Object.defineProperty(obj, objKey, {
get: function(){
return obj[objKey];
},
set: function(newVal){
show.innerHTML = newVal
}
})
inp.addEventListener('keyup', function(e){
obj[objKey] = e.target.value
})
// proxy的實現
const newObj = new Proxy(obj, {
get: function(target, key, receiver){
return Reflect.get(target, key, receiver);
},
set: function(target, key, value,receiver){
if(key === objKey){
show.innerHTML = value
}
}
})
inp.addEventListener('keyup',function(e){
newObj[objKey] = e.target.value;
})
複製程式碼
Object.defineProperty的缺點:
1.不能檢測到增加或刪除的屬性
2.陣列方面的變動,如根據索引改變元素,以及直接改變陣列長度時的變化,不能被檢測到。
聊聊你對Vue.js的template編譯的理解
先轉化成AST樹,再得到render函式返回VNode(Vue的虛擬DOM節點)
詳細步驟:首先通過compile編譯器把template編譯出AST語法樹,然後AST會經過generate(將AST語法樹轉化成render function字串的過程)得到render函式,render的返回值是VNode,VNode是Vue的虛擬DOM節點,裡面有標籤名,子節點,文字等