proxy connect abort处理方法_Vue 3.0 初探 - Proxy
前言
4 月 17 日,尤大在微博上宣布 Vue 3.0 beta 版本正式發(fā)布。
在尤大發(fā)布的《 Vue3 設(shè)計(jì)過程》文章中提到之所以重構(gòu) Vue 一個(gè)考量就是JavaScript新的語言特性在主流瀏覽器中的支持程度,其中最值得一提的就是Proxy,它為框架提供了攔截對于object的操作的能力。Vue 的一項(xiàng)核心能力就是監(jiān)聽用戶定義的狀態(tài)變化并響應(yīng)式刷新DOM。Vue 2是通過替換狀態(tài)對象屬性的getter和setter來實(shí)現(xiàn)這一特性的。改為Proxy后,可以突破Vue當(dāng)前的限制,比如無法監(jiān)聽新增屬性,還能提供更好的性能表現(xiàn)。
Two key considerations led us to the new major version (and rewrite) of Vue: First, the general availability of new JavaScript language features in mainstream browsers. Second, design and architectural issues in the current codebase that had been exposed over time.作為一名高級前端猿,我們要知其然,更要知其所以然,那就讓我們來看一下到底什么是 Proxy?
什么是 Proxy?
Proxy 這個(gè)詞翻譯過來就是“代理”,用在這里表示由它來“代理”某些操作。 Proxy 會在目標(biāo)對象之前架設(shè)一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機(jī)制,可以對外界的訪問進(jìn)行過濾和改寫。
先來看下 proxy 的基本語法
const proxy = new Proxy(target, handler)- target :您要代理的原始對象(可以是任何類型的對象,包括原生數(shù)組,函數(shù),甚至另一個(gè)代理)
- handler :一個(gè)對象,定義將攔截哪些操作以及如何重新定義攔截的操作
我們看一個(gè)簡單的例子:
const person = {name: 'muyao',age: 27 };const proxyPerson = new Proxy({}, {get: function(target, propKey) {return 35;} });proxy.name // 35 proxy.age // 35 proxy.sex // 35 不存在的屬性同樣起作用person.name // muyao 原對象未改變上面代碼中,配置對象有一個(gè)get方法,用來攔截對目標(biāo)對象屬性的訪問請求。get方法的兩個(gè)參數(shù)分別是目標(biāo)對象和所要訪問的屬性。可以看到,由于攔截函數(shù)總是返回35,所以訪問任何屬性都得到35
注意,Proxy 并沒有改變原有對象 而是生成一個(gè)新的對象,要使得 Proxy 起作用,必須針對 Proxy 實(shí)例(上例是 proxyPerson)進(jìn)行操作,而不是針對目標(biāo)對象(上例是 person)進(jìn)行操作
Proxy 支持的攔截操作一共 13 種:
- get(target, propKey, receiver):攔截對象屬性的讀取,比如 proxy.foo 和 proxy['foo']。
- set(target, propKey, value, receiver):攔截對象屬性的設(shè)置,比如 proxy.foo = v 或 proxy['foo'] = v, 返回一個(gè)布爾值。
- has(target, propKey):攔截 propKey in proxy 的操作,返回一個(gè)布爾值。
- deleteProperty(target, propKey):攔截 delete proxy[propKey] 的操作,返回一個(gè)布爾值。
- ownKeys(target):攔截 Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in 循環(huán),返回一個(gè)數(shù)組。該方法返回目標(biāo)對象所有自身的屬性的屬性名。
- getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。
- defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個(gè)布爾值。
- preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個(gè)布爾值。
- getPrototypeOf(target):攔截 Object.getPrototypeOf(proxy),返回一個(gè)對象。
- isExtensible(target):攔截 Object.isExtensible(proxy),返回一個(gè)布爾值。
- setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個(gè)布爾值。
- apply(target, object, args):攔截 Proxy 實(shí)例作為函數(shù)調(diào)用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
- construct(target, args):攔截 Proxy 實(shí)例作為構(gòu)造函數(shù)調(diào)用的操作,比如new proxy(...args)。
為什么要用 Proxy?
vue2 變更檢測
Vue2 中是遞歸遍歷 data 中的所有的 property,并使用 Object.defineProperty 把這些 property 全部轉(zhuǎn)為 getter/setter,在getter 中做數(shù)據(jù)依賴收集處理,在 setter 中 監(jiān)聽數(shù)據(jù)的變化,并通知訂閱當(dāng)前數(shù)據(jù)的地方。
// 對 data中的數(shù)據(jù)進(jìn)行深度遍歷,給對象的每個(gè)屬性添加響應(yīng)式 Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter () {const value = getter ? getter.call(obj) : valif (Dep.target) {// 進(jìn)行依賴收集dep.depend()if (childOb) {childOb.dep.depend()if (Array.isArray(value)) {// 是數(shù)組則需要對每一個(gè)成員都進(jìn)行依賴收集,如果數(shù)組的成員還是數(shù)組,則遞歸。dependArray(value)}}}return value},set: function reactiveSetter (newVal) {const value = getter ? getter.call(obj) : val/* eslint-disable no-self-compare */if (newVal === value || (newVal !== newVal && value !== value)) {return}/* eslint-enable no-self-compare */if (process.env.NODE_ENV !== 'production' && customSetter) {customSetter()}if (getter && !setter) returnif (setter) {setter.call(obj, newVal)} else {val = newVal}// 新的值需要重新進(jìn)行observe,保證數(shù)據(jù)響應(yīng)式childOb = !shallow && observe(newVal)// 將數(shù)據(jù)變化通知所有的觀察者dep.notify()}})但由于 JavaScript 的限制,這種實(shí)現(xiàn)有幾個(gè)問題:
- 無法檢測對象屬性的添加或移除,為此我們需要使用 Vue.set 和 Vue.delete 來保證響應(yīng)系統(tǒng)的運(yùn)行符合預(yù)期
- 無法監(jiān)控到數(shù)組下標(biāo)及數(shù)組長度的變化,當(dāng)直接通過數(shù)組的下標(biāo)給數(shù)組設(shè)置值或者改變數(shù)組長度時(shí),不能實(shí)時(shí)響應(yīng)
- 性能問題,當(dāng)data中數(shù)據(jù)比較多且層級很深的時(shí)候,因?yàn)橐闅vdata中所有的數(shù)據(jù)并給其設(shè)置成響應(yīng)式的,會導(dǎo)致性能下降
Vue3 改進(jìn)
Vue3 進(jìn)行了全新改進(jìn),使用 Proxy 代理的作為全新的變更檢測,不再使用 Object.defineProperty
在 Vue3 中,可以使用 reactive() 創(chuàng)建一個(gè)響應(yīng)狀態(tài)
import { reactive } from 'vue'// reactive state const state = reactive({desc: 'Hello Vue 3!',count: 0 });我們在源碼 vue-next/packages/reactivity/src/reactive.ts 文件中看到了如下的實(shí)現(xiàn):
//reactive f => createReactiveObject() function createReactiveObject(target, toProxy, toRaw, baseHandlers, collectionHandlers) {...// 設(shè)置攔截器const handlers = collectionTypes.has(target.constructor)? collectionHandlers: baseHandlers;observed = new Proxy(target, handlers);...return observed; }下面我們看下 state 經(jīng)過處理后的情況
可以看到被代理的目標(biāo)對象 state 設(shè)置了 get()、set()、deleteProperty()、has()、ownKeys()這 5 個(gè) handler,一起來看下它們都做了什么
get()
get() 會自動讀取響應(yīng)數(shù)據(jù),并進(jìn)行 track 調(diào)用
function createGetter(isReadonly = false, shallow = false) {return function get(target, key, receiver) {...// 恢復(fù)默認(rèn)行為const res = Reflect.get(target, key, receiver)...// 調(diào)用 track!isReadonly && track(target, TrackOpTypes.GET, key)return isObject(res)? isReadonly? // need to lazy access readonly and reactive here to avoid// circular dependencyreadonly(res): reactive(res): res }set()
目標(biāo)對象上不存在的屬性設(shè)置值時(shí),進(jìn)行 “添加” 操作,并且會觸發(fā) trigger() 來通知響應(yīng)系統(tǒng)的更新。解決了 Vue 2.x 中無法檢測到對象屬性的添加的問題
function createSetter(shallow = false) {return function set(target: object,key: string | symbol,value: unknown,receiver: object): boolean {...const hadKey = hasOwn(target, key)// 恢復(fù)默認(rèn)行為const result = Reflect.set(target, key, value, receiver)// 如果目標(biāo)對象在原型鏈上,不要 triggerif (target === toRaw(receiver)) {// 如果設(shè)置的屬性不在目標(biāo)對象上 就進(jìn)行 Add 這就解決了 Vue 2.x 中無法檢測到對象屬性的添加或刪除的問題if (!hadKey) {trigger(target, TriggerOpTypes.ADD, key, value)} else if (hasChanged(value, oldValue)) {trigger(target, TriggerOpTypes.SET, key, value, oldValue)}}return result} }deleteProperty()
關(guān)聯(lián) delete 操作,當(dāng)目標(biāo)對象上的屬性被刪除時(shí),會觸發(fā) trigger() 來通知響應(yīng)系統(tǒng)的更新。這也解決了 Vue 2.x 中無法檢測到對象屬性的刪除的問題
function deleteProperty(target, key) {const hadKey = hasOwn(target, key)const oldValue = (target as any)[key]const result = Reflect.deleteProperty(target, key)// 存在屬性刪除時(shí)觸發(fā) triggerif (result && hadKey) {trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)}return result }has() 和 ownKeys()
這兩個(gè) handler 并沒有修改默認(rèn)行為,但是它們都調(diào)用 track() 函數(shù),回顧上文可以知道has() 影響 in 操作的,ownKeys() 影響 for...in 及循環(huán)
function has(target: object, key: string | symbol): boolean {const result = Reflect.has(target, key)track(target, TrackOpTypes.HAS, key)return result }function ownKeys(target: object): (string | number | symbol)[] {track(target, TrackOpTypes.ITERATE, ITERATE_KEY)return Reflect.ownKeys(target) }通過上面的分析,我們可以看到,Vue3 借助 Proxy 的幾個(gè) Handler 攔截操作,收集依賴,實(shí)現(xiàn)了響應(yīng)系統(tǒng)核心。
Proxy 還可以做什么?
我們已經(jīng)看到了 Proxy 在 Vue3 中的應(yīng)用場景,其實(shí)在使用了Proxy后,對象的行為基本上都是可控的,所以我們能拿來做一些之前實(shí)現(xiàn)起來比較復(fù)雜的事情。
實(shí)現(xiàn)訪問日志
let api = {getUser: function(userId) {/* ... */},setUser: function(userId, config) {/* ... */} }; // 打日志 function log(timestamp, method) {console.log(`${timestamp} - Logging ${method} request.`); } api = new Proxy(api, {get: function(target, key, proxy) {var value = target[key];return function(...arguments) {log(new Date(), key); // 打日志return Reflect.apply(value, target, arguments);};} }); api.getUsers();校驗(yàn)?zāi)K
let numObj = { count: 0, amount: 1234, total: 14 }; numObj = new Proxy(numObj, {set(target, key, value, proxy) {if (typeof value !== 'number') {throw Error('Properties in numObj can only be numbers');}return Reflect.set(target, key, value, proxy);} });// 拋出錯(cuò)誤,因?yàn)?"foo" 不是數(shù)值 numObj.count = 'foo'; // 賦值成功 numObj.count = 333;可以看到 Proxy 可以有很多有趣的應(yīng)用,大家快快去探索吧!
歡迎關(guān)注公眾號:前端瑣話(qianduansuohua)
總結(jié)
以上是生活随笔為你收集整理的proxy connect abort处理方法_Vue 3.0 初探 - Proxy的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何用英文写朋友(12个英文朋友的实用表
- 下一篇: 教您如何辨别电源线的好坏电脑电源如何判断