VUE 面試總結

meils發表於2019-04-02

總結一些vue中必知必會的知識點,讓我們在面試之前能夠胸有成竹一點~

一、Vue元件通訊

1、父元件向子元件通訊

方法一:props

<template>
    <child :msg="message"></child>
</template>
複製程式碼
<template>
    <div>{{msg}}</div>
</template>

<script>
export default {
    props: {
        msg: {
            type: String,
            required: true
        }
    }
}
</script>
複製程式碼

方法二:使用$children

使用$children可以在父元件中訪問子元件。比如呼叫子元件的方法,並傳入值等。

2、子元件向父元件通訊

方法一:使用vue事件

父元件向子元件傳遞事件方法,子元件通過$emit觸發事件,回撥給父元件。

<template>
    <child @msgFunc="func"></child>
</template>

<script>

import child from './child.vue';

export default {
    components: {
        child
    },
    methods: {
        func (msg) {
            console.log(msg); // ssss
        }
    }
}
</script>
複製程式碼
<template>
    <button @click="handleClick">點我</button>
</template>

<script>
export default {
    props: {
        msg: {
            type: String,
            required: true
        }
    },
    methods () {
        handleClick () {
            //........
            this.$emit('msgFunc', 'ssss');
        }
    }
}
</script>
複製程式碼

方法二:使用$parent

使用$parent可以訪問父元件的例項,當然也就可以訪問父元件的屬性和方法了。

3、非父子元件、兄弟元件之間的資料傳遞

非父子元件通訊,Vue官方推薦使用一個Vue例項作為中央事件匯流排

意思就是將一個公共狀態儲存在一個這些元件共用的一個父元件上,這樣就可以使用子元件通訊父元件的方式來間接的完成通訊了。

二、new Vue()之後都發生了什麼?

vue入口原始碼如下:

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

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)
// initMixin給Vue.prototype新增:
// _init函式,
stateMixin(Vue)
/*
stateMixin給Vue.prototype新增:
$data屬性,
$props屬性,
$set函式,
$delete函式,
$watch函式,
...
*/
eventsMixin(Vue)
/*
eventsMixin給Vue.prototype新增:
$on函式,
$once函式,
$off函式,
$emit函式,
$watch方法,
...
*/
lifecycleMixin(Vue)
/*
lifecycleMixin給Vue.prototype新增:
_update方法:私有方法,用於更新dom,其中呼叫_patch產生跟新後的dom,
$forceUpdate函式,
$destroy函式,
...
*/
renderMixin(Vue)
/*
renderMixin給Vue.prototype新增:
$nextTick函式,
_render函式,
...
*/
export default Vue
複製程式碼

vue原始碼載入的時候,也就是在new Vue()之前做了一些初始化工作。

new Vue的時候會執行一個_init方法。程式碼如下:

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
複製程式碼

從上面的程式碼我們看見_init很清淅的幹了幾件事, 合併相關配置, 初始化生命週期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等

三、元件data為什麼必須是函式?

因為元件可能被多處使用,但它們的data是私有的,所以每個元件都要return一個新的data物件,如果共享data,修改其中一個會影響其他元件。

四、vue 為什麼採用Virtual DOM?

  • 建立真實DOM的代價高:真實的 DOM 節點 node 實現的屬性很多,而 vnode 僅僅實現一些必要的屬性,相比起來,建立一個 vnode 的成本比較低。

  • 觸發多次瀏覽器重繪及迴流:使用 vnode ,相當於加了一個緩衝,讓一次資料變動所帶來的所有 node 變化,先在 vnode 中進行修改,然後 diff 之後對所有產生差異的節點集中一次對 DOM tree 進行修改,以減少瀏覽器的重繪及迴流。

  • 虛擬dom由於本質是一個js物件,因此天生具備跨平臺的能力,可以實現在不同平臺的準確顯示。

  • Virtual DOM 在效能上的收益並不是最主要的,更重要的是它使得 Vue 具備了現代框架應有的高階特性。

五、Vue建立的一個大致過程

VUE 面試總結

首先例項化,然後資料繫結,元件掛在、內容替換。

結合上例子分析Vue的生命週期:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>vue生命週期學習</title>
  <script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
</head>
<body>
  <div id="app">
    <h1>{{message}}</h1>
  </div>
