Vue3源碼reactivity響應式篇之數組代理的方法

概覽

vue3中對于普通的代理包含對象數組兩類,對于數組的方法是重寫了許多方法,具體實現參見packages\reactivity\src\arrayInstrumentations.ts

arrayInstrumentations實際上就是一個對象,對象的屬性就是數組的方法,屬性值就是重寫的方法。

BaseReactiveHandler類的get(target,key,receiver)方法中,有如下代碼:

get(target, key, receiver) {/**省略 */const targetIsArray = shared.isArray(target);if (!isReadonly2) {let fn;if (targetIsArray && (fn = arrayInstrumentations[key])) {return fn;}if (key === "hasOwnProperty") {return hasOwnProperty;}}/**省略 */
}

可知,當對響應式對象target進行讀取操作時,會判斷target是否為數組,且是否key是否是arrayInstrumentations中實現的方法,若是,則調用Reflect.get讀取arrayInstrumentations中實現的方法并返回。

源碼分析

arrayInstrumentations中實現的方法有:concat, entries, every, filter, find, findIndex, findLast, findLastIndex, forEach, includes, indexOf, join, lastIndexOf, map, pop, push, reduce, reduceRight, reverse, shift, slice, sort, splice, unshift

arrayInstrumentations的實現如下:

const arrayInstrumentations = {__proto__: null,// 可迭代方法[Symbol.iterator]() {return iterator(this, Symbol.iterator, toReactive);},concat(...args) {return reactiveReadArray(this).concat(...args.map((x) => isArray(x) ? reactiveReadArray(x) : x));},entries() {return iterator(this, "entries", (value) => {value[1] = toReactive(value[1]);return value;});},every(fn, thisArg) {return apply(this, "every", fn, thisArg, void 0, arguments);},filter(fn, thisArg) {return apply(this, "filter", fn, thisArg, (v) => v.map(toReactive), arguments);},find(fn, thisArg) {return apply(this, "find", fn, thisArg, toReactive, arguments);},findIndex(fn, thisArg) {return apply(this, "findIndex", fn, thisArg, void 0, arguments);},findLast(fn, thisArg) {return apply(this, "findLast", fn, thisArg, toReactive, arguments);},findLastIndex(fn, thisArg) {return apply(this, "findLastIndex", fn, thisArg, void 0, arguments);},// flat, flatMap could benefit from ARRAY_ITERATE but are not straight-forward to implementforEach(fn, thisArg) {return apply(this, "forEach", fn, thisArg, void 0, arguments);},includes(...args) {return searchProxy(this, "includes", args);},indexOf(...args) {return searchProxy(this, "indexOf", args);},join(separator) {return reactiveReadArray(this).join(separator);},// keys() iterator only reads `length`, no optimisation requiredlastIndexOf(...args) {return searchProxy(this, "lastIndexOf", args);},map(fn, thisArg) {return apply(this, "map", fn, thisArg, void 0, arguments);},pop() {return noTracking(this, "pop");},push(...args) {return noTracking(this, "push", args);},reduce(fn, ...args) {return reduce(this, "reduce", fn, args);},reduceRight(fn, ...args) {return reduce(this, "reduceRight", fn, args);},shift() {return noTracking(this, "shift");},// slice could use ARRAY_ITERATE but also seems to beg for range trackingsome(fn, thisArg) {return apply(this, "some", fn, thisArg, void 0, arguments);},splice(...args) {return noTracking(this, "splice", args);},toReversed() {return reactiveReadArray(this).toReversed();},toSorted(comparer) {return reactiveReadArray(this).toSorted(comparer);},toSpliced(...args) {return reactiveReadArray(this).toSpliced(...args);},unshift(...args) {return noTracking(this, "unshift", args);},values() {return iterator(this, "values", toReactive);}
};

輔助方法

arrayInstrumentations是一個可迭代對象,實現了Symbol.iterator方法,在了解arrayInstrumentations之前,我們先了解下如下幾個個函數:reactiveReadArrayshallowReadArrayiteratorapplysearchProxynoTrackingreduce的實現。

reactiveReadArray

reactiveReadArray用于處理響應式數組的讀取操作,它的實現如下:

