Detailed explanation of the thought process of javascript modularization

Source: Internet
Author: User
Tags http request split

No modular Era


Before Ajax has yet to be presented, JS is just a "toy language", by Brendan Eich spent less than 10 days to invent, used in the Web page to check the form, to achieve a simple animation effect, and so on, you can recall that the Web page is littered with announcements of the era of floating.
This time does not have the front-end engineer, the service engineer only can write JS on the page casually to be able to fix the demand. The front-end code at that time might look like this:

if (xx) {
//.......
}
else{
Xxxxxxxxxxx
}
for (var i=0; i<10; i++) {
//........
}
Element.onclick = function () {
//.......
}

The code is simply stacked together, as long as it can be executed from the top down in turn.

The embryonic age of the module


2006, the concept of Ajax was proposed, the front-end has the initiative to send requests to the server and operate the ability to return data, as Google will develop this concept, the traditional Web page slowly to the "rich client" development. The business logic of the front end is more and more, the code is more and more, so some problems are leaking out:


1. Disaster for global variables
Xiaoming defines the I=1
Little just in the following code: i=0
Xiaoming in the following code: if (i==1) {...}//Tragedy


2. Function naming conflict
In the project, some common functions are usually encapsulated into a file, the common names are utils.js, Common.js ...
Xiao Ming defines a function: Functions Formatdata () {}
Small just want to implement similar function, so write: function FormatData2 () {}
Small-aperture has a similar function, so: function FormatData3 () {}
......
Avoiding naming conflicts can only be done on the basis of ugly human flesh.


3. Poor dependency Management
B.js relies on A.js, the label must be written in the order of

<script type= "Text/javascript" src= "A.js" ></script>
<script type= "Text/javascript" src= "B.js" ></script>

The order can not be wrong, and can not be omitted to write a. It's hard to reconcile when multiple people are developing.

The solution of the budding era:


1. Wrapping code with a self-executing function


Moda = function () {
var a,b; Variable A, B is not visible outside
return {
Add:function (c) {
A + B + C;
},
Format:function () {
//......
}
}
}()


In this way, the variables inside the function are hidden from the global, which is the purpose of encapsulation. But this is still flawed, moda this variable or burst into the global, with the increase in the module, the global variable will still be more and more.


2. Java-style namespaces


In order to avoid conflicts caused by global variables, it was thought that it might be possible to manage with multiple namespaces, so the code became this style:

App.util.modA = xxx;
App.tools.modA = xxx;
App.tools.modA.format = xxx;

Yahoo's Yui early is to do so, call the time to write:

App.tools.modA.format ();

This call function, write will feel nausea, so this way is not used by many people, Yui later also do not use this way.


3. jquery-style anonymous self-execution function


(function (window) {
Code

Window.jquery = window.$ = jquery;//leaks to the global by adding attributes to the window
}) (window);


jquery's encapsulation style has been imitated by many frameworks, using anonymous function wrapper code, the dependent external variable passed to this function, within the function can use these dependencies, and then at the end of the function of the module itself burst into window.


If you need to add an extension, you can use jquery as a plug-in to mount it on $.


Although this style is flexible, it does not solve the fundamental problem: whether the need to rely on the external delivery, or the addition of global variables.

What are the problems of modularity?


From the above attempts, can be summed up JS modularity needs to solve those problems:
1. How to safely package the code for a module? (Any code outside the module is not contaminated)
2. How do I uniquely identify a module?
3. How to gracefully leak out the API of the module? (Cannot add global variable)
4. How to facilitate the use of the dependent modules?


Around these problems, JS Modular began a difficult and tortuous journey.


Specifications derived from Nodejs Commonjs


2009, Nodejs turned out, creating a new era, people can use JS to write the server's code. If the browser side of the JS even if there is no modularity can be tolerated, the service end is absolutely not.


