Home  >  Article  >  Web Front-end  >  A brief analysis of JavaScript modularization

A brief analysis of JavaScript modularization

高洛峰
高洛峰Original
2017-02-04 16:24:581065browse

 Foreword

Regarding modularization, the most direct manifestation is the require and import keywords we write. If you check the relevant information, you will definitely encounter the terms CommonJS, CMD AMD, as well as RequireJS, SeaJS, etc. Strange frame. For example, the official website of SeaJS describes itself this way: "Simple and friendly module definition specification, Sea.js follows the CMD specification. Natural and intuitive code organization, automatic loading of dependencies..."

As a front-end novice, I am honest He looked confused and couldn't understand. According to my usual style, before introducing something, I always have to explain why it is needed.

JavaScript Basics

Students who work on the client side should be familiar with OC's #import "classname", Swift's Module and file modifiers, and Java's import package+class mode. We are accustomed to the pattern that referencing a file refers to a class. However, in a dynamic language like JavaScript, things have changed. For example:







Hello Wrold



// index.js
function onPress() {
var p = document.getElementById('hello');
p.innerHTML = 'Hello bestswifter';
}

 The <script> tag in HTML can be understood as import, so that the onclick event of the button can call the onPress function defined in index.js. </p> <p> Suppose as the project develops, we find that the clicked text needs to be dynamically generated and generated by other JS files, then simple index.js will not be able to do the job. We assume that the generated content is defined in the math.js file: </p> <p>// math.js<br>function add(a, b) {<br>return a + b;<br>}<br> </p> <p> According to the client’s thinking, the index.js file at this time should be written like this: </p> <p>// index.js<br>import "math.js"<br> <br>function onPress () {<br>var p = document.getElementById('hello');<br>p.innerHTML = add(1, 2);<br>}<br></p> <p> Unfortunately, JavaScript does not The import method is not supported, which means that methods in other JS files cannot be referenced in one JS file. The correct solution is to call the add method directly in index.js and reference math.js in index.html:</p> <p><html><br><head><br><script type ="text/javascript" src="index.js"></script>




Hello Wrold





// index.js
function onPress() {
var p = document.getElementById('hello');
p.innerHTML = add(1, 2);
}

You can see that this way of writing is not elegant. Index.js You have no control over the content in other JS files. Whether you can call the add method depends entirely on whether your own HTML file has correctly referenced other JS files.

Preliminary modularization

The pain points just mentioned can actually be divided into two types:

index.js cannot be imported and relies on HTML references

The source of the add method cannot be distinguished in index.js, and the concept of namespace is missing

The first question will be answered later. Let’s solve the second question first, such as putting the function into an object first. , so that we can expose an object and let users call multiple methods of this object:

//index.js
function onPress() {
var p = document.getElementById(' hello');
p.innerHTML = math.add(1, 2);
}

//math.js
var math = {
base: 0,
add: function(a, b) {
return a + b + base;
},
};

 You can see that it is already available in index.js Specify a simplified version of the namespace (that is, math). But there is still a small problem. For example, the base attribute will be exposed to the outside world and can also be modified. So a better way is to define math in a closure to hide the internal properties:

// math.js
var math = (function() {
var base = 0;
return {
add: function(a, b) {
return a + b + base;
},
};
})();

So far, we have implemented the definition and use of modules. However, one of the essence of modularization lies in the namespace, which means that we hope that our math module is not global, but imported on demand. In this way, even if multiple files expose objects with the same name, there will be no problem. Just like in node.js, the module that needs to be exposed defines its own export content, and then the caller uses the require method.

In fact, you can simply simulate the working mode of node.js and solve it by adding an intermediate layer: First define a global variable:

// global.js
var module = {
exports: {}, // Used to store all exposed content
};

Then expose the object in math.js:

var math = ( function() {
var base = 0;
return {
add: function(a, b) {
return a + b + base;
},
};
})();

