前端知識體系(2)-vue篇

1. 簡單說下vue

  • vue是漸變式框架,根據本身的需求添加功能
  • vue數據驅動採用mvvm模式,m是數據層,v是視圖層,vm是調度者
  • SPA單頁面應用,只有一個頁面,加載速率快
  • 組件化,複用性強

那麼,它有什麼缺點?javascript

  1. vue2底層基於Object.defineProperty實現響應式,這個api自己不支持IE8及如下瀏覽器
  2. csr的先天不足,首屏性能問題(白屏)
  3. 因爲百度等搜索引擎爬蟲沒法爬取js中的內容,故spa先天就對seo優化心有餘力不足(谷歌的puppeteer就挺牛逼的,實現預渲染底層也是用到了這個工具)

2. vuehereact的區別

首先你得說說相同點,兩個都是MVVM框架,數據驅動視圖,無爭議。若是說不一樣,那可能分爲如下這麼幾點:css

  1. vue是完整一套由官方維護的框架,核心庫主要有由尤雨溪大神獨自維護,而react是不要臉的書維護(不少庫由社區維護),曾經一段時間不少人質疑vue的後續維護性,彷佛這並非問題。
  2. vue上手簡單,進階式框架,白話說你能夠學一點,就能夠在你項目中去用一點,你不必定須要一次性學習整個vue才能去使用它,而react,恐怕若是你這樣會面對項目一籌莫展。
  3. 語法上vue並不限制你必須es6+徹底js形式編寫頁面,能夠視圖和js邏輯儘量分離,減小不少人看不慣react-jsx的噁心嵌套,畢竟都是做爲前端開發者,仍是更習慣於html乾淨。
  4. 不少人說react適合大型項目,適合什麼什麼,vue輕量級,適合移動端中小型項目,其實我想說,說這話的人是內心根本沒點逼數,vue徹底能夠應對複雜的大型應用,甚至於說若是你react學的不是很好,寫出來的東西或根本不如vue寫的,畢竟vue跟着官方文檔擼就行,自有人幫你規範,而react比較懶散自由,能夠自由發揮
  5. vue在國內人氣明顯賽過react,這很大程度上得益於它的不少語法包括編程思惟更符合國人思想

1. 什麼是mvvm

MVVM的核心是數據驅動即ViewModel,ViewModel是View和Model的關係映射html

MVVM本質就是基於操做數據來操做視圖進而操做DOM,藉助於MVVM無需直接操做DOM,開發者只需編寫ViewModel中有業務,使得View徹底實現自動化前端

2. 什麼是 SPA 單頁面,它的優缺點分別是什麼

SPA( single-page application )即一個web項目只有一個頁面(即一個HTML文件,HTML 內容的變換是利用路由機制實現的。vue

僅在 Web 頁面初始化加載相應的 HTML、JavaScript 和 CSS。一旦頁面加載完成,SPA 不會由於用戶的操做而進行頁面的從新加載或跳轉;取而代之的是利用路由機制實現 HTML 內容的變換,UI 與用戶的交互,避免頁面的從新加載。java

優勢:node

  • 用戶體驗好、快,內容的改變不須要從新加載整個頁面,避免了沒必要要的跳轉和重複渲染;
  • 基於上面一點,SPA 相對對服務器壓力小
  • 先後端職責分離架構清晰,前端進行交互邏輯,後端負責數據處理;

缺點react

  • 初次加載耗時多:爲實現單頁 Web 應用功能及顯示效果,須要在加載頁面的時候將 JavaScript、CSS 統一加載,部分頁面按需加載;

前進後退路由管理:因爲單頁應用在一個頁面中顯示全部的內容,因此不能使用瀏覽器的前進後退功能,全部的頁面切換須要本身創建堆棧管理;webpack

SEO 難度較大:因爲全部的內容都在一個頁面中動態替換顯示,因此在 SEO 上其有着自然的弱勢。ios

3. 生命週期

3-1 基本概念

什麼是vue生命週期? Vue 實例建立銷燬過程,就是生命週期。

注意:瀏覽器有8個鉤子,可是node中作服務端渲染的時候只有beforeCreatecreated

  • beforeCreatenew Vue()以後觸發的第一個鉤子,在當前階段data、methods、computed以及watch上的數據和方法都不能被訪問。 能夠作頁面攔截。當進一個路由的時候咱們能夠判斷是否有權限進去,是否安全進去,攜帶參數是否完整,參數是否安全。使用這個鉤子好函數的時候就避免了讓頁面去判斷,省掉了建立一個組建Vue實例。
  • created 發生在實例建立完成後,當前階段已經完成了數據觀測,也就是能夠使用數據,更改數據,在這裏更改數據不會觸發updated函數。能夠作一些初始數據的獲取,在當前階段沒法Dom進行交互(由於Dom尚未建立),若是非要想,能夠經過vm.$nextTick來訪問Dom。
  • beforeMount發生在掛載以前,在這以前template模板已導入渲染函數編譯。而當前階段虛擬Dom已經建立完成,即將開始渲染。在此時也能夠對數據進行更改,不會觸發updated。
  • mounted發生在掛載完成後,在當前階段,真實Dom掛載完畢,數據完成雙向綁定,能夠訪問到Dom節點,使用$refs屬性對Dom進行操做。
  • beforeUpdate發生在更新以前,也就是響應式數據發生更新,虛擬dom從新渲染以前被觸發,你能夠在當前階段進行更改數據,不會形成重渲染。
  • updated發生在更新完成以後,當前階段組件Dom已完成更新。要注意的是避免在此期間更改數據,由於這可能會致使無限循環的更新。
  • beforeDestroy發生在實例銷燬以前,在當前階段實例徹底能夠被使用,咱們能夠在這時進行善後收尾工做,好比清除計時器,銷燬父組件對子組件的重複監聽。beforeDestroy(){Bus.$off("saveTheme")}
  • destroyed發生在實例銷燬以後,這個時候只剩下了dom空殼。組件已被拆解,數據綁定被卸除,監聽被移出,子實例也通通被銷燬。

3-2 生命週期調用順序

  • 組件的調用順序都是先父後子
  • 渲染完成的順序是先子後父
  • 組件的銷燬操做是先父後子
  • 銷燬完成的順序是先子後父

加載渲染過程 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted

子組件更新過程 父beforeUpdate->子beforeUpdate->子updated->父updated

父組件更新過程 父 beforeUpdate -> 父 updated

銷燬過程 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

3-3 vue生命週期的做用是什麼

它的生命週期中有多個事件鉤子,讓咱們控制Vue實例過程更加清晰

3-4 第一次頁面加載會觸發哪幾個鉤子

第一次頁面加載時會觸發 beforeCreate, created, beforeMount, mounted 這幾個鉤子

3-5 每一個週期具體適合哪些場景

  • beforecreate : 能夠在這加個loading事件,在加載實例時觸發
  • created : 初始化完成時的事件寫在這裏,如在這結束loading事件,異步請求也適宜在這裏調用
  • mounted : 掛載元素,獲取到DOM節點
  • updated : 若是對數據統一處理,在這裏寫上相應函數
  • beforeDestroy : 能夠清除定時器
  • nextTick : 更新數據後當即操做dom

4.v-show 與 v-if 的區別

v-if

  • 是真正的條件渲染,由於它會確保在切換過程當中條件塊內的事件監聽器子組件適當地被銷燬和重建
  • 也是惰性的:若是在初始渲染時條件爲假,則什麼也不作——直到條件第一次變爲真時,纔會開始渲染條件塊。

v-show

無論初始條件是什麼,元素老是會被渲染,而且只是簡單地基於 CSS 的 「display」 屬性進行切換。

因此:

  • v-if 適用於在運行時不多改變條件不須要頻繁切換條件的場景;
  • v-show 則適用於須要很是頻繁切換條件的場景。

5. Vue 的單向數據流

背景:

全部的 prop 都使得其父子 prop 之間造成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,可是反過來則不行。這樣會防止從子組件意外改變父級組件的狀態,從而致使你的應用的數據流向變的混亂。

每次父級組件發生更新時,子組件中全部的 prop 都將會刷新爲最新的值。這意味着你不該該在一個子組件內部改變 prop。若是你這樣作了,Vue 會在瀏覽器的控制檯中發出警告。子組件想修改時,只能經過 $emit 派發一個自定義事件,父組件接收到後,由父組件修改

有兩種常見的試圖改變一個 prop 的情形 :

  • 這個 prop 用來傳遞一個初始值;
  • 這個子組件接下來但願將其做爲一個本地的 prop 數據來使用。

在第2狀況下,最好定義一個本地的 data屬性並將這個 prop 用做其初始值:

props: ['initialCounter'],
data: function () {
  return {
    counter: this.initialCounter//定義本地的data屬性接收prop初始值
  }
}

這個 prop 以一種原始的值傳入且須要進行轉換。

在這種狀況下,最好使用這個 prop 的值來定義一個計算屬性

props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

6.異步請求適合在哪一個生命週期調用?

官方實例的異步請求是在mounted生命週期中調用的,而實際上也能夠在created生命週期中調用。

本人推薦在 created 鉤子函數中調用異步請求,有如下優勢:

  • 更快獲取到服務端數據減小頁面 loading 時間;
  • ssr 不支持 beforeMount 、mounted 鉤子函數,因此放在 created 中有助於一致性;

7.Vue2.x組件通訊有哪些方式?

7-1 父子組件通訊

  • 父->子props;子(emit)->父(on)
  • 獲取父子組件實例 $parent / $children 如:直接在子組件的methods方法裏面寫:this.$parent.show()//show爲父組件中定義的方法
  • Ref (若是在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;若是用在子組件上,引用就指向組件實例),如在引入的子組件的標籤上掛載一個: ref="comA",而後在方法中或者子組件的數據,this.$refs.comA.titles
  • Provide、inject 官方不推薦使用,可是寫組件庫時很經常使用,祖先組件中經過provider來提供變量,而後在子孫組件中經過inject來注入變量

7-2 兄弟組件通訊

  • Event Bus 實現跨組件通訊: Vue.prototype.$bus = new Vue
  • Vuex

7-3 跨級組件通訊

  • Vuex
  • attrs,listeners
  • Provide、inject

7-4 使用

1. 父子props,on

// 子組件

<template>
  <header>
    <h1 @click="changeTitle">{{title}}</h1>//綁定一個點擊事件
  </header>
</template>
<script>
export default {
  data() {
    return {
      title:"Vue.js Demo"
    }
  },
  methods:{
    changeTitle() {
      this.$emit("titleChanged","子向父組件傳值");//自定義事件  傳遞值「子向父組件傳值」
    }
  }
}
</script>

// 父組件

<template>
  <div id="app">
    <Header @titleChanged="updateTitle" ></Header>//與子組件titleChanged自定義事件保持一致
    <h2>{{title}}</h2>
  </div>
</template>
<script>
import Header from "./Header"
export default {
  data(){
    return{
      title:"傳遞的是一個值"
    }
  },
  methods:{
    updateTitle(e){   //聲明這個函數
      this.title = e;
    }
  },
  components:{
   Header
  }
}
</script>

2. parent / $children與 ref

// A 子組件

export default {
  data () {
    return {
      title: 'a組件'
    }
  },
  methods: {
    sayHello () {
      alert('Hello');
    }
  }
}

// 父組件

<template>
  <A ref="comA"></A>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.title);  // a組件
      comA.sayHello();  // 彈窗
    }
  }
