Автор оригинала: Bill Sourour.
Я работаю с JavaScript и выкл с конца девяностых годов. Я не очень понравился это сначала, но после введения ES2015 (AKA ES6) я начал ценить JavaScript как выдающийся язык динамического программирования с огромной, выразительной мощностью.
Со временем я принял несколько шаблонов кодирования, которые привели к чищему, более ослежденным, более выразительным кодом. Теперь я делюсь с вами этими узорами.
Я написал о первом шаблоне – «Роро» – в статье ниже. Не беспокойтесь, если вы этого не читали, вы можете прочитать их в любом порядке.
Элегантные узоры в современном JavaScript: Roro Я написал свои первые несколько строк JavaScript недолго после того, как язык был изобретен. Если вы сказали мне в то время, когда я … Medium.freecodeCamp.org.
Сегодня я хотел бы представить вас с рисунком «Ice Factory».
Ледяная фабрика просто функция, которая создает и возвращает замороженный объект . Мы распаковываем это заявление в данный момент, но сначала рассмотрим, почему этот шаблон такой мощной.
Занятия JavaScript не так классны
Это часто имеет смысл группировать связанные функции в один объект. Например, в приложении электронной коммерции у нас может быть Корзина Объект, который раскрывает AddProduct Функция и A RemoveProduct функция. Затем мы могли вызвать эти функции с Cart.addproduct () и Cart.ReMoveProduct () Отказ
Если вы пришли из ориентированного на класс, ориентирован на объект, язык программирования, как Java или C #, это, вероятно, чувствует себя довольно естественным.
Если вы новичок в программировании – теперь, когда вы видели, как Cart.addproduct () Отказ Я подозреваю, что идея группировки совместных функций под одним объектом выглядит довольно хорошо.
Так как же мы создадим это приятно мало Корзина объект? Ваш первый инстинкт с современным JavaScript может быть использован Класс Отказ Что-то вроде:
// ShoppingCart.js
export default class ShoppingCart { constructor({db}) { this.db = db } addProduct (product) { this.db.push(product) } empty () { this.db = [] } get products () { return Object .freeze([...this.db]) } removeProduct (id) { // remove a product }// other methods
}
// someOtherModule.js
const db = [] const cart = new ShoppingCart({db})cart.addProduct({ name: 'foo', price: 9.99})К сожалению – даже если это выглядит красиво – классы в JavaScript ведут себя совершенно иначе от того, что вы могли ожидать.
Занятия JavaScript укусит вас, если вы не осторожны.
Например, объекты, созданные с помощью Новый Ключевое слово изменяется. Итак, вы можете на самом деле повторно назначить метод:
const db = []const cart = new ShoppingCart({db})cart.addProduct = () => 'nope!' // No Error on the line above!
cart.addProduct({ name: 'foo', price: 9.99}) // output: "nope!" FTW?Еще хуже, объекты, созданные с помощью Новый Ключевое слово наследует Прототип из Класс это было использовано для их создания. Итак, изменения в классе ‘ Прототип оказывать воздействие Все Объекты, созданные из этого Класс – Даже если изменение сделано после Объект был создан!
Посмотри на это:
const cart = new ShoppingCart({db: []})const other = new ShoppingCart({db: []})ShoppingCart.prototype .addProduct = () => 'nope!'// No Error on the line above!
cart.addProduct({ name: 'foo', price: 9.99}) // output: "nope!"other.addProduct({ name: 'bar', price: 8.88}) // output: "nope!"Тогда есть тот факт, что это В JavaScript динамически связан. Итак, если мы пройдем вокруг методов нашего Корзина объект, мы можем потерять ссылку на это Отказ Это очень противоположно, и оно может привести нас в массу неприятностей.
Обычная ловушка присваивает метод экземпляра к обработчику события.
Рассмотрим наши Cart.empty метод.
empty () { this.db = [] }Если мы назначаем этот метод непосредственно на Нажмите Событие кнопки на нашей веб-странице …
---
document .querySelector('#empty') .addEventListener( 'click', cart.empty )… Когда пользователи нажимают пустую кнопка их Корзина останется полной.
Это не работает молча потому что это теперь будет ссылаться на кнопка вместо Корзина Отказ Итак, наш Cart.empty Метод в конечном итоге назначает новое свойство нашему кнопка называется дБ и установка этого свойства на [] вместо того, чтобы влиять на Корзина Объект дБ Отказ
Это такая ошибка, которая приведет вас с ума, потому что в консоли нет ошибки, и ваш здравый смысл скажет вам, что он должен работать, но это не так.
Чтобы сделать это работать, мы должны сделать:
document .querySelector("#empty") .addEventListener( "click", () => cart.empty() )Или:
document .querySelector("#empty") .addEventListener( "click", cart.empty.bind(cart) )Я думаю Маттес Петтер Йоханссон сказал это лучшее :
Ледяная фабрика к спасению
Как я уже говорил ранее, Ледяная фабрика – это просто функция, которая создает и возвращает замороженный объект Отказ С ледяной фабрикой наша корзина покупок выглядит так:
// makeShoppingCart.js
export default function makeShoppingCart({ db}) { return Object.freeze({ addProduct, empty, getProducts, removeProduct, // others }) function addProduct (product) { db.push(product) } function empty () { db = [] } function getProducts () { return Object .freeze([...db]) } function removeProduct (id) { // remove a product }// other functions}
// someOtherModule.js
const db = []const cart = makeShoppingCart({ db })cart.addProduct({ name: 'foo', price: 9.99})Обратите внимание на наши «странные, облачные радужные ловушки» ушли:
- Мы больше не нужны
новый. Мы просто вызываем простую старую функцию JavaScript для создания нашихКорзинаобъект. - Мы больше не нужны
этоОтказ Мы можем получить доступ кдБОбъект непосредственно из наших функций-членов. - Наше
КорзинаОбъект полностью неизменен.Объект ()ЗамораживаетКорзинаОбъект, так что к ней нельзя добавлять новые свойства, существующие свойства не могут быть удалены или изменены, а прототип нельзя изменить. Просто помните, чтоОбъект ()это мелкий , так что если объект, который мы возвращаем, содержитмассивили другоеобъектМы должны убедиться, чтоObject.Freeze ()их тоже. Кроме того, если вы используете замороженный объект за пределами ES модуль , вы должны быть в Строгий режим Чтобы убедиться, что повторные присвоения вызывают ошибку, а не просто молча.
Немного конфиденциальности, пожалуйста
Еще одним преимуществом ледовых фабрик является то, что у них могут быть частные члены. Например:
function makeThing(spec) { const secret = 'shhh!' return Object.freeze({ doStuff }) function doStuff () { // We can use both spec // and secret in here }}// secret is not accessible out here
const thing = makeThing()thing.secret // undefined
Это сделано возможным из-за закрытия в JavaScript, которое вы можете прочитать больше о MDN Отказ
Небольшое признание, пожалуйста
Хотя заводские функции навсегда были навсегда вокруг JavaScript, шаблон Ice Factory был сильно вдохновлен некоторым кодом, что Дуглас Крукфорд показал в Это видео Отказ
Вот Крукфорд, демонстрирующий создание объекта с функцией, которую он называет «Конструктор»:
Моя версия Ice Factory в приведенном выше примере Крукфорда будет выглядеть так:
function makeSomething({ member }) { const { other } = makeSomethingElse() return Object.freeze({ other, method }) function method () { // code that uses "member" }}Я воспользовался функциональным подъемником, чтобы приложить свое возвращение возле вершины, так что читатели будут иметь приятное краткое описание того, что происходит перед погружением в детали.
Я также использовал разрушимость на спецификация параметр. И я переименован в шаблон на «ледяную фабрику», чтобы это было более запоминающимся и менее легко смущенным с Конструктор Функция от JavaScript Класс Отказ Но это в основном то же самое.
Итак, кредит, где кредит должен, спасибо, мистер Крукфорд.
Как насчет наследования?
Если мы отметим, построены на нашем маленьком приложении электронной коммерции, мы могли бы вскоре понять, что концепция добавления и удаления продуктов продолжает обрезать снова и снова повсюду.
Наряду с нашей корзиной, мы, вероятно, имеем объект каталога и объект заказа. И все эти, вероятно, подвергают некоторую версию `AddProduct и` RemoveProduct`.
Мы знаем, что дублирование плохого, поэтому мы в конечном итоге будем соблазнены создать что-то вроде объекта продукта, что наша корзина, каталог и заказ могут все наследовать.
Но вместо расширения наших объектов, наследуя список продуктов, мы можем вместо этого принять вечный принцип, предлагаемый в одном из самых влиятельных книг по программированию:
На самом деле, авторы этой книги – разговорно известны как «банды четырех», – продолжайте говорить:
Итак, вот наш список продуктов:
function makeProductList({ productDb }) { return Object.freeze({ addProduct, empty, getProducts, removeProduct, // others )} // definitions for // addProduct, etc…}А вот наша корзина:
function makeShoppingCart(productList) { return Object.freeze({ items: productList, someCartSpecificMethod, // …)}function someCartSpecificMethod () { // code }}И теперь мы можем просто ввести список наших продуктов в нашу корзину, как это:
const productDb = []const productList = makeProductList({ productDb })const cart = makeShoppingCart(productList)
И используйте список продуктов через свойство `Petes` Нравиться:
cart.items.addProduct()
Может быть заманчивому набрать весь список продуктов, включив его методы прямо в объект корзины покупок, например:
function makeShoppingCart({ addProduct, empty, getProducts, removeProduct, …others}) { return Object.freeze({ addProduct, empty, getProducts, removeProduct, someOtherMethod, …others)}function someOtherMethod () { // code }}На самом деле, в предыдущей версии этой статьи я сделал только что. Но тогда было указано мне, что это немного опасно (как объяснил здесь ). Итак, нам лучше придерживаться надлежащей композицией объекта.
Потрясающие. Я продан!
Всякий раз, когда мы изучаем что-то новое, особенно что-то как комплекс как архитектура и дизайн программного обеспечения, мы склонны хотеть усердно и быстрые правила. Мы хотим слышать что-то вроде « всегда сделать это» и «… никогда не не делай это».
Чем дольше я трачу работать с этим, тем больше я понимаю, что нет такой вещи, как всегда и никогда. Это о вариантах и компромиссов.
Создание объектов с ледяной фабрикой медленнее и занимает больше памяти, чем использование класса.
В типов используемых случаях я описал, это не имеет значения. Хотя они медленнее, чем классы, ледяные фабрики все еще довольно быстро.
Если вы обнаружите, что нуждаетесь в создании сотен тысяч объектов в одном выстрел, или если вы находитесь в ситуации, когда мощь памяти и обработки находятся на премии крайней премии, вам может понадобиться в классе.
Просто помните, сначала профилируйте свое приложение и не допускайте преждевременно оптимизировать. Большую часть времени создание объекта не будет иметь узкое место.
Несмотря на мой предыдущий Rant, классы не всегда ужасны. Вы не должны выкидывать рамки или библиотеку только потому, что он использует классы. На самом деле, Дэн Абрамов писал довольно красноречиво об этом в своей статье, Как использовать классы и спать ночью Отказ
Наконец, мне нужно признать, что я сделал кучу самоуверенного варианта стилей в образцах кода, которые я представлял вам:
- Я использую функциональные операторы вместо функционирования выражений Отказ
- Я поставил свой оператор возврата в верхней части (это стало возможным благодаря моему использованию функционирования, см. Выше).
- Я называю свою фабрику,
MakexвместоCreatex.илиbuildxили что-то другое. - Моя заводская функция принимает один, разрушенный, объект параметра Отказ
- Я не пользуюсь полукольонами ( Crockford также не одобрит этого )
- и так далее…
Вы можете сделать разные стильные варианты, а это нормально ! Стиль не шаблон.
Шаблон ледяной фабрики только: Используйте функцию для создания и возврата замороженного объекта Отказ Как именно вы пишете, что функция зависит от вас.
Если вы нашли эту статью полезную, пожалуйста, разбейте значок аплодисментов куча раз, чтобы помочь распространить слово. И если вы хотите узнать больше всего так, пожалуйста, подпишитесь на мой рассылку Mastery Mastery ниже. Спасибо!
Обновление 2019: Вот видео, где я использую этот шаблон, много!
Оригинал: “https://www.freecodecamp.org/news/elegant-patterns-in-modern-javascript-ice-factory-4161859a0eee/”