個人前端知識梳理-VUE篇

梳理一下我的開發中遇到的一些vue問題,記錄一下我的的理解。
======================相關文章和開源庫=======================javascript

系列文章html

1.前端知識梳理-HTML,CSS篇
前端

2.前端知識梳理-ES5篇
vue

3.前端知識梳理-ES6篇
java

4.前端知識梳理-VUE篇
git

我的維護的開源組件庫es6

1.bin-ui,一個基於vue的pc端組件庫算法

2.樹形組織結構組件
element-ui

3.bin-admin ,基於bin-ui的後臺管理系統集成方案canvas

4.bin-data ,基於bin-ui和echarts的數據可視化框架

5.其他生態連接

========================================================

1 Vue的底層原理

當你把一個普通的 JavaScript 對象傳入 Vue 實例做爲 data 選項,Vue 將遍歷此對象全部的屬性,並使用 Object.defineProperty 把這些屬性所有轉爲 getter/setter。Object.defineProperty 是 ES5 中一個沒法 shim 的特性,這也就是 Vue 不支持 IE8 以及更低版本瀏覽器的緣由。 這裏文檔只推薦官方文檔,你能遇到的問題幾乎均可以在文檔中找的答案,這部分只挑幾個常見問題進行講解說明。以便你們能夠避免此類問題的發生。 

2 vue的數據機制

單向數據流: 顧名思義,數據流是單向的。數據流動方向能夠跟蹤,流動單一,追查問題的時候能夠更快捷。缺點就是寫起來不太方便。要使UI發生變動就必須建立各類 action 來維護對應的 state 

雙向數據綁定:數據之間是相通的,將數據變動的操做隱藏在框架內部。優勢是在表單交互較多的場景下,會簡化大量與業務無關的代碼。缺點就是沒法追蹤局部狀態的變化,增長了出錯時 debug 的難度 

具體表如今,通常咱們使用自定義組件(如bin-ui中的按鈕<b-button size=’small’ v-waves :disabled=’false’>我是按鈕<b-button>)中,sizedisabled就是單項數據流的體現,父級組件傳參給按鈕組件,流動單一,按鈕組件只須要對props參數作顯示便可,雙向數據綁定多體如今form表單組件,如input,v-model是實現雙向數據綁定的語法糖,本質的數據流動仍是單向的,即v-model至關於綁定:value@input=‘’監聽返回值並更新。

3 對 vue數據驅動視圖的理解

vue特色:

  1. 各部分之間的通訊,都是雙向的
  2. 採用雙向綁定:View 的變更,自動反映在 ViewModel,反之亦然

傳統的開發老是避免不了操做dom,咱們總會想到在數據返回後去操做dom元素,可是操做dom元素的開銷也是比較大的,並且不容易將視圖層和業務層分離,所以,須要改變習慣,當咱們獲取數據完成時,只須要對當前vue狀態值進行更新,剩下的刷新操做,就交給vue虛擬dom去完成吧

4 什麼是虛擬dom

虛擬DOM是幹什麼的?這就要從瀏覽器自己講起。

如咱們所知,在瀏覽器渲染網頁的過程當中,加載到HTML文檔後,會將文檔解析並構建DOM樹,而後將其與解析CSS生成的CSSOM樹一塊兒結合產生愛的結晶——RenderObject樹,而後將RenderObject樹渲染成頁面(固然中間可能會有一些優化,好比RenderLayer樹)。這些過程都存在與渲染引擎之中,渲染引擎在瀏覽器中是於JavaScript引擎(JavaScriptCore也好V8也好)分離開的,但爲了方便JS操做DOM結構,渲染引擎會暴露一些接口供JavaScript調用。因爲這兩塊相互分離,通訊是須要付出代價的,所以JavaScript調用DOM提供的接口性能不咋地。各類性能優化的最佳實踐也都在儘量的減小DOM操做次數。

而虛擬DOM幹了什麼?它直接用JavaScript實現了DOM樹(大體上)。組件的HTML結構並不會直接生成DOM,而是映射生成虛擬的JavaScript DOM結構,經過在這個虛擬DOM上實現了一個 diff 算法找出最小變動,再把這些變動寫入實際的DOM中。這個虛擬DOM以JS結構的形式存在,計算性能會比較好,並且因爲減小了實際DOM操做次數,性能會有較大提高