Daniel gathered in the Commonjs community, developed a modules/1.0 (http://wiki.commonjs.org/wiki/Modules/1.0) specification, for the first time defined a module should look like. Specifically, the MODULES/1.0 specification contains the following:

1. Module identification should follow the rules (writing specifications)
2. Define global function require, introduce other modules by passing in the module identifier, the result of which is the API of other module burst out
3. If the modules introduced by the Require function also contain dependencies, then load the dependencies sequentially
4. If the Introduction module fails, then the Require function should report an exception
5. The module passes the variable exports to yearn for the leak api,exports can only be an object, the API of the leak must act as the attribute of this object.

This specification, immediately produced a good effect, because of its simple and direct, in the NODEJS, this modular scheme was immediately promoted.
The code that follows the COMMONJS specification looks like this: (from an official example)


Math.js
Exports.add = function () {
var sum = 0, i = 0, args = arguments, L = args.length;
while (I < L) {
Sum + + args[i++];
}
return sum;
};


Increment.js
var add = require (' math '). Add;
Exports.increment = function (val) {
Return Add (val, 1);
};



Program.js
var inc = require (' increment '). Increment;
var a = 1;
Inc. (a); 2


service to the front end of the March


The modules/1.0 specification originates from the server and cannot be used directly from the browser side, for the following reasons:
1. The outer layer has no function package, the variable total leakage in the overall situation. As in the example above, add in Increment.js.
2. Resources are loaded in a completely different way from the service side. The service side require a module that is read directly from the hard disk or in memory, and the time consumed can be ignored. The browser is different, you need to download the file from the server, and then run the code inside to get the API, the need to spend an HTTP request, that is, require the next line of code, requires a resource request to complete to execute. Because the browser end is in the form of inserts <script> tags to load resources (Ajax way not, there are cross-domain problems), there is no way to allow the code to execute synchronously, so the way like Commonjs will directly error.

So the community realizes that the specification needs to be upgraded in order to be modular in a browser environment. By the way, Commonjs originally is called Serverjs, from the name can be seen is specialized service side, in order to unify the front and back end and renamed Commonjs. (on the importance of name ~)
And in the community discussion on the next edition of the norms, the internal occurrence of a relatively large differences, split out three ideas, gradually formed three different factions:


1.modules/1.x Pie
This wave of people think that on the existing basis to improve to meet the needs of the browser, since the browser needs function packaging, the need for asynchronous loading, then a new scheme to convert the existing modules to the browser-side on the line, a bit like "royalist." Based on this proposition, the Modules/transport (http://wiki.commonjs.org/wiki/Modules/Transport) specification was developed, and a tool was proposed to transform existing modules into modules used on composite browsers. Then use the scheme again.
Browserify is a tool that compiles NODEJS modules into modules that are available in the browser. (Modules/transport specification Obscure, I am not sure what browserify is related to it, have a friend who know can talk about)
The latest version is modules/1.1.1 (http://wiki.commonjs.org/wiki/Modules/1.1.1), adding some require attributes, and adding module variables to describe module information.


2. The Modules/async faction
This wave of people is a bit like "reformist", they think the browser and server environment is too different, can not use the old module standard. Now that the browser must load the code asynchronously, the module must indicate the dependent module when it is defined, and then write the code for the module in the callback function. Module loading is also through the download-callback such a process to carry out, the idea is the basis of AMD, because the "reformist" and "royalist" ideas can not be agreed, and eventually split from the COMMONJS, independently developed a browser-side JS Modular specification AMD (asynchronous Module Definition) (Https://github.com/amdjs/amdjs-api/wiki/AMD)
The content of the AMD specification will continue to be discussed later in this article.


3. The modules/2.0 faction
This wave of people is a bit like "centrist", neither want to lose the old norms, and do not want to like AMD to push back. They think that modules/1.0 is not suitable for browsers, but some of its ideas are good, (such as through the require to declare dependencies), the new specification should be compatible with these, AMD specifications also have its good places (such as the preload of modules and the commonjs of any type of data by return, rather than exports as object) and should be adopted. Eventually they developed a modules/wrappings (http://wiki.commonjs.org/wiki/Modules/Wrappings) specification that identifies how a module should be "packaged" and contains the following:

1. There is a module variable globally to define the modules
2. Define a module by Module.declare method
3. The Module.declare method receives only one parameter, that is the factory of the module, the factory can be a function or an object, and if it is an object, then the module output is this object.
4. Module factory function passed in three parameters: Require,exports,module, used to introduce other dependencies and export this module API
5. If the factory function is finally explicitly written with return data (JS function does not write back to the default returned undefined), then returns the content is the module output.

Examples of using this specification look like this:


can use Exprots to external burst API
Module.declare (function (Require, exports, module)
{
Exports.foo = "Bar";
});




You can also return directly to the external leak data
Module.declare (function (Require)
{
return {foo: ' Bar '};
});



The rise and Compromise of Amd/requirejs


The idea of AMD, like its name, loads the required modules asynchronously, and then executes the main logic in the callback function. This is what we are accustomed to in the browser-side development, and its authors have personally implemented the AMD-compliant Requirejs,amd/requirejs quickly accepted by the broad masses of developers.
The AMD specification includes the following:

1. Use the global function define to define the module, the usage is: define (ID, dependencies?, factory);
2. ID for module identification, compliance with COMMONJS module identifiers specification
3. Dependencies is a dependent array of modules, in the factory need to be introduced into one of the form of a corresponding
4. If the value of dependencies has "require", "exports" or "module", it is consistent with the implementation in COMMONJS
5. If dependencies omitted to write, the default is ["Require", "exports", and "module"],factory will also be passed in Require,exports,module
6. If factory is a function, the module has three kinds of methods for external burst-leakage API: Return any type of data, exports.xxx=xxx, module.exports=xxx
7. If factory is an object, the object is the return value of the module

Based on the above several basic specifications, we can use this way to the modular organization code:

A.js
Define (function () {
Console.log (' a.js execution ');
return {
Hello:function () {
Console.log (' Hello, a.js ');
}
}
});




B.js
Define (function () {
Console.log (' b.js execution ');
return {
Hello:function () {
Console.log (' Hello, b.js ');
}
}
});




Main.js
Require ([' A ', ' B '], function (A, b) {
Console.log (' main.js execution ');
A.hello ();
$ (' #b '). Click (function () {
B.hello ();
});
})


When the above main.js is executed, the following output is available:
A.js execution
B.js execution
Main.js execution
Hello, a.js.
After clicking the button, the output is:
Hello, b.js.
Is this the end, as you wish? In general, there is no problem, because the two hello methods you want are executed correctly.
But if you look carefully, the b.js is preloaded and executed beforehand, (the second line of output), B.hello This method is not executed after clicking the button, if the user has no point, then the code in B.js should not be executed?
This is actually a point of Amd/requirejs is spit, the pre-download is not controversial, due to the browser's environmental characteristics, the dependent modules must be downloaded beforehand. The question is, does it need to be implemented in advance? If a module relies on 10 other modules, execute the code of the other 10 modules before the code in the module is executed, regardless of whether the modules are being used immediately. This performance consumption cannot be overlooked.
Another point to be spit is that when defining a module, all of the dependent modules are listed, and also in the factory as a formal parameter, to write two times a very big string module name, like this:

define ([' A ', ' B ', ' C ', ' d ', ' e ', ' f ', ' G '], function (A, B, C, D, E, F, g) {...})

The coding process is slightly uncomfortable.


The good point is that AMD retains the three features of the Require, Exprots, and module in Commonjs (the 4th mentioned above). You can also not list the dependencies in the dependencies array. Instead, it is introduced in code with require, as follows:


Define (function () {
Console.log (' main2.js execution ');

Require ([' a '], function (a) {
A.hello ();
});

$ (' #b '). Click (function () {
Require ([' B '], function (b) {
B.hello ();
});
});
});


We do not specify dependencies in the parameters of the define, then main2.js in the execution, it will not preload a.js and B.js, only to execute to the require when the statement will be loaded, the output of the above code is as follows:
Main2.js execution
A.js execution
Hello, a.js.
You can see that b.js is not executed, and b.js is not downloaded from the network request. The b.js is downloaded only when the button is clicked, and the method in the module is executed in the callback function. This is the veritable "lazy load".

This lazy load will undoubtedly greatly reduce the loss of initialization (download and execution are omitted), but the drawbacks are obvious, In the subsequent execution of A.hello and B.hello, the code must be downloaded in real time and then executed in the callback, such a user experience is not good, the user's operation will have a significant delay in the cotton.
But this reality is not unacceptable, after all, is the browser environment, we have been accustomed to the operation of the Web pages accompanied by various loading ...

But then, is there a better way to deal with the problem? The download phase of the resource is still in advance, and the resource execution phase is later placed until it is needed. Such a compromise approach can combine the advantages of the previous two ways, while avoiding shortcomings.
This is the modules/wrappings specification, do you remember the "centrist" mentioned earlier?
In the AMD camp, some people put forward the view that it is disgusting to write a bunch of callbacks in the code, and they prefer to use modules like this:


var a = require (' a ');
A.hello ();

$ (' #b '). Click (function () {
var B = require (' B ');
B.hello ();
});


As a result, AMD finally decided to compromise, compatible with modules/wrappings, but only partially compatible, for example, did not use Module.declare to define the module, but still with define, the timing of the implementation of the module has not changed, is still in advance implementation. Therefore, AMD will this compatibility is called Simplified commonjs wrapping, that is not a complete implementation of modules/wrappings.
With this compatibility, you can use Requirejs to write code like this:


D.js
Define (function (Require, exports, module) {
Console.log (' d.js execution ');
return {
Helloa:function () {
var a = require (' a ');
A.hello ();
},
Run:function () {
$ (' #b '). Click (function () {
var B = require (' B ');
B.hello ();
});
}
}
});


Note the slight difference in the definition of the module, the dependencies array is empty, but the parameters of the factory function must be written by hand require,exports,module (this is different from the previous dependencies and factory formal parameters are not written). This can be done using the simplified COMMONJS wrapping style, consistent with the COMMONJS format.
Although the use of seemingly simple, but in the understanding of the future generations buried a big hole. Because AMD only supports such syntax, and does not really implement the delay of the implementation of the module. What does that mean? The above code, normally should be to download a.js and B.js, and then execute the module's Helloa method to start executing the a.js inside the code, click on the button when the start of the implementation of the B.js method. This is not the case, as long as the module is introduced by another module, the code in A.js and B.js is executed in advance.
We name the above code d.js and use it somewhere else:

Require ([' d '], function (d) {

});

The code above will output
A.js execution
B.js execution
D.js execution
As you can see, the code in the A.js and b.js that is dependent on it has been executed, although the API for the D module has not yet been invoked. AMD's practice of only implementing syntax but not actually realizing the function is easy to cause understanding difficulties, is strongly spit.
(in requirejs2.0, the author declares that the issue has been addressed (https://github.com/jrburke/requirejs/wiki/Upgrading-to-RequireJS-2.0#delayed), But when I use the 2.1.20 version of the test will still be in advance, I am a bit confused about the reason, if you understand the master please advise)

An all-inclusive Cmd/seajs
Since Requirejs have the above all kinds of not very elegant place, so there will be something new to perfect it, this is the seajs,seajs of the Up-and-comer is the domestic Daniel Taobao front steps Yuber. Seajs fully embraced the Modules/wrappings specification and wrote the module without Requirejs that way to callback. And it is not exactly in accordance with the Modules/wrappings specification, SEAJS did not use declare to define the module, but to use the same define as Requirejs, perhaps the author prefers this name. (However, this is more or less confusing to people in understanding), the SEAJS definition module is written as follows:


A.js
Define (function (Require, exports, module) {
Console.log (' a.js execution ');
return {
Hello:function () {
Console.log (' Hello, a.js ');
}
}
});




B.js
Define (function (Require, exports, module) {
Console.log (' b.js execution ');
return {
Hello:function () {
Console.log (' Hello, b.js ');
}
}
});




Main.js
Define (function (Require, exports, module) {
Console.log (' main.js execution ');

var a = require (' a ');
A.hello ();

$ (' #b '). Click (function () {
var B = require (' B ');
B.hello ();
});

});


When you define a module without listing the dependent arrays, you need to pass in the formal parameter Require,exports,module in the factory function, and then it calls the ToString method of the factory function, which matches the contents of the function. By matching the Require statement to analyze dependencies, so that the real implementation of the COMMONJS-style code.
The above main.js execution will output as follows:
Main.js execution
A.js execution
Hello, a.js.
A.js and B.js will be downloaded in advance, but the code in B.js is not executed because there is no click on the button. When the button is clicked, the output is as follows:
B.js execution
Hello, b.js.
You can see the code in B.js to execute at this time. In this way, the real implementation of the "close to writing, delayed execution," is not elegant.

If you have to pick a bit of a bad thing, it is the b.js of the pre-download. You may not want to download all the resources from the beginning, and hope to download the B.js again when you click on the button, like Requirejs. In an inclusive way, SEAJS also implements this function, providing the Require.async API, which is simply written when the button is clicked:

var B = Require.async (' B ');
B.hello ();

B.js will not be loaded at the beginning. This API can be said to be simple and beautiful.

On the module of the external leakage of the API, SEAJS is also a combination of the length of the home, supporting the commonjs of exports.xxx = xxx and module.exports = xxx, but also support AMD return writing, exposed API can be any type.

You may think that Seajs is nothing more than a copy, the advantages of other people's home are copied over the combination. In fact, SEAJS is the COMMONJS standard in the browser-side of the practitioner, for the advantages of REQUIREJS also be absorbed. Look at the name of others, is the sea of the meaning of hundred rivers. (again on the importance of the name ~), since its idea is the Sea Na Chuan, the discussion is not copied on the meaningless.
In view of the Seajs fusion too many things, has not been able to say that it followed which norms, so Yuber simply on their own, named Yue cmd (Common Module Definition) norms, with a program, there will be no more criticism.

Future-oriented ES6 module standards
Since the voice of the modular development is so high, as the official ECMA must act, JS module very early in the draft, finally published in June 2015 ES6 official version. However, perhaps because the technology involved is not yet ripe, ES6 removes content about modules such as Hegazai/execution, leaving only the syntax for defining and introducing modules. So now the ES6 module is still a prototype, semi-finished products are not. But that doesn't prevent us from prying into the ES6 module standards.
Defining a module does not require specialized work, because the function of a module is to provide APIs externally, so just export with exoprt:

Way One, a.js
Export var a = 1;
Export var obj = {name: ' abc ', AGE:20};
Export function Run () {...}



Mode two, B.js
var a = 1;
var obj = {name: ' abc ', AGE:20};
function run () {...}
Export {A, obj, run}


Use the Import keyword when using modules, such as:

Import {Run as Go} from ' a '
Run ()

If you want to use all of the APIs in your module, you may not have to list each one, and you can use the Module keyword to introduce it all:

Module Foo from ' A '
Console.log (Foo.obj);
A.run ();

Indicate the API you want to use in curly braces, and you can specify an alias with AS.

The basic usage of ES6 module is that it can be seen to be somewhat weak, and currently there is no browser to support it, only to say that it is facing the future.
We can now use a number of third-party modules to compile the ES6 into a ES5 code that can be used, or a module that complies with AMD specifications, such as ES6 module Transpiler. Another project also provides a way to load ES6 modules, Es6-module-loader (https://github.com/ModuleLoader/es6-module-loader), but this is a temporary solution, Perhaps next year ES7 a release, the module loading has the standard, the browser has given the realization, these tools also did not have the function.

The future is still worth looking forward to, from the language standard support modularity, JS can be more confident into large-scale enterprise-level development.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.