</script>

3.attrs,listeners

attrs: 包含了父做用域不被 prop識別 (且獲取) 的特性綁定 ( class 和 style 除外 )。當一個組件沒有聲明任何 prop 時,這裏會包含全部父做用域的綁定 ( class 和 style 除外 ),而且能夠經過 v-bind="$attrs" 傳入內部組件。一般配合 inheritAttrs 選項一塊兒使用。

listeners: :包含了父做用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它能夠經過 v-on="$listeners" 傳入內部組件

// index.vue

<template>
  <div>
    <h2>浪裏行舟</h2>
    <child-com1 :foo="foo" :boo="boo" :coo="coo" :doo="doo" title="前端工匠"></child-com1>
  </div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
  components: { childCom1 },
  data() {
    return {
      foo: "Javascript",
      boo: "Html",
      coo: "CSS",
      doo: "Vue"
    };
  }
};
</script>

// childCom1.vue

<template class="border">
  <div>
    <p>foo: {{ foo }}</p>
    <p>childCom1的$attrs: {{ $attrs }}</p>
    <child-com2 v-bind="$attrs"></child-com2>
  </div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
  components: {
    childCom2
  },
  inheritAttrs: false, // 能夠關閉自動掛載到組件根元素上的沒有在props聲明的屬性
  props: {
    foo: String // foo做爲props屬性綁定
  },
  created() {
    console.log(this.$attrs); // 父組件中的屬性,且不在當前組件props中的屬性。{ "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
};
</script>

// childCom2.vue

<template>
  <div class="border">
    <p>boo: {{ boo }}</p>
    <p>childCom2: {{ $attrs }}</p>
    <child-com3 v-bind="$attrs"></child-com3>
  </div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
  components: {
    childCom3
  },
  inheritAttrs: false,
  props: {
    boo: String
  },
  created() {
    console.log(this.$attrs); // / 父組件中的屬性,且不在當前組件props中的屬性。{"coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
};
</script>

// childCom3.vue

<template>
  <div class="border">
    <p>childCom3: {{ $attrs }}</p>
  </div>
</template>
<script>
export default {
  props: {
    coo: String,
    title: String
  }
};
</script>

4. Provide、inject的使用:

父組件

<template>
    <div id="app">
    </div>
</template>
    <script>
        export default {
            data () {
                    return {
                        datas: [
                            {
                                id: 1,
                                label: '產品一'
                            }
                        ]
                    }
            },
            provide {
                return {
                    datas: this.datas
                }
            }
        }
    </script>

子組件

<template>
    <div>
        <ul>
        <li v-for="(item, index) in datas" :key="index">
            {{ item.label }}
        </li>
        </ul>
    </div>
</template>
    <script>
        export default {
            inject: ['datas']
        }
    </script>

8. 什麼是SSR

SSR也就是 服務端渲染,也就是將Vue在客戶端把 標籤渲染成 HTML的工做放在服務端完成,而後再把 html直接返回給客戶端。

服務端渲染 SSR 的優缺點以下:

(1)服務端渲染的優勢:

  • 更好的 SEO: 由於 SPA 頁面的內容是經過 Ajax 獲取,而搜索引擎爬取工具並不會等待 Ajax 異步完成後再抓取頁面內容,因此在 SPA 中是抓取不到頁面經過 Ajax 獲取到的內容;而 SSR 是直接由服務端返回已經渲染好的頁面(數據已經包含在頁面中),因此搜索引擎爬取工具能夠抓取渲染好的頁面;
  • 更快的內容到達時間(首屏加載更快): SPA等待全部 Vue 編譯後的 js 文件都下載完成後纔開始進行頁面的渲染,文件下載等須要必定的時間等,因此首屏渲染須要必定的時間;SSR 直接由服務端渲染好頁面直接返回顯示,無需等待下載 js 文件及再去渲染等,因此 SSR 有更快的內容到達時間;

(2) 服務端渲染的缺點:

  • 更多的開發條件限制: 例如服務端渲染只支持 beforCreatecreated 兩個鉤子函數,這會致使一些外部擴展庫須要特殊處理,才能在服務端渲染應用程序中運行;而且與能夠部署在任何靜態文件服務器上的徹底靜態單頁面應用程序 SPA 不一樣,服務端渲染應用程序,須要處於 Node.js server 運行環境;
  • 更多的服務器負載: 在 Node.js 中渲染完整的應用程序,顯然會比僅僅提供靜態文件的 server 更加大量佔用CPU 資源 (CPU-intensive - CPU 密集),所以若是你預料在高流量環境 ( high traffic ) 下使用,請準備相應的服務器負載,並明智地採用緩存策略。

9.Vue路由

9-1 vue-router 路由模式有幾種?

vue-router 有 3 種路由模式:hashhistoryabstract,對應的源碼以下所示:

switch (mode) {
  case 'history':
    this.history = new HTML5History(this, options.base)
    break
  case 'hash':
    this.history = new HashHistory(this, options.base, this.fallback)
    break
  case 'abstract':
    this.history = new AbstractHistory(this, options.base)
    break
  default:
    if (process.env.NODE_ENV !== 'production') {
      assert(false, `invalid mode: ${mode}`)
    }
}

路由模式的說明以下:

  • hash: 使用 URL hash 值來做路由。支持全部瀏覽器,包括不支持 HTML5 History Api 的瀏覽器;
  • history : 依賴 HTML5 History API 和服務器配置。具體能夠查看 HTML5 History 模式;
  • abstract : 支持全部 JavaScript 運行環境,如 Node.js 服務器端。若是發現沒有瀏覽器的 API,路由會自動強制進入這個模式.

9-2 hash路由和history路由實現原理

(1)hash 模式的實現原理

早期的前端路由的實現就是基於 location.hash 來實現的。其實現原理很簡單,location.hash 的值就是 URL 中 # 後面的內容。

好比下面這個網站,它的 location.hash 的值爲 '#search'

https://www.word.com#search

hash 路由模式的實現主要是基於下面幾個特性:

  • URL 中 hash 值只是客戶端的一種狀態,也就是說當向服務器端發出請求時,hash 部分不會被髮送
  • hash 值的改變,都會在瀏覽器的訪問歷史增長一個記錄。所以咱們能經過瀏覽器的回退、前進按鈕控制hash 的切換;
  • 能夠經過 a 標籤,並設置 href 屬性,當用戶點擊這個標籤後,URL 的 hash 值會發生改變;或者使用JavaScript 來對 loaction.hash 進行賦值,改變 URL 的 hash 值;
  • 咱們能夠使用 hashchange 事件來監聽 hash 值的變化,從而對頁面進行跳轉(渲染)。

(2)history 模式的實現原理

HTML5 提供了 History API 來實現 URL 的變化,其中作最主要的 API 有如下兩個:

  • history.pushState() //新曾歷史記錄
  • history.repalceState()。 //替換歷史記錄

