前端面經

rirmk發表於2022-12-22

1.vue優點?

  1. 輕量級框架:
    只關注檢視層,是一個構建資料的檢視集合,大小隻有幾十kb;
  2. 資料繫結:
    保留了angular的特點,在資料操作方面更為簡單;
  3. 元件化:
    保留了react的優點,實現了html的封裝和重用,在構建單頁面應用方面有著獨特的優勢;
    檢視,資料,結構分離:
    使資料的更改更為簡單,不需要進行邏輯程式碼的修改,只需要運算元據就能完成相關操作;
  4. 虛擬DOM:
    dom操作是非常耗費效能的, 不再使用原生的dom操作節點,極大解放dom操作,但具體操作的還是dom不過是換了另一種方式;
    執行速度更快:
    相比較與react而言,同樣是操作虛擬dom,就效能而言,vue存在很大的優勢。

2.vue父元件向子元件傳遞資料?

父子間通訊:父親提供資料透過屬性 props傳給兒子;兒子透過 $on 綁父親的事件,再透過 $emit 觸發自己的事件(釋出訂閱)
利用父子關係 $parent 、$children
父元件提供資料,子元件注入。 provide 、 inject ,外掛用得多。
ref 獲取元件例項,呼叫元件的屬性、方法
vuex 狀態管理實現通訊
祖孫節點可以使用: $attrs/$listeners

3.子元件向父元件傳遞事件?

  1. $emit方法
自定義子元件
<template>
  <div class="container">
     <p @click="clickAction">{{titleName}}</p>
    <div class="line"></div>
  </div>
</template>
export default {
 name: "HelloWorld",
 props:{
   titleName:{
     type:string,
     default:""
   }
 },
 methods: {
   clickAction(){
     this.$emit('clickChild',this.titleName);
   }
 }
};
父元件中呼叫子元件
<div class="message">
  <ActivityHead 
  :titleName="msgRight" 
  @clickChild="clickChild">
  </ActivityHead>
</div>
import ActivityHead from "./ActivityHead.vue";
export default {
 name: "HelloWorld",
 components: {
   ActivityHead
 },
 methods: {
     clickChild(msg){
      console.log(msg);
     }
 }
};

4.v-ifv-show的區別

共同點:

都能控制元素的顯示和隱藏;

不同點:

實現本質方法不同,v-show本質就是透過控制css中的display設定為none,控制隱藏,只會編譯一次;v-if是動態的向DOM樹內新增或者刪除DOM元素,若初始值為false,就不會編譯了。

而且v-if不停的銷燬和建立比較消耗效能。 總結:如果要頻繁切換某節點,使用v-show(切換開銷比較小,初始開銷較大)。

如果不需要頻繁切換某節點使用v-if(初始渲染開銷較小,切換開銷比較大)。

5.v-ifv-for的優先順序?

核心答案:

1、v-for優先於v-if被解析
2、如果同時出現,每次渲染都會先執行迴圈再判斷條件,無論如何迴圈都不可避免,浪費了效能
3、要避免出現這種情況,則在外層巢狀template,在這一層進行v-if判斷,然後在內部進行v-for迴圈
4、如果條件出現在迴圈內部,可透過計算屬性提前過濾掉那些不需要顯示的項

6.vue元件中data為什麼必須是一個函式?

如果data是一個函式的話,這樣每複用一次元件,就會返回一份新的data,類似於給每個元件例項建立一個私有的資料空間,讓各個元件例項維護各自的資料。

而單純的寫成物件形式,就使得所有元件例項共用了一份data,就會造成一個變了全都會變的結果。

所以說vue元件的data必須是函式。這都是因為js的特性帶來的,跟vue本身設計無關。

js本身的物件導向程式設計也是基於原型鏈和建構函式,應該會注意原型鏈上新增一般都是一個函式方法而不會去新增一個物件了。

7.VueXactionsmutations的區別?

actions

1、用於透過提交mutation改變資料

2、會預設將自身封裝為一個Promise

3、可以包含任意的非同步操作

mutations

1、透過提交commit改變資料

2、只是一個單純的函式

3、不要使用非同步操作,非同步操作會導致變數不能追蹤

2.如何在vuex中使用非同步修改?

在呼叫vuex中的方法action的時候,用promise實現非同步修改

const actions = {
    asyncLogin({ commit }, n){
        return new Promise(resolve => {
            setTimeout(() => {
                commit(types.UserLogin, n);
                resolve();
            },3000)
        })
    }
}

8.Vue有哪些元件間的通訊方式?

核心答案:

Vue 元件間通訊只要指以下 3 類通訊:
父子元件通訊、隔代元件通訊、兄弟元件通訊,下面我們分別介紹每種通訊方式且會說明此種方法可適用於哪類元件間通訊。

方法一

props/$emit

父元件A透過props的方式向子元件B傳遞,B to A 透過在 B 元件中 $emit, A 元件中 v-on 的方式實現。

1.父元件向子元件傳值

接下來我們透過一個例子,說明父元件如何向子元件傳遞值:在子元件Users.vue中如何獲取父元件App.vue中的資料

userList:["Henry","Bucky","Emily"]
//App.vue父元件
<template>
  <div id="app">
    <hook-users 
    :userList="userList"/>
    //前者自定義名稱便於子元件呼叫,後者要傳遞資料名
  </div>
</template>
<script>
import AppUsers from "./Components/AppUsers"
export default {
  name: 'App',
  data(){
    return{
      userList:["Henry","Bucky","Emily"]
    }
  },
  components:{
    "app-users":AppUsers
  }
}
//users子元件
<template>
  <div class="hello">
    <ul>
      //遍歷傳遞過來的值,然後呈現到頁面
      <li v-for="user in userList">{{user}}</li>
    </ul>
  </div>
</template>
<script>
export default {
  name: 'AppUsers',
  props:{
    userList:{ 
      type:Array,
      required:true
    }
  }
}
</script>
總結:

父元件透過props向下傳遞資料給子元件。注:元件中的資料共有三種形式:datapropscomputed

2.子元件向父元件傳值(透過事件形式)
// 子元件
<template>
  <header>
    <h1 @click="changeTitle">{{title}}</h1>//繫結一個點選事件
  </header>
</template>
<script>
export default {
  name: 'AppHeader',
  data() {
    return {
      title:"Vue.js Demo"
    }
  },
  methods:{
    changeTitle() {
      this.$emit("titleChanged","子向父元件傳值");
      //自定義事件  傳遞值“子向父元件傳值”
    }
  }
}
</script>

// 父元件
<template>
  <div id="app">
    <app-header v-on:titleChanged="updateTitle" ></app-header>
    //與子元件titleChanged自定義事件保持一致
    // updateTitle($event)接受傳遞過來的文字
    <h2>{{title}}</h2>
  </div>
</template>
<script>
import Header from "./components/Header"
export default {
  name: 'App',
  data(){
    return{
      title:"傳遞的是一個值"
    }
  },
  methods:{
    updateTitle(e){   //宣告這個函式
      this.title = e;
    }
  },
  components:{
   "app-header":Header,
  }
}
</script>
總結:

子元件透過events給父元件傳送訊息,實際上就是子元件把自己的資料傳送到父元件。

方法二、$emit/$on

這種方法透過一個空的Vue例項作為中央事件匯流排(事件中心),用它來觸發事件和監聽事件,巧妙而輕量地實現了任何元件間的通訊,包括父子、兄弟、跨級。當我們的專案比較大時,可以選擇更好的狀態管理解決方案vuex

1.具體實現方式:

var App=new Vue();
App.$emit(事件名,資料);
App.$on(事件名,data => {});

