proxy connect abort处理方法_Vue 3.0 初探 - Proxy
前言
4 月 17 日,尤大在微博上宣布 Vue 3.0 beta 版本正式發布。
在尤大發布的《 Vue3 設計過程》文章中提到之所以重構 Vue 一個考量就是JavaScript新的語言特性在主流瀏覽器中的支持程度,其中最值得一提的就是Proxy,它為框架提供了攔截對于object的操作的能力。Vue 的一項核心能力就是監聽用戶定義的狀態變化并響應式刷新DOM。Vue 2是通過替換狀態對象屬性的getter和setter來實現這一特性的。改為Proxy后,可以突破Vue當前的限制,比如無法監聽新增屬性,還能提供更好的性能表現。
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 這個詞翻譯過來就是“代理”,用在這里表示由它來“代理”某些操作。 Proxy 會在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。
先來看下 proxy 的基本語法
const proxy = new Proxy(target, handler)- target :您要代理的原始對象(可以是任何類型的對象,包括原生數組,函數,甚至另一個代理)
- handler :一個對象,定義將攔截哪些操作以及如何重新定義攔截的操作
我們看一個簡單的例子:
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 原對象未改變上面代碼中,配置對象有一個get方法,用來攔截對目標對象屬性的訪問請求。get方法的兩個參數分別是目標對象和所要訪問的屬性。可以看到,由于攔截函數總是返回35,所以訪問任何屬性都得到35
注意,Proxy 并沒有改變原有對象 而是生成一個新的對象,要使得 Proxy 起作用,必須針對 Proxy 實例(上例是 proxyPerson)進行操作,而不是針對目標對象(上例是 person)進行操作
Proxy 支持的攔截操作一共 13 種:
- get(target, propKey, receiver):攔截對象屬性的讀取,比如 proxy.foo 和 proxy['foo']。
- set(target, propKey, value, receiver):攔截對象屬性的設置,比如 proxy.foo = v 或 proxy['foo'] = v, 返回一個布爾值。
- has(target, propKey):攔截 propKey in proxy 的操作,返回一個布爾值。
- deleteProperty(target, propKey):攔截 delete proxy[propKey] 的操作,返回一個布爾值。
- ownKeys(target):攔截 Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in 循環,返回一個數組。該方法返回目標對象所有自身的屬性的屬性名。
- getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。
- defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個布爾值。
- preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個布爾值。
- getPrototypeOf(target):攔截 Object.getPrototypeOf(proxy),返回一個對象。
- isExtensible(target):攔截 Object.isExtensible(proxy),返回一個布爾值。
- setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值。
- apply(target, object, args):攔截 Proxy 實例作為函數調用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
- construct(target, args):攔截 Proxy 實例作為構造函數調用的操作,比如new proxy(...args)。
為什么要用 Proxy?
vue2 變更檢測
Vue2 中是遞歸遍歷 data 中的所有的 property,并使用 Object.defineProperty 把這些 property 全部轉為 getter/setter,在getter 中做數據依賴收集處理,在 setter 中 監聽數據的變化,并通知訂閱當前數據的地方。
// 對 data中的數據進行深度遍歷,給對象的每個屬性添加響應式 Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter () {const value = getter ? getter.call(obj) : valif (Dep.target) {// 進行依賴收集dep.depend()if (childOb) {childOb.dep.depend()if (Array.isArray(value)) {// 是數組則需要對每一個成員都進行依賴收集,如果數組的成員還是數組,則遞歸。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}// 新的值需要重新進行observe,保證數據響應式childOb = !shallow && observe(newVal)// 將數據變化通知所有的觀察者dep.notify()}})但由于 JavaScript 的限制,這種實現有幾個問題:
- 無法檢測對象屬性的添加或移除,為此我們需要使用 Vue.set 和 Vue.delete 來保證響應系統的運行符合預期
- 無法監控到數組下標及數組長度的變化,當直接通過數組的下標給數組設置值或者改變數組長度時,不能實時響應
- 性能問題,當data中數據比較多且層級很深的時候,因為要遍歷data中所有的數據并給其設置成響應式的,會導致性能下降
Vue3 改進
Vue3 進行了全新改進,使用 Proxy 代理的作為全新的變更檢測,不再使用 Object.defineProperty
在 Vue3 中,可以使用 reactive() 創建一個響應狀態
import { reactive } from 'vue'// reactive state const state = reactive({desc: 'Hello Vue 3!',count: 0 });我們在源碼 vue-next/packages/reactivity/src/reactive.ts 文件中看到了如下的實現:
//reactive f => createReactiveObject() function createReactiveObject(target, toProxy, toRaw, baseHandlers, collectionHandlers) {...// 設置攔截器const handlers = collectionTypes.has(target.constructor)? collectionHandlers: baseHandlers;observed = new Proxy(target, handlers);...return observed; }下面我們看下 state 經過處理后的情況
可以看到被代理的目標對象 state 設置了 get()、set()、deleteProperty()、has()、ownKeys()這 5 個 handler,一起來看下它們都做了什么
get()
get() 會自動讀取響應數據,并進行 track 調用
function createGetter(isReadonly = false, shallow = false) {return function get(target, key, receiver) {...// 恢復默認行為const res = Reflect.get(target, key, receiver)...// 調用 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()
目標對象上不存在的屬性設置值時,進行 “添加” 操作,并且會觸發 trigger() 來通知響應系統的更新。解決了 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)// 恢復默認行為const result = Reflect.set(target, key, value, receiver)// 如果目標對象在原型鏈上,不要 triggerif (target === toRaw(receiver)) {// 如果設置的屬性不在目標對象上 就進行 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()
關聯 delete 操作,當目標對象上的屬性被刪除時,會觸發 trigger() 來通知響應系統的更新。這也解決了 Vue 2.x 中無法檢測到對象屬性的刪除的問題
function deleteProperty(target, key) {const hadKey = hasOwn(target, key)const oldValue = (target as any)[key]const result = Reflect.deleteProperty(target, key)// 存在屬性刪除時觸發 triggerif (result && hadKey) {trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)}return result }has() 和 ownKeys()
這兩個 handler 并沒有修改默認行為,但是它們都調用 track() 函數,回顧上文可以知道has() 影響 in 操作的,ownKeys() 影響 for...in 及循環
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 的幾個 Handler 攔截操作,收集依賴,實現了響應系統核心。
Proxy 還可以做什么?
我們已經看到了 Proxy 在 Vue3 中的應用場景,其實在使用了Proxy后,對象的行為基本上都是可控的,所以我們能拿來做一些之前實現起來比較復雜的事情。
實現訪問日志
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();校驗模塊
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);} });// 拋出錯誤,因為 "foo" 不是數值 numObj.count = 'foo'; // 賦值成功 numObj.count = 333;可以看到 Proxy 可以有很多有趣的應用,大家快快去探索吧!
歡迎關注公眾號:前端瑣話(qianduansuohua)
總結
以上是生活随笔為你收集整理的proxy connect abort处理方法_Vue 3.0 初探 - Proxy的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何用英文写朋友(12个英文朋友的实用表
- 下一篇: 教您如何辨别电源线的好坏电脑电源如何判断