JS的模块化 ES6模块化及webpack打包

技术标签: JS的模块化 ES6模块化及webpack打包

js的模块化进程

现在前端技术日新月异,对于同一个问题痛点,各个时段有各自的解决方案,这就带来了很大差异。今天我就打算梳理js模块化的历史进程,讲一讲这些方案要做什么,怎么做。

js模块化进程的起因

现今的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的JavaScript代码和一大堆依赖包。当一个项目开发的越来越复杂的时候,你会遇到一些问题:命名冲突(变量和函数命名可能相同),文件依赖(引入外部的文件数目、顺序问题)等。

JavaScript发展的越来越快,超过了它产生时候的自我定位。这时候js模块化就出现了。

什么是模块化

模块化开发是一种管理方式,是一种生产方式,一种解决问题的方案。他按照功能将一个软件切分成许多部分单独开发,然后再组装起来,每一个部分即为模块。当使用模块化开发的时候可以避免刚刚的问题,并且让开发的效率变高,以及方便后期的维护。

js模块化进程

一、早期:script标签

这是最原始的 JavaScript 文件加载方式,如果把每一个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,也就是定义在 window 对象中。

缺点: 
1.污染全局作用域 
2.只能按script标签书写顺序加载 
3.文件依赖关系靠开发者主观解决

二、发展一:CommonJS规范

