Отражение и прокси в JS

JavaScript

ProxyиReflectНовый API для ES6.

Reflect

Reflect— это встроенный объект, предоставляющий методы для перехвата операций JavaScript. Reflect не является функциональным объектом, поэтому его нельзя построить.ReflectВсе методы являются статическими, насколькоMathОпять же, в настоящее время он не имеет статических свойств.

Reflectметоды объекта иProxyМетод объекта тот же.

ReflectВсего существует 13 статических методов:

его можно разделить на части, которые изначально существовалиObjectметод выше, экранируйте его, чтобыReflect, и внес небольшие изменения, чтобы сделать метод более разумным.

  1. definePropertyиObject.definePropertyАналогично, но когда объект не может быть определенObject.definePropertyсообщит об ошибкеReflect.definePropertyнет, вернетсяfalse, отдача от успехаtrue, если это не объект, он все равно сообщит об ошибке.
  2. getPrototypeOf(target)иObject.getPrototypeOfМол, возвращает прототип указанного объекта.
  3. setPrototypeOf(target, prototype)иObject.setPrototypeOfНапример, он устанавливает прототип указанного объекта в другой объект.
  4. getOwnPropertyDescriptor()иObject.getOwnPropertyDescriptorНапример, если он существует в объекте, возвращает значение данного свойства.дескриптор свойства.
  5. isExtensible(target)иObject.isExtensibleТочно так же, чтобы определить, является ли объект расширяемым (можно ли добавить к нему новые свойства), их отличие состоит в том, что, когда параметр не является объектом (исходным значением),Objectпринуждать его к объекту,Reflectявляется прямой ошибкой.
  6. preventExtensions(target)иObject.preventExtensionsТочно так же запретите добавление новых свойств к объекту, разница такая же, как и предыдущая.
  7. apply(func, thisArg, args)иFunction.prototype.apply.call(fn, obj, args)Такой же.
  8. ownKeys(target)иObject.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))то же самое, возвращает массив, содержащий все собственные свойства (за исключением унаследованных свойств)

Другая часть состоит в том, чтобы превратить функцию исходного оператора в поведение функции.

  1. has(target, key)иinПодобно оператору, пусть операция суждения станет поведением функции.
  2. deleteProperty(target, key)иdeleteКак и оператор, он превращает операцию удаления в поведение функции, которая возвращает логическое значение, представляющее успех или неудачу.
  3. construct(target, argumentsList[, newTarget])иnewоператор,targetКонструктор, второй параметр — это массив параметров конструктора класса, а третий —new.targetзначение .
  4. get(target, key[, receiver])иobj[key]То же самое, третий параметр - когда брать значениеkeyразвернутыйgetterпри доступе к его функцииthisсвязывать какreceiverобъект.
  5. set(target, key, value[, receiver])настраиватьtargetобъектkeyсвойство равноvalue, третий параметр иsetТакой же. Возвращает логическое значение.
// 老写法
'assign' in Object // true

// 新写法
Reflect.has(Object, 'assign') // true

// 老写法
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1

// 新写法
Reflect.apply(Math.floor, undefined, [1.75]) // 1

// 旧写法
delete myObj.foo;

// 新写法
Reflect.deleteProperty(myObj, 'foo');

// new 的写法
const instance = new Greeting('张三');

// Reflect.construct 的写法
const instance = Reflect.construct(Greeting, ['张三']);

// 旧写法
Object.defineProperty(MyDate, 'now', {
  value: () => Date.now()
});

// 新写法
Reflect.defineProperty(MyDate, 'now', {
  value: () => Date.now()
});

Reflect.get(1, 'foo') // 报错
Reflect.get(false, 'foo') // 报错
Reflect.set(1, 'foo', {}) // 报错
Reflect.set(false, 'foo', {}) // 报错

// ---------------

var myObject = {
  foo: 1,
  bar: 2,
  get baz() {
    return this.foo + this.bar;
  },
};

var myReceiverObject = {
  foo: 4,
  bar: 4,
};

Reflect.get(myObject, 'baz', myReceiverObject) // 8

Proxy

Прокси-объекты используются для определения настраиваемого поведения для основных операций (таких как поиск свойств, присвоение, перечисление, вызовы функций и т. д.), которые эквивалентны внесению изменений на уровне языка, поэтому они относятся к своего рода «метапрограммированию». , то есть на язык программирования для программирования.

Прокси подобен прокси между целевыми объектами, любая операция над целью должна проходить через прокси. Прокси может фильтровать и переписывать операции внешнего мира.

Proxyэто конструктор, он принимает два параметраtargetиhandler,

