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

12

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

(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. 我们不能拥有一个对子类可访问但对外部公共(即protected)不可访问的字段。有什么方法可以实现这一点吗?

  2. 如果不行,也就是说,如果我们想让parentPrivate可访问,我们需要将其设置为public。那么用户将能够修改它!

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

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

4个回答

38

只有两种方法可以将JavaScript引入HTML:

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

我知道这很明显,但我们需要这个共同的基础来进行下一步。 ;)

JavaScript没有能力将其他JavaScript文件"导入"到自己中。所有的"导入"都在HTML中完成。你可以用几种方式做到这一点:

  • Link each one individually into the HMTL
  • Dynamically link them in through some JavaScript

    var script = document.createElement("script");
    script.src = "all.js";
    document.documentElement.firstChild.appendChild(script);
    
  • Library like RequireJS. RequireJS uses Asynchronous Module Definition (AMD) API. It is the JavaScript mechanism for defining modules such that the module and its dependencies can be asynchronously loaded.

考虑把JavaScript分成单独的文件有以下几个原因:

  • 可维护性 - 每次只需要处理一个部分会更容易
  • 可读性 - 如果所有内容都在一个大文件中,很难看清楚哪个是哪个
  • 分工合作 - 多个开发人员可以在多个文件上工作,而不是只在一个大文件上工作
  • 重用 - 所有函数都可以拆分为高内聚模块

将JavaScript文件分开不会使事情变得私有,闭包使事情变得私有。

现在,考虑到最后一天当一切准备好进入生产时,最好的做法是通过将所有内容组合成一个文件来优化您的JavaScript,这样用户只需下载一个文件即可。


处理JavaScript中的私有变量时,您最终需要访问它们。
- 公共函数 - 可以更改。 - 特权函数 - 可以访问私有变量的公共函数。 - 但是,如果该函数在实例中,则只能在每个对象中进行更改。
让我用一些代码来说明。

module-test.html and main.js (merged first.js, second.js, and main.js for easier testing)

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不应被修改且其非常关键。 - Boyang
我们是在讨论两个独立的JS文件还是一个文件中的所有内容?如果是分开的,你会使用RequireJS吗?一旦这一点明确了,我们就可以尝试回答另一个问题。 - Joshua Wilson
假设我们使用RequireJS,则需要将相关的编程文件分为两个不同的文件。我已经编辑了这个问题。 - Boyang
好的。现在让我问一下,我想了解"We can't have"的含义。是1)“我们无法弄清楚如何拥有一个对子类可访问但对外部公众不可访问的字段。”还是2)“我们不想(试图防止)拥有一个对子类可访问但对外部公众不可访问的字段。”你是说选项1还是2?还是其他什么? - Joshua Wilson
实例模式非常有用。谢谢! - Boyang
显示剩余2条评论

3

在JavaScript中,可以通过将受保护变量作为依赖项传递来实现。子类必须在父类内部创建,因为只有在那里才能访问受保护的变量。示例 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是一个对象时才有效。如果它是一个字符串/整数,它将被传递值并且从子类中进行的更改将不会从父类的副本中可见。

简短的回答,但正是我要找的。通过这种方式传递的变量必须是一个对象,这样它就是被传递的引用。谢谢! - Boyang
@Eru 我尝试了你的示例,但是我只得到了 Uncaught TypeError: Cannot call method 'logProtectedVar' of null。我错过了什么吗?它对你有效吗? - Joshua Wilson
啊,我犯了一个错误。SubClassInstance: null, 应该是 SubClassInstance: App.SubClass(protectedState), - actual_kangaroo
@Eru 我尝试了更新,但是出现了“Uncaught TypeError: Object #<Object> has no method 'SubClass'”的错误。另外,如果你不在回复中 @ 我,我将不会收到通知。 - Joshua Wilson
啊,对不起 Josh!我忘记初始化 App App = window.App || {}; 我现在在 jsFiddle 上测试我的代码 :) http://jsfiddle.net/cwf9C/3/ - actual_kangaroo
显示剩余2条评论

3

1

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

RequireJS 解决了模块化问题,但并未解决数据保护问题。或者说... 无论尝试保护数据存在什么问题,以及保护数据的任何解决方案,这些问题和解决方案都不会因为 RequireJS 的存在而改变

RequireJS 实现了所有机制来指定模块之间的依赖关系,仅在需要时加载这些依赖项,避免重新加载已经加载过的内容,避免加载根本不需要的内容,快速更改模块位置,具有冗余等功能。

部署后,如果发现 RequireJS 过于笨重,则可以使用 almond 库代替。

我们不能拥有一个对子元素可访问但对外部公开不可见的字段(即受保护的)。有办法实现吗?

如果您想要模块化(即您希望子元素与父元素分开编码),我认为在JavaScript中这是不可能的。可以让子元素和父元素在同一个闭包中运行,但这将不是模块化。无论是否使用RequireJS都是如此。

否则,也就是说,如果我们希望parentPrivate可访问,则需要将其公开。那么用户就可以修改它了!

如果您想防止对parentPrivate进行赋值,则可以在包含parentPrivate的命名空间上使用{{link1: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。解决方案相同,问题也相同等。

您说得对,数据保护和模块化并不是同一个概念。但是,它们之间存在联系,因为如果我们不进行模块化(即将所有代码都放在一个文件中),我们可以通过使用模块模式并将变量设置为私有来实现数据保护。现在我想将代码进行模块化,并将其拆分为多个文件。我不知道如何在模块化的情况下实现相同的数据保护。 - Boyang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接