为所欲为的proxy

Proxy 是 JavaScript 2015 的一个新特性,下面让我们看看他实现哪些有趣的东西。

更安全的枚举类型
在 JavaScript 里,我们通常用一个对象来表示枚举值。

在计算机编程中,枚举类型是一个由一组叫做元素, 成员,
枚举成员的值组成的数据类型.枚举成员的名字在语言中通常充当常量的标识符.枚举类型的变量可以被任意的枚举成员所赋值.

但这往往是不安全,我们希望枚举值:

  • 如果不存在的话,报错。
  • 不允许动态设置,否则报错。
  • 不允许删除,否则报错。

我们下面会写一个 enum 的函数,不过先让我们来看看他在 redux 的 action types 的应用。

// enum.test.js
test('enum', () => {
  // 我们定义了俩个 action type
  const actionTypes = {
    ADD_TODO: 'add_todo',
    UPDATE_TODO: 'update_todo'
  }

  const safeActionTypes = enum(actionTypes)

  // 当读取一个不存在的枚举值时会报错
  // 因为 'DELETE_TODO' 并没有定义,所以此时会报错
  expect(() => {
    safeActionTypes['DELETE_TODO']
  }).toThrowErrorMatchingSnapshot()

  // 当删除一个枚举值时会报错
  expect(() => {
    delete safeActionTypes['ADD_TODO']
  }).toThrowErrorMatchingSnapshot()
})

那么,enum 函数怎么写呢?
很简单,只要用 Proxy 的 get , set 和 deleteProperty 钩子就好了。

// 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')
    }
  })
}

拓展一下的话,我们是不是可以写个类型校验库,在这里我们就不展开了。

测试,Mock
利用 apply 钩子,Proxy 可以检测一个函数的调用情况。

下面是一个简单的,用于单元测试的 spy 库。他可以获取函数的调用次数,以及调用时的参数等。

// 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])

Immutable
我们也可以利用 Proxy 在数据结构上做些操作,比如实现一个像 immer 的 Immutable 库。

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

export function produce(base, producer) {
  const state = {
    base, // 原来的数据
    copy: null, // 新的,复制的数据
    modified: false, // 是否修改过
  }

  const proxy = new Proxy(state, {
    get(target, prop) {
      // 如果修改过,则返回副本数据,或者返回原来的数据
      return target.modified ? target.copy[prop] : target.base[prop]
    },

    set(target, prop, value) {
      // set 钩子的时候,设置 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
}

实际效果就像下面这个样子:

我们得到了新的不同的 nextState ,但是原来的 baseState 并没有发生变化。

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

  expect(nextState.name).toBe('bar') // nestState 发生了变化
  expect(baseState.name).toBe('foo') // 而 baseState 保持不变
})

Observe,响应式系统
用 Proxy 来实现一个 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]

      // 这里可以优化一下,不应该每次都创建新的 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 会触发两次 set 钩子
  // 第一次把 colors 的 2 属性设置为 'blue'
  // 第二次把 colors 的 length 属性设置为 2
  reactiveData.colors.push('blue')

  reactiveData.user.name = 'baz'

  // 动态增加一个新的属性
  reactiveData.type = 'zzz'

  expect(stub).toHaveBeenCalledTimes(4)
})

从上面可以发现,Proxy 不仅可以代理对象,也可以代理数组;还可以代理动态增加的属性如 type 。这也是 Object.defineProperty 做不到的。

加个依赖追踪的话,我们就可以实现一个类似 Vue 或者 Mobx 的响应式系统了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大学生就业服务平台管理系统按照操作主体分为管理员和用户。管理员的功能包括学生档案管理、字典管理、试卷管理、试卷选题管理、试题表管理、考试记录表管理、答题详情表管理、错题表管理、法律法规管理、法律法规收藏管理、法律法规留言管理、就业分析管理、论坛管理、企业管理、简历管理、老师管理、简历投递管理、新闻资讯管理、新闻资讯收藏管理、新闻资讯留言管理、学生信息管理、宣传管理、学生管理、职位招聘管理、职位收藏管理、招聘咨询管理、管理员管理。用户的功能等。该系统采用了Mysql数据库,Java语言,Spring Boot框架等技术进行编程实现。 大学生就业服务平台管理系统可以提高大学生就业服务平台信息管理问题的解决效率,优化大学生就业服务平台信息处理流程,保证大学生就业服务平台信息数据的安全,它是一个非常可靠,非常安全的应用程序。 管理员权限操作的功能包括管理新闻信息,管理大学生就业服务平台信息,包括考试管理,培训管理,投递管理,薪资管理等,可以管理新闻信息。 考试管理界面,管理员在考试管理界面中可以对界面中显示,可以对考试信息的考试状态进行查看,可以添加新的考试信息等。投递管理界面,管理员在投递管理界面中查看投递种类信息,投递描述信息,新增投递信息等。新闻信息管理界面,管理员在新闻信息管理界面中新增新闻信息,可以删除新闻信息。新闻信息类型管理界面,管理员在新闻信息类型管理界面查看新闻信息的工作状态,可以对新闻信息的数据进行导出,可以添加新新闻信息的信息,可以编辑新闻信息信息,删除新闻信息信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值