本站业务范围:1、PC端软件开发、网站开发 2、移动端APP、网站、微信接口、微商城开发 3、视频教程、课程设计和辅导 4、单片机开发 5、串口通讯调试
 当前位置:文章中心 >> Jquery_Ajax_Javascript
立即购买视频教程 什么是模块捆绑
夜鹰教程网 来源:www.yyjcw.com 日期:2017-11-7 0:04:51
在这篇文章的第一部分,我谈到了什么是模块,开发者为什么使用它们,以及,在你的程序中实现模块的不同方式。
在这第二部分,将会回答捆绑模块到底意味着什么:为什么要捆绑,捆绑的不同方法,以及在网页开发中模块的未来发展。

这篇文章不能解决你的问题?我们还有相关视频教程云课堂 全套前端开发工程师培训课程

微信号:yyjcw10000 QQ:1416759661  远程协助需要加QQ!

业务范围:视频教程|程序开发|在线解答|Demo制作|远程调试| 点击查看相关的视频教程

技术范围:全端开发/前端开发/webapp/web服务/接口开发/单片机/C#/java/node/sql server/mysql/mongodb/android/。 





1. 什么是模块捆绑

抽象的概括,模块捆绑就是这样一个简单的处理:把一组模块以及它们的依赖,按照正确的次序,拼接在一个文件或一组文件里。


但正如网页开发的其它方方面面,棘手的总是潜藏在具体细节里。


2. 为什么一定要捆绑模块?

当你将程序划分成模块时,一般把它们组织成不同的文件或文件夹。很可能你还有一组所用库的模块,比如 Underscore、React。


结果,这些文件每一个都得用 <script> 标签引入你的主 HTML 文件,当用户访问你的主页时再由浏览器加载进来。每个文件都用单独的 <script> 标签引入,意味着浏览器不得不分别挨个加载它们。


对网页加载时间来说这简直是噩梦。


于是,为了解决这个问题,我们把所有文件捆绑,或“拼接”到一个文件(有时也是一组文件)中,正是为了减少请求数。当你听到开发人员谈论“构建步骤”或“构建过程”时,他们谈的就是这个。


另一个加速构建操作的常用方法是,“缩减”捆绑后的代码。缩减,是把源代码中不需要的字符(如空格、评论、换行符等等)移除,从而减小了代码的总体积,却不改变其功能。


数据更少,浏览器处理的时间就更短,便减少了下载文件花费的时间。如果你见过带 “min” 扩展名的文件,比如 “underscore-min.js”,可能就已经留意到,相比完整版,缩减版小了好多(不过很难阅读)。


任务执行工具,如 Gulp、Grunt,让开发者操作拼接和缩减更简单便捷。一边是展示给开发者看的人类可读代码,另一边是提供给浏览器的捆绑后的计算机可读代码。


3. 捆绑模块有哪些不同的方法?

如果你用的是一种标准模块模式(在第一部分讨论过)来定义模块,那么拼接和缩减文件不会出任何岔子,你其实就是把几堆纯 JavaScript 代码捆成一束。


但如果你用的是非原生模块系统,浏览器不能像 CommonJS、AMD、甚至原生 ES6 模块格式那样解析,你就需要用专门工具先转化成排列正确、浏览器可识别的代码。这正是 Browserify、RequireJS、Webpack 和其他模块捆绑工具,或模块加载工具粉墨登场的时候。


除了捆绑或加载你的模块,模块捆绑工具还有许多额外的功能,比如,当你修改或为了调试生成源码映射时,它会自动重编译你的代码。


下面就来过一遍常用的模块捆绑方法:


4. 捆绑 CommonJS

从第一部分已经知道,CommonJS 是同步加载模块,虽然没什么毛病,但对浏览器来说不太现实。我曾提过有方法可以解决——其中之一就是模块捆绑工具 Browserify ,它是为浏览器编译 CommonJS 模块的工具。


举个例子,假如你有一个个文件,它引入一个模块以计算一组数值的平均值:


var myDependency = require(‘myDependency’);

var myGrades = [93, 95, 88, 0, 91];

var myAverageGrade = myDependency.average(myGrades);

在这个场景中,我们只有一个依赖。用下面的这个命令,Browserify 会以这个文件为入口把所有依赖的模块递归地捆绑进一个文件:


browserify main.js  -o bundle.js

