覆盖require.js中的setTimeout

时间:2016-01-04 08:20:16

标签: javascript requirejs prototype require

我们在项目中使用require.js,我们需要覆盖第705行中的 setTimeout ,这是我们需要忽略/忽略这个代码总是setTimeout(我的意思是在它上面运行),问题是如果我在开源代码中更改它时显式更改版本代码将丢失,我应该如何从外部仅为require.js文件覆盖此setTimout只要我使用这个lib,就可以保留它,是否可以在全球范围内以优雅的方式完成它?

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?

4 个答案:

答案 0 :(得分:5)

您已经声明过,您的目标是解决Chrome在setTimeout针对后台标签执行的限制。我不认为这样做是个好主意,但如果你必须这样做,那么你绝对应该修补RequireJS而不是全局地弄乱setTimeout。你说:

  

如果我在开源代码中更改它,当我更改版本时,代码将丢失

仅当您不使用合理的方法执行更改时才会出现这种情况。可以理智地做到这一点。例如,您可以使用Gulp获取require.js中安装的node_modules文件(在使用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,或者你可以使用你想要执行构建的任何其他系统。要点是:

  1. 您有一个可重复且自动化的方法来修补RequireJS。如果您使用npm安装新版本的RequireJS,则在重建软件时会自动应用修补程序,只要RequireJS的代码不会以阻止应用补丁的方式改变。如果更改阻止应用修补程序,请参阅下一点。

  2. 此方法比在运行时覆盖setTimeout更强大。假设James Burke决定使用较新版本的RequireJS将checkLoaded重命名为checkDone并重命名关联的变量(以便checkLoadedTimeoutId变为checkDoneTimeoutId)。上面的gulpfile会在你再次运行时引发异常,因为它找不到要替换的文本。您必须更新要替换的文本和替换文本,以便修补程序与新版本的RequireJS一起使用。这样做的好处是,您可以获得事先已发生变化的早期警告,并且需要查看补丁。 您在游戏后期不会感到意外,也许在您已经向客户提供新版软件之后。

    在运行时覆盖setTimeout的方法将无声地无法完成其工作。他们正在寻找一个包含checkLoadedTimeoutId的函数,该函数在新版本中不再存在。因此,他们只会让RequireJS的行为与默认情况相同。失败将是一个微妙的失败。 (我已经使用建议的setTimeout自定义版本运行RequireJS,项目在未优化的情况下加载了超过50个模块。我发现使用库存setTimeout和RequireJS使用RequireJS之间没有明显区别自定义setTimeout。)

  3. 此方法不会减慢setTimeout的每次使用速度。 setTimeout由其他代码使用而不是RequireJS。无论你如何削减它,在setTimeout的自定义替换中添加代码,开始在传递给它的每个函数中查找字符串将使{em>所有使用setTimeout的速度变慢。< / p>

答案 1 :(得分:1)

您可以覆盖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));

请注意,此代码存在多个问题。如果将函数传递给包含setTimeout的{​​{1}},它也会立即执行。此外,如果require.js代码被缩小并且变量被重命名,则它将不起作用。

总而言之,没有好办法做到这一点。也许试着找到一种不同的方式来实现你想要的。还要注意,正如Madara Uchiha所说:

  

改变全局函数和对象几乎从不是一个好主意。

答案 2 :(得分:0)

如果你真的需要这个...在加载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,它可能是合适的解决方案......

答案 3 :(得分:0)

实现它的其他方法是创建库的ajax请求并在加载库之前对其进行修补,但是你需要加载一种方法来发出请求(vanilla js或jquery ......)

下一个代码是加载requirejs的示例,并在将其加载到DOM之前使用regexp对其进行修补。

$(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>