03月12, 2018

JavaScript 中的代理 Proxy

Proxy的构造方法

  1. target参数 被代理的对象
  2. handler参数 代理的操作的定义对象
let handler = {
    get: function(target, name){
        return name in target ? target[name] : "no";
    }
};

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

p.a = 1;
p.b = undefined;

console.log(p.a, p.b);    // 1, undefined

console.log('c' in p, p.c);    // false, no

静态方法

Proxy.revocable() 创建一个可撤销的Proxy对象

Proxy.revocable() 方法可以用来创建一个可撤销的代理对象,一旦某个代理对象被撤销,它将变的几乎完全不可用,在它身上执行任何的可代理操作都会抛出 TypeError 异常(注意,可代理操作一共有 14 种,执行这 14种操作以外的操作不会抛出异常)。一旦被撤销,这个代理对象永远不可能恢复到原来的状态,同时和它关联的目标对象以及处理器对象将有可能被垃圾回收掉。调用撤销方法多次将不会有任何效果,当然,也不会报错。 返回的revocable对象有两个成员属性 proxy: 代理对象 revoke: 撤销方法,该方法不需要任何参数。执行该方法后,代理对象和被代理的对象就会被撤销。

let obj = {
    name : "张三",
    age : 18
}

let revocable = Proxy.revocable(obj,{
    set : function (target, property, value, receiver) {
        if(property === 'name'){
            target[property] = "zhangsan";
        }
    }
});
revocable.proxy.name = "李四";

console.log(obj.name)//zhangsan
console.log(revocable.proxy.name)//zhangsan

revocable.revoke();
obj.name;//TypeError: Cannot perform 'get' on a proxy that has been revoked
revocable.proxy.name //TypeError: Cannot perform 'get' on a proxy that has been revoked

Proxy.handler 可以代理的操作

handler.apply()

该方法会拦截目标对象的以下操作:

  • proxy(...args) (构造方法)
  • Function.prototype.apply()
  • Function.prototype.call()
  • Reflect.apply()
class ClassMate {
    constructor(name = "张三", age = 18) {
        this.name = name;
        this.age = age;
    }

    say(arg) {
        return `hello ${arg}`;
    }
}

//代理器
let handler = {
    apply: function (target, thisArg, argumentsList) {
        console.log("************************************")
        console.log(`*  target:${target.name}`)
        console.log(`*  thisArg:${thisArg}`)
        console.log(`*  argumentsList:${argumentsList}`)
        console.log("************************************")
        return new target(argumentsList);
    }
};
let ClassMateProxy = new Proxy(ClassMate, handler);

let classMate = new ClassMate("李四", 81);//不会触发代理 classMate [object Object]
console.log(`classMate ${classMate}`)
let mateProxy1 = new ClassMateProxy();// 不会触发代理 mateProxy1 [object Object]
console.log(`mateProxy1 ${mateProxy1}`)
let mateProxy2 = ClassMateProxy();
console.log(`mateProxy2 ${mateProxy2}`)

console.log()
console.log("=================================================")
console.log()

function say(name = "张三",age = 18) {
    return `hello ${name} 年龄 ${age}`
}


let sayProxy = new Proxy(say, {
    apply: function (target, thisArg, argumentsList) {
        /* console.log("************************************")
         console.log(`*  target:${target.name}`)
         console.log(`*  thisArg:${thisArg}`)
         console.log(`*  argumentsList:${argumentsList}`)
         console.log("************************************")*/
        return target.apply(thisArg,argumentsList);
    }});

//会触发代理
console.log(sayProxy())//hello 张三 年龄 18
console.log(sayProxy.call())//hello 张三 年龄 18
console.log(sayProxy.call(null,"李四",81))//hello 李四 年龄 81
console.log(sayProxy.call(null,["李四",81]))//hello 李四,81 年龄 18
console.log(sayProxy.apply(null,["李四",81]))//hello 李四 年龄 81
console.log(sayProxy.call("李四"))//hello 张三 年龄 18

console.log()

//不会触发代理
console.log(say())//hello 张三 年龄 18
console.log(say.call(null,"李四",81))//hello 李四 年龄 81
console.log(say.call(null,["李四",81]))//hello 李四,81 年龄 18
console.log(say.apply(null,["李四",81]))//hello 李四 年龄 81
//console.log(say.apply(null,"李四",81))TypeError: CreateListFromArrayLike called on non-object
console.log(say.call("李四"))//hello 张三 年龄 18

handler.construct() 代理 new操作

在介绍handler.apply()时我时发现它的代理对象是个构造方法时,对于new 的关键字,代理并没有起作用。这是说明new 是不同于普通的方法调用,所以js中引入handler.construct() 来实现对construct的代理

  • 此方法必须要返回一个对象,否则会抛出一个异常 TypeError
