ES6(2015)Proxy

Posted 优小U

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ES6(2015)Proxy相关的知识,希望对你有一定的参考价值。

在 ES6 标准中新增的一个非常强大的功能是 Proxy,它可以自定义一些常用行为如查找、赋值、枚举、函数调用等。通过 Proxy 这个名称也可以看出来它包含了“代理”的含义,只要有“代理”的诉求都可以考虑使用 Proxy 来实现。

1. 基本语法

let p = new Proxy(target, handler)
参数含义
target用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
handler一个对象,其属性是当执行一个操作时定义代理的行为的函数

第一个参数 target 就是用来代理的“对象”,被代理之后它是不能直接被访问的,而 handler 就是实现代理的过程。

2. 拦截操作场景

let obj = {
	name: 'xiaoming',
	age: 18
}
console.log(obj.telephone) // undefined

当我们读取 telephone 的时候返回的是 undefined,因为 obj 这个对象中没有这个属性。一般我们的做法是obj.telephone || '',这样能保证不会输出undefined,但如果都是这样写有点不太美观。ES6 的 Proxy 可以让我们轻松的解决这一问题:

let obj = {
	name: 'xiaoming',
	age: 18
}
let handle = {
	get (obj, key) {
		return Reflect.has(obj, key) ? obj[key] : ''
	}
}
let p = new Proxy(obj, handle)
console.log(p.telephone)

场景1:从服务端获取的数据希望是只读,不允许在任何一个环节被修改

// ES5 遍历设置只读
for (let [key] of Object.entries(response.data)) {
    Object.defineProperty(response.data, key, {
        writable: false
    })
}
// ES6 Proxy
let data = new Proxy(response.data, {
    set(obj, key, value) {
        return false
    }
})

场景2:校验,避免与业务逻辑耦合

// Validator.js
export default (obj, key, value) => {
    if (Reflect.has(key) && value > 20) {
        obj[key] = value
    }
}

import Validator from './Validator'
let data = new Proxy(response.data, {
    set: Validator
})

场景3:对读写进行监控

let validator = {
    set(target, key, value) {
        if (key === 'age') {
            if (typeof value !== 'number' || Number.isNaN(value)) {
                throw new TypeError('Age must be a number')
            }
            if (value <= 0) {
                throw new TypeError('Age must be a positive number')
            }
        }
        return true
    }
}
const person = {
    age: 27
}
const proxy = new Proxy(person, validator)
proxy.age = 'foo'
// <- TypeError: Age must be a number
proxy.age = NaN
// <- TypeError: Age must be a number
proxy.age = 0
// <- TypeError: Age must be a positive number
proxy.age = 28
console.log(person.age)
// <- 28

// 添加监控
window.addEventListener(
    'error',
    e => {
        console.log(e.message) // Uncaught TypeError: Age must be a number
    },
    true
)

3. 常用操作

get 拦截对象属性的读取

let arr = [7, 8, 9]
arr = new Proxy(arr, {
    get(target, prop) {
        return prop in target ? target[prop] : 'error'
    }
})
console.log(arr[1]) // 8
console.log(arr[10]) // error

set 拦截对象属性的设置

let arr = []
arr = new Proxy(arr, {
    set(target, prop, val) {
        if (typeof val === 'number') {
            target[prop] = val
            return true
        } else {
            return false
        }
    }
})
arr.push(5)
arr.push(6)
console.log(arr[0], arr[1], arr.length)  // 5  6  2

has 拦截propKey in proxy的操作

let range = {
    start: 1,
    end: 5
}

range = new Proxy(range, {
    has(target, prop) {
        return prop >= target.start && prop <= target.end
    }
})
console.log(2 in range) // true
console.log(9 in range) // false

ownKeys 拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in循环,返回一个数组

let userinfo = {
    username: 'xiecheng',
    age: 34,
    _password: '***'
}
userinfo = new Proxy(userinfo, {
    ownKeys(target) {
        return Object.keys(target).filter(key => !key.startsWith('_'))
    }
})
console.log(Object.keys(userinfo))  // ["username", "age"]

deleteProperty 拦截delete proxy[propKey]的操作

let user = {
    name: 'xiecheng',
    age: 34,
    _password: '***'
}
user = new Proxy(user, {
	deleteProperty(target, prop) { // 拦截删除
        if (prop.startsWith('_')) {
            throw new Error('不可删除')
        } else {
            delete target[prop]
            return true
        }
    }
})
delete user._password // Uncaught Error: 不可删除

apply 拦截 Proxy 实例作为函数调用的操作

let sum = (...args) => {
    let num = 0
    args.forEach(item => {
        num += item
    })
    return num
}

sum = new Proxy(sum, {
    apply(target, ctx, args) {
        return target(...args) * 2
    }
})
console.log(sum(1, 2)) // 6
console.log(sum.call(null, 1, 2, 3)) // 12
console.log(sum.apply(null, [1, 2, 3])) // 12

construct 拦截 Proxy 实例作为构造函数调用的操作

let User = class {
    constructor(name) {
        this.name = name
    }
}
User = new Proxy(User, {
    construct(target, args, newTarget) {
        console.log('construct')  // construct
        return new target(...args)
    }
})
console.log(new User('xiaoming')) // User {name: "xiaoming"}

以上是关于ES6(2015)Proxy的主要内容,如果未能解决你的问题,请参考以下文章

利用ES6中的Proxy和Reflect 实现简单的双向数据绑定

《es6标准入门》chapter11中关于Proxy的一个错误例子的纠正

ES6系列_13之Proxy进行预处理(简单学习)

ES6----Proxy

ES6 模块串联

proxy思考