The javascript framework designs the module loading system of reading notes

  • 2020-03-30 04:28:02
  • OfStack

Module load, in fact, js is divided into many modules, easy to develop and maintain. Therefore, when loading many js modules, dynamic loading is needed to improve the user experience.

Before I introduce the module load library, let me introduce a method.

Dynamic loading js method:


function loadJs(url , callback){
  var node = document.createElement("script");
      node[window.addEventListener ? "onload":"onreadystatechange"] = function(){
            if(window.addEventListener || /loaded|complete/i.test(node.readyState)){
      callback();
      node.onreadystatechange = null;
    }                                                                            
  }
  node.onerror = function(){};
     node.src = url;
  var head = document.getElementsByTagName("head")[0];
  head.insertBefore(node,head.firstChild);     //AppendChild is inserted before the first node of the head to prevent the head tag from being closed under ie6. < br / > }

Since stu zhengmi used the mass framework it wrote to introduce module loading, the most commonly used in the industry are require. Js and sea-js. Therefore, I think he has a strong personality.

Let me talk about the module loading process of sea-js:

The page chaojidan.jsp, in the head tag, introduces sea-js and you get the seajs object.

We also introduce index.js.

The code of index.js is as follows:


seajs.use(['./a','jquery'],function(a,$){
    var num = a.a;
    $('#J_A').text(num);
})

A. s:


define(function(require,exports,module){
    var b = require('./b');
    var a = function(){
        return 1 + parseInt(b.b());
    }
    exports.a = a;
})

B.j s:


define(function(require,exports,module){
   var c = require('./c');     var b = function(){
        return 2 + parseInt(c.c());
    }
    exports.b = b;
})

SAN Antonio s:


define(function(require,exports,module){
    var c = function(){
        return 3;
    }
    exports.c = c;
})

As can be seen from above, module a depends on b, and b depends on c.

When the program enters index.js, seajs calls the use method.


seajs.use = function(ids, callback) {
  globalModule._use(ids, callback)
}

Var globalModule = new Module(util.pageuri, STATUS.COM)
At this point the ids - > ['. / a ', 'jquery'], the callback - > Function (a, $) {var num = a.a; $(' # J_A '). The text (num); }

Next, call globalmodule._use (ids, callback)


Module.prototype._use = function(ids, callback) {  
  var uris = resolve(ids, this.uri);      //Parse ['. / a ', 'jquery] < br / >     this._load(uris, function() {    //A, the address of the jquery module [url1,url2], and call the _load method. < br / >                 //Util.map: have the data members execute the specified function one at a time and return a new array that is the result of a callback to the original array members
      var args = util.map(uris, function(uri) {          return uri ? cachedModules[uri]._compile() : null;//If a url exists, the _compile method is called. < br / >    })
    if (callback) { callback.apply(null, args) } 
  })
   }

Function (a,$){var num = a.a; $(' # J_A '). The text (num); } flag is callback1,
Mark this._load(uris, function() {}) callback method as callback2. 
The resolve method resolves the module address, which I won't go into here.
Var uris, eventually = resolve (ids, this uri) in the uris, parsed into [' http://localhost/test/SEAJS/a.js', 'http://localhost/test/SEAJS/lib/juqery/1.7.2/juqery-debug.js'], parsing module path has just finished.

Next, execute this._load


//The _load() method primarily determines which resource files are not ready and executes callback2
if all resource files are in the ready state   //In this case, we will also make the judgment of loop dependency and load
on the unloaded js execution   Module.prototype._load = function(uris, callback2) {  
  //Util. Filter: have the data members all execute the specified function one at a time and return a new array for the member
that returns true after a callback to the original array member     //UnLoadedUris is an array of module uris that are not compiled
    var unLoadedUris = util.filter(uris, function(uri) {
      //Returns a member with a Boolean value of true for the execution function, true
if the uri exists and the internal variable cacheModules does not exist, or if the value of status in the stored information is less than status.ready       //STATUS.READY value is 4, less than four possible case is in the get, in the download. < br / >       return uri && (!cachedModules[uri] ||
          cachedModules[uri].status < STATUS.READY)
    });    
  //If all the modules in uris are ready, a callback is executed and the function body exits (the module's _compile method is then called). < br / >   var length = unLoadedUris.length
  if (length === 0) { callback2() return }
  //Number of modules not yet loaded
    var remain = length
    //Create a closure to try to load modules that are not loaded
    for (var i = 0; i < length; i++) {
      (function(uri) {
        //Instantiate a Module object
if there is no stored information for the uri in the internal cachedModules variable         var module = cachedModules[uri] ||
            (cachedModules[uri] = new Module(uri, STATUS.FETCHING))
        //If the state of the module value greater than or equal to 2, which means that the module has been downloaded and is already in the local, this time to perform onFetched () < br / >         //Or call the fetch (uri, onFetched), try to download the resource files, resource files after downloading will trigger onload, performs in the onload onFetched callback methods. < br / >         module.status >= STATUS.FETCHED ? onFetched() : fetch(uri, onFetched)
        function onFetched() {
          module = cachedModules[uri]
          //When the STATUS value of the module is greater than or equal to status.saved, it means that all the dependency information of the module has been obtained
          if (module.status >= STATUS.SAVED) {
            //GetPureDependencies: gets an array of dependencies that do not have circular dependencies
            var deps = getPureDependencies(module)
            //If the dependent array is not empty
            if (deps.length) {
              //The _load() method is executed again until the callback
is executed when all dependencies are loaded               Module.prototype._load(deps, function() {
                cb(module)
              })
            }
            //If the dependency array is empty, directly execute cb(module)
            else {
              cb(module)            
            }
          }
          //If the fetch fails, for example 404 or does not conform to the modularity specification
          //In this case, the module. The status will remain in FETCHING or FETCHED < br / >           else {
            cb()
          }
        }
      })(unLoadedUris[i])
    }
    //Cb method - executes a callback
