Proxy 代理器(1)

Proxy 代理器

主要用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,可以对外界的访问进行过滤和改写。

  • proxy结构
  • proxy事例

proxy结构

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。

var proxy = new Proxy(target, handler);

new Proxy()表示生成一个Proxy实例,
target参数表示所要拦截的目标对象,
handler参数也是一个对象,用来定制拦截行为,可以定义一系列的拦截函数,做过滤。

let target = {
    name: "张三"
};
let handler = {
    get: function (target, property) {
        if (property in target) {
            return target[property];
        } else {
            throw new ReferenceError("Property \"" + property + "\" does not exist.");
        }
    }
}

let  proxy = new Proxy(target, handler);

console.log(proxy.name)// "张三"
proxy.age // 抛出一个错误

handler支持的拦截操作:

函数参数作用
gettarget, propKey, receiver拦截对象属性的读取,比如proxy.foo和proxy[‘foo’],最后一个参数receiver是一个对象
settarget, propKey, value, receiver拦截对象属性的设置,比如proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值
hastarget, propKey拦截propKey in proxy的操作,返回一个布尔值
deletePropertytarget, propKey拦截delete proxy[propKey]的操作,返回一个布尔值。
ownKeys(target)target, propKey拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性
getOwnPropertyDescriptortarget, propKey拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
definePropertytarget, propKey, propDesc拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
preventExtensionstarget拦截Object.preventExtensions(proxy),返回一个布尔值。
getPrototypeOftarget拦截Object.getPrototypeOf(proxy),返回一个对象。
isExtensibletarget拦截Object.isExtensible(proxy),返回一个布尔值。
setPrototypeOftarget, proto拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。

如果目标对象是函数,那么还有两种额外操作可以拦截。

函数参数作用
applytarget, object, args拦截 Proxy 实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…).
constructtarget, args拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)。

GET拦截

get方法用于拦截某个属性的读取操作。上文已经有一个例子,下面是另一个拦截读取操作的例子,其次get方法可以继承。

let proto = new Proxy({}, {
  get(target, propertyKey, receiver) {
    console.log('GET '+propertyKey);
    return target[propertyKey];
  }
});

let obj = Object.create(proto);
obj.xxx // "GET xxx"

利用get拦截,实现一个生成各种DOM节点的通用函数dom。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script language="javascript">
    const dom = new Proxy({}, {
        get(target, property) {
            return function(attrs = {}, ...children) {
                const el = document.createElement(property);
                console.log(el);
                for (let prop of Object.keys(attrs)) {
                    el.setAttribute(prop, attrs[prop]);
                    console.log(prop,attrs[prop]);
                }
                for (let child of children) {
                    if (typeof child === 'string') {
                        console.log(child);
                        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 that\'s it')
        )
    );

    console.log(document.body.appendChild(el));
</script>
</body>
</html>

如果一个属性不可配置(configurable)和不可写(writable),则该属性不能被代理,通过 Proxy 对象访问该属性会报错.

const targetExtends = Object.defineProperties({}, {
    foo: {
        value: 123,
        writable: false,
        configurable: false
    },
});

const handlerExtends = {
    get(target, propKey) {
        return 'abc';
    }
};

const proxyExtends = new Proxy(targetExtends, handlerExtends);
console.log(proxyExtends.foo)

set方法

用来拦截某个属性的赋值操作。

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;

console.log(person.age) // 100
person.age = 'young' // 报错
// person.age = 300 // 报错

结合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

如果目标对象自身的某个属性,不可写也不可配置,那么set不得改变这个属性的值,只能返回同样的值,否则报错。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值