如何将变量添加到 pre-defined 函数的范围?

How do you add variables to the scope of a pre-defined function?

标题有点说明了一切。在 JavaScript 中,您如何将变量注入到您无法控制的函数(例如回调)的 inner-scope 中?这通常在各种 JavaScript 框架中完成,其中 Jest 为您提供 describeitbeforeAll 等,所有这些都在 body 回调中Jest 没有明确的知识。 Cucumber 使用共享的 World 实例更干净地做到了这一点,并且还提供了为该 object 提供自定义构造函数的控件,但如果我想添加 logger object到使用我的库的任何东西的执行范围,是将它添加到 global 的唯一答案,还是有办法将值限制在被调用函数及其回调的范围内?

编辑:这是我认为笨拙的解决方案,但它在技术上实现了目标。如果这是公认的解决方案,我会继续使用它,但我认为必须有更多 sophisticated/clean 的方法来做到这一点。

function a(opts, callback) {
  Object.assign(global, opts);
  const assignedKeys = Object.keys(opts).filter(k => Object.prototype.hasOwnProperty.call(global, k));

  for(const k in opts) {
    console.log('%s: %s', k, opts[k]); // val: 5
    callback(); // Can I see it? true
  }

  console.log('Before clean-up', assignedKeys.map(k => `${k}: ${global[k]}`).join(', '));
  // Before clean-up val: 5

  for(const k in opts) {
    delete global[k];
  }

  console.log('After clean-up', assignedKeys.map(k => `${k}: ${global[k]}`).join(', '));
  // After clean-up val: undefined
}

function b() {
  console.log('Can I see it?', !!val);
  // Can I see it? true
}

a({ val: 5 }, b);

可能不是最优雅的,但您可以 toString() 一个函数并利用 Function 构造函数注入变量。

这是您示例的 dumbed-down 版本:

function inject(cb, hw = "Hello, world!") {
  const injection = `const hw = "${hw}";`;
  const fn = new Function(`${injection} return (${cb.toString()})();`);
  const result = fn();
  if(result)
    console.log(result);
  // cb(); <-- error: hw is undefined
}

inject(() => {
  console.log(hw);
});
inject(function() {
  console.log(hw);
}, "Hello, Whosebug!");
inject(function test() {
  console.log(hw);
  return 5;
});

是的,使用全局变量是这方面的标准做法。没有办法改变你不控制的函数的范围,不可能向它“注入”新的局部变量。您唯一可以做的就是将变量放在共享范围内(并假设该函数不会隐藏它们),这通常是全局范围。

像 React 或 Jest 这样的框架可以避免这种做法,因为它们是 编写所有代码的全局框架,因此开发人员知道此功能并且不会得到被“魔法”变量搞糊涂了——而且只有很少的 well-known 个变量。不过,仍然需要告知工具有关这些全局变量的信息。这是一个 trade-off,但有时它会导致更简洁的代码是值得的。

请注意,与您的 a 函数不同,此类全局变量不是动态的而是静态声明的,它们不关心冲突和清理。如果您使用这样的框架,您将知道 up-front 有哪些全局变量,并且不会编写干扰它们的代码。

如果你真的想动态地将任意值“注入”到被调用的函数中,有一个标准技术:使用参数!您调用的函数需要声明参数才能使用它们,但这是正确的干净解决方案,并且会真正将变量限制在您想要的范围内。