Reprint: front-end modular Comments

 

 

A modular understanding

1. What is a module?

  • A complex process based on certain rules (specification) packaged into several blocks (files), and combined
  • Internal data block is implemented with a proprietary, just expose some interfaces (methods) to the external communication with other external modules

  Set, the internal data and methods of a specific functional role private, externally accessible through the interface

2. The modular evolution

  • Globally function modes: different functions into different global function package
    • Code: different functions into different global function package
    • Question: polluting the global namespace , easily lead to conflict or named data insecurity, but do not see a direct relationship between the module members
function m1(){
  //...
}
function m2(){
  //...
}
  • 2.namespace mode: Simple Object Package
    • Role: reducing global variables, resolve naming conflicts
    • Problem: data is insecure (external data can be modified directly inside the module)
let myModule = {
  data: 'www.baidu.com',
  foo() {
    console.log(`foo() ${this.data}`)
  },
  bar() {
    console.log(`bar() ${this.data}`)
  }
}
myModule.data = 'other data' //能直接修改模块内部的数据
myModule.foo() // foo() other data

Such wording would be exposed to all the members of the module, the internal state can be externally rewritten.

  • 3.IIFE mode: anonymous call from function (closure)
    • Effect: the data is private, external exposure operation only by a method
    • Coded: the behavior and data encapsulated into an internal function, to the interface by adding attributes to expose outwardly window
    • Question: If this module is dependent on another module current how to do?
// index.html文件
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
    myModule.foo()
    myModule.bar()
    console.log(myModule.data) //undefined 不能访问模块内部数据
    myModule.data = 'xxxx' //不是修改的模块内部的data
    myModule.foo() //没有改变
