Широко используемые, но игнорируемые методы ES6 (четвертый пункт Proxy и Reflect)

внешний интерфейс JavaScript

напишите в начале

  • Фестиваль лодок-драконов прошел, продолжайте обновлять.
  • ES6Распространенные, но игнорируемые методыСерия статей, в которых рассматриваются некоторые методы, навыки использования и некоторые сценарии применения, которые, по мнению автора, могут быть использованы для ежедневной разработки.

Статьи по Теме

Proxy

  • ES6-Proxy
  • ProxyИзменение поведения определенных операций по умолчанию эквивалентно внесению изменений на уровне языка, поэтому это своего рода «метапрограммирование» (meta programming), то есть программирование на языке программирования. Можно понять, что уровень «перехвата» устанавливается перед целевым объектом, и внешний доступ к объекту должен сначала пройти этот уровень перехвата, поэтому предоставляется механизм для фильтрации и перезаписи внешнего доступа.

Как использовать

  1. БудуProxyобъект, установленный наobject.proxyсвойства, так чтоobjectпозвонил на объект.
const object = { proxy: new Proxy(target, handler) };
  1. использоватьProxyобъект
const proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 'detanx';
  }
});

obj.name // 'detanx'
  • Одна и та же функция перехватчика может быть настроена на перехват нескольких операций.

Перехват операции

  • ProxyСписок поддерживаемых операций перехвата, всего13Добрый.
    1. get(target, propKey, receiver): перехватывать чтение свойств объекта, таких какproxy.fooа такжеproxy['foo'].
    2. set(target, propKey, value, receiver): перехватить настройку свойств объекта, таких какproxy.foo = vилиproxy['foo'] = v, который возвращает логическое значение.
    3. has(target, propKey): перехватpropKey in proxyОперация, возвращающая логическое значение.
    4. deleteProperty(target, propKey): перехватdelete proxy[propKey]Операция, возвращающая логическое значение.
    5. ownKeys(target): перехватObject.getOwnPropertyNames(proxy),Object.getOwnPropertySymbols(proxy),Object.keys(proxy),for...inЦикл, возврат массива. Этот метод возвращает имена свойств всех собственных свойств целевого объекта, иObject.keys()Возвращаемый результат включает только проходимые свойства самого целевого объекта.
    6. getOwnPropertyDescriptor(target, propKey): перехватObject.getOwnPropertyDescriptor(proxy, propKey), который возвращает объект описания свойства.
    7. defineProperty(target, propKey, propDesc): перехватObject.defineProperty(proxy, propKey, propDesc),Object.defineProperties(proxy, propDescs), который возвращает логическое значение.
    8. preventExtensions(target): перехватObject.preventExtensions(proxy), который возвращает логическое значение.
    9. getPrototypeOf(target): перехватObject.getPrototypeOf(proxy), который возвращает объект.
    10. isExtensible(target): перехватObject.isExtensible(proxy), который возвращает логическое значение.
    11. setPrototypeOf(target, proto): перехватObject.setPrototypeOf(proxy, proto), который возвращает логическое значение. Если целевой объект является функцией, есть две дополнительные операции, которые можно перехватить.
    12. apply(target, object, args): перехватProxyЭкземпляры — это операции, называемые функциями, напримерproxy(...args),proxy.call(object, ...args),proxy.apply(...).
    13. construct(target, args): перехватProxyЭкземпляры — это операции, называемые конструкторами, напримерnew proxy(...args).
get()
  • Перехватывать операцию чтения атрибута, которая может принимать три параметра: целевой объект, имя атрибута иProxyсам экземпляр(Строго говоря, объект, на который действует действие), где последний параметр является необязательным.
const proxy = new Proxy({}, {
  get(target, propertyKey [, receiver]) {
    return target[propertyKey];
  }
});
  • Если свойство не настраивается (configurable) и недоступна для записи (writable),ноProxyЭто свойство нельзя изменить, иначе передатьProxyПри обращении объекта к этому свойству будет сообщено об ошибке.
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
set()
  • setЭтот метод используется для перехвата операции присваивания атрибута и может принимать четыре параметра: целевой объект, имя атрибута, значение атрибута иProxyСам экземпляр, где последний параметр является необязательным.