Browserify 实际做的是,跳入文件为每一个依赖分析它的抽象语法树,从而遍历出工程的整个依赖关系。一旦它搞懂了你的依赖结构,就把它们按照正确的顺序捆绑进一个文件。那时,你只需用一个 <script> 标签,引入 bundle.js 到你的 HTML,从而只要一个 http 请求,就把你的所有源代码下载下来了。哇,还不去捆绑!


与之类似,如果你有多个文件,每个文件又有多个依赖,你要做的也很简单:告诉他你的入口文件,然后就可以瘫坐在椅子上看好戏。


最后生成的捆绑文件,可以直接导向到一些工具做压缩处理。


5. 捆绑 AMD

如果你用的是 AMD, 就需要像 RequireJS、Curl 那样的 AMD 模块加载工具。模块加载工具(与模块捆绑工具不同)会动态加载你的程序所需的模块。


再提醒一下,AMD 跟 CommonJS 最大不同在于,AMD 会异步加载模块。所以,如果你用 AMD,就无需捆绑模块到一个文件里,技术上讲也就不需要执行捆绑模块动作的构建过程。因为异步加载模块意味着在运行过程中逐步下载那些程序所必需的文件,而不是用户刚进入页面就一下把所有文件都下载下来。


然后实际中,每个用户动作会额外产生大数据量的请求,这对产品而言很不合理。所以大多数网页开发者仍然为了更优的性能,使用构建工具捆绑、缩减他们的 AMD 模块,比如像 RequireJS 优化器 r.js 一样的工具。


概括地说,在捆绑方面 AMD 和 CommonJS 的区别在于:开发时, 用 AMD 的程序可以省去构建过程。直到实际运行时,再让 r.js 那些优化工具介入处理。


要想围观 CommonJS 和 AMD 更有意思的“互撕”,看看这篇Tom Dale 的博文。


6. Webpack

捆绑工具算有些年头了,Webpack 则是初来乍到的新人。它的设计基于不知道你所用的模块系统是什么,从而让开发者各依所需选用 CommonJS、AMD 或 ES6。


你是不是纳闷,已经有像 Browserify、RequireJS 那样的捆绑工具各司其职、表现优异,为什么还需要 Webpack?好吧,至少因为一点,Webpack 额外提供了一些有用的特性,如“代码分割”——把你的代码库分割成按需加载的“块”。


比如,你有一个网页应用程序,其中相当一块代码只在特定条件下才用到,那么把整个代码库都捆绑进一个大文件就不是很高效。这时,你可以用代码分割功能,将那部分代码抽离、捆绑到另外一块,在执行时按需加载,从而避免在最开始就遇到大量负载的麻烦。其实大部分用户只会用到你应用程序的核心代码。


代码分割仅仅是 Webpack 提供的众多亮眼功能之一。关于 Webpack 与 Browserify 哪个更好,网络上充斥着各种观点,下面的是一些客观冷静的讨论,助我稍微理清了头绪:


https://gist.github.com/substack/68f8d502be42d5cd4942

http://mattdesl.svbtle.com/browserify-vs-webpack

http://blog.namangoel.com/browserify-vs-webpack-js-drama

7. ES6 模块

看完了吗?好,接下来我想谈谈 ES6 模块,它在未来会减少捆绑工具的使用。(你很快就会明白我的意思。)先来弄懂 ES6 模块是怎么被加载的。


ES6 模块同现有的 JS 模块格式最关键的区别是,它在设计之初就考虑到了静态分析。什么意思呢?当你导入模块时,导入的模块会在编译阶段,即代码开始运行之前,被解析。于是,我们能够在运行程序之前,把导出模块中不被其他模块使用的部分移除。这节省了很大的空间,减少了浏览器的压力。


那么问题来了。你在用一些工具,像 Uglify.js,缩减代码时,有一个死代码去除的处理,它和 ES6 移除没用的模块又有什么不同呢?只能说“得看情况”。


(注:死代码去除是可选步骤,即去除未使用的代码和变量。就把它想成,扔掉捆绑后代码中不需要运行的冗余部分,一定在捆绑之后。)


有时,死代码去除在 Uglify.js 和 ES6 模块中表现完全一样,而有时也不同。如果你想验证一下,Rollup’s wiki 里有个很好的示例。


ES6 的不同在于,它去除死代码的方法不同,叫做“tree shaking”,本质上它是死代码去除的反过程。它只保留你运行所需的代码,而非排除你运行所不需要的。来看个例子:


