模块模式 - 如何将一个模块的代码拆分成不同的js文件?

时间:2013-12-01 11:25:43

标签: javascript requirejs module-pattern revealing-module-pattern

对于模块模式,我正在做类似的事情:

(function(namespace) {
    // tons of code
    // blabla
})(window.myGlobalNamespace);

如何拆分代码?我可以想到几种方法,比如使用名称空间的层次结构,或者通过window.myGlobalNamespace.additionalFunc = function () {//blabla}扩展对象。还有什么其他方法?优缺点都有什么?哪一个被认为是更好的做法?

这两个答案都提出了RequireJS。能否解释一下RequireJS如何解决这些问题:

first.js:

(function(context) {
    var parentPrivate = 'parentPrivate';
})(window.myGlobalNamespace);

second.js:

(function(context) {
    this.childFunction = console.log('trying to access parent private field: ' + parentPriavte);
}(window.myGlobalNamespace.subNamspace);

main.js:

window.myGlobalNamespace.subNamspace.childFunction(); // doesn't work

人们可以做到

window.myGlobalNamespace.subNamspace.childFunction = function() {alert("a new function");}

改变我的代码行为!

这里有两个问题:

  1. 我们不能拥有儿童可以访问的字段,但不能访问外部公众(即受保护的)。有没有办法实现这个目标?

  2. 如果没有,意味着如果我们想要访问,我们需要将其公之于众。然后用户就可以修改它了!

  3. 此外,所有公共功能都可以更改和替换。我不希望这种情况发生。

    我不知道RequireJS如何解决这些问题。有人可以解释一下吗?

4 个答案:

答案 0 :(得分:38)

只有两种方法可以将JavaScript转换为HTML:

  1. 内联 - <script> some JavaScript </script>
  2. 链接 - <script src='main.js'></script>
  3. 我知道这是显而易见的,但我们需要接下来的共同点。 ;)

    JavaScript无法将其他JavaScript文件“导入”其自身。所有“导入”都在HTML中完成。您可以通过以下几种方式实现这一目标:

    • 各自单独链接到HMTL
    • 通过一些JavaScript动态链接

      var script = document.createElement("script");
      script.src = "all.js";
      document.documentElement.firstChild.appendChild(script);
      
    • 图书馆,如RequireJS。 RequireJS使用Asynchronous Module Definition (AMD) API。它是用于定义模块的JavaScript机制,以便可以异步加载模块及其依赖项。

    考虑将JavaScript分成单独文件的原因是重要的。

    • 可维护性 - 一次处理一件事变得更容易
    • 可读性 - 如果一切都在一个大文件中,很难看出是什么
    • 分工 - 让多个开发人员处理多个文件而不是一个大文件更容易
    • 重复使用 - 您的所有功能都可以分解为highly cohesive个模块

    单独的JavaScript文件不要使事情私有,闭包会使事情变得私密。

    现在,考虑到一天结束时所有内容都已准备就绪,您可以做的最好的事情是Optimize将JavaScript全部合并到一个文件中,以便用户只有一个文件可供下载。


    在JavaScript中处理私有变量时,您会在某些时候想要访问它们。

    • 公开功能 - 可以更改
    • 特权功能 - 可以访问私有变量的公共功能。
    • 但是,如果该功能位于实例中,则只能在每个对象中更改

    让我用一些代码来说明。

    module-test.html和main.js(合并first.js,second.js和main.js以便于测试)

    var MODULE = (function () {
    	//Private variables
    	var privateParent,
    	    app;
    	
    	privateParent = 'parentPrivate';
    	
    	return app = {
    		//Privileged method
    		getPrivateParent: function() {
    			return privateParent;
    		}
    	};
    }());
    
    MODULE.sub = (function (parentApp) {
    	//Private variables
    	var childMessage,
    	    Constr;
    	
    	childMessage = ' - trying to access parent private field: ' + parentApp.getPrivateParent();  //prints parentPrivate
    
    	Constr = function () {
    		this.childF = this.childFunction();
    	};
    	
    	//Constructor
    	Constr.prototype = {
    		constructor: MODULE.sub,
    		version: "1.0",
    		childFunction: function () {
    			$("#testing-div").append(childMessage + "</br>");
    		}
    	};
    	return Constr;
    	
    }(MODULE));
    	
    //We could just as easily print to the console, but the 'append' allows us to display the results on the page.
    
    $("#testing-div").append("This first part shows what <b>does not work</b>; everything is 'undefined'. " + "</br>");
    $("#testing-div").append("You are unable to access the var or func directly. " + "</br>");
    $("#testing-div").append("MODULE.privateParent = " + MODULE.privateParent + "</br>");
    $("#testing-div").append("MODULE.app = " + MODULE.app + "</br>");
    $("#testing-div").append("MODULE.sub.childMessage = " + MODULE.sub.childMessage + "</br>");
    $("#testing-div").append("MODULE.sub.Constr = " + MODULE.sub.Constr + "</br>");
    $("#testing-div").append("MODULE.sub.childFunction = " + MODULE.sub.childFunction + "</br>");
    $("#testing-div").append("END lesson. You must access childFunction() through the <b>new</b> operator." + "</br>");
    $("#testing-div").append("----------------------------------------------------" + "</br>");
    	
    $("#testing-div").append("Let's see if making an instance of the Object works" + "</br>");
    var test = new MODULE.sub();
    test.childFunction(); //run the method
    $("#testing-div").append("Looks like it did!!!!" + "</br>");
    $("#testing-div").append("----------------------------------------------------" + "</br>");
    	
    $("#testing-div").append("Now let's try to change the childFunction() ?" + "</br>");
    test.childFunction = function() {$("#testing-div").append(" - This is a new function." + "</br>");}
    test.childFunction(); // altered version
    $("#testing-div").append("Looks like it was changed. :(" + "</br>");
    $("#testing-div").append("----------------------------------------------------" + "</br>");
    $("#testing-div").append("Does it stay changed?" + "</br>");
    var test2 = new MODULE.sub();
    test2.childFunction(); // doesn't work
    $("#testing-div").append("NO, it was only Overriden in the 'test' Object.  It did not effect all the other new objects. :)" + "</br>");
    $("#testing-div").append("----------------------------------------------------" + "</br>");
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="ISO-8859-1">
    <title>Module Test</title>
    <!-- 	<script data-main="scripts/main" src="scripts/require.js"></script> -->
    </head>
    <body>
        <h1>This is a test for separate Modules and Private variables.</h1>
        <div id="testing-div">
        </div>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
        <script src="main.js"></script>
    </body>
    </html>
    ---

    如果您想使用RequireJS来完成上述任务,您可以。 RequireJS使用您和我已经使用的模块模式。如果你想分离文件,那么有两种方法可以做到这一点。

    1. 正常 - 只需将您的JS文件设置为使用RequireJS,只需稍加修改即可放入上述模块。
    2. 杠杆化 - 使用RequireJS的Module特性作为模块来设置闭包。这看起来可能更难以弄清楚,但从长远来看它可能更有效。
    3. 注意:我还没有机会比较这两个选项,但希望将它们包含在内以便完整。


      您可能会发现以下参考资料有用:

答案 1 :(得分:3)

听起来你需要的是JS。我现在在几乎所有的构建中都使用它。或者你可以看看揭示模块模式作为后备,但对于你所听到的,听起来像需要更合适。

http://requirejs.org

好读:http://net.tutsplus.com/tutorials/javascript-ajax/principles-of-maintainable-javascript/

答案 2 :(得分:3)

javascript中的受保护变量可以通过传入受保护的变量as a dependency来实现。必须在父类中创建子类,因为它只能访问受保护的变量。 Example jsFiddle

App = window.App || {};
App.ParentClass = (function(){

   var protectedState = {
      protectedVar: 'wow such protection'
   }

   return{
      SubClassInstance: App.SubClass(protectedState), //make the subclass accessible from outside
   }
})(); //closure gives us privacy

SubClass.js

App = window.App || {};
App.SubClass = function(protectedState){

    return {
        logProtectedVar : function(){
            //can access protectedState of parent
            console.log(protectedState.protectedVar);
        }
    }
}// I require protected state to work

main.js

// protected variable is accessible from parent and subclass and nowhere else

App.ParentClass.SubClassInstance.logProtectedVar();
// 'wow such protection' 

注意:正如Charles W.所提到的,这种模式仅在protectedState是一个对象时才有效。如果它是一个字符串/ int,它将按值传递,并且子类中所做的更改将不会从父项副本中显示。

答案 3 :(得分:1)

模块化(拆分代码)与数据保护(隐藏数据)不同。

RequireJS解决了模块化问题,而不是数据保护问题。或者换句话说......无论存在什么问题,试图保护数据以及存在哪些解决方案来保护数据,这些问题和解决方案在有或没有RequireJS 时是相同的。

RequireJS实现所有机制来指定模块之间的依赖关系,根据需要加载,以避免重新加载已经加载的东西,避免加载根本不需要的东西,快速改变模块的位置,有冗余等。

部署后如果发现RequireJS太重了,可以使用almond库代替。

  

我们不能拥有儿童可以访问的字段,但不能访问外部公众(即受保护的)。有没有办法实现这个目标?

如果你想要模块化(即你希望孩子与父母分开编码),我认为这在JavaScript中是不可能的。可以让子级和父级在相同的闭包中运行,但这不会是模块化。无论是否有RequireJS都是如此。

  

如果没有,这意味着如果我们想要访问,我们需要将其公之于众。然后用户就可以修改它了!

如果您想阻止分配到parentPrivate,可以在包含parentPrivate的命名空间上使用Object.freeze()

但是,我不知道各种浏览器支持它的程度如何。如果parentPrivate中的内容本身是一个对象而不是一个原始值,那么如果您不希望代码的客户端对其进行修改,则还需要将其冻结。一旦一个对象被冻结,它就会被所有人冻结,因此拥有对象的模块没有得到特殊处理来修改它。冻结不会隐藏任何东西。

或者您可以使用此RequireJS示例中的setter和getter:

define(function () {
    'use strict';

    var writable = "initial value";

    var namespace = {
        get unwritable() { return writable; },
        doSomething: function () { writable = "changed value"; }

    };

    return namespace;

});

如果模块导入为parent,则无法写入parent.unwritable,但模块本身仍可通过写入writable来更改返回的值。请注意,如果getter返回的返回值是对象而不是原始值,则调用者可以修改此对象

同样,无论您是否使用RequireJS,都是如此。解决方案是相同的,问题是相同的等等。