创建多个 javascript 模块的实例

问题描述

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

问题是如何创建 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,你的回答让我有了更好的理解,但是当我尝试使用我现有的命名空间结构来实现它时,我无法让模块相互通信。我尝试了以下方法

//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 文件/模块。页面的典型名称空间结构如下所示(但包含更多模块):

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 ],},SubAppB : {
        ModuleA : [ func / module ],}
}

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

解决方法

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

这里是我一步一步的实现,以防它对不太熟悉 Javascript 模块的其他人有所帮助。

问题的目的是:

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

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

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

创建“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) 替换为每次创建子应用程序时都会创建闭包的普通函数。

为响应您的修改而添加:

为了使您在编辑中添加的代码起作用,您需要确保在创建新的 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)创建单个文件。