覆盖 require.js 中的 setTimeout
Posted
技术标签:
【中文标题】覆盖 require.js 中的 setTimeout【英文标题】:override setTimeout in require.js 【发布时间】:2016-04-07 19:48:26 【问题描述】:我们在我们的项目中使用 require.js,我们需要覆盖第 705 行中的 setTimeout,这是我们需要以某种方式忽略/省略的代码setTimeout 根本(我的意思是越过它),如果我在更改版本时在开源代码中显式更改它,代码将会丢失,我应该如何从外部覆盖这个 setTimout 只为 require.js 文件和只要我使用这个库就保留它,是否可以在全球范围内以 优雅 方式在 JS 中做到这一点?
https://github.com/jrburke/requirejs/blob/master/require.js
这是第 705 行
//If still waiting on loads, and the waiting load is something
//other than a plugin resource, or there are still outstanding
//scripts, then just try back later.
if ((!expired || usingPathFallback) && stillLoading)
//Something is still waiting to load. Wait for it, but only
//if a timeout is not already in effect.
if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId)
checkLoadedTimeoutId = setTimeout(function ()
checkLoadedTimeoutId = 0;
checkLoaded();
, 50);
仅供参考,我们这样做的原因是 Chrome: timeouts/interval suspended in background tabs?
【问题讨论】:
这敲响了我所有的警钟,你为什么要这么做?更改全局函数和对象几乎从来没有是个好主意。 @MadaraUchiha - 查看我在 setTimout 中提供的链接,当我们在非活动选项卡上使用 require.js 时(我的意思是在打开新窗口后)每次服务加载都需要一秒钟!!! 但是,它处于非活动标签中,用户没有观看,你为什么关心它需要多长时间? @MadaraUchiha - 我试着长话短说 :) 我们的逻辑是在非活动选项卡中完成的,当打开新窗口时性能变得很差,你知道我是否可以以某种方式从外部以优雅的方式删除 setTimeout ?谢谢! 你会怎么做呢?该设置超时以半递归方式运行,以检查模块是否已加载。如果你只是使用一个普通的循环,你会冻结整个过程,直到所有模块都被加载... 【参考方案1】:实现它的其他方法是对库发出 ajax 请求并在加载库之前对其进行修补,但您需要加载一种发出请求的方法(vanilla js 或 jquery...)
下一个代码是加载 requirejs 并在将其加载到 DOM 之前使用正则表达式对其进行修补的示例。
$(function()
// here you can put the url of your own hosted requirejs or a url from a CDN
var requirejsUrl = 'https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.22/require.js';
function patch(lib)
var toReplace = /if\s*\(\(isBrowser\s*\|\|\s*isWebWorker\)\s*\&\&\s*!checkLoadedTimeoutId\)\s*\([^]|\[^]*\)*\/;
var by = 'if (isBrowser || isWebWorker) checkLoaded();';
return lib.replace(toReplace, by);
$.get(requirejsUrl, function(lib)
var libpatched = patch(lib);
var script=document.createElement('script');
script.innerText=libpatched;
$('body').append(script);
console.log(window.require); // requirejs patched is loaded
);
);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
【讨论】:
此答案中的代码异步加载 RequireJS,这意味着任何依赖于 RequireJS 的代码都必须等待 RequireJS 出现才能运行。告别<script src="path/to/require.js"></script><script>require.config(...)</script>
的简单性(我碰巧使用的一种相当常见的模式)。【参考方案2】:
您已声明您的目标是解决 Chrome 在 setTimeout
上对后台标签执行的限制。我认为这样做不是一个好主意,但如果你必须这样做,那么你绝对应该修补 RequireJS 而不是全局弄乱setTimeout
。你说:
如果我在更改版本时在开源代码中显式更改它,代码将丢失
仅当您不使用明智的方法来执行更改时,这才是正确的。明智地做到这一点是可能的。例如,您可以使用 Gulp 获取安装在 node_modules
中的 require.js
文件(在使用 npm
安装 RequireJS 之后)并在 build
中生成一个补丁文件。然后你在你的应用程序中使用这个补丁文件。这里是gulpfile.js
:
var gulp = require("gulp");
// Bluebird is a good implementation of promises.
var Promise = require("bluebird");
// fs-extra produces a `fs` module with additional functions like
// `ensureDirAsync` which is used below.
var fs = require("fs-extra");
// Make it so that for each the function in fs that is asynchronous
// and takes a callback (e.g. `fs.readFile`), a new function that
// returns promise is created (e.g. `fs.readFileAsync`).
Promise.promisifyAll(fs);
var to_replace =
"if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) \n\
checkLoadedTimeoutId = setTimeout(function () \n\
checkLoadedTimeoutId = 0;\n\
checkLoaded();\n\
, 50);";
var replace_with =
"if (isBrowser || isWebWorker) \n\
checkLoaded();";
gulp.task("default", function ()
// Use `fs.ensureDirAsync` to make sure the build directory
// exists.
return fs.ensureDirAsync("build").then(function ()
return fs.readFileAsync("node_modules/requirejs/require.js")
.then(function (data)
data = data.toString();
// We use the split/join idiom to a) check that we get
// the string to be replaced exactly once and b)
// replace it. First split...
var chunks = data.split(to_replace);
// Here we check that the results of splitting the
// chunk is what we expect.
if (chunks.length < 2)
throw new Error("did not find the pattern");
else if (chunks.length > 2)
throw new Error("found the pattern more than once");
// We found exactly one instance of the text to
// replace, go ahead. So join...
return fs.writeFileAsync("build/require.js",
chunks.join(replace_with));
);
);
);
您需要在运行之前运行npm install gulp fs-extra bluebird requirejs
。无论如何,你可以使用 Gulp,你可以使用 Grunt,或者你可以使用任何其他你想要执行构建的系统。要点是:
你有一个可重现和自动化的方法来修补 RequireJS。 如果你使用 npm
安装新版本的 RequireJS,当你重建你的软件时,补丁会自动应用,只要代码RequireJS 不会以阻止应用补丁的方式更改。如果更改阻止应用补丁,请参阅下一点。
此方法比在运行时覆盖 setTimeout
更健壮。假设 James Burke 决定在较新版本的 RequireJS 中将 checkLoaded
重命名为 checkDone
并重命名相关变量(以便checkLoadedTimeoutId
变为 checkDoneTimeoutId
)。上面的 gulpfile 会在你再次运行时引发异常,因为它找不到要替换的文本。您必须更新要替换的文本和替换,以便补丁与新版本的 RequireJS 一起使用。这样做的好处是您可以提前收到警告,告知您事情已经发生了变化,并且您需要查看补丁。 在游戏后期您不会有意外,也许在您已经向客户交付了新版本的软件之后。
在运行时覆盖setTimeout
的方法只会默默地无法完成它们的工作。他们将寻找包含checkLoadedTimeoutId
的函数,新版本中将不再存在该函数。所以他们只会让 RequireJS 以默认方式运行。失败将是微妙的。 (我已经使用建议的自定义版本 setTimeout
运行 RequireJS,该项目在未优化时会加载超过 50 个模块。我发现使用库存 setTimeout
的 RequireJS 和使用自定义 setTimeout
的 RequireJS 之间没有明显区别。 )
此方法不会减慢每次使用 setTimeout
的速度。 setTimeout
被 RequireJS 以外的其他代码使用。无论您如何削减它,将代码添加到 setTimeout
的自定义替换中,该代码开始在传递给它的每个函数中查找字符串将使 所有 setTimeout
的使用变慢。
【讨论】:
优雅的方法!它不需要编辑文件和更改 setTimeout 本身...只需要在项目中管理 gulp / grunt / other task 的那一刻。【参考方案3】:您可以覆盖 setTimeout
并检查作为回调传递的函数是否包含在 require.js (checkLoadedTimeoutId
) 中用于该函数的变量。如果是,立即调用函数,否则调用原setTimeout
函数。
(function(setTimeoutCopy)
setTimeout = function(fn, timeout)
if (fn.toString().indexOf("checkLoadedTimeoutId") >= 0)
return fn();
else
return setTimeoutCopy.apply(null, arguments);
;
(setTimeout));
请注意,此代码存在多个问题。如果您将包含checkLoadedTimeoutId
的函数传递给setTimeout
,它也会立即执行。另外,如果 require.js 代码被缩小并且变量被重命名,它也不会起作用。
总而言之,没有什么好办法做到这一点。也许尝试找到一种不同的方式来实现你想要的。还要注意,正如宇智波斑所说:
更改全局函数和对象几乎从来没有是个好主意。
【讨论】:
【参考方案4】:如果你真的需要这个...在加载 requirejs 之前尝试添加:
function isTimeoutIgnoredFunction(fn, time)
// you should configure this condition for safety, to exclude ignoring different timeOut calls!
return time == 50 && fn.toString().indexOf('checkLoadedTimeoutId') > -1;
window.setTimeoutOriginal = window.setTimeout;
window.setTimeout = function(fn, time)
if (isTimeoutIgnoredFunction(fn, time))
return fn(); // or return true if you don't need to call this
else
return window.setTimeoutOriginal.apply(this, arguments);
;
如果没有提供 requirejs minify,应该在最后的 Chrome、Firefox、IE 中工作...您需要为您支持的浏览器和缩小的 require.js 文件重写函数“isTimeoutIgnoredFunction”。
查看可以使用的字符串:
console.log((function ()
checkLoadedTimeoutId = 0;
checkLoaded();
).toString());
但在某些浏览器中,它可能只是“功能”之类的东西。
如果你没有很多 setTimeout 的,它可以是合适的解决方案...
【讨论】:
以上是关于覆盖 require.js 中的 setTimeout的主要内容,如果未能解决你的问题,请参考以下文章
使用 Require JS 的 angularJS 应用程序中的子资源完整性
JS模块化:CommonJS和AMD(Require.js)