js代理(Proxy)和反射(Reflect)的应用

跟踪属性访问

const user = {
    name: ‘Jack‘
}

const userPorxy = new Proxy(user, {
    get(target, prop) {
        console.log(`Getting ${prop}`);
        return Reflect.get(...arguments);
    },
    set(target, prop, val) {
        console.log(`Setting ${prop} to ${val}`);
        return Reflect.set(...arguments);
    }
});

userPorxy.name;
// Getting name
userPorxy.name = ‘Wango‘;
// Setting name to Wango

隐藏属性

const user = {
    name: ‘Wango‘,
    age: 24,
    addr: ‘Chongqing, China‘,
    income: ‘classified‘,
    email: ‘example@mail.com‘
}

const hiddenProps = [‘addr‘, ‘income‘, ‘email‘];

const userProxy = new Proxy(user, {
    get(target, prop) {
        if (hiddenProps.includes(prop)) {
            return undefined;
        }
        return Reflect.get(...arguments);
    },
    has(target, prop) {
        if (hiddenProps.includes(prop)) {
            return false;
        }
        return Reflect.has(...arguments);
    }
});

console.log(userProxy.name); // Wango
console.log(userProxy.addr); // undefined

console.log(‘age‘ in userProxy); // true
console.log(‘income‘ in userProxy); // false
console.log(‘email‘ in userProxy); // false

// 但是for...in依旧可以枚举属性
for (let key in userProxy) {
    console.log(key);
}

属性验证

const user = {
    name: ‘Wango‘,
    age: 24
}

const userPorxy = new Proxy(user, {
    set(target, prop, val) {
        if (prop === ‘age‘) {
            if (typeof val !== ‘number‘) {
                throw new TypeError(‘A number expected!‘);
            }
        }
        return Reflect.set(...arguments);
    }
});

userPorxy.age = 33;
console.log(userPorxy.age); // 33
userPorxy.age = ‘100‘; // TypeError: A number expected!

函数和构造函数参数验证

function add(...args) {
    return args.reduce((a, b) => a + b);
}

const addProxy = new Proxy(add, {
    apply(target, thisArg, args) {
        for (let i = 0; i < args.length; i++) {
            if (typeof args[i] !== ‘number‘) {
                throw new TypeError(‘Non-number argument provided‘);
            }
        }
        return Reflect.apply(...arguments);
    }
}); 

console.log(addProxy(1, 2, 3, 4, 5)); // 15
console.log(addProxy(1, 2, 3, 4, ‘5‘)); 
// TypeError: Non-number argument provided

class User {
    constructor(id) {
        this.id = id;
    }
}

const UserProxy = new Proxy(User, {
    construct(target, args, newTarget) {
        if (args[0] === undefined) {
            throw new Error(‘User cannot be instantiated without id‘);
        }
        return Reflect.construct(...arguments);
    }
});

const u1 = new UserProxy(‘Wango‘);
const u2 = new UserProxy(); 
// Error: User cannot be instantiated without id

数据绑定与可观察对象

被代理的类绑定到一个全局实例集合,让所有创建的实例都被添加到这个集合中


const userList = [];

class User {
    constructor(name) {
        this.name = name;
    }
}

const UserProxy = new Proxy(User, {
    construct() {
        const newUser = Reflect.construct(...arguments);
        userList.push(newUser);
        return newUser;
    }
});

new UserProxy(‘Wango‘);
new UserProxy(‘Jack‘);
new UserProxy(‘Lily‘);

console.log(userList);
// [User, User, User]

把集合绑定到一个事件分派程序,每次插入新实例时都会发送消息


function emit(newVal) {
    console.log(newVal);
}

const userList = [];

const userListProxy = new Proxy(userList, {
    set(target, prop, val, receiver) {
        const result = Reflect.set(...arguments);
        // if (result) {
        //     emit(Reflect.get(target, prop, receiver));
        // }
        // 加个判断,对length的修改不发送消息
        if (prop !== ‘length‘ && result) {
            emit(Reflect.get(target, prop, receiver));
        }
        return result;
    }
});

// push会对userList进行两次set操作,
// 第一次新增一个元素,第二次修改length的值
userListProxy.push(‘Wango‘);
// Wango
// 1
userListProxy.push(‘Lily‘);
// Lily
// 2

参考资料:

《JavaScript高级程序设计(第4版)》

js代理(Proxy)和反射(Reflect)的应用

上一篇:vue项目的package.json配置详解


下一篇:UnicodeEncodeError: 'gbk' codec can't encode character '\xa0' in position 18521: illegal multibyte sequence