虛擬dom的概念這裏只是給簡單介紹一下,vue的template模板語法,包括jsx語法,本質上最後渲染時都會宣傳成render函數,而render函數中渲染的內容,其實就是虛擬dom,在前端開發中,類庫的開發和組件開發中,render函數的編寫也是必須掌握的技能之一。

5 vue生命週期的理解

vue實例有一個完整的生命週期,生命週期也就是指一個實例從開始建立到銷燬的這個過程

beforeCreate() 在實例建立之間執行,數據未加載狀態
created() 在實例建立、數據加載後,能初始化數據,dom渲染以前執行
beforeMount() 虛擬dom已建立完成,在數據渲染前最後一次更改數據
mounted() 頁面、數據渲染完成,真實dom掛載完成
beforeUpadate() 從新渲染以前觸發
updated() 數據已經更改完成,dom 也從新 render 完成,更改數據會陷入死循環
beforeDestory() 和 destoryed() 前者是銷燬前執行(實例仍然徹底可用),後者則是銷燬後執行複製代碼

vue 生命週期這裏平常開發中常見的就是created和mounted,具體應用就是好比,我在頁面加載時須要獲取新聞列表和一些原始數據,這時候咱們就能夠在created鉤子函數中調用方法去加載數據,並進行綁定,若是有時候咱們須要手動實現圖表(echarts),這個時候由於圖表的實現機制,咱們須要確保dom元素已經渲染完成,圖表必須掛載到真實dom元素時,就必須在mounted函數中去調用圖表生成方法了。

6 v-if和v-show的區別

使用了 v-if 的時候,若是值爲 false ,那麼頁面將不會有這個 html 標籤生成。

v-show 則是無論值爲 true 仍是 false ,html 元素都會存在,只是 CSS 中的 display 顯示或隱藏

使用技巧:

1.這兩個在使用時會有一些小問題,好比v-if在使用的時候,若是子元素內有使用{{}}綁定的響應屬性,如{{current.name}},這時候若是current不存在,那麼在渲染這個元素的時候「可能」會報錯,爲啥是可能,是由於v-if有可能判斷爲false而致使內層元素根本就沒渲染,而使用v-show的話就會報錯,由於不管是true或false,內層元素都會渲染,這時若是current沒有初始化,則必定會報錯。因此,咱們若是經過v-show來顯示隱藏元素的時候,須要確保內層綁定值已經初始化完成了。

2.我的推薦,若是是內層元素不變化的,如圖片,部分樣式內容等開啓隱藏的,能夠用v-show來實現,配合transition能實現比較好的性能要求。若是是須要動態渲染的可使用v-if

3.有些狀況因爲刷新機制問題,咱們能夠經過v-if來強制開啓vue進行重繪元素,如element和bin-ui,表格的生成都是基於配置寬高動態生成的,這就須要監聽窗口大小去調用組件提供的接口從新刷新大小,但還有個暴力的解決辦法就是v-if,好比彈窗的時候再渲染表格,關閉時直接=false來強制清除元素,以便保證每次都刷新重繪元素。

7 什麼是NextTick函數

this.$nextTick()函數,官方釋義是下次DOM更新循環結束以後執行的延遲迴調,通常咱們會在修改數據以後使用$nextTick(),則能夠在回調後獲取更新後的DOM元素。

這個函數的具體使用我舉個例子,上文中說道element,包括個人bin-ui重的table都會提供一個刷新大小的接口函數來手動獲取table組件並重繪大小,可是這個函數有時候你編碼了確並未實現,那這是爲何呢,緣由就是vue在更新DOM的時候是異步執行的,只要偵聽到數據變化,vue將會開啓一個隊列,並緩衝在同一事件循環中發生的全部數據變動,若是同一個watcher被屢次觸發,只會被推入到隊列中一次。這種在緩衝時去除重複數據對於避免沒必要要的計算和DOM的操做是很是重要的。而後再下一個事件循環「tick」中,vue刷新隊列並行執行實際(已去重)的工做。