class ClassMate {
    constructor(name = "张三", age = 18) {
        this.name = name;
        this.age = age;
    }
}

let ClassMateProxy = new Proxy(ClassMate,{
    construct: function (target, argumentsList, newTarget) {
        console.log("************************************")
        console.log(`*  target:${target.name}`)
        console.log(`*  argumentsList:${argumentsList}`)
        console.log(`*  newTarget:${JSON.stringify(newTarget)}`)
        console.log("************************************")
        return newTarget;
    }
});

let classMateProxy = new ClassMateProxy("李四",81);
/**
 ************************************
 *  target:ClassMate
 *  argumentsList:李四,81
 *  newTarget:undefined
 ************************************
 * */

Reflect.construct(ClassMateProxy,[]);
/**
 ************************************
 *  target:ClassMate
 *  argumentsList:
 *  newTarget:undefined
 ***********************************
 * */

handler.defineProperty()和handler.deleteProperty()

用来代理给对象增加或者删除属性的方法 defineProperty(target,property,descriptor)
deleteProperty (target,property)

  • 拦截操作

    • defineProperty Object.defineProperty() Reflect.defineProperty()
    • deleteProperty 删除属性: delete proxy[foo] 和 delete proxy.foo Reflect.deleteProperty()
  • 参数 target: 目标对象 property: 属性名称 descriptor: 描述 defineProperty 方法必须以一个 Boolean

  • 返回 必须以一个 Boolean 返回,表示定义该属性的操作成功与否。

  • 规则
    • defineProperty
      • 如果目标对象不可扩展, 将不能添加属性。
      • 不能添加或者修改一个属性为不可配置的,如果它不作为一个目标对象的不可配置的属性存在的话。
      • 如果目标对象存在一个对应的可配置属性,这个属性可能不会是不可配置的。
      • 如果一个属性在目标对象中存在对应的属性,那么 Object.defineProperty(target, prop, descriptor) 将不会抛出异常。
      • 在严格模式下, false 作为 handler.defineProperty 方法的返回值的话将会抛出 TypeError 异常.
    • deleteProperty
      • 如果目标对象的属性是不可配置的,那么该属性不能被删除。
let obj = {
    name : "张三",
    age: 18
}
let proxy = new Proxy(obj,{
    defineProperty  : function (target,property,descriptor) {
        console.log("************************************")
        console.log(`*  target:${target.name}`)
        console.log(`*  property:${property}`)
        console.log(`*  descriptor:${descriptor}`)
        console.log("************************************")
        return Object.defineProperty(target,property,descriptor)
    },
    deleteProperty: function (target,property) {
        console.log("************************************")
        console.log(`*  target:${target.name}`)
        console.log(`*  property:${property}`)
        console.log("************************************")
        return true;
    }
});

delete proxy.name;//代理方法中没有删除操作
/**
 *************************************
 *  target:张三
 *  property:name
 ************************************
 */
console.log(obj.name)//张三
/*Reflect.deleteProperty(proxy,"name");
console.log(obj.name)//张三*/

delete obj.name// 不走代理可以删除
console.log(obj.name)

console.log()
console.log("===========================")
console.log()
Object.defineProperty(proxy,"otherName",{
    value: "李四",
    writable:true,
    enumerable: true
})
/**
 *************************************
 *  target:undefined
 *  property:otherName
 *  descriptor:[object Object]
 ************************************
 */
console.log(obj.otherName)//李四

Reflect.defineProperty(proxy,"name",{
    value:"王五"
});
/**
 *************************************
 *  target:undefined
 *  property:name
 *  descriptor:[object Object]
 ************************************
 */
console.log(obj.name)//王五

handler.get(),handler.set()

代理get和set方法,这里有个陷阱,就是不要在代理操作中调用代理对象的当前代理方法,这样会导致死循环递归。比如在handler.get方法中调用代理对象receiver.get。因为调用receiver.get使用的方法是handler.get,而handler.get又调用了receiver.get

let obj = {
    name : "张三",
    age: 18
}

let proxy = new Proxy(obj,{
    get:function (target,property,receiver) {
        console.log("************************************")
        console.log(`*  target:${target.name}`)
        console.log(`*  property:${property.toString()}`)
        /*console.log(`*  receiver:${receiver}`)//RangeError: Maximum call stack size exceeded*/
        console.log("************************************")
        return target[property];
    },
    set:function (target, property, value, receiver) {
        console.log("************************************")
        console.log(`*  target:${target}`)
        console.log(`*  property:${property.toString()}`)
        console.log(`*  value:${value}`)
        /*console.log(`*  receiver:${receiver}`)//TypeError: Cannot convert object to primitive value*/
        console.log("************************************")
        target[property] = value;
        return true;
    }
});
let name = proxy.name
/**
 * ************************************
 *  target:张三
 *  property:name
 ************************************
 * @type {string}
 */
