前端模块化

什么是模块化

1、模块化

模块化是自顶向下逐层将体系划分红多少更好的可治理模块的体式格局,用来支解、组织和打包软件,到达高度解耦

2、模块

模块是可组合、剖析、替换的单位;
每一个模块完成一个特定子功用,模块间经由历程某种体式格局组装起来,成为一个团体
模块间高度解耦,模块功用单一,可高度复用

3、前端模块化处置惩罚的题目

1、消弭全局变量,削减定名争执
2、更好地代码组织组织和开辟合作:经由历程文件拆分,更易于治理庞杂代码库,更易于多人合作开辟,下降文件兼并时刻争执的发作几率,随意马虎编写单位测试
3、依靠治理、按需加载:不再须要手动治理剧本加载递次
4、优化:
(1)代码打包:兼并小模块,抽取大众模块,在资本请求数和浏览器缓存运用方面举行适宜的弃取
(2)代码支解:按需加载代码(分路由、异步组件),处置惩罚单页面运用首屏加载迟缓的题目
(3)Tree Shaking :运用ES6模块的静态化特征。在构建历程当平剖析出代码库中未运用的代码,从终究的bundle中 去除,从而削减JS Bundle的大小
(4)Scope Hoisting:ES6模块内容导入导出绑定是运动的,能够将多个小模块兼并到一个函数当中去,关于反复变量名举行核实的重定名,从而削减Bundle的尺寸和提拔加载速率。

前端为何须要模块化(模块的生长)

1、内嵌剧本—原始写法

1.1语法

在 <script ></script>标记之间增加js代码 ,把差别的函数等简朴放在一同,就算是一个模块

    function fn1(){....}
​
    function fn2(){....}

1.2不足

代码无重用性:其他页面须要该script标签中一些代码时,须要复制粘贴
全局定名空间污染:一切变量、要领等都定义在全局作用域中,也随意马虎定名争执

2、外链剧本—原始写法

2.1语法

将js代码分红多个片断离别放入s文件中,运用<script src>引入

  <script src="1.js"></script>
​
  <script src="2.js"></script>
​
  <script src="3.js"></script>
​
  <script src="4.js"></script>

2.2不足

缺乏依靠治理:文件之间考究先后递次,相互之间存在依靠关联
全局定名空间污染:一切变量、要领等都定义在全局作用域中

3、对象封装

一个对象就是一个模块,一切模块成员都在个中

3.1语法

var obj = new Object({
    fn1 : function (){},
    fn2 : function (){}
    .....
});

3.2不足

暴露了内部成员:所以内部成员都被暴露,在外不能够随意马虎被修正
缺乏依靠治理:一个模块一个文件,文件递次还须要手动掌握
全局定名空间污染:依然须要暴露一个全局变量

4、连系对象封装与IIFE(马上实行函数表达式)

4.1 语法

将每一个文件都封装成IIFE,内部定义的变量和要领只在IIFE作用域内见效,不会污染全局。而且经由历程将这些要领变量赋值给某个全局对象来公然 , 不暴露私有成员;

var module = (function(obj){
    let a =1;
    obj.fn1=function (){}
    return obj
})(module || {});

4.2 运用

Jquery库,公然一个全局对象$, 它中包含所以要领与属性

4.3 不足

缺乏依靠治理:文件递次还须要手动掌握,比方运用jQuery的要领前,必需保证jQuery已加载完
全局定名空间污染:依然须要暴露一个全局变量

5、模块化范例的涌现
(1) js引入服务器端后,涌现的 CommonJS范例
(2)CommonJS的同步性限定了前端的运用,涌现了 AMD
(3)UMD范例的一致
(4)ES6模块的定义

CommonJs 与 nodeJs服务端的模块化完成

CommonJS是除浏览器以外 构建js生态体系为目的而发生的范例,比方服务器和桌面环境等。最早 由Mozilla的工程师Kevin Dangoor在2009年1月竖立。

2013年5月,Node.js 的包治理器 NPM 的作者 Isaac Z. Schlueter 说 CommonJS 已过期,Node.js 的内核开辟者已烧毁了该范例。

1、定义

每一个文件是一个模块,有本身的作用域。在一个文件里定义的变量、函数等都是私有的,对其他文件不可见。