targetОбернут ли целевой объект прокси-сервером (может быть объект любого типа, включая собственные массивы, функции или даже другой прокси-сервер).

handler— это объект, свойствами которого являются функции, определяющие поведение агента при выполнении операции.

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только один статический методrevocable(target, handler)Может использоваться для создания отзывного прокси-объекта. Эти два параметра такие же, как у конструктора. Он возвращает объект, содержащий сам сгенерированный прокси-объект и метод отмены для прокси-объекта.

Как только прокси-объект будет отозван, он станет почти полностью недоступным, и выполнение любой прокси-операции с ним вызовет исключение TypeError (обратите внимание, что существует 14 типов прокси-операций, и операции, отличные от этих 14, не вызовут исключение). исключение). После отзыва прокси-объект никогда не может быть восстановлен в исходное состояние, а целевой объект и объект процессора, связанный с ним, скорее всего, будут удалены сборщиком мусора. Многократный вызов метода отмены не будет иметь никакого эффекта, и, конечно же, не будет сообщено об ошибке.

var revocable = Proxy.revocable({}, {
  get(target, name) {
    return "[[" + name + "]]";
  }
});

// revocable -> {"proxy": proxy, "revoke": revoke}

var proxy = revocable.proxy;
proxy.foo;              // "[[foo]]"

revocable.revoke();     // 执行撤销方法

proxy.foo;              // TypeError
proxy.foo = 1           // 同样 TypeError
delete proxy.foo;       // 还是 TypeError
typeof proxy            // "object",因为 typeof 不属于可代理操作

handlerПараметр представляет собой объект прокси-функции, который поддерживает в общей сложности 13 функций перехвата. иReflectто же. Если операция не определена, операция будет переадресована целевому объекту.

const proxy = new Proxy({}, {
  get: function(target, property, receiver) {
    return receiver;
    // receiver 总是指向原始的读操作所在的那个对象,一般情况下就是 Proxy 实例。
  }
});
proxy.getReceiver === proxy // true

Если свойство не настраивается и недоступно для записи, прокси-сервер не может изменить свойство, в противном случае доступ к свойству через объект-прокси вызовет ошибку.

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

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

const proxy = new Proxy(target, handler);

proxy.foo
// TypeError: Invariant check failed

applyперехватчик метода调用,callиapplyработать.

var target = function () { return 'I am the target'; };
var handler = {
  apply: function () {
    return 'I am the proxy';
  }
};

var p = new Proxy(target, handler);

p()
// "I am the proxy"

definePropertyметод заблокированObject.definePropertyработать.

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

Обратите внимание, что если целевой объект не является расширяемым, defineProperty не может добавлять свойства, которых нет в целевом объекте, иначе будет сообщено об ошибке. Кроме того, если свойство целевого объекта недоступно для записи или настройки, метод defineProperty не должен изменять эти два параметра.

getPrototypeOfМетод в основном используется для перехвата и получения прототипа объекта, который будет выполнять следующие операции:

  1. Object.prototype.__proto__
  2. Object.prototype.isPrototypeOf()
  3. Object.getPrototypeOf()
  4. Reflect.getPrototypeOf()
  5. instanceof

ownKeysМетод используется для перехвата операции чтения собственных свойств объекта, которая будет перехватывать следующие операции:

  1. Object.getOwnPropertyNames()
  2. Object.getOwnPropertySymbols()
  3. Object.keys()
  4. for...in

С прокси вы можете легко проверять значения, передаваемые объекту.

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');
      }
    }

    // The default behavior to store the value
    obj[prop] = value;
  }
};

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

person.age = 100;

console.log(person.age); 
// 100

person.age = 'young'; 
// 抛出异常: Uncaught TypeError: The age is not an integer

person.age = 300; 
// 抛出异常: Uncaught RangeError: The age seems invalid

это указывает на

Хотя Proxy может проксировать доступ к целевому объекту, он не является прозрачным прокси для целевого объекта, то есть без какого-либо перехвата он не может гарантировать такое же поведение, как и целевой объект. Основная причина в том, что в случае с прокси-сервером внутреннийthisКлючевое слово будет указывать на прокси-сервер.

const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true
const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

proxy.getDate();
// TypeError: this is not a Date object.

// getDate 方法只能在Date对象实例上面拿到,
// 如果this不是Date对象实例就会报错。
// 这时,this绑定原始对象,就可以解决这个问题

const target = new Date('2015-01-01');
const handler = {
  get(target, prop) {
    if (prop === 'getDate') {
      return target.getDate.bind(target);
    }
    return Reflect.get(target, prop);
  }
};
const proxy = new Proxy(target, handler);

proxy.getDate() // 1