這兩個 API 能夠在不進行刷新的狀況下,操做瀏覽器的歷史紀錄。惟一不一樣的是,前者是新增一個歷史記錄,後者是直接替換當前的歷史記錄,以下所示:

window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);

history 路由模式的實現主要基於存在下面幾個特性:

  • pushState 和 repalceState 兩個 API 來操做實現 URL 的變化;
  • 咱們能夠使用 popstate 事件來監聽 url 的變化,從而對頁面進行跳轉(渲染);
  • history.pushState() 或 history.replaceState() 不會觸發 popstate 事件,這時咱們須要手動觸發頁面跳轉(渲染)。

10.Vue 中的 key 有什麼做用?

key 是爲 Vue 中 vnode惟一標記,經過這個 key,咱們的 diff 操做能夠更準確、更快速

Vue 的 diff 過程能夠歸納爲:

oldChnewCh 各有兩個頭尾的變量 oldStartIndex、oldEndIndexnewStartIndex、newEndIndex,它們會新節點和舊節點會進行兩兩對比,即一共有4種比較方式:newStartIndex 和oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex,若是以上 4 種比較都沒匹配,若是設置了key,就會用 key 再進行比較,在比較的過程當中,遍歷會往中間靠,一旦 StartIdx > EndIdx 代表 oldCh 和 newCh 至少有一個已經遍歷完了,就會結束比較。

因此 Vue 中 key 的做用是:key 是爲 Vue 中 vnode 的惟一標記,經過這個 key,咱們的 diff 操做能夠更準確、更快速

  • 更準確由於帶 key 就不是就地複用了,在 sameNode 函數 a.key === b.key對比中能夠避免就地複用的狀況。因此會更加準確。
  • 更快速:利用 key 的惟一性生成 map 對象來獲取對應節點,比遍歷方式更快,源碼以下:

    function createKeyToOldIdx (children, beginIdx, endIdx) {
    let i, key
    const map = {}
    for (i = beginIdx; i <= endIdx; ++i) {
      key = children[i].key
      if (isDef(key)) map[key] = i
    }
    return map
    }

參考1:Vue2.0 v-for 中 :key 到底有什麼用?

11.虛擬 DOM 實現原理

虛擬 DOM 的實現原理主要包括如下 3 部分:

  • 用 JavaScript 對象模擬真實 DOM 樹,對真實 DOM 進行抽象;
  • diff 算法 — 比較兩棵虛擬 DOM 樹的差別;
  • pach 算法 — 將兩個虛擬 DOM 對象的差別應用到真正的 DOM 樹。

詳情點擊這裏

13.虛擬 DOM 的優缺點

優勢:

  • 保證性能下限: 框架的虛擬 DOM 須要適配任何上層 API 可能產生的操做,它的一些 DOM 操做的實現必須是普適的,因此它的性能並非最優的;可是比起粗暴的 DOM 操做性能要好不少,所以框架的虛擬 DOM 至少能夠保證在你不須要手動優化的狀況下,依然能夠提供還不錯性能,即保證性能的下限;
  • 無需手動操做 DOM: 咱們再也不須要手動去操做 DOM,只須要寫好 View-Model 的代碼邏輯,框架會根據虛擬 DOM 和 數據雙向綁定,幫咱們以可預期的方式更新視圖,極大提升咱們的開發效率;
  • 跨平臺: 虛擬 DOM 本質上是 JavaScript 對象,而 DOM 與平臺強相關,相比之下虛擬 DOM 能夠進行更方便地跨平臺操做,例如服務器渲染、weex 開發等等。

缺點:

  • 沒法進行極致優化: 雖然虛擬 DOM + 合理的優化,足以應對絕大部分應用的性能需求,但在一些性能要求極高的應用中虛擬 DOM 沒法進行鍼對性的極致優化。

14.Proxy 與 Object.defineProperty 優劣對比

Proxy 的優點以下:

  • Proxy 能夠直接監聽對象而非屬性;
  • Proxy 能夠直接監聽數組的變化;
  • Proxy 有多達 13 種攔截方法,不限於 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具有的;
  • Proxy 返回的是一個新對象,咱們能夠只操做新的對象達到目的,而 Object.defineProperty 只能遍歷對象屬性直接修改;
  • Proxy 做爲新標準將受到瀏覽器廠商重點持續的性能優化,也就是傳說中的新標準的性能紅利;

Object.defineProperty 的優點以下:

  • 兼容性好,支持 IE9,而 Proxy 的存在瀏覽器兼容性問題,並且沒法用 polyfill 磨平

14-1 那麼,Proxy 能夠實現什麼功能?

Proxy 是 ES6 中新增的功能,它能夠用來自定義對象中的操做。

let p = new Proxy(target, handler)
  • target 表明須要添加代理的對象
  • handler 用來自定義對象中操做,好比能夠用來自定義 set 或者 get 函數。

下面來經過 Proxy 來實現一個數據響應式:

let onWatch = (obj, setBind, getLogger) => {
  let handler = {
    get(target, property, receiver) {
      getLogger(target, property)
      return Reflect.get(target, property, receiver)
    },
    set(target, property, value, receiver) {
      setBind(value, property)
      return Reflect.set(target, property, value)
    }
  }
  return new Proxy(obj, handler)
}
let obj = { a: 1 }
let p = onWatch(
  obj,
  (v, property) => {
    console.log(`監聽到屬性${property}改變爲${v}`)
  },
  (target, property) => {
    console.log(`'${property}' = ${target[property]}`)
  }
)
p.a = 2 // 監聽到屬性a改變爲2
p.a // 'a' = 2

在上述代碼中,經過自定義 set 和 get 函數的方式,在本來的邏輯中插入了咱們的函數邏輯,實現了在對對象任何屬性進行讀寫時發出通知。

固然這是簡單版的響應式實現,若是須要實現一個 Vue 中的響應式,須要在 get 中收集依賴,在 set 派發更新,之因此 Vue3.0 要使用 Proxy 替換本來的 API 緣由在於 Proxy 無需一層層遞歸爲每一個屬性添加代理,一次便可完成以上操做,性能上更好,而且本來的實現有一些數據更新不能監聽到,可是 Proxy 能夠完美監聽到任何方式的數據改變,惟一缺陷就是瀏覽器的兼容性很差。

15.Vue 框架怎麼實現對象和數組的監聽?

Vue 框架是經過遍歷數組 和遞歸遍歷對象,從而達到利用 Object.defineProperty() 也能對對象和數組(部分方法的操做)進行監聽。

vue2:

數組就是使用 object.defineProperty 從新定義數組的每一項,能引發數組變化的方法爲 pop 、 push 、 shift 、 unshift 、 splice 、 sort 、 reverse 這七種,只要這些方法執行改了數組內容,就更新內容

  • 是用來函數劫持的方式,重寫了數組方法,具體就是更改了數組的原型,更改爲本身的,用戶調數組的一些方法的時候,走的就是本身的方法,而後通知視圖去更新(本質就是在原有的方法上又調用了更新數據的方法)。
  • 數組項多是對象,那麼就對數組的每一項進行觀測

vue3:

改用 proxy ,可直接監聽對象數組的變化。

參考

16.Vue 是如何實現數據雙向綁定的

Vue 數據雙向綁定主要是指:數據變化更新視圖,視圖變化更新數據

輸入框內容變化時,Data 中的數據同步變化。即 View => Data 的變化。 Data 中的數據變化時,文本節點的內容同步變化。即 Data => View 的變化。

其中,View 變化更新 Data ,能夠經過事件監聽的方式來實現,因此 Vue 的數據雙向綁定的工做主要是如何根據 Data 變化更新 View。

Vue 主要經過如下 4 個步驟來實現數據雙向綁定的

  • 實現一個監聽器 Observer 對數據對象進行遍歷,包括子屬性對象的屬性,利用 Object.defineProperty() 對屬性都加上 settergetter。這樣的話,給這個對象的某個值賦值,就會觸發 setter,那麼就能監聽到了數據變化。
  • 實現一個解析器 Compile 解析 Vue 模板指令,將模板中的變量都替換成數據,而後初始化渲染頁面視圖,並將每一個指令對應的節點綁定更新函數,添加監聽數據的訂閱者,一旦數據有變更,收到通知,調用更新函數進行數據更新。
  • 實現一個訂閱者 Watcher 訂閱者是 Observer 和 Compile 之間通訊的橋樑 ,主要的任務是訂閱 Observer 中的屬性值變化的消息,當收到屬性值變化的消息時,觸發解析器 Compile 中對應的更新函數。
  • 實現一個訂閱器 Dep 訂閱器採用 發佈-訂閱 設計模式,用來收集訂閱者 Watcher,對監聽器 Observer 和 訂閱者 Watcher 進行統一管理。

17.v-model 的原理

v-model 指令在表單 input、textarea、select 等元素上建立雙向數據綁定,v-model 本質上是語法糖,會在內部爲不一樣的輸入元素使用不一樣的屬性並拋出不一樣的事件:

  • text 和 textarea 元素使用value 屬性和 input 事件;
  • checkbox 和 radio 使用 checked 屬性和 change 事件;
  • select 字段將 value 做爲 prop 並將 change 做爲事件。

