Home  >  Article  >  Web Front-end  >  A brief introduction to JavaScript modularity

A brief introduction to JavaScript modularity

阿神
阿神Original
2017-02-03 17:48:011195browse

Preface

Regarding modularization, the most direct manifestation is the require and import keywords we wrote. If If you look up relevant information, you will definitely encounter terms such as CommonJS and CMD AMD, as well as unfamiliar frameworks such as RequireJS and SeaJS. 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 are working on the client are familiar with OC You should be familiar with #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:

<html>
  <head>
    <script type="text/javascript" src="index.js"></script>
  </head>

  <body>
    <p id="hello"> Hello Wrold </p>
    <input type="button" onclick="onPress()" value="Click me" />
  </body>
 </html>
// index.js
function onPress() {
    var p = document.getElementById(&#39;hello&#39;);
    p.innerHTML = &#39;Hello bestswifter&#39;;
}

The 3f1c4e4b6b16bbbd69b2ee476dc4f83a tag in HTML can be understood as import, so that the button's onclick event can call the The onPress function in index.js.

Assume that 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:

// math.js
function add(a, b) {
    return a + b;
}

According to the client's thinking, the index.js file at this time should be written like this:

// index.js
import "math.js"

function onPress() {
    var p = document.getElementById(&#39;hello&#39;);
    p.innerHTML = add(1, 2);
}

Unfortunately, JavaScript does not support This way of writing import means that methods in one JS file cannot reference methods in other JS files at all. The correct solution is to directly call the add method in index.js, and at the same time reference math.js in index.html:

<html>
  <head>
    <script type="text/javascript" src="index.js"></script>
    <script type="text/javascript" src="math.js"></script>
  </head>

  <body>
    <p id="hello"> Hello Wrold </p>
    <input type="button" onclick="onPress()" value="Click me" />
  </body>
</html>
// index.js
function onPress() {
    var p = document.getElementById(&#39;hello&#39;);
    p.innerHTML = add(1, 2);
}

You can see that this writing method is not elegant, index.js is different from other JS You have no control over the content in the file. 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 In fact, it can be divided into two types:

1.index.js cannot be imported and relies on HTML references

2.index.js cannot distinguish the source of the add method and lacks a namespace The concept of

The first question will be answered later. Let’s start with the second question first. For example, first put the function into an object, so that we can expose an object and let the user call this object. Multiple methods:

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

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

You can see that you can already specify a simple version of the namespace (that is, math) in index.js. 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: {}, // 用来存储所有暴露的内容
};

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(&#39;hello&#39;);
    // math
    p.innerHTML = math.add(1, 2);
}


##Existing modular solution
The above simple modular approach 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 version of the 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

比较知名的规范有 CommonJS、AMD 和 CMD。而知名框架 Node.js、RequireJS 和 Seajs 分别实现了上述规范。

最早的规范是 CommonJS,Node.js 使用了这一规范。这一规范和我们之前的做法比较类似,是同步加载 JS 脚本。这么做在服务端毫无问题,因为文件都存在磁盘上,然而浏览器的特性决定了 JS 脚本需要异步加载,否则就会失去响应,因此 CommonJS 规范无法直接在浏览器中使用。


AMD

浏览器端著名的模块管理工具 Require.js 的做法是异步加载,通过 Webworker 的 importScripts(url); 函数加载 JS 脚本,然后执行当初注册的回调。Require.js 的写法是:

require([&#39;myModule1&#39;, &#39;myModule2&#39;], function (m1, m2){
    // 主回调逻辑
    m1.printName();
    m2.printName();
});

由于这两个模块是异步下载,因此哪个模块先被下载、执行并不确定,但可以肯定的是主回调一定在所有依赖都被加载完成后才执行。

Require.js 的这种写法也被称为前置加载,在写主逻辑之前必须指定所有的依赖,同时这些依赖也会立刻被异步加载。

由 Require.js 引申出来的规范被称为 AMD(Asynchronous Module Definition)。


CMD

另一种优秀的模块管理工具是 Sea.js,它的写法是:

define(function(require, exports, module) {
    var foo = require(&#39;foo&#39;); // 同步
    foo.add(1, 2); 
    ...
    require.async(&#39;math&#39;, function(math) { // 异步
        math.add(1, 2);
    });
});

Sea.js 也被称为就近加载,从它的写法上可以很明显的看到和 Require.js 的不同。我们可以在需要用到依赖的时候才申明。

Sea.js 遇到依赖后只会去下载 JS 文件,并不会执行,而是等到所有被依赖的 JS 脚本都下载完以后,才从头开始执行主逻辑。因此被依赖模块的执行顺序和书写顺序完全一致。

由 Sea.js 引申出来的规范被称为 CMD(Common Module Definition)。


ES 6 模块化

在 ES6 中,我们使用 export 关键字来导出模块,使用 import 关键字引用模块。需要说明的是,ES 6 的这套标准和目前的标准没有直接关系,目前也很少有 JS 引擎能直接支持。因此 Babel 的做法实际上是将不被支持的 import 翻译成目前已被支持的 require。

尽管目前使用 import 和 require 的区别不大(本质上是一回事),但依然强烈推荐使用 import 关键字,因为一旦 JS 引擎能够解析 ES 6 的 import 关键字,整个实现方式就会和目前发生比较大的变化。如果目前就开始使用 import 关键字,将来代码的改动会非常小。


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