https://addyosmani.com/writing-modular-js/

一些术语:

模块:可以理解为一个js文件,就像你以前需要import的那个文件一样:module不一定非要是一个外部文件,你可以动态创建一个module。

loaded into global context,module中的变量或者函数都是封装起来的,不会被暴露到

global,只有export出来的才会暴露;

你不能使用<script>来直接引用,只能通过systemjs来加载;

每个模块只有一个实例signleton;

模块就是一个黑盒子。

Package: cards package可以包含card.js module, deck.js module, 而index.js却是一个入口,将多个模块打包在一起。

app也可以看做一个package

module loader:

什么是模块化

当我们说一个应用是modular的,我们通常意思是:这个应用是由一系列高度解耦的具有不同功能的分布在不同module里面的功能模块组成的。正如你可能知道的:轻量级解耦往往具有下面的好处:你可以轻松地删除部分依赖而不会影响到其他模块的功能。

不像其他的编程语言,javascript并不原生具备模块化的能力。正因为如此,程序员只能使用第三方开发的模块依赖处理库来实现模块化开发模式:AMD, COMMONJS,ES6

script loader

在探讨AMD, COMMONJS之前我们必须来谈谈script loader这个概念。

script loading is a means to a goal, that goal being modular JavaScript that can be used in applications today - for this, use of a compatible script loader is unfortunately necessary.

有几种广泛应用的Loaders来处理AMD,COMMJS格式的模块加载,比如requireJS, curl.js,dojo,甚至system.js

从产品角度来看,使用优化工具,比如RequireJS Optimizer来拼接所有的脚本文件也是很有必要的。但是另外一个可行的应用场景是:应用程序可以在页面page load后动态加载需要的脚本,而RequireJS就能很好地满足到这一点。

AMD, COMMONJS, ES6三种模块化方案简单对比:

AMD: define + require

CMD: exports + require

ES6: export + import

AMD

AMD(Asynchronous Module Defination)format的目标是提供一个可供js开发人员当下使用的模块化js方案。

AMD module的格式本身建议定义定义的模块以及该模块的依赖dependencies可以被异步地加载。

AMD模块定义和使用

在AMD模块哲学中,有两个重要的概念: define 方法用于定义一个模块,而require 方法则用于处理dependency loading.

define(
module_id /*optional:如果该参数不存在,我们就成为匿名模块*/,
[dependencies] /*optional*/,
definition function /*function for instantiating the module or object*/
);

实例:

// A module_id (myModule) is used here for demonstration purposes only

define('myModule',
['foo', 'bar'],
// module definition function
// dependencies (foo and bar) are mapped to function parameters
function ( foo, bar ) {
// return a value that defines the module export
// (i.e the functionality we want to expose for consumption) // create your module here
var myModule = {
doStuff:function(){
console.log('Yay! Stuff');
}
} return myModule;
}); // An alternative example could be..
define('myModule',
['math', 'graph'],
function ( math, graph ) { // Note that this is a slightly different pattern
// With AMD, it's possible to define modules in a few
// different ways due as it's relatively flexible with
// certain aspects of the syntax
return {
plot: function(x, y){
return graph.drawPie(math.randomGrid(x,y));
}
}
};
});

理解AMD:require()

// Consider 'foo' and 'bar' are two external modules
// In this example, the 'exports' from the two modules loaded are passed as
// function arguments to the callback (foo and bar)
// so that they can similarly be accessed require(['foo', 'bar'], function ( foo, bar ) {
// rest of your code here
foo.doSomething();
});

动态加载dependecies

define(function ( require ) {
var isReady = false, foobar; // note the inline require within our module definition
require(['foo', 'bar'], function (foo, bar) {
isReady = true;
foobar = foo() + bar();
}); // we can still return a module
return {
isReady: isReady,
foobar: foobar
};
});

理解AMD Plugin