function reactiveReadArray(array) {const raw = toRaw(array);if (raw === array) return raw;track(raw, "iterate", ARRAY_ITERATE_KEY);return isShallow(array) ? raw : raw.map(toReactive);
}

若數組是普通數組,則直接返回數組array,否則調用track進行依賴收集,iterate表示進行的是迭代操作的依賴收集,即當使用for...ofmapforEach等迭代方法時建立依賴關系。最后分析,若響應式數組是淺響應,則返回原始數組,否則遍歷原始數組調用toReactive克隆數組

shallowReadArray

shallowReadArray用于淺層響應式數組的讀取操作,其實現如下:

function shallowReadArray(arr) {// 先將數組轉為普通數組track(arr = toRaw(arr), "iterate", ARRAY_ITERATE_KEY);return arr;
}
iterator

iterator用于處理數組的迭代操作,比如數組的valuesentries等方法,其實現如下:

function iterator(self, method, wrapValue) {// arr 是shallowReadArray返回的普通數組const arr = shallowReadArray(self);const iter = arr[method]();// 當self是深層響應式數組時,需要對迭代器的next方法進行包裝if (arr !== self && !isShallow(self)) {iter._next = iter.next;iter.next = () => {const result = iter._next();if (result.value) {// 對迭代器的next方法進行包裝,當調用next方法時,會調用wrapValue函數對值進行包裝result.value = wrapValue(result.value);}return result;};}return iter;
}

iterator的三個參數分別表示:self數組本身、method迭代方法、wrapValue包裝值的函數。在調用valuesentries等方法時會調用它。

apply

apply用于處理數組的方法調用,比如數組的mapfilter等方法,其實現如下:

function apply(self, method, fn, thisArg, wrappedRetFn, args) {const arr = shallowReadArray(self);const needsWrap = arr !== self && !isShallow(self);const methodFn = arr[method];if (methodFn !== arrayProto[method]) {const result2 = methodFn.apply(self, args);return needsWrap ? toReactive(result2) : result2;}let wrappedFn = fn;if (arr !== self) {if (needsWrap) {wrappedFn = function(item, index) {return fn.call(this, toReactive(item), index, self);};} else if (fn.length > 2) {wrappedFn = function(item, index) {return fn.call(this, item, index, self);};}}const result = methodFn.call(arr, wrappedFn, thisArg);return needsWrap && wrappedRetFn ? wrappedRetFn(result) : result;
}

apply方法接收6個參數,分別表示:self數組本身、method數組方法、fn回調函數、thisArg回調函數的this值、wrappedRetFn包裝返回值的函數、args回調函數的參數數組。

apply方法的實現邏輯如下:

  1. 調用shallowReadArray方法將數組轉為普通數組
  2. 判斷是否需要包裝,若數組不是普通數組,并且是深層響應式數組,則需要包裝,記為needsWrap
  3. 判斷數組方法method是否是數組的原生方法。若不是,則調用自定義方法,并且返回該結果;根據needsWrap判斷是否需要包裝返回值
  4. 若數組方法method是數組的原生方法,則先判斷數組是否是響應式數組,若是,則調用call方法執行原生方法arr[method],參數為fnthisArg;最后判斷,若needsWraptruewrappedReFn存在,則調用wrapped包裝結果再返回,否則直接返回。
  5. 若數組方法method是數組原生方法,且數組是響應式數組,則需要對傳入的fn進行封裝一層;若數組是深層響應式數組,即needsWraptrue需要包裝,則需要通過toReactive方法對數組項進行響應式化,否則判斷fn形參長度大于2,則調用call方法,傳入thisitemindexself
  6. 最后步驟同*(4)* ,不同的是wrappedFn不同。
searchProxy

searchProxy用于處理數組的includesindexOflastIndexOf方法,其實現如下:

function searchProxy(self, method, args) {const arr = toRaw(self);track(arr, "iterate", ARRAY_ITERATE_KEY);const res = arr[method](...args);if ((res === -1 || res === false) && isProxy(args[0])) {args[0] = toRaw(args[0]);return arr[method](...args);}return res;
}

