深度嵌套函数的替代方案 javascript

Alternatives to deep nesting functions javascript

本文关键字:方案 javascript 嵌套 函数 深度      更新时间:2023-09-26

我一直在尝试在javascript项目中组织代码,最终得到了一堆嵌套函数。据说这是不好的做法,我知道它会影响性能,但我很难想出替代方案。以下是我正在尝试执行的操作的示例:

嵌套前的代码:

function Level1(dep1, dep2, dep3, dep4){
    var tempResult1 = dep1 + dep2;
    var tempResult2 = tempResult1/2;
    var tempResult3 = dep3 + dep4;
    var mainResult = tempResult2 + tempResult3;
    return mainResult;
}

在我尝试将职责划分为层次结构后的样子:

function Level1(dep1, dep2, dep3, dep4){
    var tempResult2 = getTempResult2(dep1, dep2);
    var tempResult3 = getTempResult3(dep3, dep4);
    var mainResult = tempResult2 + tempResult3;
    return mainResult;
    function getTempResult2(dep1, dep2){
        var tempResult1 = getTempResult1(dep1, dep2);
        return tempResult1/2;
        function getTempResult1(dep1, dep2){
            return dep1 + dep2;
        }
    }
    function getTempResult3(dep3, dep4){
        return dep3 + dep4;
    }        
}

显然,对于这里使用的操作,函数有点过多,但它确实有助于使我的项目中的函数更易于管理。我不熟悉任何其他方法,因为这是我的第一个javascript项目。我在这里找到的建议只处理 1 级嵌套函数,而不是 2 级,我没有看到任何关于如何实现嵌套范围的好例子。如果有人能给我一个例子,说明如何完成我在这里寻找的组织,我会非常高兴。谢谢。

最终得到了一堆嵌套函数

您可以简单地取消嵌套它们,因为它们不用作闭包(您将所有必要的内容作为参数传递)。通过每次调用外部函数时不创建本地函数对象,您甚至可以获得一点性能优势(尽管优化得很好,可能可以忽略不计)。

我刚刚读了一堆关于嵌套函数如何成为不良做法的信息

请注意,当您想要创建闭包时,有时嵌套函数是必需的。

我不喜欢getTempResult1,2和3可以在Level1之外访问,或者getTempResult1可以在getTempResult2之外访问。

您可以使用 IEFE 创建一个额外的范围,从中仅导出Level1

var Level1 = (function() {
    function Level1(dep1, dep2, dep3, dep4) {
        var tempResult2 = getTempResult2(dep1, dep2);
        var tempResult3 = getTempResult3(dep3, dep4);
        var mainResult = tempResult2 + tempResult3;
        return mainResult;
    }
    var getTemptResult2 = (function() {
        function getTempResult2(dep1, dep2) {
            var tempResult1 = getTempResult1(dep1, dep2);
            return tempResult1/2;
        }
        function getTempResult1(dep1, dep2) {
            return dep1 + dep2;
        }
        return getTempResult2;
    })();
    function getTempResult3(dep3, dep4) {
        return dep3 + dep4;
    }        
    return Level1;
}());

也许这就是你想要的:

function Level1(dep1, dep2, dep3, dep4){
    var tempResult2 = Level1.getTempResult2(dep1, dep2);
    var tempResult3 = Level1.getTempResult3(dep3, dep4);
    var mainResult = tempResult2 + tempResult3;
    return mainResult;
}
Level1.getTempResult2 = function (dep1, dep2) {
    var tempResult1 = Level1.getTempResult2.getTempResult1(dep1, dep2);
    return tempResult1/2;   
}
Level1.getTempResult2.getTempResult1 = function (dep1, dep2){
    return dep1 + dep2;
}
Level1.getTempResult3 = function (dep3, dep4){
    return dep3 + dep4;
}

目前我尝试了

function a(val1, val2) { return a.foo(val1, val2) }
a.foo = function (x,y) { return x + y }

在我的浏览器中。该命令a(1,2)按方面打印3。其他例子:

function a() { return a.foo(1,2) }
a.foo = function (x,y) { return a.foo.bar(x,y) }
a.foo.bar = function (x,y) { return x+y }
a(1,2) // -> 3

这是一个设计问题。由于一个好的和优雅的设计总是取决于你的问题域的相当多的方面,所以从你的问题中的简化代码中不可能真正估计出最佳解决方案是什么。我将尝试向您展示一些选项,这些选项在每种情况下都解决了进行数据隐藏并避免嵌套函数或多次创建的函数的方法。