module.exports.math = math;

User index.js should now be:

var math = module. exports.math;

function onPress() {
var p = document.getElementById('hello');
// math
p.innerHTML = math.add(1, 2 );
}

Existing modularization solution

The above simple modularization method has some minor problems. First of all, index.js must strictly rely on math.js execution, because only after math.js is executed, it will register itself with the global module.export. This requires developers to manually manage the loading order of js files. As projects get larger, maintaining dependencies becomes more and more complex.

Secondly, since the browser will stop rendering the web page when loading a JS file, we also need asynchronous on-demand loading of JS files.

The last problem is that the simplified modularization solution given before does not solve the module namespace. The same export will still replace the previous content, and the solution is to maintain a "file path< ;–> Export Contents" table and load it based on the file path.

Based on the above needs, many sets of modular solutions have appeared on the market. Why there are multiple sets of standards is actually caused by the characteristics of the front end. Due to the lack of a unified standard, in many cases everyone relies on conventions when doing things, such as the above-mentioned export and require. If the provider of the code stores the export content in module.exports, and the user reads module.export, it is naturally in vain. Not only that, the implementation methods and usage scenarios of each specification are also different.

 CommonJS

The more well-known specifications include CommonJS, AMD and CMD. The well-known frameworks Node.js, RequireJS and Seajs respectively implement the above specifications.

The earliest specification is CommonJS, which is used by Node.js. This specification is similar to our previous approach, which is to load JS scripts synchronously. There is no problem in doing this on the server side, because the files are stored on the disk. However, the characteristics of the browser determine that the JS script needs to be loaded asynchronously, otherwise it will lose response, so the CommonJS specification cannot be used directly in the browser.

AMD 

The famous module management tool Require.js on the browser side is loaded asynchronously, loading the JS script through the importScripts(url); function of the Webworker, and then executing the originally registered callback. The writing method of Require.js is:

require(['myModule1', 'myModule2'], function (m1, m2){
// Main callback logic
m1.printName();
m2.printName();
});

Since these two modules are downloaded asynchronously, it is not certain which module will be downloaded and executed first, but it is certain that the main module will be downloaded and executed first. The callback must be executed after all dependencies have been loaded.

This way of writing Require.js is also called front-loading. All dependencies must be specified before writing the main logic, and these dependencies will be loaded asynchronously immediately.

The specification derived from Require.js is called AMD (Asynchronous Module Definition).

CMD

Another excellent module management tool is Sea.js. Its writing method is:

define(function(require, exports, module) {
var foo = require('foo'); // Synchronization
foo.add(1, 2);
...
require.async('math', function(math) { // Asynchronous
math.add(1, 2);
});
});

Sea.js is also called nearby loading. From the way it is written, It is obvious to see the difference from Require.js. We can declare dependencies only when we need them.

When Sea.js encounters a dependency, it will only download the JS file and not execute it. Instead, it will wait until all the dependent JS scripts have been downloaded before executing the main logic from scratch. Therefore, the execution order of dependent modules is exactly the same as the writing order.

The specification derived from Sea.js is called CMD (Common Module Definition).

 ES 6 Modularity

 In ES6, we use the export keyword to export modules and the import keyword to reference modules. It should be noted that this set of standards of ES 6 is not directly related to the current standards, and currently few JS engines can directly support it. Therefore, what Babel does is actually translate the unsupported import into the currently supported require.

Although there is currently little difference between using import and require (essentially the same thing), it is still strongly recommended to use the import keyword, because once the JS engine can parse the ES 6 import keyword, the entire implementation will be the same as Big changes are currently taking place. If you start using the import keyword now, the code changes in the future will be very small.

via:http://fullstack.blog/2017/01/25/JavaScript%20%E6%A8%A1%E5%9D%97%E5%8C%96%E7%AE%80% E8%BF%B0/

For more articles related to JavaScript modularization analysis, please pay attention to the PHP Chinese website!

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