searchProxy方法會先調用toRaw將響應式數組轉為普通數組,然后調用track收集依賴,然后調用數組的原生方法arr[method](...args),若沒找到,則判斷參數是否是代理對象,若是代理對象,則調用toRaw將代理對象轉為普通對象,最后再次調用數組的原生方法arr[method](...args),返回結果。若找到了或者參數不是代理對象,則直接返回res

noTracking

noTracking的實現如下:

function noTracking(self, method, args = []) {pauseTracking();startBatch();const res = toRaw(self)[method].apply(self, args);endBatch();resetTracking();return res;
}

noTracking方法的實現邏輯如下:

  1. 調用pauseTracking方法暫停依賴收集
  2. 調用startBatch方法開啟批量更新
  3. 調用toRaw方法將響應式數組轉為普通數組,然后調用數組的原生方法arr[method].apply(self, args),返回結果
  4. 調用endBatch方法結束批量更新
  5. 調用resetTracking方法重置依賴收集
  6. 返回結果
reduce

reduce方法用于對數組中的元素進行迭代執行fn,上次的執行結果作為下次執行的參數,返回最后的結果。其實現如下:

function reduce(self, method, fn, args) {const arr = shallowReadArray(self);let wrappedFn = fn;if (arr !== self) {if (!isShallow(self)) {wrappedFn = function(acc, item, index) {return fn.call(this, acc, toReactive(item), index, self);};} else if (fn.length > 3) {wrappedFn = function(acc, item, index) {return fn.call(this, acc, item, index, self);};}}return arr[method](wrappedFn, ...args);
}

對于數組target先判斷它是不是普通數組,若是,則調用數組的原生方法arr[method](wrappedFn, ...args),返回結果;若不是普通數組,則繼續判斷是否是淺層響應式,若不是,則調用fn.call,傳參時用toReactive將數據進行響應式處理;若是淺層響應式,則判斷fn的形參個數是否大于3,若是則包裝下fn;最后調用arr[method](wrappedFn,...args),返回結果。

輔助方法與重寫方法的關聯

輔助方法數組方法功能特點
iterator``
entries()
values()
創建響應式迭代器
自動深度轉換元素為響應式
reactiveReadArrayconcat()
join()
toReversed()
toSorted()
toSpliced()
安全讀取數組
處理深度響應式轉換
返回完全響應式的新數組
applyevery()
filter()
find()
findIndex()
findLast()
findLastIndex()
forEach()
map()
some()
通用函數應用模板
按需深度轉換結果
自動進行依賴收集
可自定義結果處理邏輯
searchProxyincludes()
indexOf()
lastIndexOf()
安全搜索處理
避免深層代理比較錯誤
收集依賴但不修改數據
noTrackingpop()
push()
shift()
splice()
unshift()
繞過響應式系統
直接操作原始數組
不觸發依賴收集
用于突變操作方法
reducereduce()
reduceRight()
專用降維處理器
特殊處理累計值邏輯
在響應式系統控制下操作

重寫的方法

  • concat(...args)

用于合并數組,返回一個新的響應式數組,不改變原數組。其實現就是先調用reactiveArray進行依賴收集,再遍歷入參數數組,若數組項是數組,則繼續調用reactiveArray收集依賴;否則不做處理,最后再調用數組的原生方法concat合并處理過后的參數數組。

  • entries

entries是一個迭代器方法,用于返回數組的鍵值對迭代器,每個迭代項是一個包含鍵和值的數組。vue3對entries的值進行了包裝

  • every

every方法用于判斷數組中的所有元素是否滿足條件,若所有元素都滿足條件,則返回true,否則返回false。有一個不滿足就會返回false。屬于數組的原生方法。

  • filter

filter方法用于過濾數組中的元素,返回一個新的數組,新數組中的元素是滿足條件的元素。若數組為空,則返回空數組。也是屬于數組的原生方法。

filter方法也是基于apply實現的,不同的是第五個參數wrappedRetFn會將最后的數組結果轉為響應式。

  • find

find方法用于查找數組中的第一個滿足條件的元素,若找到則返回該元素,否則返回undefined。屬于數組的原生方法。若找到元素,則調用toReactive方法對元素進行響應式化。

  • findLast

find作用一樣,不過findLast是從數組末尾開始查找;find是從數組開頭開始查找。

  • findIndex、**findLastIndex**都用用于查找元素索引,因此無需響應式化結果

  • forEach

