Module loading is actually dividing js into many modules to facilitate development and maintenance. Therefore, when loading many js modules, they need to be loaded dynamically to improve the user experience.
Before introducing the module loading library, let us first introduce a 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); //Insert before the first node of head to prevent an error from using appendChild before the head label is closed in IE6.
}
Since Situ Zhengmei uses the mass framework written by it to introduce module loading, the most commonly used ones 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:
Page chaojidan.jsp, in the head tag, introduce sea.js, then you will get the seajs object.
Note: When globalModule is initialized for seajs (when sea.js is introduced), the instance of Module is var globalModule = new Module(util.pageUri, STATUS.COMPILED)
At this time ids -> ['./a','jquery'], callback -> function(a,$){var num = a.a;$('#J_A').text(num);}
Module.prototype._use = function(ids, callback) {
var uris = resolve(ids, this.uri); //Resolve['./a','jquery']
This._load(uris, function() { //Calculate the parsed a, the address of the jquery module [url1, url2], and call the _load method.
//util.map: Let all data members execute a specified function at a time, and return a new array, which is the result of the callback executed by the original array members
var args = util.map(uris, function(uri) {
return uri ? cachedModules[uri]._compile(): null;//If url exists, call the _compile method.
})
if (callback) { callback.apply(null, args) }
})
}
Because after calling the _load method, two callback functions will appear, so we mark function(a,$){var num = a.a;$('#J_A').text(num);} as callback1,
Mark this._load(uris, function() { }) callback method as callback2.
The resolve method is to resolve the module address, so I won’t go into details here.
Finally, the uris in var uris = resolve(ids, this.uri) is parsed into ['http://localhost/test/SEAJS/a.js','http://localhost/test/SEAJS/lib/juqery /1.7.2/juqery-debug.js'], module path resolution has been completed.
// The _load() method mainly determines which resource files are not ready yet. If all resource files are in the ready state, callback2 will be executed.
// In this process, circular dependencies will also be judged and unloaded js will be loaded
Module.prototype._load = function(uris, callback2) {
//util.filter: Let all data members execute a specified function at a time, and return a new array, which is the member that returns true after the original array member executes the callback
//unLoadedUris is the module uri array that has not been compiled
var unLoadedUris = util.filter(uris, function(uri) {
//Returns the member whose Boolean value of the execution function is true. It returns true when the uri exists and does not exist in the internal variable cacheModules or its status value in the stored information is less than STATUS.READY
// STATUS.READY value is 4. If it is less than four, the possible situation is that it is being obtained or downloaded.
Return uri && (!cachedModules[uri] ||
cachedModules[uri].status < STATUS.READY)
});
//If all modules in uris are ready, execute the callback and exit the function body (at this time, the module's _compile method will be called).
var length = unLoadedUris.length
if (length === 0) { callback2() return }
//The number of modules that have not yet been loaded
var remain = length
//Create a closure and try to load modules that are not loaded
for (var i = 0; i < length; i ) {
(function(uri) {
//Determine if the storage information of the uri does not exist in the internal variable cachedModules, instantiate a Module object
var module = cachedModules[uri] ||
(cachedModules[uri] = new Module(uri, STATUS.FETCHING))
//If the status value of the module is greater than or equal to 2, it means that the module has been downloaded and already exists locally. At this time, onFetched() is executed
//Otherwise, call fetch(uri, onFetched) and try to download the resource file. After the resource file is downloaded, onload will be triggered, and the onFetched callback method will be executed in onload.
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: Get the dependency array without circular dependencies
var deps = getPureDependencies(module)
//If the dependency array is not empty
if (deps.length) {
//Execute the _load() method again until all dependencies are loaded and the callback is executed
Module.prototype._load(deps, function() {
cb(module)
})
}
//If the dependency array is empty, execute cb(module) directly
else {
cb(module)
}
}
// If the acquisition fails, such as 404 or does not comply with the modular specification
//In this case, module.status will remain at FETCHING or FETCHED
else { cb()
}
}
})(unLoadedUris[i])
}
// cb method - callback executed after loading all modules
Function cb(module) {
// If the module’s storage information exists, then modify the status value in its module storage information to STATUS.READY
module && (module.status = STATUS.READY)
// Execute the callback only when all modules are loaded.
--remain === 0 && callback2()
}
}
}
The array length of unLoadedUris here is 2, ['http://localhost/test/SEAJS/a.js','http://localhost/test/SEAJS/lib/juqery/1.7.2/juqery-debug .js'], so two closures named with js paths will be generated next.
Because module a is not loaded at this time, fetch(uri, onFetched) will be executed next, that is, fetch('http://localhost/test/SEAJS/a.js', onFetched).
function fetch(uri, onFetched) {
//Replace the uri with the new request address according to the rules in the map
var requestUri = util.parseMap(uri)
// First, check whether there is a requestUri record in the obtained list
If (fetchedList[requestUri]) {
// At this time, refresh the module storage information of the original uri to the requestUri redefined through map
cachedModules[uri] = cachedModules[requestUri]
// Execute onFetched and return, which means the module has been successfully obtained
onFetched()
return
}
//Query the storage information of requestUri in the acquisition list
If (fetchingList[requestUri]) {
//Add the callback corresponding to the uri in the callbacklist and return
CallbackList[requestUri].push(onFetched) //If it is being fetched, push the onFetched callback method of this module into the array and return.
return
}
// If the module you are trying to fetch does not appear in fetchedList and fetchingList, add its information in the request list and callback list respectively
fetchingList[requestUri] = true
callbackList[requestUri] = [onFetched]
// Fetches it
Module._fetch(
requestUri,
function() {
fetchedList[requestUri] = true
// Updates module status
// If module.status is equal to STATUS.FECTCHING, modify the module status to FETCHED
var module = cachedModules[uri]
If (module.status === STATUS.FETCHING) {
module.status = STATUS.FETCHED
}
if (fetchingList[requestUri]) {
delete fetchingList[requestUri]
}
// Calls callbackList Unified execution of callbacks
if (callbackList[requestUri]) {
util.forEach(callbackList[requestUri], function(fn) {
Fn () // fn is the onfeched method corresponding to module A.
})
delete callbackList[requestUri]
}
},
config.charset
)
}
Next, Module._fetch() will be executed. The callback function here is called callback3.
This method is to call the loadJs method to dynamically download a.js file. (Because there are a and jquery, two new scripts will be created). There is a question here. If you create a new script and add it to the head, the js file will be downloaded. However, in seajs, it will not be downloaded, but will wait for jquery. The script will be downloaded only after it is created and added to the head (the Google debugger sets a breakpoint and always displays pending). Why is this?
(It is recommended to read here: http://ux.sohu.com/topics/50972d9ae7de3e752e0081ff. Here I will talk about additional issues. You may know why we should use less table for layout, because table is rendering the tree. When laying out, multiple calculations are required, while div only needs one. At the same time, the Midea e-commerce interviewer told me that the table needs to be fully parsed before it will be displayed, and the div will be displayed as much as it is parsed. tags will be displayed in segments according to tbody. Therefore, in IE6, 7, and 8, if you use innerHTML to create a "
", < will be automatically added to it. /tbody>).
After the download is successful, it will be parsed and executed, and the define method will be executed. The code of module a will be executed first.
define(id,deps,function(){}) method analysis
//define definition, id: module id, deps: module dependency, factory
Module._define = function(id, deps, factory) {
//Resolve dependencies //If deps is not an array type and factory is a function
if (!util.isArray(deps) && util.isFunction(factory)) { // Regularly match the require string in the function body, and form an array to return and assign the value to deps
deps = util.parseDependencies(factory.toString())
}
//Set meta information
var meta = { id: id, dependencies: deps, factory: factory }
if (document.attachEvent) {
// Get the node of the current script
var script = util.getCurrentScript()
// If the script node exists
if (script) {
// 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 will first perform a judgment on factory to determine whether it is a function (the reason is because define can also include files and objects)
If it is a function, then the function will be obtained through factory.toString(), and the dependency of a.js will be obtained through regular matching, and the dependency will be saved in deps
For a.js, its dependency is b.js, so deps is ['./b']
And save the information of a.js var meta = { id: id, dependencies: deps, factory: factory }
For a.js meta = { id : undefined , dependencies : ['./b'] , factory : function(xxx){xxx}}
In IE 6-9 browsers, you can get the path of the current running js. However, in standard browsers, this is not feasible, so temporarily assign the meta information to anonymousModuleMeta = meta.
Then onload is triggered, and the callback method callback3 will be called. This callback method will modify the status value of the current callback module (a.js) and set it to module.status = STATUS.FETCHED.
Next, the callback corresponding to a.js in the callback queue callbackList will be uniformly executed, which is onFetched.
The onFetched method will check whether module a has dependent modules. Because a depends on b, _load() is executed on b.js that module a depends on.
will download the b module, and then execute the define method of jquery first. Because jquery does not depend on modules, after the onload callback. onFetched calls the cb method.
When b is implemented according to the same process as a, the c module will be downloaded. Finally, modules c, b, and a are all downloaded and executed define, and after onload is completed, the cb method will also be called, (first c, then b, then c)
After all modules are ready, the callback2 method will be called.
Finally callback to callback2, execute the _compile method of a and jquery modules:
First compile the a.js module, and the function of module a is executed. Because a contains require(b.js), the function of module b will be executed.
The function of module a starts executing
The function of module b starts executing
The function of module c starts executing
The function of module c is executed
The function of module b is executed
The function of module a is executed
Finally execute the jquery function.
After compilation is completed, callback1 is executed, and a and jquery objects can be used.
PS: The seajs version has been updated and there is no _compile method now. (Everyone go and see it on your own, I want to go see it too)
Then let’s talk about the module compilation_compile process of seajs.
Module.prototype._compile = function() {
126 var module = this
127 // If the module has been compiled, directly return 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 is to handle some abnormal situations, and return null directly
137 if (module.status < STATUS.SAVED && !hasModifiers(module)) {
138 return null
139 }
140 // Change the module status to COMPILING, indicating that the module is being compiled
141 module.status = STATUS.COMPILING
142
143 // Used internally within the module, it is a method used to obtain the interfaces provided by other modules (called submodules) and perform synchronous operations
144 function require(id) {
145 // Parse the path of the module according to the id
146 var uri = resolve(id, module.uri)
147//Get the module from the module cache (note that the submodule here has actually been downloaded as a dependency of the main module)
148 var child = cachedModules[uri]
149
150//Just return null when uri is invalid.
151//If the child is empty, it can only mean that the parameter filling is wrong and the uri is incorrect, then null is returned directly
152 if (!child) {
153 return null
154 }
155
156 // Avoids circular calls.
157//If the status of the submodule is STATUS.COMPILING, return child.exports directly to avoid repeatedly compiling the module due to circular dependencies
158 if (child.status === STATUS.COMPILING) {
159 return child.exports
160 }
161//Points to the module that calls the current module during initialization. According to this attribute, the Call Stack during module initialization can be obtained.
162 child.parent = module
163//Return the compiled child’s module.exports
164 return child._compile()
165 }
166 // Used internally by the module to load the module asynchronously and execute the specified callback after the loading is completed.
167 require.async = function(ids, callback) {
168 module._use(ids, callback)
169 }
170 // Use the path parsing mechanism inside the module system to parse and return the module path. This function does not load the module and only returns the resolved absolute path.
171 require.resolve = function(id) {
172 return resolve(id, module.uri)
173 }
174 // Through this attribute, you can view all modules loaded by the module system.
175 // In some cases, if you need to reload a module, you can get the uri of the module, and then delete its information by delete require.cache[uri]. This way, you will get it again the next time you use it.
176 require.cache = cachedModules
177
178 // require is a method used to obtain the interfaces provided by other modules.
179 module.require = require
180 // Exports is an object used to provide module interfaces to the outside world.
181 module.exports = {}
182 var factory = module.factory
183
184 // When factory is a function, it represents the module's construction method. By executing this method, you can get the interface provided by the module.
185 if (util.isFunction(factory)) { 186 compileStack.push(module)
187 runInModuleContext(factory, module)
188 compileStack.pop()
189 }
190 // When factory is a non-function type such as object or string, it means that the interface of the module is the object, string or other values.
191 // Such as: define({ "foo": "bar" });
192 // For example: define('I am a template. My name is {{name}}.');
193 else if (factory !== undefined) {
194 module.exports = factory
195 }
196
197 // Change the module status to COMPILED, indicating that the module has been compiled
198 module.status = STATUS.COMPILED
199 // Execute module interface modification through seajs.modify()
200 execModifiers(module)
201 return module.exports
202 }
// Execute module code according to module context
489 function runInModuleContext(fn, module) {
490 // Pass in the two parameters related to the module and the module itself
491 // exports are used to expose interfaces
492 // require is used to obtain dependent modules (synchronization) (compilation)
493 var ret = fn(module.require, module.exports, module)
494 // Support return value exposure interface form, 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, then var b = require("b.js"), will be called
The require method will return the return value of the compile method of b, and there is var c = require('c.js') in the b module.
At this time, the compile method of c will be called, and then the function of c will be called. In c, if you want to expose the object, or return the object c, then the exports of module c = c. Or directly module.export = c; in short, module c.export = c will be returned in the end; so var c = module c.export = c, in module b, you can use variable c to call the method and method of the c object in module c. property.
By analogy, eventually module a can also call the properties and methods of object b in module b.
No matter what module, as long as module.export = xx module is used, other modules can use require("xx module") to call various methods in the xx module.
The final module status will become module.status = STATUS.COMPILED.
Module.prototype._use = function(ids, callback) {
var uris = resolve(ids, this.uri); //Resolve['./a','jquery']
This._load(uris, function() { //Calculate the parsed a, the address of the jquery module [url1, url2], and call the _load method.
//util.map: Let all data members execute a specified function at a time, and return a new array, which is the result of the callback executed by the original array members
var args = util.map(uris, function(uri) {
return uri ? cachedModules[uri]._compile(): null;//If url exists, call the _compile method.
})
if (callback) { callback.apply(null, args) }
})
}
At this time args = [module a.export, module jquery.export];
seajs.use(['./a','jquery'],function(a,$){
var num = a.a;
$('#J_A').text(num);
})
At this time, a and $ in function are module a.export and module jquery.export.
Because I am currently studying jquery source code and jquery framework design, I would like to share some experiences:
I read a lot of analysis of the jquery source code on the Internet, but I couldn’t stand it anymore. It doesn’t make much sense. I recommend the jquery source code analysis of Miaowei Classroom.
Situ Zhengmei’s JavaScript framework design is personally difficult, but after reading it carefully, you will become a senior front-end engineer.
I recommend you to learn Yu Bo’s sea.js and use it. After all, it was made by the Chinese themselves. Our company's new projects or refactorings will be done using seajs.
The next step is to read the source code of modular handbars and mvc's backbone or mvvm's angular. Here I hope someone can give me some advice on what books to read, what websites to read, and what videos to watch to learn quickly.
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn