The structure of the web page is becoming more and more complex. It can be regarded as a simple APP. If you still put all the code into one file as before, there will be some problems:
Global variables affect each other
JavaScript files become larger, affecting loading speed
The structure is confusing and difficult to maintain
Compared with the backend (such as Java), you can see the obvious gap. In 2009, Ryan Dahl created the node.js project to use JavaScript for server programming, which marked the official birth of "JS modular programming".
A module is a collection of functions. Then you can split a large file into some small files, define different functions in each file, and then introduce them in HTML:
var module1 = new Object({ _count : 0, m1 : function (){ //... }, m2 : function (){ //... } });
The disadvantage of this is that all members in the module are exposed! We know that the local variables of the function cannot be accessed from the outside, so we can use the immediate execution function to optimize:
var module1 = (function(){ var _count = 0; var m1 = function(){ //... }; var m2 = function(){ //... }; return { m1 : m1, m2 : m2 }; })();
Everyone may define modules in various ways. If they can all follow certain specifications, the benefits will be It will be very large: can refer to each other!
The math.js module is defined in node.js as follows:
function add(a, b){ return a + b; } exports.add = add;
When used in other modules, just use the global require function to load:
var math = require('math'); math.add(2,3);
There is no problem in synchronizing require on the server, but the browser cannot do this in a network environment, so there is an asynchronous AMD specification:
require(['math'], function (math) {// require([module], callback); math.add(2, 3); });
The module is defined as follows (the module can Depends on other modules):
define(function (){ // define([module], callback); var add = function (x,y){ return x+y; }; return { add: add }; });
You can load many other resources with RequireJS (see here), which is very good and powerful! I use SeaJS more often at work, and the specification used is called CMD. It is recommended (should refer to asynchronous mode):
as lazy as possible!
The difference between the processing method of dependent modules and AMD is:
AMD is executed in advance (dependency front-end), and CMD is delayed execution (dependency is nearby).
The way to define a module in CMD is as follows:
define(function(require, exports, module) { var a = require('./a'); a.doSomething(); var b = require('./b'); b.doSomething(); });
For usage, please refer to the document directly, so I won’t go into details here!
When I first came into contact with modularization, I felt that this was too simple. Isn’t it just:
Set onload and src when creating the script tag!
In fact this is the case, but not entirely! Let’s start looking at the SeaJS code (sea-debug.js). A module may go through the following states during the loading process:
var STATUS = Module.STATUS = { // 1 - The `module.uri` is being fetched FETCHING: 1, // 2 - The meta data has been saved to cachedMods SAVED: 2, // 3 - The `module.dependencies` are being loaded LOADING: 3, // 4 - The module are ready to execute LOADED: 4, // 5 - The module is being executed EXECUTING: 5, // 6 - The `module.exports` is available EXECUTED: 6, // 7 - 404 ERROR: 7 }
The Modul
object is used in memory to maintain module information:
function Module(uri, deps) { this.uri = uri this.dependencies = deps || [] // 依赖模块ID列表 this.deps = {} // 依赖模块Module对象列表 this.status = 0 // 状态 this._entry = [] // 在模块加载完成之后需要调用callback的模块 }
Start the module on the page The system needs to use the seajs.use
method:
seajs.use(‘./main’, function(main) {// 依赖及回调方法 main.init(); });
The overall logic of the loading process can be seen in Module.prototype.load
:
Module.prototype.load = function() { var mod = this if (mod.status >= STATUS.LOADING) { return } mod.status = STATUS.LOADING var uris = mod.resolve() // 解析依赖模块的URL地址 emit("load", uris) for (var i = 0, len = uris.length; i < len; i++) { mod.deps[mod.dependencies[i]] = Module.get(uris[i])// 从缓存取或创建 } mod.pass(); // 将entry传递给依赖的但还没加载的模块 if (mod._entry.length) {// 本模块加载完成 mod.onload() return } var requestCache = {}; var m; // 加载依赖的模块 for (i = 0; i < len; i++) { m = cachedMods[uris[i]] if (m.status < STATUS.FETCHING) { m.fetch(requestCache) } else if (m.status === STATUS.SAVED) { m.load() } } for (var requestUri in requestCache) { if (requestCache.hasOwnProperty(requestUri)) { requestCache[requestUri]() } } }
Generally speaking, the logic is very smooth, so I won’t go into details. The only thing that is more convoluted is the _entry
array. I couldn't find a relatively easy-to-understand article on the Internet, so I roughly understood it while looking at the code and guessing. In fact, I just need to remember its goal:
When all dependent modules are loaded. Then execute the callback function!
In other words:
The array_entry stores a list of which module dependencies may be loaded after the current module is loaded (reverse relationship of dependencies) !
For example, module A depends on modules B, C, and D. Then the status after pass is as follows:
At this time A The remain
in is 3, which means that it still has three dependent modules that have not been loaded! And if module B depends on modules E and F, then A will also be passed out when it is loaded:
There are several details:
Modules that have been loaded will not be propagated;
Modules that have been propagated once will not be propagated again;
If the dependent module is being loaded, it will be propagated recursively;
After maintaining the dependency relationship, you can load the module through Module.prototype.fetch
. There are two ways# Implementation method of ##sendRequest:
load or
error method is executed. After all the dependent modules are loaded, the
onload method will be executed:
Module.prototype.onload = function() { var mod = this mod.status = STATUS.LOADED for (var i = 0, len = (mod._entry || []).length; i < len; i++) { var entry = mod._entry[i] if (--entry.remain === 0) { entry.callback() } } delete mod._entry }
--entry.remain is equivalent to telling the module corresponding to the entry: your One of the dependencies list is already completed! And
entry.remain === 0 means that all the modules it depends on have been loaded! Then the callback function will be executed at this time:
for (var i = 0, len = uris.length; i < len; i++) { exports[i] = cachedMods[uris[i]].exec(); } if (callback) { callback.apply(global, exports)// 执行回调函数 }
define method will be executed immediately to maintain the module information:
is not explicitly specified When dependencies, parseDependencies will be used to use the require() fragment in the regular matching method (it is a good habit to specify a dependency list).Then execute the
factory method to generate module data:
var exports = isFunction(factory) ? factory.call(mod.exports = {}, require, mod.exports, mod) : factory
然后执行你在seajs.use中定义的callback
方法:
if (callback) { callback.apply(global, exports) }
当你写的模块代码中require时,每次都会执行factory方法:
function require(id) { var m = mod.deps[id] || Module.get(require.resolve(id)) if (m.status == STATUS.ERROR) { throw new Error('module was broken: ' + m.uri) } return m.exec() }
到这里核心的逻辑基本上讲完了,补一张状态的转换图:
以后在用的时候就可以解释一些诡异的问题了!
模块化非常好用,因此在ECMAScript 6中也开始支持,但是浏览器支持还是比较堪忧的~~
The above is the detailed content of Detailed introduction to JavaScript modularization and SeaJs source code analysis. For more information, please follow other related articles on the PHP Chinese website!