以 input 表單元素爲例

<input v-model='something'>

至關於

<input :value="something" @input="something = $event.target.value">

18.組件中 data 爲何是一個函數?

爲何組件中的 data 必須是一個函數,而後 return 一個對象,而 new Vue 實例裏,data 能夠直接是一個對象?

// data

data() {
  return {
    message: "子組件",
    childName:this.name
  }
}

// new Vue

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

一個組件被複用屢次的話,也就會建立多個實例,本質上,這些實例用的都是同一個構造函數。

若是data是對象的話,對象屬於引用類型,會影響到全部的實例,因此爲了保證組件不一樣的實例之間data不衝突,data必須是一個函數。

而 new Vue 的實例,是不會被複用的,所以不存在引用對象的問題。

19.談談你對 keep-alive

keep-alive 是 Vue 內置的一個組件,能夠使被包含的組件保留狀態避免從新渲染 ,其有如下特性:

  • 通常結合路由和動態組件一塊兒使用,用於緩存組件;
  • 提供 includeexclude 屬性,二者都支持字符串或正則表達式, include 表示只有名稱匹配的組件會被緩存,exclude 表示任何名稱匹配的組件都不會被緩存 ,其中 exclude 的優先級比 include
  • 對應兩個鉤子函數 activateddeactivated ,當組件被激活時,觸發鉤子函數 activated,當組件被移除時,觸發鉤子函數 deactivated。

keep-alive的生命週期

  • activated: 頁面第一次進入的時候,鉤子觸發的順序是created->mounted->activated
  • deactivated: 頁面退出的時候會觸發deactivated,當再次前進或者後退的時候只觸發activated

20.父組件能夠監聽到子組件的生命週期嗎?

好比有父組件 Parent 和子組件 Child,若是父組件監聽到子組件掛載 mounted 就作一些邏輯處理,能夠經過如下寫法實現:

// Parent.vue

<Child @mounted="doSomething"/>

// Child.vue

mounted() {
  this.$emit("mounted");
}

以上須要手動經過 $emit 觸發父組件的事件,更簡單的方式能夠在父組件引用子組件時經過 @hook 來監聽便可,以下所示:

// Parent.vue

<Child @hook:mounted="doSomething" ></Child>
doSomething() {
   console.log('父組件監聽到 mounted 鉤子函數 ...');
},

// Child.vue

mounted(){
   console.log('子組件觸發 mounted 鉤子函數 ...');
},

// 以上輸出順序爲:
// 子組件觸發 mounted 鉤子函數 ...
// 父組件監聽到 mounted 鉤子函數 ...
固然 @hook 方法不只僅是能夠監聽 mounted,其它的生命週期事件,例如:created,updated 等均可以監聽。

21.直接給一個數組項賦值,Vue 能檢測到變化嗎?

因爲 JavaScript 的限制,Vue 不能檢測到如下數組的變更:

  • 當你利用索引直接設置一個數組項時,例如:vm.items[indexOfItem] = newValue
  • 當你修改數組的長度時,例如:vm.items.length = newLength

爲了解決第一個問題,Vue 提供瞭如下操做方法:

// Vue.set

Vue.set(vm.items, indexOfItem, newValue)

// vm.$set(Vue.set的一個別名)

vm.$set(vm.items, indexOfItem, newValue)

// Array.prototype.splice

vm.items.splice(indexOfItem, 1, newValue)

爲了解決第二個問題,Vue 提供瞭如下操做方法:

// Array.prototype.splice

vm.items.splice(newLength)

22.vue2.x中如何監測數組變化

使用了函數劫持的方式,重寫了數組的方法,Vue將data中的數組進行了原型鏈重寫,指向了本身定義數組原型方法。這樣當調用數組api時,能夠通知依賴更新。若是數組中包含着引用類型,會對數組中的引用類型再次遞歸遍歷進行監控。這樣就實現了監測數組變化。

22.Vue2.x和Vue3.x渲染器的diff算法分別說一下

簡單來講,diff算法有如下過程

同級比較, 再比較子節點,先判斷一方有子節點一方沒有子節點的狀況(若是新的children沒有子節點,將舊的子節點移除)

比較都有子節點的狀況(核心diff)遞歸比較子節點

正常Diff兩個樹的時間複雜度是O(n^3),但實際狀況下咱們不多會進行跨層級的移動DOM,因此Vue將Diff進行了優化,從O(n^3) -> O(n),只有當新舊children都爲多個子節點時才須要用核心的Diff算法進行同層級比較。

Vue2的核心Diff算法採用了雙端比較的算法,同時重新舊children的兩端開始進行比較,藉助key值找到可複用的節點,再進行相關操做。相比React的Diff算法,一樣狀況下能夠減小移動節點次數,減小沒必要要的性能損耗,更加的優雅。

Vue3.x借鑑了 ivi算法和 inferno算法 在建立VNode時就肯定其類型,以及在mount/patch的過程當中採用位運算來判斷一個VNode的類型,在這個基礎之上再配合核心的Diff算法,使得性能上較Vue2.x有了提高。 該算法中還運用了動態規劃的思想求解最長遞歸子序列。

23.Vue模版編譯原理

簡單說,Vue的編譯過程就是將template轉化爲render函數的過程。會經歷如下階段:

  • 生成AST
  • 樹優化
  • codegen

首先解析模版,生成AST語法樹(一種用JavaScript對象的形式來描述整個模板)。

使用大量的正則表達式對模板進行解析,遇到標籤文本的時候都會執行對應的鉤子進行相關處理。

Vue的數據是響應式的,但其實模板中並非全部的數據都是響應式的。有一些數據首次渲染後就不會變化,對應的DOM也不會變化。那麼優化過程就是深度遍歷AST樹,按照相關條件對樹節點進行標記。這些被標記的節點(靜態節點)咱們就能夠跳過對它們的比對,對運行時的模板起到很大的優化做用。

編譯的最後一步是將優化後的AST樹轉換可執行代碼

24.Computed和Watch

computed:

  • computed是計算屬性,也就是計算值,它更多用於計算值的場景
  • computed具備緩存性,computed的值在getter執行後是會緩存的,只有在它依賴的屬性值改變以後,下一次獲取computed的值時纔會從新調用對應的getter來計算
  • computed適用於計算比較消耗性能的計算場景

watch:

  • 更多的是「觀察」的做用,相似於某些數據的監聽回調,用於觀察props $emit或者本組件的值,當數據變化時來執行回調進行後續操做
  • 無緩存性,頁面從新渲染時值不變化也會執行

小結:

  • 當咱們要進行數值計算,並且依賴於其餘數據,那麼把這個數據設計爲computed
  • 若是你須要在某個數據變化時作一些事情,使用watch來觀察這個數據變化

25.nextTick

下次 DOM 更新循環結束以後執行延遲迴調。在這裏面的代碼會等到dom更新之後再執行。

<template>
  <section>
    <div ref="hello">
      <h1>Hello World ~</h1>
    </div>
    <el-button type="danger" @click="get">點擊</el-button>
  </section>
</template>
<script>
  export default {
    methods: {
      get() {
      }
    },
    mounted() {
      console.log(333);
      console.log(this.$refs['hello']);
      this.$nextTick(() => {
        console.log(444);
        console.log(this.$refs['hello']);
      });
    },
    created() {
      console.log(111);
      console.log(this.$refs['hello']);
      this.$nextTick(() => {
        console.log(222);
        console.log(this.$refs['hello']);
      });
    }
  }
</script>

image.png

詳細點擊這路

26.vue響應式原理

26-1 Vue2.x響應式數據原理

Vue在初始化數據時,會使用Object.defineProperty從新定義data中的全部屬性,當頁面使用對應屬性時,首先會進行依賴收集(收集當前組件的watcher),若是屬性發生變化通知相關依賴進行更新操做(發佈訂閱)

具體的過程

  • 首先Vue使用 initData 初始化用戶傳入的參數
  • 而後使用 new Observer 對數據進行觀測
  • 若是數據是一個對象類型就會調用 this.walk(value) 對對象進行處理,內部使用 defineeReactive 循環對象屬性定義響應式變化,核心就是使用 Object.defineProperty 從新定義數據。

26-2 Vue3.x響應式數據原理

Vue3.x改用Proxy替代Object.defineProperty。由於Proxy能夠直接監聽對象和數組的變化,而且有多達13種攔截方法。而且做爲新標準將受到瀏覽器廠商重點持續的性能優化。

Proxy只會代理對象的第一層,那麼Vue3又是怎樣處理這個問題的呢?

判斷當前Reflect.get的返回值是否爲Object,若是是則再經過reactive方法作代理, 這樣就實現了深度觀測。

監測數組的時候可能觸發屢次get/set,那麼如何防止觸發屢次呢?

咱們能夠判斷key是否爲當前被代理對象target自身屬性,也能夠判斷舊值新值是否相等,只有知足以上兩個條件之一時,纔有可能執行trigger

27.在使用計算屬性的時,函數名和data數據源中的數據能夠同名嗎?

不能同名 由於無論是計算屬性仍是data仍是props 都會被掛載在vm實例上,所以 這三個都不能同名