或者自己實現一個

class MyEventEmitter {
  constructor() {
    this.event = {};
  }
  // 監聽
  on(type, listener) {
    if (this.event[type]) {
      this.event[type].push(listener);
    } else {
      this.event[type] = [listener];
    }
  }
  //傳送監聽
  emit(type, ...rest) {
    if (this.event[type]) {
      this.event[type].map(fn => fn.apply(this, rest));
    }
  }
  //移除監聽器
  removeListener(type) {
    if (this.event[type]) {
      delete this.event[type];
      console.log(this.event);
    }
  }
  //移除所有的監聽器
  removeAllListener() {
    this.event = {};
  }
}


var MyEvent=new MyEventEmitter();
MyEvent.$emit(事件名,資料);
MyEvent.$on(事件名,data => {});
但是這種方式,記得在每次觸發監聽的時候,記得移除上一個監聽器

方法三、Vuex與localStorage

vuex 是 vue 的狀態管理器,儲存的資料是響應式的。但是並不會儲存起來,重新整理之後就回到了初始狀態,具體做法應該在vuex裡資料改變的時候把資料複製一份儲存到localStorage裡面,重新整理之後,如果localStorage裡有儲存的資料,取出來再替換store裡的state。

const jsonToString=(json)=>{
  return JSON.stringify(json)
}

const stringToJson=(keyName)=>{
  //暫不驗證資料格式
   return window.localStorage.getItem(keyName)?
   JSON.parse(window.localStorage.getItem(keyName))
   :{};
}
export default new Vuex.Store({
  state: {
    selectCity:stringToJson("selectCity")
  },
  mutations: {
    changeCity(state, selectCity) {
      state.selectCity = selectCity
      try {
        window.localStorage.setItem('selectCity',jsonToString(state.selectCity));
      // 資料改變的時候把資料複製一份儲存到localStorage裡面
      } catch (e) {}
    }
  }
})

方法四、$attrs/$listeners

如圖:

場景

有些變態需求:比如說A父元件裡面匯入了B元件,可是B元件裡面又匯入了C元件,現在需要A父元件傳值給C元件,或者是C元件需要傳值給父元件,這時候就需要用到$attrs和$listeners了。

$attrs

包含了父作用域中不作為 prop 被識別 (且獲取) 的特性繫結 (class 和 style 除外)。當一個元件沒有宣告任何 prop 時,這裡會包含所有父作用域的繫結 (class和 style 除外),並且可以透過 v-bind="$attrs" 傳入內部元件——在建立高階別的元件時非常有用。(父傳孫專用)

$listener

包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以透過 v-on="$listeners" 傳入內部元件——在建立更高層次的元件時非常有用。(孫傳父專用)

在父元件當中,最外層元件

<template>
    <div>
        <Child1 
        :child1Info="child1" 
        :child2Info="child2" 
        v-on:test1="onTest1" 
        v-on:test2="onTest2">
        </Child1>
    </div>
</template>
<script>
import Child1 from './child1';
export default {
    data() {
        return {
            child1:"hahha",
            child2:"asdsdasd"
        };
    },
    components: { Child1 },
    methods: {
        onTest1(msg) {
            console.log('test1 running...',msg);
        },
        onTest2(msg) {
            console.log('test2 running',msg);
        }
    }
};
</script>
···

//在子元件中

<template>

<div class="child-1">
    <p>在子元件當中:</p>
    <p>props-child1Info: {{child1Info}}</p>
    <p>$attrs: {{$attrs}}</p>
    <hr>
    <!-- Child2元件中能直接觸發test的原因在於 B元件呼叫C元件時 使用 v-on 繫結了$listeners 屬性 -->
    <!-- 透過v-bind 繫結$attrs屬性,Child2元件可以直接獲取到A元件中傳遞下來的props(除了child1元件中props宣告的) -->
    <Child2 v-bind="$attrs" v-on="$listeners"></Child2>
</div>

</template>
<script>
import Child2 from './child2';
export default {

props: ['child1Info'],
data() {
    return {};
},
components: { Child2 },
mounted() {
    this.$emit('test1','嘻嘻');
}

};
</script>


//在孫子元件當中:

<template>

<div class="child-2">
    <p>在最裡層元件當中child2:</p>
    <p>props-child2Info: {{child2Info}}</p>
    <p> $attrs 的值: {{$attrs}}</p>
    <hr>
</div>

</template>
<script>
export default {

props: ['child2Info'],
data() {
    return {};
},
mounted() {
    this.$emit('test2','哈哈');
}

};
</script>


#### 程式碼詳細說明:

![](https://yangyunhaiimagesoss.oss-cn-shanghai.aliyuncs.com/2009171558_1600329518020.png)

![](https://yangyunhaiimagesoss.oss-cn-shanghai.aliyuncs.com/2009171558_1600329528854.png)

![](https://yangyunhaiimagesoss.oss-cn-shanghai.aliyuncs.com/2009171600_1600329603285.png)


## 9.`Vue`中雙向資料繫結是如何實現的?

1.`vue.js` 則是採用資料劫持結合釋出者-訂閱者模式的方式。

2.透過`Object.defineProperty()`來劫持各個屬性的`setter`,`getter`.

3.在資料變動時釋出訊息給訂閱者,觸發相應的監聽回撥。我們先來看`Object.defineProperty()`這個方法:

var obj = {};
Object.defineProperty(obj, 'name', {

    get: function() {
        console.log('我被獲取了')
        return val;
    },
    set: function (newVal) {
        console.log('我被設定了')
    }

})
obj.name = 'fei';
//在給obj設定name屬性的時候,觸發了set這個方法
var val = obj.name;
//在得到obj的name屬性,會觸發get方法


## 10.單頁面應用和多頁面應用區別及優缺點?

單頁面應用(`SPA`),通俗一點說就是指只有一個主頁面的應用,瀏覽器一開始要載入所有必須的 `html`, `js`, `css`。所有的頁面內容都包含在這個所謂的主頁面中。但在寫的時候,還是會分開寫(頁面片段),然後在互動的時候由路由程式動態載入,單頁面的頁面跳轉,僅重新整理區域性資源。多應用於`pc`端。

多頁面(`MPA`),就是指一個應用中有多個頁面,頁面跳轉時是整頁重新整理

#### 單頁面的優點:

1,使用者體驗好,快,內容的改變不需要重新載入整個頁面,基於這一點spa對伺服器壓力較小

2,前後端分離

3,頁面效果會比較炫酷(比如切換頁面內容時的專場動畫)

#### 單頁面缺點:

1,不利於`seo`

2,導航不可用,如果一定要導航需要自行實現前進、後退。(由於是單頁面不能用瀏覽器的前進後退功能,所以需要自己建立堆疊管理)

3,初次載入時耗時多

4,頁面複雜度提高很多

## 11.`vue`中`v-if`和`v-for`優先順序?

`v-for`和`v-if`不應該一起使用,必要情況下應該替換成`computed`屬性。原因:`v-for`比`v-if`優先,如果每一次都需要遍歷整個陣列,將會影響速度,尤其是當之需要渲染很小一部分的時候。

<li
v-for="user in users"
v-if="user.isActive"
:key="user.id">
{{ user.name }}
</li>


如上情況,即使100個user中之需要使用一個資料,也會迴圈整個陣列。

omputed: {
activeUsers: function () {

return this.users.filter(function (user) {
  return user.isActive
})

}
}

<ul>
<li
v-for="user in activeUsers"
:key="user.id">
{{ user.name }}
</li>
</ul>


## 12.`Vue`事件的修飾符()?


1)``.stop`:等同於`JavaScript`中的`event.stopPropagation()``,防止事件冒泡

2)`.prevent`:等同於`JavaScript`中的`event.preventDefault()`,防止執行預設的行為(如果事件可取消,則取消該事件,而不停止事件的進一步傳播)

3)`.capture`:與事件冒泡的方向相反,事件捕獲由外到內