</body>
<script>
  var vm = new Vue({
    el: '#app',
    data: {
      message: 'Vue的生命週期'
    },
    beforeCreate: function() {
      console.group('------beforeCreate建立前狀態------');
      console.log("%c%s", "color:red" , "el     : " + this.$el); //undefined
      console.log("%c%s", "color:red","data   : " + this.$data); //undefined 
      console.log("%c%s", "color:red","message: " + this.message) 
    },
    created: function() {
      console.group('------created建立完畢狀態------');
      console.log("%c%s", "color:red","el     : " + this.$el); //undefined
      console.log("%c%s", "color:red","data   : " + this.$data); //已被初始化 
      console.log("%c%s", "color:red","message: " + this.message); //已被初始化
    },
    beforeMount: function() {
      console.group('------beforeMount掛載前狀態------');
      console.log("%c%s", "color:red","el     : " + (this.$el)); //已被初始化
      console.log(this.$el);
      console.log("%c%s", "color:red","data   : " + this.$data); //已被初始化  
      console.log("%c%s", "color:red","message: " + this.message); //已被初始化  
    },
    mounted: function() {
      console.group('------mounted 掛載結束狀態------');
      console.log("%c%s", "color:red","el     : " + this.$el); //已被初始化
      console.log(this.$el);    
      console.log("%c%s", "color:red","data   : " + this.$data); //已被初始化
      console.log("%c%s", "color:red","message: " + this.message); //已被初始化 
    },
    beforeUpdate: function () {
      console.group('beforeUpdate 更新前狀態===============》');
      console.log("%c%s", "color:red","el     : " + this.$el);
      console.log(this.$el);   
      console.log("%c%s", "color:red","data   : " + this.$data); 
      console.log("%c%s", "color:red","message: " + this.message); 
    },
    updated: function () {
      console.group('updated 更新完成狀態===============》');
      console.log("%c%s", "color:red","el     : " + this.$el);
      console.log(this.$el); 
      console.log("%c%s", "color:red","data   : " + this.$data); 
      console.log("%c%s", "color:red","message: " + this.message); 
    },
    beforeDestroy: function () {
      console.group('beforeDestroy 銷燬前狀態===============》');
      console.log("%c%s", "color:red","el     : " + this.$el);
      console.log(this.$el);    
      console.log("%c%s", "color:red","data   : " + this.$data); 
      console.log("%c%s", "color:red","message: " + this.message); 
    },
    destroyed: function () {
      console.group('destroyed 銷燬完成狀態===============》');
      console.log("%c%s", "color:red","el     : " + this.$el);
      console.log(this.$el);  
      console.log("%c%s", "color:red","data   : " + this.$data); 
      console.log("%c%s", "color:red","message: " + this.message)
    }
  })
</script>
</html>
複製程式碼

VUE 面試總結

[1] 在beforeCreate和created鉤子函式之間的生命週期

VUE 面試總結
在這個階段開始初始化事件、並且對資料進行了檢測(劫持),這時候還沒有el。

[2] created鉤子函式和beforeMount間的生命週期

VUE 面試總結

  • 首先會判斷物件是否有el選項。如果有的話就繼續向下編譯,如果沒有el選項,則停止編譯,也就意味著停止了生命週期,直到在該vue例項上呼叫vm.$mount(el)。此時註釋掉程式碼中:el: '#app',

VUE 面試總結
如果我們在後面繼續呼叫vm.$mount(el),可以發現程式碼繼續向下執行了

  • 然後,我們往下看,template引數選項的有無對生命週期的影響。

(1).如果vue例項物件中有template引數選項,則將其作為模板編譯成render函式。 (2).如果沒有template選項,則將外部HTML作為模板編譯。 (3).可以看到template中的模板優先順序要高於outer HTML的優先順序。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>vue生命週期學習</title>
  <script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
</head>
<body>
  <div id="app">
    <!--html中修改的-->
    <h1>{{message + '這是在outer HTML中的'}}</h1>
  </div>
