Before ES6, JS did not have mature modular functions like other languages. Pages could only introduce their own or third-party scripts by inserting script tags, and it was easy to cause naming conflicts. The js community made a lot of efforts to achieve the effect of "modules" in the running environment at that time.
Common js modular standards include CommonJS and AMD. The former is used in the node environment, and the latter is implemented by etc. in the browser environment. In addition, there are domestic open source projects that follow CMD specifications. (At present, maintenance has been stopped with the popularity of es6, both AMD and CMD will be a piece of history)
Browser js loader
Implementing a simple js loader is not complicated, it can be mainly divided intoPath, download module, parse module dependencies, parse moduleFour steps.
First, define the module. In various specifications, a js file usually represents a module. Then, we can construct a closure in the module file and pass an object out as the export of the module:
define(factory() { var x = { a: 1 }; return x; });
The define function receives a factory function parameter. When the browser executes the script, the define function executes the factory and stores its return value in the module object modules of the loader.
How to identify a module? You can use the uri of the file, which is the unique identifier and is a natural id.
There are several forms of file path path:
- Absolute path:http://xxx, file://xxx
- Relative paths: ./xxx , ../xxx , xxx (relative to the file path of the current page)
- Virtual absolute path: /xxx/ indicates the website root directory
Therefore, a resolvePath function is needed to parse different forms of paths into uri, and the file path of the current page is parsed.
Next, suppose we need to reference and two modules and set up the callback function f that needs a and b to execute. We hope that the loader will pull a and b. When both a and b are loaded, take a and b from the modules as parameters and pass it to f, and perform the next operation. Here, you can use the observer mode (i.e., subscription/publish mode) to create an eventProxy, subscribe to load a and load b events; after the definition function is executed at the end, after the export is mounted modules, emit an event that has been loaded in this module. After the eventProxy is received, check whether both a and b are loaded. If it is completed, pass the parameter to f to execute the callback.
Similarly, eventProxy can also implement module dependency loading.
// define([ '', '' ], factory (c, d) { var x = c + d; return x; });
The first parameter of the define function can be passed into a dependency array, indicating that the module a depends on c and d. When define is executed, eventProxy is told to subscribe to the loading events of c and d. Once loaded, the callback function f is executed to store the export of a, and emit event a has been loaded.
The original method of loading scripts on the browser is to insert a script tag, and after specifying src, the browser starts to download the script.
Then the module loading in the loader can be implemented using dom operations, insert a script tag and specify src. At this time, the module is in the download state.
PS: In the browser, dynamic insertion of script tags is different from the script loading method when the page dom is first loaded:
The first time the page is loaded, the browser will parse the dom from top to bottom. When encountering script tags, download the script and block the dom parsing. Wait until the script is downloaded and executed before continuing to parse the dom after it is downloaded and executed (modern browsers have done preload optimization and will download multiple scripts in advance, but the execution order is consistent with their order in the dom, and block other dom parsing when executing)
Dynamically insert script,
var a = ('script'); ='xxx'; (a);
The browser will execute after the script is downloaded, and the process is asynchronous.
After the download is completed, perform the above operation, parse dependencies -> load dependencies -> parse this module -> load completion -> execute callbacks.
After the module is downloaded, how do you know its uri when parsing it? There are two types of outgoing, one is to obtain the src attribute of this object; the other is to use it in the define function.
Implementing basic functions is relatively simple, with less than 200 lines of code:
var zmm = { _modules: {}, _configs: { // Used to splice relative paths basePath: (function (path) { if (( - 1) === '/') { path = (0, - 1); } return (() + + 1); })(), // Used to splice relative root paths host: + '//' + + '/' } }; = function (_uri) { // Determine whether the module already exists, whether it is loading or loading return this._modules.hasOwnProperty(_uri); }; = function (_uri) { // Determine whether the module has been loaded return !!this._modules[_uri]; }; = function (_uri) { // The new module occupies a pit, but has not yet loaded, which means it is loading; preventing duplicate loading if (!this._modules.hasOwnProperty(_uri)) { this._modules[_uri] = null; } }; = function (_uri, mod) { this._modules[_uri] = mod; }; = function (uris) { var i, nsc; for (i = 0; i < ; i++) { if (!(uris[i])) { (uris[i]); // Start loading var nsc = ('script'); = uri; (nsc); } } }; = function (path) { // Return to the absolute path var res = '', paths = [], resPaths; if ((/.*:\/\/.*/)) { // Absolute path res = (/.*:\/\/.*?\//)[0]; // Protocol + domain name path = (); } else if ((0) === '/') { // Relative root path / beginning res = this._configs.host; path = (1); } else { // relative path ./ or ../ or direct file name res = this._configs.host; resPaths = this._configs.('/'); } resPaths = resPaths || []; paths = ('/'); for (var i = 0; i < ; i++) { if (paths[i] === '..') { (); } else if (paths[i] === '.') { // do nothing } else { (paths[i]); } } res += ('/'); return res; }; var define = = function (dependPaths, fac) { var _uri = ; if ((_uri)) { return; } var factory, depPaths, uris = []; if ( === 1) { factory = arguments[0]; // Mount to module group (_uri, factory()); // Tell proxy that the module is loaded (_uri); } else { // There is a dependency situation factory = arguments[1]; // Completed callback function (arguments[0], function () { (_uri, (null, arguments)); (_uri); }); } }; = function (paths, callback) { if (!(paths)) { paths = [paths]; } var uris = [], i; for (i = 0; i < ; i++) { ((paths[i])); } // Register the event first, then load it (uris, callback); (uris); }; = function () { var proxy = {}; var taskId = 0; var taskList = {}; var execute = function (task) { var uris = , callback = ; for (var i = 0, arr = []; i < ; i++) { (zmm._modules[uris[i]]); } (null, arr); }; var deal_loaded = function (_uri) { var i, k, task, sum; // When a module is loaded, iterate through the current task stack for (k in taskList) { if (!(k)) { continue; } task = taskList[k]; if ((_uri) > -1) { // Check whether all the modules in this task are loaded for (i = 0, sum = 0; i < ; i++) { if (([i])) { sum ++; } } if (sum === ) { // All loaded and delete the task delete(taskList[k]); execute(task); } } } }; = function (uris, callback) { // Check first if all loaded for (var i = 0, sum = 0; i < ; i++) { if ((uris[i])) { sum ++; } } if (sum === ) { execute({ uris: uris, callback: callback }); } else { // Subscribe to new loading tasks var task = { uris: uris, callback: callback }; taskList['' + taskId] = task; taskId ++; } }; = function (_uri) { (_uri + ' is loaded!'); deal_loaded(_uri); }; return proxy; }();
Circular dependency problem
"Loop loading" means that the execution of a script depends on script b, and the execution of script b in turn depends on script a. This is a design that should be avoided as much as possible.
Browser side
Load module a with the zmm tool above:
// ('/', function(){...}); // define('/', function(b) { var a = 1; a = b + 1; return a; }); // define('/', function(a) { var b = a + 1; return b; });
It will fall into a deadlock state where a wait for b to complete loading and b waits for a to complete loading. If you encounter this situation, it is also a deadlock. Perhaps it is because this behavior should not occur by default.
Seajs can alleviate the problem of circular dependency, but it must be rewritten:
// define('./js/a', function (require, exports, module) { var a = 1; ('./b', function (b) { a = b + 1; = a; //a= 3 }); = a; // a= 1 }); // define('./js/b', function (require, exports, module) { var a = require('./a'); var b = a + 1; = b; }); // ('./js/a', function (a) { (a); // 1 });
But to do this, you must first know that b will depend on itself, and the output in use is the value of a when b is not loaded, and use does not know that a's value will change after it is changed.
On the browser side, there seems to be no good solution. The loop dependency problem encountered by node module loading is much smaller.
node/CommonJS
An important feature of CommonJS module is execution when loading, that is, all script codes will be executed when required. CommonJS practices that once a module is "looped", only the executed part is output, and the unexecuted part will not be output.
// var a = 1; = a; var b = require('./b'); a = b + 1; = a; // var a = require('./a'); var b = a + 1; = b; // var a = require('./a'); (a); //3
In the above code, first load module a and execute the require function. At this time, a module a has been hung in memory, and its exports are an empty object ={}; then the code being executed; before executing var b = require('./b'); before, =1, then execute require(b); when executed, the =1, and after b is loaded, the execution right is returned; finally the output of module a is 3.
CommonJS has implementation differences with browser-side loaders. The modules loaded by node are all locally and perform a synchronous loading process, that is, loading according to the dependency relationship. When the loading statement is executed, another module will be loaded. After loading, it will return to the function call point to continue execution. Due to the inherent limitations of the browser loading scripts, it can only be loaded asynchronously and executed callbacks to achieve it.
ES6
The operating mechanism of the ES6 module is different from that of CommonJS. When it encounters the module loading command import, it will not execute the module, but will only generate a reference. When it really needs to be used, go to the module to get the value. Therefore, the ES6 module is a dynamic reference and there is no problem of cached values. Moreover, the variables in the module are bound to the module where it is located.
This leads to a fundamental difference between ES6 handling "loop loading" and CommonJS. ES6 doesn't care whether "loop loading" occurs at all, but just generates a reference to the loaded module. The developer needs to ensure that the value can be obtained when the value is truly obtained.
Let’s take a look at an example:
// import { odd } from './odd'; export var counter = 0; export function even(n) { counter++; return n == 0 || odd(n - 1);} // import { even } from './even'; export function odd(n) { return n != 0 && even(n - 1);} // import * as m from './'; (10); // true; = 6
In the above code, the function even has a parameter n. As long as it is not equal to 0, 1 will be subtracted and the loaded odd() will be passed. It will do similar things.
In the above code, foo() will be executed 6 times in the process of parameter n changing from 10 to 0, so the variable counter is equal to 6. When even() is called the second time, the parameter n changes from 20 to 0, and foo() will be executed 11 times in total, plus the previous 6 times, so the variable counter is equal to 17.
If this example is rewritten to CommonJS, it will not be executed at all and an error will be reported.
// var odd = require('./odd'); var counter = 0; = counter; = function(n) { counter++; return n == 0 || odd(n - 1); } // var even = require('./even').even; = function(n) { return n != 0 && even(n - 1); } // var m = require('./even'); (10); // TypeError: even is not a function
In the above code, loading and then loading, forming a "loop loading". At this time, the execution engine will output the part that has been executed (no results exist), so in which the variable even is equal to null, and an error will be reported after the call even(n-1) is called later.
The above is all the content of this article. I hope the content of this article will be of some help to everyone’s study or work. If you have any questions, you can leave a message to communicate. At the same time, I hope to support me more!