4)`.self`:只會觸發自己範圍內的事件,不包含子元素

5)`.once`:只會觸發一次

6)`.passive`:`passive`表示`listener`函式不會呼叫`preventDefault()`

`passive`主要用在移動端的`scroll`事件,來提高瀏覽器響應速度,提升使用者體驗。因為`passive=true`等於提前告訴了瀏覽器,`touchstart`和`touchmove`不會阻止預設事件,手剛開始觸控,瀏覽器就可以立刻給與響應;

否則,手觸控螢幕了,但要等待`touchstart`和`touchmove`的結果,多了這一步,響應時間就長了,使用者體驗也就差了。


## 13.`Vue`的兩個核心是什麼?

#### 1、資料驅動:
在vue中,資料的改變會驅動檢視的自動更新。傳統的做法是需要手動改變DOM來使得檢視更新,而vue只需要改變資料。

#### 2、元件
元件化開發,優點很多,可以很好的降低資料之間的耦合度。將常用的程式碼封裝成元件之後(vue元件封裝方法),就能高度的複用,提高程式碼的可重用性。一個頁面/模組可以由多個元件所組成。

### 14、`react`和`vue`的區別

#### 相同點

* 資料驅動頁面提供響應式的試圖元件
* 都有`virtual DOM`,元件化的開發透過`props`引數進行父子之間元件傳遞資料都實現了`webComponents`規範
* 資料流動單向都支援伺服器的渲染SSR
* 都有支援`native`的方法`react`有`React native vue`有`wexx`

#### 不同點

* 資料繫結`Vue`實現了雙向的資料繫結`react`資料流動是單向的
* 資料渲染大規模的資料渲染`react`更快
* 使用場景`React`配合`Redux`架構適合大規模多人協作複雜專案Vue適合小快的專案
* 開發風格`react`推薦做法`jsx` + `inline style`把`html`和`css`都寫在`js`了
* `vue`是採用`webpack` +`vue-loader`單檔案元件格式`html`, `js`, `css`同一個檔案



## 15.`vue3.0`有哪些新特性

#### vue3.0的設計目標
* 更小
* 更快
* 加強TypeScript支援
* 加強API設計一致性
* 提高自身可維護性
* 開放更多底層功能

具體可以從以下方面來理解

#### 1,壓縮包體積更小
當前最小化並被壓縮的 `Vue` 執行時大小約為 20kB(2.6.10 版為 22.8kB)。`Vue 3.0`捆綁包的大小大約會`減少一半`,即只有`10kB`!

#### 2,Object.defineProperty -> Proxy

`Object.defineProperty`是一個相對比較昂貴的操作,因為它直接操作物件的屬性,顆粒度比較小。將它替換為`es6`的`Proxy`,在目標物件之上架了一層攔截,代理的是物件而不是物件的屬性。這樣可以將原本對物件屬性的操作變為對整個物件的操作,顆粒度變大。

`javascript`引擎在解析的時候希望物件的結構越穩定越好,如果物件一直在變,可最佳化性降低,`proxy`不需要對原始物件做太多操作。

#### 3,Virtual DOM 重構

vdom的本質是一個抽象層,用`javascript`描述介面渲染成什麼樣子。`react`用`jsx`,沒辦法檢測出可以最佳化的動態程式碼,所以做時間分片,`vue`中足夠快的話可以不用時間分片。

#### 傳統vdom的效能瓶頸:

雖然 Vue 能夠保證觸發更新的元件最小化,但在單個元件內部依然需要遍歷該元件的整個 vdom 樹。
傳統 vdom 的效能跟模版大小正相關,跟動態節點的數量無關。在一些元件整個模版內只有少量動態節點的情況下,這些遍歷都是效能的浪費。
`JSX` 和手寫的 `render function` 是完全動態的,過度的靈活性導致執行時可以用於最佳化的資訊不足
那為什麼不直接拋棄vdom呢?

高階場景下手寫 `render function` 獲得更強的表達力

生成的程式碼更簡潔

#### 相容2.x

`vue`的特點是底層為`Virtual DOM`,上層包含有大量靜態資訊的模版。為了相容手寫 `render function`,最大化利用模版靜態資訊,`vue3.0`採用了動靜結合的解決方案,將`vdom`的操作顆粒度變小,每次觸發更新不再以元件為單位進行遍歷,主要更改如下

將模版基於動態節點指令切割為巢狀的區塊

每個區塊內部的節點結構是固定的

每個區塊只需要以一個 Array 追蹤自身包含的動態節點

vue3.0將 vdom 更新效能由與模版整體大小相關提升為與動態內容的數量相關


#### 4, 更多編譯時最佳化
Slot 預設編譯為函式:父子之間不存在強耦合,提升效能
Monomorphic vnode factory:引數一致化,給它children資訊,
Compiler-generated flags for vnode/children types

#### 5,選用Function_based API
為什麼撤銷 `Class API` ?

1,更好地支援`TypeScript`

`Props` 和其它需要注入到 `this` 的屬性導致型別宣告依然存在問題
`Decorators`提案的嚴重不穩定使得依賴它的方案具有重大風險
2,除了型別支援以外 `Class API` 並不帶來任何新的優勢

3,`vue`中的`UI`元件很少用到繼承,一般都是組合,可以用`Function-based API`


## 16.`Vue`效能最佳化方法

### 1)編碼階段
* 儘量減少data中的資料,data中的資料都會增加getter和setter,會收集對應的watcher;
* 如果需要使用v-for給每項元素繫結事件時使用事件代理;
* SPA 頁面採用keep-alive快取元件;
* 在更多的情況下,使用v-if替代v-show;
* key保證唯一;
* 使用路由懶載入、非同步元件;
* 防抖、節流;
* 第三方模組按需匯入;
* 長列表滾動到可視區域動態載入;
* 圖片懶載入;

### 2)使用者體驗:
* 骨架屏;
* PWA;
* 還可以使用快取(客戶端快取、服務端快取)最佳化、服務端開啟gzip壓縮等。

### 3)SEO最佳化
* 預渲染;
* 服務端渲染SSR;

### 4)打包最佳化
* 壓縮程式碼;
* Tree Shaking/Scope Hoisting;
* 使用cdn載入第三方模組;
* 多執行緒打包happypack;
* splitChunks抽離公共檔案;
* sourceMap最佳化;


## 17.`v-model`的原理

`v-model`本質就是一個語法糖,可以看成是value + input方法的語法糖。可以透過model屬性的prop和event屬性來進行自定義。

>原生的v-model,會根據標籤的不同生成不同的事件和屬性。

>v-model 在內部為不同的輸入元素使用不同的屬性並丟擲不同的事件:

1)text 和 textarea 元素使用 value 屬性和 input 事件;

2)checkbox 和 radio 使用 checked 屬性和 change 事件;

3)select 欄位將 value 作為 prop 並將 change 作為事件。

### 例子

model: {
prop: 'checked',
event: 'change'
}

如果想要更改 `checked` 這個 `prop` 可以在 `Vue` 的 `instance` 中用以下這行程式碼傳送 `change` 這個 `event`,並將目標的變動值傳給 `checked` 這個 `prop`。

this.$emit('change', $event.target.value);



## 18.`nextTick`的實現原理是什麼?

在下次 `DOM` 更新迴圈結束之後執行延遲迴調。`nextTick`主要使用了宏任務和微任務。根據執行環境分別嘗試採用