我们有一个 util.js 文件,里面写了一些函数,再用 ES6 语法将它们一一导出(exports):


export function each(collection, iterator) {

  if (Array.isArray(collection)) {

    for (var i = 0; i < collection.length; i++) {

      iterator(collection[i], i, collection);

    }

  } else {

    for (var key in collection) {

      iterator(collection[key], key, collection);

    }

  }

}

export function filter(collection, test) {

  var filtered = [];

  each(collection, function (item) {

    if (test(item)) {

      filtered.push(item);

    }

  });

  return filtered;

}

export function map(collection, iterator) {

  var mapped = [];

  each(collection, function (value, key, collection) {

    mapped.push(iterator(value));

  });

  return mapped;

}

export function reduce(collection, iterator, accumulator) {

  var startingValueMissing = accumulator === undefined;

  each(collection, function (item) {

    if (startingValueMissing) {

      accumulator = item;

      startingValueMissing = false;

    } else {

      accumulator = iterator(accumulator, item);

    }

  });

  return accumulator;

}

然后,假设我们不知道自己的程序将要用到哪个功能函数,所以把这些模块全都导入(import)到 main.js:


import  *  as  Utils  from ‘./utils.js’;

但到最后我们只用了 each 函数:


import * as Utils from ‘./utils.js’;


Utils.each([1, 2, 3], function (x) { console.log(x) });

经过“tree shaking”之后,把相应的模块加载进来,新的 main.js 会是这个样子:


function each(collection, iterator) {

  if (Array.isArray(collection)) {

    for (var i = 0; i < collection.length; i++) {

      iterator(collection[i], i, collection);

    }

  } else {

    for (var key in collection) {

      iterator(collection[key], key, collection);

    }

  }

};


each([1, 2, 3], function (x) { console.log(x) });

注意到了吗,导出模块中只有我们用到的 each 被引入进来了。


另外,如果我们突然决定用 filter 函数而非 each,结果就会是这样:


import * as Utils from ‘./utils.js’;


Utils.filter([1, 2, 3], function (x) { return x === 2 });

“tree shaking” 之后的样子:


function each(collection, iterator) {

  if (Array.isArray(collection)) {

    for (var i = 0; i < collection.length; i++) {

      iterator(collection[i], i, collection);

    }

  } else {

    for (var key in collection) {

      iterator(collection[key], key, collection);

    }

  }

};


function filter(collection, test) {

  var filtered = [];

  each(collection, function (item) {

    if (test(item)) {

      filtered.push(item);

    }

  });

  return filtered;

};


filter([1, 2, 3], function (x) { return x === 2 });

注意了(敲黑板),这次 each 和 filter 都包括了进来。这是因为 filter 定义中用到了 each,所以要把两者都导入才能让模块正常运行。


很炫吧?


想来点有难度的吗,可以去 Rollup.js 的 live demo and editor 鼓捣鼓捣 “tree shaking”。


8. 构建 ES6 模块

现在我们已经知道 ES6 模块的加载方式与其它模块格式不同,但仍未谈到使用 ES6 模块时它的构建过程是怎样的?


而不尽完美的是,ES6 模块还需要一些额外的处理,因为有关浏览器如何加载 ES6 模块还没有原生的实现方法。




https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import


下面列出一些可选方案来构建或转换 ES6 模块,以便在浏览器中运行。其中第一条是当下最常用的方法:


使用转译工具(如 Babel、Traceur)将你的 ES6 代码转译成 ES5 中 CommonJS、AMD或 UMD 中任一格式。再以管道的方式导向一个模块捆绑工具,如 Browserify、Webpack,捆绑成一个或多个文件。


用 Rollup.js。它和第一条很类似,但捎带上了 ES6 模块的特性——在构建之前静态分析 ES6 代码和它的依赖关系。它利用“tree shaking”让你的代码包最小。大体来说,使用 Rollup.js 处理你的 ES6 模块比使用 Browserify 或 Webpack 最主要的好处就是,“tree shaking”能让你的代码包更小。有一点要谨慎,Rollup 能让你把代码捆绑成多种格式,包括 ES6、CommonJS、AMD、UMD 和 IIFE。IIFE 和 UMD 代码包在浏览器中照常运行,但如果你捆绑成 AMD、CommonJS 或 ES6,就得用另外的方法再将其转化成浏览器能解析的格式(比如用 Browserify、Webpack、RequireJS 等等)。


