Vue2響應式原理

来源:https://www.cnblogs.com/burc/archive/2023/03/24/17251352.html
-Advertisement-
Play Games

響應式基本原理就是,在初始化vue實例的時候,對data的每一個屬性都通過 Object.defineProperty 定義一次,在數據被set的時候,做一些操作,改變相應的視圖 ...


Vue.js 基本上遵循 MVVM(Model–View–ViewModel)架構模式,數據模型僅僅是普通的 JavaScript 對象。而當你修改它們時,視圖會進行更新。 本文講解一下 Vue 響應式系統的底層細節。

檢測變化註意事項

Vue 2.0中,是基於 Object.defineProperty 實現的響應式系統 (這個方法是 ES5 中一個無法 shim 的特性,這也就是 Vue 不支持 IE8 以及更低版本瀏覽器的原因)
vue3 中,是基於 Proxy/Reflect 來實現的

  1. 由於 JavaScript 的限制,這個 Object.defineProperty() api 沒辦法監聽數組長度的變化,也不能檢測數組和對象的新增變化。
  2. Vue 無法檢測通過數組索引直接改變數組項的操作,這不是 Object.defineProperty() api 的原因,而是尤大認為性能消耗與帶來的用戶體驗不成正比。對數組進行響應式檢測會帶來很大的性能消耗,因為數組項可能會大,比如1000條、10000條。

響應式原理

響應式基本原理就是,在 Vue 的構造函數中,對 options 的 data 進行處理。即在初始化vue實例的時候,對data、props等對象的每一個屬性都通過 Object.defineProperty 定義一次,在數據被set的時候,做一些操作,改變相應的視圖。

數據觀測

讓我們基於 Object.defineProperty 來實現一下對數組和對象的劫持。

import { newArrayProto } from './array'

class Observer {
  constructor(data){
    if (Array.isArray(data)) {
      // 這裡我們可以重寫可以修改數組本身的方法 7個方法,切片編程:需要保留數組原有的特性,並且可以重寫部分方法
      data.__proto__ = newArrayProto
      this.observeArray(data) // 如果數組中放的是對象 可以監控到對象的變化
    } else {
      this.walk(data)
    }
  }
  // 迴圈對象"重新定義屬性",對屬性依次劫持,性能差
  walk(data) {
    Object.keys(data).forEach(key => defineReactive(data, key, data[key]))
  }
  // 觀測數組
  observeArray(data) {
    data.forEach(item => observe(item))
  }
}

function defineReactive(data,key,value){
  observe(value)  // 深度屬性劫持,對所有的對象都進行屬性劫持

  Object.defineProperty(data,key,{
    get(){
      return value
    },
    set(newValue){
      if(newValue == value) return
      observe(newValue) // 修改屬性之後重新觀測,目的:新值為對象或數組的話,可以劫持其數據
      value = newValue
    }
  })
}

export function observe(data) {
  // 只對對象進行劫持
  if(typeof data !== 'object' || data == null){
    return
  }
  return new Observer(data)
}

重寫數組7個變異方法

7個方法是指:push、pop、shift、unshift、sort、reverse、splice。(這七個都是會改變原數組的)

實現思路:面向切片編程!!!

不是直接粗暴重寫 Array.prototype 上的方法,而是通過原型鏈繼承與函數劫持進行的移花接木。

利用 Object.create(Array.prototype) 生成一個新的對象 newArrayProto,該對象的 __proto__指向 Array.prototype,然後將我們數組的 __proto__指向擁有重寫方法的新對象 newArrayProto,這樣就保證了 newArrayProto 和 Array.prototype 都在數組的原型鏈上。

arr.__proto__ === newArrayProto;newArrayProto.__proto__ === Array.prototype

然後在重寫方法的內部使用 Array.prototype.push.call 調用原來的方法,並對新增數據進行劫持觀測。

let oldArrayProto = Array.prototype // 獲取數組的原型

export let newArrayProto = Object.create(oldArrayProto)

// 找到所有的變異方法
let methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice']

methods.forEach(method => {
  // 這裡重寫了數組的方法
  newArrayProto[method] = function (...args) {
    // args reset參數收集,args為真正數組,arguments為偽數組
    const result = oldArrayProto[method].call(this, ...args) // 內部調用原來的方法,函數的劫持,切片編程

    // 我們需要對新增的數據再次進行劫持
    let inserted
    let ob = this.__ob__

    switch (method) {
      case 'push':
      case 'unshift': // arr.unshift(1,2,3)
        inserted = args
        break
      case 'splice': // arr.splice(0,1,{a:1},{a:1})
        inserted = args.slice(2)
      default:
        break
    }

    if (inserted) {
      // 對新增的內容再次進行觀測
      ob.observeArray(inserted)
    }
    return result
  }
})

增加__ob__屬性

