2011-02-22 104 views
13

我正在启动一个新项目,并且正在审查我的最佳实践以尝试防止出现任何问题,并且查看我已经使用了哪些不良习惯。Javascript模块模式和子模块初始化模式

我对使用模块/子模块模式在Javascript中处理初始化序列的方式并不满意。

比方说,我的代码最终与全球范围内类似

FOO.init() 
FOO.module1.init() 
FOO.module2.init() 
FOO.module3.init() 
FOO.module4.init() 

我基本上做的(错误检查和细节omittied为了简洁):

var FOO = (function (me) { 
    me.init = function() { 
     for (var i in me.inits) { 
      me.inits[i](); 
     } 
    } 

    return me; 
}(FOO || {})); 

var FOO = (function (parent) { 
    var me = parent.module1 = parent.module1 || {}; 

    me.init = function() { 
    } 

    parent.inits.push(me.init); 

    return parent; 
}(FOO || {})); 

$(document).ready(FOO.init); 

进行初始化。

我知道我之前已经阅读过这篇文章,但是我现在无法提供正确的搜索条件来查找文章。有没有一个经过深思熟虑和测试的模式来处理像这样的坐标初始化?

谢谢。

编辑:在重新阅读本文后,我认为有一点上下文会通知答案。

在我的情况下,每个模块/子模块都在它自己的文件中。基本模块定义站点的基本功能,子模块启用不同的功能。例如,子模块可以在搜索框上连线自动完成,而另一个模块可以将静态标题图像变成旋转横幅。子模块由CMS启用/禁用,所以我确实希望在基本模块内部显式调用离开,以便CMS可以管理所有内容。我还认为有CMS的具体方法来完成这一点,但我寻找一个通用的Javascript模式来做到这一点,以提供可能使用不同CMS的项目之间的一致性和可重复性。

回答

18

我个人有一个不同的编码风格。这是其中之一。另一种是基本上backbone.js

var myProgram = (function() { 
    var someGlobal, someGlobal2; 

    var subModule1 = (function() { 
     ...  

     var init = function() { 

     }; 

     ... 

     init(); 

     return { 
      "someMethod": someMethod, 
      ... 
     }; 
    }()); 

    var OtherSubModule = (function() { 
     ... 
     var init = function(param) { ... }; 
     ... 
     return { 
      "init": init, 
      ... 
     }; 
    }()); 

    var init = function(param) { 
     ... 

     OtherSubModule.init({ 
      "foo": param.foo, 
      "bar": param.bar, 
      ... 
     }); 
    }; 


    return { 
     "init": init, 
     "somePublic": OtherSubModule.foobar, 
     ... 
    } 
}()); 

使用的样式模仿取决于我是否需要一个公共API提供给其他用户,其中骨干确实好了很多。我倾向于使用由init函数驱动的模块进行初始配置,其余部分完全由事件驱动。

[编辑]

鉴于编辑的问题,我对此有不同的模式。每个文件在我的情况下,它是$.FooBar.plugins

(function() { 

    var foo = function() { ... }; 

    var bar = (function() { ... }()); 

    myNamespace.plugins["MyPlugin"] = function() { 

     ... do stuff 
     ... bind to evevnts 
    }; 

}()); 

然后我们使用引导捆扎机,这是像这样定义了一些对象上的功能:

(function() { 

    var needed = function() { 
     // Feature detection 
    }; 

    var load = function() { ... }; 

    var getOptions = function() { 
     // Call something on a page by page basis. 
    }; 

    for (var plugin in pluginList) { 
     if (needed(plugin)) { 
       load(plugin, function() { 
        // get page specific options 
        var options = getOptions(); 
        // run plugin 
        myNameSpace.plugins[plugin](options); 
        // If all have been loaded trigger ready handlers 
        if (pluginCurrentCount == pluginCount) { 
         readyTrigger(); 
        } 
       }); 
       pluginCount++; 
     } 
    } 

    // start loading plugins after all have been counted 
    load.startLoading(); 

    var readyTrigger = function() { 
     // Run all ready handlers 
    } 

    // Implement your own DOM ready function to run when all plugins 
    // have loaded. 
    myNameSpace.ready = function(handler) { 
     if (isReady) { 
      handler(); 
     } else { 
      readyList.push(handler); 
     } 

    }; 
}()); 

这是一个很大的差距和伪代码,但你的应该明白。如果它不是obvouis觉得质疑它。

然后在页面上,我们有这样的事情

<html> 
<head> 
    <script type="text/javascript"> 

    var pageSpecific = { 
     "pluginName": { 
       "cssClass": "foobar", 
       "submitOnEnter": false, 
       ... 
     }, 
     ... 
    }; 

    </script> 
    <script src="bootstrapper.js" /> 
    ... 
</head> 
<body> 
    ... 
</body> 
</html> 
+0

