创建多个 javascript 模块的实例

Creating an instance of multiple javascript modules

这与 javascript 代码有关。我的应用程序有许多子应用程序,有时在一个页面上使用多次。一个示例是允许用户搜索某些数据并显示搜索结果的应用程序。该应用程序可以在页面的多个位置使用,以搜索不同类型的数据。 每个子应用程序通常包含许多 javascript 个模块,每个模块都在一个单独的文件中。我经历过许多不同的模块模式,试图创建多个 modules/files 的单独实例,但没有成功。网上有很多关于如何创建对象的多个实例、使用工厂模式等的建议,但我无法使它在我的应用程序中使用名称 space 结构和模块模式。请参阅下面的示例。

问题是如何创建 SubAppA 的多个独立实例,包括其所有子模块。

(New file)

var MainApp     = MainApp || {};
MainApp.SubAppA = MainApp.SubAppA || {};

MainApp.SubAppA.Config = (function () {
    
    function A () { ... };
    function B () { ... };
    
    return {
        A : A,
        B : B
    }
})();

(New file)

var MainApp     = MainApp || {};
MainApp.SubAppA = MainApp.SubAppA || {};

MainApp.SubAppA.GetData = (function () {
    
    function A () { ... };
    function B () { ... };
    
    return {
        A : A,
        B : B
    }
})();

(New file)

var MainApp     = MainApp || {};
MainApp.SubAppA = MainApp.SubAppA || {};

