灏天阁

Proxy Reflect

· Yin灏

Proxy

Proxy意思为 “代理”,即在访问对象之前建立一道 “拦截”,任何访问该对象的操作之前都会通过这道 “拦截”,即执行Proxy里面定义的方法。

let pro = new Proxy(target,handler);
/*
    new Proxy()表示生成一个Proxy实例
    target参数表示所要拦截的目标对象
    handler参数也是一个对象,用来定制拦截行为。
*/

handler

Proxy 支持 13 种拦截行为(handle),针对解决上一节的问题,简单介绍下其中 2 种拦截行为,get 与 set。

get

get(target, propKey, receiver)
/*
  用于拦截某个属性的读取操作,可以接受三个参数:
    - target:目标对象
    - propKey:属性名
    - receiver(可选):proxy 实例本身(严格地说,是操作行为所针对的对象)
*/

set

set(target, propKey, value, receiver)
/*
  用于拦截某个属性的赋值操作,可以接受四个参数:
    - target:目标对象
    - propKey:属性名
    - value:属性值
    - receiver(可选):Proxy 实例本身
*/

示例1:

let hero = {
    name: '赵云',
    age: 25
}

let handler = {}; // 未对拦截对象设定拦截方法

let heroProxy = new Proxy(hero, handler);

console.log(heroProxy.name); // '赵云'

heroProxy.name = '黄忠';

console.log(heroProxy.name); // '黄忠'

示例2:

let hero = {
    name: '赵云',
    age: 25
}

let handler = {
    get: (hero, name) => {
        const heroName = `英雄名是${hero.name}`;
        return heroName;
    },
    set: (hero, name, value) => {
        console.log(`${hero.name} change to ${value}`);
        hero[name] = value;
        return true;
    }
};

let heroProxy = new Proxy(hero, handler);

console.log(heroProxy.name); // 英雄名是赵云

heroProxy.name = '吕布';

console.log(heroProxy.name);

/*
  英雄名是赵云
  赵云 change to 吕布
  英雄名是吕布
*/

示例3:

Proxy 也可以作为其他对象的原型对象使用。

let hero = {
    name: '赵云',
    age: 25
}

let handler = {
    get: (hero, name) => {
        const heroName = `英雄名是${hero.name}`;
        return heroName;
    },
    set: (hero, name, value) => {
        console.log(`${hero.name} change to ${value}`);
        hero[name] = value;
        return true;
    }
};

let heroProxy = new Proxy(hero, handler);

// Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
let obj = Object.create(heroProxy)

console.log(obj.name);
obj.name = '黄忠';
console.log(obj.name);

/*
  英雄名是赵云
  赵云 change to 吕布
  英雄名是吕布
*/

解决数据绑定的问题

let hero = {
    name: '赵云',
    hp: 100,
    sp: 100,
    equipment: ['马', '长枪']
}

let handler = {
    set(target, property, value) {
        console.log(target); // {name: "赵云", hp: 100, sp: 100, equipment: Array(2)}
        console.log(property); // hp
        console.log(value); // 200
        target[property] = value; // 更改属性值
        return true;
    }
}

let heroProxy = new Proxy(hero, handler);

heroProxy.hp = 200;

console.log(hero.hp); // 200

最后,同样把 “佩剑” 添加到英雄的装备中

let heroProxy2 = new Proxy(hero.equipment, handler);
heroProxy2.push('佩剑');
/*
  可以发现,heroProxy.push('佩剑'); 触发了两次 set,原因是push即修改了 hero.equipment 的内容,又修改了 hero.equipment 的 length。
*/

Reflect

在了解了 Proxy之 后,细心的我们一定发现,若需要在 Proxy 内部调用对象的默认行为,该如何实现?

Reflect正是 ES6 为了操作对象而提供的新 API

基本特点
  • 只要 Proxy 对象具有的代理方法,Reflect 对象全部具有,以静态方法的形式存在。这些方法能够执行默认行为,无论 Proxy 怎么修改默认行为,总是可以通过 Reflect 对应的方法获取默认行为。

  • 修改某些 Object 方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc) 在无法定义属性时,会抛出一个错误,而 Reflect.defineProperty(obj, name, desc) 则会返回 false

  • Object 操作都变成函数行为。某些 Object 操作是命令式,比如 name in objdelete obj[name],而 Reflect.has(obj, name) Reflect.deleteProperty(obj, name) 让它们变成了函数行为。