由于您要求隐藏数据并保持 getTempResult1 对 getTempResult2 以外的任何内容隐藏,因此我将假设这些函数中的每一个都相当复杂,并且可能由不同的人编写,并且您希望隐藏内部结构。这需要为每个类创建一个不同的类,而不仅仅是一个函数。

我将用更具体的示例替换您的示例代码,并使用适当的 OOP 方法来解决问题。假设您正在构建一个电子商务应用程序。级别 1 将是一个发票类,dep1-4 可能是:购买价格、利润率、贴现率、税率。我们将制作一个非常简单的应用程序,它将计算: purchase price - discount + profit + taxes = total price

我希望这足够忠实地类似于您的问题,以便您可以欣赏使用的一些 OOP 技术(对于完成的计算来说,它在结构上仍然有点矫枉过正,但这是 OOP 中的一个教训,如果问题域在未来变得更加复杂,则允许大量的可扩展性,比如说你走向国际并且必须计算不同国家的税收等)。

我将使用 OoJs 库来能够在 JavaScript 中执行适当的 OOP。请参阅下面的代码在 jsfiddle 上工作。

电子商务应用程序的想法灵感来自Shalloway和Trott的"Dessign模式解释"一书

总之,您会发现此解决方案:

  • 隐藏实现详细信息
  • 没有嵌套函数
  • 每个函数仅创建一次
  • 具有
  • 可扩展性、可维护性,并且在发生变化时具有灵活性要求

因此,使用我们的类的代码将如下所示:

// Create your namespace to avoid polluting global
//
var eComm = eComm || {}

// this will be some factory code which will return the needed objects, it won't actually use them.
// Normally this should be a class living in our eComm namespace
//
function factory( db, clientID )
{
   // we would assume here that the hardcoded rates would be found in the database using the client id.
   //
   var discount = new eComm.Discount( 5            ) // in %
   var profit   = new eComm.Profit  ( 20, discount ) // in %
   var taxRate  = new eComm.TaxRate ( 5 , profit   ) // in %

   // note that I use a simple aggragation approach, because I don't know the
   // actual complexity of your problem domain. It makes this very simple ecommerce
   // code not entirely ideal. If we would just perform a chain of operations on
   // a number, other design patterns would be more suited, like a decorator.
   // It is not appropriate to just pass taxRate to Invoice, because it is no different
   // than profit or discount, it just comes later in a chain of calculations.
   // I have taken this approach to show that it is possible to hide steps of the
   // implementation down a hierarchy.
   //
   return new eComm.Invoice( taxRate )
}

// now when we will actually use it, it looks like this
// wrapped it in a function call because on global scope
// we would have to put this entirely at the bottom
// if you put all your code in classes you don't have this
// problem. They can occur in any order
//
function usage()
{
   var invoice = factory( "database", 1654 /* the client id */ )
   invoice.addPurchase( 1574 ) // price in some currency
   invoice.addPurchase( 1200 ) // a second purchase

   // in reality you would probably also pass an object representing an output
   // device to Invoice (a printer, or a pdf generator, ...)
   //
   console.log( invoice.total() )
}

实际类。它看起来很长,但那是因为他们做的越少,开销就越大(相对而言)。为了简洁起见,我省略了越来越多的代码,因为这些类看起来都非常相似。

;( function class_Invoice( namespace )
{
   'use strict'; // recommended
   if( namespace[ "Invoice" ] ) return    // protect against double inclusions
       namespace.Invoice = Invoice
   var Static            = OoJs.setupClass( namespace, "Invoice" )

   // constructor
   //
   function Invoice( taxRate )
   {
      // should do validation as javascript is loosely typed
      //
      if( "TaxRate" !== OoJs.typeOf( taxRate ) )
         ;// throw an error

      // Data members
      //
      this.taxRate    = taxRate
      this.totalPrice = 0

      this.Protected( "taxRate", "totalPrice" ) // if you want them available to subclasses

      var iFace = this.Public( total, addPurchase ) // make these methods public
      return iFace
   }

   // all your method definitions go here
   //
   function addPurchase( price )
   {
      this.totalPrice += this.taxRate.calculate( price )
   }

   function total()
   {
      return this.totalPrice
   }
})( eComm )

