Module loading actually divides js into many modules for easy development and maintenance. Therefore, when loading many js modules, it is necessary to load dynamically to improve the user experience.
Before introducing the module loading library, let me introduce a method.
Dynamic loading of js method:
The code copy is as follows:
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); //Before inserting it into the first node of the head, prevent the head tag under ie6 from being closed, and use appendChild to report an error.
}
Since Situ Zhengmei used the mass framework it wrote to introduce module loading, the most used in the industry is require.js and sea.js. Therefore, I think he has a strong personality.
Let me talk about the loading process of sea.js:
The page chaosan.js is introduced in the head tag, and the seajs object will be obtained.
Also introduce index.js.
The code of index.js is as follows:
The code copy is as follows:
seajs.use(['./a','jquery'],function(a,$){
var num = aa;
$('#J_A').text(num);
})
a.js:
The code copy is as follows:
define(function(require,exports,module){
var b = require('./b');
var a = function(){
return 1 + parseInt(bb());
}
exports.a = a;
})
b.js:
The code copy is as follows:
define(function(require,exports,module){
var c = require('./c');
var b = function(){
return 2 + parseInt(cc());
}
exports.b = b;
})
c.js:
The code copy is as follows:
define(function(require,exports,module){
var c = function(){
return 3;
}
exports.c = c;
})
From the above, we can see that module a depends on b, and b depends on c.
When the program enters index.js, seajs will call the use method.
The code copy is as follows:
seajs.use = function(ids, callback) {
globalModule._use(ids, callback)
}
Description: When globalModule is initialized in seajs (when sea.js is introduced), the instance of Module var globalModule = new Module(util.pageUri, STATUS.COMPILED)
At this time ids -> ['./a','jquery'], callback -> function(a,$){var num = aa;$('#J_A').text(num);}
Next, globalModule._use(ids, callback) will be called
The code copy is as follows:
Module.prototype._use = function(ids, callback) {
var uris = resolve(ids, this.uri); //Resolution ['./a','jquery']
this._load(uris, function() { //Call the address of the parsed a and jquery module [url1,url2] and call the _load method.
//util.map: Let all data members execute the specified function at a time and return a new array, which is the result of the original array member's callback execution
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 flag function(a,$){var num = aa;$('#J_A').text(num);} to callback1,
Set this._load(uris, function() { }) callback method flag to 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) was parsed into ['http://localhost/test/SEAJS/a.js','http://localhost/test/SEAJS/lib/juqery/1.7.2/juqery-debug.js'], and the module path resolution has been completed.
And next this._load will be executed
The code copy is as follows:
// The _load() method will first determine which resource files are not ready yet. If all resource files are in the ready state, callback2 will be executed.
// In this case, we will also make circular dependencies and perform loading on unloaded js
Module.prototype._load = function(uris, callback2) {
//util.filter: Let all data members execute the specified function at a time and return a new array. The array is a member that returns true after executing a callback from the original array member.
//unLoadedUris is an array of modules that are not compiled
var unLoadedUris = util.filter(uris, function(uri) {
//Returns a member whose execution function boolean value is true, returns true when the uri exists and does not exist in the internal variable cacheModules or it stores the value of status less than STATUS.READY in the storage information
// If the STATUS.READY value is 4, if it is less than four, it is possible that it is being retrieved and downloaded.
return uri && (!cachedModules[uri] ||
cachedModules[uri].status < STATUS.READY)
});
//If all the modules in uris are ready, execute the callback and exit the function body (this time the module's _compile method will be called).
var length = unLoadedUris.length
if (length === 0) { callback2() return }
//The number of modules not loaded
var remains = length
//Create closures and try to load modules that are not loaded
for (var i = 0; i < length; i++) {
(function(uri) {
//Judge if the uri storage information 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 state 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, fetch(uri, onFetched) is called to try to download the resource file. Onload will be triggered after the resource file is downloaded, and the onFetched method will be executed in the onload.
module.status >= STATUS.FETCHED ? onFetched() : fetch(uri, onFetched)
function onFetched() {
module = cachedModules[uri]
//When the state value of the module is greater than or equal to STATUS.SAVED, it means that all dependency information of the module has been obtained
if (module.status >= STATUS.SAVED) {
//getPureDependencies: Get a dependency array with no 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 after the loading of all dependencies is completed
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 be maintained at FETCHING or FETCHED
else {
cb()
}
}
})(unLoadedUris[i])
}
// cb method - execute callback after loading all modules
function cb(module) {
// If the module's storage information exists, then modify the status value in its module storage information and modify it to STATUS.READY
module && (module.status = STATUS.READY)
// Callbacks are executed only when all modules are loaded.
--remain === 0 && callback2()
}
}
}
Here, the array length of unLoadedUris is 2, ['http://localhost/test/SEAJS/a.js','http://localhost/test/SEAJS/lib/juqery/1.7.2/juqery-debug.js'], so two closures with the name of the js path will be generated next.
Take http://localhost/test/SEAJS/a.js as an example
Next: First, a Module will be created:
The code copy is as follows:
cachedModules('http://localhost/test/SEAJS/a.js') = new Module('http://localhost/test/SEAJS/a.js',1)
module.status >= STATUS.FETCHED ? onFetched() : fetch(uri, onFetched)
Because the 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).
The code copy is as follows:
function fetch(uri, onFetched) {
// Replace uri with the new request address according to the rules in the map
var requestUri = util.parseMap(uri)
// First, find out whether the requestUri record is contained in the obtained list
if (fetchedList[requestUri]) {
// At this time, refresh the module storage information of the original uri to the requestUri redefined by map
cachedModules[uri] = cachedModules[requestUri]
// Execute onFetched and return, which means that the module has been successfully obtained
onFetched()
Return
}
//Query requestUri's storage information in the obtain list
if (fetchingList[requestUri]) {
// Add the callback corresponding to the uri in the callbacklist and return
callbackList[requestUri].push(onFetched) //If it is being retrieved, push the onFetched callback method of this module into the array and return it.
Return
}
// If the modules you are trying to obtain do not appear in fetchedList and fetchingList, add their 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, and we call the callback function here as callback3.
This method is to call the loadJs method to dynamically download the a.js file. (Because there are a and jquery, two new scripts will be created). There is a question here. If you create a script for a and add it to the head, you will download the js file. However, in seajs, it is not downloaded. Instead, you will wait until the script for jquery is established and added to the head before you download it (Google debugger sets a breakpoint, and it keeps showing pending waiting). Is this for Mao?
(Recommended here: http://ux.sohu.com/topics/50972d9ae7de3e752e0081ff. I'll talk about additional questions here. You may know why we need to use tables to layout less, because when the table is rendered, it needs to calculate multiple times, while the div only needs once. At the same time, the interviewer of Midea e-commerce told me that the table needs to be parsed all before it will be displayed, and the div will be displayed as much as it parsed. After verification, if there is a tbody tag in the table, it will be displayed in segments according to tbody. Therefore, in IE6, 7, 8, if you use innerHTML to create a "<table></table>", it will automatically add <tbody></tbody> to it.).
After the download is successful, it will be parsed and executed, and the define method is executed. Here we will first execute the code of module a.
Define(id,deps,function(){}) method analysis
The code copy is as follows:
//define definition, id: module id, deps: module dependency, factory
Module._define = function(id, deps, factory) {
//Resolve dependencies//If deps is not an array type, factory is a function
if (!util.isArray(deps) && util.isFunction(factory)) { // The function body regularly matches the require string and forms an array to return the assignment 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 the factory to determine whether it is a function (the reason is that the definition 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 is matched regularly, 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 the ie 6-9 browser, you can get the path to running js. However, in a standard browser, this is not feasible, so you can assign the meta information to anonymousModuleMeta = meta.
Then trigger onload, 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 executed uniformly, that is, onFetched.
The onFetched method checks whether a module a has dependent module. Because a depends on b, execute _load() on b.js on which module a depends.
The b module will be downloaded, and the jquery definition method will be executed first. Because jquery does not depend on modules, after onload callback. onFetched calls cb method.
When b is implemented in the same process as a, module c will be downloaded. Finally, the c, b, a modules are downloaded and executed, and after the 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, the callback is called back2 and the _compile method of the a and jquery modules is executed:
First, compile the a.js module, and execute the function of module a. Because a has require(b.js) in it, it will execute the function of module b.
The function of module a starts execution
The function of module b begins to execute
The function of module c starts execution
The function execution of module c has been completed
The function execution of module b has been completed
The function execution of module a has been completed
Finally, execute the function of jquery.
After compilation, execute callback1 and you can use a and jquery objects.
PS: The seajs version has been updated, and there is no _compile method now. (Everyone goes to see it yourself, I want to see it too)
Let’s talk about the seajs module compilation_compile process.
First, the compilation of a.js
The code copy is as follows:
Module.prototype._compile = function() {
126 var module = this
127 // If the module has been compiled, then return module.exports directly
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 exceptions to deal with, and then return null directly
137 if (module.status < STATUS.SAVED && !hasModifiers(module)) {
138 return null
139 }
140 // Change the module status to COMPILING, which means the module is being compiled
141 module.status = STATUS.COMPILING
142
143 // The internal use of the module is a method used to obtain the interface provided by other modules (called submodules) and operate synchronously
144 function require(id) {
145 // Path the module's path according to id
146 var uri = resolve(id, module.uri)
147 // Get modules from module cache (note that the dependencies of the submodule as the main module have been downloaded)
148 var child = cachedModules[uri]
149
150 // Just return null when uri is invalid.
151 // If child is empty, it can only mean that the parameter is filled incorrectly, and then return null 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 repeated compilation of modules due to circular dependencies.
158 if (child.status === STATUS.COMPILING) {
159 return child.exports
160 }
161 // Point to the module that calls the current module during initialization. Based on this property, you can get the Call Stack when the module is initialized.
162 child.parent = module
163 // Return the module.exports of compiled child
164 return child._compile()
165 }
166 // Used internally 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 resolution mechanism inside the module system to parse and return the module path. This function does not load the module, and only returns the parsed absolute path.
171 require.resolve = function(id) {
172 return resolve(id, module.uri)
173 }
174 // Through this property, 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 will be re-acquisitioned the next time you use it.
176 require.cache = cachedModules
177
178 // require is a method to obtain interfaces provided by other modules.
179 module.require = require
180 // exports is an object that provides module interfaces to the outside.
181 module.exports = {}
182 var factory = module.factory
183
184 // When factory is a function, it represents the constructor of the module. By executing this method, you can obtain the interface provided by the module to the outside.
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 an object, a string, etc., the interface representing the module is the object, a string, and other values.
191 // For example: 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, which means 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 }
The code copy is as follows:
if (util.isFunction(factory)) {
186 compileStack.push(module)
187 runInModuleContext(factory, module)
188 compileStack.pop()
189 }
Here is the initialization of module.export. runInModuleContext method:
The code copy is as follows:
// 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 are used to expose interfaces
492 // require is used to obtain dependent modules (synchronous) (compile)
493 var ret = fn(module.require, module.exports, module)
494 // Supports the interface form of return value exposure, 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, and 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 the object is to be exposed, or the return object c is returned, the exports of module c will be exports = 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 methods and properties of the c object in module c.
By analogy, module a can eventually call the properties and methods of the b object in module b.
No matter what module, as long as you use module.export = xx module, other modules can use require("xx module") to call various methods in the xx module.
The final module state will become module.status = STATUS.COMPILED.
The code copy is as follows:
Module.prototype._use = function(ids, callback) {
var uris = resolve(ids, this.uri); //Resolution ['./a','jquery']
this._load(uris, function() { //Call the address of the parsed a and jquery module [url1,url2] and call the _load method.
//util.map: Let all data members execute the specified function at a time and return a new array, which is the result of the original array member's callback execution
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];
The code copy is as follows:
seajs.use(['./a','jquery'],function(a,$){
var num = aa;
$('#J_A').text(num);
})
At this time, a and $ in the function are module a.export and module jquery.export.
Because I am now studying jquery source code and jquery framework design, I share some experience:
jquery source code, I have read a lot of analysis online, but I can't read it anymore as I look at it. It's not very meaningful, so I recommend Miaowei Classroom's jquery source code analysis.
Situ Zhengmei’s JavaScript framework design is difficult, but after careful reading, you will become a senior front-end engineer.
I suggest learning and using Yu Bo’s sea.js, after all, it is made by the Chinese themselves. Our company's new projects or reconstructions will be done using seajs.
Next is the source code intensive reading of modular handsbars and mvc backbone or mvvm angular. Here I hope someone will give me suggestions on what books, websites, and videos to learn quickly.