requirejs - Performance of multiple calls to requi

2019-04-28 22:34发布

问题:

I'm wondering what is the proper approach for using RequireJS in a project with multiple modules, regarding performance of multiple require calls with less dependencies vs a single require with all the dependencies.

Let's take the case where, for an app, I need to load some modules: gmaps, jquery, module1, module2, module3. The use for some of the modules is quite independent. So, the question is which of the following alternatives is recommended (supposedly this code is the main module loaded into the page):

require(['gmaps'], function(gmaps){
   gmaps.use();
});

require(['jquery','module1'], function(jquery, module1){
   module1.use();
});

require(['jquery','module2'], function(jquery, module2){
   module2.use();
});

require(['jquery','module3'], function(jquery, module3){
   module3.use();
});

vs

require(['jquery','module1','module1','module1','gmaps'], function(jquery, module1,module2,module3,gmaps){
   module1.use();
   module2.use();
   module3.use();
   gmaps.use();
});

In other words, what is the performance penalty of require and which is the best practice.

回答1:

The answer to the question here is "it depends." I'm speaking as someone who has used RequireJS in a large application but who has not thoroughly read the code of RequireJS. (Just pointing out that people who know the innards of RequireJS inside and out might explain differently than I do.) The cost of require can be broken down into 3 cost scenarios:

  1. If the module has never been loaded, require loads a file from a server, executes the file, executes the factory function for the module and returns a reference to the module. (The cost of loading files from the network usually dwarfs other costs.)

  2. If the module has already been loaded but never required, require executes the factory function for the module and returns a reference to the module. (This will normally happen in an optimized application.)

  3. If the module has already been loaded and required, require returns a reference to the module.

Cost scenario 1 > cost scenario 2 > cost scenario 3.

First, let's consider the case where there is one module per file. The application is not optimized. I have a module named module1 which is required once in a blue moon. It's usage in a main application could be modeled like this:

define(["module1", <bunch of other required modules>],
    function (module1, <bunch of other modules variables>) {

    [...]

    if (rare_condition_happening_once_in_a_blue_moon)
        module1.use();

    [...]
});

Here I always pay the price of cost scenario number 1 even if I don't use the module. It would be better to do:

define([<bunch of required modules>],
    function (<bunch of module variables>) {

    [...]

    if (rare_condition_happening_once_in_a_blue_moon)
        require(["module1"], function (module1) {
            module1.use();
        });

    [...]
});

This way, I'd pay the price of loading the module only once in a blue moon.

Now, what if I need to use module repeatedly? This can be modeled as:

define(["module1", <bunch of other required modules>],
    function (module1, <bunch of other modules variables>) {

    [...]

    for(iterate a gazillion times)
        module1.use();

    [...]
});

In this case, cost scenario number 1 is paid once, and that's all. If I use require like this:

define([<bunch of required modules>],
    function (<bunch of module variables>) {

    [...]

    for(iterate a gazillion times)
        require(["module1"], function (module1) {
            module1.use();
        });

    [...]
});

I'm paying cost scenario number 1 once and a (gazillion times - 1) cost scenario number 3. At the end of the day, whether the module1 should be included among the requirements of the define call or in a separate require call depends on the specifics of your application.

The analysis changes if the application has been optimized by using r.js or a home-made optimization. If the application is optimized so that all modules are in one file, every time you paid cost scenario 1 in the cases above, you'd pay cost scenario 2 instead.

For the sake of completeness, I'll add that in cases where you do not know ahead of time the module you might want to load, using require is necessary.

define([<bunch of other required modules>],
    function (<bunch of other modules variables>) {

    [...]

    require(<requirements unknown ahead of time>, function(m1, m2, ...) {
        m1.foo();
        m2.foo();
        [...]
    });

    [...]
});


回答2:

It depends on what loading behavior you want.

When you require a module that has not been required yet, a HTTP request will be made to load the required module. If the required module has been loaded atleast once, it is stored in require's cache, and so when you require the module the second time, it will use the cached value.

So, its a choice between lazy or eager loading.

The problem that require is trying to solve, is that each module can define it's dependencies - so your first approach would probably be "best practice". In the end, you'd want to use r.js to build your application into a single JS file.



回答3:

Second approach is in my opinion against the main purpose of the require js, which is the separation of different program logic. Why would you use requirejs if you put your code in the one "module" anyway? .In terms of performance it is important to define what is your definition of performance. If it is the speed of your code execution, then there should be no visible difference. The code will execute as soon as all required modules are loaded. If on the other hand, what you mean by performance is: when the code execution starts - then obviously in the second approach you will have additional overhead caused by additional requests for files where your required code is located.

EDIT:

After taking a closer look to the question, the first part of my answer did not fit it well. The code is actually referencing external modules, so the second option is not against the purpose of the requirejs library and is actually the better approach as long as the module which combines its dependencies is not trying to do too much.