28.怎麼解決vue打包後靜態資源圖片失效的問題

找到config/index.js 配置文件,找build打包對象裏的assetsPublicPath屬性 默認值爲/,更改成./就行了

29. 怎麼解決vue動態設置img的src不生效的問題?

由於動態添加src被當作靜態資源處理了,沒有進行編譯,因此要加上require。

<img :src="require('../../../assets/images/xxx.png')" />

30. 使用vue渲染大量數據時應該怎麼優化?說下你的思路!

Object.freeze

適合一些 big data的業務場景。尤爲是作管理後臺的時候,常常會有一些超大數據量table,或者一個含有 n 多數據的圖表,這種數據量很大的東西使用起來最明顯的感覺就是卡。但其實不少時候其實這些數據其實並不須要響應式變化,這時候你就能夠使用 Object.freeze 方法了,它能夠凍結一個對象(注意它不併是 vue 特有的 api)。

當你把一個普通的 JavaScript 對象傳給 Vue 實例的 data 選項,Vue 將遍歷此對象全部的屬性,並使用 Object.defineProperty 把這些屬性所有轉爲 getter/setter,它們讓 Vue 能進行追蹤依賴,在屬性被訪問和修改時通知變化。

使用了 Object.freeze 以後,不只能夠減小 observer 的開銷,還能減小很多內存開銷

使用方式:

this.item = Object.freeze(Object.assign({}, this.item))

30. vue 自定義指令

先了解一下,在 vue 中,有不少內置的指令.

好比:

  • v-bind: 屬性綁定,把數據綁定在HTML元素的屬性上.
  • v-html & v-text 把數據綁定在HTML元素的屬性上,做用同 innerHTML & innerText
  • v-on: 綁定HTML元素事件

因此,關於指令,咱們能夠總結下面幾點:

  • 指令是寫在 HTML 屬性地方的,<input v-model='name' type='text' />
  • 指令都是以 v- 開頭的.
  • 指令表達式的右邊通常也能夠跟值 v-if = false

Vue自定義指令案例1

例如:咱們須要一個指令,寫在某個HTML表單元素上,而後讓它在被加載到DOM中時,自動獲取焦點.

// 和自定義過濾器同樣,咱們這裏定義的是全局指令
Vue.directive('focus',{
    inserted(el) {
      el.focus()
    }
})

<div id='app'>
    <input type="text">
    <input type="text" v-focus placeholder="我有v-focus,因此,我獲取了焦點">
  </div>

image.png

先總結幾個點:

  • 使用 Vue.directive() 來新建一個全局指令,(指令使用在HTML元素屬性上的)
  • Vue.directive('focus') 第一個參數focus是指令名,指令名在聲明的時候,不須要加 v-
  • 在使用指令的HTML元素上,<input type="text" v-focus placeholder="我有v-focus,因此,我獲取了焦點"/> 咱們須要加上 v-.
  • Vue.directive('focus',{}) 第二個參數是一個對象,對象內部有個 inserted() 的函數,函數有 el 這個參數.
  • el 這個參數表示了綁定這個指令的 DOM元素,在這裏就是後面那個有 placeholderinput,el 就等價於 document.getElementById('el.id')
  • 能夠利用 $(el) 無縫鏈接 jQuery

指令的生命週期

用指令咱們須要:

  • 新增一個指令
  • 定義指令的第二個參數裏的 inserted 函數
  • 在須要獲取焦點的元素上,使用這個指令.