let prxyo = new Proxy({}, {
  set(target, propertyKey, value [, receiver]) {
    return target[propertyKey];
  }
});
  • Если свойство самого целевого объекта не настраивается (configurable) и недоступна для записи (writable),Так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"
  • В строгом режиме,setпрокси если нет возвратаtrue, будет сообщено об ошибке.
'use strict';
const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = receiver;
    // 无论有没有下面这一行,都会报错
    return false;
  }
};
const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
// TypeError: 'set' on proxy: trap returned falsish for property 'foo'
apply()
  • applyметод перехвата вызовов функций,callа такжеapplyработать.
  • applyМетод может принимать три параметра, а именно целевой объект и объект контекста целевого объекта (this) и массив параметров целевого объекта.
const proxy = new Proxy( {}, {
  apply (target, ctx, args) {
    return Reflect.apply(...arguments);
  }
});
  • пример перехвата
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
  • позвонить напрямуюReflect.applyметод также будет заблокирован.
Reflect.apply(proxy, null, [9, 10]) // 38
has()
  • hasметод перехватаHasPropertyработать(Примечание неHasOwnProperty,Прямо сейчасhasМетод не определяет, является ли свойство собственным свойством объекта или унаследованным свойством.), то есть при суждении о наличии у объекта определенного свойства этот метод сработает. Типичная операция этоinоператор. Он может принимать два параметра: целевой объект и запрашиваемое имя свойства.
  • используется для скрытия определенных свойств