// With AMD, it's possible to load in assets of almost any kind
// including text-files and HTML. This enables us to have template
// dependencies which can be used to skin components either on
// page-load or dynamically. define(['./templates', 'text!./template.md','css!./template.css'],
function( templates, template ){
console.log(templates);
// do some fun template stuff here.
}
});

通过两种方式来加载AMD模块:

1. require.js

require(['app/myModule'],
function( myModule ){
// start the main module which in-turn
// loads other modules
var module = new myModule();
module.doStuff();
});

2.curl.js

curl(['app/myModule.js'],
function( myModule ){
// start the main module which in-turn
// loads other modules
var module = new myModule();
module.doStuff();
});

为什么说AMD是javascript模块化的一个比较好的选择?

我们来看看他解决的问题和优势:

1. 定义了一个清晰的建议去如何实现灵活的模块;

2. 相比于当前通过全局引入一个对象,和通过<script>标签加载文件的方案要清晰地多。清晰地定义一个模块以及这个模块的所有依赖;

3. 模块定义是封装好的,这样我们就不会污染global namespace。(比如常用的jquery,实际上我们就污染全局空间,引入了$这个变量)

4. 比一些替代方案可能更好用(比如commonjs),没有cross-domain的问题,没有local/debugging问题,也没有对server side工具有任何依赖。大部分AMD Loaders支持并不需要任何build process的AMD modules

5. 提供了一种在单个文件中包含多个模块的'transport'方案。而比如commonjs却要求必须统一一个transport format

6. 可以轻松实现lazy load script

AMD Module Design patterns

传统的js设计模式也可以方便地以AMD方式传承和使用:

AMD modules with jQuery

使用Jquery(注意这种AMD模块引入jquery的方式来使用没有在全局命名空间中引入$或者jquery!):

define(['js/jquery.js','js/jquery.color.js','js/underscore.js'],
function($, colorPlugin, _){
// Here we've passed in jQuery, the color plugin and Underscore
// None of these will be accessible in the global scope, but we
// can easily reference them below. // Pseudo-randomize an array of colors, selecting the first
// item in the shuffled array
var shuffleColor = _.first(_.shuffle(['#666','#333','#111'])); // Animate the background-color of any elements with the class
// 'item' on the page using the shuffled color
$('.item').animate({'backgroundColor': shuffleColor }); return {};
// What we return can be used by other modules
});

上面是jquery以模块方式来使用,我们还得看看jquery为了可以被以AMD方式加载应用,我们应该做什么来改造jquery:

CommonJS

require()和exports

这一点和AMD类似的, exports指示本模块需要暴露给其他模块使用的对象;require则表明本模块的依赖模块

// package/lib is a dependency we require
var lib = require('package/lib'); // some behaviour for our module
function foo(){
lib.log('hello world!');
} // export (expose) foo to other modules
exports.foo = foo;

更复杂一点的例子:

// define more behaviour we would like to expose
function foobar(){
this.foo = function(){
console.log('Hello foo');
} this.bar = function(){
console.log('Hello bar');
}
} // expose foobar to other modules
exports.foobar = foobar; // an application consuming 'foobar' // access the module relative to the path
// where both usage and module files exist
// in the same directory var foobar = require('./foobar').foobar,
test = new foobar(); test.bar(); // 'Hello bar'

同时使用多个依赖:

var modA = require('./foo');
var modB = require('./bar'); exports.app = function(){
console.log('Im an application!');
} exports.foo = function(){
return modA.helloWorld();
}

注意commonJS module export时的以下情况:

// CMD 模式下的合法exports
exports.someFunc = someFunc;
module.exports.someFunc = someFunc; module.exports = {};
module.exports = function(){}; //相反下面就是非法的!
exports = {};
exports = function(){};

ES6

由于ES6本身是原生语言支持实现的模块化,但是现代浏览器大多都还未支持,因此必须使用相应的transpiler工具转换成ES5的AMD,CMD模块,再借助于systemjs/requirejs等模块加载工具才能使用。

上图中直到生成AMD, CommonJS的ES5模块都属于dev/build流程,而从ES5 CMD,AMD代码到SystemJS/RequireJS加载则属于runtime过程

