- Оригинальный адрес:JavaScript Factory Functions with ES6+
- Оригинальный автор:Eric Elliott
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:lampui
- Корректор:IridescentMia,sunui
Примечание. Это восьмая часть серии статей «Написание программного обеспечения» о том, как с нуля изучить методы функционального программирования и композиционного программного обеспечения в JavaScript ES6+. , см. Википедию.Composability). В будущем будет еще больше захватывающего контента, так что следите за обновлениями!
| | Далее >
заводская функцияэто функция, которая возвращает объект, это не класс и не конструктор. В JavaScript любая функция может возвращать объект, если функция не использовалась до этого.new
ключевое слово, но возвращает объект, то эта функция является фабричной.
Поскольку фабричная функция предоставляет возможность легко генерировать экземпляры объектов без необходимости глубокого изучения классов иnew
сложность ключевых слов, поэтому фабричные функции всегда были привлекательны в JavaScript.
JavaScript предоставляет очень удобный синтаксис литерала объекта, код выглядит следующим образом:
const user = {
userName: 'echo',
avatar: 'echo.png'
};
Подобно синтаксису JSON (JSON — это литеральный синтаксис объекта на основе JavaScript),:
(двоеточие) Слева — имя свойства, справа — значение свойства. Вы можете получить доступ к переменным с помощью оператора точки:
console.log(user.userName); // "echo"
Или используйте квадратные скобки и имена свойств для доступа к переменным:
const key = 'avatar';
console.log( user[key] ); // "echo.png"
Если в области есть переменная с тем же именем, что и у вашего свойства, вы можете использовать переменную непосредственно в литерале объекта, опуская двоеточие и значение свойства:
const userName = 'echo';
const avatar = 'echo.png';
const user = {
userName,
avatar
};
console.log(user);
// { "avatar": "echo.png", "userName": "echo" }
Литералы объектов поддерживают краткую запись. мы можем добавить.setUserName()
Методы:
const userName = 'echo';
const avatar = 'echo.png';
const user = {
userName,
avatar,
setUserName (userName) {
this.userName = userName;
return this;
}
};
console.log(user.setUserName('Foo').userName); // "Foo"
В краткой записи,this
Указывает на объект, для которого был вызван метод. Чтобы вызвать метод для объекта, просто используйте оператор точки для доступа к методу и вызовите его в круглых скобках, например.game.play()
только что вgame
вызвать этот объект.play()
. Чтобы вызвать метод с помощью оператора точки, метод должен быть свойством объекта. Вы также можете использовать метод прототипа функции.call()
,.apply()
или.bind()
Применение метода к объекту.
В этом примереuser.setUserName('Foo')
вuser
вызов на объект.setUserName()
,следовательноthis === user
. существует.setUserName()
метод, мы проходимthis
Эта цитата была изменена.userName
, затем возвращает тот же экземпляр объекта, чтобы упростить последующую цепочку методов.
Литералы ориентированы на один объект, а фабричные методы подходят для многих объектов.
Если вам нужно создать несколько объектов, вам следует рассмотреть возможность использования литералов объектов в сочетании с фабричными функциями.
Используя фабричные функции, вы можете создать столько пользовательских объектов, сколько захотите. Если бы вы разрабатывали приложение чата, вы бы использовали пользовательский объект для представления текущего пользователя и ряд пользовательских объектов для представления других пользователей, которые вошли в систему и общаются в чате, для отображения их имен, аватаров и т. д.
давайте положимuser
объект превращается вcreateUser()
Заводской метод:
const createUser = ({ userName, avatar }) => ({
userName,
avatar,
setUserName (userName) {
this.userName = userName;
return this;
}
});
console.log(createUser({ userName: 'echo', avatar: 'echo.png' }));
/*
{
"avatar": "echo.png",
"userName": "echo",
"setUserName": [Function setUserName]
}
*/
возвращаемый объект
стрелочная функция (=>
) имеет характеристику неявного возврата: если тело функции состоит из одного выражения, его можно опуститьreturn
ключевые слова.()=>'foo'
это функция, которая не принимает аргументов и возвращает строку"foo"
.
Будьте осторожны при возврате литералов объекта. При использовании фигурных скобок JavaScript по умолчанию создает тело функции, например.{ broken: true }
. Если вам нужно вернуть явный литерал объекта, вам нужно устранить неоднозначность, заключив литерал объекта в круглые скобки, например:
const noop = () => { foo: 'bar' };
console.log(noop()); // undefined
const createFoo = () => ({ foo: 'bar' });
console.log(createFoo()); // { foo: "bar" }
В первом примереfoo:
интерпретируется как метка,bar
интерпретируется как выражение, которое не присваивается и не возвращается, поэтому функция возвращаетundefined
.
существуетcreateFoo()
В примере круглые скобки заставляют фигурные скобки интерпретировать выражение требуемого значения, а не тело функции.
разрушать
Обратите особое внимание на объявления функций:
const createUser = ({ userName, avatar }) => ({
В этой строке фигурные скобки ({, }
) представляет деструктурирование объекта. Эта функция имеет один параметр (то есть объект), но из этого параметра деконструируются два формальных параметра,userName
а такжеavatar
. Эти формальные параметры можно использовать как переменные в теле функции. Деструктурирование также можно использовать с массивами:
const swap = ([first, second]) => [second, first];
console.log( swap([1, 2]) ); // [2, 1]
Вы можете использовать расширенный синтаксис (...varName
) принимает остальные значения массива (или списка параметров) и возвращает значения как один элемент:
const rotate = ([first, ...rest]) => [...rest, first];
console.log( rotate([1, 2, 3]) ); // [2, 3, 1]
Расчетная стоимость недвижимости
Ранее мы использовали метод квадратных скобок для динамического доступа к значению свойства объекта:
const key = 'avatar';
console.log( user[key] ); // "echo.png"
Мы также можем вычислить значения свойств для присвоения значений:
const arrToObj = ([key, value]) => ({ [key]: value });
console.log( arrToObj([ 'foo', 'bar' ]) ); // { "foo": "bar" }
В этом примереarrToObj
принимает пару ключ-значение (иначе元组
) и преобразовать его в объект. Поскольку мы не знаем имя свойства, нам нужно вычислить имя свойства, чтобы установить значение свойства для объекта. Для этого мы используем нотацию с квадратными скобками, чтобы установить имя свойства и поместить его в контекст литерала объекта для создания объекта:
{ [key]: value }
После выполнения присваивания мы можем получить вот такой объект:
{ "foo": "bar" }
параметры по умолчанию
Функции JavaScript поддерживают значения параметров по умолчанию, что дает нам следующие преимущества:
- Пользователь может опустить параметры с соответствующими значениями по умолчанию.
- Функции более информативны, потому что значения по умолчанию предоставляют ожидаемые примеры ввода.
- IDE и инструменты статического анализа могут использовать значения по умолчанию для определения типов параметров. Например, значение по умолчанию
1
Указывает, что тип данных, который может принимать параметр,Number
.
Используя параметры по умолчанию, мы можем предоставить нашcreateUser
Заводская функция описывает ожидаемый интерфейс и может автоматически дополняться, если пользователь не предоставил никакой информации某些
деталь:
const createUser = ({
userName = 'Anonymous',
avatar = 'anon.png'
} = {}) => ({
userName,
avatar
});
console.log(
// { userName: "echo", avatar: 'anon.png' }
createUser({ userName: 'echo' }),
// { userName: "Anonymous", avatar: 'anon.png' }
createUser()
);
Последняя часть сигнатуры функции может выглядеть немного забавно:
} = {}) => ({
в последней части объявления параметра= {}
Представление: если переданный фактический параметр не соответствует требованиям, в качестве параметра по умолчанию будет использоваться пустой объект. Когда вы пытаетесь разрушить назначение из пустого объекта, значение свойства по умолчанию заполняется автоматически, потому что это то, что делают значения по умолчанию: замените предопределенными значениямиundefined
.
если нет= {}
Это значение по умолчанию, и нетcreateUser()
передать допустимые аргументы, будет выдана ошибка, потому что вы не можетеundefined
Доступ к свойствам в .
Типовое суждение
На момент написания этой статьи в JavaScript не было встроенных аннотаций типов, но в последние годы появился ряд инструментов или фреймворков форматирования, чтобы заполнить этот пробел, в том числе JSDoc (от которого отказались из-за лучших опций), FacebookFlowи MicrosoftTypeScript. я лично используюrtype, потому что я нахожу его более читабельным, чем TypeScript, с точки зрения функционального программирования.
До написания этой статьи схемы аннотаций различных типов фактически были сопоставимы. Ни одно из них не защищено спецификацией JavaScript, и каждое решение имеет свои вопиющие недостатки.
Вывод типа — это процесс вывода типа переменной на основе контекста, в котором она находится, и в JavaScript это отличная альтернатива аннотациям типов.
Если вы предоставите достаточно подсказок для вывода в стандартных функциях JavaScript, вы сможете получить большую часть преимуществ аннотаций типов, не беспокоясь о каких-либо дополнительных затратах или рисках.
Даже если вы решите использовать такой инструмент, как TypeScript или Flow, вы должны по возможности использовать преимущества вывода типов и сохранять аннотации типов, когда вывод типов утомляет. Например, собственный JavaScript не поддерживает определение общих интерфейсов. Но использование TypeScript или rtype позволяет легко и эффективно определять интерфейсы.
Tern.js— это популярный инструмент вывода типов JavaScript, который имеет плагины для многих редакторов кода или IDE.
Microsoft Visual Studio Code не требует Tern, потому что он предоставляет возможности вывода типов TypeScript для написания кода JavaScript.
Когда вы указываете значения параметров по умолчанию в функциях JavaScript, многие инструменты вывода типов, такие как Tern.js, TypeScript и Flow, могут давать в IDE подсказки, помогающие разработчикам правильно использовать API.
Без значений по умолчанию различные IDE (а чаще и мы сами) не имеют достаточно информации, чтобы сказать, какой тип аргументов ожидает функция.
Со значениями по умолчанию среда IDE (а чаще и мы сами) может вывести тип из кода.
Ограничение параметров фиксированными типами (что сделало бы общие функции и функции более высокого порядка более ограниченными) не очень разумно. Но с точки зрения того, когда этот подход имеет смысл, обычно просто используются аргументы по умолчанию, даже если вы уже используете TypeScript или Flow для вывода типов.
Фабричные функции для структур Mixin
Фабричные функции хороши для создания объектов с хорошим API. Обычно они удовлетворяют базовые потребности, но через некоторое время вы столкнетесь с ситуацией, когда похожие функции всегда встроены в разные типы объектов, поэтому вам нужно абстрагировать эти функции в функции примесей, чтобы их можно было легко использовать повторно.
Фабричная функция миксина вот-вот продемонстрирует свои таланты. Давайте построимwithConstructor
Функция mixin, положить.constructor
Свойства добавляются ко всем экземплярам объекта.
with-constructor.js:
const withConstructor = constructor => o => {
const proto = Object.assign({},
Object.getPrototypeOf(o),
{ constructor }
);
return Object.assign(Object.create(proto), o);
};
Теперь вы можете импортировать и использовать другие миксины:
import withConstructor from `./with-constructor';
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
// 或者 `import pipe from 'lodash/fp/flow';`
// 设置一些 mixin 的功能
const withFlying = o => {
let isFlying = false;
return {
...o,
fly () {
isFlying = true;
return this;
},
land () {
isFlying = false;
return this;
},
isFlying: () => isFlying
}
};
const withBattery = ({ capacity }) => o => {
let percentCharged = 100;
return {
...o,
draw (percent) {
const remaining = percentCharged - percent;
percentCharged = remaining > 0 ? remaining : 0;
return this;
},
getCharge: () => percentCharged,
get capacity () {
return capacity
}
};
};
const createDrone = ({ capacity = '3000mAh' }) => pipe(
withFlying,
withBattery({ capacity }),
withConstructor(createDrone)
)({});
const myDrone = createDrone({ capacity: '5500mAh' });
console.log(`
can fly: ${ myDrone.fly().isFlying() === true }
can land: ${ myDrone.land().isFlying() === false }
battery capacity: ${ myDrone.capacity }
battery status: ${ myDrone.draw(50).getCharge() }%
battery drained: ${ myDrone.draw(75).getCharge() }%
`);
console.log(`
constructor linked: ${ myDrone.constructor === createDrone }
`);
Как видите, многоразовыйwithConstructor()
Миксин просто добавляется к другим миксинам.pipeline
середина.withBattery()
Может использоваться другими типами объектов, например, роботами, электрическими скейтбордами или зарядными устройствами для портативных устройств.withFlying()
Может использоваться для моделирования летающих автомобилей, ракет или воздушных шаров.
Композиция объектов — это скорее способ мышления, чем конкретная техника написания кода. Вы можете использовать его во многих местах. Функциональная композиция — это просто самый простой способ построить свой образ мышления с нуля, а фабричные функции — это простой способ упаковать детали реализации композиции объектов в дружественный API.
В заключение
ES6 предоставляет удобный синтаксис для создания объектов и фабричных функций, которого достаточно в большинстве случаев, но поскольку это JavaScript, существует более удобный и более похожий на Java синтаксис:class
ключевые слова.
В JavaScript классы более многословны и ограничены, чем фабрики, и более подвержены проблемам при рефакторинге кода, но также приняты основными интерфейсными фреймворками, такими как React и Angular, и есть несколько редких случаев использования, которые делают классы более значимыми.
"Иногда самая элегантная реализация — это просто функция. Не метод, не класс, не фреймворк. Просто функция." ~ Джон Кармак
Наконец, вы также должны помнить, чтобы не усложнять.Фабричные функции не нужны.Для определенной проблемы ваше решение должно быть:
纯函数 > 工厂函数 > 函数式 Mixin > 类
Next: Why Composition is Harder with Classes >
следующий
Хотите узнать больше о композиции объектов в JavaScript?
Изучайте Javascript с Эриком Эллиоттом,Сейчас или никогда!
Eric Elliottда«Написание приложений JavaScript»(О'Рейли) и«Изучайте Javascript с Эриком Эллиоттом»Автор двух книг. Он внес свой вклад во многие компании и организации, такие какAdobe Systems,Zumba Fitness,The Wall Street Journal,ESPNа такжеBBCи т. д., а также ведущих художников во многих учреждениях, включая, помимо прочего,Usher,Frank Oceanтак же какMetallica.
Большую часть времени он проводил в районе залива Сан-Франциско с самой красивой женщиной в мире.
благодарныйJS_Cheerleader.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,React,внешний интерфейс,задняя часть,товар,дизайнЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.