例如,當你設置 vm.someData = 'new value',該組件不會當即從新渲染。當刷新隊列時,組件會在下一個事件循環「tick」中更新。多數狀況咱們不須要關心這個過程,可是若是你想基於更新後的 DOM 狀態來作點什麼,這就可能會有些棘手。雖然 Vue.js 一般鼓勵開發人員使用「數據驅動」的方式思考,避免直接接觸 DOM,可是有時咱們必需要這麼作。爲了在數據變化以後等待 Vue 完成更新 DOM,能夠在數據變化以後當即使用 Vue.nextTick(callback)。這樣回調函數將在 DOM 更新完成後被調用。例如:

<div id="example">{{message}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改數據
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
})
複製代碼

在組件內使用 vm.$nextTick() 實例方法特別方便,由於它不須要全局 Vue,而且回調函數中的 this 將自動綁定到當前的 Vue 實例上:

Vue.component('example', {
  template: '<span>{{ message }}</span>',
  data: function () {
    return {
      message: '未更新'
    }
  },
  methods: {
    updateMessage: function () {
      this.message = '已更新'
      console.log(this.$el.textContent) // => '未更新'
      this.$nextTick(function () {
        console.log(this.$el.textContent) // => '已更新'
      })
    }
  }
})
複製代碼

由於 $nextTick() 返回一個 Promise 對象,因此你可使用新的 ES2017 async/await 語法完成相同的事情:

methods: {
  updateMessage: async function () {
    this.message = '已更新'
    console.log(this.$el.textContent) // => '未更新'
    await this.$nextTick()
    console.log(this.$el.textContent) // => '已更新'
  }
}複製代碼

回到上文所述的現象上,因爲dom刷新是異步的,並且被加入到一個隊列中去,相似setTimeout異步隊列,咱們沒法肯定你當前但願刷新表格大小事件是否在當前渲染幀中執行。所以,在咱們動態計算了寬度/高度後,咱們須要準確的獲取已經更新dom元素後的組件,咱們就要在nextTick函數中去獲取執行刷新函數,這樣就能夠保證元素重繪正常而不須要使用v-if強制刷新了。 

注意這裏使用this.$nextTick是官方提供的方法,咱們也能夠用setTimeout(func,20)默認20毫秒來模擬nextTick函數,20毫秒是經驗值,但仍是推薦使用nextTick函數。 

8 Vue中key值的做用

當 Vue.js 用 v-for 正在更新已渲染過的元素列表時,它默認用「就地複用」策略。若是數據項的順序被改變,Vue 將不會移動 DOM 元素來匹配數據項的順序, 而是簡單複用此處每一個元素,而且確保它在特定索引下顯示已被渲染過的每一個元素。key的做用主要是爲了高效的更新虛擬DOM。

這塊的注意點主要在動態組件和v-for時,爲了標識獨有dom,key值通常咱們取相似id這種惟一且 不變的變量,若是僅爲了區分dom,切元素不會頻繁更新(增刪改)則可以使用index索引

9 組件通訊

1.父組件向子組件通訊:子組件經過 props 屬性,綁定父組件數據,實現雙方通訊

2.子組件向父組件通訊:將父組件的事件在子組件中經過 $emit 觸發

3.非父子組件、兄弟組件之間的數據傳遞

  •   eventBus中央事件總線let EventBus = new Vue();
  •    Vuex 數據管理
  •   3.3 provide和inject 注入(多用於組件form)

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

額外的,每次父級組件發生更新時,子組件中全部的 prop 都將會刷新爲最新的值。這意味着你應該在一個子組件內部改變 prop。若是你這樣作了,Vue 會在瀏覽器的控制檯中發出警告。

組件通訊,結合4.2的部分,如 <b-button size=’small’ v-waves :disabled=’false’>我是按鈕<b-button> 這其中size,:disabled 都是prop傳值,也就是父組件傳值給子組件,這裏有個注意點,靜態數據綁定,相似size=’small’ :disabled=’false’都屬於靜態綁定,即不會動態根據父組件 data中的響應值作變化,這裏數據綁定有個規定,若是數據值爲靜態數據切爲字符串,則能夠省略v-bind:即像size=’small’,不然全部的數據綁定都須要使用v-bind:,通常咱們會省略,已冒號開頭,後面跟隨的就是綁定值,這個值也能夠是各類數據類型,表達式,甚至是函數返回,如 :data=’[1,2,3]’ :data=’list? list : []’ :data=’33’ 等 若是是布爾值的傳值還有個建議寫法,如,直接在組件中寫 <b-button disabled>我是按鈕<b-button> 這裏 的disabled 就至關於 :disabled=’true’ 

