ES6中Proxy的基本使用

1、初始化的几种方式

1.1、Proxy关联原始对象

let obj = {
    name: 'sian'
}
/**
 * new Proxy(target, handler);
 * 第一个参数为原始对象
 * 第二个参数为拦截行为
 */
let proxy = new Proxy(obj, {
    get: function(target, prop) {
        return 'xxx'
    }
});
object.name // sian
proxy.name // xxx

1.2、通过继承的方式实现代理

即:Proxy做为其他对象的原型对象

let proxy = new Proxy({}, {
    get: function(target, prop) {
        return 'xxx'
    }
})
let obj = Object.create(proxy);
// obj并没有time属性,根据原型链,最终会到proxy对象上读取
obj.name // xxx
let person = {
    age: 10
}
Object.setPrototypeOf(person, proxy);
person.name // xxx

2、实例方法

2.1、get(target, propKey, receiver)

拦截对象属性的读取,比如proxy.fooproxy['foo']

  • 数组负数索引实现倒序取值(如:-1取最后一个对象)
function CreateArray(...elements) {
    let target = [];
    let handler = {
        get: function (target, propKey, receiver) {
            let index = Number(propKey);
            if (index < 0) {
                index = target.length + index;
            }
            return Reflect.get(target, String(index), receiver);
        }
    }
    target.push(...elements);
    let proxy = new Proxy(target, handler);
    return proxy;
}

let array = CreateArray(1, 2, 3, 4, 5);

console.log('array[-1]: ', array[-1]) // array[-1]: 5
  • 读取操作转为执行某个函数,实现属性的链式操作
function Pipe(value) {
    // 存储链式调用函数
    let funcStack = [];
    let proxy = new Proxy({}, {
        get: function (target, propKey) {
            // 取值
            if (propKey === 'get') {
                return funcStack.reduce((preValue, func) => func(preValue), value)
            }
            // 链式调用,将函数压入调用栈,取值时逐个调取
            else {
                funcStack.push(window[propKey])
            }
            return proxy;
        }
    })
    return proxy;
}

var double = value => value * 2;
var pow = value => value * value;

let ret = Pipe(3).double.pow.get;
console.log('ret: ', ret); // ret: 36
  • get方法的第三个参数receiver指的是调用者本身,如果是proxy对象调用则为proxy对象,如果原始对象调用则为原始对象
let proxy = new Proxy({}, {
    get: function(target, propKey, receiver) {
        return receiver;
    }
})

// 代理对象调用方法,则receiver为代理对象
let ret = proxy.getReceiver === proxy
console.log('ret: ', ret);

// 通过原型链的方式创建代理,使用原始对象调用方法,receiver为原始对象
let obj = Object.create(proxy);
let ret2 = obj.getReceiver === obj;
console.log('ret2: ', ret2);

2.2、set(target, proKey, value, receiver)

拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值

  • 赋值校验
let proxy = new Proxy({}, {
    set:function(target, propKey, value) {
        // 大于100的都赋值为100
        if (value > 100) {
            value = 100;
        }
        target[propKey] = value;
        return true;
    }
})

let person = Object.create(proxy);
person.age = 120;
console.log('person.age: ', person.age)

2.3、has(target, propKey)

拦截propKey in proxy操作,返回一个布尔值

  • 调用代理时触发该方法
let handler = {
    has(target, key) {
        if (key[0] === '_') {
            return false;
        }
        return key in target;
    }
}

let target = {
    _prop : 'xxx',
    prop : 'zzz'
}

let proxy = new Proxy(target, handler);

console.log('prop' in proxy) // true
console.log('_prop' in proxy) // false
  • 注意:如果是通过继承的方式实现的代理,该方法不会被调用
let handler = {
    has(target, key) {
        if (key[0] === '_') {
            return false;
        }
        return key in target;
    }
}

let target = {
    _prop : 'xxx',
    prop : 'zzz'
}

Object.setPrototypeOf(target, new Proxy({}, handler));

