【问题标题】:Alternatives to deep nesting functions javascript深度嵌套函数 javascript 的替代方案
【发布时间】:2014-04-03 00:30:15
【问题描述】:

我一直在尝试在一个 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 级,而且我没有看到任何关于如何实现嵌套范围的好例子。如果有人能给我一个例子来说明我在这里寻找的组织的方法,我会非常感激。谢谢。

【问题讨论】:

  • 我一直在做更多的 javascript,现在根据 Bergi 使用 IIFE 和模块系统的建议使用 requirejs。

标签: javascript function oop


【解决方案1】:

最终得到了一堆嵌套函数

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

我刚刚阅读了一堆关于嵌套函数是如何不好的做法

请注意,当您想create a closure 时,有时需要嵌套函数。

我不喜欢 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;
}());

【讨论】:

  • getTempResult1 仍然可以从 getTempResult3 和主要的 Level1 主体访问,但我不喜欢。
  • 我确实喜欢这种通用格式。有没有办法在其中为 getTempResult2 嵌套另一个 IEFE 或类似的东西?
  • 是的,这是可能的,尽管从代码可读性的角度来看,嵌套在某些时候会变得丑陋 :-) 您可以考虑使用模块系统,将它们放入单独的文件中。
  • 模块系统是指在相互导入的单独 .js 文件中定义一堆模块吗?
  • 我是这么想的,是的,但可能不适用于您的实际情况。
【解决方案2】:

也许这就是你想要的:

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、2 和 3 可以在 Level1 之外访问,或者 getTempResult1 可以在 getTempResult2 之外访问。
  • 这有什么问题(除非你把它们放在全局命名空间中)?
  • 在 Level1 中嵌套 getTempResult2、getTempResult1 将使它们保持私有。在 getTempResult2 中嵌套 getTempResult1 没有任何作用,因为它已经是私有的。
  • @Remento 对外部调用者是私有的,是的,但对可能在 getTempResult2 范围内的任何其他函数不适用。在我的项目中有时会发生这种情况,我想防止那里发生冲突。
  • 传统方式?并非如此,使用函数作为命名空间对象是相当少见的。通常你会使用类构造函数或模块对象在它们上面放置静态方法。
【解决方案3】:

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

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

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

我希望这足以忠实地解决您的问题,以便您可以欣赏使用的一些 OOP 技术(对于已完成的计算来说,它在结构上仍然过于矫枉过正,但它是 OOP 的一课,并且在出现问题时允许大量的可扩展性域将来会变得更加复杂,例如您要走向国际,并且必须计算不同国家/地区的税收等)。

我将使用 OoJs library 在 JavaScript 中进行正确的 OOP。请参阅下面的代码working on jsfiddle

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

总之你会发现这个解决方案:

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

所以使用我们的类的代码如下所示:

// 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()

【讨论】:

    【解决方案4】:

    您无需担心调用堆栈会变得更深。

    这是premature optimization的示例

    【讨论】:

    • 他问的不是调用堆栈,而是嵌套
    • 那么你是说嵌套函数没问题?我以这种方式编写代码的部分原因是为了避免这个问题(过早的优化)。现在我已经完成了基本功能,它可能会产生性能影响(由于一堆嵌套函数,div 在被单击/移动后需要一秒钟才能做出反应),但我不确定。如果这没问题,我会保留它,我只是阅读了一堆关于嵌套函数如何是不好的做法,并认为必须有替代方法。
    【解决方案5】:

    尝试查看用于 node.js 的 step module。我一直在使用它。

    但是,您甚至可以在 node.js 环境之外使用 step.js 脚本(注意:我没有对此进行测试)。至少,它展示了如何展平任意数量的嵌套级别。

    【讨论】:

    • "step - An async control-flow library" 你确定你链接了正确的东西吗?
    • 其实也支持同步调用。
    • 好的,虽然我仍然不明白这对这里有什么帮助。您能否发布一些代码,您将如何在 OP 示例中使用它?
    【解决方案6】:

    我知道这个问题已经得到解答,但我想我会给你一些额外的资源来帮助你完成这次冒险。我认为这是您深入了解 javascript 设计模式的最佳时机。

    Learning JavaScript Design Patterns Addy Osmani 是一本很棒的读物/资源,可以了解创建 javascript 应用程序、创建可重用代码、闭包等的多种模式。任何正在内部讨论如何更好地组织我的嵌套函数/范围的人,等应该阅读它。

    这是他关于工厂模式

    的文章中的一个示例 sn-p
    // 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 );
    

    希望这篇文章可以帮助您和其他寻找类似答案的人。

    【讨论】:

      【解决方案7】:

      最好尽可能少地实现函数以实现最佳重用。例如:

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

      现在假设下雨了,我只想洗碗,因为洗碗是在 doChores 中实现的,我不能不遛狗就打电话。应该这样做:

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

      函数 walkTheDog 实现了遛狗,washTheDishes 实现了洗碗,因此可以单独调用它们。

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

      有关参数传递的更多信息可以在这里找到:https://stackoverflow.com/a/16063711/1641941 在传递(构造函数)参数下

      【讨论】:

        猜你喜欢
        • 2020-01-15
        • 1970-01-01
        • 2021-11-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-25
        • 2013-08-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多