10 插槽

這裏簡單介紹一個插槽概念,組件編寫時能夠提供props和插槽兩種類型的傳值方式,區別不一樣於props傳值的是,插槽 更加靈活多變,如上文的按鈕 

<b-button size=’small’ v-waves :disabled=’false’>我是按鈕<b-button> 按鈕中間的‘我是按鈕’就是插槽,插槽容許你往其中插入你想要的任何內容,如 

<b-button><i class=’iconfont close’></i><span>我是按鈕</span><b-button> 你能夠插入你想定製的任意內容給組件,當前前提是組件提供了一個<slot></slot>的默認插槽 這就像你小時候玩的卡帶遊戲機,你想玩什麼遊戲就插入什麼樣的卡帶便可  

可是這樣仍然不夠強大,若是我想我插入內容時默認就有個字符或者元素在那呢,這裏就是你須要在組件中<slot>….</slot>中間寫入你想要的默認元素內容就好了

類比於電腦主板,不一樣的生產廠商都會按照一種共同的標準提供不一樣的接口插槽,爲的就是方便用戶按需擴展,vue的組件插槽也提供了這種方式。有時候咱們也會有多個插槽,如 

<div class="container">
  <header>
    <!-- 咱們但願把頁頭放這裏 -->
  </header>
  <main>
    <!-- 咱們但願把主要內容放這裏 -->
  </main>
  <footer>
    <!-- 咱們但願把頁腳放這裏 -->
  </footer>
</div>
複製代碼

對於這樣的狀況,<slot> 元素有一個特殊的特性:name。這個特性能夠用來定義額外的插槽:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
複製代碼

一個不帶 name 的 <slot> 出口會帶有隱含的名字「default」。

在向具名插槽提供內容的時候,咱們能夠在一個 <template> 元素上使用 v-slot 指令,並以 v-slot 的參數的形式提供其名稱:

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template v-slot:footer>
    <p>Here's some contact info</p> </template> </base-layout> 複製代碼

如今 <template> 元素中的全部內容都將會被傳入相應的插槽。任何沒有被包裹在帶有 v-slot 的 <template> 中的內容都會被視爲默認插槽的內容。

插槽不只有默認和具名插槽,還用做用域插槽,如父級插入子組件的插槽能夠訪問子組件的數據,這就是做用域插槽,具體使用參考bin-ui,element-ui中表格的用法

11 混入mixin

在新項目運用vue實現數據綁定的同事可能會發現,每一個vue實例下都寫了一行

mixins:[mixin],那這個是幹嗎的呢

混入 (mixin) 提供了一種很是靈活的方式,來分發 Vue 組件中的可複用功能。一個混入對象能夠包含任意組件選項。當組件使用混入對象時,全部混入對象的選項將被「混合」進入該組件自己的選項

mixin混入在vue開發中十分常見,它的用法也很是的簡單,你只須要記住,公共的方法函數屬性通通均可以放置到mixin中,主要就是爲了複用代碼,減小代碼冗餘,如,公共的請求封裝,公共的分頁屬性,公共的查詢跳轉等均可以。

mixin的混入策略能夠簡單的理解爲如下幾點

1.數據對象,data中的屬性,會進行遞歸合併,類比es6的展開運算符,若是mixin中存在的,澤一組件定義的數據優先。

2.同名的鉤子函數會合並數組,如mounted ,若是組件和mixin都定義了,則都會執行,且組件內定義的後置。即混入的代碼會優先執行。

3.同名的函數會覆蓋。相似data中的屬性,會將組件內函數和mixin中函數遞歸合併,同名覆蓋,這點相似於es5的,同名函數覆蓋,組件內函數覆蓋混入函數。

12 vue如何獲取dom元素呢

jQuery時代的核心就是獲取dom元素,並進行一系列的操做,可是vue數據驅動視圖時若是有必要獲取dom元素(如獲取元素繪製echarts,繪製canvas)時該如何獲取呢。

