在 require.js 中覆盖 setTimeout
override setTimeout in require.js
我们在项目中使用 require.js,我们需要覆盖第 705 行的 setTimeout,这是我们需要 的代码ignore/omit 不知何故 这个 setTimeout(我的意思是 运行 超过它),问题是如果我在更改版本时在开源代码中显式更改它,代码将会丢失,我应该如何仅从 require.js 文件的外部覆盖此 setTimout 并在我使用此 lib 时保留它,是否可以在 JS 中以 elegant 方式全局执行此操作?
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?
如果你真的需要这个...尝试在加载 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 缩小,应该在最后 Chrome、Firefox、IE 中工作...您需要为您支持的浏览器和缩小的 require.js 文件重写函数 "isTimeoutIgnoredFunction"。
查看您可以使用的字符串:
console.log((function () {
checkLoadedTimeoutId = 0;
checkLoaded();
}).toString());
但在某些浏览器中,它可能只是类似于 "Function"。
如果你的 setTimeout 不多,它可能是合适的解决方案...
您可以覆盖 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 代码被缩小并且变量被重命名,它将不起作用。
综上所述,没有什么好的办法。也许尝试找到一种不同的方式来实现你想要的。还要注意,正如宇智波斑所说:
Changing global functions and objects is almost never a good idea.
您已声明您的目标是解决 Chrome 对 setTimeout
对后台选项卡执行的限制。我不认为这样做是个好主意,但如果你必须这样做,那么你绝对应该修补 RequireJS,而不是在全局范围内搞乱 setTimeout
。你说:
if I change it in the open source code explicit when I change version the code will be lost
仅当您未使用明智的方法执行更改时才会出现这种情况。明智地做到这一点是可能的。例如,您可以使用 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,您可以使用 G运行t,或者您可以使用您想要执行构建的任何其他系统。要点是:
您有一个可重现的自动化方法来修补 RequireJS。 如果您使用 npm
安装新版本的 RequireJS,当您重建您的软件补丁会自动应用,只要 RequireJS 的代码没有以阻止应用补丁的方式更改。如果更改阻止应用补丁,请参阅下一点,了解会发生什么。
此方法比在 运行 时覆盖 setTimeout
更健壮。 假设 James Burke 决定使用较新版本的 RequireJS将 checkLoaded
重命名为 checkDone
并重命名关联的变量(以便 checkLoadedTimeoutId
变为 checkDoneTimeoutId
)。上面的 gulpfile 将在您再次 运行 时引发异常,因为它找不到要替换的文本。您必须更新要替换的文本和替换,以便补丁与新版本的 RequireJS 一起使用。这样做的好处是您会提前收到警告,告知您情况已发生变化,您需要查看补丁。 你不会在比赛后期感到惊讶,也许在你已经向客户交付了新版本的软件之后。
在 运行 时覆盖 setTimeout
的方法将默默地无法完成它们的工作。他们将寻找包含 checkLoadedTimeoutId
的函数,该函数在新版本中将不再存在。所以他们只会让 RequireJS 按默认方式运行。失败将是一个微妙的失败。 (我已经 运行 RequireJS 与建议的自定义版本 setTimeout
以及一个在未优化时加载超过 50 个模块的项目。我没有看到 RequireJS 使用股票 setTimeout
和RequireJS 使用自定义 setTimeout
.)
此方法不会减慢每次使用 setTimeout
。 setTimeout
被 RequireJS 以外的其他代码使用。无论您如何削减它,将代码添加到 setTimeout
的自定义替换中,开始在传递给它的每个函数中查找字符串将使 所有 次使用 setTimeout
较慢。
实现它的其他方法是对库发出 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>
我们在项目中使用 require.js,我们需要覆盖第 705 行的 setTimeout,这是我们需要 的代码ignore/omit 不知何故 这个 setTimeout(我的意思是 运行 超过它),问题是如果我在更改版本时在开源代码中显式更改它,代码将会丢失,我应该如何仅从 require.js 文件的外部覆盖此 setTimout 并在我使用此 lib 时保留它,是否可以在 JS 中以 elegant 方式全局执行此操作?
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?
如果你真的需要这个...尝试在加载 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 缩小,应该在最后 Chrome、Firefox、IE 中工作...您需要为您支持的浏览器和缩小的 require.js 文件重写函数 "isTimeoutIgnoredFunction"。
查看您可以使用的字符串:
console.log((function () {
checkLoadedTimeoutId = 0;
checkLoaded();
}).toString());
但在某些浏览器中,它可能只是类似于 "Function"。
如果你的 setTimeout 不多,它可能是合适的解决方案...
您可以覆盖 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 代码被缩小并且变量被重命名,它将不起作用。
综上所述,没有什么好的办法。也许尝试找到一种不同的方式来实现你想要的。还要注意,正如宇智波斑所说:
Changing global functions and objects is almost never a good idea.
您已声明您的目标是解决 Chrome 对 setTimeout
对后台选项卡执行的限制。我不认为这样做是个好主意,但如果你必须这样做,那么你绝对应该修补 RequireJS,而不是在全局范围内搞乱 setTimeout
。你说:
if I change it in the open source code explicit when I change version the code will be lost
仅当您未使用明智的方法执行更改时才会出现这种情况。明智地做到这一点是可能的。例如,您可以使用 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,您可以使用 G运行t,或者您可以使用您想要执行构建的任何其他系统。要点是:
您有一个可重现的自动化方法来修补 RequireJS。 如果您使用
npm
安装新版本的 RequireJS,当您重建您的软件补丁会自动应用,只要 RequireJS 的代码没有以阻止应用补丁的方式更改。如果更改阻止应用补丁,请参阅下一点,了解会发生什么。此方法比在 运行 时覆盖
setTimeout
更健壮。 假设 James Burke 决定使用较新版本的 RequireJS将checkLoaded
重命名为checkDone
并重命名关联的变量(以便checkLoadedTimeoutId
变为checkDoneTimeoutId
)。上面的 gulpfile 将在您再次 运行 时引发异常,因为它找不到要替换的文本。您必须更新要替换的文本和替换,以便补丁与新版本的 RequireJS 一起使用。这样做的好处是您会提前收到警告,告知您情况已发生变化,您需要查看补丁。 你不会在比赛后期感到惊讶,也许在你已经向客户交付了新版本的软件之后。在 运行 时覆盖
setTimeout
的方法将默默地无法完成它们的工作。他们将寻找包含checkLoadedTimeoutId
的函数,该函数在新版本中将不再存在。所以他们只会让 RequireJS 按默认方式运行。失败将是一个微妙的失败。 (我已经 运行 RequireJS 与建议的自定义版本setTimeout
以及一个在未优化时加载超过 50 个模块的项目。我没有看到 RequireJS 使用股票setTimeout
和RequireJS 使用自定义setTimeout
.)此方法不会减慢每次使用
setTimeout
。setTimeout
被 RequireJS 以外的其他代码使用。无论您如何削减它,将代码添加到setTimeout
的自定义替换中,开始在传递给它的每个函数中查找字符串将使 所有 次使用setTimeout
较慢。
实现它的其他方法是对库发出 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>