forEach方法用于遍歷數組中的每個元素,沒有不返回值。屬于數組的原生方法。

  • includesindexOflastIndexOf用于判斷數組是否包含指定元素,基于searchProxy方法實現。

  • join

join方法用于將數組中的所有元素轉換為字符串,返回一個新的字符串。若數組為空,則返回空字符串。屬于數組的原生方法。

vue3中重寫join方法是基于reactiveReadArray方法實現,解決了深層響應式數組的join調用

  • map

map方法用于遍歷數組中的每個元素,返回一個新的數組,新數組中的元素是遍歷函數的返回值。若數組為空,則返回空數組。屬于數組的原生方法。

  • poppushshiftunshiftsplice

如上幾個方法會可能改變原數組的長度,為了避免數組的長度變化觸發監聽,因此暫停依賴的收集,它們是基于noTracking方法實現。

  • reducereduceRight

這兩個方法就是基于輔助方法reduce實現的,根據數組的響應式特性決定如何包裝fn
reduce是從數組的開頭進行迭代執行fnreduceRight是從數組的末尾進行迭代執行fn

  • toReversedtoSortedtoSpliced
    這三個方法都是基于reactiveReadArray實現,并且它們是ES2023新增的特性,屬于數組的原生方法。

總結

以下是針對 Vue 3 響應式系統中 arrayInstrumentations 對象的完整分析,根據內部使用的核心輔助方法進行分類總結:

方法名稱使用的輔助方法特殊處理響應式行為
[Symbol.iterator]iterator使用 toReactive 轉換每個元素? 迭代時自動轉換響應式
concatreactiveReadArray遞歸處理嵌套數組? 返回深度響應式的新數組
entriesiterator轉換值為響應式 value[1] = toReactive(value[1])? 返回鍵值對的響應式迭代器
everyapply無特殊轉換?? 依賴收集但不修改源數組
filterapply結果數組元素使用 v => v.map(toReactive) 轉換? 返回過濾后的響應式新數組
findapply找到的元素用 toReactive 轉換?? 單個元素響應式轉換
findIndexapply無特殊轉換?? 純數值返回
findLastapply找到的元素用 toReactive 轉換?? 單個元素響應式轉換
findLastIndexapply無特殊轉換?? 純數值返回
forEachapply無特殊轉換?? 僅遍歷不做轉換
includessearchProxy自定義搜索處理?? 不觸發深層響應式,但收集依賴
indexOfsearchProxy自定義搜索處理?? 不觸發深層響應式,但收集依賴
joinreactiveReadArray直接調用原始方法?? 返回字符串不響應
lastIndexOfsearchProxy自定義搜索處理?? 不觸發深層響應式,但收集依賴
mapapply無特殊轉換?? 返回非響應式數組
popnoTracking修改操作🛑 禁止依賴收集,直接修改
pushnoTracking修改操作🛑 禁止依賴收集,直接修改
reducereduce專用降維處理?? 收集依賴但不自動轉換值
reduceRightreduce專用降維處理?? 收集依賴但不自動轉換值
shiftnoTracking修改操作🛑 禁止依賴收集,直接修改
someapply無特殊轉換?? 依賴收集但不修改源數組
splicenoTracking修改操作🛑 禁止依賴收集,直接修改
toReversedreactiveReadArray直接調用 ES2023 新方法? 返回反向的響應式新數組
toSortedreactiveReadArray直接調用 ES2023 新方法? 返回排序后的響應式新數組
toSplicedreactiveReadArray直接調用 ES2023 新方法? 返回裁剪后的響應式新數組
unshiftnoTracking修改操作🛑 禁止依賴收集,直接修改
valuesiterator使用 toReactive 轉換每個元素? 返回元素的響應式迭代器

輔助方法功能說明