;( function class_TaxRate( namespace )
{
       namespace.TaxRate = TaxRate
   var Static            = OoJs.setupClass( namespace, "TaxRate" )

   // constructor
   //
   function TaxRate( rate, profit )
   {
      // do your validation on profit and rate as above
      this.rate   = rate
      this.profit = profit
      this.Protected( "profit" ) // if you want
      return this.Public( calculate )
   }

   function calculate( price )
   {
      return this.profit.calculate( price ) * ( 1 + this.rate / 100 )
   }
})( eComm )

;( function class_Profit( namespace )
{
       namespace.Profit = Profit
   var Static           = OoJs.setupClass( namespace, "Profit" )

   // constructor
   //
   function Profit( rate, discount )
   {
      this.rate     = rate
      this.discount = discount
      return this.Public( calculate )
   }

   function calculate( price )
   {
      return this.discount.calculate( price ) * ( 1 + this.rate / 100 )
   }
})( eComm )

;( function class_Discount( namespace )
{
       namespace.Discount = Discount
   var Static             = OoJs.setupClass( namespace, "Discount" )

   // constructor
   //
   function Discount( rate )
   {
      this.rate = rate
      return this.Public( calculate )
   }

   function calculate( price )
   {
      return price - price * this.rate / 100
   }
})( eComm )

usage()
您无需

担心调用堆栈会深入几级。

这是过早优化的一个例子

尝试查看用于 node.js 的步骤模块。我一直在使用它。

但是,即使在节点.js环境之外,您也可以使用 step.js 脚本(注意:我尚未对此进行测试)。至少,它显示了如何扁平化任意数量的嵌套级别。

我知道

这已经得到了回答,但我想我会给你留下一些额外的资源来帮助你进行这次冒险。我认为这是你深入研究JavaScript设计模式的最佳时机。

Addy Osmani 的学习 JavaScript 设计模式是一个很棒的阅读/资源,可以了解创建 JavaScript 应用程序、创建可重用代码、闭包等的多种模式。

这是他关于工厂模式的文章中的一个示例片段

// Types.js - Constructors used behind the scenes
// A constructor for defining new cars
function Car( options ) {
 // some defaults
 this.doors = options.doors || 4;
 this.state = options.state || "brand new";
 this.color = options.color || "silver";
}
// A constructor for defining new trucks
function Truck( options){
  this.state = options.state || "used";
  this.wheelSize = options.wheelSize || "large";
  this.color = options.color || "blue";
}

// FactoryExample.js
// Define a skeleton vehicle factory
function VehicleFactory() {}
// Define the prototypes and utilities for this factory
// Our default vehicleClass is Car
VehicleFactory.prototype.vehicleClass = Car;
// Our Factory method for creating new Vehicle instances
VehicleFactory.prototype.createVehicle = function ( options ) {
switch(options.vehicleType){
  case "car":
  this.vehicleClass = Car;
  break;
  case "truck":
    this.vehicleClass = Truck;
  break;
  //defaults to VehicleFactory.prototype.vehicleClass (Car)
 }
return new this.vehicleClass( options );
};

// Create an instance of our factory that makes cars
var carFactory = new VehicleFactory();
var car = carFactory.createVehicle( {
            vehicleType: "car",
            color: "yellow",
            doors: 6 } );
// Test to confirm our car was created using the vehicleClass/prototype Car
// Outputs: true
console.log( car instanceof Car );
// Outputs: Car object of color "yellow", doors: 6 in a "brand new" state
console.log( car );

希望本文能帮助您和其他人寻找类似的答案。

最好

让函数实现得尽可能少,以实现最佳重用。例如:

function doChores(){
  //actually wash the dishes
  //actually walk the dog.
}

现在假设下雨了,我只想洗碗,因为洗碗是在doChores中实现的,如果不遛狗,我就不能称之为。以下是应该如何完成:

function doChores(){
  walkTheDog();
  washTheDishes();
}

walkTheDog函数实现遛狗和洗碗实现洗碗,以便可以单独调用它们。

您面临的问题是将变量传递给函数链时。我通常将一个参数传递给函数,该参数包含一个具有所需参数的对象。每个函数都可以读取或改变它们所关注的传递对象的成员。如果以后需要添加更多参数,则无需更改函数的签名(例如function(arg1, arg2, newArg)),您将始终拥有function(args)

有关参数传递的详细信息,请参阅此处:https://stackoverflow.com/a/16063711/1641941 在传递(构造函数)参数下