* Promise
* MutationObserver
* setImmediate
* 如果以上都不行則採用setTimeout

定義了一個非同步方法,多次呼叫`nextTick`會將方法存入佇列中,透過這個非同步方法清空當前佇列。


#### 19.談談`Computed`和`Watch`!

`Computed`本質是一個具備快取的`watcher`,依賴的屬性發生變化就會更新檢視。

適用於計算比較消耗效能的計算場景。當表示式過於複雜時,在模板中放入過多邏輯會讓模板難以維護,可以將複雜的邏輯放入計算屬性中處理。

`Watch`沒有快取性,更多的是觀察的作用,可以監聽某些資料執行回撥。

當我們需要深度監聽物件中的屬性時,可以開啟`deep:true`選項,這樣便會對物件中的每一項進行監聽。

這樣會帶來效能問題,最佳化的話可以使用字串形式監聽,如果沒有寫到元件中,不要忘記使用`unWatch`手動登出哦。

#### 20.說一下`Vue`的生命週期

`beforeCreate`是`new Vue()`之後觸發的第一個鉤子,在當前階段`data`、`methods`、`computed`以及`watch`上的資料和方法都不能被訪問。

`created`在例項建立完成後發生,當前階段已經完成了資料觀測,也就是可以使用資料,更改資料,在這裡更改資料不會觸發updated函式。可以做一些初始資料的獲取,在當前階段無法與Dom進行互動,如果非要想,可以透過`vm.$nextTick`來訪問`Dom`。

`beforeMount`發生在掛載之前,在這之前`template`模板已匯入渲染函式編譯。而當前階段虛擬`Dom`已經建立完成,即將開始渲染。在此時也可以對資料進行更改,不會觸發`updated`。
`mounted`在掛載完成後發生,在當前階段,真實的`Dom`掛載完畢,資料完成雙向繫結,可以訪問到`Dom`節點,使用`$refs`屬性對`Dom`進行操作。

`beforeUpdate`發生在更新之前,也就是響應式資料發生更新,虛擬`dom`重新渲染之前被觸發,你可以在當前階段進行更改資料,不會造成重渲染。

`updated`發生在更新完成之後,當前階段元件`Dom`已完成更新。要注意的是避免在此期間更改資料,因為這可能會導致無限迴圈的更新。
`beforeDestroy`發生在例項銷燬之前,在當前階段例項完全可以被使用,我們可以在這時進行善後收尾工作,比如清除計時器。

`destroyed`發生在例項銷燬之後,這個時候只剩下了`dom`空殼。元件已被拆解,資料繫結被卸除,監聽被移出,子例項也統統被銷燬。




## 21.`Vue`模版編譯原理知道嗎,能簡單說一下?

簡單說,`Vue`的編譯過程就是將`template`轉化為`render`函式的過程。會經歷以下階段:

* 生成AST樹
* 最佳化
* codegen

首先解析模版,生成`AST`語法樹(一種用`JavaScript`物件的形式來描述整個模板)。
使用大量的正規表示式對模板進行解析,遇到標籤、文字的時候都會執行對應的鉤子進行相關處理。

`Vue`的資料是響應式的,但其實模板中並不是所有的資料都是響應式的。有一些資料首次渲染後就不會再變化,對應的`DOM`也不會變化。那麼最佳化過程就是深度遍歷AST樹,按照相關條件對樹節點進行標記。這些被標記的節點(靜態節點)我們就可以跳過對它們的比對,對執行時的模板起到很大的最佳化作用。

編譯的最後一步是將最佳化後的`AST`樹轉換為可執行的程式碼。

## 22.`Vue`的優點及缺點?
首先Vue最核心的兩個特點,資料驅動和元件化。

### 響應式:
這也就是vue.js最大的優點,透過MVVM思想實現資料的雙向繫結,透過虛擬DOM讓我們可以用資料來操作DOM,而不必去操作真實的DOM,提升了效能。且讓開發者有更多的時間去思考業務邏輯。

### 元件化:
把一個單頁應用中的各個模組拆分到一個個元件當中,或者把一些公共的部分抽離出來做成一個可複用的元件。所以元件化帶來的好處就是,提高了開發效率,方便重複使用,使專案的可維護性更強。

### 虛擬DOM:
當然,這個不是vue中獨有的。

### 缺點:
基於物件配置檔案的寫法,也就是options寫法,開發時不利於對一個屬性的查詢。另外一些缺點,在小專案中感覺不太出什麼,vuex的魔法字串,對ts的支援。相容性上存在一些問題。

### 不利於seo:
導航不可用,如果一定要導航需要自行實現前進、後退。(由於是單頁面不能用瀏覽器的前進後退功能,所以需要自己建立堆疊管理)。
初次載入時耗時多。


## 23.`Vue`中`hash`模式和`history`模式的區別?

最明顯的是在顯示上,`hash`模式的`URL`中會夾雜著`#`號,而history沒有。

`Vue`底層對它們的實現方式不同。hash模式是依靠`onhashchange`事件(監聽`location.hash`的改變),而`history`模式是主要是依靠的`HTML5 history`中新增的兩個方法,`pushState()`可以改變url地址且不會傳送請求,`replaceState()`可以讀取歷史記錄棧,還可以對瀏覽器記錄進行修改。

當真正需要透過`URL`向後端傳送HTTP請求的時候,比如常見的使用者手動輸入`URL`後回車,或者是重新整理(重啟)瀏覽器,這時候`history`模式需要後端的支援。

因為`history`模式下,前端的`URL`必須和實際向後端傳送請求的URL一致,例如有一個`URL`是帶有路徑`path`的(例如`www.lindaidai.wang/blogs/id`),如果後端沒有對這個路徑做處理的話,就會返回`404`錯誤。所以需要後端增加一個覆蓋所有情況的候選資源,一般會配合前端給出的一個`404`頁面。

hash:

window.onhashchange = function(event){
// location.hash獲取到的是包括#號的,如"#heading-3"
// 所以可以擷取一下
let hash = location.hash.slice(1);
}


## 24.你的介面請求一般放在哪個生命週期中?

介面請求一般放在`mounted`中,但需要注意的是服務端渲染時不支援`mounted`,需要放到`created`中。
1. 如果不需要操作Dom元素或子元件, 那放到created或者mounted中都可以, 如果需要操作Dom元素, 則需要放到mounted中. 並且, 需要注意的是, 即便是放到created, Vue不會等待非同步返回的資料再去執行下一步. 從體驗的角度考慮, 放在mounted中更好.


#### 25.`Vue SSR`渲染原理?

### 優點:

#### 更利於SEO

不同爬蟲工作原理類似,只會爬取原始碼,不會執行網站的任何指令碼(Google除外,據說Googlebot可以執行javaScript)。使用了Vue或者其它MVVM框架之後,頁面大多數DOM元素都是在客戶端根據js動態生成,可供爬蟲抓取分析的內容大大減少。另外,瀏覽器爬蟲不會等待我們的資料完成之後再去抓取我們的頁面資料。服務端渲染返回給客戶端的是已經獲取了非同步資料並執行JavaScript指令碼的最終HTML,網路爬中就可以抓取到完整頁面的資訊。

#### 更利於首屏渲染
首屏的渲染是node傳送過來的html字串,並不依賴於js檔案了,這就會使使用者更快的看到頁面的內容。尤其是針對大型單頁應用,打包後檔案體積比較大,普通客戶端渲染載入所有所需檔案時間較長,首頁就會有一個很長的白屏等待時間。

### 場景:
互動少,資料多,例如新聞,部落格,論壇類等

### 原理:

