可以将额外的数据注入模块的范围吗?或者:managing/exposing 模块作用域状态 from/to 外部的可能性是什么?

Is it ok to inject additional data into a module's scope? Or: What are the possibilities of managing/exposing module scoped state from/to the outside?

我正在使用揭示模块模式设计 Javascript 模块。在我的 html 中,我有 onmouseover="Test.OnLoad()"。完成后我需要回调。我用 Test.Callback 设置回调。有一个更好的方法吗?我基本上需要编写很多 JavaScript 模块,并希望将功能封装起来。谢谢!

const Test = (function () {
  const items = [];
  let callBack;

  function handleOnLoad() {
    items.push({ name: "Test" });
    callBack(items);
  }
  function setCallBack(cb) {
    callBack = cb;
  }

  return{
    OnLoad: handleOnLoad,
    CallBack: setCallBack
  };
}());

Test.CallBack(function(items) {
  console.log(`Item 0: ${ items[0].name }, items.length: ${ items.length }`);
});
body { margin: 0; }
.test { display: inline-block; }
.test:hover { cursor: pointer; }
.as-console-wrapper { min-height: 88%!important; }
<div class="test" onmouseover="Test.OnLoad()">mouseover test</div>

OP 的方法是通过 items 访问实现可自定义回调目标的一种方法,同时保持 items 完全封装在其模块范围内。

另一种方法可能是 getter,它将 (甚至可能是不可变的) items 副本公开到 public。任何与回调相关的实现(如果只是为了 items 封装)就没有必要了。

为了不意外地alter/mutate封装数据,大部分情况下不传递封装引用而是传递此类数据的浅表副本就已经足够了。有时,如果 appropriate/available,甚至可能会通过完整的 clone.

当然也必须更改 mouseover 处理...

const Test = (function () {
  const items = [];

  function handleOnLoad() {
    items.push({ name: `Test_${ items.length + 1 }` });
  }
  function getItems() {
    return (typeof structuredClone === 'function')
      && structuredClone(items) // a full `items` clone.
      || [...items];  // not the `items` reference ...
                      // but just a shallow copy of it.
  }

  return{
    OnLoad: handleOnLoad,
    getItems,
  };
}());

function logLastItemName(items) {
  console.log(`last item name: ${ items.at(-1).name }, items.length: ${ items.length }`);
}

function init() {
  document
    .querySelector('.test.event-listener')
    .addEventListener('mouseover', (/*evt*/) => {
      Test.OnLoad();
      logLastItemName(Test.getItems());  
    });
}
init();
body { margin: 0; }
.test { display: inline-block; margin-right: 20px; }
.test:hover { cursor: pointer; }
.as-console-wrapper { min-height: 88%!important; }
<div
  class="test inline-script"
  onmouseover="Test.OnLoad(); logLastItemName(Test.getItems());"
>inline-script mouseover test</div>

<div class="test event-listener">event-listener mouseover test</div>

让模块完全具有创建自定义回调的单个 public 函数是另一种方法。

也许它甚至是最受欢迎的解决方案,因为既不需要向模块中注入任何数据,也不必为受控的 items 访问实施 getter 函数。

creator 函数接受一个参数,即稍后传递给 items 引用的自定义函数,以及 returns 包裹模块和自定义处理程序部分的函数。

另一个优点是可以根据需要创建尽可能多的自定义但与模块或 items 相关的处理程序。

const Test = (function () {
  const items = [];

  function writeItem() {
    items.push({ name: `Test_${ items.length + 1 }` });
  }

  function createLoadHandler(customCallback) {
    return function handleLoad (/*evt*/) {

      writeItem();
      customCallback(items);
    }
  }

  return{
    createLoadHandler
  };
}());

function logLastItemName(items) {
  console.log(`last item name: ${ items.at(-1).name }, items.length: ${ items.length }`);
}
const customLoadHandler = Test.createLoadHandler(logLastItemName);

function init() {
  document
    .querySelector('.test.event-listener')
    .addEventListener('mouseover', customLoadHandler);
}
init();
body { margin: 0; }
.test { display: inline-block; margin-right: 20px; }
.test:hover { cursor: pointer; }
.as-console-wrapper { min-height: 88%!important; }
<div
  class="test inline-script"
  onmouseover="customLoadHandler();"
>inline-script mouseover test</div>

<div class="test event-listener">event-listener mouseover test</div>