ES6—Proxy

概述

  1. 在目标对象前增添一层拦截,来代理某些操作,类似代理器
//对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为
var obj = new Proxy({}, {
  get: function (target, propKey, receiver) {
    console.log(`getting ${propKey}!`);
    return Reflect.get(target, propKey, receiver);
  },
  set: function (target, propKey, value, receiver) {
    console.log(`setting ${propKey}!`);
    return Reflect.set(target, propKey, value, receiver);
  }
});
  1. Proxy实例也可作为其他对象的原型对象
// proxy对象是obj对象的原型,obj对象没有time属性,根据原型链就会在proxy对象上读取该属性,导致拦截
var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

let obj = Object.create(proxy);
obj.time // 35

Proxy实例的方法

get()

  1. get用法
    拦截属性的读取操作
get: function(目标对象, 属性名, proxy实例)
  1. get方法可以继承
//拦截器定义在property对象,堆读取obj对象继承的属性时会生效
let proto = new Proxy({}, {
  get(target, propertyKey, receiver) {
    console.log('GET ' + propertyKey);
    return target[propertyKey];
  }
});

let obj = Object.create(proto);
obj.foo // "GET foo"
  1. get方法的第三个参数的例子,它总是指向原始的读操作所在的那个对象,一般情况下就是 Proxy 实例
//proxy对象的getReceiver属性是由proxy对象提供的,所以receiver指向proxy对象
const proxy = new Proxy({}, {
  get: function(target, key, receiver) {
    return receiver;
  }
});
proxy.getReceiver === proxy // true

set()

  1. set方法用来拦截某个属性的赋值操作
set(目标对象, 属性名, 属性值, Proxy实例)
  1. set方法的第四个参数receiver,指的是原始的操作行为所在的那个对象,一般情况下是proxy实例本身
//myObj并没有foo属性,因此引擎会到myObj的原型链去找foo属性。myObj的原型对象proxy是一个 Proxy 实例,设置它的foo属性会触发set方法。这时,第四个参数receiver就指向原始赋值行为所在的对象myObj。
const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = receiver;
  }
};
const proxy = new Proxy({}, handler);
const myObj = {};
Object.setPrototypeOf(myObj, proxy);

myObj.foo = 'bar';
myObj.foo === myObj // true
  1. 如果目标对象自身的某个属性不可写,那么set方法将不起作用
const obj = {};
Object.defineProperty(obj, 'foo', {
  value: 'bar',
  writable: false
});

const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = 'baz';
  }
};

const proxy = new Proxy(obj, handler);
proxy.foo = 'baz';
proxy.foo // "bar"
  1. 严格模式下,set代理如果没有返回true,就会报错

apply()

  1. 拦截函数的调用、call和apply操作
apply(目标对象, 目标对象上下文对象, 目标对象参数数组)

每当执行proxy函数(直接调用或call和apply调用),就会被apply方法拦截。

var twice = {
  apply (target, ctx, args) {
    return Reflect.apply(...arguments) * 2;
  }
};
function sum (left, right) {
  return left + right;
};
var proxy = new Proxy(sum, twice);
proxy(1, 2) // 6
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 30

has()

  1. 用来拦截HasProperty操作,即判断对象是否具有某个属性
has(目标对象, 需查询的属性名)
  1. 如果原对象不可配置或者禁止扩展,这时has()拦截会报错
var obj = { a: 10 };
Object.preventExtensions(obj);

var p = new Proxy(obj, {
  has: function(target, prop) {
    return false;
  }
});

'a' in p // TypeError is thrown
  1. has()拦截只对in运算符生效,对for…in循环不生效。
  2. has()方法拦截的是HasProperty操作,而不是HasOwnProperty操作,即has()方法不判断一个属性是对象自身的属性,还是继承的属性
let stu1 = {name: '张三', score: 59};
let stu2 = {name: '李四', score: 99};

let handler = {
  has(target, prop) {
    if (prop === 'score' && target[prop] < 60) {
      console.log(`${target.name} 不及格`);
      return false;
    }
    return prop in target;
  }
}