proxy.name = "王五"
/**
 * ************************************
 *  target:[object Object]
 *  property:name
 *  value:王五
 ************************************
 */

handler.getOwnPropertyDescriptor()

handler.getOwnPropertyDescriptor() 方法是拦截 Object.getOwnPropertyDescriptor() 的代理。

let obj = {
    name: "张三",
    age: 18
}

let proxy = new Proxy(obj,{
    getOwnPropertyDescriptor:function (target, prop) {
        console.log("************************************")
        console.log(`*  target.prop:${target[prop]}`)
        console.log(`*  property:${prop}`)
        console.log("************************************")
        return Object.getOwnPropertyDescriptor(target,prop);
    }
});
let descriptor = Object.getOwnPropertyDescriptor(proxy,"name");
/**
 * ************************************
 *  target.prop:张三
 *  property:name
 ************************************
 */
console.log(descriptor)//{ value: '张三',writable: true,enumerable: true,    configurable: true }
Reflect.getOwnPropertyDescriptor(proxy,"name")

handler.ownKeys()

handler.ownKeys() 方法用于拦截获取对象的所有键

  • 拦截:
    • Object.getOwnPropertyNames()
    • Object.getOwnPropertySymbols()
    • Object.keys()
    • Reflect.ownKeys()
let obj = {
    name: "张三",
    age: 18,
    toString:function () {
        return `{name: ${this.name},age: ${this.age} }`;
    }
}

let proxy = new Proxy(obj,{
    ownKeys:function (target) {
        console.log("************************************")
        console.log(`*  target:${target}`)
        console.log(`*  target:${Object.keys(target)}`)
        console.log("************************************")
        return Object.keys(target);
    }
});
Object.getOwnPropertyNames(proxy);
// Reflect.ownKeys(proxy)
/**
 * ************************************
 *  target:{name: 张三,age: 18 }
 *  target:name,age,toString
 ************************************
 */

handler.getPrototypeOf()和handler.setPrototypeOf()

用于拦截代理对象获取和设置原型的方法

  • 获取原型
    • Object.setPrototypeOf()
    • Reflect.setPrototypeOf()
  • 设置原型
    • Object.getPrototypeOf()
    • Reflect.getPrototypeOf()
    • proto
    • Object.prototype.isPrototypeOf()
    • instanceof
var obj = {};
var p = new Proxy(obj, {
    getPrototypeOf(target) {
        return Array.prototype;
    }
});
console.log(
    Object.getPrototypeOf(p) === Array.prototype,  // true
    Reflect.getPrototypeOf(p) === Array.prototype, // true
    p.__proto__ === Array.prototype,               // true
    Array.prototype.isPrototypeOf(p),              // true
    p instanceof Array                             // true
);


var handlerReturnsFalse = {
    setPrototypeOf(target, newProto) {
        return false;
    }
};

var newProto = {}, target = {};

var p1 = new Proxy(target, handlerReturnsFalse);
Reflect.setPrototypeOf(p1, newProto); // returns false
Object.setPrototypeOf(p1, newProto); // throws a TypeError

handler.isExtensible()和handler.preventExtensions()

用来拦截禁止对象扩展操作

  • preventExtensions
    • Object.preventExtensions()
    • Reflect.preventExtensions()
  • isExtensible
    • Object.isExtensible()
    • Reflect.isExtensible()
var p = new Proxy({}, {
    isExtensible: function(target) {
        console.log('isExtensible');
        return true;//也可以return 1;等表示为true的值
    },
    preventExtensions: function(target) {
        console.log('preventExtensions');
        Object.preventExtensions(target);
        return true;
    }
});

console.log(Object.isExtensible(p));//isExtensible true

console.log(Object.preventExtensions(p)); // preventExtensions {}

handler.has()

handler.has() 方法可以看作是针对 in 操作的钩子.

  • 拦截下面这些操作:
    • 属性查询: foo in proxy
    • 继承属性查询: foo in Object.create(proxy)
    • with 检查: with(proxy) { (foo); }
    • Reflect.has()
var p = new Proxy({}, {
    has: function(target, prop) {
        console.log('has: ' + prop);
        return true;
    }
});

console.log('a' in p); //has: a ,true

本文链接:https://www.qiangshuidiyu.xin/post/js-proxy.html

-- EOF --

Comments