为其他对象提供一种代理以控制对这个对象的访问
回顾Object.defineProperty
只要准备过面试,相信你一定熟知ES5的Object.defineProperty,遍历属性、get/set劫持监听、双向绑定、重写数组原型支持响应式、balabala
当然Object.defineProperty还有4大属性,configurable、enumerable、writable、value
良好的兼容性
不能一次监听所有属性
1 2 3 4 5 6 7 8 9 10 11
| Object.keys(obj).forEach(key => { Object.defineProperty(obj, key, { set() {}, get() {} }) })
obj = new Proxy(obj, { get() {} set() {} })
|
无法监听新增加的属性
1 2 3 4
| Vue.set(obj, "a", "1");
obj.a = "1";
|
无法响应数组操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const arr = [1, 2, 3]; arr[0] = 10; arr[3] = 10; arr.push(10);
[...Array(1000)].forEach((item, index) => { Object.defineProperty(arr, `${index}`, { set() {}, get() {} }) }); arr[30] = 10; arr[40] = 10;
|
必考题如何重写数组原型用以支持监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const arrayProto = Array.prototype; const subArrProto = Object.create(arrayProto); const methods = ['pop', 'shift', 'unshift', 'sort', 'reverse', 'splice', 'push']; methods.forEach(method => { subArrProto[method] = function() { arrayProto[method].call(this, ...arguments); }; Object.defineProperty(subArrProto, method, { set() {}, get() {} }) })
|
看看Proxy
Proxy 构造函数接收两个参数,第一个参数是需要拦截的目标对象,这个对象只可以是对象、数组或者函数
第二个参数则是一个配置对象,提供了拦截方法,即使这个配置对象为空对象,返回的 Proxy 新实例也不是原来的目标对象(用Reflect获取原目标对象哦)
令人惊叹的13种拦截操作
当然有很多和ts的新语法糖一样令人摸不着头脑能用在何处
get
拦截对目标对象属性的读取 get(目标对象,属性名,Proxy 实例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const proxy = new Proxy(person, { get(target, prop) { if (prop[0] === '_') { throw new Error(`${prop} is private attribute`); } return target[prop] } }) proxy._aaa;
const defaults = (obj, initial) => { return new Proxy(obj, { get(target, prop) { if (prop in target) { return target[prop] } return initial } }) } person = defaults(person, 0); persion.aaa;
|
set
拦截对属性的赋值操作 set(目标对象,属性名,属性值,Proxy 实例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| set(target, key, value, receiver) { console.log(`${key} has been set to ${value}`); Reflect.set(target, key, value); }
set(target, key, value, receiver) { if (key === 'self') { Reflect.set(target, key, receiver); } else { Reflect.set(target, key, value); } }
Object.defineProperty(person, 'name', { writable: false })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| const validators = { name: { validate(value) { return value.length > 6; }, message: '用户名长度不能小于6' }, password: { validate(value) { return value.length > 10; }, message: '密码长度不能小于10' }, moblie: { validate(value) { return /^1(3|5|7|8|9)[0-9]{9}$/.test(value); }, message: '手机号格式错误' } } function validator(obj, validators) { return new Proxy(obj, { set(target, key, value) { const validator = validators[key] if (!validator) { target[key] = value; } else if (validator.validate(value)) { target[key] = value; } else { alert(validator.message || ""); } } }) } let form = {}; form = validator(form, validators); form.name = '666';
|
apply
拦截函数的调用 apply(目标对象,this,参数数组)
1 2 3 4 5 6 7 8 9 10
| const func = new Proxy(test, { _count: 0, apply(target, context, args) { console.log('hello, world'); target.apply(context, args); } }) func();
|
construct
拦截 new 操作符 construct(目标对象,构造函数的参数列表,Proxy 对象)
返回一个对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function Person(name, age) { this.name = name; this.age = age; } const P = new Proxy(Person, { construct(target, args, newTarget) { console.log('construct'); return new target(...args); } construct(target, args, newTarget) { return { name: args[0], age: args[1] } } }) const p = new P('tom', 21);
|
构造函数没有返回值或者返回原始类型的值,那么默认返回this,
如果返回引用类型的值,那么最终 new 出来的就是这个值
能做什么
用于代理类
对属性设置 get 拦截,对原型函数设置 apply 拦截
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class Person { constructor(name, age) { this.name = name; this.age = age; } say() { console.log(`my name is ${this.name}, and my age is ${this.age}`) } } const proxyTrack = (targetClass) => { const prototype = targetClass.prototype; Object.getOwnPropertyNames(prototype).forEach((name) => { targetClass.prototype[name] = new Proxy(prototype[name], { apply(target, context, args) { console.time(); target.apply(context, args); console.timeEnd(); } }) }) return new Proxy(targetClass, { construct(target, args) { const obj = new target(...args); return new Proxy(obj, { get(target, prop) { console.log(`${target.name}.${prop} is being getting`); return target[prop] } }) } }) } const MyClass = proxyTrack(Person); const myClass = new MyClass('tom', 21); myClass.say(); myClass.name;
|
对象链取值
多层属性可选链 a?.b
普通方式:
`country.province && country.province.city && country.province.city.name;
lodash方式:
_.get(country, 'province.city.name')
;
可选链:
country?.province?.city?.name
Proxy:
get(country).province.city.name()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| const obj={ person:{} } let isFirst = true; function noop() {} let proxyVoid = get(undefined); function get (obj) { if (obj === undefined) { if (!isFirst) { return proxyVoid; } isFirst = false; } return new Proxy(noop, { get(target, prop) { if ( obj !== undefined && obj !== null && obj.hasOwnProperty(prop) ) { return get(obj[prop]); } return proxyVoid; } // 代理noop,支持返回执行的时候传入的参数 apply(target, context, [arg]) { return obj === undefined ? arg : obj; }, }) } get(obj)() === obj; get(obj).person(); // {} get(obj).person.name(); // undefined get(obj).person.name.xxx.yyy.zzz(); // undefined
|