静态方法

Reflect 对象一共有 13 个静态方法(匹配 Proxy 的13种拦截行为)。

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args)
  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.defineProperty(target, name, desc)
  • Reflect.deleteProperty(target, name)
  • Reflect.has(target, name)
  • Reflect.ownKeys(target)
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)

大部分与 Object 对象的同名方法的作用都是相同的,而且它与 Proxy 对象的方法是一一对应的。

下面通过 3 个实例来对比 Object 对象方法与 Reflect 对象方法。

示例1:

// Object对象方法
try {
    Object.defineProperty(target, name, property)
} catch (e) {
    console.log('error');
}

//Reflect对象方法
if (Reflect(target, name, property)) {
    console.log("success");
} else {
    console.log("error")
}
// 由于 Reflect(target, name, property) 返回的是 boolean,代码语义性更好。

示例2:

let hero = {
    name: '赵云',
    hp: 100,
    sp: 100,
    equipment: ['马', '长枪']
}

//Object对象方法
console.log('name' in hero); // true

//Reflect对象方法
console.log(Reflect.has(hero, 'name')); // true

// Object操作是命令式,而Reflect让它们变成了函数行为

示例3:

let hero = {
    name: '赵云',
    hp: 100,
    sp: 100,
    equipment: ['马', '长枪']
}

let handler = {
    get(target, name, receiver) {
        if (name === "name") {
            console.log("success");
        } else {
            console.log("failure");
        }
        return Reflect.get(target, name, receiver);
    }
}

let heroProxy = new Proxy(hero, handler);
console.log(heroProxy.name);

/*
  Reflect 对象的操作和 Proxy 对象的操作一一对应,在 Proxy 的拦截操作中,可以直接利用 Reflect 对象直接获取 Proxy 的默认值。
*/

/*
  success
  赵云
*/

观察者模式

观察者模式(Observer mode)指的是函数自动观察数据对象,一旦数据有变化,函数就会自动执行。

let hero = {
    name: '赵云',
    hp: 100,
    sp: 100,
    equipment: ['马', '长枪']
}

const handler = {
    set(target, key, value, receiver) {
        //内部调用对应的 Reflect 方法
        const result = Reflect.set(target, key, value, receiver);
        console.log(result);
        // Reflect.set() 已经更改了 hero 的值
        console.log(hero); // {name: "黄忠", hp: 100, sp: 100, equipment: Array(2)}
        //执行观察者队列
        observableArray.forEach(item => item());
        return result;
    }
}

//初始化Proxy对象,设置拦截操作
const createProxy = (obj) => new Proxy(obj, handler);

//初始化观察者队列
const observableArray = new Set();

const heroProxy = createProxy(hero);

//将监听函数加入队列
observableArray.add(() => {
    console.log(heroProxy.name);
});

heroProxy.name = "黄忠";
// --> 黄忠

对象的多重继承

实现对象间的单继承,比如 obj2 继承 obj1,可以使用 Object.setPrototypeOf 方法,但是没法实现多继承。

const people = {
    name: 'people',
    run() {
        console.log('people.run:', this.name);
    }
};

const powerMan = {
    name: 'powerMan',
    run() {
        console.log('powerMan.run:', this.name);
    },
    fight() {
        console.log('powerMan.fight:', this.name);
    }
};

const handler = {
    get(target, name, receiver) {
        if (Reflect.has(target, name)) {
            return Reflect.get(target, name, receiver);
        } else {
            for (let P of target[Symbol.for('[[Prototype]]')]) {
                if (Reflect.has(P, name)) {
                    return Reflect.get(P, name, receiver);
                }
            }
        }
    }
};

const hero = new Proxy({
    name: 'hero',
    strike() {
        this.run();
        this.fight();
    }
}, handler);

hero[Symbol.for('[[Prototype]]')] = [people, powerMan];
hero.strike();

/*
  people.run: hero
  powerMan.fight: hero
*/

用了一个自定义的属性 Symbol.for("[[Prototype]]") 来表示要继承的多个父对象。

然后用 Proxy 来拦截所有 hero 中的 get 请求,使用 Reflect.has 方法检查 hero 中是否存在相应的属性或者方法。

如果存在,则直接转发;如果不存在,则遍历父对象列表,在父对象中逐个检查是否存在相应的属性或者方法。

若存在则调用。若不存在,则相当于 get 返回 undefined

- Book Lists -