谢谢。我的真实代码更接近你的第二个例子,这让我对我的方法感觉更好。你的例子也给了我一些改进的想法。 – mpdonadio 2011-02-23 01:15:22

0

我在一个委托pub/sub模式可能对你似乎在这里说明问题的工作工作。我认为我们的目标是最终不会重复我们已经在我们的父对象中编写的sub-objects中的代码,除非该代码完全针对该模块。一般来说,我认为我们应该摆脱这种想法,即我们必须复制其他语言中存在的“经典继承”式编程风格,并简单地利用JavaScript本身具备的功能。 “继承”可以更多地涉及在执行上下文中调用属性和方法(类似于我们如何使用.call().apply()),并且不需要生成一个庞大的代码库,其中包含了“扩展”其他模块的大量代码库,无论如何,它们都是完全孤立的单个物体。

我会很快把这个在一个更大的项目中使用,并看到了很多代码(警告:YES,在对象上__proto__set属性)打交道时,它是如何工作

// Global automatic reference counting 
var referenceCount = 0; 

// Core constructor (a factory function for objects) 
function Class(obj) { 

    // Represents `this` (the invoking object) 
    var This = {}; 

    // Allow the inheritance of an object or object properties when `this` is defined 
    for(var k in obj) 
     This[k] = obj[k]; 

    // Basic `setter` method for `this` 
    This.publish = function(obj) { 
     for(var k in obj) 
      this[k] = obj[k]; 
    }; 

    // Basic `unsetter` method for `this` 
    This.unpublish = function(obj) { 
     if(obj in this) 
      delete this[obj]; 
     else 
      for(var k in obj) 
       if(k in this) 
        delete this[k]; 
    }; 

    // Allow `this` to subscribe to the updates of another object 
    This.subscribe = function(obj) { 
     this.__proto__ = obj; 
    }; 

    // Allow `this` to unsubscribe from the updates of another object 
    This.unsubscribe = function(obj) { 
     this.subscribe({}.__proto__); 
    }; 

    // Allow `this` to permanently consume a property from another object 
    This.plagiarize = function(obj) {  
     var key = Object.keys(obj); 
     var v, i = key.length; 
     while(i--) { 
      v = key[i]; 
      this[v] = obj[v][v]; 
     } 
    }; 

    // Apply a unique identifier which corresponds with the global reference counter (and increment it) 
    This.publish({ id: ++referenceCount }); 

    // return to `this` a newly constructed object 
    return This; 
} 

这里的想法是,你为一种类型的对象编写一个API(或模块,如果你喜欢)。例如,您的模块可能是一个“视图API”,其中包含视图对象可能关心的所有方法。你只写一次这些方法,而不再一次。通过在这些方法中编写代码,就好像该模块是任何未来调用对象(使用thisthis的一次定义的替身),您可以避免重写或覆盖该行后面的属性和方法。因此,对于任何未来可能对其功能感兴趣的具体对象(例如视图),此API实际上成为中心delegate(或者,如果需要,您可以将其称为abstract class)。我们已经在作为数据代理的服务器(大多数Web API)上编写了集中的API;为什么我们不应该将这种相同的理念延伸到客户端以更好地组织我们的观点,客户端模型和其他对象?

这个代表模式和传统模块模式之间的主要区别在于,对于这种模式,通常最终会有一个模块看起来很长,看起来像一个API,它的方法可以产生非常不同的结果,具体取决于调用对象(以及执行上下文),而不是具有许多逻辑,方法和唯一属性最终导致它们重复彼此代码的半长模块。有了这种模式,你可能会看到很少的逻辑,你再也不用再使用var self = this - 这就是使它成为抽象委托而不是具体模块的原因。像Backbone这样的框架几乎可以实现这一点,但他们最终没有这样做。 Backbone是一个“对象继承”和模块化代码组织的框架 - 这很好 - 但我认为它增加的最大价值实际上是让人们同意“某种标准”的事实(但我可以说相同约CoffeeScript; eww ...)。

这种模式的最后一个方面涉及到你的第一个例子。通过在核心中包含基本访问方法和功能,我们可以真正创建与父母完全同步的对象,并且能够根据需要异想天开地切断他们的关系(如果需要)。我已经使用prototypes来完成这个任务,但是你也可以考虑另一种方法(使用构造函数)。我认为这会产生一个更真实的版本pub/sub,其中发布者对他们的订阅者一无所知,并且订阅者只在他们订阅他们时才知道发布者。

关于这方面的更多信息,我有一个更详细的记录示例,它概述了我创建的要点中的一些可能的用例。当然,变量的名称可能会更改为在您的项目中更有意义的名称 - 我使用这些名称来表达其目的。这里的要点:https://gist.github.com/bennyschmidt/5069513

我有兴趣听到更多关于您所有人都想到的其他模块化设计模式。 :>

+0

我说过我会将这个例子应用到一个项目中:https://github.com/bennyschmidt/tileengine – Benny 2013-03-14 04:12:34