細細品一品VUE

1.更改數據後,頁面會立刻重新渲染嗎?

數據變化,頁面就會重新渲染。VUE更新DOM操作是異步執行的,只要偵聽到數據變化就會開啓一個異步隊列,同步執行棧執行完畢後會執行異步執行棧,隊列中微任務先執行。


2.如何在更改數據後,看到渲染後頁面上的值?

利用vm.$nextTickVue.nextTick
頁面重新渲染,DOM更新後,會立刻執行vm.$nextTick
△兩者的區別vue.nextTick內部函數的this指向window,vm.$nextTick內部函數的this指向vue實例對象。

nextTick是怎樣實現的?

nextTick是在下次DOM更新循環結束之後執行延遲迴調,在Vue更新Dom操作是異步執行的,分爲宏任務和微任務,微任務會先執行,在nextTick實現中,會先判斷是否執行微任務,不支持的話,纔會執行宏任務,他根據執行環境分別嘗試採用Promise,MutationObserve,setImmedate,如果以上都不行採用setTimeout.

  if(typeof Promise !== 'undefined') {
    // 微任務
    // 首先看一下瀏覽器中有沒有promise
    // 因爲IE瀏覽器中不能執行Promise
    const p = Promise.resolve();

  } else if(typeof MutationObserver !== 'undefined') {
    // 微任務
    // 突變觀察
    // 監聽文檔中文字的變化,如果文字有變化,就會執行回調
    // vue的具體做法是:創建一個假節點,然後讓這個假節點稍微改動一下,就會執行對應的函數
  } else if(typeof setImmediate !== 'undefined') {
    // 宏任務
    // 只在IE下有
  } else {
    // 宏任務
    // 如果上面都不能執行,那麼則會調用setTimeout
  }


3.插值表達式{{ }}中可以寫什麼?

1.將vue中的數據寫在插值表達式中
2.直接填寫數據值(數字,布爾值,字符串,undefined,null,數組,對象)
3.寫表達式,運算表達式,邏輯表達式,三元表達式

不能寫什麼?
不可以寫語句,流程控制。

記住:插值表達式中,可以寫:data、js數據、表達式,其他的想都不要想。
注意,只要插值表達式中使用了數據,必須在data中聲明過,否則會報錯(有一種可能不報錯,是寫了在原型鏈上找不到的,之爲undefined,就不報錯啦);


4.除了未被聲明,未被渲染的數據外,還有什麼數據更改後不會渲染頁面?

1)利用索引直接設置一個數組項的時候
2)修改數組長度
3)添加刪除對象

那如何響應式的更新數組和對象?

更改數組:


1. 利用數組變異方法:push、pop、shift、unshift、splice、sort、reverse
2. 利用vm.$set/Vue.set實例方法
3. 利用vm.$set或Vue.set刪除數組中的某一項

更改對象:

vm.$set是Vue.set的別名
使用方法:Vue.set(object, propertyName, value),也就是這個意思:Vue.set(要改誰,改它的什麼,改成啥)
vm.$delete是Vue.delete的別名
使用方法:Vue.delete(object, target),也就是這個意思:Vue.delete(要刪除誰的值,刪除哪個)

爲什麼不會被渲染上頁面?

vue 2.x利用object.defineProperty實現響應式

const data = {
  name: 'shanshan',
  age: 18,
  shan: {
    name: 'shanshan',
    age: 18,
    obj: {}
  },
  arr: [1, 2, 3]
}

const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
['push', 'pop', 'shift', 'unshift' ,'sort', 'splice', 'reverse'].forEach(method => {
  arrayMethods[method] = function () {
    arrayProto[method].call(this, ...arguments);
    render();
  }
})

function defineReactive (data, key, value) {
  observer(value);
  Object.defineProperty(data, key, {
    get () {
      return value;
    },
    set (newVal) {
      if(value === newVal) {
        return;
      }
      value = newVal;
      render();
    }
  })
}

function observer (data) {
  if(Array.isArray(data)) {
    data.__proto__ = arrayMethods;
    return;
  }

  if(typeof data === 'object') {
    for(let key in data) {
      defineReactive(data, key, data[key])
    }
  }
}

function render () {
  console.log('頁面渲染啦');
}

function $set (data, key, value) {
  if(Array.isArray(data)) {
    data.splice(key, 1, value);
    return value;
  }
  defineReactive(data, key, value);
  render();
  return value;
}

function $delete(data, key) {
  if(Array.isArray(data)) {
    data.splice(key, 1);
    return;
  }
  delete data[key];
  render();
}

observer(data);

缺點:

1)需要遞歸查詢
(2)監聽不到數組不存在的索引的改變
(3)監聽不到數組長度的改變
(4)監聽不到對象的增刪