在每一个模块内部,module变量代表当前模块,它的exports属性是对外的接口,加载某个模块(require)时,实在加载的是该模块的 exports属性

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

2、语法

CommonJS包含重要包含三部份:模块导入(加载),模块定义、模块标识

2.1 模块导入:require() ——返回该模块的exports属性

var  module1 = require('./module1.js');

2.2 模块定义 :module.exports

//module1.js
module.exports.fn1 = function (){}

2.3 模块标识:require()要领的参数

必需是字符串
能够是以./ ../开首的相对途径
能够是绝对途径
能够省略后缀名

3、特性

自动依靠治理:模块加载的递次 依靠于其在代码中涌现的递次
不污染全局作用域:模块内部代码运转在本身的私有作用域
可屡次加载,但只实行一次:模块能够屡次加载,然则只会在第一次加载时运转一次,然后运转效果被缓存,今后再加载,直接读取缓存效果。假如想让模块再次实行,必需清晰缓存
同步加载模块:只要加载完成以后,才实行背面的操纵
运转时加载

4、nodejs中的完成

4.1 module对象

node中供应了一个Module组织函数,每一个模块都是组织函数的实例。每一个模块内部,都要一个module对象,代表当前模块

      //Module组织函数
        function Module(id,parent){
            this.id=id;//模块的标识符,平常为带有绝对途径的模块文件名
            this.exports ={};//模块暴露出去的要领或许变量
            this.parent=parent;//返回一个对象,父级模块,挪用该模块的模块
            if(parent && parent.children){
                parent.children.push(this); 
            }
    ​
            this.filename =null;//模块文件名,带有绝对途径
           this.loaded=false;//返回一个布尔值,该模块是不是加载完成(因为是运转时加载,所以代表是不是已实行终了)
            this.chilren =[];//返回数组,该模块要用到的其他模块
        }

​
    //实例化一个模块
    var module1 =new Module(filename,parent)

4.2 module.exports属性

module.exports属性示意当前模块对外输出的接口,其他文件加载该模块,现实上就是读取module.exports变量。

4.3 exports变量

node为每一个模块供应了exoprts变量,指向module.exports。等同于在每一个模块头部,有一行代码

var exports = module.exports;

在对外输出时,能够向exports对象增加要领

exports.fn1 =function(){}

不能直接将exports指向一个值,如许会割断exports与module.exports的联络

exports = function(x) {console.log(x)};

假如一个模块的module.exports是一个单一的值,不能运用exports输出,只能运用module.exports输出

//hello函数是没法对外输出的,因为module.exports被从新赋值了。

exports.hello = function() {
  return 'hello';
};
​
module.exports = 'Hello world';


4.4 node中的模块分类

node中模块分为两类:一类为mode供应的中心模块,另一类为 用户编写的文件模块

4.4.1 中心模块

即node供应的内置模块如 http模块、url模块、fs模块等

中心模块在node源代码的编译历程当中被编译进了二进制文件,在node历程启动的时刻,会被直接加载进内存,因而援用这些模块的时刻,文件定位和编译实行这两步会被省略。

在途径剖析中会优先推断中心模块,加载速率最快。

4.4.2 文件模块

即外部引入的模块 如node_modules中的模块,项目中本身编写的js文件等

在运转时动态加载,须要完全的途径剖析,文件定位,编译实行这三部,加载速率比中心模块慢

4.5 途径剖析、文件定位、编译实行

4.5.1途径剖析

不管中心模块照样文件模块都须要阅历途径剖析这一步,Node支撑以下几种情势的模块标识符,来引入模块:

//中心模块
require('http')
----------------------------
//文件模块
​
//以.开首的相对途径,(能够不带扩大名)
require('./a.js')
  
//以..开首的相对途径,(能够不带扩大名)
require('../b.js')
​
//以/最先的绝对途径,(能够不带扩大名)
require('/c.js')
​
//外部模块称号
require('express')
​
//外部模块某一个文件
require('codemirror/addon/merge/merge.js');