after all modules are loaded     function cb(module) {
      //If the module's store information exists, change the value of status in its module store information to status.ready
      module && (module.status = STATUS.READY)
      //Callbacks are only executed when all modules are loaded. < br / >       --remain === 0 && callback2()
    }
  }
}

Here unLoadedUris array length is 2, [' http://localhost/test/SEAJS/a.js', 'http://localhost/test/SEAJS/lib/juqery/1.7.2/juqery-debug.js'], so will produce two next to js path for the name of the closure.

(link: http://localhost/test/SEAJS/a.js), for example
Next: a Module is created:


cachedModules('http://localhost/test/SEAJS/a.js') = new Module('http://localhost/test/SEAJS/a.js',1)
module.status >= STATUS.FETCHED ? onFetched() : fetch(uri, onFetched)

When a module is not loaded so will execute the fetch (uri, onFetched) fetch (' http://localhost/test/SEAJS/a.js' onFetched).


function fetch(uri, onFetched) {
    //According to the rules in the map, replace the uri with the new request address
    var requestUri = util.parseMap(uri)
    //First look in the fetched list to see if the requestUri record
is included     if (fetchedList[requestUri]) {
      //At this point the module store information of the original uri is flushed to
on the requestUri redefined by the map       cachedModules[uri] = cachedModules[requestUri]
      //Perform onFetched and returns, means that the module has been successful < br / >       onFetched()
      return
    }
    //Query the requestUri's store information in the get list
    if (fetchingList[requestUri]) {
      //A callback corresponding to this uri is added to the callbacklist and returns
      callbackList[requestUri].push(onFetched)    //If you are obtained, the module of onFetched callback methods push into the array, and returns. < br / >       return
    }
    //If the module you are trying to retrieve does not appear in the fetchedList and fetchingList, add its information
to the request list and the callback list, respectively     fetchingList[requestUri] = true
    callbackList[requestUri] = [onFetched]
    // Fetches it
    Module._fetch(
        requestUri,
        function() {
          fetchedList[requestUri] = true
          // Updates module status
          //If the module. The status equal status. FECTCHING, then modify the module status to FETCHED < br / >           var module = cachedModules[uri]
          if (module.status === STATUS.FETCHING) {
            module.status = STATUS.FETCHED
          }
          if (fetchingList[requestUri]) {
            delete fetchingList[requestUri]
          }
          //The Calls callbackList performs a uniform callback
          if (callbackList[requestUri]) {
            util.forEach(callbackList[requestUri], function(fn) {
              fn()    //Fn is the onFeched method for module a. < br / >             })
            delete callbackList[requestUri]
          }
        },
        config.charset
    )
  }

The next step is module._fetch (), the callback function we call callback3.

This method simply calls the loadJs method to dynamically download the a.js file. (because there is a and jquery, so two new scripts will be created), here is a question, new a script, and add to the head, will download js file, but in seajs, did not download, but until the jquery script is established, and added to the head, will download (Google debugger set breakpoints, always show pending waiting). Is this for wool?
: (recommended here (link: http://ux.sohu.com/topics/50972d9ae7de3e752e0081ff), here I say additional questions, you may know about why we should use the table to layout, less because the table in the render tree, need to calculate for many times, and div need only once. At the same time, the interviewer of midea e-commerce told me that the table would not be displayed until all the tables were parsed, and the div would be displayed as much as it parsed. If there is a tbody tag in the verified table, it will be segmented according to tbody. So in IE6,7,8, if you use innerHTML to create a "< Table> < / table>" Will automatically add < Tbody> < / tbody> ). .
Once the download is successful, it will be parsed and executed with the define method. This is going to execute the code for module a first.
Define (id,deps,function(){}) method


//Define,id: module id, deps: module dependency, factory
  Module._define = function(id, deps, factory) {
   // Analytic dependency // if deps Not an array type, at the same time factory Is the function
   if (!util.isArray(deps) && util.isFunction(factory)) { //The function body matches the require string on a regular basis and forms an array that returns an assignment to deps
     deps = util.parseDependencies(factory.toString())
   }
  //Set meta information
   var meta = { id: id, dependencies: deps, factory: factory } 
   if (document.attachEvent) {
     //Gets the node of the current script
     var script = util.getCurrentScript()
       //If the script node exists
     if (script) {
         //You get the original uri address
         derivedUri = util.unParseMap(util.getScriptAbsoluteSrc(script)) }
         if (!derivedUri) {
             util.log('Failed to derive URI from interactive script for:', factory.toString(), 'warn')
         }
     }
 .........
 }

Define first performs a judgment on the factory to see if it is a function (the reason being that you can also include files, objects, etc.)

If it's a function, it gets the function through factory.tostring (), gets the dependency of a.js through a regular match, and stores the dependency in deps

For a.js, its dependency is b.js so the deps is ['. / b ']

Var meta = {id: id, dependencies: deps, factory: factory}

For a.js meta = {id: undefined, dependencies: ['./b'], factory: function(XXX){XXX}}

The path where js is currently running is available in ie 6-9 but is not available in a standard browser, so assign the meta information to anonymousModuleMeta = meta for the moment.

Then trigger the onload, then will call the callback method callback3, the callback method can modify the current state of the callback module (a. s) value, and set it to the module. The status = status. The FETCHED.

Again next, to implement the callback queue callbackList unity of a. s corresponding callback, namely onFetched.

OnFetched method will check if there is a dependent on a module module, because a relies on b, so the module a rely on b.j s execution _load ().

I'm going to download module b, and I'm going to execute the define method of jquery. Since jquery does not depend on modules, the onload callback is called. OnFetched call cb method.

When b is implemented in the same way as a, the c module is downloaded. Finally, modules c,b and a will download and execute define, and when the onload is finished, it will also call cb method, (c first, then b, then c).

Once all the modules are ready, the callback2 method is called.
The final callback to callback2 executes the _compile method for the a and jquery modules:

First, compile the a.js module, and execute the function of module a. Since a requires (b.js), it will execute the function of module b.
Function of module a starts to execute
The function of module b starts to execute
The function of module c starts to execute
The function of module c is completed
The function of module b is completed
The function of module a is completed

Finally, execute jquery's function.

Once the compilation is complete, callback1 is executed and the a and jquery objects are ready to be used.

PS: the seajs version has been updated, now there is no _compile method. (go see it yourself, I'll go see it too)

Next, we will talk about the module _compile process of seajs.

The first is the compilation of a.js


Module.prototype._compile = function() {
126     var module = this         
127     //If the module is already compiled, it returns module.exports
128     if (module.status === STATUS.COMPILED) {
129       return module.exports
130     }
133     //  1. the module file is 404.
134     //  2. the module file is not written with valid module format.
135     //  3. other error cases.
136     //Here are some exception cases where null
is returned directly 137     if (module.status < STATUS.SAVED && !hasModifiers(module)) {
138       return null
139     }
140     //Changing the module state to compile means the module is COMPILING
141     module.status = STATUS.COMPILING
142
143     //Module internal use, is a method to obtain the interface provided by other modules (called submodules), synchronous operation
144     function require(id) {
145         //The path of the module is resolved by id
146         var uri = resolve(id, module.uri)
147         //Get the module from the module cache (note that the submodule as a dependency of the main module has been downloaded)
148         var child = cachedModules[uri]
149
150         // Just return null when uri is invalid.
151         //If the child is null, which simply means that the uri is incorrect due to an error in parameter filling, return null
152         if (!child) {
153           return null
154         }
155
156         // Avoids circular calls.
157         //If the state of the submodule is STATUS.COM hold, return child.exports directly to avoid repeatedly compiling the module
because of loop dependencies 158         if (child.status === STATUS.COMPILING) {
159           return child.exports
160         }
161         //Points to the module that invokes the current module at initialization time. According to this property, the Call Stack when the module is initialized can be obtained.
162         child.parent = module
163         //Returns the compiled child's module.exports
164         return child._compile()
165     }
166     //Used internally by the module to load the module asynchronously and to execute the specified callback after loading. < br / > 167     require.async = function(ids, callback) {
168       module._use(ids, callback)
169     }
170     //Use the path resolution mechanism within the module system to resolve and return the module path. This function does not load the module and only returns the absolute path parsed. < br / > 171     require.resolve = function(id) {
172       return resolve(id, module.uri)
173     }
174     //With this property, you can see all the modules that the module system has loaded. < br / > 175     //In some cases, if a module needs to be reloaded, you can get the module's uri and then delete its information by deleting requirement.cache [uri]. This will be retrieved the next time you use it. < br / > 176     require.cache = cachedModules
177
178     //Require is a method to get an interface provided by another module. < br / > 179     module.require = require
180     //Exports is an object that provides a module interface to the outside world. < br / > 181     module.exports = {}
182     var factory = module.factory
183
184     //When factory is a function, it represents the construction method of the module. By executing this method, the interface provided by the module is obtained. < br / > 185     if (util.isFunction(factory)) {
186       compileStack.push(module)
187       runInModuleContext(factory, module)
188       compileStack.pop()
189     }
190     //When factory is a non-functional type such as object, string, etc., the interface of the module is the equivalent of the object and string. < br / > 191     //Define ({"foo": "bar"}); < br / > 192     //Define ('I am a template. My name is {{name}}.'); < br / > 193     else if (factory !== undefined) {
194       module.exports = factory
195     }
196
197     //Changing the state of a module to COMPILED means that the module has COMPILED
198     module.status = STATUS.COMPILED
199     //Modify the module interface by seajs.modify()
200     execModifiers(module)
201     return module.exports
202   }


if (util.isFunction(factory)) {
186       compileStack.push(module)
187       runInModuleContext(factory, module)
188       compileStack.pop()
189     }

So we're just going to initialize module.export. RunInModuleContext method:


//Execute the module code
according to the module context 489   function runInModuleContext(fn, module) {
490     //Pass in two parameters related to the module and the module itself
491     //Exports is used to expose an interface
492     //Require is used to get the dependent module (synchronization) (compile)
493     var ret = fn(module.require, module.exports, module)
494     //Supports return value exposure interface forms such as
495     // return {
496     //   fn1 : xx
497     //   ,fn2 : xx
498     //   ...
499     // }
500     if (ret !== undefined) {
501       module.exports = ret
502     }
503   }

Execute the function method in a.js, at which point var b = require("b.js") is called,
The require method returns the return value of the compile method for b, which in turn has var c = require('c.js').
C's compile method is called, and then c's function is called. In c, if you want to expose an object or return object c, then exports of module c = c. Or module.export = c; Anyway, we're going to return the module c.export = c; So var c = module c.export = c, in module b, you can use the variable c to call the methods and properties of c objects in module c.
By analogy, eventually module a can also call the properties and methods of b object in module b.
No matter what module, as long as the module.export = xx module, other modules can use require("xx module "), call various methods in xx module.
Finally, the state of the module becomes module.status = STATUS.COM.

The same code at the page code block index 6

Args = [module a.export, module jquery. Export];
The same code at the page code block index 1
In this case, the a and $in function are module a.export and module jquery.export.

Because I am now studying the jquery source code and jquery framework design, so I share some experience:
Jquery source code, I read a lot of analysis on the Internet, looking at it can not read down. Meaning is not big, recommend the jquery source code of the wonderful taste class.

Situ zhengmei's javascript framework design, personally feel difficult, but after intensive reading, you are a senior front-end engineer.

Sea. Js of yu bo, I suggest to learn and use it. After all, it is made by the Chinese themselves. Our company will use seajs for new projects or refactorings.


Related articles: