#模塊化的歷史和模塊加載器 [TOC]javascript
##模塊化的需求來源css
http://huangxuan.me/js-module-7day/#/ ##實現模塊化 ###基本原理html
function m1(){ // 模塊內容 // 這種方式污染全局變量 }
var module1 = new Object({ _count = 0; m1: function(){ } // 模塊成員會被暴露,內部狀態能夠被外部改寫 })
var module1 = (function($){ var _count = 0; var m1 = function(){ // ... } var _$body = $('body'); var foo = function(){ console.log(_$body); } return { m1: m1, foo: foo } // })(jQuery) module1.foo();
##模塊加載器 ###js加載問題前端
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script src="js/require.js" data-main="js/main"></script> </body> </html>
// 主模塊(入口文件)的寫法 require.config({ paths: { "jquery": 'jquery.min', "underscore": 'underscore.min', "backbone": 'backbone.min' } }); require(['jquery', 'underscore','backbone'], function($, _, Backbone){ console.log(_.min([1,10,2])) }); require(['math'], function (math) { alert(math.min([10, 11])) });
// 定義模塊的方法 define(['underscore'], function(_){ var min = function (x, y){ return _.min(x,y); }; return { min: min } });
此外,require.js提供了一個優化工具r.js,能夠將模塊打包,減小網絡請求java
require.js還有一些插件,能夠將圖片和文本打包 ####sea.js(CMD規範)react
// 引入sea.js而且配置入口文件 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app">dfslljdslf</div> <script src="../sea-modules/seajs/seajs/2.2.0/sea.js"></script> <script> seajs.config({ base: '../sea-modules/', alias: { 'jquery': 'jquery/jquery/1.10.1/jquery.js' } }) seajs.use('../static/test/main.js'); </script> </body> </html>
// 入口文件寫法 define(function (require, exports, module) { var test = require('./test'); test.fadeOut(); });
// 定義模塊方法 define(function(require, exports, module){ var $ = require('jquery'); exports.fadeOut = function(){ $('#app').fadeOut(); } })
#####區別 寫法上:require.js依賴前置,sea.js依賴就近,語法更像common.js 加載和執行順序上:require.js依賴提早執行,sea.js依賴懶執行,打日誌能夠看出來jquery
https://blog.csdn.net/zshake/article/details/53054722 ####common.js(node.js服務器端)webpack
// 最終導出的是exports對象 console.log("example.js"); exports.message = "hi"; exports.say = function (){ console.log("hello"); };
// 使用require加載模塊 var example = require('./example.js');
####ES6 module ES6 模塊的設計思想是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時肯定這些東西。好比,CommonJS 模塊就是對象,輸入時必須查找對象屬性。git
// CommonJS模塊 let { stat, exists, readFile } = require('fs'); // 等同於 let _fs = require('fs'); let stat = _fs.stat; let exists = _fs.exists; let readfile = _fs.readfile;
上面代碼的實質是總體加載fs模塊(即加載fs的全部方法),生成一個對象(_fs),而後再從這個對象上面讀取 3 個方法。這種加載稱爲「運行時加載」,由於只有運行時才能獲得這個對象,致使徹底沒辦法在編譯時作「靜態優化」。 ES6 模塊不是對象,而是經過export命令顯式指定輸出的代碼,再經過import命令輸入。
// ES6模塊 import { stat, exists, readFile } from 'fs';
上面代碼的實質是從fs模塊加載 3 個方法,其餘方法不加載。這種加載稱爲「編譯時加載」或者靜態加載,即 ES6 能夠在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高。固然,這也致使了無法引用 ES6 模塊自己,由於它不是對象。 因爲 ES6 模塊是編譯時加載,使得靜態分析成爲可能。有了它,就能進一步拓寬 JavaScript 的語法,好比引入宏(macro)和類型檢驗(type system)這些只能靠靜態分析實現的功能。
// export寫法 // 1.直接導出 export var firstName = 'Michael'; // 2.先聲明後導出 var firstName = 'Michael'; export {firstName} // 3.導出函數或類 export function multiply(x,y){ return x+y; } // 4.export命令規定的是對外的接口,必須與模塊內部的變量創建一一對應關係 export 1 // 報錯 var m = 1; export m; //報錯 export var m = 1; // 不報錯 export {m} // 不報錯 // import寫法 import {firstName, lastName, year} from './profile.js'; import { lastName as surname } from './profile.js'; // js後綴能夠省略 import {myMethod} from 'util'; // import命令具備提高效果,會提高到整個模塊的頭部,首先執行 foo(); import { foo } from 'my_module'; // 因爲import是靜態執行,因此不能使用表達式和變量,這些只有在運行時才能獲得結果的語法結構 import { 'f' + 'oo' } from 'my_module'; // 報錯 // import語句會執行所加載的模塊,所以能夠有下面的寫法 import 'lodash'; // 僅僅執行lodash模塊,可是不輸入任何值 // 模塊的總體加載 // circle.js export function area(radius) { return Math.PI * radius * radius; } export function circumference(radius) { return 2 * Math.PI * radius; } import * as circle from './circle'; console.log('圓面積:' + circle.area(4)); console.log('圓周長:' + circle.circumference(14)); // export default用法 // export-default.js export default function () { console.log('foo'); } // import-default.js import customName from './export-default'; // 不用括號 customName(); // 'foo' // 一個模塊只能有一個默認輸出,所以export default命令只能使用一次
import()方法 在語法上,條件加載就不可能實現。若是import命令要取代 Node 的require方法,這就造成了一個障礙。由於require是運行時加載模塊,import命令沒法取代require的動態加載功能。
const path = './' + fileName; const myModual = require(path);
上面的語句就是動態加載,require到底加載哪個模塊,只有運行時才知道。import命令作不到這一點。 所以,有一個提案,建議引入import()函數,完成動態加載。
import(specifier)
上面代碼中,import函數的參數specifier,指定所要加載的模塊的位置。import命令可以接受什麼參數,import()函數就能接受什麼參數,二者區別主要是後者爲動態加載。 import()返回一個 Promise 對象。下面是一個例子。
const main = document.querySelector('main'); import(`./section-modules/${someVariable}.js`) .then(module => { module.loadPageInto(main); }) .catch(err => { main.textContent = err.message; });
import()相似於 Node 的require方法,區別主要是前者是異步加載,後者是同步加載。 適用場景
button.addEventListener('click', event => { import('./dialogBox.js') .then(dialogBox => { dialogBox.open(); }) .catch(error => { /* Error handling */ }) });
if (condition) { import('moduleA').then(...); } else { import('moduleB').then(...); }
import(f()) // 根據函數f的返回結果,加載不一樣的模塊 .then(...);
注意點 import()加載模塊成功之後,這個模塊會做爲一個對象,看成then方法的參數。
// 解構賦值 import('./myModule.js') .then(({export1, export2}) => { // ...· }); // 同時加載多個模塊 Promise.all([ import('./module1.js'), import('./module2.js'), import('./module3.js'), ]) .then(([module1, module2, module3]) => { ··· });
http://es6.ruanyifeng.com/#docs/module ###打包工具 require.js有相似r.js的打包工具,此外還有一些打包工具 ####browserify 讓瀏覽器加載Nodejs模塊
$ browserify main.js -o bundle.js
https://javascript.ruanyifeng.com/tool/browserify.html ####webpack 能夠打包各類文件資源,可支持各類模塊規範 #####webpack.config.js配置文件 配置文件自己就是一個模塊
var path = require('path'); var webpack = require('webpack'); var devFlagPlugin = new webpack.DefinePlugin({ _DEV_: JSON.stringify(JSON.parse(process.env.DEBUG || 'false')) }); module.exports={ entry: { // 入口文件,String/Array/Object bundle1: 'main1.jsx', bundle2: 'main2.jsx', bundle: 'main.jsx' }, output:{ // 指定輸出位置和文件名 filename:'[name].js', path: path.resolve(__dirname, './') }, mode:'development', module: { // loader 讓 webpack 可以去處理那些非 JavaScript 文件(webpack 自身只理解 JavaScript) rules: [ { test: /\.jsx?$/, exclude: /node_modules/, use:{ loader: 'babel-loader', // 處理ES6 options: { presets: ['es2015', 'react'] } } }, { test: /\.css$/, // 處理css文件 use: [ { loader: 'style-loader', }, { loader: 'css-loader', options: { modules: true, } } ] }, { test: /\.(jpg|png)$/, // 解析圖片模塊 use: { loader: 'url-loader', options: { limit: 8192 } } } ] }, plugins: [ // loader 被用於轉換某些類型的模塊,而插件則能夠用於執行範圍更廣的任務,須要經過使用 new 操做符來建立它的一個實例 devFlagPlugin, // 根據node命令行參數設置變量_DEV_,_DEV_能夠在任何模塊使用 new webpack.optimize.CommonsChunkPlugin({ // code splitting name: 'commons', filename: 'commons.js', }) ] };
// main.js const React = require('react'); const ReactDOM = require('react-dom'); var style = require('./app.css'); // 導入css文件模塊(css module) var img1 = new Image(); img1.src = require('./big.png'); // 導入圖片資源 document.body.appendChild(img1); if(_DEV_){ document.write(new Date()); } ReactDOM.render( <div> <h1 className={style.h1}>Hello, world!</h1> <h2 className="h2">Hello, webpack</h2> </div>, document.querySelector('#wrapper') );
webpack.config.js還能夠導出一個函數或promise對象,能夠導出多種規範的模塊,能夠熱替換,只更新修改的模塊。 webpack 從命令行或配置文件中定義的一個模塊列表開始,處理你的應用程序。 從這些入口起點開始,webpack 遞歸地構建一個依賴圖,這個依賴圖包含着應用程序所需的每一個模塊,而後將全部這些模塊打包爲少許的 bundle - 一般只有一個 - 可由瀏覽器加載。
https://www.webpackjs.com/concepts/ https://github.com/ruanyf/webpack-demos#demo10-code-splitting-source http://www.ruanyifeng.com/blog/2012/10/javascript_module.html http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_definition.html http://www.ruanyifeng.com/blog/2012/11/require_js.html