console.log('prop' in target) // true
console.log('_prop' in target) // true
  • 该方法对for...in循环不生效
let handler = {
    has(target, key) {
        if (key[0] === '_') {
            return false;
        }
        return key in target;
    }
}

let target = {
    _prop : 'xxx',
    prop : 'zzz'
}

let proxy = new Proxy(target, handler);

for(let key in target) {
    // 两个属性均会输出
    console.log('key: ', key)
}

2.4、deleteProperty(target, propKey)

拦截delete proxy[propKey]的操作,返回一个布尔值

let handler = {
    deleteProperty(target, key){
        console.log('deleteProperty');
        return Reflect.deleteProperty(target, key);
    }
}
let person = {
    age: 12
}

let proxy = new Proxy(person, handler);


console.log('person: ', person); // person:  {age: 12}
delete proxy.age; // deleteProperty
console.log('person: ', person); // person:  {}

2.5、ownKeys(target)

拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性

let handler = {
    ownKeys(target) {
        console.log('ownKeys');
        return Reflect.ownKeys(target);
    }
}

let person = {
    name: 'xxx',
    age: 18
}

let proxy = new Proxy(person, handler);

// Object.keys
Object.keys(proxy);
// for...in
for(let key in proxy) {
    console.log('key: ', key)
}
// getOwnPropertyNames()
Object.getOwnPropertyNames(proxy)
// getOwnPropertySymbols()
Object.getOwnPropertySymbols(proxy);
  • for...in中如果返回的数组中不包含原对象的属性,则不会遍历出值
let handler = {
    ownKeys(target) {
        console.log('ownKeys');
        // 数组中必须是字符串或Symbol否则会报错
        return ['a', 'b'];
    }
}

let person = {
    name: 'xxx',
    age: 12
}

let proxy = new Proxy(person, handler);

// 不会有输出
for(let key in proxy) {
    console.log('key: ', key);
}

2.6、getOwnPropertyDescriptor(target, propKey)

拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象

  • 当前对象自己属性的描述
let handler = {
    getOwnPropertyDescriptor(target, key) {
        console.log('getOwnPropertyDescriptor...');
        return Reflect.getOwnPropertyDescriptor(target, key);
    }
}
let person = {
    age: 12
}

let proxy = new Proxy(person, handler);

let age = Object.getOwnPropertyDescriptor(proxy, 'age');
let name = Object.getOwnPropertyDescriptor(proxy, 'name');
console.log('age: ', age); // {value: 12, writable: true, enumerable: true, configurable: true}
console.log('name: ', name); // undefined

2.7、defineProperty(target, propKey, propDesc)

拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值

let handler = {
    defineProperty(target, key, descriptor) {
        console.log('defineProperty')
        return Reflect.defineProperty(target, key, descriptor);
    }
}

let person = {
    age: 10
}

let proxy = new Proxy(person, handler);

// 两种方式增加属性都会触发
proxy.height = 180;
Object.defineProperty(proxy, 'weight', {
    value: 70
});

console.log('person: ', person)

2.8、preventExtensions(target)

拦截Object.preventExtensions(proxy),返回一个布尔值

let handler = {
    preventExtensions(target) {
        console.log('perventExtensions...');
        return Reflect.preventExtensions(target);
    }
}

let person = {
    name: 'xxx',
    age: 13
}

let proxy = new Proxy(person, handler);

Object.preventExtensions(proxy);

2.9、getPrototypeOf(target)

拦截Object.getPrototypeOf(proxy),返回一个布尔值

  • Object.prototype.__proto__
  • Object.prototype.isPrototypeOf()
  • Object.getPrototypeOf()
  • Reflect.getPrototypeOf()
  • instanceof
let handler = {
    getPrototypeOf(target) {
        console.log('getPrototypeOf....');
        return Reflect.getPrototypeOf(target);
    }
}

let target = {
    name: 'xxx'
}

let proxy = new Proxy(target, handler);