9. 过关斩将

一个网页开发者不得不过五关斩六将。把我们养眼的 ES6 模块转化成浏览器可解析的东西往往不是那么轻而易举。


有人会问,什么时候 ES6 模块可以直接运行在浏览器中,不用再做额外的修修补补?


庆幸的是,“迟早会的”。


ECMAScript 目前有一个解决方案叫ECMAScript 6 module loader API。概括地说,它是基于 Promise 的程序 API,将会动态加载模块,并缓存起来,以免后面再引入时不再重新加载新版本。


像这样:


myModule.js


export class myModule {

  constructor() {

    console.log('Hello, I am a module');

  }


  hello() {

    console.log('hello!');

  }


  goodbye() {

    console.log('goodbye!');

  }

}

main.js


System.import(‘myModule’).then(function (myModule) {

  new myModule.hello();

});


// ‘Hello!, I am a module!’

还有另一种方式,你可以在 script 标签直接指定 “type=module” 来定义模块:


<script type="module">

  // loads the 'myModule' export from 'mymodule.js'

  import { hello } from 'mymodule';

  new Hello(); // 'Hello, I am a module!'

</script>

如果你还没看过模块加载器 API 的代码库,我强烈建议去瞟一眼。


另外,如果你想以测试驱动方式推动这个方案,去看下 SystemJS,它正是基于 ES6 Module Loader polyfill。SystemJS 在浏览器和 Node 中动态加载任何格式的模块(ES6 模块、AMD、CommonJS、全局代码)。它在一个“注册表”里记录所有加载过的模块,以免重复加载已有的模块。而且,它可以自动转译 ES6 模块(你只需简单设置一个选项),还能够从其它任何类型的模块中加载成任何类型的模块。相当利索!


10. 有了原生 ES6 模块,我们仍然需要捆绑工具吗?

ES6 模块的日益盛行引起了一些有趣的现象:


10.1、HTTP/2 会让模块捆绑工具被废弃吗?


HTTP/1 只允许一次 TCP 连接发送一个请求,所以在加载多个资源时得要多个请求。用HTTP/2 的话,一切都不一样了。它是完全的多路复用,多个请求和响应可以并行发生。于是,我们可以用一个连接同时处理多个请求。


因为每个 HTTP 请求的代价大大低于 HTTP/1,长远来看,加载一批模块不再导致严重的性能问题。有人便说,这表示没必要再捆绑模块了。这肯定是大有可能,但实际上要看情况而定。


因为一点,模块捆绑还有 HTTP/2 所没有的好处,比如移除冗余的导出模块以节省空间。如果你是建一个网站,性能至关重要、分秒必争,那么长久下来,捆绑模块或许能给你额外的优势。不过,如果说你对性能要求没有如此苛刻,那么跳过构建步骤是可以省下一些为了减小代码包而花去的时间。


总的来说,绝大多数网站都用上 HTTP/2 的那个时候离我们现在还很远。我预测构建过程将会保留,至少在近期内。


附注:HTTP/2 还有其它不同,如果你感兴趣,这是很好的资源。


10.2、CommonJS、AMD、UMD 会被废弃吗?


一旦 ES6 真成了绝对的标准,我们还需要其它非原生的模块格式吗?


我觉得还有。


遵循单一标准在 JavaScript 中导入、导出模块,而不需要中间步骤——网页开发长期受益于此。得要多久才能来到 ES6 成为模块标准的那一天?


有可能要相当一段时间。


况且很多人都喜欢有多种“口味”可选,“仅此一款”可能永远不会成真。


11. 总结

我希望这篇分上下两部分的文章有助于理清开发者谈论模块捆绑时所用的一些术语概念。如果你还不甚了了,倒回去再看看第一部分


一如既往,请在评论中自由交流、提问。


祝你“捆绑”愉快!


