随着前端功能越来越复杂,前端代码日益膨胀,为了减少维护成本,提高代码的可复用性,前端模块化势在必行。
所有js文件都在一个html中引入,造成以下不良影响:
请求过多。首先我们要依赖多个模块,那样就会发送多个请求,导致请求过多依赖模糊。我们不知道他们的具体依赖关系是什么,也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。难以维护。以上两种原因就导致了很难维护,很可能出现牵一发而动全身的情况导致项目出现严重的问题。webpack中是这样定义的:
在模块化编程中,开发者将程序分解成离散功能块(discrete chunks of functionality),并称之为模块。 每个模块具有比完整程序更小的接触面,使得校验、调试、测试轻而易举。 精心编写的模块提供了可靠的抽象和封装界限,使得应用程序中每个模块都具有条理清楚的设计和明确的目的。
模块应该是职责单一、相互独立、低耦合的、高度内聚且可替换的离散功能块。
模块化是一种处理复杂系统分解成为更好的可管理模块的方式,它可以把系统代码划分为一系列职责单一,高度解耦且可替换的模块,系统中某一部分的变化将如何影响其它部分就会变得显而易见,系统的可维护性更加简单易得。
模块化是一种分治的思想,通过分解复杂系统为独立的模块实现细粒度的精细控制,对于复杂系统的维护和管理十分有益。模块化也是组件化的基石,是构成现在色彩斑斓的前端世界的前提条件。
前端开发和其他开发工作的主要区别,首先是前端是基于多语言、多层次的编码和组织工作,其次前端产品的交付是基于浏览器,这些资源是通过增量加载的方式运行到浏览器端,如何在开发环境组织好这些碎片化的代码和资源,并且保证他们在浏览器端快速、优雅的加载和更新,就需要一个模块化系统。
function fn1(){ // ...}function fn2(){ // ...}
通过 script 标签引入文件,调用相关的函数。这样需要手动去管理依赖顺序,容易造成命名冲突,污染全局,随着项目的复杂度增加维护成本也越来越高。
var output = { _count: 0, fn1: function(){ // ... }}
这样可以解决上面的全局污染的问题,有那么点命名空间的意思,但是随着项目复杂度增加需要越来越多的这样的对象需要维护,不说别的,取名字都是个问题。最关键的还是内部的属性还是可以被直接访问和修改。
var module = (function(){ var _count = 0; var fn1 = function (){ // ... } var fn2 = function fn2(){ // ... } return { fn1: fn1, fn2: fn2 }})()module.fn1();module._count; // undefined
这样就拥有独立的词法作用域,内存中只会存在一份 copy。这不仅避免了外界访问此 iife 中的变量,而且又不会污染全局作用域,通过 return 暴露出公共接口供外界调用。这其实就是现代模块化实现的基础。
还有基于闭包实现的松耦合拓展、紧耦合拓展、继承、子模块、跨文件共享私有对象、基于 new 构造的各种方式,这种方式在现在看来都不再优雅。
// 松耦合拓展// 这种方式使得可以在不同的文件中以相同结构共同实现一个功能块,且不用考虑在引入这些文件时候的顺序问题。// 缺点是没办法重写你的一些属性或者函数,也不能在初始化的时候就是用module的属性。var module = (function(my){ // ... return my})(module || {})// 紧耦合拓展(没有传默认参数)// 加载顺序不再自由,但是可以重载var module = (function(my){ var old = my.someoldfunc my.someoldfunc = function(){ // 重载方法,依然可通过old调用旧的方法... } return my})(module)
历史上,javascript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 ruby 的require、python 的import,甚至就连 css 都有@import,但是 javascript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。
在 es6 之前,社区制定了一些模块加载方案,最主要的有 commonjs 和 amd 两种。前者用于服务器,后者用于浏览器。es6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 commonjs 和 amd 规范,成为浏览器和服务器通用的模块解决方案。
es6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。commonjs 和 amd 模块,都只能在运行时确定这些东西。比如,commonjs 模块就是对象,输入时必须查找对象属性。
目前实现模块化的规范主要有:
commonjscmdamdes6模块commonjs 是以在浏览器环境之外构建 javascript 生态系统为目标而产生的项目,比如在服务器和桌面环境中。
采用同步加载模块的方式,也就是说只有加载完成,才能执行后面的操作。commonjs 代表:node 应用中的模块,通俗的说就是你用 npm 安装的模块。
它使用 require 引用和加载模块,exports 定义和导出模块,module 标识模块。使用 require 时需要去读取并执行该文件,然后返回 exports 导出的内容。
//定义模块 math.js var random=math.random()*10; function printrandom(){ console.log(random) } function printintrandom(){ console.log(math.floor(random)) } //模块输出 module.exports={ printrandom:printrandom, printintrandom:printintrandom } //加载模块 math.js var math=require('math') //调用模块提供的方法 math.printintrandom() math.printrandom()
|-js |-dist //打包生成文件的目录 |-src //源码所在的目录 |-module1.js |-module2.js |-module3.js |-app.js //应用主源文件|-index.html //运行于浏览器上|-package.json { "name": "browrify-test", "version": "1.0.0" }
注意:index.html文件要运行在浏览器上,需要借助browrify将app.js文件打包编译,如果直接在index.html引入app.js就会报错!
根目录下运行browrify js/src/app.js -o js/dist/bundle.js
在index.html文件中引入<script type=”text/javascript” src=”js/dist/bundle.js”></script>
异步模块定义,所谓异步是指模块和模块的依赖可以被异步加载,他们的加载不会影响它后面语句的运行。有效避免了采用同步加载方式中导致的页面假死现象。amd代表:requirejs。
amd一开始是commonjs规范中的一个草案,全称是asynchronous module definition,即异步模块加载机制。后来由该草案的作者以requirejs实现了amd规范,所以一般说amd也是指requirejs。
requirejs是一个工具库,主要用于客户端的模块管理。它的模块管理遵守amd规范,requirejs的基本思想是,通过define方法,将代码定义为模块;通过require方法,实现代码的模块加载。
它主要有两个接口:define 和 require。define 是模块开发者关注的方法,而 require 则是模块使用者关注的方法。
define(id?, dependencies?, factory);//id :可选参数,它指的是模块的名字。//dependencies:可选参数,定义中模块所依赖模块的数组。//factory:模块初始化要执行的函数或对象
需要注意的是,dependencies有多少个元素,factory就有多少个传参,位置一一对应。使用栗子:
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) { exports.verb = function() { return beta.verb(); //or: //return require("beta").verb(); } });
require([module], callback);//module:一个数组,里面的成员就是要加载的模块.//callback:模块加载成功之后的回调函数。
需要注意的是 ,module 有多少个元素,callback 就有多少个传参,位置一一对应。
require(["a","b","c"],function(a,b,c){ //code here });
amd 运行时核心思想是「early executing」,也就是提前执行依赖 amd 的这个特性有好有坏:
首先,尽早执行依赖可以尽早发现错误。另外,尽早执行依赖通常可以带来更好的用户体验,也容易产生浪费。引用amd的javscript库: 目前,主要有两个javascript库实现了amd规范:require.js和curl.js在浏览器环境中异步加载模块;并行加载多个模块;开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅;不符合通用的模块化思维方式,是一种妥协的实现。然后将requ上海国际马拉松赛ire.js导入项目: js/libs/require.js
|-js |-libs |-require.js |-modules |-alerter.js |-datarvice.js |-main.js|-index.html
// datarvice.js文件// 定义没有依赖的模块define(function() { let msg = 'www.baidu.com' function getmsg() { return msg.toupperca() } return { getmsg } // 暴露模块});//alerter.js文件// 定义有依赖的模块define(['datarvice'], function(datarvice) { let name = 'tom' function showmsg() { alert(datarvi中国快餐排行ce.getmsg() + ', ' + name) } // 暴露模块 return { showmsg }});// main.js文件(function() { require.config({ baurl: 'js/', //基本路径 出发点在根目录下 paths: { //映射: 模块标识名: 路径 alerter: './modules/alerter', //此处不能写成alerter.js,会报错 datarvice: './modules/datarvice' } }); 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>
在index.html引入 <script data-main=”js/main” src=”js/libs/require.js”></script>
此外在项目中如何引入第三方库?只需在上面代码的基础稍作修改:
// alerter.js文件define(['datarvice', 'jquery'], function(datarvice, $) { let name = 'tom' function showmsg() { alert(datarvice.getmsg() + ', ' + name) } $('body').css('background', 'green') // 暴露模块 return { showmsg }});// main.js文件(function() { require.config({ baurl: 'js/', //基本路径 出发点在根目录下 内部推荐 paths: { //自定义模块 alerter: './modules/alerter', //此处不能写成alerter.js,会报错 datarvice: './modules/datarvice', // 第三方库模块 jquery: './libs/jquery-1.10.1' //注意:写成jquery会报错 } }) require(['alerter'], function(alerter) { alerter.showmsg() })})()
上例是在alerter.js文件中引入jquery第三方库,main.js文件也要有相应的路径配置。 小结:通过两者的比较,可以得出amd模块定义的方法非常清晰,不会污染全局环境,能够清楚地显示依赖关系。amd模式可以用于浏览器环境,并且允许非同步加载模块,也可以根据需要动态加载模块。
cmd是ajs在推广过程中生产的对模块定义的规范,在web浏览器端的模块加载器中,ajs与requirejs并称,ajs作者为阿里的玉伯。
cmd规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。cmd规范整合了commonjs和amd规范的特点。在 a.js 中,所有 javascript 模块都遵循 cmd模块定义规范。
//定义没有依赖的模块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})
define(function (require) { var m1 = require('./module1') var m4 = require('./module4') m1.show() m4.show()})
然后将a.js导入项目: js/libs/a.js
|-js |-libs |-a.js |-modules |-module1.js |-module2.js |-module3.js |-module4.js |-main.js|-index.html
// 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()})
<script type="text/javascript" src="js/libs/a.js"></script><script type="text/javascript"> ajs.u('./js/modules/main')</script>
es6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。所以说es6是编译时加载,不同于commonjs的运行时加载(实际加载的是一整个对象),es6模块不是对象,而是通过export命令显式指定输出的代码,输入时也采用静态命令的形式。
es6 的模块自动采用严格模式,不管你有没有在模块头部加上”u strict”;。
严格模式主要有以下限制。
变量必须声明后再使用函数的参数不能有同名属性,否则报错不能使用with语句不能对只读属性赋值,否则报错不能使用前缀 0 表示八进制数,否则报错不能删除不可删除的属性,否则报错不能删除变量delete prop,会报错,只能删除属性delete global[prop]eval不会在它的外层作用域引入变量eval和arguments不能被重新赋值arguments不会自动反映函数参数的变化不能使用arguments.callee不能使用arguments.caller禁止this指向全局对象不能使用fn.caller和fn.arguments获取函数调用的堆栈增加了保留字(比如protected、static和interface)上面这些限制,模块都必须遵守。由于严格模式是 es5 引入的,不属于 es6,所以请参阅相关 es5 书籍,本书不再详细介绍了。
其中,尤其需要注意this的限制。es6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 js 文件,里面使用export命令输出变量。
function cul(){ let ulele = document.createelement("ul"); for(let i = 0; i < 5; i++){ let liele = document.createelement("li"); liele.inner电磁加热原理html = "无序列表" + i; ulele.appendchild(liele); } return ulele;}let ul = cul();export {ul};
使用export命令定义了模块的对外接口以后,其他 js 文件就可以通过import命令加载这个模块。
import {table} from "../te井冈山旅游景点st/test_table.js";import {div} from "../test/test_div.js" ;import {ul} from "../test/test_ul.js" ;export {table, div, ul};
它们有两个重大差异:
1. commonjs 模块输出的是一个值的拷贝,es6 模块输出的是值的引用。
2. commonjs 模块是运行时加载,es6 模块是编译时输出接口。
第二个差异是因为 commonjs 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 es6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
下面重点解释第一个差异,我们还是举上面那个commonjs模块的加载机制例子:
// lib.jxport let counter = 3;export function inccounter() { counter++;}// main.jsimport { counter, inccounter } from './lib';console.log(counter); // 3inccounter();console.log(counter); // 4
es6 模块的运行机制与 commonjs 不一样。es6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
本文发布于:2023-04-05 12:02:48,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/bd5142c8027a66cfa1a5768fb08eb91b.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:模块化编程的好处(讲解编程入门基础知识).doc
本文 PDF 下载地址:模块化编程的好处(讲解编程入门基础知识).pdf
留言与评论(共有 0 条评论) |