當一個指令綁定到一個元素上時,其實指令的內部會有五個生命週期事件函數.

  • bind(){} 當指令綁定到 HTML 元素上時觸發.只調用一次.
  • inserted() 當綁定了指令的這個HTML元素插入到父元素上時觸發(在這裏父元素是 div#app).但不保證,父元素已經插入了 DOM 文檔.
  • updated() 所在組件的VNode更新時調用.
  • componentUpdate 指令所在的組件的VNode以及其子VNode 所有更新後調用.
  • unbind: 指令和元素解綁的時候調用,只調用一次

Vue 指令的聲明周期函數

Vue.directive('gqs',{
    bind() {
      // 當指令綁定到 HTML 元素上時觸發.**只調用一次**
      console.log('bind triggerd')
    },
    inserted() {
      // 當綁定了指令的這個HTML元素插入到父元素上時觸發(在這裏父元素是 `div#app`)**.但不保證,父元素已經插入了 DOM 文檔.**
      console.log('inserted triggerd')
    },
    updated() {
      // 所在組件的`VNode`更新時調用.
      console.log('updated triggerd')
    },
    componentUpdated() {
      // 指令所在組件的 VNode 及其子 VNode 所有更新後調用。
      console.log('componentUpdated triggerd')
      
    },
    unbind() {
      // 只調用一次,指令與元素解綁時調用.
      console.log('unbind triggerd')
    }
  })

HTML

<div id='app' v-gqs></div>

結果:

bind triggerd
inserted triggerd

發現默認狀況下只有 bind 和 inserted 聲明周期函數觸發了.

那麼剩下的三個何時觸發呢?

<div id='app' >
    <p v-gqs v-if="show">v-if是刪除或者新建dom元素,它會觸發unbind指令聲明週期嗎?</p>
    <button @click="show=!show">toggle</button>
  </div>

當指令綁定的元素被銷燬時,會觸發指令的 unbind 事件.
(新建並顯示,仍然是觸發 bind & inserted)

unbind觸發.gif

<p v-gqs v-show="show2">v-show設置元素的display:block|none,會觸發componentUpdated事件</p>
 <button @click="show2=!show2">toggle-v-show</button>
  • 一個把元素從DOM刪除觸發unbind().---> 僅僅是刪除.
  • 一個顯示設置元素的隱藏和顯示的時候觸發 componentUpdated() ---> block | none 都觸發.

31. vue實例掛載的過程是什麼

32. 組件和插件有什麼區別

  • 組件 (Component) 是用來構成你的 App 的業務模塊,它的目標是 App.vue。
  • 插件 (Plugin) 是用來加強你的技術棧的功能模塊,它的目標是 Vue 自己。

33.使用vue過程當中可能會遇到的問題(坑)有哪些

34. 動態給vue的data添加一個新的屬性時會發生什麼

根據官方文檔定義:

若是在實例建立以後添加新的屬性到實例上,它不會觸發視圖更新。

Vue 不容許在已經建立實例動態添加新的根級響應式屬性 (root-level reactive property)。

然而它能夠使用 Vue.set(object, key, value) 方法將響應屬性添加到嵌套的對象上。

35. SPA首屏加載速度慢的怎麼解決

  1. 經過Gzip壓縮
  2. 使用路由懶加載
  3. 利用webpack中的externals這個屬性把不須要打包的庫文件都分離出去,減少項目打包後的大小
  4. 使用SSR渲染

36. mixin

多個實例引用了相同或類似的 方法或屬性等,可將這些重複的內容抽取出來做爲mixins的js,export出去,在須要引用的vue文件經過mixins屬性注入,與 當前實例其餘內容進行 merge

一個混入對象能夠包含任意組件選項。同一個生命週期,混入對象會比組件先執行

//暴露兩個mixins對象

export const mixinsTest1 = {
    methods: {
        hello1() {
            console.log("hello1");
        }
    },
    created() {
        this.hello1();
    },
}


export const mixinsTest2 = {
    methods:{
        hello2(){
            console.log("hello2");
        }
    },
    created() {
        this.hello2();
    },
}
<template>
<div>
    home
</div>
</template>

<script>
import {mixinsTest1,mixinsTest2} from '../util/test.js'
export default {
  name: "Home",
  data () {
    return {
    };
  },
  created(){
      console.log("1212");
  },
  mixins:[mixinsTest2,mixinsTest1] // 先調用哪一個mixins對象,就先執行哪一個
}
</script>

hello2
hello1
1212

37. vue的核心是什麼

  1. 數據驅動 專一於View 層。它讓開發者省去了操做DOM的過程,只須要改變數據。
  2. 組件響應原理 數據(model)改變驅動視圖(view)自動更新
  3. 組件化 擴展HTML元素,封裝可重用的代碼。

38. vue經常使用的修飾符有哪些

  • v-model: .trim .number
  • v-on: .stop .prevent

    39. v-on能夠綁定多個方法嗎?

<input v-model="msg" type="text" v-on="{input:a, focus:b}"/>

40. template編譯的理解

41.axios是什麼,如何中斷axios的請求

42. 如何引入scss?

安裝scss依賴包:

npm install sass-loader --save-dev npm install node-sass --save-dev

在build文件夾下修改 webpack.base.conf.js 文件,在 module 下的 rules 裏添加配置,以下:

{ test: /\.scss$/, loaders: ['style', 'css', 'sass'] }

應用:

在vue文件中應用scss時,須要在style樣式標籤上添加lang="scss",即<style lang="scss">。

43. 在vue中watch和created哪一個先執行

watch 中的 immediate 會讓監聽在初始值聲明的時候去執行監聽計算,不然就是 created 先執行

44. 在vue中created與activated有什麼區別

created():在實例建立完成後被當即調用。在這一步,實例已完成如下的配置:數據觀測 (data observer),property 和方法的運算,watch/event 事件回調。然而,掛載階段還沒開始,$el property 目前尚不可用。

activated():是在路由設置<keep-alive></keep-alive>時,纔會有這個生命週期。在被 keep-alive 緩存的組件激活時調用。

45. 爲何在v-for中的key不推薦使用隨機數或者index呢

由於在插入數據或者刪除數據的時候,會致使後面的數據的key綁定的index變化,進而致使重新渲染,效率會下降

46. 如何批量引入組件

動態組件使用方法

<keep-alive>
    <component :is="isWhich"></component>
</keep-alive>
使用標籤保存狀態,即切換組件再次回來依然是原來的樣子,頁面不會刷新,若不須要能夠去掉。

經過事件改變is綁定的isWhich值便可切換成不一樣的組件,isWhich的值爲組件名稱。

47. vue中怎麼重置data

使用場景:

好比,有一個表單,表單提交成功後,但願組件恢復到初始狀態,重置data數據。

使用Object.assign()vm.$data能夠獲取當前狀態下的data,vm.$options.data能夠獲取到組件初始化狀態下的data

初始狀態下設置data數據的默認值,重置時直接bject.assign(this.$data, this.$options.data())

說明:

  • this.$data獲取當前狀態下的data
  • this.$options.data()獲取該組件初始狀態下的data(即初始默認值)
  • 若是隻想修改data的某個屬性值,能夠this[屬性名] = this.$options.data()[屬性名],如this.message = this.$options.data().message

48. vue渲染模板時怎麼保留模板中的HTML註釋呢

<template comments>
  ...
</template>

49. style加scoped屬性的用途和原理

  • 用途:防止全局同名CSS污染
  • 原理:在標籤加上v-data-something屬性,再在選擇器時加上對應[v-data-something],即CSS屬性選擇器,以此完成相似做用域的選擇方式

50.在vue項目中如何配置favicon

  • 將 favicon 圖片放到 static 文件夾下
  • 而後在 index.html 中添加:

    <link rel="shortcut icon" type="image/x-icon" href="static/favicon.ico">

51. babel-polyfill模塊

Babel默認只轉換新的JavaScript句法(syntax),而不轉換新的API,好比Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局對象,以及一些定義在全局對象上的方法(好比Object.assign)都不會轉碼。

舉例來講,ES6在Array對象上新增了Array.from方法。Babel就不會轉碼這個方法。若是想讓這個方法運行,必須使用babel-polyfill,爲當前環境提供一個墊片。

52. 在vue事件中傳入$event,使用e.target和e.currentTarget有什麼區別

  • currentTarget: 事件綁定的元素
  • target: 鼠標觸發的元素

53. vue怎麼實現強制刷新組件

強制從新渲染

this.$forceUpdate()

強制從新刷新某組件

//模版上綁定key
<SomeComponent :key="theKey"/>
//選項裏綁定data
data(){
  return{
      theKey:0
  }
}

//刷新key達到刷新組件的目的

theKey++;

54. vue給組件綁定自定義事件無效怎麼解決

加入.native修飾符

55. vue的屬性名與method的方法名同樣時會發生什麼問題

報錯 "Method 'xxx' has already been defined as a data property"

鍵名優先級:props > data > methods

56. vue變量名若是以_、$開頭的屬性會發生什麼問題

實例建立以後,能夠經過 vm.$data 訪問原始數據對象。Vue 實例也代理了 data 對象上全部的屬性,所以訪問 vm.a 等價於訪問 vm.$data.a

_$ 開頭的屬性 不會 被 Vue 實例代理,由於它們可能和 Vue 內置的屬性、API 方法衝突。能夠使用 vm.$data._property 的方式訪問這些屬性。

57. vue項目本地開發完成後部署到服務器後報404

使用了history模式,然後端又沒有進行相關資源配置。

58. vue的表單修飾符.lazy

v-model默認的觸發條件是input事件,加了.lazy修飾符以後,v-model會在change事件觸發的時候去監聽

59. vue爲何要求組件模板只能有一個根元素

diff算法要求,源碼中patch.js中的patchVnode也是根據樹狀結構進行遍歷

60. 在vue中使用this應該注意哪些問題

生命週期的鉤子函數不能使用箭頭函數,否者this不能指向vue實例

61. <template></template>有什麼用

包裹嵌套其它元素,使元素具備區域性,自身具備三個特色:

  • 隱藏性:不會顯示在頁面中
  • 任意性:能夠寫在頁面的任意地方
  • 無效性: 沒有一個根元素包裹,任何HTML內容都是無效的

62. 組件中寫name選項有什麼做用

  • 項目使用keep-alive時,可搭配組件name進行緩存過濾
  • DOM作遞歸組件時須要調用自身name
  • vue-devtools調試工具裏顯示的組見名稱是由vue中組件name決定的

63. prop是怎麼作驗證的

  • 單個類型就用Number等基礎類型
  • 多個類型用數組
  • 必填的話設置require爲true
  • 默認值的話設置default
  • 對象和數組設置默認用工廠函數
  • 自定義驗證函數validator。

64. vue權限管理,按鈕和路由

65. 大型項目你該怎麼劃分結構和劃分組件

  • views目錄存放一級路由的組件,即視圖組件
  • Components目錄存放組件
  • Store存放vuex相關文件
  • Router目錄存放路由相關文件
  • Untils目錄存放工具js文件
  • API目錄存放封裝好的與後端交互的邏輯
  • Assets存放靜態文件

66. vue3.0的新特性

67. 高階組件

68. vue-loader是什麼

解析和轉換 .vue 文件,提取出其中的邏輯代碼 script、樣式代碼 style、以及 HTML 模版 template,再分別把它們交給對應的 Loader 去處理。

69. 怎麼捕獲組件vue的錯誤信息

70.怎麼配置跨域

71. vue-router怎麼配置404頁面

設置 path: '*' , 而且放在最後一個

72. vue-router如何響應路由參數的變化

爲何要響應參數變化?

  • 切換路由,路由參數發生了變化,可是頁面數據及時更新,須要強制刷新後纔會變化。
  • 不一樣路由渲染相同的組件時(組件複用比銷燬從新建立效率要高),在切換路由後,當前組件下的生命週期函數不會再被調用。

解決方案:

使用 watch 監聽

watch: {
    $route(to, from){
        if(to != from) {
            console.log("監聽到路由變化,作出相應的處理");
        }
    }
}

向 router-view 組件中添加 key

<router-view :key="$route.fullPath"></router-view>

$route.fullPath 是完成後解析的URL,包含其查詢參數信息和hash完整路徑

73. 切換到新路由時,面要滾動到頂部或保持原先的滾動位置

在路由實例中配置
scrollBehavior(ro,form,savedPosition){
//滾動到頂部
return {x:0,y:0}
//保持原先的滾動位置
return {selector:falsy}
}

74. 路由懶加載

75. MVVM

全稱: Model-View-ViewModel , Model 表示數據模型層, view 表示視圖層, ViewModel 是 View 和 Model 層的橋樑數據綁定viewModel 層並自動渲染到頁面中,視圖變化通知 viewModel 層更新數據。

76. vue中的事件綁定原理

事件綁定有幾種?

  1. 原生的事件綁定,原生 dom 事件的綁定,採用的是 addEventListener 實現。
  2. 組件的事件綁定,組件綁定事件採用的是 $on 方法 。
  • 普通元素原生事件綁定在上是經過@click進行綁定的
  • 組件原生事件綁定是經過@click.native進行綁定的,組件中的nativeOn是等價於on的。
  • 組件自定義事件是經過@click綁定的,是經過 $on 方法來實現的,必須有$emit才能夠觸發。

解釋下這2種的區別:

  • 原生:好比咱們在原生便籤上寫一個事件<div @click="getData"></div>,直接觸發的就是原生的點擊事件
  • 組件: 如今咱們自定義了個組件,想要組件上面寫事件,<BtnGroup @click="getName" @click.native="getData"></BtnGroup>,這時候,要觸發原生的點擊事件getData,就須要使用修飾符.native,由於直接使用@click是接收來自子組件emit過來的事件getName,這樣纔不會衝突。
let compiler = require('vue-template-compiler'); // vue loader中的包
let r1 = compiler.compile('<div @click="fn()"></div>'); // 給普通標籤綁定click事件
// 給組件綁定一個事件,有兩種綁定方法
// 一種@click.native,這個綁定的就是原生事件
// 另外一種@click,這個綁定的就是組件自定義事件
let r2 = compiler.compile('<my-component @click.native="fn" @click="fn1"></mycomponent>');
console.log(r1.render); // {on:{click}} 
console.log(r2.render); // {nativeOn:{click},on:{click}}
// 爲何組件要加native?由於組件最終會把nativeOn屬性放到on的屬性中去,這個on會單獨處理
// 組件中的nativeOn 等價於 普通元素on,組件on會單獨處理

詳細鏈接

77. Vue爲什麼採用異步渲染

Vue在更新DOM時是異步執行的,只要偵聽到數據變化,將開啓一個隊列,並緩衝在同一事件循環中發生的全部數據變動,若是同一個watcher屢次觸發,只會被推入隊列一次,這種在緩衝時去除重複數據對於減小沒必要要計算和DOM操做是很是重要的.

而後,在下一個的事件循環tick中,Vue刷新隊列並執行實際(已去重的)工做,Vue在內部對異步隊列嘗試使用原生的Promise.then、MutationObserver和setImmediate,若是執行環境不支持,則會採用setTimeout(fn, 0)代替。

描述

對於Vue爲什麼採用異步渲染,簡單來講就是爲了提高性能,由於不採用異步更新,在每次更新數據都會對當前組件進行從新渲染,爲了性能考慮,Vue會在本輪數據更新後,再去異步更新視圖,舉個例子,讓咱們在一個方法內重複更新一個值。

this.msg = 1;
this.msg = 2;
this.msg = 3;

事實上,咱們真正想要的其實只是最後一次更新而已,也就是說前三次DOM更新都是能夠省略的,咱們只須要等全部狀態都修改好了以後再進行渲染就能夠減小一些性能損耗。

對於渲染方面的問題是很明確的,最終只渲染一次確定比修改以後即渲染所耗費的性能少,在這裏咱們還須要考慮一下異步更新隊列的相關問題,假設咱們如今是進行了相關處理使得每次更新數據只進行一次真實DOM渲染,來讓咱們考慮異步更新隊列的性能優化。

假設這裏是同步更新隊列,this.msg=1,大體會發生這些事

msg值更新 -> 觸發setter -> 觸發Watcher的update -> 從新調用 render -> 生成新的vdom -> dom-diff -> dom更新

這裏的dom更新並非渲染(即佈局、繪製、合成等一系列步驟),而是更新內存中的DOM樹結構,以後再運行this.msg=2,再重複上述步驟,以後的第3次更新一樣會觸發相同的流程,等開始渲染的時候,最新的DOM樹中確實只會存在更新完成3,從這裏來看,前2次對msg的操做以及Vue內部對它的處理都是無用的操做,能夠進行優化處理。

若是是異步更新隊列,會是下面的狀況

運行this.msg=1,並非當即進行上面的流程,而是將對msg有依賴的Watcher都保存在隊列中,該隊列可能這樣[Watcher1, Watcher2...],當運行this.msg=2後,一樣是將對msg有依賴的Watcher保存到隊列中,Vue內部會作去重判斷,此次操做後,能夠認爲隊列數據沒有發生變化,第3次更新也是上面的過程。

固然,你不可能只對msg有操做,你可能對該組件中的另外一個屬性也有操做,好比this.otherMsg=othermessage,一樣會把對otherMsg有依賴的Watcher添加到異步更新隊列中,由於有重複判斷操做,這個Watcher也只會在隊列中存在一次,本次異步任務執行結束後,會進入下一個任務執行流程,其實就是遍歷異步更新隊列中的每個Watcher,觸發其update,而後進行從新調用render -> new vdom -> dom-diff -> dom更新等流程,可是這種方式和同步更新隊列相比,無論操做多少次msg,Vue在內部只會進行一次從新調用真實更新流程。

因此,對於異步更新隊列不是節省了 渲染成本,而是節省了 Vue內部計算及DOM樹操做的成本,無論採用哪一種方式,渲染確實只有一次。

此外,組件內部實際使用VirtualDOM進行渲染,也就是說,組件內部實際上是不關心哪一個狀態發生了變化,它只須要計算一次就能夠得知哪些節點須要更新,也就是說,若是更改了N個狀態,其實只須要發送一個信號就能夠將DOM更新到最新,若是咱們更新多個值。

this.msg = 1;
this.age = 2;
this.name = 3;

此處咱們分三次修改了三種狀態,但其實Vue只會渲染一次,由於VIrtualDOM只須要一次就能夠將整個組件的DOM更新到最新,它根本不會關心這個更新的信號究竟是從哪一個具體的狀態發出來的。

而爲了達到這個目的,咱們須要將渲染操做推遲到全部的狀態都修改完成,爲了作到這一點只須要將渲染操做推遲到本輪事件循環的最後或者下一輪事件循環,也就是說,只須要在本輪事件循環最後,等前面更新狀態的語句都執行完以後執行一次渲染操做,它就能夠無視前面各類更新狀態的語法,不管前面寫了多少條更新狀態的語句,只在最後渲染一次就能夠了。

將渲染推遲到本輪事件循環的最後執行渲染的時機會比推遲到下一輪快不少,因此Vue優先將渲染操做推遲到本輪事件循環的最後,若是執行環境不支持會降級到下一輪,Vue的變化偵測機制(setter)決定了它必然會在每次狀態發生變化時都會發出渲染的信號,但Vue會在收到信號以後檢查隊列中是否已經存在這個任務,保證隊列中不會有重複,若是隊列中不存在則將渲染操做添加到隊列中,以後經過異步的方式延遲執行隊列中的全部渲染的操做並清空隊列,當同一輪事件循環中反覆修改狀態時,並不會反覆向隊列中添加相同的渲染操做,因此咱們在使用Vue時,修改狀態後更新DOM都是異步的。

當數據變化後會調用notify方法,將watcher遍歷,調用update方法通知watcher進行更新,這時候watcher並不會當即去執行,在update中會調用queueWatcher方法將watcher放到了一個隊列裏,在queueWatcher會根據watcher的進行去重,若多個屬性依賴一個watcher,則若是隊列中沒有該watcher就會將該watcher添加到隊列中,而後便會在$nextTick方法的執行隊列中加入一個flushSchedulerQueue方法(這個方法將會觸發在緩衝隊列的全部回調的執行),而後將$nextTick方法的回調加入$nextTick方法中維護的執行隊列,flushSchedulerQueue中開始會觸發一個before的方法,其實就是beforeUpdate,而後watcher.run()纔開始真正執行watcher,執行完頁面就渲染完成,更新完成後會調用updated鉤子。

$nextTick

在上文中談到了對於Vue爲什麼採用異步渲染,假如此時咱們有一個需求,須要在頁面渲染完成後取得頁面的DOM元素,而因爲渲染是異步的,咱們不能直接在定義的方法中同步取得這個值的,因而就有了vm.$nextTick方法,Vue中$nextTick方法將回調延遲到下次DOM更新循環以後執行,也就是在下次DOM更新循環結束以後執行延遲迴調,在修改數據以後當即使用這個方法,可以獲取更新後的DOM。簡單來講就是當數據更新時,在DOM中渲染完成後,執行回調函數。

經過一個簡單的例子來演示$nextTick方法的做用,首先須要知道Vue在更新DOM時是異步執行的,也就是說在更新數據時其不會阻塞代碼的執行,直到執行棧中代碼執行結束以後,纔開始執行異步任務隊列的代碼,因此在數據更新時,組件不會當即渲染,此時在獲取到DOM結構後取得的值依然是舊的值,而在$nextTick方法中設定的回調函數會在組件渲染完成以後執行,取得DOM結構後取得的值即是新的值。

<!DOCTYPE html>
<html>
<head>
    <title>Vue</title>
</head>
<body>
    <div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el: '#app',
        data: {
            msg: 'Vue'
        },
        template:`
            <div>
                <div ref="msgElement">{{msg}}</div>
                <button @click="updateMsg">updateMsg</button>
            </div>
        `,
        methods:{
            updateMsg: function(){
                this.msg = "Update";
                console.log("DOM未更新:", this.$refs.msgElement.innerHTML)
                this.$nextTick(() => {
                    console.log("DOM已更新:", this.$refs.msgElement.innerHTML)
                })
            }
        },
        
    })
