首頁 > 軟體

一起來看看Vue的核心原理剖析

2022-03-23 19:00:49

前言:

  • 相信大家閱讀過很多關於Vue2的文章,我也閱讀過很多,但是大部分文章介紹的都是如何在專案中進行應用,技術點如果使用,功能如何實現;
  • 今天小編為大家帶來這篇Vue2的核心原理剖析就是為大家介紹我們常用的Vue2他是如何實現的核心內容,我們簡單程式碼的背後究竟他做了哪些,讓大家能夠 知其然,知其所以然

學習目標:

  • 瞭解Object.defineProperty原理
  • 瞭解set、get關聯使用
  • 瞭解資料反應到識圖的過程
  • 瞭解檢視更換如何影響資料
  • 掌握MVVM

Object.defineProperty

 <script>
    // 1. 字面量定義
    let data = {
      name: 'aa'
    }
    data.name = 'bb' // 這種情況下我們並不能知道name屬性發生了變化

    // 2. Object.defineProperty  
    let data1 = {}
    Object.defineProperty(data1, 'name', {
      // 當我們存取data1的name屬性的時候自動呼叫的方法
      // 並且get函數的返回值就是你拿到的值
      get() {
        console.log('你存取了data1的name屬性')
        return 'aa'
      },
      // 當我們設定修改name屬性的時候自動呼叫的函數
      // 並且屬性最新的值會被當成實參傳入進來
      set(newValue) {
        console.log('你修改了data1的name屬性最新的值為', newValue)
        // 這個位置 只要你修改了name屬性就會得到執行
        // 所以如果你想要在name變化的時候 完成一些自己的事情
        // 都可以放到這裡來執行
        // 1. ajax()
        // 2. 操作一塊dom區域
      }
    })
    // 以上是js中物件定義的另外一種方案,可以在存取屬性和設定屬性的時候自動呼叫對應的函數
    // 存取屬性:data.name  data['name']  
    // 設定屬性:data.name = 'bb'  data['name'] = 'bb'
  </script>

 響應式的核心API   

get、set

 <script>
    // let data = {
    //   name: 'aa'
    // }
    let data = {}
    let _name = 'aa'
    Object.defineProperty(data, 'name', {
      get() {
        console.log('你存取了data1的name屬性')
        return _name
      },

      set(newValue) {
        console.log('你修改了data1的name屬性最新的值為', newValue)
        _name = newValue
      }
    })
    // 問題產生的原因:get中直接返回了一個固定的值,並且set函數中新值拿到了但是沒有做任何事情
    // 解決方案:通過宣告一箇中間變數,讓get函數中return出去這個變數
    // 並且在set函數中把最新的值設定到這個中間變數身上,起到一個set和get操作的一個
    // 資料的效果
  </script>

資料反應到檢視

資料的變化可以引起檢視的變化(通過操作dom把資料放到對應的位置上去 如果資料變化之後就用資料最新的值再重新放一次)

方案一:命令式操作

1.document.querySelector(’#app’).innerText = data.name

2.set函數中重新執行一下document.querySelector(’#app’).innerText = data.name

方案二:宣告式渲染

v-text指令的實現

 <p v-text="name"></p>

核心邏輯:通過‘模板編譯’找到標記了v-text的元素,然後把對應的資料通過操作domapi放上去

 <div id="app">
   <p v-text="name"></p>
   <p></p>
 </app>

1.通過app根元素找到所有的子節點 (元素節點,文位元組點…) -> dom.nodeChilds

2.通過節點型別篩選出元素節點 (p) -> nodeType 1元素節點 3文位元組點

3.通過v-text找到需要設定的具體的節點 <p v-text></p>4.找到繫結了v-text標記的元素 拿到它身上所有的屬性 id class v-text=“name”

5.通過v-text=“name” 拿到指令型別 ‘v-text’ 拿到需要繫結的資料的屬性名 ‘name’

6.判斷當前是v-text指令 然後通過操作domapi 把name屬性對應的值放上去 node.innerText = data[name]

以上整個過程可以稱作‘模板編譯’

檢視的變化反映到資料

input元素 v-model雙向繫結
M -> V
V -> M

M -> V

1.通過app根元素找到所有的子節點 (元素節點,文位元組點…) -> dom.nodeChilds

2.通過節點型別篩選出元素節點 (p) -> nodeType 1元素節點 3文位元組點

3.通過v-text找到需要設定的具體的節點 <p v-text></p>4.找到繫結了v-text標記的元素 拿到它身上所有的屬性 id class v-text=“name”

5.通過v-model=“name” 拿到指令型別 ‘v-model’ 拿到需要繫結的資料的屬性名 ‘name’

6.判斷當前是v-model指令 然後通過操作domapi 把name屬性對應的值放上去node.value = data[name]

v-model和v-text除了指令型別不一致,使用的dom api不一致 其它的步驟是完全一致的

V -> M

本質:事件監聽在回撥函數中拿到input中輸入的最新的值然後賦值給繫結的屬性

 node.addEventListener('input',(e)=>{
   data[name] = e.target.value
 })

以上總結:

1.資料的響應式

2.資料變化影響檢視

3.檢視變化影響資料

4.指令是如何實現的(常規實現邏輯)

優化工作:

1.通用的資料響應式處理

   data(){
       return {
          name:'cp',
          age:28
      }
   }

基於現成的資料,然後都處理成響應式 

 Object.keys(data) // 由所有的物件的key組成的陣列
    Object.keys(data).forEach(key=>{
      // key 屬性名
      // data[key]  屬性值
      // data 原物件
      // 將所有的key都轉成get和set的形式
      defineReactive(data,key,data[key])
    })
    function defineReactive(data,key,value){
      Oject.defineProperty(data, key, {
        get(){
          return value
        },
        set(newValue){
          value = newValue
        }
      })
    }

2.釋出訂閱模式

問題:

  <div>
      <p v-text="name"></p>
      <p v-text="name"></p>
      <div  v-text="age"></div>
    </div>

name發生變化之後 我需要做的事情是更新倆個p標籤,而現在不管你更新了哪個資料,所有的標籤都會被重新操作賦值,無法做到精準更新

解決問題的思路:

1.資料發生變化之後最關鍵的程式碼是什麼?

 node.innerText = data[name]

2.設計一個儲存結構

每一個響應式資料可能被多個標籤繫結 是一個‘一對多’的關係

 {
        name: [()=>{ node(p1).innerText = data[name]},()=>{ node(p2).innerText = data[name]}...]
      }

釋出訂閱(自定義事件) 解決的問題就是 ‘1對多’的問題

實現簡單的釋出訂閱模式:

瀏覽器的事件模型

dom.addEventLister(‘click’,()=>{})

只要呼叫click事件,所有繫結的回撥函數都會執行 顯然是一個1對多的關係

  const Dep = {
      map:{},
      collect(eventName,fn){
        // 如果從來沒有收集過當前事件就先初始化成陣列
        if(!this.map[eventName]){
          this.map[eventName] = []
        }
        // 已經初始化好了就直接往裡面push新增
        this.map[eventName].push(fn)
      },
      trigger(eventName){
        this.map[eventName].forEach(fn=>fn())
      }
    }

使用釋出訂閱模式優化現存問題

先前的寫法 不管是哪個資料發生變化我們都是粗暴的執行一下compile函數即可

現在的寫法 我們在compile函數初次執行的時候 完成更新函數的收集 然後在資料變化的時候

通過資料的key找到相對應的更新函數 依次執行 達到精準更新的效果

總結

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!  


IT145.com E-mail:sddin#qq.com