复制链接 网友评论 收藏本文 关闭此页
上一条: 产品图片放大镜效果  下一条: ES6中使用箭头定义函数
夜鹰教程网成立于2008年,目前已经运营了将近 13 年,发布了大量关于 html5/css3/C#/asp.net/java/python/nodejs/mongodb/sql server/android/javascript/mysql/mvc/easyui/vue/echarts原创教程。 我们一直都在坚持的是:认证负责、一丝不苟、以工匠的精神来打磨每一套教程,让读者感受到作者的用心。我们默默投入的时间,确保每一套教程都是一件作品,而不是呆板的文字和视频! 目前我们推出在线辅导班试运营,模式为一对一辅导,教学工具为QQ。我们的辅导学科包括 java 、android原生开发、webapp开发、商城开发、C#和asp.net开发,winform和物联网开发、web前端开发,但不仅限于此。 普通班针对的是国内学员,例如想打好基础的大学生、想转行的有志青年、想深入学习的程序员、想开发软件的初学者或者业余爱好者等。 就业办针对即将毕业上岗的大四学生,或者打算转行的初级开发工程师。 留学生班针对的是在欧美、加拿大、澳洲、日本、韩国、新加坡等地留学的中国学子,目的是让大家熟练地掌握编程技能,按时完成老师布置的作业,并能顺利地通过考试。 详细咨询QQ:1416759661   夜鹰教程网  基于角色的权限管理系统(c-s/b-s)。
  夜鹰教程网  基于nodejs的聊天室开发视频教程
  夜鹰教程网  Git分布式版本管理视频教程
  夜鹰教程网  MVC+EasyUI视频教程
  夜鹰教程网  在线考试系统视频教程
  夜鹰教程网  MongoDB视频教程。
  夜鹰教程网 Canvas视频教程
  夜鹰教程网 报表开发视频教程
  推荐教程/优惠活动

  热门服务/教程目录

  夜鹰教程网  新手必看,详细又全面。
  夜鹰教程网  购买教程  夜鹰教程网  在线支付-方便
  夜鹰教程网  担保交易-快捷安全   夜鹰教程网  闪电发货
  夜鹰教程网  电话和QQ随时可以联系我们。
  夜鹰教程网 不会的功能都可以找我们,按工作量收费。

客服电话:153 9760 0032

购买教程QQ:1416759661  
  热点推荐
ajax 清除缓存的两种方法
js日历控件点击日期显示在文本框中…
HTML、JS与FLASH 之间的静态传值方…
主题:ajax请求JSP,为什么GET就是…
javascript 改变iframe(框架)的方…
javascript取鼠标当前坐标
推荐一款网页软键盘 很漂亮的哦
ajax session过期问题的几个解决方…
js文字间隔停顿向上滚动效果
ajax 服务器文本框自动填值
js技术技巧收藏(200例)---1
ajax 数据库中随机读取5条数据动态…
主题:这是否是个捷径?Ajax利用S…
揭开AJAX神秘的面纱(AJAX个人学习…
常用的JS后台导航菜单
  尊贵服务
夜鹰教程网 承接业务:软件开发 网站开发 网页设计 .Net+C#+VS2008+MSsql+Jquery+ExtJs全套高清完整版视频教程
  最近更新
js处理键盘事件(keydown event)…
Web前端技术疑点难点汇总
Asp.Net Core2.0允许跨域请求设置…
XMLHttpRequest请求中的跨域问题
原生js节点的操作 创建、添加、移…
VUE2.0组件:父组件子组件之间值的…
JavaScript是世界上最流行的脚本语…
js正则表达式表单验证详解
js正则表达式大全
详细且实用的JS正则表达式大全
EcmaScript5中扩展了叫bind的方法…
attachEvent和addEventListener的…
addEventListener的使用方式
通过构造器的方式来创建函数
为什么需要addEventListener
  工具下载  需要远程协助? 

sql2008视频教程 c#视频教程

VIP服务:如果您的某个功能不会做,可以加我们QQ,给你做DEMO!

JQUERY  Asp.net教程

MVC视频教程  vs2012
.NET+sql开发
手机:15397600032 C#视频教程下载
微信小程序 vue.js高级实例视频教程

教程咨询QQ:1416759661


这篇文章不能解决你的问题?我们还有相关视频教程云课堂 全套前端开发工程师培训课程

微信号:yyjcw10000 QQ:1416759661  远程协助需要加QQ!

业务范围:视频教程|程序开发|在线解答|Demo制作|远程调试| 点击查看相关的视频教程

技术范围:全端开发/前端开发/webapp/web服务/接口开发/单片机/C#/java/node/sql server/mysql/mongodb/android/。 



关于我们 | 购买教程 | 网站建设 | 技术辅导 | 常见问题 | 联系我们 | 友情链接

夜鹰教程网 版权所有 www.yyjcw.com All rights reserved 备案号:蜀ICP备08011740号3