The target
- Learn the history of modularity
- Modern modular specification
- Easy handwriting implementation
The main points of
The history of modularity
1. What is a module?
-
The internal data and implementation of a block are private and expose interfaces (methods) to communicate with other external modules
-
Encapsulate a complex program into several blocks (files) according to certain rules (specifications) and combine them together
-
In essence module is a kind of external communication interface, code segmentation/combination of management. The way it is rendered varies from modular solution to modular solution, basically by file granularity.
2. Why modularity?
-
With the development of the front end and the gradual separation of the front end, but also with the front end capabilities in depth have been enhanced, the market has decided that the need for better code management, organization, communication mode, also accompanied by a variety of modular technology mode.
-
As projects get larger, breaking them into modules reduces complexity and allows for more elegant code management
-
Modularity facilitates collaborative development and process oriented development
-
Independent distribution of a single module – more compatible with microservices
3. History of modular development
3.1 Pre-modularization in the chaotic period, the organization of code depends on “experience” and basically is the global function mode: different functions are encapsulated into different global functions
Disadvantages: contaminates the global namespace, causing naming conflicts or data insecurity, and no direct relationship between module members
function fn () {};
Copy the code
3.2 Namespace-Namespace mode: Encapsulate simple objects
Disadvantages: Data insecurity (external data can be directly modified inside the module)
var student = {
name: 'tom'.getScore: function() {}
}
student.name;
student.getScore();
Copy the code
3.3 IIFE Mode: Anonymous Function Invocation (closure)
- Advantages: Modular solutions are improved again, using closures to solve pollution problems, more pure cohesion.
- Weakness: old driver 🛴 has opened Baidu
What if the current module depends on another module?
// moduleA.js
(function(global) {
var name = 'tom';
function getScore() {};
// Mount the module to global variables and expose them
global.moduleA = { name, getScrore }; }) (window)
Copy the code
There are many more tricks based on IIFE, which are the cornerstone of modern modular implementations 🛠
4. In general, modern modularization mechanisms address the following issues:
- Naming pollution, global pollution, variable conflict and other basic problems
- Cohesive and private, variables cannot be contaminated by the outside world
- How do I import (rely on) other modules and expose interfaces to other modules
- The most annoying is the dependency order problem, old drivers still remember the previous Jquery problem 😩
- Introducing modules can lead to problems with circular references and other boundary cases
Modular specification
Brief explanation, detailed explanation please find Baidu niang
– CommonJS
Node.js adopts CommonJS module specification, and the loading module is synchronous. Module. exports exports exports require(XXX) import module, XXX is the module name; For a custom module, XXX is the module file path
// moduleA.js
let a = 5;
const add = function (value) {
return value + a;
};
module.exports.a = a;
module.exports.add = add;
Copy the code
const moduleA = require('./moduleA.js');
console.log(moduleA.a); // 5
console.log(moduleA.add(1)); // 6
Copy the code
– AMD
- The AMD specification is an asynchronous loading module that allows you to specify callback functions
- There is no doubt that AMD is represented by the well-known RequireJS
- Module preloading: as the name implies, it is the first to introduce the required module, similar to jquery, but the advantage is not like jquery, it must strictly follow the order of introduction, otherwise it will report errors.
The ferrous module
define(['moduleA'].function(_) {
console.log('moduleA load');
return {
str: function() {
console.log('moduleA run');
// Business implementation...
return _.repeat('> > > > > > > > > >'.20)}}})Copy the code
The introduction of the module
require(['moduleA'].function(moduleA){
// Use moduleA's variables and methods
console.log(moduleA.str())
})
Copy the code
Why can define and require be used directly? Ok, it ⛏ ⛏ ⛏
const def = new Map(a);// define the module to trigger when require, so rely on -> collect first
define = (name, deps, factory) = > {
/** * deps relies on the factory callback function */
def.set(name, { name, deps, factory });
}
// Triggers loading dependencies
require = (deps, factory) = > {
return new Promise((resolve, reject) = > {
Promise.all(deps.map(dep= > {
// Create a new script tag and place it in the head
return __load(dep).then(() = > {
const { deps, factory } = def.get(dep);
if (deps.length === 0) return factory(null);
// The recursive processing module is nested with submodules
return require(deps, factory)
})
})).then(resolve, reject)
})
.then(instances= >factory(... instances)) }Copy the code
See here still don’t understand AMD how to implement call me home service ☎
– CMD
The CMD specification is designed specifically for the browser side. Modules are loaded asynchronously and executed only when they are in use. The CMD specification incorporates the features of the CommonJS and AMD specifications. In Sea-.js, all JavaScript modules follow the CMD module definition specification.
Go to CSDN, Blog Garden, SegmentFault…
// sea.js
// This callback takes three arguments: require, exports, module
define('a'.function (require.exports.module) {
console.log('a load')
exports.run = function () { console.log('a run') }
})
define('b'.function (require.exports.module) {
console.log('b load')
exports.run = function () { console.log('b run') }
})
define('main'.function (require.exports.module) {
console.log('main run')
// CMD is basically load on demand
// Import modules only when needed
var a = require('a')
a.run()
var b = require('b')
b.run()
})
sj.use('main')
// main run
// a load
// a run
// b load
// b run
Copy the code
Word-wrap: break-word! Important; “> < p style =” max-width: 100%
// Use regular expressions to get all modules of require
const getDepsFromFn = (fn) = > {
let matches = [];
// require('a ')
/ / 1. (? :require\() -> require( -> (? :) non-trapping grouping
/ / 2. (? :['"]) -> require('
//3. ([^'"]+) -> a -> Avoid backtracking -> Backtracking state machine
let reg = / (? :require\()(? :['"])([^'"]+)/g; // RegExp
let r = null;
while((r = reg.exec(fn.toString())) ! = =null) {
reg.lastIndex
matches.push(r[1])}return matches
}
// define to extract dependencies
const define = (id, factory) = > {
const url = toUrl(id); // the url is the path to the parsed module
const deps = getDepsFromFn(factory); // Get all modules of require
if(! modules[id]) { modules[id] = { url, id, factory, deps } } }const __exports = (id) = > exports[id] || (exports[id] = {});
const __module = this;
// __require introduces and loads modules
const __require = (id) = > {
// require('a') id is a
// the _load method is omitted by creating a new script tag SRC in the head to introduce the corresponding dependency
return __load(id).then(() = > {
// After loading
const { factory, deps } = modules[id];
if(! deps || deps.length ===0) {
// define's callback function requires, exports, module
factory(__require, __exports(id), __module);
return __exports(id);
}
returnsj.use(deps, factory); })}/ / implementation __require
sj.use = (modules, callback) = > {
mods = Array.isArray(modules) ? modules : [modules];
return new Promise((resolve, reject) = > {
Promise.all(modules.map(module= > {
// the _load method is omitted by creating a new script tag SRC in the head to introduce the corresponding dependency
return __load(module).then(() = > {
const { factory } = modules[mod];
return factory(__require, __exports(modules), __module)
})
})).then(resolve, reject)
}).then(instances= >callback && callback(... instances)) }Copy the code
Same as above ~ see don’t understand CMD how to realize call my door-to-door service ☎
– ES6 modular
The ES6 module is designed to be as static as possible, so that the module dependencies, as well as the input and output variables, can be determined at compile time. Both CommonJS and AMD modules can only determine these things at runtime.
// The export command is used to specify the external interface of a module, and the import command is used to input functions provided by other modules.
// add.js
let num = 0;
const add = function (a, b) {
return a + b;
};
export { num, add };
/** references the module **/
import { num, add } from './add';
console.log(add(10 + num))
Copy the code
Import export low-level implementation will be supplemented after I summarize, the future can be 😁😁
Supplementary knowledge
The CommonJS module outputs a copy of the value
- CommonJS
// a.js
let name = 'morrain'
let age = 18
exports.name = name
exports.age = age
exports.setAge = function(a){
age = a
}
// b.js
var a = require('a.js')
console.log(a.age) / / 18
a.setAge(19)
console.log(a.age) / / 18
Copy the code
In A. js, exports is assigned to an object (temporarily called the exported object), and the age attribute of the exported object is derived from the age variable. As the age variable is a numeric type, belonging to one of the basic types of JS and passed by value, the age attribute only gets the copy value of the age variable. That is, any change in the age variable since the assignment has nothing to do with the count attribute of the exported object.
- ES6
The ES6 module outputs references to values
// a.js
let name = 'xuSir jun'
let age = 18
exports.name = name
exports.getAge = function(){
return age
}
// b.js
var a = import('a.js')
console.log(a.name) / / 'xuSir jun'
a.name = 'rename'
var b = import('a.js')
console.log(b.name) // 'rename'
Copy the code
Unlike the export specification provided by Commonjs, ES Module exports are references to values, and the loaded value of an import changes when the original value changes.
🎈🎈🎈