輔助方法用途響應式處理特點
iterator創建數組迭代器? 自動轉換元素為響應式 (toReactive)
reactiveReadArray安全讀取數組(見前文分析)? 自動處理深層響應式轉換
apply通用方法應用(在函數調用前后執行依賴跟蹤)?? 僅自動收集依賴,不處理結果轉換(除非指定回調)
searchProxy處理數組搜索方法(indexOf/includes 等)?? 避免深層代理干擾比較邏輯
reduce專用降維方法處理器?? 特殊處理累計值邏輯
noTracking禁止依賴收集的修改操作🛑 完全繞過響應式系統執行原始操作

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/94240.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/94240.shtml
英文地址,請注明出處:http://en.pswp.cn/web/94240.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何玩轉K8s:從入門到實戰

一、K8S介紹及部署 1 應用的部署方式演變 部署應用程序的方式上,主要經歷了三個階段: 傳統部署:互聯網早期,會直接將應用程序部署在物理機上 優點:簡單,不需要其它技術的參與 缺點:不能為應用…

綜合測驗:配置主dns,dhcp,虛擬主機,nfs文件共享等

綜合實驗(所有設備關閉防火墻和selinux)在appsrv上部署主dns,為example.com提供域名解析 安裝bind bind-chroot rootappsrv ~]# yum install bind bind-chroot -y編輯主配置文件,全局配置文件,正向解析文件 [rootappsrv ~]# vim /etc/named.c…

MySQL數據庫管理與索引優化全攻略

一、表管理1.建庫語法:create database if not exists 數據庫名;命名規則:僅可使用數字、字母、下劃線、不能純數字;區分字母大小寫;具有唯一性;不可使用MySQL命令或特殊字符。相關命令:show databases; …

基于大模型構建 Java 混淆的方式方法(從入門到精通 · 含開源實踐)

1. 目標與威脅模型:你到底想防什么? 把“混淆”當作成本疊加器:讓逆向者付出更多時間與技能,而不影響用戶體驗與可維護性。可用 Collberg 等提出的四指標來權衡:有效性/韌性/隱蔽性/成本(potency/resilience/stealth/cost)。近年的研究也在重審這些評估方法,建議結合可…

RabbitMQ面試精講 Day 28:Docker與Kubernetes部署實踐

【RabbitMQ面試精講 Day 28】Docker與Kubernetes部署實踐 在微服務架構日益普及的今天,消息中間件RabbitMQ已成為解耦系統、異步通信的核心組件。隨著云原生技術的成熟,如何在Docker與Kubernetes(K8s)環境中高效、高可用地部署Ra…

神經網絡和深度學習介紹

目錄 1.深度學習的介紹 2.神經網絡的構造 ①神經元結構 ②神經網絡組成 ③權重核心性 3.神經網絡的本質 4.感知器 單層感知器的局限性: 5.多層感知器 多層感知器的優勢: 6.偏置 7.神經網絡的設計 8.損失函數 常用的損失函數: 9…

云原生俱樂部-k8s知識點歸納(8)

這一部分主要講一講CRD客戶資源定義、Gateway API、Priority Class優先類、HPA自動擴縮這四部分內容。還剩下Argo CD的內容了整個k8s,至于operator的話單獨有一本書,都是實戰內容。CRD客戶資源定義先來講一講這節內容的幾個核心術語,Custom R…

【機器學習】7.隨機森林之數學原理

隨機森林(Random Forest)的數學原理核心是“決策樹基學習器 Bootstrap抽樣 特征隨機選擇” 的集成框架,通過降低單棵決策樹的方差、提升模型泛化能力來工作。以下分步驟解析其數學推導與核心邏輯: 一、 基學習器:決策…

大模型微調面試題全解析:從概念到實戰

大模型微調面試題全解析&#xff1a;從概念到實戰 微調基礎概念 本文較長&#xff0c;建議點贊收藏&#xff0c;以免遺失。更多AI大模型開發 學習視頻/籽料/面試題 都在這>>Github<< >>gitee<< &#xff08;一&#xff09;什么是微調 微調&#xf…

Linux: network: arp: arp_accept

文章目錄 接收 linux 代碼 arp協議的處理 接收 arp_accept - BOOLEAN Define behavior for gratuitous ARP frames who’s IP is not already present in the ARP table: 0 - don’t create new entries in the ARP table 1 - create new entries in the ARP table Both repli…

SpringBoot 整合 Langchain4j RAG 技術深度使用解析

