<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
本文接Vue2響應式系統 、Vue2 響應式系統之分支切換 ,響應式系統之巢狀、響應式系統之深度響應 還沒有看過的小夥伴需要看一下。
import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { list: ["hello"], }; observe(data); const updateComponent = () => { for (const item of data.list) { console.log(item); } }; new Watcher(updateComponent); data.list = ["hello", "liang"];
先可以一分鐘思考下會輸出什麼。
雖然 的值是陣列,但我們是對 進行整體賦值,所以依舊會觸發 的 ,觸發 進行重新執行,輸出如下:list
data.list
data.list
set
Watcher
import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { list: ["hello"], }; observe(data); const updateComponent = () => { for (const item of data.list) { console.log(item); } }; new Watcher(updateComponent); data.list.push("liang");
先可以一分鐘思考下會輸出什麼。
這次是呼叫 方法,但我們對 方法什麼都沒做,因此就不會觸發 了。push
push
Watcher
為了讓 還有陣列的其他方法也生效,我們需要去重寫它們,通過push
代理模式 我們可以將陣列的原方法先儲存起來,然後執行,並且加上自己額外的操作。
/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */ /* export function def(obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true, }); } */ import { def } from "./util"; const arrayProto = Array.prototype; export const arrayMethods = Object.create(arrayProto); const methodsToPatch = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse", ]; /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method]; def(arrayMethods, method, function mutator(...args) { const result = original.apply(this, args); /*****************這裡相當於呼叫了物件 set 需要通知 watcher ************************/ // 待補充 /**************************************************************************** */ return result; }); });
當呼叫了陣列的 或者其他方法,就相當於我們之前重寫屬性的 ,上邊待補充的地方需要做的就是通知 中的 。push
set
dep
Watcher
export function defineReactive(obj, key, val, shallow) { const property = Object.getOwnPropertyDescriptor(obj, key); // 讀取使用者可能自己定義了的 get、set const getter = property && property.get; const setter = property && property.set; // val 沒有傳進來話進行手動賦值 if ((!getter || setter) && arguments.length === 2) { val = obj[key]; } const dep = new Dep(); // 持有一個 Dep 物件,用來儲存所有依賴於該變數的 Watcher let childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); } return value; }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val; if (setter) { setter.call(obj, newVal); } else { val = newVal; } dep.notify(); }, }); }
如上邊的程式碼,之前的 是通過閉包,每一個屬性都有一個各自的 ,負責收集 和通知 。dep
dep
Watcher
Watcher
那麼對於陣列的話,我們的 放到哪裡比較簡單呢?dep
回憶一下現在的結構。
const data = { list: ["hello"], }; observe(data); const updateComponent = () => { for (const item of data.list) { console.log(item); } }; new Watcher(updateComponent);
上邊的程式碼執行過後會是下圖的結構。
list
屬性在閉包中擁有了 屬性,通過 ,收集到了包含 函數的 。Dep
new Watcher
updateCompnent
Watcher
同時因為 的 是陣列,也就是物件,通過上篇 list
value
["hello"]
響應式系統之深度響應 (opens new window)我們知道,它也會去呼叫 函數。Observer
那麼,我是不是在 中也加一個 就可以了。Observer
Dep
這樣當我們呼叫陣列方法去修改 的值的時候,去通知 中的 就可以了。['hello']
Observer
Dep
按照上邊的思路,完善一下 類。Observer
export class Observer { constructor(value) { /******新增 *************************/ this.dep = new Dep(); /************************************/ this.walk(value); } /** * 遍歷物件所有的屬性,呼叫 defineReactive * 攔截物件屬性的 get 和 set 方法 */ walk(obj) { const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } } }
然後在 中,當前 中的 也去收集依賴。get
Oberver
dep
export function defineReactive(obj, key, val, shallow) { const property = Object.getOwnPropertyDescriptor(obj, key); // 讀取使用者可能自己定義了的 get、set const getter = property && property.get; const setter = property && property.set; // val 沒有傳進來話進行手動賦值 if ((!getter || setter) && arguments.length === 2) { val = obj[key]; } const dep = new Dep(); // 持有一個 Dep 物件,用來儲存所有依賴於該變數的 Watcher let childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); /******新增 *************************/ if (childOb) { // 當前 value 是陣列,去收集依賴 if (Array.isArray(value)) { childOb.dep.depend(); } } /************************************/ } return value; }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val; if (setter) { setter.call(obj, newVal); } else { val = newVal; } dep.notify(); }, }); }
我們已經重寫了 方法,但直接覆蓋全域性的 方法肯定是不好的,我們可以在 類中去操作,如果當前 是陣列,就去攔截它的 方法。array
arrray
Observer
value
array
這裡就回到 的原型鏈上了,我們可以通過瀏覽器自帶的 ,將當前物件的原型指向我們重寫過的方法即可。js
__proto__
考慮相容性的問題,如果 不存在,我們直接將重寫過的方法複製給當前物件即可。__proto__
import { arrayMethods } from './array' // 上邊重寫的所有陣列方法 /* export const hasProto = "__proto__" in {}; */ export class Observer { constructor(value) { this.dep = new Dep(); /******新增 *************************/ if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } /************************************/ } else { this.walk(value); } } /** * 遍歷物件所有的屬性,呼叫 defineReactive * 攔截物件屬性的 get 和 set 方法 */ walk(obj) { const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } } } /** * Augment a target Object or Array by intercepting * the prototype chain using __proto__ */ function protoAugment(target, src) { /* eslint-disable no-proto */ target.__proto__ = src; /* eslint-enable no-proto */ } /** * Augment a target Object or Array by defining * hidden properties. */ /* istanbul ignore next */ function copyAugment(target, src, keys) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i]; def(target, key, src[key]); } }
還需要考慮一點,陣列方法中我們只能拿到 值,那麼怎麼拿到 對應的 呢。value
value
Observer
我們只需要在 類中,增加一個屬性來指向自身即可。Observe
export class Observer { constructor(value) { this.dep = new Dep(); /******新增 *************************/ def(value, '__ob__', this) /************************************/ if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } } else { this.walk(value); } } ... }
回到最開始重寫的 方法中,只需要從 中拿到 去通知 即可。array
__ob__
Dep
Watcher
/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */ import { def } from "./util"; const arrayProto = Array.prototype; export const arrayMethods = Object.create(arrayProto); const methodsToPatch = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse", ]; /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method]; def(arrayMethods, method, function mutator(...args) { const result = original.apply(this, args); /*****************這裡相當於呼叫了物件 set 需要通知 watcher ************************/ const ob = this.__ob__; // notify change ob.dep.notify(); /**************************************************************************** */ return result; }); });
import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { list: ["hello"], }; observe(data); const updateComponent = () => { for (const item of data.list) { console.log(item); } }; new Watcher(updateComponent); data.list.push("liang");
這樣當呼叫 方法的時候,就會觸發相應的 來執行 函數了。push
Watcher
updateComponent
當前的依賴就變成了下邊的樣子:
對於陣列的響應式我們解決了三個問題,依賴放在哪裡、收集依賴和通知依賴。
我們來和普通物件屬性進行一下對比。
到此這篇關於Vue2 響應式系統之陣列的文章就介紹到這了,更多相關Vue2 陣列內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45