● Node 会优先去内存中查找婚配中心模块,假如婚配胜利便不会再继承查找
(1)比方require http 模块的时刻,会优先从中心模块里去胜利婚配
● 假如中心模块没有婚配胜利,便归类为文件模块
(2) 以.、..和/开首的标识符,require都邑依据当前文件途径将这个相对途径或许绝对途径转化为实在途径,也就是我们日常平凡最常见的一种途径剖析
(3)非途径情势的文件模块 如上面的’express’ 和’codemirror/addon/merge/merge.js’,这类模块是一种特别的文件模块,平常称为自定义模块。

4.5.1.1 模块途径

自定义模块的查找最费时,因为关于自定义模块有一个模块途径,Node会依据这个模块途径顺次递归查找。
模块途径——Node的模块途径是一个数组,模块途径存放在module.paths属性上。
我们能够找一个基于npm或许yarn治理项目,在根目次下竖立一个test.js文件,内容为console.log(module.paths),以下:

//test.js
console.log(module.paths);
然后在根目次下用Node实行

node test.js
能够看到我们已将模块途径打印出来。

能够看到模块途径的天生划定规矩以下:
● 当前路文件下的node_modules目次
● 父目次下的node_modules目次
● 父目次的父目次下的node_modules目次
● 沿途径向上逐级递归,直到根目次下的node_modules目次
关于自定义文件比方express,就会依据模块途径顺次递归查找。
在查找同时并举行文件定位。

4.5.2文件定位

● 扩大名剖析
我们在运用require的时刻有时刻会省略扩大名,那末Node怎样定位到详细的文件呢?
这类情况下,Node会顺次根据.js、.json、.node的序次一次婚配。(.node是C++扩大文件编译以后天生的文件)
若扩大名婚配失利,则会将其当作一个包来处置惩罚,我这里直接理解为npm包

● 包处置惩罚
关于包Node会首先在当前包目次下查找package.json(CommonJS包范例)经由历程JSON.parse( )剖析出包形貌对象,依据main属性指定的进口文件名举行下一步定位。
假如文件缺乏扩大名,将依据扩大名剖析划定规矩定位。
若main指定文件名毛病或许压根没有package.json,Node会将包目次下的index当作默许文件名。
再顺次婚配index.js、index.json、index.node。
若以上步骤都没有定位胜利将,进入下一个模块途径——父目次下的node_modules目次下查找,直到查找到根目次下的node_modules,若都没有定位到,将抛出查找失利的非常。

4.5.3模块编译

● .js文件——经由历程fs模块同步读取文件后编译实行
● .node文件——用C/C++编写的扩大文件,经由历程dlopen( )要领加载末了编译天生的文件。
● .json——经由历程fs模块同步读取文件后,用JSON.parse( ) 剖析返回效果。
● 其他扩大名文件。它们都是被当作.js文件载入。

每一个编译胜利的文件都邑将其文件途径作为索引缓存在Module._cache对象上,以进步二次引入的机能。
这里我们只解说一下JavaScript模块的编译历程,以解答前面所说的CommonJS模块中的require、exports、module变量的泉源。

我们还知道Node的每一个模块中都有filename、dirname 这两个变量,是怎样来的的呢?
实在JavaScript模块在编译历程当中,全部所要加载的剧本内容,被放到一个新的函数当中,如许能够防止污染全局环境。该函数的参数包含require、module、exports,以及其他一些参数。
Node对猎取的JavaScript文件内容举行了头部和尾部的包装。在头部增加了(function (exports, require, module,filename, dirname){n,而在尾部增加了n}); 。

因而一个JS模块经由编译以后会被包装成下面的模样:

(function(exports, require, module, __filename, __dirname){
  var express = require('express') ;
  exports.method = function (params){
   ...
  };
});

4.6 模块加载机制

团体加载实行,导入的是被输出的值得拷贝,即 一旦输出一个值,模块内部的变化就影响不到这个值

// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};
​
​
​
​
// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;
​
console.log(counter);  // 3
incCounter();
console.log(counter); // 3 
//counter输出今后,lib.js模块内部的变化就影响不到counter了。

4.7 require 的内部处置惩罚流程

require不是一个全局敕令,而是指向当前模块的module.require敕令,module.require又挪用node内部敕令Module._load

require —>module.require——>Module._load

