【问题标题】:Module pattern- How to split the code for one module into different js files?模块模式 - 如何将一个模块的代码拆分成不同的 js 文件?
【发布时间】:2013-12-17 04:13:08
【问题描述】:

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

(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. 如果不是,这意味着如果我们想让 eparentPrivate 可访问,我们需要将其公开。然后用户就可以修改了!

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

我不明白 RequireJS 是如何解决这些问题的。有人能解释一下吗?

【问题讨论】:

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


    【解决方案1】:

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

    1. 内联 - <script> some JavaScript </script>
    2. 链接 - <script src='main.js'></script>

    我知道这很明显,但我们需要为接下来的事情达成共识。 ;)

    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 中处理私有变量时,您有时会想要访问它们。

    • 公共功能 - 可以更改
    • Privileged 函数 - 一个 Public 函数,可以访问 Private 变量。
    • 但是,如果函数位于 实例 中,则它只能在每个对象中进行更改

    让我用一些代码来说明。

    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 的模块特性作为模块来设置闭包。这看起来可能更难弄清楚,但从长远来看它可能更有效。

    注意:我还没有机会比较这两个选项,但为了完整起见,我想将它们包括在内。


    您可能会发现以下参考资料很有帮助:

    【讨论】:

    • 假设我有模块A和模块B,模块A想从模块B调用myFunction。但是,如果我将myFunction公开,是不是意味着客户端也可以改变功能(通过window.B.myFunction = ...)?我应该如何解决这个问题?假设 myFunction 不应该被修改并且它很关键
    • 我们谈论的是 2 个单独的 JS 文件还是 1 个文件中的所有内容?如果分开你会使用RequireJS吗?一旦清楚这一点,我们就可以尝试回答另一个问题。
    • 2 个单独的文件,并假设我们使用的是 RequireJS。我已经编辑了问题。
    • 好的。现在让我问清楚,我想理解“我们不能拥有”。 1)“我们无法弄清楚如何拥有一个儿童可以访问但外部公众无法访问的字段。”或 2)“我们不希望(试图阻止)有一个儿童可以访问但外部公众无法访问的字段。”你说的是选项1还是2?还是别的什么?
    • @CharlesW。我想我明白你在问什么并更新了我的答案。
    【解决方案2】:

    听起来你想要的是 Require JS。我现在几乎在所有构建中都使用它。或者,您可以查看显示模块模式作为后备,但对于您所追求的,听起来 Require 更合适。

    http://requirejs.org

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

    【讨论】:

      【解决方案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
      

      子类.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 是一个对象时才有效。如果它是一个字符串/整数,它将按值传递,并且在子类中所做的更改不会从父副本中可见。

      【讨论】:

      • 简短的回答,但正是我正在寻找的。但是,以这种方式传递的变量必须是一个对象,因此它是传递的引用。谢谢!
      • @Eru 我试过你的例子,我只得到Uncaught TypeError: Cannot call method 'logProtectedVar' of null。我错过了什么吗?它对你有用吗?
      • 啊,我搞错了。 SubClassInstance: null, 应该是 SubClassInstance: App.SubClass(protectedState),
      • @Eru 我尝试了更新,我得到了Uncaught TypeError: Object #&lt;Object&gt; has no method 'SubClass'。另外,如果你不@我,我不会收到你回复的通知。
      • 啊对不起乔希!我忘了初始化 App App = window.App || {}; 我现在在 jsFiddle 中测试了我的代码 :) jsfiddle.net/cwf9C/3
      【解决方案4】:

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

      RequireJS 解决了模块化问题,而不是数据保护问题。或者换一种说法……无论尝试保护数据存在什么问题,也存在任何保护数据的解决方案,无论有没有 RequireJS,这些问题和解决方案都是一样的

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

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

      我们不能有一个儿童可以访问但外部公众不能访问的字段(即受保护的)。有什么方法可以实现吗?

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

      如果不是,这意味着如果我们希望 eparentPrivate 可以访问,我们需要将其公开。然后用户就可以修改它了!

      如果您想阻止分配给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,这都是正确的。解决方案是一样的,问题是一样的,等等。

      【讨论】:

      • 你说得对,指出数据保护和模块化是不一样的。但是,它们是相关的,因为如果我们不进行模块化(即将所有代码放在一个文件中),我们可以通过使用模块模式并使变量私有化来实现数据保护。现在想实现模块化,把代码拆分成多个文件。不知道如何在模块化下实现同样的数据保护。
      猜你喜欢
      • 1970-01-01
      • 2021-01-23
      • 2017-01-02
      • 2019-05-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-02
      • 1970-01-01
      相关资源
      最近更新 更多