</body>
<script>
  var vm = new Vue({
    el: '#app',
    template: "<h1>{{message +'這是在template中的'}}</h1>", //在vue配置項中修改的
    data: {
      message: 'Vue的生命週期'
    }
</script>
</html>
複製程式碼

VUE 面試總結

那麼將vue物件中template的選項註釋掉後列印如下資訊:

VUE 面試總結

這下就可以想想什麼el的判斷要在template之前了~是因為vue需要通過el找到對應的outer template。

在vue物件中還有一個render函式,它是以createElement作為引數,然後做渲染操作,而且我們可以直接嵌入JSX.

new Vue({
    el: '#app',
    render: function(createElement) {
        return createElement('h1', 'this is createElement')
    }
})
複製程式碼

所以綜合排名優先順序: render函式選項 > template選項 > outer HTML.

總之在這個階段就是判斷是否有el,或者template。然後進行處理

[3] beforeMount和mounted 鉤子函式間的生命週期

VUE 面試總結

這段時間就是替換$el,將{{message}}轉為真正的內容。

[4] mounted

VUE 面試總結

在mounted之前h1中還是通過{{message}}進行佔位的,因為此時掛在到頁面上的還是JavaScript中的虛擬DOM形式。在mounted之後可以看到h1中的內容發生了變化。

[5] beforeUpdate鉤子函式和updated鉤子函式間的生命週期

當vue發現data中的資料發生了改變,會觸發對應元件的重新渲染,先後呼叫beforeUpdate和updated鉤子函式。

[6] beforeDestroy和destroyed鉤子函式間的生命週期

beforeDestroy鉤子函式在例項銷燬之前呼叫。在這一步,例項仍然完全可用。 destroyed鉤子函式在Vue 例項銷燬後呼叫。呼叫後,Vue 例項指示的所有東西都會解繫結,所有的事件監聽器會被移除,所有的子例項也會被銷燬。

VUE 面試總結

VUE 面試總結

六、自定義指令

VUE 面試總結

在程式碼的例項中

VUE 面試總結

指令鉤子函式會被傳入以下引數:

el:指令所繫結的元素,可以用來直接操作 DOM 。
binding:一個物件,包含以下屬性:

    - name:指令名,不包括 v- 字首。
    - value:指令的繫結值,例如:v-my-directive="1 + 1" 中,繫結值為 2。
    - oldValue:指令繫結的前一個值,僅在 update 和 componentUpdated 鉤子中可用。無論值是否改變都可用。
    - expression:字串形式的指令表示式。例如 v-my-directive="1 + 1" 中,表示式為 "1 + 1"。
    - arg:傳給指令的引數,可選。例如 v-my-directive:foo 中,引數為 "foo"。
    - modifiers:一個包含修飾符的物件。例如:v-my-directive.foo.bar 中,修飾符物件為 { foo: true, bar: true }。
    - vnode:Vue 編譯生成的虛擬節點。移步 VNode API 來了解更多詳情。
    - oldVnode:上一個虛擬節點,僅在 update 和 componentUpdated 鉤子中可用。
複製程式碼

七、Vue.js 外掛開發詳解

Vue.js 外掛開發詳解

八、元件style的scoped

問題:在元件中用js動態建立的dom,新增樣式不生效。
複製程式碼
 <template>
     <div class="test"></div>
</template>
<script>
    let a=document.querySelector('.test');
    let newDom=document.createElement("div"); // 建立dom
    newDom.setAttribute("class","testAdd" ); // 新增樣式
    a.appendChild(newDom); // 插入dom
</script>
<style scoped>
.test{
   background:blue;
    height:100px;
    width:100px;
}
.testAdd{
    background:red;
    height:100px;
    width:100px;
}
</style>
複製程式碼
  • 結果
// test生效   testAdd 不生效
<div data-v-1b971ada class="test"><div class="testAdd"></div></div>
.test[data-v-1b971ada]{ // 注意data-v-1b971ada
    background:blue;
    height:100px;
    width:100px;
}
複製程式碼

原因

<style> 標籤有 scoped 屬性時,它的 CSS 只作用於當前元件中的元素。 它會為元件中所有的標籤和class樣式新增一個scoped標識,就像上面結果中的data-v-1b971ada。 所以原因就很清楚了:因為動態新增的dom沒有scoped新增的標識,沒有跟testAdd的樣式匹配起來,導致樣式失效。

  • 解決辦法:

去掉scoped即可。

九、Vue 陣列/物件更新 檢視不更新

     data() { // data資料
        return {
          arr: [1,2,3],
          obj:{
              a: 1,
              b: 2
          }
        };
      },
   // 資料更新 陣列檢視不更新
    this.arr[0] = 'OBKoro1';
    this.arr.length = 1;
    console.log(arr);// ['OBKoro1'];
    // 資料更新 物件檢視不更新
    this.obj.c = 'OBKoro1';
    delete this.obj.a;
    console.log(obj);  // {b:2,c:'OBKoro1'}
複製程式碼

由於js的限制,Vue 不能檢測以上陣列的變動,以及物件的新增/刪除,很多人會因為像上面這樣操作,出現檢視沒有更新的問題。

解決方式:

  • 1、this.$set(你要改變的陣列/物件,你要改變的位置/key,你要改成什麼value)
this.$set(this.arr, 0, "OBKoro1"); // 改變陣列
this.$set(this.obj, "c", "OBKoro1"); // 改變物件
複製程式碼
  • 2、陣列原生方法觸發檢視更新:

Vue可以監測到陣列變化的,陣列原生方法:

splice()、 push()、pop()、shift()、unshift()、sort()、reverse()
複製程式碼

意思是使用這些方法不用我們再進行額外的操作,檢視自動進行更新。

十、Vue 過濾器的作用

過濾器,通常用於後臺管理系統,或者一些約定型別,過濾。Vue過濾器用法是很簡單,但是很多朋友可能都沒有用過。

 <!-- 在雙花括號中 -->
{{ message | filterTest }}
<!-- 在 `v-bind` 中 -->
<div :id="message | filterTest"></div>
複製程式碼
export default {    
     data() {
        return {
         message:1   
        }
     },
    filters: {  
        filterTest(value) {
            // value在這裡是message的值
            if(value===1){
                return '最後輸出這個值';
            }
        }
    }
}
複製程式碼

用法就是上面講的這樣,可以自己在元件中試一試就知道了,很簡單很好用的。

十一、列表渲染相關

1、v-for迴圈繫結model:

input在v-for中可以像如下這麼進行繫結,我敢打賭很多人不知道。

// 資料    
data() {
      return{
        // 這裡設定 key / value
        obj: {
          ob: "OB",
          koro1: "Koro1"
        },
        // model 儲存
        model: {
          ob: "預設ob",
          koro1: "預設koro1"
        }   
      }
  },
// html模板
<div v-for="(value,key) in obj">
   <input type="text" v-model="model[key]">
</div>
// input就跟資料繫結在一起了,那兩個預設資料也會在input中顯示
複製程式碼

2、v-if儘量不要與v-for在同一節點使用:

v-for 的優先順序比 v-if 更高,如果它們處於同一節點的話,那麼每一個迴圈都會執行一遍v-if。

如果你想根據迴圈中的每一項的資料來判斷是否渲染,那麼你這樣做是對的:

 <li v-for="todo in todos" v-if="todo.type===1">
  {{ todo }}
</li>
複製程式碼

如果你想要根據某些條件跳過迴圈,而又跟將要渲染的每一項資料沒有關係的話,你可以將v-if放在v-for的父節點:

// 陣列是否有資料 跟每個元素沒有關係
<ul v-if="todos.length">
  <li v-for="todo in todos">
    {{ todo }}
  </li>
</ul>
<p v-else>No todos left!</p>
複製程式碼

如上,正確使用v-for與v-if優先順序的關係,可以為你節省大量的效能。

十二、深度watch和watch立即觸發回撥

watch很多人都在用,但是這watch中的這兩個選項deep、immediate,或許不是很多人都知道,我猜。

  • deep

在選項引數中指定 deep: true,可以監聽物件中屬性的變化。

  • immediate

在選項引數中指定 immediate: true, 將立即以表示式的當前值觸發回撥,也就是立即觸發一次。

 watch: {
    obj: {
      handler(val, oldVal) {
        console.log('屬性發生變化觸發這個回撥',val, oldVal);
      },
      deep: true // 監聽這個物件中的每一個屬性變化
    },
    step: { // 屬性
      //watch
      handler(val, oldVal) {
        console.log("預設立即觸發一次", val, oldVal);
      },
      immediate: true // 預設立即觸發一次
    },
  },
複製程式碼

Vue.js中 watch 的高階用法

十三、這些情況下不要使用箭頭函式:

  • 不應該使用箭頭函式來定義一個生命週期方法
  • 不應該使用箭頭函式來定義 method 函式
  • 不應該使用箭頭函式來定義計算屬性函式
  • 不應該對 data 屬性使用箭頭函式
  • 不應該使用箭頭函式來定義 watcher 函式

箭頭函式繫結了父級作用域的上下文,this 將不會按照期望指向 Vue 例項。 也就是說,你不能使用this來訪問你元件中的data資料以及method方法了。 this將會指向undefined。

十四、路由懶載入寫法

// 我所採用的方法,個人感覺比較簡潔一些,少了一步引入賦值。
const router = new VueRouter({
  routes: [
    path: '/app',
    component: () => import('./app'),  // 引入元件
  ]
})
// Vue路由文件的寫法:
const app = () => import('./app.vue') // 引入元件
const router = new VueRouter({
  routes: [
    { path: '/app', component: app }
  ]
})
複製程式碼

如果我們的路由比較多的話,是不是要在路由上方引入十幾行元件?

第一種跟第二種方法相比就是把引入賦值的一步,直接寫在component上面,本質上是一樣的。兩種方式都可以的,只是第一種是懶載入的方式。

十五、專案啟動路由和404

export default new Router({
  routes: [
    {
      path: '/', // 專案啟動頁
      redirect:'/login'  // 重定向到下方宣告的路由 
    },
    {
      path: '*', // 404 頁面 
      component: () => import('./notFind') // 或者使用component也可以的
    },
  ]
})
複製程式碼