MOdule._load =function(require,parent,isMain){
    1.搜检缓存Module._cache ,是不是有指定模块
    2.假如缓存中没有,就竖立一个新的MOdule实例
    3.将实例保存到缓存
    4.运用Module,load()加载指定的模块文件
    5.读取文件内容后,运用module.compile()实行文件代码
    6.假如加载/剖析历程报错,就从缓存中删除该模块
    7.返回该模块的module.exports
}

Module.compile要领是同步实行的,一切Module.load 要等它实行完成,才会向用户返回 module.exports的值

AMD 与 requireJs

因为node重要用户服务端编程,模块文件平常都已存在于当地硬盘,所以加载起来比较快,不必斟酌非同步加载的体式格局,因而CommonJS范例比较实用。然则假如是浏览器环境,要从服务器端加载资本,这时候就必需采纳非同步形式。

1、模块定义

define(id? dependencies?,factory)

id为string范例,示意模块标识
dependencies:为Array范例,示意须要依靠的模块
factory:为function或许Object,示意要举行的回调

1.1 自力模块(不须要依靠模块)

define({
    fn1:function(){}
 })
​
define(function(){
    return {
        fn1:function(){},   
    }
})

1.2 非自力模块(有依靠其他模块)

define(['module1','module2'],function(){})   //  依靠必需一最先就写好

2、模块导入

require([‘a’,’b’],function(a,b){})

3、特性

依靠治理:被依靠的文件早于主逻辑被加载实行 ;
运转时加载;
异步加载模块:在模块的加载历程当中纵然require的模块还没有猎取到,也不会影响背面代码的实行,不会壅塞页面衬着

4、RequireJS

AMD范例是RequireJS在推行历程当中对模块定义的范例化产出

CMD 与 seajs

1、模块定义

在依靠示例部份,CMD支撑动态引入,require、exports和module经由历程形参传递给模块,在须要依靠模块时,随时挪用require( )引入即可,示比方下:

define(factory)

1.1 factory 三个参数

function(require,exports,module)
require用于导入其他模块接口
exports 用于导出接口
module存储了与当前模块相关联的一些属性与要领

1.2 例子

define(function(require ,exports,module) {
    //挪用依靠模块increment的increment要领
    var inc = require('increment').increment;   // 依靠能够就近誊写
    var a = 1;
    inc(a); 
    module.id == "program"; 
});

2、模块导入

require('途径')  

3、特性

依靠就近誊写:平常不再define的参数中写依靠,就近誊写
耽误实行

UMD通用范例

兼容CommonJS、AMD 、CMD、全局援用
写法:

(function(global,factory){
    typeof exports === 'object'&& typeof module!=='undefined'
    ?module.exports =factory()   //CommonJS
    :typeof define ==='fucntion' && define.amd
    ?define(factory)         //AMD CMD
    :(global.returnExports = factory()) //挂载到全局
}(this,function(){
    //////暴露的要领
    return fn1
}))

es6 module

1、模块导出 export

export 输入变量、函数、类等 与模块内部的变量竖立一一对应关联

//写法一   
export var a=1;
//写法二
var a=1;
export {a}
//写法三  as举行重定名
var b=1; 
export {b as a}
//写法四
var a=1
export default a 

export语句输出的接口,与其对应的值是动态绑定关联,即经由历程该接口,能够取到模块内部及时的值。

export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);

上面代码输出变量foo,值为bar,500 毫秒以后变成baz。

2、模块输入 import

2.1 写法一

import敕令接收一对大括号,内里指定要加载指定模块,并从中输入变量

import {firstName, lastName, year} from './profile.js';
import { lastName as surname } from './profile.js';

大括号内里的变量名,必需与被导入模块(profile.js)对外接口的称号雷同。
能够运用as关键字,将输入的变量重定名。

2.2 写法二

import 背面写模块途径——–实行所加载的模块,但不输入任何值

import 'lodash';

上面代码仅仅实行lodash模块,然则不输入任何值。
假如屡次反复实行统一句import语句,那末只会实行一次,而不会实行屡次。

2.3 写法三

用星号(*)指定一个对象,团体加载一切对象到这个对象上 ——团体模块加载

import * as circle from './circle';

2.4 export default 与 import

export default 现实导出的为一个叫做 default 的变量,所以其背面不能跟变量声明语句
运用export default敕令时,import是不须要加{}的
不运用export default时,import是必需加{}