let oproxy1 = new Proxy(stu1, handler);
let oproxy2 = new Proxy(stu2, handler);

'score' in oproxy1
// 张三 不及格
// false

'score' in oproxy2
// true

for (let a in oproxy1) {
  console.log(oproxy1[a]);
}
// 张三
// 59

for (let b in oproxy2) {
  console.log(oproxy2[b]);
}
// 李四
// 99

construct()

  1. 用于拦截new命令
construct(目标对象, 构造函数的参数数组, new命令作用的构造函数)
  1. construct必须返回一个对象,目标对象必须是函数
  2. construct()方法中的this指向的是handler,而不是实例对象
const handler = {
  construct: function(target, args) {
    console.log(this === handler);
    return new target(...args);
  }
}

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

deleteProperty()

deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除

//deleteProperty方法拦截了delete操作符,删除第一个字符为下划线的属性会报错
var handler = {
  deleteProperty (target, key) {
    invariant(key, 'delete');
    delete target[key];
    return true;
  }
};
function invariant (key, action) {
  if (key[0] === '_') {
    throw new Error(`Invalid attempt to ${action} private "${key}" property`);
  }
}

var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: Invalid attempt to delete private "_prop" property

defineProperty()

拦截了Object.defineProperty()操作

//defineProperty()方法内部没有任何操作,只返回false,导致添加新属性总是无效
var handler = {
  defineProperty (target, key, descriptor) {
    return false;
  }
};
var target = {};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar' // 不会生效

getOwnPropertyDescriptor()

拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined。

//handler.getOwnPropertyDescriptor()方法对于第一个字符为下划线的属性名会返回undefined
var handler = {
  getOwnPropertyDescriptor (target, key) {
    if (key[0] === '_') {
      return;
    }
    return Object.getOwnPropertyDescriptor(target, key);
  }
};
var target = { _foo: 'bar', baz: 'tar' };
var proxy = new Proxy(target, handler);
Object.getOwnPropertyDescriptor(proxy, 'wat')
// undefined
Object.getOwnPropertyDescriptor(proxy, '_foo')
// undefined
Object.getOwnPropertyDescriptor(proxy, 'baz')
// { value: 'tar', writable: true, enumerable: true, configurable: true }

getPrototypeOf()

用来拦截获取对象原型

  1. Object.prototype.proto
  2. Object.prototype.isPrototypeOf()
  3. Object.getPrototypeOf()
  4. Reflect.getPrototypeOf()
  5. instanceof
//getPrototypeOf()方法拦截Object.getPrototypeOf(),返回proto对象
var proto = {};
var p = new Proxy({}, {
  getPrototypeOf(target) {
    return proto;
  }
});
Object.getPrototypeOf(p) === proto // true

isExtensible()

拦截Object.isExtensible()操作(该方法只能返回bool)

//设置了isExtensible()方法,在调用Object.isExtensible时会输出called
var p = new Proxy({}, {
  isExtensible: function(target) {
    console.log("called");
    return true;
  }
});

Object.isExtensible(p)
// "called"
// true

ownKeys()

  1. 用来拦截对象自身属性的读取操作
  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • for…in循环
  1. 使用Object.keys()方法时,有三类属性会被ownKeys()方法自动过滤,不会返回
  • 目标对象上不存在的属性
  • 属性名为 Symbol 值
  • 不可遍历(enumerable)的属性

preventExtensions

  1. 拦截Object.preventExtensions(),该方法返回一个布尔值
  2. 只有目标对象不可扩展时(即Object.isExtensible(proxy)为false),proxy.preventExtensions才能返回true

setPrototypeOf()

用来拦截Object.setPrototypeOf()方法

//只要修改target的原型对象,就会报错
var handler = {
  setPrototypeOf (target, proto) {
    throw new Error('Changing the prototype is forbidden');
  }
};
var proto = {};
var target = function () {};
var proxy = new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto);
// Error: Changing the prototype is forbidden

Proxy.revocable()

返回一个可取消的Proxy实例

let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);

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

revoke();
proxy.foo //Revoked

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

this问题

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

//一旦proxy代理target,target.m()内部的this就是指向proxy,而不是target
const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值