vue提供了一個方法,首先在dom元素中編寫ref=’table’,這其實就是相似設置id或class,只是爲了給vue進行識別使用。

獲取方式也很是簡單,只須要this.$refs.tablethis.$refs[‘table’]便可得到dom元素

若是你給一個vue組件設置ref並使用this.$refs獲取的則是這個組件的實例,你能夠經過這個實例調用組件的內部方法,如上文提到的table刷新方法。

this.$refs.table.handleResize()

13 vue爲何不觸發響應式更新

這個問題通常有兩種緣由。

1.沒有設置響應式對象屬性的添加刪除

受現代 JavaScript 的限制 (並且 Object.observe 也已經被廢棄),Vue 沒法檢測到對象屬性的添加或刪除。因爲 Vue 會在初始化實例時對屬性執行 getter/setter 轉化,因此屬性必須在 data 對象上存在才能讓 Vue 將它轉換爲響應式的。例如:

var vm = new Vue({
  data:{
    a:1
  }
})
// `vm.a` 是響應式的
vm.b = 2
// `vm.b` 是非響應式的
複製代碼

對於已經建立的實例,Vue 不容許動態添加根級別的響應式屬性。可是,可使用 Vue.set(object, propertyName, value) 方法向嵌套對象添加響應式屬性。例如,對於:

Vue.set(vm.someObject, 'b', 2)複製代碼

您還可使用 vm.$set 實例方法,這也是全局 Vue.set 方法的別名:

this.$set(this.someObject,'b',2)複製代碼

有時你可能須要爲已有對象賦值多個新屬性,好比使用 Object.assign()_.extend()。可是,這樣添加到對象上的新屬性不會觸發更新。在這種狀況下,你應該用原對象與要混合進去的對象的屬性一塊兒建立一個新的對象。

// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`複製代碼
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })複製代碼

分析:這就是前面說說的對象拷貝問題,若是你只使用註釋的內容來去擴展對象的話,這裏a和b實際仍是至關於你直接寫this.someObject.a=1 , this.someObject.b=2,這裏仍然不會觸發更新。

若是採用下面的方式進行編碼,本質上實際至關於建立了一個新的對象並變動了整個響應式對象someObject的引用地址。所以,vue會從新觸發更新。

2.數組索引值設置一個項或數組長度改變

改變數組的長度或者設置一個項

var vm = new Vue({
  data(){
	return{
            list:[{id:1,name:’張三’},{id:2,name:’李四’}]
        }
  }
})
this.list[1]= {id:3,name:’王五’}

this.list.length = 1
複製代碼

實際輸出的效果就是數據變化了但不會更新視圖變化複製代碼

數組問題的解決辦法有兩種 

  1. 建立新數組總體替換原有數組值。
  2. 使用Js中的數組操做函數(本質上仍是返回了一個新的數組)也是數組的替換原理。 支持的方法有,push,pop,shift,unshift,splice,sort,reverse 不支持的方法有filter,concat,slice 

所以,咱們再遇到數組操做時,通常推薦建立新數組來操做並更新視圖,這裏就是上文中提到的深拷貝。由於咱們不能保證咱們當前操做的數組是否是包含對象或者其餘引用類型。  

最後結合上文提到的判斷精準類型,來實現一個遞歸調用的深拷貝函數 

function typeOf (obj) {
  const  toString = Object.prototype.toString
  const  map = {
    '[object Boolean]': 'boolean',
    '[object Number]': 'number',
    '[object String]': 'string',
    '[object Function]': 'function',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regExp',
    '[object Undefined]': 'undefined',
    '[object Null]': 'null',
    '[object Object]': 'object'
  }
  return map[toString.call(obj)]
}
// 深拷貝函數
function deepCopy (data) {
  const t = typeOf(data)
  let o
  if (t === 'array') {
    o = []
  } else if (t === 'object') {
    o = {}
  } else {
    return data
  }
  if (t === 'array') {
    for (let i = 0; i < data.length; i++) {
      o.push(deepCopy(data[i]))
    }
  } else if (t === 'object') {
    for (let i in data) {
      o[i] = deepCopy(data[i])
    }
  }
  return o
}
複製代碼
相關文章
相關標籤/搜索