proxy connect abort处理方法_Vue 3.0 初探 - Proxy
前言
4 月 17 日,尤大在微博上宣布 Vue 3.0 beta 版本正式發(fā)布。
在尤大發(fā)布的《 Vue3 設(shè)計(jì)過(guò)程》文章中提到之所以重構(gòu) Vue 一個(gè)考量就是JavaScript新的語(yǔ)言特性在主流瀏覽器中的支持程度,其中最值得一提的就是Proxy,它為框架提供了攔截對(duì)于object的操作的能力。Vue 的一項(xiàng)核心能力就是監(jiān)聽(tīng)用戶(hù)定義的狀態(tài)變化并響應(yīng)式刷新DOM。Vue 2是通過(guò)替換狀態(tài)對(duì)象屬性的getter和setter來(lái)實(shí)現(xiàn)這一特性的。改為Proxy后,可以突破Vue當(dāng)前的限制,比如無(wú)法監(jiān)聽(tīng)新增屬性,還能提供更好的性能表現(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.作為一名高級(jí)前端猿,我們要知其然,更要知其所以然,那就讓我們來(lái)看一下到底什么是 Proxy?
什么是 Proxy?
Proxy 這個(gè)詞翻譯過(guò)來(lái)就是“代理”,用在這里表示由它來(lái)“代理”某些操作。 Proxy 會(huì)在目標(biāo)對(duì)象之前架設(shè)一層“攔截”,外界對(duì)該對(duì)象的訪問(wèn),都必須先通過(guò)這層攔截,因此提供了一種機(jī)制,可以對(duì)外界的訪問(wèn)進(jìn)行過(guò)濾和改寫(xiě)。
先來(lái)看下 proxy 的基本語(yǔ)法
const proxy = new Proxy(target, handler)- target :您要代理的原始對(duì)象(可以是任何類(lèi)型的對(duì)象,包括原生數(shù)組,函數(shù),甚至另一個(gè)代理)
- handler :一個(gè)對(duì)象,定義將攔截哪些操作以及如何重新定義攔截的操作
我們看一個(gè)簡(jiǎn)單的例子:
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 原對(duì)象未改變上面代碼中,配置對(duì)象有一個(gè)get方法,用來(lái)攔截對(duì)目標(biāo)對(duì)象屬性的訪問(wèn)請(qǐng)求。get方法的兩個(gè)參數(shù)分別是目標(biāo)對(duì)象和所要訪問(wèn)的屬性。可以看到,由于攔截函數(shù)總是返回35,所以訪問(wèn)任何屬性都得到35
注意,Proxy 并沒(méi)有改變?cè)袑?duì)象 而是生成一個(gè)新的對(duì)象,要使得 Proxy 起作用,必須針對(duì) Proxy 實(shí)例(上例是 proxyPerson)進(jìn)行操作,而不是針對(duì)目標(biāo)對(duì)象(上例是 person)進(jìn)行操作
Proxy 支持的攔截操作一共 13 種:
- get(target, propKey, receiver):攔截對(duì)象屬性的讀取,比如 proxy.foo 和 proxy['foo']。
- set(target, propKey, value, receiver):攔截對(duì)象屬性的設(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)對(duì)象所有自身的屬性的屬性名。
- getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對(duì)象。
- 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è)對(duì)象。
- 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 變更檢測(cè)
Vue2 中是遞歸遍歷 data 中的所有的 property,并使用 Object.defineProperty 把這些 property 全部轉(zhuǎn)為 getter/setter,在getter 中做數(shù)據(jù)依賴(lài)收集處理,在 setter 中 監(jiān)聽(tīng)數(shù)據(jù)的變化,并通知訂閱當(dāng)前數(shù)據(jù)的地方。
// 對(duì) data中的數(shù)據(jù)進(jìn)行深度遍歷,給對(duì)象的每個(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)行依賴(lài)收集dep.depend()if (childOb) {childOb.dep.depend()if (Array.isArray(value)) {// 是數(shù)組則需要對(duì)每一個(gè)成員都進(jìn)行依賴(lài)收集,如果數(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è)問(wèn)題:
- 無(wú)法檢測(cè)對(duì)象屬性的添加或移除,為此我們需要使用 Vue.set 和 Vue.delete 來(lái)保證響應(yīng)系統(tǒng)的運(yùn)行符合預(yù)期
- 無(wú)法監(jiān)控到數(shù)組下標(biāo)及數(shù)組長(zhǎng)度的變化,當(dāng)直接通過(guò)數(shù)組的下標(biāo)給數(shù)組設(shè)置值或者改變數(shù)組長(zhǎng)度時(shí),不能實(shí)時(shí)響應(yīng)
- 性能問(wèn)題,當(dāng)data中數(shù)據(jù)比較多且層級(jí)很深的時(shí)候,因?yàn)橐闅vdata中所有的數(shù)據(jù)并給其設(shè)置成響應(yīng)式的,會(huì)導(dǎo)致性能下降
Vue3 改進(jìn)
Vue3 進(jìn)行了全新改進(jìn),使用 Proxy 代理的作為全新的變更檢測(cè),不再使用 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 });我們?cè)谠创a 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)過(guò)處理后的情況
可以看到被代理的目標(biāo)對(duì)象 state 設(shè)置了 get()、set()、deleteProperty()、has()、ownKeys()這 5 個(gè) handler,一起來(lái)看下它們都做了什么
get()
get() 會(huì)自動(dòng)讀取響應(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)對(duì)象上不存在的屬性設(shè)置值時(shí),進(jìn)行 “添加” 操作,并且會(huì)觸發(fā) trigger() 來(lái)通知響應(yīng)系統(tǒng)的更新。解決了 Vue 2.x 中無(wú)法檢測(cè)到對(duì)象屬性的添加的問(wèn)題
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)對(duì)象在原型鏈上,不要 triggerif (target === toRaw(receiver)) {// 如果設(shè)置的屬性不在目標(biāo)對(duì)象上 就進(jìn)行 Add 這就解決了 Vue 2.x 中無(wú)法檢測(cè)到對(duì)象屬性的添加或刪除的問(wèn)題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)對(duì)象上的屬性被刪除時(shí),會(huì)觸發(fā) trigger() 來(lái)通知響應(yīng)系統(tǒng)的更新。這也解決了 Vue 2.x 中無(wú)法檢測(cè)到對(duì)象屬性的刪除的問(wèn)題
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 并沒(méi)有修改默認(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) }通過(guò)上面的分析,我們可以看到,Vue3 借助 Proxy 的幾個(gè) Handler 攔截操作,收集依賴(lài),實(shí)現(xiàn)了響應(yīng)系統(tǒng)核心。
Proxy 還可以做什么?
我們已經(jīng)看到了 Proxy 在 Vue3 中的應(yīng)用場(chǎng)景,其實(shí)在使用了Proxy后,對(duì)象的行為基本上都是可控的,所以我們能拿來(lái)做一些之前實(shí)現(xiàn)起來(lái)比較復(fù)雜的事情。
實(shí)現(xiàn)訪問(wè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)注公眾號(hào):前端瑣話(huà)(qianduansuohua)
總結(jié)
以上是生活随笔為你收集整理的proxy connect abort处理方法_Vue 3.0 初探 - Proxy的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何用英文写朋友(12个英文朋友的实用表
- 下一篇: vue webpack 自动打开页面_v