module 'math' {
// named exports
export default function sum(x, y) { //注意default export是当import
return x + y;
}
export var pi = 3.141593;
}
//或者使用下面的literal export方法
export { sum as sumFunc, pi }
// we can import in script code, not just inside a module
import {sum, pi} from 'math';
alert("2π = " + sum(pi, pi));
// 或者这样import和调用
import *as mathmodule from 'math';
alert("2π = " + mathmodule.sum(mathmodule.pi, mathmodule.pi));

// 下面的语法则同时import了default export和自选export: sum是上面export default定义的
// 而pi则没有export default关键字!
import sum, { pi } from 'math'

关于babel

babel是一个transpiler,代码转换器,它起到了es6和es5的桥梁的作用,作为build step而存在于工具链的, 支持所有的模块格式:module formats. Typescript和babel是类似的,它支持es6并且transpile到es5

module bundler

在上面ES6模块章节提到要让build step将es6模块转化出来的es5 cmd模块能在浏览器中运行,有一个解决方案就是:在浏览器中加载一个module loader(requireJS/SystemJS),由加载器来动态加载相应的js文件。和这个方案对应的有另外一个方案就是使用module bundler(browserify/webpack),这个bundler工具作为build step的延伸。实际上如果是ES6 module,webpack将调用babel来transpile成CMD模块,并且最后打包成一个或者多个大的chunk直接加载到浏览器运行(注意:这时无需systemjs/requirejs加载器,因为webpack本身已经能够认识CMD的require/exports,AMD的define!!)

模块化工具链

我们已经知道js语言本身并不支持模块化,同时浏览器中js和服务端nodejs中的js运行环境是不同的,如何实现浏览器中js模块化,不是一个很简单的事情,主流有两种方案:

1. requirejs/seajs:他们是一种在线“编译”模块的方案,相当于在页面上加载一个CommonJS/AMD模块格式解释器。这样浏览器就认识了上面讲到的define, exports,module这些东西,也就实现了模块化。

2.browserify/webpack:是一个预编译模块打包的方案,相比于第一种方案,这个方案更加智能。由于是预编译的,不需要在浏览器中加载解释器。你在本地直接写JS,不管是AMD/CMD/ES6风格的模块化,它都能认识,并且编译成浏览器认识的JS

注意: browerify打包器本身只支持Commonjs模块,如果要打包AMD模块,则需要另外的plugin来实现AMD到CMD的转换!!https://github.com/jaredhanson/deamdify

gulp一般作为task runner,在前端构建流程中起到非常重要的作用,它在软件工程界和C语言的make类似。

Browserify: It provides a way to bundle CommonJS modules together, adheres to the Unix philosophy, is in fact a good alternative to Webpack.
http://www.zhihu.com/question/37020798