</script>
</html>

異步機制#

Js是單線程的,其引入了同步阻塞與異步非阻塞的執行模式,在Js異步模式中維護了一個Event Loop,Event Loop是一個執行模型,在不一樣的地方有不一樣的實現,瀏覽器和NodeJS基於不一樣的技術實現了各自的Event Loop。瀏覽器的Event Loop是在HTML5的規範中明肯定義,NodeJS的Event Loop是基於libuv實現的。

在瀏覽器中的Event Loop執行棧Execution Stack、後臺線程Background Threads、宏隊列Macrotask Queue、微隊列Microtask Queue組成。

  • 執行棧就是在主線程執行同步任務數據結構,函數調用造成了一個由若干幀組成的棧。
  • 後臺線程就是瀏覽器實現對於setTimeout、setInterval、XMLHttpRequest等等的執行線程。
  • 宏隊列,一些異步任務的回調會依次進入宏隊列,等待後續被調用,包括setTimeout、setInterval、setImmediate(Node)、requestAnimationFrame、UI rendering、I/O等操做。
  • 微隊列,另外一些異步任務的回調會依次進入微隊列,等待後續調用,包括Promise、process.nextTick(Node)、Object.observe、MutationObserver等操做。

當Js執行時,進行以下流程:

  • 首先將執行棧中代碼同步執行,將這些代碼中異步任務加入後臺線程中。
  • 執行棧中的同步代碼執行完畢後,執行棧清空,並開始掃描微隊列。
  • 取出微隊列隊首任務,放入執行棧中執行,此時微隊列是進行了出隊操做。
  • 當執行棧執行完成後,繼續出隊微隊列任務並執行,直到微隊列任務所有執行完畢。
  • 最後一個微隊列任務出隊並進入執行棧後微隊列中任務爲空,當執行棧任務完成後,開始掃面微隊列爲空,繼續掃描宏隊列任務,宏隊列出隊,放入執行棧中執行,執行完畢後繼續掃描微隊列爲空則掃描宏隊列,出隊執行。不斷往復...。