簡單說一下Vue2.x響應式數據原理

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

那你知道Vue3.x響應式數據原理嗎?

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

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

判斷當前Reflect.get的返回值是否爲Object,如果是則再通過reactive方法做代理,
這樣就實現了深度觀測。
❝
監測數組的時候可能觸發多次get/set,那麼如何防止觸發多次呢?
❞
我們可以判斷key是否爲當前被代理對象target自身屬性,也可以判斷舊值與新值是否相等,只有滿足以上兩個條件之一時,纔有可能執行trigger。

再說一下vue2.x中如何監測數組變化?

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

5.爲什麼不用index作爲數組的key值

因爲數組改變的時候頁面會重新渲染,Vue會根據key值來判斷需不需要移動元素,用索引作key的話,頁面重新渲染後,元素的key值會被重新賦值。Vue會對比渲染前後擁有同樣key的元素,如果發現有變動,就會再生成一個元素,如果用索引作爲key值,假如你reverse之後,所有元素會被重新生成。

6.處理複雜邏輯,計算屬性(computed)和方法(method)最本質的區別;

計算屬性是基於響應式依賴進行緩存的,計算屬性的值一直存在與緩存中(緩存可以減少開銷),只要他依賴的data數據不變,每次訪問計算屬性,會like返回緩存的結果,而不是再次執行函數。而方法是每次觸發新的渲染,調用方法鍾會再次執行函數。


7.偵聽器(watch) VS 計算屬性(computed)

computed:本質是一個具備緩存的watcher,依賴的屬性發生變化就會更新視圖,適用於多個數據影響一個數據,比較消耗性能的計算場景。當表達式過於複雜時,模板中放過多邏輯會讓模板難以維護,可以將複雜的邏輯放入計算屬性中。
watch:沒有緩存性,更多的是觀察作用,監聽某些數據,執行回調,深度監聽對象的屬性的時候(deep:true),便於對對象中的每一個屬性進行監聽,一個數據影響多個數據。

(1)都可以觀察和響應VUE實例的數據變動
(2)watch:一個數據影響多個數據;計算屬性:多個數據影響一個數據
(3)偵聽器中可以異步執行,計算屬性不可以。

8.組件template中的data爲什麼是一個函數?

return{
data:
}

一個組件被複用多次會創建多個實例。本質上這些實例用的都是同一個構造函數,若data是對象,對象屬於引用類型,會影響到所有實例,是函數的話,每個實例可以維護一份被返回的對象的獨立拷貝,他可以保證組件不通實例之間的data不衝突。

9.v-text VS Mustache

<span v-text="msg"></span>
<!-- 和下面的一樣 -->
<span>{{msg}}</span>

區別:
v-text替換元素中所有的文本,Mustache只替換自己,不清空元素內容
v-text 優先級高於 {{ }}


10.textContent VS innerText

設置文本替換時,兩者都會把指定節點下的所有子節點也一併替換掉。
textContent 會獲取所有元素的內容,包括 <script><style>元素,然而 innerText 不會。
innerText 受 CSS 樣式的影響,並且不會返回隱藏元素的文本,而textContent會。
由於 innerText 受 CSS 樣式的影響,它會觸發重排(reflow),但textContent 不會。
innerText 不是標準制定出來的 api,而是IE引入的,所以對IE支持更友好。textContent雖然作爲標準方法但是隻支持IE8+以上的瀏覽器,在最新的瀏覽器中,兩個都可以使用。
綜上,Vue這裏使用textContent是從性能的角度考慮的。


11.阻止默認事件

注意
使用修飾符時,順序很重要。相應的代碼會以同樣的順序產生。因此,
v-on:click.prevent.self 會阻止所有的點擊的默認事件
v-on:click.self.prevent 只會阻止對元素自身點擊的默認事件
不要把 .passive 和 .prevent 一起使用,因爲 .prevent 將會被忽略,同時瀏覽器可能會向你展示一個警告。


12.v-model原理

雙向數據綁定,本質是一個語法糖,可以看成是input+value方法的語法糖。可以根據model屬性的prop和event屬性來進行自定義。

VUE事件綁定原理:原生事件綁定是通過addeventlistener綁定給真實元素。
組件事件綁定是通過Vue自定義的$on實現的

手寫v-model
步驟:(1)通過綁定的數據給元素設置value(2)觸發input事件,去更改數據的值(3)
更改數據後,同步input的value值。

Vue.directive('mymodel',{
    bind(el,binding,vnode){
        const vm = vnode.context;
        const{value,expression} = binding;
        el.value = value;
        el.oninput = function(e){
            const inputVal = el.value;
            vm[expression] = inputVal;
        }
    },
    update(el,binding){
        const {value} = binding;
        el.value = value
    }
})