目錄 一、前言 二、Langchain4j RAG介紹 2.1 什么是LangChain4j 2.2 LangChain4j RAG技術介紹 2.2.1 RAG技術原理 2.2.2 LangChain4j中的RAG實現 2.2.3 LangChain4j RAG技術優勢 2.2.4 LangChain4j RAG技術應用場景 三、LangChain4j RAG 技術深度使用 3.1 文檔加載與解…

百度深度學習面試:batch_size的選擇問題

題目在深度學習中&#xff0c;為什么batch_size設置為1不好&#xff1f;為什么batch_size設為整個數據集的大小也不好&#xff1f;&#xff08;假設服務器顯存足夠&#xff09;解答這是一個非常核心的深度學習超參數問題。即使顯存足夠&#xff0c;選擇極端的 batch_size 也通常…

AWS Fargate 完全指南:在無服務器容器中釋放應用潛能

容器化技術帶來了應用交付的革命,但管理運行容器的底層服務器集群卻帶來了新的復雜性。如何在不犧牲容器靈活性的前提下,擺脫服務器的運維重負? AWS Fargate 應運而生。它是一款為容器打造的無服務器計算引擎,讓您能夠專注于構建應用程序,而無需管理服務器。本文將帶您深…

WSL Ubuntu數據遷移

將 WSL 中的 Ubuntu 遷移到其他磁盤可有效釋放 C 盤空間并優化系統性能。以下是詳細步驟及注意事項&#xff1a;&#x1f4cd; ??遷移步驟????備份 WSL 數據&#xff08;防止意外丟失&#xff09;??以管理員身份打開 PowerShell 或命令提示符。導出 Ubuntu 實例為壓縮包…

基于STM32的病房監測系統/環境監測系統/人體健康監測系統

基于STM32的病房監測系統/環境監測系統/人體健康監測系統 持續更新&#xff0c;歡迎關注!!! 基于STM32的病房監測系統/環境監測系統/人體健康監測系統 隨著科技的進步與人們健康意識的提升&#xff0c;環境與人體健康監測的需求日益增長。在醫療、居住和工作環境中&#xff0c…

【適合中小企業應用的Flask網站部署指南】【小白指南系列】如何在Windows Server服務器上部署Flask網站和SSL證書開啟HTTPS

【適合中小企業應用的Flask網站部署指南】【小白指南系列】如何在Windows Server服務器上部署Flask網站和SSL證書開啟HTTPS 前言&#xff1a; 上一篇文章已經配置好Redis數據庫和網站雛形建立了。現在完善了一個比較重大的功能和進度之后&#xff0c;我們嘗試初步將Flask項目網…

std::exchange詳解

一、基本概念與函數原型 std::exchange 是 C++14 引入的標準庫函數,定義于 <utility> 頭文件。其核心功能是原子性地替換對象的值并返回舊值,適用于資源管理、狀態機更新等場景。 函數原型: template <class T, class U = T> T exchange(T& obj,

kubernetes-dashboard使用http不登錄

安裝了k8s v1.28&#xff0c;想要安裝kubernetes-dashboard以便可視化管理平臺&#xff0c;網上很多資料都是版本比較低的&#xff0c;自己摸索了很久&#xff0c;終于搞定了。直接上配置文件&#xff0c;拿去kubectl apply -f k8s-dashb.yml就行了。 # Copyright 2017 The Kub…

道路車道線分割數據集左車道右車道中線labelme格式3494張4類別

數據集格式&#xff1a;labelme格式(不包含mask文件&#xff0c;僅僅包含jpg圖片和對應的json文件)圖片數量(jpg文件個數)&#xff1a;3494標注數量(json文件個數)&#xff1a;3494標注類別數&#xff1a;4標注類別名稱:["center_lane","right_lane","…

12.Shell腳本修煉手冊--函數的基礎認知與實戰演練(fock炸彈!!)

Shell 函數的知識與實踐 文章目錄Shell 函數的知識與實踐Shell 函數介紹Shell 函數的語法Shell 函數的執行1. 不帶參數的函數執行2. 帶參數的函數執行Shell 函數的基礎實踐示例 1&#xff1a;簡單的 hello 函數&#xff08;驗證 “先定義后調用”&#xff09;示例 2&#xff1a…