// 隐藏_开头的属性
var handler = {
  has (target, key) {
    if (key[0] === '_') {
      return false;
    }
    return key in target;
  }
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false
  • hasПара перехватаfor...inПетля не работает.
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()
  • constructметод перехватаnewКоманда, ниже приведен способ записи объекта перехвата. Он может принимать три параметра: целевой объект, объект параметра конструктора и при создании объекта экземпляра,newКонструктор командного действия.constructВозвращаемый метод должен быть объектом, иначе будет сообщено об ошибке.
var handler = {
  construct (target, args, newTarget) {
    return new target(...args);
  }
};

var p = new Proxy(function () {}, {
  construct: function(target, args) {
    console.log('called: ' + args.join(', '));
    return { value: args[0] * 10 };
  }
});

(new p(1)).value
// "called: 1"
// 10

var p = new Proxy(function() {}, {
  construct: function(target, argumentsList) {
    return 1;
  }
});

new p() // 报错
// Uncaught TypeError: 'construct' on proxy: trap returned non-object ('1')
deleteProperty()
  • deletePropertyметод перехватаdeleteоперации, если этот метод выдает ошибку или возвращаетfalse, текущее свойство не может бытьdeleteкоманда удалить.Сам целевой объект не настраивается (configurable) свойства, не может бытьdeletePropertyМетод удаляется, иначе сообщается об ошибке.
var handler = {
  deleteProperty (target, key) {
    delete target[key];
    return true;
  }
};

var target = { prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
defineProperty()
  • defineProperty()метод заблокированObject.defineProperty()работать.Если целевой объект не является расширяемым (non-extensible),ноdefineProperty()Вы не можете добавлять свойства, которых нет в целевом объекте, иначе будет сообщено об ошибке. Кроме того, если свойство целевого объекта недоступно для записи (writable) или не настраивается (configurable),ноdefineProperty()Методы не должны изменять эти две настройки.
var handler = {
  defineProperty (target, key, descriptor) {
    return false;
  }
};
var target = {};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar' // 不会生效
getOwnPropertyDescriptor()
  • перехватыватьObject.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()
  • Метод в основном используется для перехвата и получения прототипа объекта, а возвращаемое значение должно быть объектом илиnull, иначе будет сообщено об ошибке.
  • Если целевой объект не является расширяемым (non-extensible),getPrototypeOf()Метод должен возвращать объект-прототип целевого объекта.
  • Заблокируйте следующие операции:
    1. Object.prototype.__proto__
    2. Object.prototype.isPrototypeOf()
    3. Object.getPrototypeOf()
    4. Reflect.getPrototypeOf()
    5. instanceof
var proto = {};
var p = new Proxy({}, {
  getPrototypeOf(target) {
    return proto;
  }
});
Object.getPrototypeOf(p) === proto // true
isExtensible(),preventExtensions()а такжеsetPrototypeOf()
  1. Та же точка
    • Эти три метода могут возвращать только логические значения, в противном случае они будут автоматически преобразованы в логические значения.
  2. разница
    • isExtensible()Метод имеет строгое ограничение: его возвращаемое значение должно соответствовать значению целевого объекта.isExtensibleСвойства остаются прежними, в противном случае ошибка будет брошена.
    var p = new Proxy({}, {
      isExtensible: function(target) {
        console.log("called");
        return true;
      }
    });
    
    Object.isExtensible(p)
    // "called"
    // true
    
    // 报错
    var p = new Proxy({}, {
      isExtensible: function(target) {
        return false;
      }
    });
    
    Object.isExtensible(p)
    // Uncaught TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true')
    
    • preventExtensions()методы имеют ограничение, только если целевой объект не является расширяемым (т.е.Object.isExtensible(proxy)дляfalse),proxy.preventExtensionsвозвращатьtrue, иначе будет сообщено об ошибке. Чтобы предотвратить эту проблему, обычно необходимоproxy.preventExtensions()метод, вызов один разObject.preventExtensions().
    var proxy = new Proxy({}, {
      preventExtensions: function(target) {
        return true;
      }
    });
    
    Object.preventExtensions(proxy)
    // Uncaught TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible
    
    // 纠正后
    var proxy = new Proxy({}, {
      preventExtensions: function(target) {
        console.log('called');
        Object.preventExtensions(target);
        return true;
      }
    });
    
    Object.preventExtensions(proxy)
    // "called"
    // Proxy {}
    
    • setPrototypeOf()Если целевой объект не является расширяемым (non-extensible),setPrototypeOf()Метод не должен изменять прототип целевого объекта.
    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
    
ownKeys()
  • ownKeys()Метод используется для перехвата операции чтения собственных свойств объекта. В частности, перехватываются следующие действия.
    1. Object.getOwnPropertyNames()
    2. Object.getOwnPropertySymbols()
    3. Object.keys()
    4. for...inцикл
  • использоватьObject.keys()метод, есть три типа свойств, которые будутownKeys()Метод автоматически фильтруется и не возвращается. Атрибут, который не существует в целевом объекте, имя атрибутаSymbolзначение, не проходное (enumerable) характеристики.
  • ограничения использования (важно)
    1. ownKeys()Элемент массива, возвращаемый методом, может быть только строкой илиSymbolстоимость.Если есть другие типы значений или возвращаемое значение вообще не является массивом, будет сообщено об ошибке.
    var obj = {};
    
    var p = new Proxy(obj, {
      ownKeys: function(target) {
        return [123, true, undefined, null, {}, []];
      }
    });
    
    Object.getOwnPropertyNames(p)
    // Uncaught TypeError: 123 is not a valid property name
    
    1. Если сам целевой объект содержит ненастраиваемое свойство, это свойство должно бытьownKeys()Метод возвращает значение, иначе сообщается об ошибке.
    var obj = {};
    Object.defineProperty(obj, 'a', {
      configurable: false,
      enumerable: true,
      value: 10 }
    );
    
    var p = new Proxy(obj, {
      ownKeys: function(target) {
        return ['b'];
      }
    });
    
    Object.getOwnPropertyNames(p)
    // Uncaught TypeError: 'ownKeys' on proxy: trap result did not include 'a'
    
    1. Целевой объект не является расширяемым (non-extensible), когдаownKeys()Массив, возвращаемый методом, должен содержать все атрибуты исходного объекта и не может содержать избыточные атрибуты, иначе будет сообщено об ошибке.
    var obj = {
      a: 1
    };
    
    Object.preventExtensions(obj);
    
    var p = new Proxy(obj, {
      ownKeys: function(target) {
        return ['a', 'b'];
      }
    });
    
    Object.getOwnPropertyNames(p)
    // Uncaught TypeError: 'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible
    

Proxy.revocable()

  • Proxy.revocable()метод возвращает объект,proxyсобственностьProxyпример,revokeСвойство является функцией и может быть отмененоProxyпример.
let target = {};
let handler = {};

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

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

revoke();
proxy.foo // TypeError: Revoked
  • при исполненииrevokeПосле мероприятия посетитеProxyслучае будет выдана ошибка.
  • Proxy.revocable()Один из сценариев использования заключается в том, что целевой объект не допускает прямого доступа и должен быть доступен через прокси.После окончания доступа право прокси отменяется, и дальнейший доступ не разрешается.

thisвопрос

  • когдаproxyПосле прокси,thisнаправление изменится.
const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true
  • Пример, так какthisизменения ориентации, приводящие кProxyНевозможно проксировать целевой объект.
const _name = new WeakMap();

class Person {
  constructor(name) {
    _name.set(this, name);
  }
  get name() {
    return _name.get(this);
  }
}

const jane = new Person('Jane');
jane.name // 'Jane'

const proxy = new Proxy(jane, {});
proxy.name // undefined

=> // 增加get拦截
const proxy = new Proxy(jane, {
  get(target, prop) {
    console.log('prop', prop)
    if (prop === 'name') {
      return target.name
    }
    return Reflect.get(target, prop);
  }
});
proxy.name // Jane
  • Внутренние свойства некоторых нативных объектов могут быть переданы только через правильныйthisчтобы получить это, такProxyТакже нет возможности проксировать свойства этих нативных объектов. В настоящее время,thisПривязка исходного объекта решает эту проблему.
const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

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

// 在get中绑定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() // 

vue3 заменить defineProperty на прокси

  • Object.defineProperty
    1. Object.definePropertyИзменение нижнего индекса массива не может быть отслежено, в результате чего значение массива устанавливается непосредственно через нижний индекс массива, и не может реагировать в режиме реального времени. После внутренней обработки Vue вы можете использовать следующие методы для мониторинга массива.push(),pop(),shift(),unshift(),splice(),sort(),reverse(), потому что только для восьми вышеуказанных методовhackобработки, поэтому атрибуты других массивов также невозможно обнаружить, и они все еще имеют определенные ограничения.
    2. Object.definePropertyТолько свойства объектов могут быть захвачены, поэтому нам нужно перебирать каждое свойство каждого объекта.
  • Proxy
    1. Вы можете захватить весь объект и вернуть новый объект.
    2. имеют13операция по захвату.

Reflect

  1. БудуObjectМетоды объекта, которые явно являются внутренними для языка (например,Object.defineProperty), вставитьReflectна объекте.
  2. изменить некоторыеObjectВозвращаемый результат метода делает его более разумным. Например,Object.defineProperty(obj, name, desc)Ошибка выдается, когда свойство не может быть определено, иReflect.defineProperty(obj, name, desc)вернусьfalse.
// 老写法
try {
  Object.defineProperty(target, property, attributes);
  // success
} catch (e) {
  // failure
}

// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}
  1. ПозволятьObjectОперации становятся функциональным поведением. немногоObjectОперации обязательны, напримерname in objа такжеdelete obj[name],а такжеReflect.has(obj, name)а такжеReflect.deleteProperty(obj, name)Сделайте их функциональным поведением.
// 老写法
'assign' in Object // true

// 新写法
Reflect.has(Object, 'assign') // true
  1. Reflectметоды объекта иProxyМетоды объекта соответствуют один к одному, пока онProxyметоды объекта, вы можетеReflectНайдите соответствующий метод на объекте.КаждыйProxyОперация перехвата объекта (get、delete、has、...), внутренне вызывает соответствующийReflectметод.