现代JavaScript中的优雅模式(Bill Sourour团队周期)

哈Ha! 当时相当著名的JavaScript老师Bill Sourour写了几篇有关JS中现代模式的文章。 作为本文的一部分,我们将尝试回顾他分享的想法。 并不是说这是一些独特的模式,但我希望本文能找到读者。 从Habr的政策来看,本文不是“翻译”,因为 我描述了Bill的文章指出我的想法。

鲁罗


缩写表示接收对象,返回对象-获取对象,返回对象。 我提供了原始文章的链接链接

Bill写道,他想出了一种编写函数的方法,其中大多数函数仅接受一个参数-一个带有函数参数的对象。 它们还返回结果的对象。 Bill受此想法的重组(ES6功能之一)的启发。

对于那些不了解破坏的人,我将在故事中给出必要的解释。

想象一下,我们有包含其对数据对象中呈现的应用程序某些部分的权限的用户数据。 我们需要根据此数据显示某些信息。 为此,我们可以提供以下实现:

//   const user = { name: 'John Doe', login: 'john_doe', password: 12345, active: true, rules: { finance: true, analitics: true, hr: false } }; //   const users = [user]; //,     function findUsersByRule ( rule, withContactInfo, includeInactive) { //        active const filtredUsers= users.filter(item => includeInactive ? item.rules[rule] : item.active && item.rules[rule]); //  ()   ( )     withContactInfo return withContactInfo ? filtredUsers.reduce((acc, curr) => { acc[curr.id] = curr; return acc; }, {}) : filtredUsers.map(item => item.id) } //     findUsersByRule( 'finance', true, true) 

使用上面的代码,我们将获得所需的结果。 但是,以这种方式编写代码存在一些陷阱。

首先,对findUsersByRule函数的调用非常令人怀疑。 请注意最后两个参数有多歧义。 如果我们的应用程序几乎不再需要联系信息(withContactInfo),却几乎总是需要非活动用户(includeInactive),会发生什么情况? 我们将始终必须传递逻辑值。 现在,尽管函数声明紧跟其调用,但这并没有那么可怕,但可以想象一下,您在另一个模块的某个地方看到了这样的调用。 您将必须查找带有函数声明的模块,以了解为何将两个纯格式的逻辑值传输到该模块。

其次,如果要使某些参数成为强制性参数,则必须编写如下代码:

 function findUsersByRule ( role, withContactInfo, includeInactive) { if (!role) { throw Error(...) ; } //...  } 

在这种情况下,我们的功能除了其搜索职责外,还将执行验证,我们只是想通过某些参数来查找用户。 当然,搜索功能可以使用验证功能,但随后输入参数的列表将扩展。 这也是这种编码模式的负号。

分解涉及将复杂的结构分解为简单的部分。 在JavaScript中,这种复杂的结构通常是对象或数组。 使用解构语法,可以从数组或对象中提取小片段。 此语法可用于声明变量或其用途。 您还可以使用嵌套解构的语法来管理嵌套结构。

使用解构,上一个示例中的函数将如下所示:

 function findUsersByRule ({ rule, withContactInfo, includeInactive}) { //    } findUsersByRule({ rule: 'finance', withContactInfo: true, includeInactive: true}) 

请注意,我们的函数看起来几乎相同,只是我们在参数两边加上了方括号。 现在,我们的函数withContactInfo需要接收三个不同的参数,而是需要一个具有以下属性的对象: rulewithContactInfoincludeInactive

这没有那么多歧义,更容易阅读和理解。 此外,参数的跳过或其他顺序不再是问题,因为现在它们已被命名为对象的属性。 我们还可以安全地向函数声明中添加新参数。 另外,由于 由于解构会复制传递的值,因此其在函数中的更改不会影响原始值。

具有所需参数的问题也可以以更优雅的方式解决。

 function requiredParam (param) { const requiredParamError = new Error( `Required parameter, "${param}" is missing.` ) } function findUsersByRule ({ rule = requiredParam('rule'), withContactInfo, includeInactive} = {}) {...} 

如果我们不传递规则值,则默认传递的函数将起作用,这将引发异常。

JS中的函数只能返回一个值,因此您可以使用一个对象来传输更多信息。 当然,我们并不总是需要函数来返回很多信息,在某些情况下,我们会对原语的返回感到满意,例如, findUserId很自然地findUserId在某种条件下返回一个标识符。

同样,这种方法简化了功能的组成。 实际上,通过组合,函数应仅采用一个参数。 RORO模式遵循相同的合同。

Bill Sourour: “像任何模板一样,RORO应该被视为我们工具箱中的另一个工具。 “我们在有益的地方使用它,使参数列表更易于理解和灵活,并且返回值更具表现力。”

制冰厂


您可以在链接中找到原始文章。

根据作者的说法,此模板是创建并返回冻结对象的函数。

比尔想。 在某些情况下,这种模式可以代替我们常用的ES6类。 例如,我们有一个特定的食物篮,我们可以在其中添加/删除产品。

ES6课程:

 // ShoppingCart.js 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 }) 

使用new关键字创建的对象是可变的,即 我们可以重写类实例方法。

 const db = [] const cart = new ShoppingCart({db}) cart.addProduct = () => 'nope!' //   JS  cart.addProduct({ name: 'foo', price: 9.99 }) // output: "nope!"     

还应该记住,JS中的类是在原型委托上实现的,因此,我们可以在类的原型中更改方法的实现,并且这些更改将影响所有现有实例(我在有关OOP文章中对此进行了更详细的讨论 )。

 const cart = new ShoppingCart({db: []}) const other = new ShoppingCart({db: []}) ShoppingCart.prototype .addProduct = () => 'nope!' //     JS cart.addProduct({ name: 'foo', price: 9.99 }) // output: "nope!" other.addProduct({ name: 'bar', price: 8.88 }) // output: "nope!" 

同意,此类功能会给我们带来很多麻烦。

另一个常见的问题是将实例方法分配给事件处理程序。

 document .querySelector('#empty') .addEventListener( 'click', cart.empty ) 

单击按钮不会清空购物篮。 该方法为名为db的按钮分配了一个新属性,并将此属性设置为[]而不影响购物车对象的db。 但是,控制台中没有错误,您的常识将告诉您代码应该可以,但事实并非如此。

为了使此代码有效,您必须编写一个箭头函数:

 document .querySelector("#empty") .addEventListener( "click", () => cart.empty() ) 

或使用绑定绑定上下文:

 document .querySelector("#empty") .addEventListener( "click", cart.empty.bind(cart) ) 

制冰厂将帮助我们避免这些陷阱。

 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 }) 

这种模式的特点:

  • 无需使用new关键字
  • 无需绑定此
  • 推车是完全便携式的
  • 可以声明从外部看不到的局部变量

 function makeThing(spec) { const secret = 'shhh!' return Object.freeze({ doStuff }) function doStuff () { //    secret } } // secret    const thing = makeThing() thing.secret // undefined 

  • 模式支持继承
  • 与使用类相比,使用Ice Factory创建对象的速度更慢,并且占用的内存更多(在许多情况下,我们可能需要使用类,因此我建议本文
  • 这是一个可以分配为回调的常用函数

结论


当我们谈论正在开发的软件的体系结构时,我们应该始终做出方便的妥协。 在这条路径上没有严格的规则和限制,每种情况都是唯一的,因此我们的武器库中的模式越多,我们就越有可能在特定情况下选择最佳架构选项。

Source: https://habr.com/ru/post/zh-CN472816/


All Articles