https://addyosmani.com/writing-modular-js/javascript
模塊:能夠理解爲一個js文件,就像你之前須要import的那個文件同樣:module不必定非要是一個外部文件,你能夠動態建立一個module。css
loaded into global context,module中的變量或者函數都是封裝起來的,不會被暴露到前端
global,只有export出來的纔會暴露;java
你不能使用<script>來直接引用,只能經過systemjs來加載;node
每一個模塊只有一個實例signleton;jquery
模塊就是一個黑盒子。webpack
Package: cards package能夠包含card.js module, deck.js module, 而index.js倒是一個入口,將多個模塊打包在一塊兒。git
app也能夠看作一個package程序員
module loader:es6
當咱們說一個應用是modular的,咱們一般意思是:這個應用是由一系列高度解耦的具備不一樣功能的分佈在不一樣module裏面的功能模塊組成的。正如你可能知道的:輕量級解耦每每具備下面的好處:你能夠輕鬆地刪除部分依賴而不會影響到其餘模塊的功能。
不像其餘的編程語言,javascript並不原生具有模塊化的能力。正由於如此,程序員只能使用第三方開發的模塊依賴處理庫來實現模塊化開發模式:AMD, COMMONJS,ES6
在探討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: define + require
CMD: exports + require
ES6: export + import
AMD(Asynchronous Module Defination)format的目標是提供一個可供js開發人員當下使用的模塊化js方案。
AMD module的格式自己建議定義定義的模塊以及該模塊的依賴dependencies能夠被異步地加載。
在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)); } } }; });
// 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 }; });
// 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. } });
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(); });
咱們來看看他解決的問題和優點:
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
傳統的js設計模式也能夠方便地以AMD方式傳承和使用:
使用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:
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自己是原生語言支持實現的模塊化,可是現代瀏覽器大多都還未支持,所以必須使用相應的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是一個transpiler,代碼轉換器,它起到了es6和es5的橋樑的做用,做爲build step而存在於工具鏈的, 支持全部的模塊格式:module formats. Typescript和babel是相似的,它支持es6而且transpile到es5
在上面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相似。