Article directory
- 1. Understanding modularity
-
- 1.What is modularity
- 2. The history of modularity
- 3. No problems caused by modularity
- 2. CommonJS and Node
-
- 1. CommonJS specification and Node relationship
- 2. Modular case
- 3.exports export
- 4.module.exports export
- 5. Change the imported object
- 6.require search module rules
- 7. Module loading process
- 3. AMD
-
- 1.Disadvantages of CommonJS specification
- 2.AMD specifications
- 3.Usage of require.js
- 4. CMD specifications
-
- 1.CMD specification
- 2.Usage of SeaJS
- 5. ES Module
-
- 1. Get to know ES Module
- 2. Case code problem analysis
- 3.export keyword
- 4.import keyword
- 5. Use export and import in combination
- 6.default usage
- 7.import function
- 6. ES Module implementation principle
-
- 1.ES Module analysis process
- 2. Phase 1: Construction Phase
- 3. Phases 2 and 3: Instantiation Phase – Evaluation Phase
1. Understand modularity
1. What is modularity
What exactly are modularization and modular development?
-
In fact, the ultimate goal of modular development is to
divide the program into small structures
; -
Write
your own logical code
in this structure and have its ownscope
. When defining variable nouns,will not affect other structures
; -
This structure can export the variables, functions, objects, etc. that you want to
expose
to its structure for use; -
You can also
import
variables, functions, objects, etc. in other structures in a certain way;
The structure mentioned above is a module; the process of developing a program according to this structure is the process of modular development
;
No matter how much you love JavaScript and how well it’s developed, it has a lot of flaws:
- For example, the problem of
variable scope
defined by var; - For example, the object-oriented nature of JavaScript cannot use classes like conventional object-oriented languages;
- For example, JavaScript does not have modularity issues;
For the early JavaScript without modularity, it did bring a lot of problems;
2. The history of modularization
In the early days of web development, Brendan Eich developed JavaScript only as a scripting language
to do some simple form validation
or animation implementation
, etc., that The code is still very small:
- At this time, we only need to write the JavaScript code into the
tag;
- There is no need to write it in multiple files; it is even popular: JavaScript programs are usually
only one line
in length.
But with the rapid development of front-end and JavaScript, JavaScript code becomes more and more complex:
- The emergence of ajax,
separation of front-end and front-end development
, means that after the back-end returns data, we need torender the front-end page
through JavaScript; - With the emergence of SPA, front-end pages have become more complex: a series of complex requirements including
front-end routing
,state management
, etc. need to be implemented through JavaScript; - Including the implementation of Node, writing complex back-end programs in JavaScript, the lack of modularity is a fatal flaw;
Therefore, modularization is already a very urgent need for JavaScript:
However, JavaScript itself did not launch its own modular solution until ES6
(2015);
Prior to this, in order to allow JavaScript to support modularization, many different modularization specifications emerged: AMD
, CMD
, CommonJS
, etc.;
In our course, I will explain modularity in JavaScript in detail, especially with CommonJS and ES6.
3. There are no problems caused by modularity
The lack of modularity in the early days caused many problems: such as naming conflicts
Of course, we have a way to solve the above problem: Immediate function invocation expression
(IIFE)
- IIFE (Immediately Invoked Function Expression)
However, we actually brought new problems:
- First, I must remember the name of the
return object
in each module so that I can use it correctly when using other modules; - Second, the code is
messy
to write, and the code in each file needs to be wrapped in an anonymous function to write; - Third, in the absence of appropriate specifications, everyone and every company may
name it arbitrarily
, or even havemodule names that are the same
;
Therefore, we will find that although modularization is achieved, our implementation is too simple and unstandardized.
- We need to formulate certain specifications to constrain everyone to write modular code according to this specification;
- This specification should include core functions: the module itself can export exposed properties, and the module can import the properties it needs;
JavaScript Community
In order to solve the above problems, a series of useful specifications have emerged. Next, we will learn some representative specifications.
2. CommonJS and Node
1. The relationship between CommonJS specifications and Node
We need to know that CommonJS is a standard
. It was originally proposed for use outside browsers
and was named ServerJS
at the time. Later, it was To reflect its breadth, it was modified to CommonJS
. We usually call it CJS
for short.
-
Node
is a representativeimplementation
of CommonJS on theserver side
; -
Browserify
is an implementation of CommonJS inBrowser
; -
The
webpack
packaging tool hassupport and conversion
for CommonJS;
Therefore, CommonJS is supported and implemented in Node, allowing us to easily carry out modular development during the development of node:
-
Each js file in Node is a
separate module
; -
This module includes the core variables of the CommonJS specification:
exports
,module.exports
,require
; -
We can use these variables to facilitate
modular development
;
We mentioned earlier that the core of modularity is export
and import
, which are implemented in Node:
exports
andmodule.exports
can be responsible for exporting the content in the module;- The
require
function can help us import content from other modules (custom modules, system modules, third-party library modules);
2. Modular case
Let’s take a look at the following 2 files
3.exports export
Note: exports is an object
. We can add many attributes to this object, and the added attributes will be exported;
// bar.js const name = "zhangsan" const age = 18 function sayHello(name) {<!-- --> console.log("hello " + name) } exports.name = name exports.age = age exports.sayHello = sayHello
You can import it in another file:
// main.js const bar = require("./bar") console.log(bar.name) console.log(bar.age) bar.sayHello(bar.name)
What does the above line accomplish? Understand the following sentence, the modularity in Node is clear at a glance
- It means that the bar variable in main is equal to the exports object;
- That is to say, require finally found the exports object through various search methods;
- And assign this exports object to the bar variable;
- The bar variable is the exports object;
4.module.exports export
But when we often export things in Node, we export them through module.exports
:
- What is the relationship or difference between
module.exports
andexports
?
We trace the source through the analysis of the CommonJS specification in Wikipedia:
-
There is no concept of module.exports in CommonJS;
-
But in order to realize the export of modules, Node uses the
Module
class. Each module is aninstance
of Module, that is, module; -
Therefore, what is actually used for export in Node is actually not exports at all, but module.exports;
-
Because module is the real implementer of export;
But why can exports also be exported?
- This is because the exports attribute of the module object is a
reference
of the exports object; - That is to say, module.exports = exports = bar in main;
const name = "zhangsan" const age = 18 function sayHello(name) {<!-- --> console.log("hello " + name) } module.exports = {<!-- --> name, age, sayHello } // Does not take effect because module.exports is exported // exports = {<!-- --> // name, // age, // sayHello // }
5. Change the imported object
Let's study what happened when modifying the code from several aspects here?
-
When the three projects are referenced, what exactly happens when the name attribute in exports is modified?
The value of the importer will also be modified together.
-
In the case where the three are referenced, if the name attribute of bar in main is modified, what will happen in the bar module?
The value of the exporter will also be modified together.
-
If module.exports no longer refers to the exports object, is there any point in modifying export?
No sense anymore
6.require search module rules
We now know that require
is a function that can help us introduce exported objects
in a file (module).
So, what are the search rules for require?
- Here I summarize the more common search rules:
- The import format is as follows: require(X)
Case 1: X is a Nodecore module
, such as path, http
require("path")
- Return directly to the core module and stop searching
Case 2: X starts with ./
or ../
or /
(root directory)
require("./abc/cba.js")
Step 1: Treat X as a file
and search in the corresponding directory;
- If there is a suffix name, search for the corresponding file according to the format of the suffix name.
- If there is no suffix, the order will be as follows:
- Find file X directly
- Find X.js files
- Find X.json file
- Find X.node file
Step 2: If the corresponding file is not found, use X as a directory
Find the index
file under the directory
- Find the X/index.js file
- Find the X/index.json file
- Find the X/index.node file
- If not found, an error is reported: not found
Case 3: It is directly an X (no path), and X is not a core module
// Written in D:\BaiduSyncdisk\frontend\code\06JS_MODULE\02 Modular Development\01CommonJs\main.js require('abc')
[ 'D:\disk\frontend\code\06JS_MODULE\02 Modular development\01CommonJs\\ ode_modules', 'D:\disk\frontend\code\06JS_MODULE\02 Modular Development\\ ode_modules', 'D:\disk\frontend\code\06JS_MODULE\\ ode_modules', 'D:\disk\frontend\code\\ ode_modules', 'D:\disk\frontend\\ ode_modules', 'D:\disk\\ ode_modules', 'D:\\ ode_modules' ]
If none of the above paths are found, an error will be reported: not found
7. Module loading process
Conclusion 1: When the module is introduced for the first time, the js code in the module will be run once
Conclusion 2: When a module is introduced multiple times, it will be cached and ultimately loaded (run) only once.
- Why is it only loaded and run once?
- This is because every module object module has an attribute:
loaded
. - If it is false, it means it has not been loaded yet, if it is true, it means it has been loaded;
Conclusion 3: If there is a loop introduction, what is the loading order?
If the reference relationship of the modules in the figure below appears, what is the loading order?
- This is actually a data structure:
Graph structure
; - In the process of traversing the graph structure, there are depth first search (DFS, depth first search) and breadth first search (BFS, breadth first search);
- Node uses the
depth first algorithm
: main -> aaa -> ccc -> ddd -> eee ->bbb
3. AMD
1.Disadvantages of CommonJS specification
CommonJS loading modules are synchronous
:
-
Synchronous means that the content in the current module cannot be run until the corresponding module is loaded;
-
This will not be a problem on the server, because the js files loaded by the server are local files and the loading speed is very fast;
What about applying it to the browser?
- When the browser loads a js file, it needs to download the file from the server first, and then load and run it;
- Then using synchronization means that subsequent js codes will
not run properly
, even some simple DOM operations;
So in browsers we usually don't use the CommonJS specification:
- Of course, using CommonJS in
webpack
is another matter; - Because it will convert our code into code that the browser can directly execute;
In the early days, in order to use modularity in browsers, AMD
or CMD
was usually used:
- However, on the one hand, modern browsers already support
ES Modules
, and on the other hand, CommonJS or ES Module code can be converted with the help of tools such as webpack; - AMD and CMD already use
very little
;
2.AMD specifications
AMD is mainly a modular specification applied to browsers:
-
AMD is the abbreviation of Asynchronous Module Definition;
-
It uses the
asynchronous loading
module; -
In fact, AMD's specification is earlier than CommonJS, but CommonJS is still used, while AMD is used less;
As we mentioned, the specification only defines how the code should be written, and it can only be applied if there is a specific implementation:
- The more commonly used libraries implemented by AMD are
require.js
andcurl.js
;
Usage of 3.require.js
Step 1: Download require.js
- Download address: https://github.com/requirejs/requirejs
- Find the require.js file;
Step 2: Define the HTML script tag, introduce require.js and define the entry file:
- The function of the data-main attribute is to load and execute the file after loading the src file.
<script src="./lib/require.js" data-main="./index.js"></script>
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script src="./lib/require.js" data-main="./src/main.js"></script> </body> </html>
src/foo.js
define(function() {<!-- --> const name = "abc" const age = 18 function sum(num1, num2) {<!-- --> return num1 + num2 } return {<!-- --> name, age, sum } })
src/bar.js
define(["foo"], function(foo) {<!-- --> console.log("--------") // require(["foo"], function(foo) {<!-- --> // console.log("bar:", foo) // }) console.log("bar:", foo) })
src/main.js
require.config({<!-- --> baseUrl: '', paths: {<!-- --> foo: "./src/foo", bar: "./src/bar" } }) require(["foo", "bar"], function(foo) {<!-- --> console.log("main:", foo) })
4. CMD specification
1.CMD specification
The CMD specification is also a modular specification applied to browsers:
- CMD is the abbreviation of Common Module Definition;
- It also uses the
asynchronous loading
module, but it absorbs the advantages of CommonJS; - However, CMD is rarely used at present;
CMD also has its own excellent implementation plan:
- SeaJS
2.Usage of SeaJS
Step 1: Download SeaJS
- Download address: https://github.com/seajs/seajs
- Find sea.js in the dist folder
Step 2: Introduce sea.js
and use the main entry file
- seajs specifies the main entry file
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script src="./lib/sea.js"></script> <script> seajs.use("./src/main.js") </script> </body> </html>
src/foo.js
define(function(require, exports, module) {<!-- --> const name = "mq" const age = 18 function sum(num1, num2) {<!-- --> return num1 + num2 } // exports.name = name // exports.age = age module.exports = {<!-- --> name, age, sum } });
src/main.js
define(function(require, exports, module) {<!-- --> const foo = require("./foo") console.log("main:", foo) })
5. ES Module
1. Get to know ES Module
The lack of modularity in JavaScript has always been its pain point, which is why the community standards we studied earlier were produced: CommonJS, AMD, CMD, etc. So when ECMA launched its own modular system, everyone was extremely excited.
There are some differences between the modularization of ES Module and CommonJS:
- On the one hand, it uses the
import
andexport
keywords; - On the other hand, it uses
static analysis
at compile time, and also addsdynamic reference
;
The ES Module module uses the export and import keywords to implement modularization:
- export is responsible for exporting the content within the module;
- import is responsible for importing content from other modules;
Understand: Using ES Module will automatically adopt strict mode: use strict
2. Case code problem analysis
Here I demonstrate ES6 modular development in the browser:
<script type="module" src="main.js"></script>
If you run the code directly in the browser, the following error will be reported:
This is explained on MDN:
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules
- You need to pay attention to local testing - if you load an Html file locally (such as a file with a file:// path), you will encounter
CORS errors
because of Javascript module security requirements; - You need to test through a server;
The VSCode plug-in I use here: Live Server
3.export keyword
The export keyword exports variables, functions, classes, etc. in a module;
We want to export all other content, which can be done in the following ways:
- Method 1: Add the export keyword directly in front of the statement statement
- Method 2: Put all identifiers that need to be exported into {} after export
- Note: The
{}
here is not an enhanced way of writing object literals in ES6, nor does{}
represent an object; - Therefore:
export {name: name}
is wrong;
- Note: The
- Method 3: Give the identifier an alias when exporting
- Aliases using the
as
keyword
- Aliases using the
// 3. Export method three: export const name = "mq" export const age = 18 export function sayHello() {<!-- --> console.log("sayHello") } export class Person {<!-- -->} // console.log(name) // 1. Export method one: // export {<!-- --> // name, // age, // sayHello // } // 2. Export method two: give the identifier an alias when exporting // export {<!-- --> // name as fname, // age, // sayHello // }
4.import keyword
The import keyword is responsible for importing content from another module
There are also several ways to import content:
- Method 1: import {identifier list} from module’;
- Note: {} here is not an object, it only stores the contents of the imported identifier list;
- Method 2: Alias the identifier when importing
- Aliases using the as keyword
- Method 3: Use * to place
module function
on a module function object (a module object)
// 1. Import method one: // import { name, age, sayHello } from "./foo.js" // 2. Import method two: alias the identifier when importing // import { name as fname, age, sayHello } from "./foo.js" // 3. You can alias the entire module when importing import * as foo from "./foo.js" const name = "main" console.log(name) console.log(foo.name) console.log(foo.age) foo.sayHello()
5. Use export and import in combination
Supplement: export and import can be used in combination
Why do we do this?
- When developing and packaging a functional library, we usually want to put all exposed interfaces into one file;
- This makes it easy to specify
unified interface specifications
and is also convenient for reading; - At this time, we can use
export
andimport
in combination;
import {<!-- --> formatCount, formatDate } from './format.js' import {<!-- --> parseLyric } from './parse.js' export {<!-- --> formatCount, formatDate, parseLyric } // Optimization 1: // export { formatCount, formatDate } from './format.js' // export { parseLyric } from './parse.js' // Optimization 2: // export * from './format.js' // export * from './parse.js'
6.default usage
The export functions we learned earlier are all named exports
(named exports):
- The name is specified when exporting;
- You need to know the specific name when importing;
There is also an export called Default export
(default export)
- By default, you do not need to specify a name when exporting;
- There is no need to use {} when importing, and you can specify the name yourself;
- It also facilitates our interoperability with existing CommonJS and other specifications;
Note: In a module, there can only be one default export (default export);
// 1. Default export: // // 1.1. Define functions // function parseLyric() {<!-- --> // return ["lyrics"] // } // const name = "aaaa" // // export {<!-- --> // // parseLyric, // // name // // } // 1.2. Default export // export default parseLyric // 2. Define the identifier directly as the default export export default function() {<!-- --> return ["New Lyrics"] } // export default function() {<!-- --> // return ["lyrics"] // } // Note: A module can only have one default export
// import { parseLyric } from "./parse_lyric.js" import parseLyric from "./parse_lyric.js" console.log(parseLyric())
7.import function
Loading a module through import cannot be placed in logical code, such as:
if (condition) {<!-- --> import {<!-- --> age } from "./foo.js" }
Why does this happen?
- This is because the ES Module must know its dependencies when it is parsed by the JS engine;
- Since the js code is not running at this time, it is impossible to make judgments similar to if based on the execution of the code;
- Even the way to write the splicing path is wrong: because we
must determine the value of path at runtime
;
But in some cases, we really want to load a module dynamically:
- If the path to load the module is dynamically selected based on immovable conditions;
- At this time we need to use the
import()
function to dynamically load;- The import function returns a Promise, and the result can be obtained through then;
import {<!-- --> name, age, sayHello } from "./foo.js" console.log(name, age) // 2.Use of import function let flag = true if (flag) {<!-- --> // It is not allowed to write import declaration syntax in logical code, it can only be written to the top level of js code // import { name, age, sayHello } from "./foo.js" // console.log(name, age) // Only when the logic is indeed established, a module needs to be imported // import function // const importPromise = import("./foo.js") // importPromise.then(res => {<!-- --> // console.log(res.name, res.age) // }) import("./foo.js").then(res => {<!-- --> console.log(res.name, res.age) }) console.log("------") }
import.meta
is an object that exposes context-specific
metadata properties to JavaScript modules.
- It contains information about this module, such as the
URL
of this module; - New features in ES11 (ES2020);
export const name = "foo" export const age = 18 export function sayHello() {<!-- --> console.log("sayHello") } console.log(import.meta)
6. ES Module implementation principle
1.ES Module parsing process
How is ES Module parsed by the browser and allows modules to reference each other?
- https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/
The parsing process of ES Module can be divided into three stages:
- Phase 1:
Construction
(Construction), find the js file according to the address, download it, and parse it into a module record (Module Record); - Phase 2:
Instantiation
(Instantiation), instantiate the module record, allocate memory space, parse the import and export statements of the module, and point the module to the corresponding memory address. - Phase 3:
Run
(Evaluation), run the code, calculate the value, and fill the value into the memory address;