工厂函数
在学习jQuery的时候,我们经常会看到“工厂函数”这个概念,那么究竟什么是“工厂函数”呢?我们来看看概念,“所谓工厂函数,就是指这些内建函数都是类对象,当你调用他们时,实际上是创建了一个类实例”。意思就是当我调用这个函数,实际上是先利用类创建了一个对象,然后返回这个对象。由于Javascript本身不是严格的面向对象的语言(不包含类),实际上来说,Javascript并没有严格的“工厂函数”,但是在Javascript中,我们能利用函数模拟类。
我们首先通过new关键字创建了一个对象,obj就相当于Object的实例。我们通过类实例化了一个对象,然后给这个对象相应的属性,最后返回对象。我们可以通过调用这个函数来创建对象,这样的话,实际上工厂函数也很好理解了:
1,它是一个函数。
2,它用来创建对象。
3,它像工厂一样,“生产”出来的函数都是“标准件”(拥有同样的属性)
不学习函数和对象,你不可能成为一名JavaScript程序员,并且当他们一起使用时,是构建块,我们需要从一个称为 组合(composition) 的强大对象范例开始。今天我们来看一些惯用的模式,使用工厂函数来组成函数,对象和 Promises 。组合模式是将一批子对象组织为树形结构,一条顶层的命令会在操作树中所有的对象。当一个函数返回一个对象时,我们称之他为 工厂函数(factory function) 。
让我们来看一个简单的例子。
1
2
3
4
5
6
7
|
function createJelly() { return { type: 'jelly' , colour: 'red' scoops: 3 }; } |
下面我们通过一些实例给大家介绍。
每次我们调用这个工厂函数,它将返回一个新的 jelly(果冻) 对象实例。要注意的重点是,我们不必在工厂函数名称前面加上 create ,但它可以让其他人更清楚函数的意图。对于 type 属性也是如此,但通常它可以帮助我们区分我们程序的对象。
1.带参数的工厂函数
像所有函数一样,我们可以通过参数来定义我们的工厂函数 (icecream 冰淇淋),这可以用来改变返回对象的模型。
1
2
3
4
5
6
7
|
function createIceCream(flavour= 'Vanilla' ) { return { type: 'icecream' , scoops: 3, flavour } } |
理论上,您可以使用带有数百个参数的工厂函数来返回非常特使的深层嵌套对象,但正如我们将看到的,这根本不是组合的精髓。
2.组合的工厂函数
在一个工厂函数中定义另一个工厂函数,可以帮助我们把复杂的工厂函数拆分成更小的,可重用的碎片。
例如,我们可以创建一个 dessert(甜点)工厂函数,通过前面的 jelly(果冻)和 icecream(冰淇淋)工厂函数来定义。
1
2
3
4
5
6
7
8
9
|
function createDessert() { return { type: 'dessert' , bowl: [ createJelly(), createIceCream() ] }; } |
我们可以组合工厂函数来构建任意复杂的对象,这不需要我们结合使用 new 或 this 。对象可以用 has-a (具有) 关系而不是 is-a (是) 来表示。也就是说,可以用组合而不是继承来实现。
例如,使用继承。
1
2
3
4
5
6
7
8
9
10
11
|
// A trifle *is a* dessert 蛋糕*是*甜点 function Trifle() { Dessert.apply( this , arguments); } Trifle.prototype = Dessert.prototype; // 或者 class Trifle extends Dessert { constructor() { super (); } } |
我们可以用组合模式表达相同的意思。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// A trifle *has* layers of jelly, custard and cream. It also *has a* topping. // 蛋糕 *有* 果冻层,奶酪层和奶油层,顶部还 *有* 装饰配料。 function createTrifle() { return { type: 'trifle' , layers: [ createJelly(), createCustard(), createCream() ], topping: createAlmonds() }; } |
3.异步的工厂函数
并非所有工厂都会立即返回数据。例如,有些必须先获取数据。在这些情况下,我们可以返回 Promises 来定义工厂函数。
1
2
3
4
5
6
7
8
9
10
11
12
|
function getMeal(menuUrl) { return new Promise((resolve, reject) => { fetch(menuUrl) .then(result => { resolve({ type: 'meal' , courses: result.json() }); }) . catch (reject); }); } |
这种深度嵌套的缩进会使异步工厂难以阅读和测试。将它们分解成多个不同的工厂通常是有帮助的,可以使用如下编写。
1
2
3
4
5
6
7
8
9
10
11
|
function getMeal(menuUrl) { return fetch(menuUrl) .then(result => result.json()) .then(json => createMeal(json)); } function createMeal(courses=[]) { return { type: 'meal' , courses }; } |
当然,我们可以使用回调函数,但是我们已经有了 Promise.all 这样的工具返回 Promises 来定义工厂函数。
1
2
3
4
5
6
7
8
9
10
|
function getWeeksMeals() { const menuUrl = 'jsfood.com/' ; return Promise.all([ getMeal(`${menuUrl}/monday`), getMeal(`${menuUrl}/tuesday`), getMeal(`${menuUrl}/wednesday`), getMeal(`${menuUrl}/thursday`), getMeal(`${menuUrl}/friday`) ]); } |
我们使用 get 而不是 create 作为命名约定来显示这些工厂做一些异步工作和返回promise。
4.函数和方法
到目前为止,我们还没有看到任何工厂用方法返回对象,这是故意的。这是因为一般来说,我们不需要这么做。工厂允许我们从计算中分离我们的数据。这意味着我们总是能够将对象序列化为JSON,这对于在会话之间持久化,通过HTTP或WebSockets发送它们,并将它们放入数据存储很重要。
例如,不是在 jelly(果冻) 对象上定义 eat 方法,我们可以定义一个新的函数,它接受一个对象作为参数并返回一个修改的版本。
1
2
3
4
5
6
|
function eatJelly(jelly) { if (jelly.scoops > 0) { jelly.scoops -= 1; } return jelly; } |
一点点句法帮助使这是一个可行的模式,那些喜欢编程而不改变数据结构的人。对于那些喜欢编程而不改变数据结构的人来说,使用 ES6 的 ... 语法 是一个可行的模式。
1
2
3
4
5
6
7
|
function eat(jelly) { if (jelly.scoops > 0) { return { ...jelly, scoops: jelly.scoops - 1 }; } else { return jelly; } } |
现在,不是这样写:
1
2
|
import { createJelly } from './jelly' ; createJelly().eat(); |
而是这样写
1
2
|
import { createJelly, eatJelly } from './jelly' ; eatJelly(createJelly()); |
最终结果是一个函数,它接受一个对象并返回一个对象。我们称之为返回对象的函数是什么? 一个工厂!
5.高级工厂
将工厂传递给 高阶函数 ,这将给我们带来巨大的控制力。例如,我们可以使用这个概念来创建一个增强的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function giveTimestamp(factory) { return (...args) => { const instance = factory(...args); const time = Date.now(); return { time, instance }; }; } const createOrder = giveTimestamp( function (ingredients) { return { type: 'order' , ingredients }; }); |
这个增强的对象采用一个现有工厂,并将其包装以创建返回带有时间戳实例的工厂。或者,如果我们想要确保一个工厂返回不可变的对象,我们可以用 freezer 来增强它。
1
2
3
4
5
|
function freezer(factory) { return (...args) => Object.freeze(factory(...args))); } const createImmutableIceCream = freezer(createIceCream); createImmutableIceCream( 'strawberry' ).flavour = 'mint' ; // Error! |