了解模块化 javascript 模式

Posted

技术标签:

【中文标题】了解模块化 javascript 模式【英文标题】:understanding a modular javascript pattern 【发布时间】:2012-07-22 22:04:47 【问题描述】:

我正在尝试编写“更好”的 javascript

以下是我发现并尝试采用的一种模式。但是,我对它的使用有点困惑。

比如说,我有一个名为“工作”的页面。该页面上的任何 JS 功能都将封装在以下内容中:

window.jobs = (function(jobs, $, undefined)
    return 
        addNew: function()
            // job-adding code
        
    
)(window.jobs|| , jQuery);

$(function()
    $('.add_job').on('click', function(event)
        event.preventDefault();
        window.jobs.addNew();
    );
);

正如您可能推断的那样,我所做的只是用对全局作业对象中的函数的调用替换了本应位于匿名事件处理函数中的所有代码。我不知道为什么这是一件好事,除了它减少了可变碰撞的可能性并使整个事情变得更整洁,但这对我来说已经足够了。

这个——可能相当明显——的问题是:我所有的事件绑定初始化类型的东西仍然坐在我闪亮的新工作对象之外:它应该在哪里?在工作对象内部?在作业对象内的返回对象内?在 init() 函数内部?

我只是想了解一个稳定的基本框架,用于放入简单的功能。我不是在构建 JS 应用程序,我只是想编写比实际更健壮和可维护的代码现在。热烈欢迎任何和所有建议:)

【问题讨论】:

非常感谢您提供的非常有帮助的答案;我知道那里有大量信息,来自 Crockfords、Osmanis 和 Resigs,但是将这些概念与您自己的代码相关地解释是非常有帮助的。我会将 jAndy 的回答标记为已接受,因为虽然我显然无法说明哪个是“正确”的解决方案,但他的回答似乎最好地解释了我应该如何应用我正在采取的方法。 【参考方案1】:

您也可以将您的应用程序分解为任意数量的modules / objects

例如,您可以拥有另一个对象/模块来缓存和定义您的所有 DOM 节点和另一个只处理任何事件的对象/模块。比如:

(function ( win, doc, $, undef ) 
    win.myApp = win.myApp ||  ;

    var eventHandler = 
        onJobClick: function( event ) 
            event.preventDefault();
            myApp.addNew();
        
    ;

    var nodes = (function() 
        var rootNode = $( '.myRootNode' ),
            addJob = rootNode.find( '.add_job' );

        return 
            rootNode: rootNode,
            addJob: addJob
        ;
    ());

    $(function() 
        myApp.nodes.addJob.on( 'click', myApp.handler.onJobClick );
    );

    myApp.nodes = nodes;
    myApp.handler = eventHandler;
( this, this.document, jQuery ));

如何在这个(模块)模式中创建单例并不重要,无论是作为文字、构造函数、Object.create() 还是诸如此类。它需要符合您的要求。

但是您应该尝试创建尽可能多的特定模块/对象。当然,如果将这些单例/模块/对象分成多个 javascript 文件并按需加载它们更有意义,那么在你说刀之前,你就处于模块化编程模式的世界,处理requireJS 和 AMD或 CommonJS 模块。

【讨论】:

这个想法是保持模块(JavaScript 文件)范围隔离。但是我们仍然需要模块之间的通信。这可以通过 CommonJS 方式轻松实现 - 通过 exports 从模块中导出您想要的任何内容,并通过 require 在模块中导入。您可以使用 github.com/dsheiko/cjsc 或 browserify.org 将 CommonJs 模块编译为适合浏览器的单个文件。它甚至在 ES6 中以更明确的方式实现 - wiki.ecmascript.org/doku.php?id=harmony:modules 还有另一种方式。您可以定义一个 Mediator 对象,该对象被注入到每个模块范围中【参考方案2】:

在封装方面,你很好:你甚至可以在 jQuery 闭包中声明addNew,你仍然可以避免全局范围。我认为你得到的更多的是实现接近 MVC 架构的东西。

我喜欢做的事情是创建一个您使用 DOM 元素实例化的对象,该对象负责自己的绑定/提供访问其控件的方法等。

