我一直在尝试在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级,我没有看到任何关于如何实现嵌套作用域的好例子。如果有人可以给我一个实现我在这里寻找的组织方式的例子,我会非常感激。感谢。
答案 0 :(得分: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
答案 1 :(得分:2)
最终得到了一堆嵌套函数
你可以简单地删除它们,因为它们不被用作闭包(你将所有必要的东西作为参数传递)。通过在每次调用外部函数时都不创建本地函数对象,您甚至可以获得一点性能优势(尽管这已被很好地优化,但可能忽略不计)。
我刚看了一堆关于嵌套函数是如何做坏事
请注意,当您想要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;
}());
答案 2 :(得分:1)
这是一个设计问题。由于优雅和优雅的设计总是取决于您的问题领域的许多方面,因此从您的问题中的简化代码中无法真正估计出最佳解决方案的内容。我将尝试向您展示一些选项,在每种情况下都会解决数据隐藏的方法,并避免多次创建的嵌套函数或函数。
由于你要求数据隐藏并且保持getTempResult1不受getTempResult2以外的任何东西的隐藏,我将假设这些函数中的每一个都相当复杂并且可能由不同的人编写,并且你想要保持内部隐藏。这保证为每一个创建一个不同的类而不仅仅是一个函数。
我将用更实际的示例替换您的示例代码,并使用适当的OOP方法来解决问题。假设您正在构建电子商务应用程序。 Level1将是发票类,dep1-4可能是:购买价格,利润率,折扣率,税率。我们将制作一个非常简单的应用程序,它将计算:purchase price - discount + profit + taxes = total price
我希望这足以忠实地解决你的问题,这样你就可以理解所使用的一些OOP技术(它在计算结构方面仍然过于苛刻,但它是OOP的一个教训,如果问题可以提供很大的可扩展性域名在未来变得更加复杂,比如你去国际,必须为不同的国家计算税收等)。
我将使用OoJs library在JavaScript中进行适当的OOP。请参阅以下代码working on jsfiddle。
电子商务应用的想法源自Shalloway和Trott的“Dessign patterns explain”一书
总之,你会发现这个解决方案:
所以使用我们的类的代码如下所示:
// 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()
答案 3 :(得分:0)
你不必担心调用堆栈有多深。
的示例答案 4 :(得分:0)
尝试查看适用于node.js的step module。我一直都在使用它。
但是,您甚至可能在node.js环境之外使用step.js脚本(注意:我没有对此进行测试)。至少,它显示了如何平整任意数量的嵌套级别。
答案 5 :(得分:0)
我知道这已经回答了,但我想我会留下一些额外的资源来帮助你完成这次冒险。我认为这是您深入了解javascript设计模式的最佳时机。
Addy Osmani的Learning JavaScript Design Patterns是一个很棒的阅读/资源,可以学习创建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 );
希望这篇文章可以帮助您和其他寻找类似答案的人。
答案 6 :(得分:0)
尽可能少地使用函数工具以实现最佳重用是一个好主意。例如:
function doChores(){
//actually wash the dishes
//actually walk the dog.
}
现在让我们说它正在下雨,我只想洗碗,因为洗碗是在doChores实施的,我不能不遛狗就叫它。以下是应该如何做的:
function doChores(){
walkTheDog();
washTheDishes();
}
功能walkTheDog实现了遛狗和洗涤.Dishes实现洗碗,因此可以被称为。
您遇到的问题是将变量传递给函数链时。我通常将一个参数传递给一个函数,该参数包含一个带有所需参数的对象。每个函数都可以读取或改变它们所关注的传递对象的成员。如果以后您需要添加更多参数,那么您不需要更改功能的签名(例如function(arg1, arg2, newArg)
),您将始终拥有function(args)
有关参数传递的更多信息可以在这里找到:https://stackoverflow.com/a/16063711/1641941在Passing(构造函数)参数下