相當於服務端前面加了一層url分配,可以假想為服務端的中間層,

當位址列url改變或者直接重新整理,其實直接從伺服器返回內容,是一個包含內容部分的html模板,是服務端渲染

而在互動過程中則是ajax處理操作,區域性重新整理,首先是在history模式下,透過history. pushState方式進而url改變,然後請求後臺資料服務,拿到真正的資料,做到區域性重新整理,這時候接收的是資料而不是模板

### 缺點

#### 服務端壓力較大
本來是透過客戶端完成渲染,現在統一到服務端node服務去做。尤其是高併發訪問的情況,會大量佔用服務端CPU資源;

#### 開發條件受限
在服務端渲染中,created和beforeCreate之外的生命週期鉤子不可用,因此專案引用的第三方的庫也不可用其它生命週期鉤子,這對引用庫的選擇產生了很大的限制;

#### 安全問題

因為做了node服務,因此安全方面也需要考慮DDOS攻擊和sql注入

#### 學習成本相對較高
除了對webpack、Vue要熟悉,還需要掌握node、Express相關技術。相對於客戶端渲染,專案構建、部署過程更加複雜。


#### 26.`new Vue()` 發生了什麼?
1)`new Vue()`是建立`Vue`例項,它內部執行了根例項的初始化過程。

2)具體包括以下操作:
* 選項合併
* `$children`,`$refs`,`$slots`,`$createElement`等例項屬性的方法初始化
* 自定義事件處理
* 資料響應式處理
* 生命週期鉤子呼叫 (`beforecreate created`)
* 可能的掛載

### 總結:

`new Vue()`建立了根例項並準備好資料和方法,未來執行掛載時,此過程還會遞迴的應用於它的子元件上,最終形成一個有緊密關係的元件例項樹。

#### 27.`Vue.use`是幹什麼的?原理是什麼?
>`vue.use` 是用來使用外掛的,我們可以在外掛中擴充套件全域性元件、指令、原型方法等。

1、檢查外掛是否註冊,若已註冊,則直接跳出;

2、處理入參,將第一個引數之後的引數歸集,並在首部塞入 `this` 上下文;

3、執行註冊方法,呼叫定義好的 `install` 方法,傳入處理的引數,若沒有 `install` 方法並且外掛本身為 `function` 則直接進行註冊;

1) 外掛不能重複的載入

`install` 方法的第一個引數是vue的建構函式,其他引數是Vue.set中除了第一個引數的其他引數; 程式碼:`args.unshift(this)`

2) 呼叫外掛的install 方法 程式碼:

typeof plugin.install === 'function'


3) 外掛本身是一個函式,直接讓函式執行。 程式碼:

plugin.apply(null, args)


4) 快取外掛。  程式碼:

installedPlugins.push(plugin)


#### 28.請說一下響應式資料的理解?

1) 物件內部透過`defineReactive`方法,使用 `Object.defineProperty()` 監聽資料屬性的 `get` 來進行資料依賴收集,再透過 `set` 來完成資料更新的派發;

2) 陣列則透過重寫陣列方法來實現的。擴充套件它的 7 個變更⽅法,透過監聽這些方法可以做到依賴收集和派發更新;

#### 對應原始碼

Object.defineProperty(obj, key, {

enumerable: true,
configurable: true,
get: function reactiveGetter () {
  const value = getter ? getter.call(obj) : val
  if (Dep.target) {
    dep.depend() // ** 收集依賴 ** /
    if (childOb) {
      childOb.dep.depend()
      if (Array.isArray(value)) {
        dependArray(value)
      }
    }
  }
  return value
},
set: function reactiveSetter (newVal) {
  const value = getter ? getter.call(obj) : val
  if (newVal === value || (newVal !== newVal && value !== value)) {
    return
  }
  if (process.env.NODE_ENV !== 'production' && customSetter) {
    customSetter()
  }
  val = newVal
  childOb = !shallow && observe(newVal)
  dep.notify() /**通知相關依賴進行更新**/
}

})



#### 29.`Vue`中是如何檢測陣列變化?

陣列考慮效能原因沒有用`defineProperty`對陣列的每一項進行攔截,而是選擇重寫陣列 方法以進行重寫。

當陣列呼叫到這 7 個方法的時候,執行 `ob.dep.notify()` 進行派發通知 `Watcher` 更新;

在Vue中修改陣列的索引和長度是無法監控到的。

需要透過以下7種變異方法修改陣列才會觸發陣列對應的`wacther`進行更新。

陣列中如果是物件資料型別也會進行遞迴劫持。

#### 說明:那如果想要改索引更新資料怎麼辦?

可以透過`Vue.set()`來進行處理 =》 核心內部用的是 `splice` 方法。


#### 原始碼

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) { // 重寫原型方法
const original = arrayProto[method] // 呼叫原陣列的方法
def(arrayMethods, method, function mutator (...args) {

const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
  case 'push':
  case 'unshift':
    inserted = args
    break
  case 'splice':
    inserted = args.slice(2)
    break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify() // 當呼叫陣列方法後,手動通知檢視更新
return result

})
})
this.observeArray(value) // 進行深度監控


#### 30.`Vue.set` 方法是如何實現的?

### 核心答案
為什麼`$set`可以觸發更新,我們給物件和陣列本身都增加了`dep`屬性,當給物件新增不存在的屬性則觸發物件依賴的`watcher`去更新,當修改陣列索引時我們呼叫陣列本身的`splice`方法去更新陣列。

### 補充答案
1) 如果是陣列,呼叫重寫的splice方法 (這樣可以更新檢視 )
程式碼:

target.splice(key, 1, val)

2) 如果不是響應式的也不需要將其定義成響應式屬性。
3) 如果是物件,將屬性定義成響應式的  

defineReactive(ob.value, key, val)


### 原始碼地址

export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&

(isUndef(target) || isPrimitive(target))

) {

warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)

}
if (Array.isArray(target) && isValidArrayIndex(key)) {

target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val

}
if (key in target && !(key in Object.prototype)) {

target[key] = val
return val

}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {

process.env.NODE_ENV !== 'production' && warn(
  'Avoid adding reactive properties to a Vue instance or its root $data ' +
  'at runtime - declare it upfront in the data option.'
)
return val

}
if (!ob) {

target[key] = val
return val

}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}


#### 31.`Vue`中模板編譯原理?
### 核心答案
將`template`轉換成`render`函式
### 補充說明
這裡要注意的是我們在開發時儘量不要使用template.

因為將template轉化成render方法需要在執行時進行編譯操作會有效能損耗,同時引用帶有complier包的vue體積也會變大.

預設.vue檔案中的 template處理是透過vue-loader 來進行處理的並不是透過執行時的編譯。
### 流程如下
1) 將 template 模板轉換成 ast 語法樹 - parserHTML

2) 對靜態語法做靜態標記 - markUp

3) 重新生成程式碼 - codeGen

### 原始碼地址