</script>
// module.js文件
(function(window) {
  let data = 'www.baidu.com'
  //操作数据的函数
  function foo() {
    //用于暴露有函数
    console.log(`foo() ${data}`)
  }
  function bar() {
    //用于暴露有函数
    console.log(`bar() ${data}`)
    otherFun() //内部调用
  }
  function otherFun() {
    //内部私有的函数
    console.log('otherFun()')
  }
  //暴露行为
  window.myModule = { foo, bar } //ES6写法
//完整写法 window,myModule={foo:foo,bar:bar} })(window)

Finally, the results obtained:

  • IIFE Enhanced mode: introducing dependent

This is the cornerstone of modern module implements

// module.js文件
(function(window, $) {
  let data = 'www.baidu.com'
  //操作数据的函数
  function foo() {
    //用于暴露有函数
    console.log(`foo() ${data}`)
    $('body').css('background', 'red')
  }
  function bar() {
    //用于暴露有函数
    console.log(`bar() ${data}`)
    otherFun() //内部调用
  }
  function otherFun() {
    //内部私有的函数
    console.log('otherFun()')
  }
  //暴露行为
  window.myModule = { foo, bar }
})(window, jQuery)
 // index.html文件
  <!-- 引入的js必须有一定顺序 -->
  <script type="text/javascript" src="jquery-1.10.1.js"></script>
  <script type="text/javascript" src="module.js"></script>
  <script type="text/javascript">
    myModule.foo()
  </script>

Jquery method by the example of the background color of the page to red, so must the introduction of the jQuery library, put the library as a parameter. In addition to doing so to ensure the independence of the module, so that further dependencies between modules become apparent.

3. The benefits of modularity

  • Avoid naming conflicts (reducing namespace pollution)
  • Better separation, demand loading
  • Higher reusability
  • High maintainability

4. The introduction of a plurality of <script>after problems

  • Too many requests

First we have to rely on a number of modules, so it will send multiple requests, leading to excessive requests

  • Dependence blur

We do not know what their specific dependencies are , that is very easy because they do not understand the dependencies between lead load sequencing error.

  • Difficult to maintain

These two reasons it has led to difficult to maintain, indeed affect the situation as a whole is very likely lead to project serious problems.
The modular course has several advantages, however, a need to introduce multiple page js file, there will be more of these problems. These problems can be solved by a modular specification, the following describes the development of the most popular commonjs, AMD, ES6, CMD specification.

Second, the modular specification

1.CommonJS

The current node is used CommonJs specification

(1 Overview

Node Application is composed of modules, using CommonJS module specification. Each file is a module, has its own scope. In a document which defined variables, functions, classes are private, not visible to other documents. On the server side, the load module is loaded run-time synchronization; on the browser side, the module compiler package deal in advance.

(2) features

  • All code runs in the module scope , does not pollute the global scope.
  • Modules can be loaded more than once, but will only run once when you first load, then run the results are cached, and later loaded directly read cache results. To get the module to run again, you must clear the cache.
  • Sequentially loaded modules, the order in which they appear in the code.

(3) The basic syntax

  • Exposure module: module.exports = valueorexports.xxx = value
  • The introduction of modules: require(xxx)If a third-party modules, xxx is the module name; if it is a custom module, xxx for the module file path

Here we have a question: What CommonJS exposure module in the end is?  CommonJS specification, each internal module, module variable represents the current module. This variable is an object, its exports attributes (ie module.exports) is the external interface. Load a module, in fact, it is loaded module.exports properties of the module.

// example.js
var x = 5;
var addX = function (value) {
  return value + x;
};
module.exports.x = x;
module.exports.addX = addX;

The above code by the variable x and output functions module.exports addX.

var example = require('./example.js');//如果参数字符串以“./”开头,则表示加载的是一个位于相对路径
console.log(example.x); // 5
console.log(example.addX(1)); // 6

require command is used to load module file. Basic functions require command that reads and executes a JavaScript file, and then returns the module exports the object. If you do not find the specified module error.

(4) the module loading mechanism

Note: CommonJS module loading mechanism that is a copy of the input value is outputted. That is, once a value output changes inside the module will not affect the value. This is a major difference with the ES6 modular (will explain later), consider the following example:

// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};

The above code output internal variables internal counter and rewriting method incCounter this variable.

// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;

console.log(counter);  // 3
incCounter();
console.log(counter); // 3

Codes described above, after the counter output changes within the module lib.js not affecting the counter. This is because the counter value is a primitive type, it will be cached. Unless written as a function, to get the value of internal changes.

 

(5) The server-side implementation

① download and install node.js

② to create a project structure

Note: Automatically generated package.json with npm init time, package name (package name) can not have Chinese and capital

|-modules
  |-module1.js
  |-module2.js
  |-module3.js
|-app.js
|-package.json
  {
    "name": "commonJS-node",
    "version": "1.0.0"
  }

③ download third-party modules

npm install uniq --save // 用于数组去重

④ definition module codes

//module1.js
module.exports = {
  msg: 'module1',
  foo() {
    console.log(this.msg)
  }
}
//module2.js
module.exports = function() {
  console.log('module2')
}
//module3.js
exports.foo = function() {
  console.log('foo() module3')
}
exports.arr = [1, 2, 3, 3, 2]
// app.js文件
// 引入第三方库,应该放置在最前面
let uniq = require('uniq')
let module1 = require('./modules/module1')
let module2 = require('./modules/module2')
let module3 = require('./modules/module3')

module1.foo() //module1
module2() //module2
module3.foo() //foo() module3
console.log(uniq(module3.arr)) //[ 1, 2, 3 ]

⑤ through the node running app.js

Enter the command line node app.js, run the JS file

 

(6) the browser-side implementation (aid Browserify)

Browserify is a module for browser environment used packaging tool, as in node environment, but also by require(‘modules‘)organizing references and dependencies between modules, both cited npm modules, you can also write your own reference module, and then packaged into js file, and then through the page <script>tag to load.

Of course, for many NodeJS module, such as module io related to the operation, even after packing by browserify certainly can not run in a browser environment, in which case they will be used to rewrite the browser side branch support module, you can in browserify search to search for these modules.

 

① create a project structure

|-js
  |-dist //打包生成文件的目录
  |-src //源码所在的目录
    |-module1.js
    |-module2.js
    |-module3.js
    |-app.js //应用主源文件
|-index.html //运行于浏览器上
|-package.json
  {
    "name": "browserify-test",
    "version": "1.0.0"
  }

② Download browserify

  • Global: npm install browserify -g
  • 局部: npm install browserify --save-dev

③ definition module codes (the same server)

Note: index.htmlfile to run in the browser, it needs browserify the app.jsfile compiled package, if directly index.htmlintroduced app.jserror will be!

④ packing process js

Run in the root directorybrowserify js/src/app.js -o js/dist/bundle.js

或者browserify js/src/app.js  >  js/dist/bundle.js

⑤ page redistributed

Introduced in the index.html file<script type="text/javascript" src="js/dist/bundle.js"></script>

 

 

 

 

2.AMD

CommonJS specifications load module is synchronous , meaning that only the loading is completed, to perform subsequent operations. AMD specification is Non-synchronized loading module , allowing to specify a callback function. Because Node.js is mainly used for server programming, module files are generally already exists in the local hard disk, so loading up more quickly, regardless of the way asynchronous loading, so CommonJS more applicable norms. However, if a browser environment, from server-side load module, then you must use asynchronous mode, the browser generally use the AMD specification . In addition AMD specification than the norm achieve CommonJS to come forward early in the browser.

(1) AMD basic syntax specification

Definition of exposure modules:

//定义没有依赖的模块
define(function(){
   return 模块
})
//定义有依赖的模块
define(['module1', 'module2'], function(m1, m2){
   return 模块
})

The introduction of the use of modules:

require(['module1', 'module2'], function(m1, m2){
   使用m1/m2
})

(2) AMD specifications using unused require.js

By implementing both methods of comparison to illustrate the benefits of using AMD specification.

  • Unused AMD specifications
// dataService.js文件
(function (window) {
  let msg = 'www.baidu.com'
  function getMsg() {
    return msg.toUpperCase()
  }
  window.dataService = {getMsg}
})(window)
// alerter.js文件
(function (window, dataService) {
  let name = 'Tom'
  function showMsg() {
    alert(dataService.getMsg() + ', ' + name)
  }
  window.alerter = {showMsg}
})(window, dataService)
// main.js文件
(function (alerter) {
  alerter.showMsg()
})(alerter)
// index.html文件
<div><h1>Modular Demo 1: 未使用AMD(require.js)</h1></div>
<script type="text/javascript" src="js/modules/dataService.js"></script>
<script type="text/javascript" src="js/modules/alerter.js"></script>
<script type="text/javascript" src="js/main.js"></script>

Final results are as follows:

This way is obvious shortcomings: First, send multiple requests, js file order followed by the introduction can not be wrong, otherwise it will complain!

  • Use require.js

RequireJS is a tool library, the main module for managing client. Its modular management to comply with AMD specifications, the basic idea is RequireJS by define method, the code is defined as the module; by methods require to achieve module loading code.
Next AMD specification describes the steps implemented in the browser:

① download require.js, and the introduction of

  • Official website: http://www.requirejs.cn/
  • github : https://github.com/requirejs/requirejs

Then require.js import project: js / libs / require.js

② to create a project structure

|-js
  |-libs
    |-require.js
  |-modules
    |-alerter.js
    |-dataService.js
  |-main.js
|-index.html

③ module code defined require.js

// dataService.js文件
// 定义没有依赖的模块
define(function() {
  let msg = 'www.baidu.com'
  function getMsg() {
    return msg.toUpperCase()
  }
  return { getMsg } // 暴露模块
})
//alerter.js文件
// 定义有依赖的模块
define(['dataService'], function(dataService) {
  let name = 'Tom'
  function showMsg() {
    alert(dataService.getMsg() + ', ' + name)
  }
  // 暴露模块
  return { showMsg }
})
// main.js文件
(function() {
  require.config({
    baseUrl: 'js/', //基本路径 出发点在根目录下
    paths: {
      //映射: 模块标识名: 路径
      alerter: './modules/alerter', //此处不能写成alerter.js,会报错
      dataService: './modules/dataService'
    }
  })
  require(['alerter'], function(alerter) {
    alerter.showMsg()
  })
})()
// index.html文件
<!DOCTYPE html>
<html>
  <head>
    <title>Modular Demo</title>
  </head>
  <body>
    <!-- 引入require.js并指定js主文件的入口 -->
    <script data-main="js/main" src="js/libs/require.js"></script>
  </body>
</html>

④ page introduction require.js modules:

Introduced in index.html <script data-main="js/main" src="js/libs/require.js"></script>

** Also how third-party libraries in a project? ** Just above the code base minor modifications:

// alerter.js文件
define(['dataService', 'jquery'], function(dataService, $) {
  let name = 'Tom'
  function showMsg() {
    alert(dataService.getMsg() + ', ' + name)
  }
  $('body').css('background', 'green')
  // 暴露模块
  return { showMsg }
})
// main.js文件
(function() {
  require.config({
    baseUrl: 'js/', //基本路径 出发点在根目录下
    paths: {
      //自定义模块
      alerter: './modules/alerter', //此处不能写成alerter.js,会报错
      dataService: './modules/dataService',
      // 第三方库模块
      jquery: './libs/jquery-1.10.1' //注意:写成jQuery会报错
    }
  })
  require(['alerter'], function(alerter) {
    alerter.showMsg()
  })
})()

The above example is the introduction of third-party libraries in alerter.js jQuery file, main.js there must be corresponding file path configurations.
Summary: By comparison of the two methods, can be obtained very clear definition of AMD module, will not pollute global environment, it can be clearly show dependencies. AMD browser environment mode may be used, and allows asynchronous loading module, the module may be dynamically loaded as needed.

 

 

3.CMD

CMD specification specifically for browser-side load module is asynchronous, it will perform a load module. CMD specification incorporates features CommonJS and AMD specification. In Sea.js, all JavaScript modules follow the CMD module defined specifications.

(1) CMD basic syntax specification

Definition of exposure modules:

//定义没有依赖的模块
define(function(require, exports, module){
  exports.xxx = value
  module.exports = value
})
//定义有依赖的模块
define(function(require, exports, module){
  //引入依赖模块(同步)
  var module2 = require('./module2')
  //引入依赖模块(异步)
    require.async('./module3', function (m3) {
    })
  //暴露模块
  exports.xxx = value
})

The introduction of the use of modules:

define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})

(2) sea.js simple tutorial

① download sea.js, and the introduction of

然后将sea.js导入项目: js/libs/sea.js

②创建项目结构

|-js
  |-libs
    |-sea.js
  |-modules
    |-module1.js
    |-module2.js
    |-module3.js
    |-module4.js
    |-main.js
|-index.html

③定义sea.js的模块代码

// module1.js文件
define(function (require, exports, module) {
  //内部变量数据
  var data = 'atguigu.com'
  //内部函数
  function show() {
    console.log('module1 show() ' + data)
  }
  //向外暴露
  exports.show = show
})
// module2.js文件
define(function (require, exports, module) {
  module.exports = {
    msg: 'I Will Back'
  }
})
// module3.js文件
define(function(require, exports, module) {
  const API_KEY = 'abc123'
  exports.API_KEY = API_KEY
})
// module4.js文件
define(function (require, exports, module) {
  //引入依赖模块(同步)
  var module2 = require('./module2')
  function show() {
    console.log('module4 show() ' + module2.msg)
  }
  exports.show = show
  //引入依赖模块(异步)
  require.async('./module3', function (m3) {
    console.log('异步引入依赖模块3  ' + m3.API_KEY)
  })
})
// main.js文件
define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})