// 以下4句代码都会触发handler中的getPrototypeOf方法
Object.getPrototypeOf(proxy)
Reflect.getPrototypeOf(proxy)
proxy instanceof Object
// Object.prototype是否是proxy的原型
Object.prototype.isPrototypeOf(proxy)

2.10、isExtensible(target)

拦截Object.isExtensible(proxy),返回一个布尔值

let handler = {
    isExtensible(target) {
        console.log('isExtensible...');
        // 这里如果返回和原对象的结果不一致会报错
        // 如果Object.preventExtensions(target),则必须返回false,否则必须返回true
        return Reflect.isExtensible(target);
    }
}

let person = {
    name: 'xxx'
}
// 禁止扩展
Object.preventExtensions(person)

let proxy = new Proxy(person, handler);

let ret = Object.isExtensible(proxy);
console.log('ret: ', ret) // false

2.11、setPrototypeOf(target, proto)

拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截

let handler = {
    setPrototypeOf(target, proto) {
        console.log('setPrototypeOf', proto)
        // 返回一个布尔值
        return true;
    }
}

let person = {
    name: 'xxx',
    age: 13
}

let proxy = new Proxy(person, handler);

Object.setPrototypeOf(proxy, new Function())

2.12、apply(target, object, args)

拦截Proxy实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apple(...)

  • 该方法作用的是函数,即Proxy()的第一个参数是函数
let handler = {
    apply(target, ctx, args) {
        return Reflect.apply(target, ctx, args) * 2;
    }
}

function sum(a, b) {
    return a + b;
}

let proxy = new Proxy(sum, handler);
console.log(proxy(1, 2)); // 6
console.log(proxy.call(null, 1, 2)); // 6
console.log(proxy.apply(null, [1, 2])); // 6
console.log(Reflect.apply(proxy, null, [1, 2])); // 6

2.13、construct(target, args, newTarget)

拦截Proxy实例作为构造函数调用的操作,比如new Proxy(...args), target:目录对象

let handler = {
    construct(target, args, newTarget){
        console.log('target: ', target, 'args: ', args, 'newTarget: ', newTarget);
        return Reflect.construct(target, args, newTarget);
    }
}

function Target(age) {
    this.age = age;
}

let Person = new Proxy(Target, handler);

let p = new Person(1);
console.log('p: ', p)// Target {age: 1}

3、其他特性

3.1、revocable方法

Proxy的静态方法revocable返回proxy对象与revoca函数

let handler = {
    get:function(target, propKey, receiver) {
        return 'xxx';
    }
}

let person = {
    name: 'name'
}

let {proxy, revoca} = Proxy.revocable(person, handler);

console.log('proxy.name: ', proxy.name)

revoca();
// 再次访问报错
console.log('proxy.name: ', proxy.name)

3.2、this指针

  • target内部,一般情况下this指向的是调用者,即target调用则指向target,proxy调用则指向proxy
let target = {
    test() {
        console.log(this === target)
    }
}
let proxy = new Proxy(target, {});

target.test(); // true
proxy.test();  // false
  • handler内方法中this指向的是handler对象
let handler = {
    get(target, propKey, receiver) {
        console.log(this == handler);
    }
}

let proxy = new Proxy({}, handler);

proxy.foo; // 触发get方法,控制台输出true
  • 有些原生对象的内部属性只有this指针才能访问,从而proxy无法代理
let date = new Date('2021-01-01');
console.log(date.getDate()); // 1

let proxy = new Proxy(date, {})
console.log(proxy.getDate()) // Uncaught TypeError: this is not a Date object.

// 可以通过this绑定原生对象来解决这个问题
let date = new Date('2021-01-01');
let proxy = new Proxy(date, {
    get(target, propKey, receiver) {
        if (propKey === 'getDate') {
            // 绑定this为原始对象
            return target[propKey].bind(target);
        }
        return Reflect.get(target, propKey, receiver);
    }
})
console.log(proxy.getDate()) // 1

Leave a Reply