這是一個噁心又巧妙的屬性,我們在 Observer 類內部,把 this 實例添加到了響應式數據上。相當於給所有響應式數據增加了一個標識,並且可以在響應式數據上獲取 Observer 實例上的方法

class Observer {
  constructor(data) {
    // data.__ob__ = this // 給數據加了一個標識 如果數據上有__ob__ 則說明這個屬性被觀測過了
    Object.defineProperty(data, '__ob__', {
      value: this,
      enumerable: false, // 將__ob__ 變成不可枚舉 (迴圈的時候無法獲取到,防止棧溢出)
    })

    if (Array.isArray(data)) {
      // 這裡我們可以重寫可以修改數組本身的方法 7個方法,切片編程:需要保留數組原有的特性,並且可以重寫部分方法
      data.__proto__ = newArrayProto
      this.observeArray(data) // 如果數組中放的是對象 可以監控到對象的變化
    } else {
      this.walk(data)
    }
  }

}

__ob__有兩大用處:

  1. 如果一個對象被劫持過了,那就不需要再被劫持了,要判斷一個對象是否被劫持過,可以通過__ob__來判斷
// 數據觀測
export function observe(data) {
  // 只對對象進行劫持
  if (typeof data !== 'object' || data == null) {
    return
  }

  // 如果一個對象被劫持過了,那就不需要再被劫持了 (要判斷一個對象是否被劫持過,可以在對象上增添一個實例,用實例的原型鏈來判斷是否被劫持過)
  if (data.__ob__ instanceof Observer) {
    return data.__ob__
  }

  return new Observer(data)
}
  1. 我們重寫了數組的7個變異方法,其中 push、unshift、splice 這三個方法會給數組新增成員。此時需要對新增的成員再次進行觀測,可以通過__ob__調用 Observer 實例上的 observeArray 方法
人間不正經生活手冊
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 資料庫設計 一、資料庫設計概述 資料庫的生命周期 從資料庫演變過程的角度來看,資料庫的生命周期可分為兩個階段: 資料庫分析與設計階段 需求分析 概念設計 邏輯設計 物理設計 資料庫實現與操作階段 資料庫的實現 操作與監督 修改與調整 資料庫設計的目標 滿足應用功能需求:主要是指用戶當前與可預知的將來 ...
  • 一、管理方式 ElasticSearch作為最常用的搜索引擎組件,在系統架構中發揮極其重要的能力,可以極大的提升數據的載入和檢索效率;但不可否認的是,在長期的應用實踐中,也發現很多不好處理的流程和場景; 從直觀感覺上說,業務中對索引的使用主要涉及如圖的幾個流程,其核心也就是索引的結構維護與數據的流動 ...
  • 國際移動用戶識別碼( IMSI) international mobile subscriber identity 國際上為唯一識別一個移動用戶所分配的號碼。 從技術上講,IMSI可以徹底解決國際漫游問題。但是由於北美目前仍有大量的AMPS系統使用MIN號碼,且北美的MDN和MIN採用相同的編號,系 ...
  • Mac/Windows 瀏覽器開發者工具遠程調試 iPhone/Android 頁面 在移動端 Web 開發中,有時候只通過模擬器進行調試是不夠的,需要在真機環境下進行調試才能發現並解決一些問題。而移動端真機環境瀏覽器沒有開發者工具,在這種情況下,使用 PC 端瀏覽器開發者工具對移動端真機環境的 W ...
  • 問題描述 第一次搜索結果,沒有 選擇。關閉後再次打開 下拉框選項還是上一次的搜索結果。 這個現象能理解,但是也能被挑刺,遂修改——再次點擊的時候,展示全部 解決思路: 使用el-select的@visible-change方法,監聽下拉框打開關閉事件。 關閉時,將下拉選項的內容改為全部條件。 ...
  • eval,一個我曾經避之不及的函數,最近我對它產生了一點新的感觸:eval有時候也可以用,有奇效。 一般在使用js進行開發時,是不建議使用eval這類函數的。在JavaScript中,eval可以計算傳入的字元串,將其當作js代碼來執行。因為它可執行js代碼的特性,有可能被第三方利用,傳入惡意js代 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 建模 首先我們需要一些貼圖素材 貼圖素材一般可以在3dtextures網站上找到,這裡我找了2份,包含了牆的法線貼圖和潮濕地面的法線、透明度、粗糙度貼圖 通過kokomi.AssetManager將貼圖素材一次性全部載入出來,將它們應用到 ...
  • NPM(Node Package Manager)是 Node.js 的包管理工具,用來安裝各種 Node.js 的擴展。 NPM是 JavaScript 的包管理工具,也是世界上最大的軟體註冊表。有超過 60 萬個 JavaScript 代碼包可供下載,每周下載約 30 億次。NPM讓 JavaS ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...