13.key值的唯一性

key就是在更新組件時,判斷兩個節點是否相同,相同就複用,不同就創建新的刪除舊的。(強制更新組件,避免原地複用帶來的副作用)

不用key
就地複用節點,a.key和b.key都是undefined,所以不會重新創建和刪除節點,只會在節點的屬性層面上進行比較和更新。
無法維持組件的狀態:動態效果啊開關等狀態
性能下降:要複用很多節點,順序和原來的完全不同,創建和刪除節點的數量就會比帶key的時候增加很多。

不建議將數組的索引作爲key值:當改變數組時,頁面會重新渲染,Vue會根據key值來判斷要不要移動元素。例如使用reverse當頁面重新渲染時,當使用數組的索引作爲key值時,頁面重新渲染後,元素的key值會重新被賦值。Vue會比對渲染前後擁有同樣key的元素,發現有變動,就會再生成一個元素,如果用索引作key值得話,那麼此時,所有的元素都會被重新生成。

14.v-for和v-if不能一起使用

v-for 比 v-if 具有更高的優先級,也許會在重新渲染的時候遍歷整個列表

15. .v-model VS .sync

Vue 1.x :.sync可以在子組件中修改父組件的狀態,會造成整個狀態的變換很難追溯。
vue 2.0 : 移除
vue 3.0 : 迴歸,語法糖和v-model實現原理一樣
(1)兩個都是用來實現雙向數據傳遞的,都是語法糖,最終通過prop + 事件來達成目的。
(2)當一個組件對外只暴露一個受控狀態,並且都符合統一標準的時候,用v-model來處理,.sync更靈活,雙向數據傳遞都可以使用。


16.keep-alive

包裹動態組件的時候,會緩存不活動的組件實例,而不是銷燬它們。keep-alive是一個抽象組件;他自身不會渲染一個DOM元素,也不會出現在父組件中。
常用的兩個屬性:include/exclude 允許組件有條件進行緩存。
兩個生命週期:actived/deactiveted 得知當前組件是否處於活躍狀態。

運用了LRU算法。


17.組建通信

父子:(1)prop:父組件傳遞給子組件時;
(2)$emit,$on:子組件傳遞給父組件時;
(3)ref:父組件中訪問子實例的數據;$ref只會在組件渲染完成之後生效
(4)provide & inject :祖先組件提供數據(provide),子組件按需注入(inject),會將組件的阻止方式耦合在一起,使組件重構困難,不推薦。
兄弟組件:(1)eventBus(事件總線)
(1)$attrs:祖先組件 ->子孫組件【撰寫基礎組件】
(2)$listenner:子孫組件中執行祖先組件的函數【監聽器】
(3)$root:子組件中訪問根實例
跨級組建通信:$attr .$listener provide inject


18.Vue生命週期