function baseCompile (
template: string,
options: CompilerOptions
) {
const ast = parse(template.trim(), options) // 1.將模板轉化成ast語法樹
if (options.optimize !== false) { // 2.最佳化樹

optimize(ast, options)

}
const code = generate(ast, options) // 3.生成樹
return {

ast,
render: code.render,
staticRenderFns: code.staticRenderFns

}
})
const ncname = [a-zA-Z_][\\-\\.0-9_a-zA-Z]*;
const qnameCapture = ((?:${ncname}\\:)?${ncname});
const startTagOpen = new RegExp(^<${qnameCapture}); // 標籤開頭的正則 捕獲的內容是標籤名
const endTag = new RegExp(^<\\/${qnameCapture}[^>]*>); // 匹配標籤結尾的 </div>
const attribute = /^\s(1+)(?:\s(=)\s(?:"(2)"+|'(3*)'+|(4+)))?/; // 匹配屬性的
const startTagClose = /^\s*(/?)>/; // 匹配標籤結束的 >
let root;
let currentParent;
let stack = []
function createASTElement(tagName,attrs){

return {
    tag:tagName,
    type:1,
    children:[],
    attrs,
    parent:null
}

}
function start(tagName,attrs){

let element = createASTElement(tagName,attrs);
if(!root){
    root = element;
}
currentParent = element;
stack.push(element);

}
function chars(text){

currentParent.children.push({
    type:3,
    text
})

}
function end(tagName){

const element = stack[stack.length-1];
stack.length --; 
currentParent = stack[stack.length-1];
if(currentParent){
    element.parent = currentParent;
    currentParent.children.push(element)
}

}
function parseHTML(html){

while(html){
    let textEnd = html.indexOf('<');
    if(textEnd == 0){
        const startTagMatch = parseStartTag();
        if(startTagMatch){
            start(startTagMatch.tagName,startTagMatch.attrs);
            continue;
        }
        const endTagMatch = html.match(endTag);
        if(endTagMatch){
            advance(endTagMatch[0].length);
            end(endTagMatch[1])
        }
    }
    let text;
    if(textEnd >=0 ){
        text = html.substring(0,textEnd)
    }
    if(text){
        advance(text.length);
        chars(text);
    }
}
function advance(n) {
    html = html.substring(n);
}
function parseStartTag(){
    const start = html.match(startTagOpen);
    if(start){
        const match = {
            tagName:start[1],
            attrs:[]
        }
        advance(start[0].length);
        let attr,end
        while(!(end = html.match(startTagClose)) && (attr=html.match(attribute))){
            advance(attr[0].length);
            match.attrs.push({name:attr[1],value:attr[3]})
        }
        if(end){
            advance(end[0].length);
            return match
        }
    }
}

}
// 生成語法樹
parseHTML(<div id="container"><p>hello<span>zf</span></p></div>);
function gen(node){

if(node.type == 1){
    return generate(node);
}else{
    return `_v(${JSON.stringify(node.text)})`
}

}
function genChildren(el){

const children = el.children;
if(el.children){
    return `[${children.map(c=>gen(c)).join(',')}]`
}else{
    return false;
}

}
function genProps(attrs){

let str = '';
for(let i = 0; i < attrs.length;i++){
    let attr = attrs[i];
    str+= `${attr.name}:${attr.value},`;
}
return `{attrs:{${str.slice(0,-1)}}}`

}
function generate(el){

let children = genChildren(el);
let code = `_c('${el.tag}'${
    el.attrs.length? `,${genProps(el.attrs)}`:''
}${
    children? `,${children}`:''
})`;
return code;

}
// 根據語法樹生成新的程式碼
let code = generate(root);
let render = with(this){return ${code}};
// 包裝成函式
let renderFn = new Function(render);
console.log(renderFn.toString());



#### 32.`Proxy` 與 `Object.defineProperty` 優劣對比?
### Proxy 的優勢如下:
1)`Proxy` 可以直接監聽物件而非屬性;

2)`Proxy` 可以直接監聽陣列的變化;

3)`Proxy` 有多達 13 種攔截方法,不限於 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具備的;

4)`Proxy` 返回的是一個新物件,我們可以只操作新的物件達到目的,而 Object.defineProperty 只能遍歷物件屬性直接修改;

5)Proxy 作為新標準將受到瀏覽器廠商重點持續的效能最佳化,也就是傳說中的新標準的效能紅利;

### Object.defineProperty 的優勢如下:
相容性好,支援 IE9,而 Proxy 的存在瀏覽器相容性問題,而且無法用 polyfill 磨平,因此 Vue 的作者才宣告需要等到下個大版本( 3.0 )才能用 Proxy 重寫。


#### 33.`Vue3.x`響應式資料原理?

`Vue3.x`改用`Proxy`替代`Object.defineProperty`。

因為`Proxy`可以直接監聽物件和陣列的變化,並且有多達13種攔截方法。並且作為新標準將受到瀏覽器廠商重點持續的效能最佳化。

`Proxy`只會代理物件的第一層,那麼`Vue3`又是怎樣處理這個問題的呢?
判斷當前`Reflect.get`的返回值是否為`Object`,如果是則再透過`reactive`方法做代理, 這樣就實現了深度觀測。

監測陣列的時候可能觸發多次`get/set`,那麼如何防止觸發多次呢?
我們可以判斷`key`是否為當前被代理物件`target`自身屬性,也可以判斷舊值與新值是否相等,只有滿足以上兩個條件之一時,才有可能執行`trigger`。

#### 34.`Vue`的生命週期方法有哪些?
>總共分為8個階段:建立前/後,載入前/後,更新前/後,銷燬前/後。

### 1、建立前/後:
1) `beforeCreate`階段:`vue`例項的掛載元素`el`和資料物件`data`都為`undefined`,還未初始化。

說明:在當前階段data、methods、computed以及watch上的資料和方法都不能被訪問。

2) created階段:vue例項的資料物件data有了,el還沒有。

說明:可以做一些初始資料的獲取,在當前階段無法與Dom進行互動,如果非要想,可以透過vm.$nextTick來訪問Dom。

### 2、載入前/後:
1) beforeMount階段:vue例項的$el和data都初始化了,但還是掛載之前為虛擬的dom節點。
說明:當前階段虛擬Dom已經建立完成,即將開始渲染。在此時也可以對資料進行更改,不會觸發updated。

2) mounted階段:vue例項掛載完成,data.message成功渲染。

說明:在當前階段,真實的Dom掛載完畢,資料完成雙向繫結,可以訪問到Dom節點,使用$refs屬性對Dom進行操作。

## 3、更新前/後:
1) beforeUpdate階段:響應式資料更新時呼叫,發生在虛擬DOM打補丁之前,適合在更新之前訪問現有的DOM,比如手動移除已新增的事件監聽器。

說明:可以在當前階段進行更改資料,不會造成重渲染。

2) updated階段:虛擬DOM重新渲染和打補丁之後呼叫,組成新的DOM已經更新,避免在這個鉤子函式中運算元據,防止死迴圈。

說明:當前階段元件Dom已完成更新。要注意的是避免在此期間更改資料,因為這可能會導致無限迴圈的更新。

## 4、銷燬前/後:
1) beforeDestroy階段:例項銷燬前呼叫,例項還可以用,this能獲取到例項,常用於銷燬定時器,解綁事件。

說明:在當前階段例項完全可以被使用,我們可以在這時進行善後收尾工作,比如清除計時器。

2) destroyed階段:例項銷燬後呼叫,呼叫後所有事件監聽器會被移除,所有的子例項都會被銷燬。

說明:當前階段元件已被拆解,資料繫結被卸除,監聽被移出,子例項也統統被銷燬。


### 補充說明
第一次頁面載入時會觸發:beforeCreate, created, beforeMount, mounted。

1) created 例項已經建立完成,因為它是最早觸發的原因可以進行一些資料,資源的請求。(伺服器渲染支援created方法)

2) mounted 例項已經掛載完成,可以進行一些DOM操作。(介面請求)


#### 35.生命週期鉤子是如何實現的?
#### 核心答案:
Vue的生命週期鉤子就是回撥函式而已,當建立元件例項的過程中會呼叫對應的鉤子方法。

#### 補充說明
內部主要是使用callHook方法來呼叫對應的方法。核心是一個釋出訂閱模式,將鉤子訂閱好(內部採用陣列的方式儲存),在對應的階段進行釋出。


