整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          大廠二面讓我談?wù)刅ue.js框架,我慌了,我不知道啊

          Vue.js 是什么

          Vue (讀音 /vju?/,類似于 view) 是一套用于構(gòu)建用戶界面的漸進(jìn)式框架。

          Vue 的核心庫只關(guān)注視圖層,不僅易于上手,還便于與第三方庫或既有項(xiàng)目整合。另一方面,當(dāng)與現(xiàn)代化的工具鏈以及各種支持類庫結(jié)合使用時(shí),Vue 也完全能夠?yàn)閺?fù)雜的單頁應(yīng)用提供驅(qū)動(dòng)。

          初始Vue.js

          創(chuàng)建Demo.html
          
          <!-- id標(biāo)識(shí)vue作用的范圍 -->
          <div id="app">
              <!-- {{}} 插值表達(dá)式,綁定vue中的data數(shù)據(jù) -->
              {{ message }}
          </div>
          <!--導(dǎo)入vue.js庫-->
          <script src="vue.min.js"></script>
          <script>
              // 創(chuàng)建一個(gè)vue對(duì)象
              new Vue({
                  el: '#app',//綁定vue作用的范圍
                  data: {//定義頁面中顯示的模型數(shù)據(jù)
                      message: 'Hello Vue!'
                  }
              })
          </script>

          這就是聲明式渲染:Vue.js 的核心是一個(gè)允許采用簡(jiǎn)潔的模板語法來聲明式地將數(shù)據(jù)渲染進(jìn) DOM 的系統(tǒng)

          這里的核心思想就是沒有繁瑣的DOM操作,例如jQuery中,我們需要先找到div節(jié)點(diǎn),獲取到DOM對(duì)象,然后進(jìn)行一系列的節(jié)點(diǎn)操作

          創(chuàng)建代碼片段

          在vs code中創(chuàng)建代碼片段:

          文件 => 首選項(xiàng) => 用戶代碼片段 => 新建全局代碼片段/或文件夾代碼片段:vue-html.code-snippets

          注意:制作代碼片段的時(shí)候,字符串中如果包含文件中復(fù)制過來的“Tab”鍵的空格,要換成“空格鍵”的空格

          {
              "vue htm": {
                  "scope": "html",
                  "prefix": "vuehtml",
                  "body": [
                      "<!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>Document</title>",
                      "</head>",
                      "",
                      "<body>",
                      "    <div id=\"app\">",
                      "",
                      "    </div>",
                      "    <script src=\"vue.min.js\"></script>",
                      "    <script>",
                      "        new Vue({",
                      "            el: '#app',",
                      "            data: {",
                      "                $1",
                      "            }",
                      "        })",
                      "    </script>",
                      "</body>",
                      "",
                      "</html>",
                  ],
                  "description": "my vue template in html"
              }
          }

          分享目標(biāo)

          了解 Vue.js 的組件化機(jī)制了解 Vue.js 的響應(yīng)式系統(tǒng)原理了解 Vue.js 中的 Virtual DOM 及 Diff 原理

          Vue.js概述

          Vue 是一套用于構(gòu)建用戶界面的漸進(jìn)式MVVM框架。那怎么理解漸進(jìn)式呢?漸進(jìn)式含義:強(qiáng)制主張最少。

          Vue.js包含了聲明式渲染、組件化系統(tǒng)、客戶端路由、大規(guī)模狀態(tài)管理、構(gòu)建工具、數(shù)據(jù)持久化、跨平臺(tái)支持等,但在實(shí)際開發(fā)中,并沒有強(qiáng)制要求開發(fā)者之后某一特定功能,而是根據(jù)需求逐漸擴(kuò)展。 Vue.js的核心庫只關(guān)心視圖渲染,且由于漸進(jìn)式的特性,Vue.js便于與第三方庫或既有項(xiàng)目整合。

          組件機(jī)制

          定義:

          組件就是對(duì)一個(gè)功能和樣式進(jìn)行獨(dú)立的封裝,讓HTML元素得到擴(kuò)展,從而使得代碼得到復(fù)用,使得開發(fā)靈活,更加高效。

          與HTML元素一樣,Vue.js的組件擁有外部傳入的屬性prop和事件,除此之外,組件還擁有自己的狀態(tài)data和通過數(shù)據(jù)和狀態(tài)計(jì)算出來的計(jì)算屬性computed,各個(gè)維度組合起來決定組件最終呈現(xiàn)的樣子與交互的邏輯。

          數(shù)據(jù)傳遞

          每一個(gè)組件之間的作用域是孤立的,這個(gè)意味著組件之間的數(shù)據(jù)不應(yīng)該出現(xiàn)引用關(guān)系,即使出現(xiàn)了引用關(guān)系,也不允許組件操作組件內(nèi)部以外的其他數(shù)據(jù)。Vue中,允許向組件內(nèi)部傳遞prop數(shù)據(jù),組件內(nèi)部需要顯性地聲明該prop字段,如下聲明一個(gè)child組件:

          <!-- child.vue -->
          <template>
              <div>{{msg}}</div>
          </template>
          <script>
          export default {
              props: {
                  msg: {
                      type: String,
                      default: 'hello world' // 當(dāng)default為引用類型時(shí),需要使用 function 形式返回
                  }
              }
          }
          </script>

          父組件向該組件傳遞數(shù)據(jù):

          <!-- parent.vue -->
          <template>
              <child :msg="parentMsg"></child>
          </template>
          <script>
          import child from './child';
          export default {
              components: {
                  child
              },
              data () {
                  return {
                      parentMsg: 'some words'
                  }
              }
          }
          </script>

          事件傳遞

          Vue內(nèi)部實(shí)現(xiàn)了一個(gè)事件總線系統(tǒng),即EventBus。在Vue中可以使用 EventBus 來作為溝通橋梁的概念,每一個(gè)Vue的組件實(shí)例都繼承了 EventBus,都可以接受事件on和發(fā)送事件emit

          如上面一個(gè)例子,child.vue組件想修改 parent.vue組件的 parentMsg 數(shù)據(jù),怎么辦呢?為了保證數(shù)據(jù)流的可追溯性,直接修改組件內(nèi)propmsg 字段是不提倡的,且例子中為非引用類型 String,直接修改也修改不了,這個(gè)時(shí)候需要將修改 parentMsg的事件傳遞給child.vue,讓 child.vue 來觸發(fā)修改parentMsg 的事件。如:

          <!-- child.vue -->
          <template>
              <div>{{msg}}</div>
          </template>
          <script>
          export default {
              props: {
                  msg: {
                      type: String,
                      default: 'hello world'
                  }
              },
              methods: {
                  changeMsg(newMsg) {
                      this.$emit('updateMsg', newMsg);
                  }
              }
          }
          </script>

          父組件:

          <!-- parent.vue -->
          <template>
              <child :msg="parentMsg" @updateMsg="changeParentMsg"></child>
          </template>
          <script>
          import child from './child';
          export default {
              components: {
                  child
              },
              data () {
                  return {
                      parentMsg: 'some words'
                  }
              },
              methods: {
                  changeParentMsg: function (newMsg) {
                      this.parentMsg = newMsg
                  }
              }
          }
          </script>

          父組件parent.vue 向子組件child.vue傳遞了updateMsg事件,在子組件實(shí)例化的時(shí)候,子組件將updateMsg事件使用on函數(shù)注冊(cè)到組件內(nèi)部,需要觸發(fā)事件的時(shí)候,調(diào)用函數(shù)this.emit來觸發(fā)事件。

          除了父子組件之間的事件傳遞,還可以使用一個(gè) Vue 實(shí)例為多層級(jí)的父子組件建立數(shù)據(jù)通信的橋梁,如:

          const eventBus = new Vue();
          
          // 父組件中使用$on監(jiān)聽事件
          eventBus.$on('eventName', val => {
              //  ...do something
          })
          
          // 子組件使用$emit觸發(fā)事件
          eventBus.$emit('eventName', 'this is a message.');

          除了on和emit以外,事件總線系統(tǒng)還提供了另外兩個(gè)方法,once和off,所有事件如下:

          • $on:監(jiān)聽、注冊(cè)事件。
          • $emit:觸發(fā)事件。
          • $once:注冊(cè)事件,僅允許該事件觸發(fā)一次,觸發(fā)結(jié)束后立即移除事件。
          • $off:移除事件。
          • 內(nèi)容分發(fā)

          Vue實(shí)現(xiàn)了一套遵循 Web Components 規(guī)范草案 的內(nèi)容分發(fā)系統(tǒng),即將元素作為承載分發(fā)內(nèi)容的出口。

          插槽slot,也是組件的一塊HTML模板,這一塊模板顯示不顯示、以及怎樣顯示由父組件來決定。實(shí)際上,一個(gè)slot最核心的兩個(gè)問題在這里就點(diǎn)出來了,是顯示不顯示和怎樣顯示。

          插槽又分默認(rèn)插槽、具名插槽。

          默認(rèn)插槽

          又名單個(gè)插槽、匿名插槽,與具名插槽相對(duì),這類插槽沒有具體名字,一個(gè)組件只能有一個(gè)該類插槽。

          如:

          <template>
          <!-- 父組件 parent.vue -->
          <div class="parent">
              <h1>父容器</h1>
              <child>
                  <div class="tmpl">
                      <span>菜單1</span>
                  </div>
              </child>
          </div>
          </template>
          <template>
          <!-- 子組件 child.vue -->
          <div class="child">
              <h1>子組件</h1>
              <slot></slot>
          </div>
          </template>

          如上,渲染時(shí)子組件的slot標(biāo)簽會(huì)被父組件傳入的div.tmpl替換。

          具名插槽

          匿名插槽沒有name屬性,所以叫匿名插槽。那么,插槽加了name屬性,就變成了具名插槽。具名插槽可以在一個(gè)組件中出現(xiàn)N次,出現(xiàn)在不同的位置,只需要使用不同的name屬性區(qū)分即可。

          如:

          <template>
          <!-- 父組件 parent.vue -->
          <div class="parent">
              <h1>父容器</h1>
              <child>
                  <div class="tmpl" slot="up">
                      <span>菜單up-1</span>
                  </div>
                  <div class="tmpl" slot="down">
                      <span>菜單down-1</span>
                  </div>
                  <div class="tmpl">
                      <span>菜單->1</span>
                  </div>
              </child>
          </div>
          </template>
          <template>
              <div class="child">
                  <!-- 具名插槽 -->
                  <slot name="up"></slot>
                  <h3>這里是子組件</h3>
                  <!-- 具名插槽 -->
                  <slot name="down"></slot>
                  <!-- 匿名插槽 -->
                  <slot></slot>
              </div>
          </template>

          如上,slot標(biāo)簽會(huì)根據(jù)父容器給 child標(biāo)簽內(nèi)傳入的內(nèi)容的slot 屬性值,替換對(duì)應(yīng)的內(nèi)容。

          其實(shí),默認(rèn)插槽也有name屬性值,為default,同樣指定 slotname值為 default,一樣可以顯示父組件中傳入的沒有指定slot的內(nèi)容。

          作用域插槽

          作用域插槽可以是默認(rèn)插槽,也可以是具名插槽,不一樣的地方是,作用域插槽可以為slot標(biāo)簽綁定數(shù)據(jù),讓其父組件可以獲取到子組件的數(shù)據(jù)。

          如:

          <template>
              <!-- parent.vue -->
              <div class="parent">
                  <h1>這是父組件</h1>
                  <current-user>
                      <template slot="default" slot-scope="slotProps">
                          {{ slotProps.user.name }}
                      </template>
                  </current-user>
              </div>
          </template>
          <template>
              <!-- child.vue -->
              <div class="child">
                  <h1>這是子組件</h1>
                  <slot :user="user"></slot>
              </div>
          </template>
          <script>
          export default {
              data() {
                  return {
                      user: {
                          name: 'xiehuangbao1123'
                      }
                  }
              }
          }
          </script>

          如上例子,子組件 child在渲染默認(rèn)插槽slot的時(shí)候,將數(shù)據(jù) user傳遞給了 slot 標(biāo)簽,在渲染過程中,父組件可以通過slot-scope屬性獲取到user數(shù)據(jù)并渲染視圖。

          slot 實(shí)現(xiàn)原理:

          當(dāng)子組件vm實(shí)例化時(shí),獲取到父組件傳入的 slot 標(biāo)簽的內(nèi)容,存放在vm.slot中,默認(rèn)插槽為vm.slot.default,具名插槽為vm.slot.xxx,xxx為插槽名,當(dāng)組件執(zhí)行渲染函數(shù)時(shí)候,遇到<slot>標(biāo)簽,使用slot中的內(nèi)容進(jìn)行替換,此時(shí)可以為插槽傳遞數(shù)據(jù),若存在數(shù)據(jù),則可曾該插槽為作用域插槽。

          至此,父子組件的關(guān)系如下圖:

          模板渲染

          Vue.js 的核心是聲明式渲染,與命令式渲染不同,聲明式渲染只需要告訴程序,我們想要的什么效果,其他的事情讓程序自己去做。而命令式渲染,需要命令程序一步一步根據(jù)命令執(zhí)行渲染。如下例子區(qū)分:

          var arr = [1, 2, 3, 4, 5];
          
          // 命令式渲染,關(guān)心每一步、關(guān)心流程。用命令去實(shí)現(xiàn)
          var newArr = [];
          for (var i = 0; i < arr.length; i++) {
              newArr.push(arr[i] * 2);
          }
          
          // 聲明式渲染,不用關(guān)心中間流程,只需要關(guān)心結(jié)果和實(shí)現(xiàn)的條件
          var newArr1 = arr.map(function (item) {
              return item * 2;
          });

          Vue.js實(shí)現(xiàn)了iffor、事件、數(shù)據(jù)綁定等指令,允許采用簡(jiǎn)潔的模板語法來聲明式地將數(shù)據(jù)渲染出視圖。

          模板編譯

          為什么要進(jìn)行模板編譯?實(shí)際上,我們組件中的 template語法是無法被瀏覽器解析的,因?yàn)樗皇钦_的 HTML 語法,而模板編譯,就是將組件的 template編譯成可執(zhí)行的 JavaScript代碼,即將 template 轉(zhuǎn)化為真正的渲染函數(shù)。

          模板編譯分三個(gè)階段,parseoptimizegenerate,最終生成render函數(shù)。

          parse階段:

          使用正在表達(dá)式將template進(jìn)行字符串解析,得到指令、classstyle等數(shù)據(jù),生成抽象語法樹 AST

          optimize階段:

          尋找 AST 中的靜態(tài)節(jié)點(diǎn)進(jìn)行標(biāo)記,為后面 VNodepatch 過程中對(duì)比做優(yōu)化。被標(biāo)記為static 的節(jié)點(diǎn)在后面的diff 算法中會(huì)被直接忽略,不做詳細(xì)的比較。

          generate階段:

          根據(jù) AST 結(jié)構(gòu)拼接生成render函數(shù)的字符串。

          預(yù)編譯

          對(duì)于 Vue 組件來說,模板編譯只會(huì)在組件實(shí)例化的時(shí)候編譯一次,生成渲染函數(shù)之后再也不會(huì)進(jìn)行編譯。因此,編譯對(duì)組件的 runtime 是一種性能損耗。而模板編譯的目的僅僅是將template轉(zhuǎn)化為render function,而這個(gè)過程,正好可以在項(xiàng)目構(gòu)建的過程中完成。

          比如webpack的vue-loader依賴了vue-template-compiler模塊,在 webpack 構(gòu)建過程中,將template預(yù)編譯成 render 函數(shù),在 runtime 可直接跳過模板編譯過程。

          回過頭看,runtime 需要是僅僅是 render 函數(shù),而我們有了預(yù)編譯之后,我們只需要保證構(gòu)建過程中生成 render 函數(shù)就可以。與 React 類似,在添加JSX的語法糖編譯器babel-plugin-transform-vue-jsx之后,我們可以在 Vue 組件中使用JSX語法直接書寫 render 函數(shù)。

          <script>
          export default {
              data() {
                  return {
                      msg: 'Hello JSX.'
                  }
              },
              render() {
                  const msg = this.msg;
                  return <div>
                      {msg}
                  </div>;
              }
          }
          </script>

          如上面組件,使用 JSX 之后,可以在 JS 代碼中直接使用 html 標(biāo)簽,而且聲明了 render 函數(shù)以后,我們不再需要聲明 template。當(dāng)然,假如我們同時(shí)聲明了 template 標(biāo)簽和 render 函數(shù),構(gòu)建過程中,template 編譯的結(jié)果將覆蓋原有的 render 函數(shù),即 template 的優(yōu)先級(jí)高于直接書寫的 render 函數(shù)。

          相對(duì)于 template 而言,JSX 具有更高的靈活性,面對(duì)與一些復(fù)雜的組件來說,JSX 有著天然的優(yōu)勢(shì),而 template 雖然顯得有些呆滯,但是代碼結(jié)構(gòu)上更符合視圖與邏輯分離的習(xí)慣,更簡(jiǎn)單、更直觀、更好維護(hù)。

          需要注意的是,最后生成的 render 函數(shù)是被包裹在with語法中運(yùn)行的。

          小結(jié)

          Vue 組件通過 prop 進(jìn)行數(shù)據(jù)傳遞,并實(shí)現(xiàn)了數(shù)據(jù)總線系統(tǒng)EventBus,組件集成了EventBus進(jìn)行事件注冊(cè)監(jiān)聽、事件觸發(fā),使用slot進(jìn)行內(nèi)容分發(fā)。

          除此以外,實(shí)現(xiàn)了一套聲明式模板系統(tǒng),在runtime或者預(yù)編譯是對(duì)模板進(jìn)行編譯,生成渲染函數(shù),供組件渲染視圖使用。

          響應(yīng)式系統(tǒng)

          Vue.js 是一款 MVVM 的JS框架,當(dāng)對(duì)數(shù)據(jù)模型data進(jìn)行修改時(shí),視圖會(huì)自動(dòng)得到更新,即框架幫我們完成了更新DOM的操作,而不需要我們手動(dòng)的操作DOM。可以這么理解,當(dāng)我們對(duì)數(shù)據(jù)進(jìn)行賦值的時(shí)候,Vue 告訴了所有依賴該數(shù)據(jù)模型的組件,你依賴的數(shù)據(jù)有更新,你需要進(jìn)行重渲染了,這個(gè)時(shí)候,組件就會(huì)重渲染,完成了視圖的更新。

          數(shù)據(jù)模型 、計(jì)算屬性 和監(jiān)聽器

          在組件中,可以為每個(gè)組件定義數(shù)據(jù)模型data、計(jì)算屬性computed、監(jiān)聽器watch。

          數(shù)據(jù)模型:Vue 實(shí)例在創(chuàng)建過程中,對(duì)數(shù)據(jù)模型data的每一個(gè)屬性加入到響應(yīng)式系統(tǒng)中,當(dāng)數(shù)據(jù)被更改時(shí),視圖將得到響應(yīng),同步更新。data必須采用函數(shù)的方式 return,不使用 return 包裹的數(shù)據(jù)會(huì)在項(xiàng)目的全局可見,會(huì)造成變量污染;使用return包裹后數(shù)據(jù)中變量只在當(dāng)前組件中生效,不會(huì)影響其他組件。

          計(jì)算屬性:computed基于組件響應(yīng)式依賴進(jìn)行計(jì)算得到結(jié)果并緩存起來。只在相關(guān)響應(yīng)式依賴發(fā)生改變時(shí)它們才會(huì)重新求值,也就是說,只有它依賴的響應(yīng)式數(shù)據(jù)(data、prop、computed本身)發(fā)生變化了才會(huì)重新計(jì)算。那什么時(shí)候應(yīng)該使用計(jì)算屬性呢?模板內(nèi)的表達(dá)式非常便利,但是設(shè)計(jì)它們的初衷是用于簡(jiǎn)單運(yùn)算的。在模板中放入太多的邏輯會(huì)讓模板過重且難以維護(hù)。對(duì)于任何復(fù)雜邏輯,你都應(yīng)當(dāng)使用計(jì)算屬性。

          監(jiān)聽器:監(jiān)聽器watch作用如其名,它可以監(jiān)聽響應(yīng)式數(shù)據(jù)的變化,響應(yīng)式數(shù)據(jù)包括 data、prop、computed,當(dāng)響應(yīng)式數(shù)據(jù)發(fā)生變化時(shí),可以做出相應(yīng)的處理。當(dāng)需要在數(shù)據(jù)變化時(shí)執(zhí)行異步或開銷較大的操作時(shí),這個(gè)方式是最有用的。

          響應(yīng)式原理

          在 Vue 中,數(shù)據(jù)模型下的所有屬性,會(huì)被 Vue 使用Object.defineProperty(Vue3.0 使用 Proxy)進(jìn)行數(shù)據(jù)劫持代理。響應(yīng)式的核心機(jī)制是觀察者模式,數(shù)據(jù)是被觀察的一方,一旦發(fā)生變化,通知所有觀察者,這樣觀察者可以做出響應(yīng),比如當(dāng)觀察者為視圖時(shí),視圖可以做出視圖的更新。

          Vue.js 的響應(yīng)式系統(tǒng)以來三個(gè)重要的概念,Observer、Dep、Watcher。

          發(fā)布者-Observer

          1. Observe 扮演的角色是發(fā)布者,他的主要作用是在組件vm初始化的時(shí),調(diào)用defineReactive函數(shù),使用Object.defineProperty方法對(duì)對(duì)象的每一個(gè)子屬性進(jìn)行數(shù)據(jù)劫持/監(jiān)聽,即為每個(gè)屬性添加getter和setter,將對(duì)應(yīng)的屬性值變成響應(yīng)式。
          2. 在組件初始化時(shí),調(diào)用initState函數(shù),內(nèi)部執(zhí)行initState、initProps、initComputed方法,分別對(duì)data、prop、computed進(jìn)行初始化,讓其變成響應(yīng)式。
          3. 初始化props時(shí),對(duì)所有props進(jìn)行遍歷,調(diào)用defineReactive函數(shù),將每個(gè) prop 屬性值變成響應(yīng)式,然后將其掛載到_props中,然后通過代理,把vm.xxx代理到vm._props.xxx中。
          4. 同理,初始化data時(shí),與prop相同,對(duì)所有data進(jìn)行遍歷,調(diào)用defineReactive函數(shù),將每個(gè) data 屬性值變成響應(yīng)式,然后將其掛載到_data中,然后通過代理,把vm.xxx代理到vm._data.xxx中。
          5. 初始化computed,首先創(chuàng)建一個(gè)觀察者對(duì)象computed-watcher,然后遍歷computed的每一個(gè)屬性,對(duì)每一個(gè)屬性值調(diào)用defineComputed方法,使用Object.defineProperty將其變成響應(yīng)式的同時(shí),將其代理到組件實(shí)例上,即可通過vm.xxx訪問到xxx計(jì)算屬性。

          調(diào)度中心/訂閱器-Dep

          1. Dep 扮演的角色是調(diào)度中心/訂閱器,在調(diào)用defineReactive將屬性值變成響應(yīng)式的過程中,也為每個(gè)屬性值實(shí)例化了一個(gè)Dep,主要作用是對(duì)觀察者(Watcher)進(jìn)行管理,收集觀察者和通知觀察者目標(biāo)更新,即當(dāng)屬性值數(shù)據(jù)發(fā)生改變時(shí),會(huì)遍歷觀察者列表(dep.subs),通知所有的 watcher,讓訂閱者執(zhí)行自己的update邏輯。
          2. 其dep的任務(wù)是,在屬性的getter方法中,調(diào)用dep.depend()方法,將觀察者(即 Watcher,可能是組件的render function,可能是 computed,也可能是屬性監(jiān)聽watch)保存在內(nèi)部,完成其依賴收集。在屬性的setter方法中,調(diào)用dep.notify()方法,通知所有觀察者執(zhí)行更新,完成派發(fā)更新。

          觀察者-Watcher

          Watcher 扮演的角色是訂閱者/觀察者,他的主要作用是為觀察屬性提供回調(diào)函數(shù)以及收集依賴,當(dāng)被觀察的值發(fā)生變化時(shí),會(huì)接收到來自調(diào)度中心Dep的通知,從而觸發(fā)回調(diào)函數(shù)。

          而Watcher又分為三類,normal-watchercomputed-watcherrender-watcher

          • normal-watcher:在組件鉤子函數(shù)watch中定義,即監(jiān)聽的屬性改變了,都會(huì)觸發(fā)定義好的回調(diào)函數(shù)。
          • computed-watcher:在組件鉤子函數(shù)computed中定義的,每一個(gè)computed屬性,最后都會(huì)生成一個(gè)對(duì)應(yīng)的Watcher對(duì)象,但是這類Watcher有個(gè)特點(diǎn):當(dāng)計(jì)算屬性依賴于其他數(shù)據(jù)時(shí),屬性并不會(huì)立即重新計(jì)算,只有之后其他地方需要讀取屬性的時(shí)候,它才會(huì)真正計(jì)算,即具備lazy(懶計(jì)算)特性。
          • render-watcher:每一個(gè)組件都會(huì)有一個(gè)render-watcher, 當(dāng)data/computed中的屬性改變的時(shí)候,會(huì)調(diào)用該Watcher來更新組件的視圖。

          這三種Watcher也有固定的執(zhí)行順序,分別是:computed-render -> normal-watcher -> render-watcher。這樣就能盡可能地保證,在更新組件視圖的時(shí)候,computed 屬性已經(jīng)是最新值了,如果 render-watcher 排在 computed-render 前面,就會(huì)導(dǎo)致頁面更新的時(shí)候 computed 值為舊數(shù)據(jù)。

          小結(jié)

          Observer 負(fù)責(zé)將數(shù)據(jù)進(jìn)行攔截,Watcher 負(fù)責(zé)訂閱,觀察數(shù)據(jù)變化, Dep 負(fù)責(zé)接收訂閱并通知 Observer 和接收發(fā)布并通知所有 Watcher。

          Virtual DOM

          在 Vue 中,template被編譯成瀏覽器可執(zhí)行的render function,然后配合響應(yīng)式系統(tǒng),將render function掛載在render-watcher中,當(dāng)有數(shù)據(jù)更改的時(shí)候,調(diào)度中心Dep通知該render-watcher執(zhí)行render function,完成視圖的渲染與更新。

          整個(gè)流程看似通順,但是當(dāng)執(zhí)行render function時(shí),如果每次都全量刪除并重建 DOM,這對(duì)執(zhí)行性能來說,無疑是一種巨大的損耗,因?yàn)槲覀冎溃瑸g覽器的DOM很“昂貴”的,當(dāng)我們頻繁的更新 DOM,會(huì)產(chǎn)生一定的性能問題。

          為了解決這個(gè)問題,Vue 使用 JS 對(duì)象將瀏覽器的 DOM 進(jìn)行的抽象,這個(gè)抽象被稱為 Virtual DOM。Virtual DOM 的每個(gè)節(jié)點(diǎn)被定義為VNode,當(dāng)每次執(zhí)行render function時(shí),Vue 對(duì)更新前后的VNode進(jìn)行Diff對(duì)比,找出盡可能少的我們需要更新的真實(shí) DOM 節(jié)點(diǎn),然后只更新需要更新的節(jié)點(diǎn),從而解決頻繁更新 DOM 產(chǎn)生的性能問題。

          VNode

          VNode,全稱virtual node,即虛擬節(jié)點(diǎn),對(duì)真實(shí) DOM 節(jié)點(diǎn)的虛擬描述,在 Vue 的每一個(gè)組件實(shí)例中,會(huì)掛載一個(gè)$createElement函數(shù),所有的VNode都是由這個(gè)函數(shù)創(chuàng)建的。

          比如創(chuàng)建一個(gè) div:

          // 聲明 render function
          render: function (createElement) {
              // 也可以使用 this.$createElement 創(chuàng)建 VNode
              return createElement('div', 'hellow world');
          }
          // 以上 render 方法返回html片段 <div>hellow world</div>

          render 函數(shù)執(zhí)行后,會(huì)根據(jù)VNode Tree將 VNode 映射生成真實(shí) DOM,從而完成視圖的渲染。

          Diff

          Diff 將新老 VNode 節(jié)點(diǎn)進(jìn)行比對(duì),然后將根據(jù)兩者的比較結(jié)果進(jìn)行最小單位地修改視圖,而不是將整個(gè)視圖根據(jù)新的 VNode 重繪,進(jìn)而達(dá)到提升性能的目的。

          patch

          Vue.js 內(nèi)部的 diff 被稱為patch。其 diff 算法的是通過同層的樹節(jié)點(diǎn)進(jìn)行比較,而非對(duì)樹進(jìn)行逐層搜索遍歷的方式,所以時(shí)間復(fù)雜度只有O(n),是一種相當(dāng)高效的算法。

          首先定義新老節(jié)點(diǎn)是否相同判定函數(shù)sameVnode:滿足鍵值key和標(biāo)簽名tag必須一致等條件,返回true,否則false。

          在進(jìn)行patch之前,新老 VNode 是否滿足條件sameVnode(oldVnode, newVnode),滿足條件之后,進(jìn)入流程patchVnode,否則被判定為不相同節(jié)點(diǎn),此時(shí)會(huì)移除老節(jié)點(diǎn),創(chuàng)建新節(jié)點(diǎn)。

          patchVnode

          patchVnode 的主要作用是判定如何對(duì)子節(jié)點(diǎn)進(jìn)行更新,

          1. 如果新舊VNode都是靜態(tài)的,同時(shí)它們的key相同(代表同一節(jié)點(diǎn)),并且新的 VNode 是 clone 或者是標(biāo)記了 once(標(biāo)記v-once屬性,只渲染一次),那么只需要替換 DOM 以及 VNode 即可。
          2. 新老節(jié)點(diǎn)均有子節(jié)點(diǎn),則對(duì)子節(jié)點(diǎn)進(jìn)行 diff 操作,進(jìn)行updateChildren,這個(gè) updateChildren 也是 diff 的核心。
          3. 如果老節(jié)點(diǎn)沒有子節(jié)點(diǎn)而新節(jié)點(diǎn)存在子節(jié)點(diǎn),先清空老節(jié)點(diǎn) DOM 的文本內(nèi)容,然后為當(dāng)前 DOM 節(jié)點(diǎn)加入子節(jié)點(diǎn)。
          4. 當(dāng)新節(jié)點(diǎn)沒有子節(jié)點(diǎn)而老節(jié)點(diǎn)有子節(jié)點(diǎn)的時(shí)候,則移除該 DOM 節(jié)點(diǎn)的所有子節(jié)點(diǎn)。
          5. 當(dāng)新老節(jié)點(diǎn)都無子節(jié)點(diǎn)的時(shí)候,只是文本的替換。

          updateChildren

          Diff 的核心,對(duì)比新老子節(jié)點(diǎn)數(shù)據(jù),判定如何對(duì)子節(jié)點(diǎn)進(jìn)行操作,在對(duì)比過程中,由于老的子節(jié)點(diǎn)存在對(duì)當(dāng)前真實(shí) DOM 的引用,新的子節(jié)點(diǎn)只是一個(gè) VNode 數(shù)組,所以在進(jìn)行遍歷的過程中,若發(fā)現(xiàn)需要更新真實(shí) DOM 的地方,則會(huì)直接在老的子節(jié)點(diǎn)上進(jìn)行真實(shí) DOM 的操作,等到遍歷結(jié)束,新老子節(jié)點(diǎn)則已同步結(jié)束。

          updateChildren內(nèi)部定義了4個(gè)變量,分別是oldStartIdx、oldEndIdx、newStartIdx、newEndIdx,分別表示正在 Diff 對(duì)比的新老子節(jié)點(diǎn)的左右邊界點(diǎn)索引,在老子節(jié)點(diǎn)數(shù)組中,索引在oldStartIdx與oldEndIdx中間的節(jié)點(diǎn),表示老子節(jié)點(diǎn)中為被遍歷處理的節(jié)點(diǎn),所以小于oldStartIdx或大于oldEndIdx的表示未被遍歷處理的節(jié)點(diǎn)。同理,在新的子節(jié)點(diǎn)數(shù)組中,索引在newStartIdx與newEndIdx中間的節(jié)點(diǎn),表示老子節(jié)點(diǎn)中為被遍歷處理的節(jié)點(diǎn),所以小于newStartIdx或大于newEndIdx的表示未被遍歷處理的節(jié)點(diǎn)。

          每一次遍歷,oldStartIdx和oldEndIdx與newStartIdx和newEndIdx之間的距離會(huì)向中間靠攏。當(dāng) oldStartIdx > oldEndIdx 或者 newStartIdx > newEndIdx 時(shí)結(jié)束循環(huán)。

          在遍歷中,取出4索引對(duì)應(yīng)的 Vnode節(jié)點(diǎn):

          • oldStartIdx:oldStartVnode
          • oldEndIdx:oldEndVnode
          • newStartIdx:newStartVnode
          • newEndIdx:newEndVnode

          diff 過程中,如果存在key,并且滿足sameVnode,會(huì)將該 DOM 節(jié)點(diǎn)進(jìn)行復(fù)用,否則則會(huì)創(chuàng)建一個(gè)新的 DOM 節(jié)點(diǎn)。

          首先,oldStartVnode、oldEndVnode與newStartVnode、newEndVnode兩兩比較,一共有 2*2=4 種比較方法。

          情況一:當(dāng)oldStartVnode與newStartVnode滿足 sameVnode,則oldStartVnode與newStartVnode進(jìn)行 patchVnode,并且oldStartIdx與newStartIdx有移動(dòng)。

          情況二:與情況一類似,當(dāng)oldEndVnode與newEndVnode滿足 sameVnode,則oldEndVnode與newEndVnode進(jìn)行 patchVnode,并且oldEndIdx與newEndIdx左移動(dòng)。

          情況三:當(dāng)oldStartVnode與newEndVnode滿足 sameVnode,則說明oldStartVnode已經(jīng)跑到了oldEndVnode后面去了,此時(shí)oldStartVnode與newEndVnode進(jìn)行 patchVnode 的同時(shí),還需要將oldStartVnode的真實(shí) DOM 節(jié)點(diǎn)移動(dòng)到oldEndVnode的后面,并且oldStartIdx右移,newEndIdx左移。

          情況四:與情況三類似,當(dāng)oldEndVnode與newStartVnode滿足 sameVnode,則說明oldEndVnode已經(jīng)跑到了oldStartVnode前面去了,此時(shí)oldEndVnode與newStartVnode進(jìn)行 patchVnode 的同時(shí),還需要將oldEndVnode的真實(shí) DOM 節(jié)點(diǎn)移動(dòng)到oldStartVnode的前面,并且oldStartIdx右移,newEndIdx左移。

          當(dāng)這四種情況都不滿足,則在oldStartIdx與oldEndIdx之間查找與newStartVnode滿足sameVnode的節(jié)點(diǎn),若存在,則將匹配的節(jié)點(diǎn)真實(shí) DOM 移動(dòng)到oldStartVnode的前面。

          若不存在,說明newStartVnode為新節(jié)點(diǎn),創(chuàng)建新節(jié)點(diǎn)放在oldStartVnode前面即可。

          當(dāng) oldStartIdx > oldEndIdx 或者 newStartIdx > newEndIdx,循環(huán)結(jié)束,這個(gè)時(shí)候我們需要處理那些未被遍歷到的 VNode。

          當(dāng) oldStartIdx > oldEndIdx 時(shí),說明老的節(jié)點(diǎn)已經(jīng)遍歷完,而新的節(jié)點(diǎn)沒遍歷完,這個(gè)時(shí)候需要將新的節(jié)點(diǎn)創(chuàng)建之后放在oldEndVnode后面。

          當(dāng) newStartIdx > newEndIdx 時(shí),說明新的節(jié)點(diǎn)已經(jīng)遍歷完,而老的節(jié)點(diǎn)沒遍歷完,這個(gè)時(shí)候要將沒遍歷的老的節(jié)點(diǎn)全都刪除。

          此時(shí)已經(jīng)完成了子節(jié)點(diǎn)的匹配。下面是一個(gè)例子 patch 過程圖:

          總結(jié)

          借用官方的一幅圖:

          Vue.js 實(shí)現(xiàn)了一套聲明式渲染引擎,并在runtime或者預(yù)編譯時(shí)將聲明式的模板編譯成渲染函數(shù),掛載在觀察者 Watcher 中,在渲染函數(shù)中(touch),響應(yīng)式系統(tǒng)使用響應(yīng)式數(shù)據(jù)的getter方法對(duì)觀察者進(jìn)行依賴收集(Collect as Dependency),使用響應(yīng)式數(shù)據(jù)的setter方法通知(notify)所有觀察者進(jìn)行更新,此時(shí)觀察者 Watcher 會(huì)觸發(fā)組件的渲染函數(shù)(Trigger re-render),組件執(zhí)行的 render 函數(shù),生成一個(gè)新的 Virtual DOM Tree,此時(shí) Vue 會(huì)對(duì)新老 Virtual DOM Tree 進(jìn)行 Diff,查找出需要操作的真實(shí) DOM 并對(duì)其進(jìn)行更新。

          于使用 webpack 的配置,我們可以從以下幾個(gè)方面來進(jìn)行了解與認(rèn)識(shí):

          簡(jiǎn)單配置

          調(diào)整webpack配置的最簡(jiǎn)單方法是為 configureWebpack 選項(xiàng)提供對(duì)象vue.config.js

          module.exports = {
          	configureWebpack: {
          		plugins: [
          			new MyAwesomeWebpackPlugin()
          		]
          	}
          }
          

          修改加載程序的選項(xiàng)

          添加一個(gè)新的加載器

          檢測(cè)項(xiàng)目的webpack配置

          輸出重定向到配置好的文件中便于檢查:

          vue inspect > output.js
          

          通過指定路徑檢查配置子集:

          vue inspect module.rule.0
          

          以命名規(guī)則或插件為目標(biāo):

          vue inspect --rule vue
          vue inspect --plugin html
          

          列出所有命名的規(guī)則和插件:

          vue inspect --rules
          vue inspect --plugins
          

          將解析的配置用作文件,使用以下路徑

          <projectRoot>/node_modules/@vue/cli-service/webpack.config.js
          

          文件可以動(dòng)態(tài)解析并導(dǎo)出 vue-cli-service命令中使用的完全相同的webpack配置,包括來自插件甚至自定義配置的配置。

          鏈接:https://www.9xkd.com/


          主站蜘蛛池模板: 无码人妻精品一区二区蜜桃 | 国产精品一区二区三区99| 国产精品被窝福利一区 | 日本免费电影一区| 亚洲第一区视频在线观看| 无码人妻精品一区二| 午夜视频在线观看一区二区| 亚洲av无码一区二区三区乱子伦| 免费在线视频一区| 国产凸凹视频一区二区| 无码国产精品一区二区免费I6| 蜜臀AV一区二区| 久久久无码精品国产一区 | 一区二区网站在线观看| 中文字幕乱码亚洲精品一区| 色窝窝无码一区二区三区色欲 | 婷婷亚洲综合一区二区| 亚洲欧洲无码一区二区三区| 99久久精品国产一区二区成人| 日本免费一区二区久久人人澡| 精品一区二区三区免费观看| 国产精品免费大片一区二区| 国产精品毛片a∨一区二区三区| 一区一区三区产品乱码| 韩国一区二区视频| 无码一区二区波多野结衣播放搜索| 精品国产一区AV天美传媒| 中文字幕一区二区三区久久网站| 2022年亚洲午夜一区二区福利| 国产91精品一区二区麻豆网站| 3d动漫精品啪啪一区二区中| 伊人色综合一区二区三区影院视频| 午夜性色一区二区三区免费不卡视频| 精品国产乱子伦一区二区三区| 精品国产一区二区三区| 国产日韩综合一区二区性色AV| 一区二区视频免费观看| 国产成人精品无码一区二区三区 | 精品少妇ay一区二区三区| 中文字幕乱码一区久久麻豆樱花 | 国产在线不卡一区二区三区 |