Skip to main content

Объекты II

get и set

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

const person = Object.create(
{
// object protorype
// сюда можно добавлять кастомные методы
showSomething() {
console.log('Hello, World!');
}
},
{
name: {
value: 'Leonel Messi',

// дескрипторы
enumerable: true, // по умолчанию false (не будет отображаться в цикле for)
writable: true, // чтобы можно было перезаписывать
configurable: true, // чтобы работал delete
},
birthYear: {
value: 1987,
enumerable: true,
writable: true,
},
age: {
// вычисляемое поле
// вычисление значения данного поля
get() {
return new Date().getFullYear() - this.birthYear;
},

// Будет отрабатывать данный код, когда будет присваиваться новое значение этому полю
set(value) {
alert(value);
},
}
}
);
person.age; // 36
person.age = 35 ; // --> alert(35)


for (let key in person) {
console.log(`${key}:`, person[key]);

if (person.hasOwnProperty) {
// если не нужно пробегаться по прототипу
}
}

Proxy и Reflect

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

Объект Proxy «оборачивается» вокруг другого объекта и может перехватывать (и, при желании, самостоятельно обрабатывать) разные действия с ним, например чтение/запись свойств и другие. Далее мы будем называть такие объекты «прокси».

Прокси используются во многих библиотеках и некоторых браузерных фреймворках.

let proxy = new Proxy(target, handler);
  • target – это объект, для которого нужно сделать прокси, может быть чем угодно, включая функции.
  • handler – конфигурация прокси: объект с «ловушками» («traps»): методами, которые перехватывают разные операции, например, ловушка get – для чтения свойства из target, ловушка set – для записи свойства в target и так далее.

Проксирование объектов

// простой объект
const person = {
name: 'Vladilen',
age: 25,
job: 'Fullstack',
}

// прокси-объект принимает исходный объект
const objProxy = new Proxy(person, {
// обработчик получения данных
get(target, prop) {
if (prop === 'age') {
return target['age'] + 1;
} else {
return target[prop];
}
},

// обработчик присвоения данных
set(target, prop, value) {
if (prop in target) {
target[prop] = value;
} else {
throw new Error('No property');
}
},

// проверяет наличие полей в объекте
has(target, prop) {
return ['name', 'age', 'job'].includes(prop);
},

deleteProperty(target, prop) {
console.log('deleting ...', prop);
}
});

person.age; // 25
objProxy.age; // 26
objProxy.name = "New name"; // поменяет исходный объект

// метод has
'name' in objProxy; // true
'contry' in objProxy; // false

// метод deleteProperty - удаляет поле из Proxy и исходного объекта
delete objProxy.age; // deleting ... age

Проксирование функций

const log = (text) => `Log: ${text}`;

const fnProxy = new Proxy(log, {
apply(target, thisArg, args) {
console.log('Calling fn...');

// работа вызова по умолчанию - можно преобразовывать return
// return target.apply(thisArg, args);

// как вариант
return target.apply(thisArg, args).toUpperCase();
}
});

log('abc'); // 'Log: abc'
fnProxy('abc'); // 'LOG: ABC'

Проксирование функций-экземпляров-класов

Допустим есть какой-либо класс

class Person {
constructor() {
this.name = name;
this.age = age;
}
}

// проксируем экземпляр этого класса
const personProxy = new Proxy(Person, {
// надстройка над constructor()
construct(target, args) {
}
});

Пример Проксирование функций - дефолтные параметры

const withDefaultParams = (target, defaultValue = 0) => {
return new Proxy(target, {
get: (obj, prop) => (prop in obj ? obj[prop] : defaultValue)
})
}

const position = withDefaultParams({
x: 10,
y: 20,
}, 0);

// Если поля нет в параметре, то будет возвращаться дефолтное значение
position.x; // 10
position.y; // 20
position.z; // 0
position.i; // 0

Пример Проксирование функций - свойства с префиксом

const withHiddenProps = (target, prefix = '_') => {
return new Proxy(target, {
has: (obj, prop) => (prop in obj) && (!prop.startWith(prefix)),
ownKeys: obj => Reflect.ownKeys(obj).filter((p => !prop.startWith(prefix))), // получение массива изключей
get: (obj, prop, reciever) => (prop in reciever ? obj[prop] : void 0)
, });
}

Пример приватных полей

const props = {
name: 'Abby',
chat: 'the last of us. Part II',
getChat() {
this._privateMethod();
},
_privateMethod() {
console.log(this._privateProp);
},
__privateMethodToo() {},
_privateProp: 'Нельзя получить просто так',
};

const checkPrivateProp = prop => prop.startsWith('_');

const proxyProps = new Proxy(props, {
get(target, prop) {
if (checkPrivateProp(prop)) {
throw new Error("Нет прав");
} else {
const value = target[prop];
return (typeof value === 'function') ? value.bind(target) : value;
}
},
set(target, prop, val) {
if (checkPrivateProp(prop)) {
throw new Error("Нет прав");
} else {
target[prop] = val;
return true;
}
},
deleteProperty(target, prop) {
if (checkPrivateProp(prop)) {
throw new Error("Нет прав");
} else {
delete target[prop];
return true;
}
}
});

proxyProps.getChat();
delete proxyProps.chat;

proxyProps.newProp = 2;
console.log(proxyProps.newProp);

try {
proxyProps._newPrivateProp = 'Super game';
} catch (error) {
console.log(error);
}

try {
delete proxyProps._privateProp;
} catch (error) {
console.log(error); // Error: Нет прав
}

/*
* Вывод в консоль следующий:
Нельзя получить просто так
2
Error: Нет прав
Error: Нет прав
*/