对象劫持、访问劫持、拦截、代理——Proxy以及功能实现

目录
  • 概述
  • proxy实例的方法
  • 应用例子
概述

Proxy在目标对象前设一个拦截层,外界对该对象的访问都必须先通过这层拦截,因此提供了一种机制可以对外界的访问进行过滤和改写。

对空对象进行拦截
/**
 * 对空对象进行拦截,重定义了
 */
var obj = new Proxy({}, {
    get: function (target, key, receiver) {
        console.log(`getting ${key}!`);
        return Reflect.get(target, key, receiver);
    },
    set: function (target,key,value,receiver) {
        console.log(`setting ${key}!`);
        return Reflect.set(target, key, value, receiver);
    }
});

obj.count = 1;
// setting count!
++obj.count
// getting count!
// setting count!
// 2

上面的代码说明,Proxy实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。

get方法拦截读取操作
/**
 * get方法用于拦截某个属性的读取操作
 */

 var person = {
     name: "张三"
 };

 var proxy = new Proxy (person, {
     get: function (target, property) {
         if (property in target) {
             console.log(target[property]);
         }
         else{
             throw new ReferenceError("Property \"" + property + "\" does not exist. ");
         }
     }
 })

 proxy.name
//  proxy.age
get拦截实现数组读取负数索引
/**
 * 使用get拦截实现数组读取负数索引
 */
function createArray(...elements) {
    let handler = {
        get(target, propsKey, receiver){
            let index = Number(propKey);
            if (index < 0){
                propKey = String(target.length + index);
            }
            return Reflect.get(target, propKey, receiver);
        }
    };

    let target = [];
    target.push(...elements);
    return new Proxy(target, handler);
}

let arr = createArray('a', 'b', 'c');
arr[-1] // c
利用get拦截实现一个生成各种DOM节点的通用函数dom
/**
 * 利用get拦截实现一个生成各种DOM节点的通用函数dom
 */

 const dom = new Proxy({},{
     get(target,property){
         return function (attrs{}, ...children) {
             // 创建元素节点
             const el = document.createElement(property);
             for(let prop of Object.keys(attrs)){
                 el.setAttribute(prop,attrs[prop]);
             }
             // 创建文本节点
             for(let child of children){
                 child = document.createTextNode(child);
             }
             el.appendChild(child);
         }
         return el;
     }
 });

 const el = dom.div({},'hello,my name is',
 dom.a({href:'//example.com'},'Mark'),',I like:',
 dom.ul({},
    dom.li({},'the web')),
    dom.li({},'Food'),
    dom.li({},'actually')
 )
set方法用于拦截某个属性的赋值操作

假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy对象保证age的属性值符合要求

/**
 * 假定Person对象有一个age属性,该属性应该是一个不大于200的整数,
 * 那么可以使用Proxy对象保证age的属性值符合要求
 */

 let validator = {
     set: function (obj,prop,value) {
         if (prop === 'age') {
             if (!Number.isInteger(value)) {
                 throw new TypeError('The age is not an integer');
             }
             if (value > 200) {
                 throw new RangeError('The age seems invalid');
             }
         }

         //对于age以外的属性,直接保存
         obj[prop] = value;
     }
 };

 let person = new Proxy({}, validator);

 person.age = 100;
 person.age // 100
 person.age = 'young' // 报错
 person.age = 300 // 报错
结合get和set方法,防止内部属性被外部读/写
 /**
  * 结合get和set方法,防止内部属性被外部读/写
  */
 var handler = {
     get (target, key){
         invariant(key, 'get');
         return target[key];
     },
     set (target,key,value){
         invariant(key, 'set');
         target[key] = value;
         return true;
     }
 };
 function invariant(key, action) {
     if (key[0] === '_') {
         throw new Error(`Invalid attempt to ${action} 
         private "${key}" property`);
     }
 }
 var target = {};
 var proxy = new Proxy(target, handler);
 proxy._prop
//Error :Invalid attempt to get private "_prop" property
proxy._prop = 'c'
// Error: Invalid attempt to set private "_prop" property

上面的代码中,只要读/写的属性名的第一个字符是下划线,一律抛出错误,从而达到禁止读/写内部属性的目的。

Proxy.revocable()

该方法返回一个可取消的Proxy实例。

//proxy.revocable()
let target = {};
let handler = {};

// 返回一个对象,proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例
let {proxy, revoke} = Proxy.revocable(target, handler);

proxy.foo = 123;
proxy.foo // 123

revoke();
proxy.foo //TypeError: Revoked

该方法的一个使用场景是:目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。

this问题

虽然Proxy可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下也无法保证与目标对象的行为一致。主要原因是在Proxy代理的情况下,目标对象内部的this关键字会指向Proxy代理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值