MainApp.SubAppA.DisplayData = (function () {
    
    etc.....

非常感谢

--- MikeM 提出的解决方案后的附加信息 -----

谢谢 MikeM,您的回答让我有了更好的理解,但是当尝试使用我现有的名称 space 结构来实现它时,我无法让模块相互通信。我尝试了以下方法:

//Solution $S.AS - New file
var $S  = $S || {};
$S.AS   = $S.AS || {};

$S.AS.DataStore = function () {
    var _SomeVar    = "Default";
    
    function SetVar (Data) {
        _SomeVar = Data;
    };
    function GetVar () {
        return _SomeVar;
    };
    
    return {
        SetVar : SetVar,
        GetVar : GetVar
    }
};

//Solution $S.AS - New file
var $S  = $S || {};
$S.AS   = $S.AS || {};

$S.AS.ManageData = function () {
    
    function StoreData (Data) {
        console.log($S.AS.DataStore);  //outputs f ()
        //Does now work since DataStore is now a function
        //$S.AS.DataStore.SetVar(Data);  
        $S.AS.DataStore().SetVar(Data);  
    };
    
    function DisplayData () {  
        //Does now work since DataStore is now a function
        //var SomeVar = $S.AS.DataStore.GetVar();  
        //Does not work, still outputs "Default"
        var SomeVar = $S.AS.DataStore().GetVar();
        console.log(SomeVar);
    };
    
    return {
        StoreData : StoreData,
        DisplayData : DisplayData
    }
};

//Solution $S.AS - New file - The contructor function for AS
var MainApp     = MainApp || {};
MainApp.S       = MainApp.S || {};
MainApp.S.AS    = MainApp.S.AS || {};

MainApp.S.AS = function () {
    
    this.DataStore      = $S.AS.DataStore();
    this.ManageData     = $S.AS.ManageData();
    //additional modules
    
};

//Main/Page specific code - creating the different instances
MainApp.S.AS_1 = new MainApp.S.AS();
MainApp.S.AS_2 = new MainApp.S.AS();

//Attemps to store and retrieve data

//Stores AAA in the DataStore module
MainApp.S.AS_1.ManageData.StoreData("AAA");
//Stores BBB in the DataStore module
MainApp.S.AS_2.ManageData.StoreData("BBB");
//Not working, ouputs: "Default" (Desired result is "AAA")
MainApp.S.AS_1.ManageData.DisplayData();
//Not working, ouputs: "Default" (Desired result is "BBB");
MainApp.S.AS_2.ManageData.DisplayData();

我想我明白为什么会输出“默认”(调用是对页面加载时存储的原始变量进行调用)但不知道如何修复它。
对于上下文,我有一个自定义 php 脚本,它连接页面所需的所有 JS 文件,然后将它们作为单个标记添加到页面。我认为这会加快脚本加载速度,特别是因为我的大部分页面都有 50+ JS files/modules。页面的典型名称 space 结构如下所示(但包含更多模块):

MainApp = {
    //Page specific or utility modules
    ModuleA : [ func / module ],
    ModuleB : [ func / module ],
    ModuleC : [ func / module ],
    ModuleD : [ func / module ],
    //Resulable applications consisting of multiple modules
    SubAppA : {
        ModuleA : [ func / module ],
        ModuleB : [ func / module ],
        ModuleC : [ func / module ],
        ModuleD : [ func / module ],
    },
    SubAppB : {
        ModuleA : [ func / module ],
        ModuleB : [ func / module ],
        ModuleC : [ func / module ],
        ModuleD : [ func / module ],
    }
}

我希望我能以某种方式保留这个结构以避免模块名称冲突的风险。我很高兴改变模块本身的结构(例如,从 IIFE 到其他东西)以获得问题的解决方案。 谢谢

创建“SubAppA 的多个独立实例,包括其所有子模块”的一种方法的简单示例,其中模块在多个文件中定义:

// test.js
import { MainApp } from './mainApp.js';
import { SubApp } from './subApp.js';

MainApp.SubAppA = new SubApp();
console.log(MainApp.SubAppA.Config.A());    // 1

MainApp.SubAppB = new SubApp();
console.log(MainApp.SubAppB.Config.B());    // -1


// subApp.js
import { config } from './config.js';
import { getData } from './getData.js';

export function SubApp() {
  this.Config = config();
  this.GetData = getData();
}


// config.js
export function config() {
  let counter = 0;
  function A() { return ++counter };
  function B() { return --counter };
  return {
    A: A,
    B: B
  }
}

作为单个文件:

const MainApp = {};

function SubApp() {
  this.Config = config();
  this.GetData = getData();
}

function config() {
  let counter = 0;
  function A() { return ++counter };
  function B() { return --counter };
  return {
    A: A,
    B: B
  }
}

function getData() {
  let counter = 0;
  function A() { return ++counter };
  function B() { return --counter };
  return {
    A: A,
    B: B
  }
}

MainApp.SubAppA = new SubApp();
console.log(MainApp.SubAppA.Config.A());    // 1
console.log(MainApp.SubAppA.GetData.A());   // 1
console.log(MainApp.SubAppA.Config.B());    // 0

MainApp.SubAppB = new SubApp();
console.log(MainApp.SubAppB.Config.B());    // -1
console.log(MainApp.SubAppB.GetData.B());   // -1
console.log(MainApp.SubAppB.Config.A());    // 0
       

与您自己的代码的重要区别在于,我已将立即调用函数表达式 (IIFE) 替换为每次创建 SubApp 时都会创建闭包的普通函数。

为响应您的编辑而添加:

为了使您在编辑中添加的代码起作用,您需要确保在创建新的 ManageData 对象时传递对父对象的引用:

$S.AS.ManageData = function (owner) {

    function StoreData (Data) {  
        owner.DataStore.SetVar(Data);  
    };
    
    function DisplayData () {  
        var SomeVar = owner.DataStore.GetVar();
        console.log(SomeVar);
    };
    
    return {
        StoreData : StoreData,
        DisplayData : DisplayData
    }
};

// ...

MainApp.S.AS = function () {    
    this.DataStore      = $S.AS.DataStore();
    this.ManageData     = $S.AS.ManageData(this);  
};

我鼓励您使用 ES 模块来构建代码并避免命名冲突。 可以从 roll-up.js 等模块打包器创建单个文件。

感谢 MikeM,现在可以使用了,非常有帮助!是的,我知道我需要更仔细地研究 ES 模块。在一个完全不同的领域工作了 17 年后,我最近学会了编码以测试一个想法,因此必须走一些捷径......

下面是我的实现步骤,以防对其他不太熟悉 Javascript 模块的人有所帮助。

问题的目的是:

  • 允许一个应用程序的多个实例存在于一个页面上,每个应用程序由多个模块组成
  • 允许模块调用其他模块中的方法
  • 使应用程序的实例化(创建新版本)变得容易

与以下解决方案相关的一些注意事项:

  • 而不是静态调用其他模块中的方法(例如 ReuseApp.App1.Display.DisplayData(Data),每个模块存储新创建的应用程序实例的顶级对象的内部引用(例如 _App.Display.DisplayData(数据).
  • 创建的模块没有立即调用的功能(即没有 IIFE 模式)。
  • 需要一个引用所有必需模块的构造函数。此函数会将新创建的对象 (this) 发送到每个模块,例如this.Config = ReuseApps.App1.Config(这个);
  • 每个模块将此引用作为参数(App)并将其存储在模块(_App)中。 _App会在调用其他模块方法时使用。

分步指南:

步骤 A:使用以下模式创建模块(如果不需要,请忽略多级命名空间):

//Separate file e.g. Config.js
const ReuseApps = ReuseApps || {};
ReuseApps.App1 = ReuseApps.App1 || {};

ReuseApps.App1.Config = function (App) {
   let  _App;  //Used to call other module methods
   let _Settings = {};
   function Init (Settings) {
      _Settings = Settings;
      //Configure app e.g. store element refs, add event handlers etc
      var Data = GetDataFromSomeWhere();
      //Call another module using the _App reference
      _App.Display.DisplayData(Data);
   }
   _App = App;
   Return {
      Init : Init
   }
}

//Separate file e.g. Display.js
const ReuseApps = ReuseApps || {};
ReuseApps.App1 = ReuseApps.App1 || {};

ReuseApps.App1.Display = function (App) {
    let _App;  //Used to call other module methods
    function DisplayData (Data) {
       //Display Data in DOM
   }
   _App = App;
   return {
      DisplayData : DisplayData
   }
}

步骤 B:创建创建应用程序新实例所需的构造函数

//can be in separate file e.g. app1_create.js
function App1Create () {
   this.Config = ReuseApps.App1.Config(this);
   this.Display = ReuseApps.App1.Display(this);
   //etc more modules …
}

步骤 C:在主代码中创建上述应用程序的单独实例

//Create new instance using constructur function
//(Assumes the MainApp namespace exists already)
MainApp.ViewDataList = new App1Create();
//If application needs to be initiated
var Settings = { some settings };
MainApp.ViewDataList.Config.Init(Settings);