(1)beforeCreate:new vue()之後觸發的第一各鉤子,當前階段data,methods,computed,watch上的數據和方法都不能訪問。
(2)created:實例創建完成之後發生,完成了數據觀測,可以使用數據,更改數據,在這裏更改數據不會觸發update,可以做初始數據的獲取,無法與Dom進行交互(非想交互可以通過vm.$nextTick來訪問
(3)beforeMount:發生在掛在之前,在這之前template模板已導入,渲染函數編譯。DOM穿件完成,即將開始渲染,此時可以更改數據,不會觸發update。
(4)mouted:掛載完成之後發生,真實DOM掛載完畢,數據完成雙向綁定,可以訪問到DOM節點,使用$refs屬性。
(5)beforeUpdate:更新之前,響應式數據發生更新,虛擬dom重新渲染之前被觸發,可以在這個階段進行更改數據,不會被渲染。
(6)Updated:更新完成之後,dom已經完成更新,避免在此進行更改數據,可能會導致無限循環。
(7)beforeDestory:實例銷燬前,實例被使用,收尾(清楚定時器)
(8)destroyed:實例銷燬之後,只剩dom空殼,組件被拆解,數據綁定被卸除,監聽被移除,子例銷燬。
△除了beforeCreate和Create鉤子之外,其他鉤子均在服務器端渲染期間不被調用。
updated不要修改data裏面賦值的數據,否則會導致死循環。

①beforeCreate鉤子調用在initState之前,initState作用是對props,methods,data,computed,watch等做初始化處理。
②mount組件核心是先實例化一個渲染watcher在他的回調函數中調用了updatacomponent方法,在執行vm.render()函數渲染虛擬節點之前,把虛擬節點patch到真實Dom後,執行mouted鉤子。
③接口請求一般放在mounted中,但是服務端渲染時不支持mounted,需要放到eated中。

19.什麼是虛擬DOM

MVC MVP架構模式希望可以從代碼組織方式來降低維護複雜引用程序的難度,DOM還是要操作只是將數據,操作,視圖分離了。所以有了MVVM:只要在模板中聲明試圖組件是和什麼數據綁定的,雙向綁定引擎就會在狀態更新的時候自動更新視圖。MVVM會降低我們的維護狀態,大大減少了代碼中的試圖更新邏輯。

什麼是虛擬DOM?
創建虛擬DOM就是爲了更高效,頻繁的更新DOM。是一個常規的JS對象。

原理:

用新渲染的對象樹去和舊的對比,記錄這兩棵樹的差異。記錄下來的不同就是我們需要對頁面真正的DOM操作,然後把它們應用到真正的DOM樹上,頁面就變更了。這樣就可做到:視圖的結構整個全新渲染了,最後操作DOM的時候只變更有不同的地方。

即Virtual Dom算法:
用JS對象結構表示DOM樹結構,然後用這個樹構建一個真正的DOM樹,插到文檔中;當狀態變更的時候,重新構造一顆新的對象樹,用新的樹和舊的樹進行比較,記錄兩棵樹的差異,把差異應用到真正的DOM樹上,視圖就更新了。

虛擬DOM本質上是在JS和DOM上做了一個緩存。

虛擬DOM核心:diff算法

在實際的代碼中,會對新舊兩棵樹進行深度的遍歷,給每一個節點進行標
記。然後在新舊兩棵樹的對比中,將不同的地方記錄下來。
function diff(oldTree, newTree) {
 var index = 0 // 當前節點的標誌
 var patches = {} // 記錄每個節點差異的地方
 dfsWalk(oldTree, newTree, index, patches)
 return patches
}
function dfsWalk(oldNode, newNode, index, patches) {
 // 對比 newNode 和 oldNode 的差異地方進行記錄
 patches[index] = [...]
 diffChildren(oldNode.children, newNode.children, index, patches)
}
function diffChildren(oldChildren, newChildren, index, patches) {
 let leftNode = null
 var currentNodeIndex = index
 oldChildren.forEach((child, i) => {
 var newChild = newChildren[i]
 currentNodeIndex = (leftNode && leftNode.count) // 計算節點的
標記
 ? currentNodeIndex + leftNode.count + 1
 : currentNodeIndex + 1
 dfsWalk(child, newChild, currentNodeIndex, patches) // 遍歷子節
點
 leftNode = child
 })
}
同理,有 p 是 patches[1], ul 是 patches[3],以此類推
patches 指的是差異變化,這些差異包括:
1、節點類型的不同,
2、節點類型相同,但是屬性值不同,文本內容不同。
所以有這麼幾種類型:
var REPLACE = 0, // replace 替換
 REORDER = 1, // reorder 父節點中子節點的操作
 PROPS = 2, // props 屬性的變化
 TEXT = 3 // text 文本內容的變化
如果節點類型不同,就說明是需要替換,例如將 div 替換成 section,就記錄
渡一教育
9
下差異:
patches[0] = [{
 type: REPLACE,
 node: newNode // section
},{
 type: PROPS,
 props: {
 id: 'container'
 }
}]
在標題二中構建了真正的 DOM 樹的信息,所以先對那一棵 DOM 樹進行
深度優先的遍歷,遍歷的時候同
patches 對象進行對比,找到其中的差異,然後應用到 DOM 操作中。
function patch(node, patches) {
 var walker = {index: 0} // 記錄當前節點的標誌
 dfsWalk(node, walker, patches)
}
function dfsWalk(node, walker, patches) {
 var currentPatches = patches[walker.index] // 這是當前節點的差異
 var len = node.childNodes
 ? node.childNodes.length
 : 0
 for (var i = 0; i < len; i++) { // 深度遍歷子節點
 var child = node.childNodes[i]
 walker.index++
 dfsWalk(child, walker, patches)
 }
 if (currentPatches) {
 applyPatches(node, currentPatches) // 對當前節點進行 DOM 操作
 } }
// 將差異的部分應用到 DOM 中
function applyPatches(node, currentPatches) {
 currentPatches.forEach((currentPatch) => {
 switch (currentPatch.type) {
 case REPLACE:
 var newNode = (typeof currentPatch.node === 'string')
 ? document.createTextNode(currentPatch.node)
 : currentPatch.node.render()
 node.parentNode.replaceChild(newNode, node)
 break;
 case REORDER:
 reorderChldren(node, currentPatch.moves)
 break
 case PROPS:
 setProps(node, currentPatch.props)
 break
 case TEXT:
 if (node.textContent) {
 node.textContent = currentPatch.content
 } else {
 node.nodeValue = currentPatch.content
 }
 break
 default:
 throw new Error('Unknown patch type ' + 
currentPatch.type)
 }
 })
}

20

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章