//person.js
export function getName() {
 ...
}
//my_module
import {getName} from './person.js';
​
​
​
//person.js
export default function getName(){
 ...
}
//my_module
import getName from './person.js';
​
​
//person.js
export name = 'dingman';
export default function getName(){
  ...
}
​
//my_module
import getName, { name } from './person.js';

3、特性

编译时加载:编译的时刻就能够肯定模块的依靠关联,已输入与输出的变量

各范例总结

1、 CommonJS

环境:服务器环境
特性:(1)同步加载;(2)运转时加载 (3)屡次加载,只第一次实行,今后直接读取缓存
运用: Nodejs
语法:

  导入 :  require()
  导出:module.exports 或许 exports
    

2、AMD

环境:浏览器
特性:(1)异步加载 (2)运转时加载(3)治理依靠,依靠前置誊写 (4)依靠提早实行(加载完马上实行)
运用:RequireJS
语法:

  导入:require(['依靠模块'],fucntion(依靠模块变量援用){回调函数})
  导出(定义):define(id?def?factory(){return ///})

3、CMD

环境:浏览器
特性:(1)异步加载 (2)运转时加载 (3)治理依靠,依靠就近誊写(4)依靠耽误实行 (require的时刻才实行)
运用:SeaJS
语法:

导入:require()
导出: define(function(require,exports,module){})

4、UMD

环境:浏览器或服务器
特性:(1)兼容CommonJS AMD UMD 全局运用
语法:无导入导出,只是一种兼容写法

5、ES6 module

环境:浏览器或服务器
特性:(1)编译时加载(2)按需加载 (3)动态更新
运用:es6最新语法
语法:

导入 :import 
导出:export、 export default

各范例的区分提炼

1、CommonJS与ES6

1.1 是不是动态更新

es6 :输出的值是动态绑定,会及时动态更新。
CommonJS :输出的是值的缓存,不存在动态更新

1.2 加载机遇

//ES6模块
import { basename, dirname, parse } from 'path';
​
//CommonJS模块
let { basename, dirname, parse } = require('path');

es6 :

编译时加载,ES6能够在编译时就完成模块加载;
按需加载,ES6会从path模块只加载3个要领,其他不会加载。
动态援用,及时更新,当ES6碰到import时,不会像CommonJS一样去实行模块,而是天生一个动态的只读援用,当真正须要的时刻再到模块里去取值,所以ES6模块是动态援用,而且不会缓存值。

CommonJS:

运转时加载
加载全部对象:require path模块时,实在 CommonJS会将path模块运转一遍,并返回一个对象,并将这个对象缓存起来,这个对象包含path这个模块的一切API。
运用缓存值,不会及时更新:今后不管多少次加载这个模块都是取第一次运转的效果,除非手动消灭。因为CommonJS模块输出的是值的拷贝,所以当模块内值变化时,不会影响到输出的值

2、CMD 与 AMD

2.1 cmd依靠就近,AMD依靠前置

// CMD
define(function(require, exports, module) {
    var a = require('./a')
    a.doSomething()       
    var b = require('./b') // 依靠能够就近誊写
    b.doSomething()
    // ... 
})
// AMD
define(['./a', './b'], function(a, b) {// 依靠必需一最先就写好
    a.doSomething()
    b.doSomething() 
    ...
})

2.2 CMD耽误实行,AMD提早实行

AMD

在加载模块完成后就马上实行该模块,
一切模块都加载实行完成后 才会进入require的回调函数,实行主逻辑
(会涌现 谁人依靠模块先下载完,哪一个就先实行,与实行递次誊写递次不一致
AMD RequireJS 从 2.0 最先,也改成能够耽误实行(依据写法差别,处置惩罚体式格局差别)

CMD

加载完某个依靠模块后 并不实行,
当一切依靠模块加载完成落后入主逻辑,碰到require语句时才**实行对应依靠模块**。
(保证了**实行递次与誊写递次的一致**)




参考:

http://es6.ruanyifeng.com/#do…
https://zhuanlan.zhihu.com/p/…

    原文作者:二丽
    原文地址: https://segmentfault.com/a/1190000016280358
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