#### 36.`Vue` 的父元件和子元件生命週期鉤子執行順序?

### 核心答案:
第一次頁面載入時會觸發 beforeCreate, created, beforeMount, mounted 這幾個鉤子。

### 渲染過程:
父元件掛載完成一定是等子元件都掛載完成後,才算是父元件掛載完,所以父元件的mounted在子元件mouted之後

* 父beforeCreate -> 
* 父created -> 
* 父beforeMount -> 
* 子beforeCreate -> 
* 子created -> 
* 子beforeMount -> 
* 子mounted -> 
* 父mounted

### 子元件更新過程:
影響到父元件:
* 父beforeUpdate -> 
* 子beforeUpdate->
* 子updated -> 
* 父updted

不影響父元件:
* 子beforeUpdate -> 
* 子updated

### 父元件更新過程:
影響到子元件:
* 父beforeUpdate -> 
* 子beforeUpdate->
* 子updated -> 
* 父updted

不影響子元件:
* 父beforeUpdate -> 
* 父updated

### 銷燬過程:
* 父beforeDestroy -> 
* 子beforeDestroy -> 
* 子destroyed -> 
* 父destroyed

>重要:父元件等待子元件完成後,才會執行自己對應完成的鉤子。


#### 37.元件中寫 `name`選項有哪些好處及作用?

### 核心答案:
1) 可以透過名字找到對應的元件 ( 遞迴元件 )
2) 可以透過name屬性實現快取功能 (keep-alive)
3) 可以透過name來識別元件 (跨級元件通訊時非常重要)


#### 38.`keep-alive`平時在哪裡使用?原理是?
### 核心答案:
keep-alive 主要是元件快取,採用的是LRU演算法。最近最久未使用法。

常用的兩個屬性include/exclude,允許元件有條件的進行快取。

兩個生命週期activated/deactivated,用來得知當前元件是否處於活躍狀態。

### 原始碼

abstract: true, // 抽象元件
props:{

include: patternTypes,  // 要快取的有哪些
exclude: patternTypes, // 要排除的有哪些
max: [String, Number] //最大快取數量 

}
if(cache[key]) { // 透過key 找到快取,獲取例項

vnode.componentInstance = cache[key].componentInstance
remove(keys, key) //將key刪除掉 
keys.push(key) // 放到末尾 

} else {

cache[key] = vnode // 沒有快取過 
keys.push(key) //儲存key
if(this.max && keys.length > parseInt(this.max)) { // 如果超過最大快取數 
// 刪除最早快取的 
pruneCacheEntry(cache, keys[0], keys, this._vnode)

}
}
vnode.data.keepAlive = true // 標記走了快取


#### 39.`Vue.mixin`的使用場景和原理?
### 核心答案:
Vue.mixin的作用就是抽離公共的業務邏輯,原理類似“物件的繼承”,當元件初始化時會呼叫 mergeOptions方法進行合併,採用策略模式針對不同的屬性進行合併,如果混入的資料和本身元件中的資料衝突,會採用“就近原則”以元件的資料為準。
### 補充回答:
`mixin`中有很多缺陷“命名衝突問題”,“依賴問題”,“資料來源問題”,這裡強調一下`mixin`的資料是不會被共享的。
### 注意事項
如果只是提取公用的資料或者通用的方法,並且這些資料或者方法,不需要元件間進行維護,就可以使用`mixins`。(類似於js中封裝的一些公用的方法)


#### 40.`mixins`和`vuex`的區別?
`vuex`公共狀態管理,在一個元件被引入後,如果該元件改變了vuex裡面的資料狀態,其他引入vuex資料的元件也會對應修改,所有的vue元件應用的都是同一份vuex資料。(在js中,有點類似於淺複製)

`vue`引入`mixins`資料,`mixins`資料或方法,在每一個元件中都是獨立的,互不干擾的,都屬於vue元件自身。(在js中,有點類似於深度複製)



#### 41.`mixins`和公共元件的區別?
通用的資料和方法,確實可以提出一個通用的元件,由父子元件傳參的形式進行分享公用。
### 公共元件
子元件透過props接收來自父元件(公共元件)的引數或者方法,但vue不建議,子元件直接修改props接收到的父元件的資料。需要在子元件的data中或者computed中定義一個欄位來接收。(有點麻煩)
公共元件最主要的作用還是複用相同的vue元件(有檢視,有方法,有狀態
)。
### mixins
如果只是提取公用的資料或者通用的方法,並且這些資料或者方法,不需要元件間進行維護,就可以使用mixins。(類似於js中封裝的一些公用的方法)


#### 42.`Vue-router`有幾種鉤子函式?具體是什麼及執行流程是怎樣的?
### 核心答案:
路由鉤子的執行流程,鉤子函式種類有:全域性守衛、路由守衛、元件守衛。

### 完整的導航解析流程
`1.`導航被觸發;

`2.`在失活的元件裡呼叫beforeRouteLeave守衛;

`3.`呼叫全域性beforeEach守衛;

`4.`在複用元件裡呼叫beforeRouteUpdate守衛;

`5.`呼叫路由配置裡的beforeEnter守衛;

`6.`解析非同步路由元件;

`7.`在被啟用的元件裡呼叫beforeRouteEnter守衛;

`8.`呼叫全域性beforeResolve守衛;

`9.`導航被確認;

`10.`呼叫全域性的afterEach鉤子;

`11.`DOM更新;

`12.`用建立好的例項呼叫beforeRouteEnter守衛中傳給next的回撥函式。




#### 43.`vue-router` 兩種模式的區別?

### 核心答案:
`vue-router` 有 3 種路由模式:`hash`、`history`、`abstract`。

1) `hash`模式:hash + hashChange

特點:hash雖然在URL中,但不被包括在HTTP請求中;用來指導瀏覽器動作,對服務端安全無用,hash不會重載入頁面。透過監聽 hash(#)的變化來執行js程式碼 從而實現 頁面的改變。 

### 核心程式碼:

window.addEventListener(‘hashchange‘,function(){

self.urlChange()

})

2) `history`模式:historyApi + popState

`HTML5`推出的history API,由pushState()記錄操作歷史,監聽popstate事件來監聽到狀態變更;
因為 只要重新整理 這個url(www.ff.ff/jjkj/fdfd/fdf/fd)就會請求伺服器,然而伺服器上根本沒有這個資源,所以就會報404,解決方案就 配置一下伺服器端。
#### 說明:

1)`hash`: 使用 URL hash 值來作路由。支援所有瀏覽器,包括不支援 HTML5 History Api 的瀏覽器;

2)`history` : 依賴 HTML5 History API 和伺服器配置。具體可以檢視 HTML5 History 模式;

3)`abstract` : 支援所有 JavaScript 執行環境,如 Node.js 伺服器端。如果發現沒有瀏覽器的 API,路由會自動強制進入這個模式.


#### 44.`Vue` 為什麼需要虛擬DOM?  虛擬`DOM`的優劣如何?

## 核心答案:
`Virtual DOM` 就是用js物件來描述真實DOM,是對真實DOM的抽象,由於直接操作DOM效能低但是js層的操作效率高,可以將DOM操作轉化成物件操作,最終透過diff演算法比對差異進行更新DOM (減少了對真實DOM的操作)。虛擬DOM不依賴真實平臺環境從而也可以實現跨平臺。

## 補充回答:
`虛擬DOM`的實現就是普通物件包含tag、data、hildren等屬性對真實節點的描述。(本質上就是在JS和DOM之間的一個快取)

`Vue2`的 Virtual DOM 借鑑了開源庫snabbdom的實現。

