<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          學(xué)會 Proxy 真的可以為所欲為

          共 7277字,需瀏覽 15分鐘

           ·

          2021-03-15 00:17

          作者:y你個c
          https://zhuanlan.zhihu.com/p/35080324

          Proxy 是 JavaScript 2015 的一個新特性,下面讓我們看看他實(shí)現(xiàn)哪些有趣的東西。

          更安全的枚舉類型

          在 JavaScript 里,我們通常用一個對象來表示枚舉值。

          但這往往是不安全,我們希望枚舉值:

          • 如果不存在的話,報錯。
          • 不允許動態(tài)設(shè)置,否則報錯。
          • 不允許刪除,否則報錯。

          我們下面會寫一個 enum 的函數(shù),不過先讓我們來看看他在 redux 的 action types 的應(yīng)用。

          // enum.test.js
          test('enum', () => {
            // 我們定義了倆個 action type
            const actionTypes = {
              ADD_TODO'add_todo',
              UPDATE_TODO'update_todo'
            }

            const safeActionTypes = enum(actionTypes)

            // 當(dāng)讀取一個不存在的枚舉值時會報錯
            // 因?yàn)?nbsp;'DELETE_TODO' 并沒有定義,所以此時會報錯
            expect(() => {
              safeActionTypes['DELETE_TODO']
            }).toThrowErrorMatchingSnapshot()

            // 當(dāng)刪除一個枚舉值時會報錯
            expect(() => {
              delete safeActionTypes['ADD_TODO']
            }).toThrowErrorMatchingSnapshot()
          })

          那么,enum 函數(shù)怎么寫呢?
          很簡單,只要用 Proxy 的 get , setdeleteProperty 鉤子就好了。

          // erum.js
          export default function enum(object{
            return new Proxy(object, {
              get(target, prop) {
                if (target[prop]) {
                  return Reflect.get(target, prop)
                } else {
                  throw new ReferenceError(`Unknown enum '${prop}'`)
                }
              },

              set() {
                throw new TypeError('Enum is readonly')
              },

              deleteProperty() {
                throw new TypeError('Enum is readonly')
              }
            })
          }

          拓展一下的話,我們是不是可以寫個類型校驗(yàn)庫,在這里我們就不展開了。

          測試,Mock

          利用 apply 鉤子,Proxy 可以檢測一個函數(shù)的調(diào)用情況。

          下面是一個簡單的,用于單元測試的 spy 庫。他可以獲取函數(shù)的調(diào)用次數(shù),以及調(diào)用時的參數(shù)等。

          // spy.js
          export function spy({
            const spyFn = function({}
            spyFn.toBeCalledTimes = 0
            spyFn.lastCalledWith = undefined

            return new Proxy(spyFn, {
              apply(target, thisArg, argumentsList) {
                target.toBeCalledTimes += 1
                target.lastCalledWith = argumentsList.join(', ')
              }
            })
          }

          // spy.test.js
          const colors = ['red''blue']
          const callback = spy()

          colors.forEach(color => callback(color))

          expect(callback.toBeCalledTimes).toBe(colors.length)
          expect(callback.lastCalledWith).toBe(colors[1])

          另外,用 Proxy 寫一個斷言庫也是挺方便的,這里就不展開了。

          Immutable

          我們也可以利用 Proxy 在數(shù)據(jù)結(jié)構(gòu)上做些操作,比如實(shí)現(xiàn)一個像 immer 的 Immutable 庫。

          import { shallowCopy } from './utils/index'

          export function produce(base, producer{
            const state = {
              base, // 原來的數(shù)據(jù)
              copynull// 新的,復(fù)制的數(shù)據(jù)
              modifiedfalse// 是否修改過
            }

            const proxy = new Proxy(state, {
              get(target, prop) {
                // 如果修改過,則返回副本數(shù)據(jù),或者返回原來的數(shù)據(jù)
                return target.modified ? target.copy[prop] : target.base[prop]
              },

              set(target, prop, value) {
                // set 鉤子的時候,設(shè)置 modifyied 為 true
                if (!target.modifyied) {
                  target.modified = true
                  target.copy = shallowCopy(target.base)
                }

                target.copy[prop] = value
                return true
              }
            })

            producer.call(proxy, proxy)

            return proxy
          }

          實(shí)際效果就像下面這個樣子:

          我們得到了新的不同的 nextState ,但是原來的 baseState 并沒有發(fā)生變化。

          test('produce', () => {
            const baseState = {
              name'foo'
            }
            const nextState = produce(baseState, draft => {
              draft.name = 'bar'
            })

            expect(nextState.name).toBe('bar'// nestState 發(fā)生了變化
            expect(baseState.name).toBe('foo'// 而 baseState 保持不變
          })

          Observe,響應(yīng)式系統(tǒng)

          用 Proxy 來實(shí)現(xiàn)一個 pub/sub 模式也是挺簡單的。

          // observe.js
          export function observe(target, onChange{
            return createProxy(target, onChange)
          }

          function createProxy(target, onChange{
            const trap = {
              get(object, prop) {
                const value = object[prop]

                // 這里可以優(yōu)化一下,不應(yīng)該每次都創(chuàng)建新的 proxy
                if (typeof value === 'object' && value !== null) {
                  return createProxy(object[prop], onChange)
                }

                return value
              },

              set(object, prop, value, ...args) {
                onChange()
                return Reflect.set(object, prop, value, ...args)
              }
            }

            return new Proxy(target, trap)
          }

          // observe.test.js
          test('observe', () => {
            const stub = jest.fn()
            const data = {
              user: {
                name'foo',
              },
              colors: ['red'],
            }

            const reactiveData = observe(data, stub)

            // push 會觸發(fā)兩次 set 鉤子
            // 第一次把 colors 的 2 屬性設(shè)置為 'blue'
            // 第二次把 colors 的 length 屬性設(shè)置為 2
            reactiveData.colors.push('blue')

            reactiveData.user.name = 'baz'

            // 動態(tài)增加一個新的屬性
            reactiveData.type = 'zzz'

            expect(stub).toHaveBeenCalledTimes(4)
          })

          從上面可以發(fā)現(xiàn),Proxy 不僅可以代理對象,也可以代理數(shù)組;還可以代理動態(tài)增加的屬性如 type 。這也是 Object.defineProperty 做不到的。

          加個依賴追蹤的話,我們就可以實(shí)現(xiàn)一個類似 Vue 或者 Mobx 的響應(yīng)式系統(tǒng)了。

          更多有趣的例子

          我們還可以用 Proxy 實(shí)現(xiàn)很多東西,比如埋點(diǎn)可以不,性能監(jiān)控可以不?

          • How to use JavaScript Proxies for Fun and Profit
          • ES6 Features - 10 Use Cases for Proxy
          • proxy-fun

          Proxy 的更多玩法,大家好好挖掘挖掘

          瀏覽 59
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人理伦A级A片在线论坛 | 一级大片在线 | 亚洲色成人无码777777在线 | 国产黄片免费在线观看 | 欧美亚洲国产高清 |