實例#

// Step 1
console.log(1);

// Step 2
setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3);
  });
}, 0);

// Step 3
new Promise((resolve, reject) => {
  console.log(4);
  resolve();
}).then(() => {
  console.log(5);
})

// Step 4
setTimeout(() => {
  console.log(6);
}, 0);

// Step 5
console.log(7);

// Step N
// ...

// Result
/*
  1
  4
  7
  5
  2
  3
  6
*/

分析#

在瞭解異步任務的執行隊列後,回到中$nextTick方法,當用戶數據更新時,Vue將會維護一個緩衝隊列,對於全部的更新數據將要進行的組件渲染與DOM操做進行必定的策略處理後加入緩衝隊列,而後便會在$nextTick方法的執行隊列中加入一個flushSchedulerQueue方法(這個方法將會觸發在緩衝隊列的全部回調的執行),而後將$nextTick方法的回調加入$nextTick方法中維護的執行隊列,在異步掛載的執行隊列觸發時就會首先會首先執行flushSchedulerQueue方法來處理DOM渲染的任務,而後再去執行$nextTick方法構建的任務,這樣就能夠實如今$nextTick方法中取得已渲染完成的DOM結構。

在測試的過程當中發現了一個頗有意思的現象,在上述例子中的加入兩個按鈕,在點擊updateMsg按鈕的結果是3 2 1,點擊updateMsgTest按鈕的運行結果是2 3 1。

<!DOCTYPE html>
<html>
<head>
    <title>Vue</title>
</head>
<body>
    <div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el: '#app',
        data: {
            msg: 'Vue'
        },
        template:`
            <div>
                <div ref="msgElement">{{msg}}</div>
                <button @click="updateMsg">updateMsg</button>
                <button @click="updateMsgTest">updateMsgTest</button>
            </div>
        `,
        methods:{
            updateMsg: function(){
                this.msg = "Update";
                setTimeout(() => console.log(1))
                Promise.resolve().then(() => console.log(2))
                this.$nextTick(() => {
                    console.log(3)
                })
            },
            updateMsgTest: function(){
                setTimeout(() => console.log(1))
                Promise.resolve().then(() => console.log(2))
                this.$nextTick(() => {
                    console.log(3)
                })
            }
        },
        
    })
</script>
</html>

這裏假設運行環境中Promise對象是徹底支持的,那麼使用setTimeout是宏隊列在最後執行這個是沒有異議的,可是使用$nextTick方法以及自行定義的Promise實例是有執行順序的問題的,雖然都是微隊列任務,可是在Vue中具體實現的緣由致使了執行順序可能會有所不一樣,首先直接看一下$nextTick方法的源碼,關鍵地方添加了註釋,請注意這是Vue2.4.2版本的源碼,在後期$nextTick方法可能有所變動。

/**
 * Defer a task to execute it asynchronously.
 */
var nextTick = (function () {
  // 閉包 內部變量
  var callbacks = []; // 執行隊列
  var pending = false; // 標識,用以判斷在某個事件循環中是否爲第一次加入,第一次加入的時候才觸發異步執行的隊列掛載
  var timerFunc; // 以何種方法執行掛載異步執行隊列,這裏假設Promise是徹底支持的

  function nextTickHandler () { // 異步掛載的執行任務,觸發時就已經正式準備開始執行異步任務了
    pending = false; // 標識置false
    var copies = callbacks.slice(0); // 建立副本
    callbacks.length = 0; // 執行隊列置空
    for (var i = 0; i < copies.length; i++) {
      copies[i](); // 執行
    }
  }
   
   // 若是支持promise
  if (typeof Promise !== 'undefined' && isNative(Promise)) {
    var p = Promise.resolve();
    var logError = function (err) { console.error(err); };
    timerFunc = function () {
      p.then(nextTickHandler).catch(logError); // 掛載異步任務隊列
      if (isIOS) { setTimeout(noop); }
    };
  } else if (typeof MutationObserver !== 'undefined' && (
    isNative(MutationObserver) ||
    MutationObserver.toString() === '[object MutationObserverConstructor]'
  )) {
    var counter = 1;
    var observer = new MutationObserver(nextTickHandler);
    var textNode = document.createTextNode(String(counter));
    observer.observe(textNode, {
      characterData: true
    });
    timerFunc = function () {
      counter = (counter + 1) % 2;
      textNode.data = String(counter);
    };
  } else {
    // fallback to setTimeout
    /* istanbul ignore next */
    timerFunc = function () {
      setTimeout(nextTickHandler, 0);
    };
  }

  return function queueNextTick (cb, ctx) { // nextTick方法真正導出的方法
    var _resolve;
    callbacks.push(function () { // 添加到執行隊列中 並加入異常處理
      if (cb) {
        try {
          cb.call(ctx);
        } catch (e) {
          handleError(e, ctx, 'nextTick');
        }
      } else if (_resolve) {
        _resolve(ctx);
      }
    });
    //判斷在當前事件循環中是否爲第一次加入,如果第一次加入則置標識爲true並執行timerFunc函數用以掛載執行隊列到Promise
    // 這個標識在執行隊列中的任務將要執行時便置爲false並建立執行隊列的副本去運行執行隊列中的任務,參見nextTickHandler函數的實現
    // 在當前事件循環中置標識true並掛載,而後再次調用nextTick方法時只是將任務加入到執行隊列中,直到掛載的異步任務觸發,便置標識爲false而後執行任務,再次調用nextTick方法時就是一樣的執行方式而後不斷如此往復
    if (!pending) { 
      pending = true;
      timerFunc();
    }
    if (!cb && typeof Promise !== 'undefined') {
      return new Promise(function (resolve, reject) {
        _resolve = resolve;
      })
    }
  }
})();

回到剛纔提出的問題上,在更新DOM操做時會先觸發$nextTick方法的回調,解決這個問題的關鍵在於誰先將異步任務掛載到Promise對象上。

首先對有數據更新的updateMsg按鈕觸發的方法進行debug,斷點設置在Vue.js的715行,版本爲2.4.2,在查看調用棧以及傳入的參數時能夠觀察到第一次執行$nextTick方法的實際上是因爲數據更新而調用的nextTick(flushSchedulerQueue)語句,也就是說在執行this.msg = "Update";的時候就已經觸發了第一次的$nextTick方法,此時在$nextTick方法中的任務隊列會首先將flushSchedulerQueue方法加入隊列並掛載$nextTick方法的執行隊列到Promise對象上,而後纔是自行自定義的Promise.resolve().then(() => console.log(2))語句的掛載,當執行微任務隊列中的任務時,首先會執行第一個掛載到Promise的任務,此時這個任務是運行執行隊列,這個隊列中有兩個方法,首先會運行flushSchedulerQueue方法去觸發組件的DOM渲染操做,而後再執行console.log(3),而後執行第二個微隊列的任務也就是() => console.log(2),此時微任務隊列清空,而後再去宏任務隊列執行console.log(1)。

接下來對於沒有數據更新的updateMsgTest按鈕觸發的方法進行debug,斷點設置在一樣的位置,此時沒有數據更新,那麼第一次觸發$nextTick方法的是自行定義的回調函數,那麼此時$nextTick方法的執行隊列纔會被掛載到Promise對象上,很顯然在此以前自行定義的輸出2的Promise回調已經被掛載,那麼對於這個按鈕綁定的方法的執行流程即是首先執行console.log(2),而後執行$nextTick方法閉包的執行隊列,此時執行隊列中只有一個回調函數console.log(3),此時微任務隊列清空,而後再去宏任務隊列執行console.log(1)。

簡單來講就是誰先掛載Promise對象的問題,在調用$nextTick方法時就會將其閉包內部維護的執行隊列掛載到Promise對象,在數據更新時Vue內部首先就會執行$nextTick方法,以後便將執行隊列掛載到了Promise對象上,其實在明白Js的Event Loop模型後,將數據更新也看作一個$nextTick方法的調用,而且明白$nextTick方法會一次性執行全部推入的回調,就能夠明白其執行順序的問題了,下面是一個關於$nextTick方法的最小化的DEMO。

var nextTick = (function(){

    var pending = false;
    const callback = [];
    var p = Promise.resolve();

    var handler = function(){
        pending = true;
        callback.forEach(fn => fn());
    }

    var timerFunc = function(){
        p.then(handler);
    }

    return function queueNextTick(fn){
        callback.push(() => fn());
        if(!pending){
            pending = true;
            timerFunc();
        }
    }

})();


(function(){
    nextTick(() => console.log("觸發DOM渲染隊列的方法")); // 註釋 / 取消註釋 來查看效果
    setTimeout(() => console.log(1))
    Promise.resolve().then(() => console.log(2))
    nextTick(() => {
        console.log(3)
    })
})();
相關文章
相關標籤/搜索