Почему геттеры / сеттеры - плохая идея в JavaScript
Автор: admin
Дата: 13.09.2020 02:06
Может показаться, что геттеры и сеттеры экономят время и упрощают ваш код, но на самом деле они вызывают скрытые ошибки, которые не очевидны с первого взгляда.
Как работают геттеры и сеттеры Сначала краткое описание того, что это такое: Иногда желательно разрешить доступ к свойству, которое возвращает динамически вычисляемое значение, или вы можете захотеть отразить статус внутренней переменной, не требуя использования явных вызовов методов.
Чтобы проиллюстрировать, как они работают, давайте посмотрим на объект person, который имеет два свойства: firstName и lastName, а также одно вычисленное значение fullName.
var obj = {
firstName: "Maks",
lastName: "Nemisj"
}
Object.defineProperty(person, 'fullName', {
get: function () {
return this.firstName + ' ' + this.lastName;
}
});
Вычисленное значение fullName вернет конкатенацию как firstName, так и lastName.
Чтобы получить вычисленное значение fullName, больше нет необходимости в ужасных скобках, таких как person.fullName(), но можно использовать простой var fullName = person.fullName. То же самое относится и к сеттерам, вы можете установить значение с помощью функции:
Object.defineProperty(person, 'fullName', {
set: function(value) {
var names = value.split(' ');
this.firstName = names[0];
this.lastName = names[1];
}
});
Использование setter также просто: person.fullName = 'Boris Gorbachev' Это вызовет функцию, определенную выше, и разделит Бориса Горбачева на firstName и lastName.
До геттеров и сеттеров для получения значения мы будем использовать что-то вроде getFullName(), а для установки значения будет использоваться person.setFullName('Maks Nemisj'). А что произойдет, если имя функции написано с ошибкой, а person.getFullName() записано как person.getFulName() - JavaScript выдаст ошибку:
person.getFulName();// TypeError: undefined is not a function
Эта ошибка возникает в нужном месте и в нужный момент. Доступ к несуществующим функциям объекта вызовет ошибку - это хорошо. Теперь посмотрим, что происходит, когда сеттер используется с неправильным именем?
person.fulName = 'Boris Gorbachev';
Объекты являются расширяемыми и могут иметь динамически назначаемые ключи и значения, поэтому во время выполнения не возникает никаких ошибок. Такое поведение означает, что ошибки могут быть видны где-то в пользовательском интерфейсе или, может быть, когда какая-то операция выполняется с неправильным значением, но не в тот момент, когда произошла настоящая опечатка.
Частично эту проблему можно решить с помощью API уплотнения. Всякий раз, когда объект запечатывается, он не может быть изменен, что означает, что fulName попытается назначить новый ключ объекту person, и это не удастся. По какой-то причине, когда я тестировал это в node.js v4.0, он не работал так, как я ожидал. Так что я сомневаюсь в этом решении. Что еще более расстраивает, так это то, что для геттеров нет никакого решения. Как я уже упоминал, объекты являются расширяемыми и отказоустойчивыми, что означает, что доступ к несуществующему ключу вообще не приведет к какой-либо ошибке.
В настоящее время классы не очень приветствуются в некоторых сообществах JavaScript. Люди спорят о необходимости их использования на функциональном языке, основанном на прототипах, таком как JavaScript. Однако факт в том, что классы находятся в спецификации ECMAScript 2015 (ES6) и останутся там некоторое время. Для меня классы - это способ указать четко определенные API-интерфейсы между внешним миром (потребителями) классов и внутренними компонентами приложения. Это абстракция, которая излагает правила черным по белому и предполагает, что эти правила не изменятся в ближайшее время. Пора улучшить объект person и сделать из него настоящий класс (настолько реальным, насколько класс может быть в JavaScript). Person определяет интерфейс для получения и установки fullName.
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return this.firstName + ' ' + this.lastName;
}
setFullName(value) {
var names = value.split(' ');
this.firstName = names[0];
this.lastName = names[1];
}
}
Классы определяют строгое описание интерфейса, но геттеры и сеттеры делают его менее строгим, чем должно быть. Мы уже привыкли к набухшим ошибкам при опечатках в ключах при работе с объектными литералами и с JSON. По крайней мере, я надеялся, что классы будут более строгими и обеспечат лучшую обратную связь с разработчиками в этом смысле. Хотя эта ситуация ничем не отличается при определении геттеров и сеттеров в классе. Это не помешает другим делать опечатки без обратной связи.
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getfullName() {
return this.firstName + ' ' + this.lastName;
}
setfullName(value) {
var names = value.split(' ');
this.firstName = names[0];
this.lastName = names[1];
}
}
Выполнение с опечаткой не приведет к ошибке:
var person = new Person('Maks', 'Nemisj');
console.log(person.fulName);
То же нестрогое, не подробное, не отслеживаемое поведение, ведущее к возможным ошибкам. После того, как я это обнаружил, у меня возник вопрос: можно ли что-нибудь сделать, чтобы сделать классы более строгими при использовании геттеров и сеттеров? Выяснил: конечно есть, но разве это хуже? Добавить дополнительный уровень сложности в код, чтобы использовать меньше фигурных скобок? Также возможно не использовать геттеры и сеттеры для определения API, и это решит проблему. Если вы не заядлый разработчик и не хотите продолжать, есть другое решение, описанное ниже.
Прокси на помощь? Помимо сеттеров и получателей, ECMAScript 2015 (ES6) также поставляется с прокси-объектом. Прокси-серверы помогают определить метод делегатора, который можно использовать для выполнения различных действий до того, как будет осуществлен реальный доступ к ключу. Собственно, это похоже на динамические геттеры / сеттеры. Прокси-объекты могут использоваться для перехвата любого доступа к экземпляру класса и выдачи ошибки, если предопределенный метод получения или установки не был найден в этом классе. Для этого необходимо выполнить два действия: Создайте список геттеров и сеттеров на основе прототипа Person. Создайте объект Proxy, который будет проверять эти списки. Давайте реализуем это. Во-первых, чтобы узнать, какие геттеры и сеттеры доступны в классе Person, это можно использовать getOwnPropertyNames и getOwnPropertyDescriptor: 2
var names = Object.getOwnPropertyNames(Person.prototype);
var getters = names.filter((name) => {
var result = Object.getOwnPropertyDescriptor(Person.prototype, name);
return !!result.get;
});
var setters = names.filter((name) => {
var result = Object.getOwnPropertyDescriptor(Person.prototype, name);
return !!result.set;
});
После этого создайте объект Proxy, который будет протестирован по этим спискам:
var handler = {
get(target, name) {
if (getters.indexOf(name) != -1) {
return target[name];
}
throw new Error('Getter "' + name + '" not found in "Person"');
},
set(target, name) {
if (setters.indexOf(name) != -1) {
return target[name];
}
throw new Error('Setter "' + name + '" not found in "Person"');
}
};
person = new Proxy(person, handler);
Теперь всякий раз, когда вы будете пытаться получить доступ к person.fulName, будет отображаться сообщение Ошибка: Getter «fulName» не найден в «Person». Надеюсь, эта статья помогла вам понять всю картину о геттерах и сеттерах, а также об опасности, которую они могут привнести в код.