javascript模块化以及加载打包的更多相关文章

  1. Javascript图片预加载详解

    预加载图片是提高用户体验的一个很好方法.图片预先加载到浏览器中,访问者便可顺利地在你的网站上冲浪,并享受到极快的加载速度.这对图片画廊及图片占据很大比例的网站来说十分有利,它保证了图片快速.无缝地发布 ...

  2. javascript图片懒加载与预加载的分析

    javascript图片懒加载与预加载的分析 懒加载与预加载的基本概念.  懒加载也叫延迟加载:前一篇文章有介绍:JS图片延迟加载 延迟加载图片或符合某些条件时才加载某些图片. 预加载:提前加载图片, ...

  3. HTML5+javascript实现图片加载进度动画效果

    在网上找资料的时候,看到网上有图片加载进度的效果,手痒就自己也写了一个. 图片加载完后,隐藏loading效果. 想看加载效果,请ctrel+F5强制刷新或者清理缓存. 效果预览:   0%   // ...

  4. 第一百一十八节,JavaScript,动态加载脚本和样式

    JavaScript,动态加载脚本和样式 一动态脚本 当网站需求变大,脚本的需求也逐步变大.我们就不得不引入太多的JS脚本而降低了整站的性能,所以就出现了动态脚本的概念,在适时的时候加载相应的脚本. ...

  5. Javascript中页面加载完成后优先执行顺序

    Javascript中页面加载完成后优先执行顺序 document优先于windowwindow优先于element //document加载完成执行方法体 document.addEventList ...

  6. js, javascript 图片懒加载 实例代码

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. Javascript图片预加载详解 分类: JavaScript HTML+CSS 2015-05-29 11:01 768人阅读 评论(0) 收藏

    预加载图片是提高用户体验的一个很好方法.图片预先加载到浏览器中,访问者便可顺利地在你的网站上冲浪,并享受到极快的加载速度.这对图片画廊及图片占据很大比例的网站来说十分有利,它保证了图片快速.无缝地发布 ...

  8. webdriver定位页面元素时使用set_page_load_time()和JavaScript停止页面加载

    webdriver定位页面元素时使用set_page_load_time()和JavaScript停止页面加载 原文:https://my.oschina.net/u/2344787/blog/400 ...

  9. 第一百五十七节,封装库--JavaScript,预加载图片

    封装库--JavaScript,预加载图片 首先了解一个Image对象,为图片对象 Image对象 var temp_img = new Image();   //创建一个临时区域的图片对象alert ...

随机推荐

  1. debian sudo

    apt-get install sudo vi /etc/sudoers add CentOS 7 root ALL=(ALL) ALL Debian root ALL=(ALL:ALL) ALL 按 ...

  2. 浅谈flex布局

    Flex布局,俗称弹性布局,有了这个布局,咱们做的事情很多,以前那些很难实现比如说垂直居中之类都不存在了. 盒模型布局依赖于float,display,定位之类的方式来布局,这种的布局对一些特殊布局来 ...

  3. Orcale 之子查询

    子查询和连接查询一样,都提供了使用单个查询访问多个表中的数据的方法.子查询在其他查询的基础上,提供一种进一步有效的方式来访问数据. IN 关键字 使用 IN 关键字可以将原表中特定的的值与子查询中返回 ...

  4. Derby的jar说明

    Derby的jar说明 Derby的下载后,解压发现lib中有很多jar包,下面说明一下每个jar包的用途: 引擎库 derby.jar是引擎库,必须的 For embedded databases. ...

  5. MySql的存储引擎介绍

    下面主要介绍InnoDB.MyISAM和MEMEORY三种存储引擎. InnoDB存储引擎 InnoDB遵循CNU通用公开许可(GPL)发行.InnoDB已经被一些重量级互联网公司所采用,如雅虎.Sl ...

  6. no jpeg in java.library.path;java.lang.NoClassDefFoundError: Could not initialize class sun.awt.image.codec.JPEGImageEncoderImpl

    no jpeg in java.library.path;java.lang.NoClassDefFoundError: Could not initialize class sun.awt.imag ...

  7. ElasticSearch基础入门

    1.query查询表达式 Elasticsearch 提供一个丰富灵活的查询语言叫做 查询表达式 , 查询表达式(Query DSL)是一种非常灵活又富有表现力的 查询语言,它支持构建更加复杂和健壮的 ...

  8. ASP.NET能知道的东西

    ASP.NET能知道的东西 获取服务器电脑名: Page.Server.ManchineName 获取用户信息: Page.User 获取客户端电脑名:Page.Request.UserHostNam ...

  9. ASP.NET Web API根据代码注释生成Help文档

    使用Visual Studio新建一个ASP.NET Web API项目,直接运行,查看Help文档可以看到如下的API帮助说明 如何在Description中显示描述. 1. 打开Controlle ...

  10. Java - 关于扩展线程安全类的一些思考

    重用可以节省我们进行开发和测试(测试比我们自己测严谨地多)的时间和其他各种成本. 但是,对一个线程安全类进行扩展的时候就需要思考一些问题. 比如我们熟知的线程安全类Vector,该类中对所有的公有方法 ...