④在index.html中引入

<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
  seajs.use('./js/modules/main')
</script>

最后得到结果如下:

 

4.ES6模块化

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。

(1)ES6模块化语法

export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {
    return a + b;
};
export { basicNum, add };
/** 引用模块 **/
import { basicNum, add } from './math';
function test(ele) {
    ele.textContent = add(99 + basicNum);
}

如上例所示,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。

// export-default.js
export default function () {
  console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'

模块默认输出, 其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。

(2)ES6 模块与 CommonJS 模块的差异

它们有两个重大差异:

① CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

② CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

下面重点解释第一个差异,我们还是举上面那个CommonJS模块的加载机制例子:

// lib.js
export let counter = 3;
export function incCounter() {
  counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4

ES6 模块的运行机制与 CommonJS 不一样。ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

(3) ES6-Babel-Browserify使用教程

简单来说就一句话:使用Babel将ES6编译为ES5代码,使用Browserify编译打包js。

①定义package.json文件

 {
   "name" : "es6-babel-browserify",
   "version" : "1.0.0"
 }

②安装babel-cli, babel-preset-es2015和browserify

  • npm install babel-cli browserify -g
  • npm install babel-preset-es2015 --save-dev
  • preset 预设(将es6转换成es5的所有插件打包)

③定义.babelrc文件

  {
    "presets": ["es2015"]
  }

④定义模块代码

//module1.js文件
// 分别暴露
export function foo() {
  console.log('foo() module1')
}
export function bar() {
  console.log('bar() module1')
}
//module2.js文件
// 统一暴露
function fun1() {
  console.log('fun1() module2')
}
function fun2() {
  console.log('fun2() module2')
}
export { fun1, fun2 }
//module3.js文件
// 默认暴露 可以暴露任意数据类项,暴露什么数据,接收到就是什么数据
export default () => {
  console.log('默认暴露')
}
// app.js文件
import { foo, bar } from './module1'
import { fun1, fun2 } from './module2'
import module3 from './module3'
foo()
bar()
fun1()
fun2()
module3()

⑤ 编译并在index.html中引入

  • 使用Babel将ES6编译为ES5代码(但包含CommonJS语法) : babel js/src -d js/lib
  • 使用Browserify编译js : browserify js/lib/app.js -o js/lib/bundle.js

然后在index.html文件中引入

 <script type="text/javascript" src="js/lib/bundle.js"></script>

最后得到如下结果:

此外第三方库(以jQuery为例)如何引入呢?
首先安装依赖npm install jquery@1
然后在app.js文件中引入

//app.js文件
import { foo, bar } from './module1'
import { fun1, fun2 } from './module2'
import module3 from './module3'
import $ from 'jquery'

foo()
bar()
fun1()
fun2()
module3()
$('body').css('background', 'green')

三、总结

  • CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD CMD解决方案。
  • AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅。
  • CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行。不过,依赖SPM 打包,模块的加载逻辑偏重
  • ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

Guess you like

Origin www.cnblogs.com/zjx304/p/10181121.html