例子:

   // (pretend we're inside a closure already)
   var myObj = function(args)
       this.el = args.el; // just a selector, e.g. #myId
       this.html = args.html;
       this.bindings = args.bindings || ;
   

   myObj.prototype.appendTo = function(elem)
       elem.innerHTML += this.html;
       this.bindControls();
   ;

   myObj.prototype.remove = function()
       $(this.el).remove(); // using jQuery
   ;

   myObj.prototype.bindControls = function()
       for(var i in this.bindings) // event#selector : function
           var boundFunc = function(e) return this.bindings[i].call(this,e); ;
           $(this.el).on(i,boundFunc);
       
   ; 

【讨论】:

这听起来很像 jQuery 插件模式,准确吗?无论如何,看到它被这样分解是非常有用的。谢谢 - 在这里,有一个upvote :)【参考方案3】:

你现在的做法和我一样,我通常在匿名函数本身内创建窗口对象,然后在其中声明(在本例中:jClass = window.jClass)。

(function (jClass, $, undefined)  

    /// <param name="$" type="jQuery" />

    var VERSION      = '1.31';
    UPDATED_DATE = '7/20/2012';

    // Private Namespace Variables
    var _self         = jClass; // internal self-reference
    jClass        = window.jClass; // (fix for intellisense)
    $             = jQuery; // save rights to jQuery (also fixes vsdoc Intellisense)

    // I init my namespace from inside itself
    $(function () 
        jClass.init('branchName');
    );

    jClass.init = function(branch) 
      this._branch = branch;
      this._globalFunctionality( globalDatePicker: true );
      this._jQueryValidateAdditions();

      //put GLOBAL IMAGES to preload in the array
      this._preloadImages( [''] );

      this._log('*******************************************************');
      this._log('jClass Loaded Successfully :: v' + VERSION + ' :: Last Updated: ' + UPDATED_DATE);
      this._log('*******************************************************\n');
;

    jClass._log = function() 
    //NOTE: Global Log (cross browser Console.log - for Testing purposes)
    //ENDNOTE 

    try  console.log.apply(console, arguments);   
    catch (e) 
        try  opera.postError.apply(opera, arguments); 
        catch (e)  /* IE Currently shut OFF : alert(Array.prototype.join.call(arguments, ' '));*/ 
       
;

(window.jClass= window.jClass|| , jQuery));

我让它们像这样完全匿名的原因是,假设在另一个文件中我想为这个 jClass 添加更多功能。我只是创建另一个:

(function jClass, $, undefined) 

jClass.newFunction = function (params) 
    // new stuff here
;

(window.jClass = window.jClass || , jQuery))

如你所见,我更喜欢 object.object 表示法,但你可以使用对象字面量 object : object,这取决于你!

无论哪种方式,通过将所有这些分开,并在没有实际页面逻辑的情况下进行封装,可以更轻松地将其包含在 globalJS 文件中,并且您网站上的每个页面都可以使用它。比如下面这个例子。

jClass._log('log this text for me');

您不想将模型逻辑与您的业务逻辑交织在一起,因此您在正确的道路上将两者分开,并允许您的全局命名空间/类/等更加灵活!

【讨论】:

【参考方案4】:

您可以在此处找到有关模块模式的全面研究:http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html 它涵盖了块范围模块方法的所有方面。然而在实践中你会有相当多的文件来封装你的代码,所以问题是如何组合它们的属性。 AMD...每个模块加载产生的多个 HTTP 请求会损害您的页面响应时间。因此,您可以将 CommonJS 编译为适合在浏览器中使用的单个 JavaScript 文件。看看有多简单http://dsheiko.github.io/cjsc/

【讨论】:

以上是关于了解模块化 javascript 模式的主要内容,如果未能解决你的问题,请参考以下文章

前端面试基本---JavaScript严格模式

javasc多文件的作用域&模块中的作用域

技术2015年需要了解的前端框架和语言

加载模块脚本失败:服务器以“text/plain”的非 JavaScript MIME 类型响应

加载模块脚本失败:服务器以非 JavaScript MIME 类型“”响应。执行严格的 MIME 类型检查

JavaScript 基础入门丨第一天学习规划