允许模块通过require方法来同步加载(同步意味阻塞)所要依赖的其他模块,然后通过module.exports来导出需要暴露的接口。

 
  1. // module add.js

  2. module.exports = function add (a, b) { return a + b; }

  3.  
  4. // main.js

  5. var {add} = require('./math');

  6. console.log('1 + 2 = ' + add(1,2);

CommonJS 是以在浏览器环境之外构建JavaScript 生态系统为目标而产生的项目,比如在服务器和桌面环境中。

三、发展二:AMD/CMD

(1)AMD

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出(异步模块定义)。

AMD标准中定义了以下两个API:

  1. require([module], callback);
  2. define(id, [depends], callback);

require接口用来加载一系列模块,define接口用来定义并暴露一个模块。

 
  1. define(['./a', './b'], function(a, b) {

  2. // 依赖必须一开始就写好

  3. a.add1()

  4. ...

  5. b.add2()

  6. ...

  7. })

优点: 
1、适合在浏览器环境中异步加载模块 2、可以并行加载多个模块

(2)CMD

CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。(在CommomJS和AMD基础上提出)

 
  1. define(function (requie, exports, module) {

  2. //依赖可以就近书写

  3. var a = require('./a');

  4. a.add1();

  5. ...

  6. if (status) {

  7. var b = requie('./b');

  8. b.add2();

  9. }

  10. });

优点: 
1、依赖就近,延迟执行 2、可以很容易在服务器中运行

(3)AMD 和 CMD 的区别

AMD和CMD起来很相似,但是还是有一些细微的差别:

1、对于依赖的模块,AMD是提前执行,CMD是延迟执行。

2、AMD推崇依赖前置;CMD推崇依赖就近,只有在用到某个模块的时候再去require。

3、AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一

四、发展三:ES6模块化

EcmaScript6 标准增加了JavaScript语言层面的模块体系定义。

在 ES6 中,我们使用export关键字来导出模块,使用import关键字引用模块。

 
  1. // module math.jsx

  2. export default class Math extends React.Component{}

  3.  
  4. // main.js

  5. import Math from "./Math";

目前很少JS引擎能直接支持 ES6 标准,因此 Babel 的做法实际上是将不被支持的import翻译成目前已被支持的require。

 

ES6详解八:模块(Module)

 

基本用法

命名导出(named exports)

可以直接在任何变量或者函数前面加上一个 export 关键字,就可以将它导出。 
这种写法非常简洁,和平时几乎没有区别,唯一的区别就是在需要导出的地方加上一个 export 关键字。 
比如:

 
  1. export const sqrt = Math.sqrt;

  2. export function square(x) {

  3. return x * x;

  4. }

  5. export function diag(x, y) {

  6. return sqrt(square(x) + square(y));

  7. }

然后在另一个文件中这样引用:

 
  1. import { square, diag } from 'lib';

  2. console.log(square(11)); // 121

  3. console.log(diag(4, 3));

你可能会注意到这个奇怪的语法 { square, diag } 不就是前面讲过的 destructing吗。所以你会以为还可以这样写:

 
  1. import lib from 'lib';

  2. square = lib.square;

但是其实这样是错的,因为 import { square, diag } from 'lib’; 是import的特有语法,并不是 destructing 语法,所以其实import的时候并不是直接把整个模块以对象的形式引入的。

如果你希望能通过 lib.square 的形式来写,你应该这样导入:

 
  1. import * as lib from 'lib';

  2. square = lib.square;

不过值得注意的一点是,如果你直接用babel编译,执行是会报错的。因为 babel 并不会完全编译 modules,他只是把 ES6 的modules语法编译成了 CMD 的语法,所以还需要用 browserify 之类的工具再次编译一遍。 
如果你发现 browserify 找不到 lib,可以改成 from ‘./lib’ 试试。

默认导出

大家会发现上面的写法比较麻烦,因为必须要指定一个名字。其实很多时候一个模块只导出了一个变量,根本没必要指定一个名字。 
还有一种用法叫默认导出,就是指定一个变量作为默认值导出:

 
  1. //------ myFunc.js ------

  2. export default function () { ... };

  3.  
  4. //------ main1.js ------

  5. import myFunc from 'myFunc';

  6. myFunc();

默认导出的时候不需要指定一个变量名,它默认就是文件名。 
这里的区别不仅仅是不用写名字,而是 导出的默认值就是模块本身,而不是模块下面的一个属性,即是 import myFunc from 'myFunc’; 而不是 import {myFunc} from 'myFunc’;

命名导出结合默认导出

默认导出同样可以结合命名导出来使用:

 
  1. export default function (obj) {

  2. ...

  3. };

  4. export function each(obj, iterator, context) {

  5. ...

  6. }

  7. export { each as forEach };

上面的代码导出了一个默认的函数,然后由导出了两个命名函数,我们可以这样导入:

 import _, { each } from 'underscore';
  • 1
  • 2

注意这个逗号语法,分割了默认导出和命名导出

其实这个默认导出只是一个特殊的名字叫 default,你也可以就直接用他的名字,把它当做命名导出来用,下面两种写法是等价的:

 
  1. import { default as foo } from 'lib';

  2. import foo from 'lib';

同样的,你也可以通过显示指定 default 名字来做默认导出, 下面两种写法是一样的:

 
  1. //------ module1.js ------

  2. export default 123;

  3.  
  4. //------ module2.js ------

  5. const D = 123;

  6. export { D as default };

仅支持静态导入导出

ES6规范只支持静态的导入和导出,也就是必须要在编译时就能确定,在运行时才能确定的是不行的,比如下面的代码就是不对的:

 
  1. //动态导入

  2. var mylib;

  3. if (Math.random()) {

  4. mylib = require('foo');

  5. } else {

  6. mylib = require('bar');

  7. }

  8. //动态导出

  9. if (Math.random()) {

  10. exports.baz = ...;

  11. }

为什么要这么做,主要是两点:

  1. 性能,在编译阶段即完成所有模块导入,如果在运行时进行会降低速度
  2. 更好的检查错误,比如对变量类型进行检查

各种导入和导出方式总结

总结一下,ES6提供了如下几种导入方式:

 
  1. // Default exports and named exports

  2. import theDefault, { named1, named2 } from 'src/mylib';

  3. import theDefault from 'src/mylib';

  4. import { named1, named2 } from 'src/mylib';

  5.  
  6. // Renaming: import named1 as myNamed1

  7. import { named1 as myNamed1, named2 } from 'src/mylib';

  8.  
  9. // Importing the module as an object

  10. // (with one property per named export)

  11. import * as mylib from 'src/mylib';

  12.  
  13. // Only load the module, don’t import anything

  14. import 'src/mylib';

如下几种导出方式:

 
  1. //命名导出

  2. export var myVar1 = ...;

  3. export let myVar2 = ...;

  4. export const MY_CONST = ...;

  5.  
  6. export function myFunc() {

  7. ...

  8. }

  9. export function* myGeneratorFunc() {

  10. ...

  11. }

  12. export class MyClass {

  13. ...

  14. }

  15. // default 导出

  16. export default 123;

  17. export default function (x) {

  18. return x

  19. };

  20. export default x => x;

  21. export default class {

  22. constructor(x, y) {

  23. this.x = x;

  24. this.y = y;

  25. }

  26. };

  27. //也可以自己列出所有导出内容

  28. const MY_CONST = ...;

  29. function myFunc() {

  30. ...

  31. }

  32.  
  33. export { MY_CONST, myFunc };

  34. //或者在导出的时候给他们改个名字

  35. export { MY_CONST as THE_CONST, myFunc as theFunc };

  36.  
  37. //还可以导出从其他地方导入的模块

  38. export * from 'src/other_module';

  39. export { foo, bar } from 'src/other_module';

  40. export { foo as myFoo, bar } from 'src/other_module';

 

浅谈webpack打包原理

模块化机制

webpack并不强制你使用某种模块化方案,而是通过兼容所有模块化方案让你无痛接入项目。有了webpack,你可以随意选择你喜欢的模块化方案,至于怎么处理模块之间的依赖关系及如何按需打包,webpack会帮你处理好的。

关于模块化的一些内容,可以看看我之前的文章:js的模块化进程

核心思想:

  1. 一切皆模块: 
    正如js文件可以是一个“模块(module)”一样,其他的(如css、image或html)文件也可视作模 块。因此,你可以require(‘myJSfile.js’)亦可以require(‘myCSSfile.css’)。这意味着我们可以将事物(业务)分割成更小的易于管理的片段,从而达到重复利用等的目的。
  2. 按需加载: 
    传统的模块打包工具(module bundlers)最终将所有的模块编译生成一个庞大的bundle.js文件。但是在真实的app里边,“bundle.js”文件可能有10M到15M之大可能会导致应用一直处于加载中状态。因此Webpack使用许多特性来分割代码然后生成多个“bundle”文件,而且异步加载部分代码以实现按需加载。

文件管理

  • 每个文件都是一个资源,可以用require/import导入js
  • 每个入口文件会把自己所依赖(即require)的资源全部打包在一起,一个资源多次引用的话,只会打包一份
  • 对于多个入口的情况,其实就是分别独立的执行单个入口情况,每个入口文件不相干(可用CommonsChunkPlugin优化)

打包原理

把所有依赖打包成一个bundle.js文件,通过代码分割成单元片段并按需加载。

如图,entry.js是入口文件,调用了util1.js和util2.js,而util1.js又调用了util2.js。

打包后的bundle.js例子

 
  1. /******/ ([

  2. /* 0 */ //模块id

  3. /***/ function(module, exports, __webpack_require__) {

  4.  
  5. __webpack_require__(1); //require资源文件id

  6. __webpack_require__(2);

  7.  
  8. /***/ },

  9. /* 1 */

  10. /***/ function(module, exports, __webpack_require__) {

  11. //util1.js文件

  12. __webpack_require__(2);

  13. var util1=1;

  14. exports.util1=util1;

  15.  
  16. /***/ },

  17. /* 2 */

  18. /***/ function(module, exports) {

  19. //util2.js文件

  20. var util2=1;

  21. exports.util2=util2;

  22.  
  23. /***/ }

  24. ...

  25. ...

  26. /******/ ]);

  27. bundle.js是以模块 id 为记号,通过函数把各个文件依赖封装达到分割效果,如上代码 id 为 0 表示 entry 模块需要的依赖, 1 表示 util1模块需要的依赖
  28. require资源文件 id 表示该文件需要加载的各个模块,如上代码_webpack_require__(1) 表示 util1.js 模块,__webpack_require__(2) 表示 util2.js 模块
  29. exports.util1=util1 模块化的体现,输出该模块

来源:网络


智能推荐

ES6模块化的导入和导出

一、导出——export 步骤1:在html代码中引入js文件,script标签中加入 type=“module” ; 步骤2:把需要共享的变量导出——export{ 变量 }; (1)导出变量 导出方式1: 导出方式2: (2)导出函数、类 导出方式1: 导出方式2: 某些情况下,一个模块中包含某个的功能,我们并不希望给这个...

JS的模块化

  什么是模块化开发? 前端开发中,起初只要在script标签中嵌入几十上百行代码就能实现一些基本的交互效果,后来js得到重视,应用也广泛起来了,jQuery,Ajax,Node.Js,MVC,MVVM等的助力也使得前端开发得到重视,也使得前端项目越来越复杂,然而,JavaScript却没有为组织代码提供任何明显帮助,甚至没有类的概念,更不用说模块(module)了,那么什么是模块呢? ...

redis中的hash扩容渐进式rehash过程

背景: redis字典(hash表)当数据越来越多的时候,就会发生扩容,也就是rehash 对比:java中的hashmap,当数据数量达到阈值的时候(0.75),就会发生rehash,hash表长度变为原来的二倍,将原hash表数据全部重新计算hash地址,重新分配位置,达到rehash目的 redis中的hash表采用的是渐进式hash的方式: 1、redis字典(hash表)底层有...

不同系统下的回车\r和换行\n,及其历史

2019独角兽企业重金招聘Python工程师标准>>>      我们平时按下键盘上的‘回车键’,就能实现回车换行【我们在屏幕上所看到的就是光标移到了下一行的开头位置!!ps:不讨论软件实现的特殊功能,如word里的回车智能缩进】。因此对这个按键更准确说应该叫做‘回车换行键’ 就且将这种将光标移到下行开...

windows操作系统,python环境下django的自动安装

首先,在Windows操作系统下安装python,完成python环境的搭建。(我看有的博客需要配置环境变量,其实不必要,因为我们在安装的时候只要勾选如下图所示即可避免不必要的麻烦) 第二步,使用快捷键windows+R或者点击如下图所示,之后点击运行 在其中输入cmd即可进入dos命令。 第三步,输入以下命令 就会自动安装,等待安装完成即可。 第四步,检测django是否安装成功,如果出现如下图...

猜你喜欢

Qt之日志输出文件

    做过项目的童鞋可能都使用过日志功能,以便有异常错误能够快速跟踪、定位,Qt也提供的类似的机制。之前用Qt4做项目时使用的是Qt::qInstallMsgHandler(),到了Qt5,使用了新的Qt::qInstallMessageHandler()来替代,详情请查看Qt助手(C++ API changes)。 描述     助手中在C++ API ...

基于雇员流失率数据进行多分类模型训练及阈值调整实践-大数据ML样本集案例实战

版权声明:本套技术专栏是作者(秦凯新)平时工作的总结和升华,通过从真实商业环境抽取案例进行总结和分享,并给出商业应用的调优建议和集群环境容量规划等内容,请持续关注本套博客。QQ邮箱地址:[email protected],如有任何学术交流,可随时联系。 1 数据的预处理分析 2 数据标准化处理 3 sklearn多模型封装(已废弃,学思想) 4 阈值概率调整 5 总结 方便复习,整成笔记,内容粗略...

Exception: java.lang.OutOfMemoryError

  最近在跑公司的项目的过程中我发现,当一个tomcat中运行多个项目的时候,就会报这个错误。我以前由于使用Maven-tomcat插件方式运行项目,所有没有遇到过这种情况。   自己查了一下资料,但是后来我发现有些帖子中描述的解决方法:window–preferences—tomcat—JVM Setting ——append to J...

【C#】关于WinForm窗体程序的退出方法总结

很多人对于关闭窗口都有不同的需求,以下是关闭窗体的几种方法: 一、关闭窗体 1. this.Close();仅仅是关闭当前窗口,若不是主窗体的话程序无法退出。 2. Application.Exit();强制所有消息终止,退出所有的窗体。 3. Application.ExitThread();强制终止调用线程上的所有消息。 4. System.Environment.Exit(0);最彻底的退出...

采用eclipse+jboss开发ejb3.0学习笔记

<v:shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,2...

问答精选

How to get the sum of rows using a vector and the make the result in a column

I have a dataframe and i want to calculate the sum of variables present in a vector in every row and make the sum in other variable after i want the name of new variable created to be from the name of...

Call a custom class in zend framework

I have a very rough project that done partially in zend framework (not ZF2). The 'application', 'library' and 'public' folders are on the same root. Now i need to create a library 'Anil' in the 'libra...

Hide Some Of The Categories In Magento?

I want to hide some of the categories from magento home page. I have created some categories like New Arrivals, Special Offer Which I would like to show in some different way may be in left panel or r...

Reading an internal file in Android Studio

I am writing to an internal file in Android Studio The code lets me write lines of data split with commas. I am able to then go to another activity and read it all out at once. However I want to be ab...

Rails Acts_as_votable Gem Like/Unlike Buttons with Ajax

I'm new to Ruby On Rails, I used the acts_as_votable gem to create Like and Unlike Buttons to make Users like and unlike Posts but I can't make them change from Like to Unlike (and viceversa) and upda...

相关问题

相关文章

热门文章

推荐文章

相关标签

推荐问答