Присвоить значение выше прокси

218
12 октября 2017, 13:23

В следующем коде прокси с get-перехватчиком в прототип (__proto__) объекта. Соответственно, по схеме чтения свойств в js, до перехватчика очередь доходит только в том случае, если в непосредственно в объекте такого свойства нет. После присваивания свойство появляется в объекте и до прокси дело не доходит. Это так задумано.

var x = Object.create(new Proxy({}, { 
  get(obj, key) { 
    if (typeof key !== 'symbol') { 
      console.log('Reading a nonexisting property: ' + key); 
    } 
  } 
})); 
 
var temp; 
console.log(1, Object.prototype.hasOwnProperty.call(x, 'a')); 
 
temp = x.a; 
console.log(2, Object.prototype.hasOwnProperty.call(x, 'a'), temp); 
 
temp = x.a; 
console.log(3, Object.prototype.hasOwnProperty.call(x, 'a'), temp); 
 
x.a = 12; 
console.log(4, Object.prototype.hasOwnProperty.call(x, 'a')); 
 
temp = x.a; 
console.log(5, Object.prototype.hasOwnProperty.call(x, 'a'), temp);
.as-console-wrapper.as-console-wrapper { max-height: 100vh }

Теперь я в этот код добавляю перехватчик set:

var x = Object.create(new Proxy({}, { 
  get(obj, key) { 
    if (typeof key !== 'symbol') { 
      console.log('Reading a nonexisting property: ' + key); 
    } 
  }, 
  set(obj, key, val, receiver) { 
    console.log('Assigning a property: ' + key); 
    Reflect.set(obj, key, val); // Inside of proxy, not outside 
    //Reflect.set(receiver, key, val); // Infinite recursion 
    return true; 
  } 
})); 
 
var temp; 
console.log(1, Object.prototype.hasOwnProperty.call(x, 'a')); 
 
temp = x.a; 
console.log(2, Object.prototype.hasOwnProperty.call(x, 'a'), temp); 
 
temp = x.a; 
console.log(3, Object.prototype.hasOwnProperty.call(x, 'a'), temp); 
 
x.a = 12; 
console.log(4, Object.prototype.hasOwnProperty.call(x, 'a')); 
 
temp = x.a; 
console.log(5, Object.prototype.hasOwnProperty.call(x, 'a'), temp);
.as-console-wrapper.as-console-wrapper { max-height: 100vh }

Проблема в том, что не получается записать свойство в сам объект - свойство записывается либо в тот объект, который находится внутри прокси, либо всё уходит в рекурсию метода set. Как это можно исправить? Т. е. результат должен быть такой же, как в предыдущем сниппете, только с появлением строки Assigning a property: a.

PS: Этот вопрос на английском.

Answer 1

Додумался до такого способа, но выглядит каким-то костылём:

function directSet(obj, key, val) { 
  var proto = Object.getPrototypeOf(obj); 
  Object.setPrototypeOf(obj, null); 
  var res = Reflect.set(obj, key, val); 
  Object.setPrototypeOf(obj, proto); 
  return res; 
} 
 
var x = Object.create(new Proxy({}, { 
  get(obj, key) { 
    if (typeof key !== 'symbol') { 
      console.log('Reading a nonexisting property: ' + key); 
    } 
  }, 
  set(obj, key, val, receiver) { 
    console.log('Assigning a property: ' + key); 
    console.log(directSet(receiver, key, val)); 
    return true; 
  } 
})); 
 
var temp; 
console.log(1, Object.prototype.hasOwnProperty.call(x, 'a')); 
 
temp = x.a; 
console.log(2, Object.prototype.hasOwnProperty.call(x, 'a'), temp); 
 
temp = x.a; 
console.log(3, Object.prototype.hasOwnProperty.call(x, 'a'), temp); 
 
x.a = 12; 
console.log(4, Object.prototype.hasOwnProperty.call(x, 'a')); 
 
temp = x.a; 
console.log(5, Object.prototype.hasOwnProperty.call(x, 'a'), temp);
.as-console-wrapper.as-console-wrapper { max-height: 100vh }

Answer 2

Перевод ответа от @Bergi

Для создания свойства надо воспользоваться Object.defineProperty (или Reflect.defineProperty). Просто попытка установить свойство присваиванием или Reflect.set будет вызывать проход по цепочке прототипов и попадать в перехватчик set, что приведёт к бесконечной рекурсии.

new Proxy({}, {
  get(target, key) {
    if (typeof key !== 'symbol') {
      console.log('Reading a nonexisting property: ' + key);
    }
  },
  set(target, key, val, receiver) {
    console.log('Assigning a property: ' + key);
    return Reflect.defineProperty(receiver, key, {
      value: val,
      writable: true,
      enumerable: true,
      configurable: true
    });
  }
});

Полный пример:

var x = Object.create(new Proxy({}, { 
  get(obj, key) { 
    if (typeof key !== 'symbol') { 
      console.log('Reading a nonexisting property: ' + key); 
    } 
  }, 
  set(obj, key, val, receiver) { 
    console.log('Assigning a nonexisting property: ' + key); 
 
    return Reflect.defineProperty(receiver, key, { 
      value: val, 
      writable: true, 
      enumerable: true, 
      configurable: true 
    }); 
  } 
})); 
 
var temp; 
console.log(1, Object.prototype.hasOwnProperty.call(x, 'a')); 
 
temp = x.a; // get trap 
console.log(2, Object.prototype.hasOwnProperty.call(x, 'a'), temp); 
 
temp = x.a; // get trap 
console.log(3, Object.prototype.hasOwnProperty.call(x, 'a'), temp); 
 
x.a = 12;   // set trap creates a property and sets it 
console.log(4, Object.prototype.hasOwnProperty.call(x, 'a')); 
 
temp = x.a; // direct read - no traps 
console.log(5, Object.prototype.hasOwnProperty.call(x, 'a'), temp); 
 
x.a = 42;   // direct write - no traps 
console.log(6, Object.prototype.hasOwnProperty.call(x, 'a')); 
 
temp = x.a; // direct read - no traps 
console.log(7, Object.prototype.hasOwnProperty.call(x, 'a'), temp);
.as-console-wrapper.as-console-wrapper { max-height: 100vh }

READ ALSO
Анимация canvas

Анимация canvas

Как сделать анимацию в canvas, а именно чтобы на экране появлялись квадраты в разных места, разного цветаИнтересует именно что надо сделать,...

180
Как заменить prev/next из owl carousel на стрелки из font-awesome?

Как заменить prev/next из owl carousel на стрелки из font-awesome?

Собственно вопросЕсли включить навигацию там кнопки prev/next

433
Изменение свойств списка Vue.js

Изменение свойств списка Vue.js

Проблема в рендере списков, vue не отображает изменения свойств списка, только изменения в структуре самого спискаПри нажатии кнопки change должен...

200
Вывести изображения из XML

Вывести изображения из XML

Есть XML файл https://d3aqoihi2n8ty8cloudfront

266