`VirtualDOM`對映到真實DOM要經歷VNode的create、diff、patch等階段。


#### 45.`Vue`中`key`的作用和工作原理,說說你對它的理解

### 核心答案:
#### 例如:

v-for="(item, index) in tableList" :key="index"


key的作用主要是為了高效的更新虛擬DOM,其原理是vue在patch過程中透過key可以精準判斷兩個節點是否是同一個,從而避免頻繁更新不同元素,使得整個patch過程更加高效,減少DOM操作量,提高效能。

#### 補充回答:
1) 若不設定key還可能在列表更新時引發一些隱蔽的bug

2) vue中在使用相同標籤名元素的過渡切換時,也會使用到key屬性,其目的也是為了讓vue可以區分它們,否則vue只會替換其內部屬性而不會觸發過渡效果。



#### 46.說說`$nextTick`的使用?

1) `nextTick`的回撥是在下次`DOM`更新迴圈結束之後執行的延遲迴調。

2)  在修改資料之後立即使用這個方法,獲取更新後的`DOM`。

3) `nextTick`主要使用了宏任務和微任務。

#### 47.`computed` 和 `watch` 的區別和運用的場景?

### 核心答案:
`computed`: 

計算屬性。依賴其它屬性值,並且 computed 的值有快取,只有它依賴的屬性值發生改變,下一次獲取 computed 的值時才會重新計算 computed 的值;

`watch`: 

監聽資料的變化。更多的是「觀察」的作用,類似於某些資料的監聽回撥 ,每當監聽的資料變化時都會執行回撥進行後續操作;

### 運用場景:
1)當我們需要進行數值計算,並且依賴於其它資料時,應該使用 computed,因為可以利用 computed 的快取特性,避免每次獲取值時,都要重新計算;

2)當我們需要在資料變化時執行非同步或開銷較大的操作時,應該使用 watch,使用 watch 選項允許我們執行非同步操作 ( 訪問一個 API ),限制我們執行該操作的頻率,並在我們得到最終結果前,設定中間狀態。

這些都是計算屬性無法做到的。

#### 48.如何理解自定義指令?

#### 核心答案:
>指令的實現原理,可以從編譯原理 =>程式碼生成=> 指令鉤子實現進行概述

1)在生成 ast 語法樹時,遇到指令會給當前元素新增directives屬性

2)透過 genDirectives 生成指令程式碼

3)在patch前將指令的鉤子提取到 cbs中,在patch過程中呼叫對應的鉤子。

4)當執行指令對應鉤子函式時,呼叫對應指令定義的方法


#### 49.`$router`和`$route`的區別?

`this.$router`與`this.$route` 兩者相差 一個 `r` 但是差距蠻大的。

`Vue Router`是`Vue.js`的路由管理器,在Vue例項內部,可以透過`$router`訪問路由例項

透過`$route`可以訪問當前啟用的路由的狀態資訊,包含了當前`URL`解析得到的資訊,還有URL匹配到的路由記錄,可以將`$router`理解為一個容器去管理了一組`$route`,

而`$route`是進行了當前URL和元件的對映。


#### 50.`$router`物件有哪些屬性?
* `$router.app`: 配置了router的Vue根例項。

* `$router.mode`: 路由使用的模式。

* `$router.currentRoute`: 當前路由對應的路由資訊物件。

#### 51.`$router`物件有哪些方法?
* $router.beforeEach(to, from, next)
* $router.beforeResolve(to, from, next)
* $router.afterEach(to, from)
* $router.push(location[, onComplete[, onAbort]])
* $router.replace(location[, onComplete[, onAbort]])
* $router.go(n)
* $router.back()$router.back
* $history.forward()
* $router.getMatchedComponents([location])
* $router.resolve(location[, current[, append]])
* $router.addRoutes(route)
* $router.onReady(callback[, errorCallback])
* $router.onError(callback)

### 52.`$route`物件有哪些屬性?
* `$route.path`: 
返回字串,對應當前路由的路徑,總是解析為絕對路徑。

* `$route.params`: 
返回一個key-value物件,包含了動態片段和全匹配片段,如果沒有路由引數,就是一個空物件。

* `$route.query`:
 返回一個key-value物件,表示URL查詢引數。

* `$route.hash`: 
返回當前路由的帶#的hash值,如果沒有hash值,則為空字串。

* `$route.fullPath`: 
返回完成解析後的URL,包含查詢引數和hash的完整路徑。

* `$route.matched`: 
返回一個陣列,包含當前路由的所有巢狀路徑片段的路由記錄,路由記錄就是routes配置陣列中的物件副本。

* `$route.name`: 
如果存在當前路由名稱則返回當前路由的名稱。

* `$route.redirectedFrom`: 
如果存在重定向,即為重定向來源的路由的名字。


#### 53.對`MVC (react)` `MVVM(vue)`的瞭解?
### 標籤  騰訊  阿里  西門子

### 什麼是MVC
* `M(modal)`:是應用程式中處理資料邏輯的部分。
* `V (view)`  :是應用程式中資料顯示的部分。
* `C(controller)`:是應用程式中處理使用者互動的地方

### 什麼是MVVM
* M(modal):模型,定義資料結構。
* C(controller):實現業務邏輯,資料的增刪改查。在MVVM模式中一般把C層算在M層中,(只有在理想的雙向繫結模式下,Controller 才會完全的消失。這種理想狀態一般不存在)。
* VM(viewModal):檢視View的模型、對映和顯示邏輯(如if for等,非業務邏輯),另外繫結器也在此層。ViewModel是基於檢視開發的一套模型,如果你的應用是給盲人用的,那麼也可以開發一套基於Audio的模型AudioModel。
* V(view) :將ViewModel透過特定的GUI展示出來,並在GUI控制元件上繫結檢視互動事件,V(iew)一般由MVVM框架自動生成在瀏覽器中。


### 54.`Vue.js` 如何讓`CSS`只在當前元件中起作⽤?

將當前元件的 `<style>` 修改為 `<style scoped>`

#### 55.`Vue` 中的`diff`原理?

### 核心答案:
vue的diff演算法是平級比較,不考慮跨級比較的情況。內部採用深度遞迴的方式 + 雙指標的方式進行比較。

### 補充回答:
1) 先比較是否是相同節點

2) 相同節點比較屬性,並複用老節點

3) 比較兒子節點,考慮老節點和新節點兒子的情況

4) 最佳化比較:頭頭、尾尾、頭尾、尾頭

5) 比對查詢進行復用

### Vue2 與 Vue3.x 的diff演算法:
Vue2的核心Diff演算法採用了雙端比較的演算法,同時從新舊children的兩端開始進行比較,藉助key值找到可複用的節點,再進行相關操作。

Vue3.x借鑑了ivi演算法和 inferno演算法,該演算法中還運用了動態規劃的思想求解最長遞迴子序列。(實際的實現可以結合Vue3.x原始碼看。)

### 56.Vuex的5個核心屬性是什麼?
分別是 state、getters、mutations、actions、modules 。

### 57.在Vuex中使用mutationvuex要注意什麼?
mutation 必須是同步函式

### 58.Vuex中action和mutation有什麼相同點?
第二引數都可以接收外部提交時傳來的引數。 

this.$store.dispatch('ACTION_NAME',data)

this.$store.commit('SET_NUMBER',10)

在元件中多次提交同一個action,怎麼寫使用更方便。

### 59.那為什麼new Vue裡data可以是一個物件?

new Vue({
el: '#app',
router,
template: '<App/>',
components: {App}
})


  1. \s"'<>/=
  2. "
  3. '
  4. \s"'=<>`

相關文章