言
The last time, I have learned
【THE LAST TIME】一直是我想寫(xiě)的一個(gè)系列,旨在厚積薄發(fā),重溫前端。
也是對(duì)自己的查缺補(bǔ)漏和技術(shù)分享。
歡迎大家多多評(píng)論指點(diǎn)吐槽。
系列文章均首發(fā)于公眾號(hào)【全棧前端精選】,筆者文章集合詳見(jiàn)GitHub 地址:Nealyang/personalBlog。目錄和發(fā)文順序皆為暫定
隨著互聯(lián)網(wǎng)的發(fā)展,前端開(kāi)發(fā)也變的越來(lái)越復(fù)雜,從一開(kāi)始的表單驗(yàn)證到現(xiàn)在動(dòng)不動(dòng)上千上萬(wàn)行代碼的項(xiàng)目開(kāi)發(fā),團(tuán)隊(duì)協(xié)作就是我們不可避免的工作方式,為了更好地管理功能邏輯,模塊化的概念也就漸漸產(chǎn)生了。
好的書(shū)籍會(huì)分章節(jié),好的代碼得分模塊。
JavaScript 在早期的設(shè)計(jì)中就沒(méi)有模塊、包甚至類的概念,雖然 ES6 中有了 class 關(guān)鍵字,那也只是個(gè)語(yǔ)法糖。隨意隨著項(xiàng)目復(fù)雜度的增加,開(kāi)發(fā)者必然需要模擬類的功能,來(lái)隔離、封裝、組織復(fù)雜的 JavaScript 代碼,而這種封裝和隔離,也被被我們稱之為模塊化。
模塊就是一個(gè)實(shí)現(xiàn)特定功能的文件 or 代碼塊。隨著前端工程體系建設(shè)的愈發(fā)成熟,或許模塊化的概念已經(jīng)在前端圈子里已經(jīng)耳熟能詳了。
但是對(duì)于很多開(kāi)發(fā)者而言,ES6 中的 export、import,nodejs 中的 require、exports.xx、module.exports到底有什么區(qū)別?為什么又有 CommonJS,又有 AMD,CMD,UMD?區(qū)別是什么?甚至我們?cè)诰帉?xiě) ts 文件的時(shí)候,還需要在配置文件里面說(shuō)明什么模塊方式,在項(xiàng)目中使用的時(shí)候,我們又是否真正知道,你用的到底是基于哪一種規(guī)范的模塊化?
本文對(duì)你寫(xiě)代碼沒(méi)有一點(diǎn)幫助,但是如果你還對(duì)上述的問(wèn)題存有疑惑或者想了解JavaScript 模塊化的前世古今,那么我們開(kāi)始吧~
公眾號(hào)回復(fù)【xmind2】獲取源文件
所謂的模塊化,粗俗的講,就是把一大坨代碼,一鏟一鏟分成一個(gè)個(gè)小小坨。當(dāng)然,這種分割也必須是合理的,以便于你增減或者修改功能,并且不會(huì)影響整體系統(tǒng)的穩(wěn)定性。
個(gè)人認(rèn)為模塊化具有以下幾個(gè)好處:
對(duì)于某一工程作業(yè)或者行為進(jìn)行定性的信息規(guī)定。主要是因?yàn)闊o(wú)法精準(zhǔn)定量而形成的標(biāo)準(zhǔn),所以,被稱為規(guī)范。在模塊化還沒(méi)有規(guī)范確定的時(shí)候,我們都稱之為原始模塊化。
回到我們剛剛說(shuō)的模塊的定義,模塊就是一個(gè)實(shí)現(xiàn)特定功能的文件 or 代碼塊(這是我自己給定義的)。專業(yè)定義是,在程序設(shè)計(jì)中,為完成某一功能所需的一段程序或子程序;或指能由編譯程序、裝配程序等處理的獨(dú)立程序單位;或指大型軟件系統(tǒng)的一部分。而函數(shù)的一個(gè)功能就是實(shí)現(xiàn)特定邏輯的一組語(yǔ)句打包。并且 JavaScript 的作用域就是基于函數(shù)的。所以最原始之處,函數(shù)必然是作為模塊化的第一步。
//函數(shù)1
function fn1(){
//statement
}
//函數(shù)2
function fn2(){
//statement
}
其實(shí)就是把變量名塞的深一點(diǎn)。。。
let module1={
let tag : 1,
let name:'module1',
fun1(){
console.log('this is fun1')
},
fun2(){
console.log('this is fun2')
}
}
我們?cè)谑褂玫臅r(shí)候呢,就直接
module1.fun2();
IIFE 就是立即執(zhí)行函數(shù),我們可以通過(guò)匿名閉包的形式來(lái)實(shí)現(xiàn)模塊化
let global='Hello, I am a global variable :)';
(function () {
// 在函數(shù)的作用域中下面的變量是私有的
const myGrades=[93, 95, 88, 0, 55, 91];
let average=function() {
let total=myGrades.reduce(function(accumulator, item) {
return accumulator + item}, 0);
return 'Your average grade is ' + total / myGrades.length + '.';
}
let failing=function(){
let failingGrades=myGrades.filter(function(item) {
return item < 70;});
return 'You failed ' + failingGrades.length + ' times.';
}
console.log(failing());
console.log(global);
}());
// 控制臺(tái)顯示:'You failed 2 times.'
// 控制臺(tái)顯示:'Hello, I am a global variable :)'
這種方法的好處在于,你可以在函數(shù)內(nèi)部使用局部變量,而不會(huì)意外覆蓋同名全局變量,但仍然能夠訪問(wèn)到全局變量
類似如上的 IIFE ,還有非常多的演進(jìn)寫(xiě)法
比如引入依賴:
// module.js文件
(function(window, $) {
let data='www.baidu.com'
//操作數(shù)據(jù)的函數(shù)
function foo() {
//用于暴露有函數(shù)
console.log(`foo() ${data}`)
$('body').css('background', 'red')
}
function bar() {
//用于暴露有函數(shù)
console.log(`bar() ${data}`)
otherFun() //內(nèi)部調(diào)用
}
function otherFun() {
//內(nèi)部私有的函數(shù)
console.log('otherFun()')
}
//暴露行為
window.myModule={ foo, bar }
})(window, jQuery)
// index.html文件
<!-- 引入的js必須有一定順序 -->
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
myModule.foo()
</script>
還有一種所謂的揭示模塊模式 Revealing module pattern
var myGradesCalculate=(function () {
// 在函數(shù)的作用域中下面的變量是私有的
var myGrades=[93, 95, 88, 0, 55, 91];
var average=function() {
var total=myGrades.reduce(function(accumulator, item) {
return accumulator + item;
}, 0);
return'Your average grade is ' + total / myGrades.length + '.';
};
var failing=function() {
var failingGrades=myGrades.filter(function(item) {
return item < 70;
});
return 'You failed ' + failingGrades.length + ' times.';
};
// 將公有指針指向私有方法
return {
average: average,
failing: failing
}
})();
myGradesCalculate.failing(); // 'You failed 2 times.'
myGradesCalculate.average(); // 'Your average grade is 70.33333333333333.'
這和我們之前的實(shí)現(xiàn)方法非常相近,除了它會(huì)確保,在所有的變量和方法暴露之前都會(huì)保持私有.
上述的所有解決方案都有一個(gè)共同點(diǎn):使用單個(gè)全局變量來(lái)把所有的代碼包含在一個(gè)函數(shù)內(nèi),由此來(lái)創(chuàng)建私有的命名空間和閉包作用域。
雖然每種方法都比較有效,但也都有各自的短板。
隨著大前端時(shí)代的到來(lái),常見(jiàn)的 JavaScript 模塊規(guī)范也就有了:CommonJS、AMD、CMD、UMD、ES6 原生。
CommonJS 是 JavaScript 的一個(gè)模塊化規(guī)范,主要用于服務(wù)端Nodejs 中,當(dāng)然,通過(guò)轉(zhuǎn)換打包,也可以運(yùn)行在瀏覽器端。畢竟服務(wù)端加載的模塊都是存放于本地磁盤(pán)中,所以加載起來(lái)比較快,不需要考慮異步方式。
根據(jù)規(guī)范,每一個(gè)文件既是一個(gè)模塊,其內(nèi)部定義的變量是屬于這個(gè)模塊的,不會(huì)污染全局變量。
CommonJS 的核心思想是通過(guò) require 方法來(lái)同步加載所依賴的模塊,然后通過(guò) exports 或者 module.exprots 來(lái)導(dǎo)出對(duì)外暴露的接口。
CommonJS 的規(guī)范說(shuō)明,一個(gè)單獨(dú)的文件就是一個(gè)模塊,也就是一個(gè)單獨(dú)的作用域。并且模塊只有一個(gè)出口,module.exports/exports.xxx
// lib/math.js
const NAME='Nealayng';
module.exports.author=NAME;
module.exports.add=(a,b)=> a+b;
加載模塊使用 require 方法,該方法讀取文件并且執(zhí)行,返回文件中 module.exports 對(duì)象
// main.js
const mathLib=require('./lib/math');
console.log(mathLib.author);//Nealyang
console.log(mathLib.add(1,2));// 3
由于瀏覽器不支持 CommonJS 規(guī)范,因?yàn)槠涓緵](méi)有 module、exports、require 等變量,如果要使用,則必須轉(zhuǎn)換格式。Browserify是目前最常用的CommonJS格式轉(zhuǎn)換的工具,我們可以通過(guò)安裝browserify來(lái)對(duì)其進(jìn)行轉(zhuǎn)換.但是我們?nèi)匀恍枰⒁猓捎?CommonJS 的規(guī)范是阻塞式加載,并且模塊文件存放在服務(wù)器端,可能會(huì)出現(xiàn)假死的等待狀態(tài)。
npm i browserify -g
然后使用如下命令
browserify main.js -o js/bundle/main.js
然后在 HTML 中引入使用即可。
有一說(shuō)一,在瀏覽器中使用 CommonJS 的規(guī)范去加載模塊,真的不是很方便。如果一定要使用,我們可以使用browserify編譯打包,也可以使用require1k,直接在瀏覽器上運(yùn)行即可。
其實(shí)在 nodejs 中模塊的實(shí)現(xiàn)并非完全按照 CommonJS 的規(guī)范來(lái)的,而是進(jìn)行了取舍。
Node 中,一個(gè)文件是一個(gè)模塊->module
源碼定義如下:
function Module(id='', parent) {
this.id=id;
this.path=path.dirname(id);
this.exports={};
this.parent=parent;
updateChildren(parent, this, false);
this.filename=null;
this.loaded=false;
this.children=[];
}
//實(shí)例化一個(gè)模塊
var module=new Module(filename, parent);
CommonJS 的一個(gè)模塊,就是一個(gè)腳本文件。require命令第一次加載該腳本,就會(huì)執(zhí)行整個(gè)腳本,然后在內(nèi)存生成一個(gè)對(duì)象。
{
id: '...',
exports: { ... },
loaded: true,
...
}
上面代碼就是 Node 內(nèi)部加載模塊后生成的一個(gè)對(duì)象。該對(duì)象的id屬性是模塊名,exports屬性是模塊輸出的各個(gè)接口,loaded屬性是一個(gè)布爾值,表示該模塊的腳本是否執(zhí)行完畢。其他還有很多屬性,這里都省略不介紹了。
以后需要用到這個(gè)模塊的時(shí)候,就會(huì)到exports屬性上面取值。即使再次執(zhí)行require命令,也不會(huì)再次執(zhí)行該模塊,而是到緩存之中取值。也就是說(shuō),CommonJS 模塊無(wú)論加載多少次,都只會(huì)在第一次加載時(shí)運(yùn)行一次,以后再加載,就返回第一次運(yùn)行的結(jié)果,除非手動(dòng)清除系統(tǒng)緩存。
再去深究具體的實(shí)現(xiàn)細(xì)節(jié)。。那就。。。下一篇分享吧~
Asynchronous Module Definition:異步模塊定義。
也就是解決我們上面說(shuō)的 CommonJS 在瀏覽器端致命的問(wèn)題:假死。
CommonJS規(guī)范加載模塊是同步的,也就是說(shuō),只有加載完成,才能執(zhí)行后面的操作。AMD規(guī)范則是異步加載模塊,允許指定回調(diào)函數(shù)。
由于其并非原生 js 所支持的那種寫(xiě)法。所以使用 AMD 規(guī)范開(kāi)發(fā)的時(shí)候就需要大名鼎鼎的函數(shù)庫(kù) require.js 的支持了。
https://github.com/requirejs/requirejs
關(guān)于 require.js 的更詳細(xì)使用說(shuō)明可以參考官網(wǎng) api:https://requirejs.org/docs/api.html
require.js 主要解決兩個(gè)問(wèn)題:
define(id,[dependence],callback)
require([moduleName],callback);
<script src="scripts/require.js" data-main="scripts/app.js"></script>
data-main 指定入口文件,比如這里指定 scripts 下的 app.js 文件,那么只有直接或者間接與app.js有依賴關(guān)系的模塊才會(huì)被插入到html中。
通過(guò)這個(gè)函數(shù)可以對(duì)requirejs進(jìn)行靈活的配置,其參數(shù)為一個(gè)配置對(duì)象,配置項(xiàng)及含義如下:
require.config({
//默認(rèn)情況下從這個(gè)文件開(kāi)始拉去取資源
baseUrl:'scripts/app',
//如果你的依賴模塊以pb頭,會(huì)從scripts/pb加載模塊。
paths:{
pb:'../pb'
},
// load backbone as a shim,所謂就是將沒(méi)有采用requirejs方式定義
//模塊的東西轉(zhuǎn)變?yōu)閞equirejs模塊
shim:{
'backbone':{
deps:['underscore'],
exports:'Backbone'
}
}
});
|-js
|-libs
|-require.js
|-modules
|-article.js
|-user.js
|-main.js
|-index.html
// user.js文件
// 定義沒(méi)有依賴的模塊
define(function() {
let author='Nealyang'
function getAuthor() {
return author.toUpperCase()
}
return { getAuthor } // 暴露模塊
})
//article.js文件
// 定義有依賴的模塊
define(['user'], function(user) {
let name='THE LAST TIME'
function consoleMsg() {
console.log(`${name} by ${user.getAuthor()}`);
}
// 暴露模塊
return { consoleMsg }
})
// main.js
(function() {
require.config({
baseUrl: 'js/', //基本路徑 出發(fā)點(diǎn)在根目錄下
paths: {
//映射: 模塊標(biāo)識(shí)名: 路徑
article: './modules/article', //此處不能寫(xiě)成article.js,會(huì)報(bào)錯(cuò)
user: './modules/user'
}
})
require(['article'], function(alerter) {
article.consoleMsg()
})
})()
// 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>
如果我們需要引入第三方庫(kù),則需要在 main.js 文件中引入
(function() {
require.config({
baseUrl: 'js/',
paths: {
article: './modules/article',
user: './modules/user',
// 第三方庫(kù)模塊
jquery: './libs/jquery-1.10.1' //注意:寫(xiě)成jQuery會(huì)報(bào)錯(cuò)
}
})
require(['article'], function(alerter) {
article.consoleMsg()
})
})()
關(guān)于 require.js 的使用,仔細(xì)看文檔,其實(shí)還是有很多知識(shí)點(diǎn)的。但是鑒于我們著實(shí)現(xiàn)在使用不多(我也不熟),所以這里也就參考網(wǎng)上優(yōu)秀文章和自己實(shí)踐,拋磚引玉。
CMD是阿里的玉伯提出來(lái)的(大神的成長(zhǎng)故事可在公眾號(hào)回復(fù)【大佬】),js 的函數(shù)為 sea.js,它和 AMD 其實(shí)非常的相似,文件即為模塊,但是其最主要的區(qū)別是實(shí)現(xiàn)了按需加載。推崇依賴就近的原則,模塊延遲執(zhí)行,而 AMD 所依賴模塊式提前執(zhí)行(requireJS 2.0 后也改為了延遲執(zhí)行)
//AMD
define(['./a','./b'], function (a, b) {
//依賴一開(kāi)始就寫(xiě)好
a.test();
b.test();
});
//CMD
define(function (requie, exports, module) {
//依賴可以就近書(shū)寫(xiě)
var a=require('./a');
a.test();
...
//按需加載
if (status) {
var b=requie('./b');
b.test();
}
});
https://github.com/seajs/seajs
https://seajs.github.io/seajs/docs/
準(zhǔn)確的說(shuō) CMD 是 SeaJS 在推廣過(guò)程中對(duì)模塊定義的規(guī)范化產(chǎn)物。
也可以說(shuō)SeaJS 是一個(gè)遵循 CMD 規(guī)范的 JavaScript 模塊加載框架,可以實(shí)現(xiàn) JavaScript 的 CMD 模塊化開(kāi)發(fā)方式。
SeaJS 只是實(shí)現(xiàn) JavaScript的模塊化和按需加載,并未擴(kuò)展 JavaScript 語(yǔ)言本身。SeaJS 的主要目的是讓開(kāi)發(fā)人員更加專注于代碼本身,從繁重的 JavaScript 文件以及對(duì)象依賴處理中解放出來(lái)。
毫不夸張的說(shuō),我們現(xiàn)在詳情頁(yè)就是 SeaJS+Kissy。。。(即將升級(jí))
Seajs 追求簡(jiǎn)單、自然的代碼書(shū)寫(xiě)和組織方式,具有如下核心特性:
Sea.js 還提供常用插件,非常有助于開(kāi)發(fā)調(diào)試和性能優(yōu)化,并具有豐富的可擴(kuò)展接口。
examples/
|-- sea-modules 存放 seajs、jquery 等文件,這也是模塊的部署目錄
|-- static 存放各個(gè)項(xiàng)目的 js、css 文件
| |-- hello
| |-- lucky
| `-- todo
`-- app 存放 html 等文件
|-- hello.html
|-- lucky.html
`-- todo.html
我們從 hello.html 入手,來(lái)瞧瞧使用 Sea.js 如何組織代碼。
在 hello.html 頁(yè)尾,通過(guò) script 引入 sea.js 后,有一段配置代碼
// seajs 的簡(jiǎn)單配置
seajs.config({
base: "../sea-modules/",
alias: {
"jquery": "jquery/jquery/1.10.1/jquery.js"
}
})
// 加載入口模塊
seajs.use("../static/hello/src/main")
sea.js 在下載完成后,會(huì)自動(dòng)加載入口模塊。頁(yè)面中的代碼就這么簡(jiǎn)單。
這個(gè)小游戲有兩個(gè)模塊 spinning.js 和 main.js,遵循統(tǒng)一的寫(xiě)法:
// 所有模塊都通過(guò) define 來(lái)定義
define(function(require, exports, module) {
// 通過(guò) require 引入依賴
var $=require('jquery');
var Spinning=require('./spinning');
// 通過(guò) exports 對(duì)外提供接口
exports.doSomething=...
// 或者通過(guò) module.exports 提供整個(gè)接口
module.exports=...
});
上面就是 Sea.js 推薦的 CMD 模塊書(shū)寫(xiě)格式。如果你有使用過(guò) Node.js,一切都很自然。
以上實(shí)例,來(lái)源于官網(wǎng) Example。更多 Demo 查看:https://github.com/seajs/examples
UMD 其實(shí)我個(gè)人還是覺(jué)得非常。。。。不喜歡的。ifElse 就 universal 了。。。。
UMD 是 AMD 和 CommonJS 的綜合產(chǎn)物。如上所說(shuō),AMD 的用武之地是瀏覽器,非阻塞式加載。CommonJS 主要用于服務(wù)端 Nodejs 中使用。所以人們就想到了一個(gè)通用的模式 UMD(universal module definition)。來(lái)解決跨平臺(tái)的問(wèn)題。
沒(méi)錯(cuò)!就是 ifElse 的寫(xiě)法。
核心思想就是:先判斷是否支持Node.js的模塊(exports)是否存在,存在則使用Node.js模塊模式。
在判斷是否支持AMD(define是否存在),存在則使用AMD方式加載模塊。
(function (window, factory) {
if (typeof exports==='object') {
module.exports=factory();
} else if (typeof define==='function' && define.amd) {
define(factory);
} else {
window.eventUtil=factory();
}
})(this, function () {
//module ...
});
關(guān)于 UMD 更多的example 可移步github:https://github.com/umdjs/umd
如果你一直讀到現(xiàn)在,那么恭喜你,我們開(kāi)始介紹我們最新的模塊化了!
通過(guò)上面的介紹我們知道,要么模塊化依賴環(huán)境,要么需要引入額外的類庫(kù)。說(shuō)到底就是社區(qū)找到的一種妥協(xié)方案然后得到了大家的認(rèn)可。但是歸根結(jié)底不是官方呀。終于,ECMAScript 官宣了模塊化的支持,真正的規(guī)范。
在ES6中,我們可以使用 import 關(guān)鍵字引入模塊,通過(guò) export 關(guān)鍵字導(dǎo)出模塊,功能較之于前幾個(gè)方案更為強(qiáng)大,也是我們所推崇的,但是由于ES6目前無(wú)法在所有瀏覽器中執(zhí)行,所以,我們還需通過(guò)babel將不被支持的import編譯為當(dāng)前受到廣泛支持的 require。
ES6 的模塊化汲取了 CommonJS 和AMD 的優(yōu)點(diǎn),擁有簡(jiǎn)潔的語(yǔ)法和異步的支持。并且寫(xiě)法也和 CommonJS 非常的相似。
關(guān)于 ES6 模塊的基本用法相比大家都比較熟悉了。這里我們主要和 CommonJS 對(duì)比學(xué)習(xí)。
兩大差異:
// lib/counter.js
var counter=1;
function increment() {
counter++;
}
function decrement() {
counter--;
}
module.exports={
counter: counter,
increment: increment,
decrement: decrement
};
// src/main.js
var counter=require('../../lib/counter');
counter.increment();
console.log(counter.counter); // 1
在 main.js 當(dāng)中的實(shí)例是和原本模塊完全不相干的。這也就解釋了為什么調(diào)用了 counter.increment() 之后仍然返回1。因?yàn)槲覀円氲?counter 變量和模塊里的是兩個(gè)不同的實(shí)例。
所以調(diào)用 counter.increment() 方法只會(huì)改變模塊中的 counter .想要修改引入的 counter 只有手動(dòng)一下啦:
counter.counter++;
console.log(counter.counter); // 2
而通過(guò) import 語(yǔ)句,可以引入實(shí)時(shí)只讀的模塊:
// lib/counter.js
export let counter=1;
export function increment() {
counter++;
}
export function decrement() {
counter--;
}
// src/main.js
import * as counter from '../../counter';
console.log(counter.counter); // 1
counter.increment();
console.log(counter.counter); // 2
因?yàn)?CommonJS 加載的是一個(gè)對(duì)象(module.exports),對(duì)象只有在有腳本運(yùn)行的時(shí)候才能生成。而 ES6 模塊不是一個(gè)對(duì)象,只是一個(gè)靜態(tài)的定義。在代碼解析階段就會(huì)生成。
ES6 模塊是編譯時(shí)輸出接口,因此有如下2個(gè)特點(diǎn):
// a.js
console.log('a.js')
import { foo } from './b';
// b.js
export let foo=1;
console.log('b.js 先執(zhí)行');
// 執(zhí)行結(jié)果:
// b.js 先執(zhí)行
// a.js
// a.js
import { foo } from './b';
console.log('a.js');
export const bar=1;
export const bar2=()=> {
console.log('bar2');
}
export function bar3() {
console.log('bar3');
}
// b.js
export let foo=1;
import * as a from './a';
console.log(a);
// 執(zhí)行結(jié)果:
// { bar: undefined, bar2: undefined, bar3: [Function: bar3] }
// a.js
“循環(huán)加載”(circular dependency)指的是,a腳本的執(zhí)行依賴b腳本,而b腳本的執(zhí)行又依賴a腳本。
// a.js
var b=require('b');
// b.js
var a=require('a');
循環(huán)加載如果處理不好,還可能導(dǎo)致遞歸加載,使得程序無(wú)法執(zhí)行,因此應(yīng)該避免出現(xiàn)。
在 CommonJS 中,腳本代碼在 require 的時(shí)候,就會(huì)全部執(zhí)行。一旦出現(xiàn)某個(gè)模塊被"循環(huán)加載",就只輸出已經(jīng)執(zhí)行的部分,還未執(zhí)行的部分不會(huì)輸出。
// a.js
exports.done=false;
var b=require('./b.js');
console.log('在 a.js 之中,b.done=%j', b.done);
exports.done=true;
console.log('a.js 執(zhí)行完畢');
// b.js
exports.done=false;
var a=require('./a.js');
console.log('在 b.js 之中,a.done=%j', a.done);
exports.done=true;
console.log('b.js 執(zhí)行完畢');
// main.js
var a=require('./a.js');
var b=require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
輸出結(jié)果為:
在 b.js 之中,a.done=false
b.js 執(zhí)行完畢
在 a.js 之中,b.done=true
a.js 執(zhí)行完畢
在 main.js 之中, a.done=true, b.done=true
從上面我們可以看出:
ES6 處理“循環(huán)加載”與 CommonJS 有本質(zhì)的不同**。ES6 模塊是動(dòng)態(tài)引用**,如果使用import從一個(gè)模塊加載變量(即import foo from 'foo'),那些變量不會(huì)被緩存,而是成為一個(gè)指向被加載模塊的引用,需要開(kāi)發(fā)者自己保證,真正取值的時(shí)候能夠取到值。
// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar);
export let foo='foo';
// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo);
export let bar='bar';
運(yùn)行結(jié)果如下:
b.mjs
ReferenceError: foo is not defined
上面代碼中,執(zhí)行a.mjs以后會(huì)報(bào)錯(cuò),foo變量未定義.
具體的執(zhí)行結(jié)果如下:
解決這個(gè)問(wèn)題的方法,就是讓b.mjs運(yùn)行的時(shí)候,foo已經(jīng)有定義了。這可以通過(guò)將foo寫(xiě)成函數(shù)來(lái)解決。
// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar());
function foo() { return 'foo' }
export {foo};
// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo());
function bar() { return 'bar' }
export {bar};
最后執(zhí)行結(jié)果為:
b.mjs
foo
a.mjs
bar
關(guān)于 ES6 詳細(xì)的模塊的介紹,強(qiáng)烈推薦阮一峰的 ES6 入門(mén)和深入理解 ES6 一書(shū)
公眾號(hào)【全棧前端精選】 個(gè)人微信【is_Nealyang】
頁(yè)切圖過(guò)程中div+css命名規(guī)則
標(biāo)簽屬性命名規(guī)范 (建議)
下劃線連接符命名法“hello_world”
中杠 連接符命名法“hello-world”
駱駝式命名法“helloWorld”
內(nèi)容:content/container 導(dǎo)航:nav 側(cè)欄:sidebar
欄目:column 標(biāo)志:logo 頁(yè)面主體:main
廣告:banner 熱點(diǎn):hot 新聞:news
下載:download 子導(dǎo)航:subnav 菜單:menu
搜索:search 頁(yè)腳:footer 滾動(dòng):scroll
版權(quán):copyright 友情鏈接:friendlink 子菜單:submenu
內(nèi)容:content 標(biāo)簽頁(yè):tab 文章列表:list
注冊(cè):regsiter 提示信息:msg 小技巧:tips
加入:joinus 欄目標(biāo)題:title 指南:guild
服務(wù):service 狀態(tài):status 投票:vote
尾:footer 合作伙伴:partner 登錄條:loginbar
頁(yè)面外圍控制整體布局寬度:wrapper 左右中:left right center
(二)注釋的寫(xiě)法:
/* Footer */
內(nèi)容區(qū)
/* End Footer */
(三)id(具有唯一性)的命名:
(1)頁(yè)面結(jié)構(gòu)
容器: container 頁(yè)頭:header 內(nèi)容:content/container
頁(yè)面主體:main 頁(yè)尾:footer 導(dǎo)航:nav
側(cè)欄:sidebar 欄目:column 左右中:left right center
頁(yè)面外圍控制整體布局寬度:wrapper
(2)導(dǎo)航
導(dǎo)航:nav
主導(dǎo)航:mainbav
子導(dǎo)航:subnav
頂導(dǎo)航:topnav
邊導(dǎo)航:sidebar
左導(dǎo)航:leftsidebar
右導(dǎo)航:rightsidebar
菜單:menu 子菜單:submenu 標(biāo)題: title 摘要: summary
(3)功能
標(biāo)志:logo
廣告:banner
登陸:login
登錄條:loginbar
注冊(cè):regsiter
搜索:search
功能區(qū):shop
標(biāo)題:title
加入:joinus
狀態(tài):status
按鈕:btn
滾動(dòng):scroll
標(biāo)簽頁(yè):tab
文章列表:list
提示信息:msg
當(dāng)前的: current
小技巧:tips
圖標(biāo): icon
注釋:note
指南:guild
服務(wù):service
熱點(diǎn):hot
新聞:news
下載:download
投票:vote
合作伙伴:partner
友情鏈接:link
版權(quán):copyright
(四)class的命名:
(1)顏色:使用顏色的名稱或者16進(jìn)制代碼,如
.red { color: red; }
.f60 { color: #f60; }
.ff8600 { color: #ff8600; }
(2)字體大小,直接使用"font+字體大小"作為名稱,如
.font12px { font-size: 12px; }
.font9pt {font-size: 9pt; }
(3)對(duì)齊樣式,使用對(duì)齊目標(biāo)的英文名稱,如
.left { float:left; }
.bottom { float:bottom; }
(4)標(biāo)題欄樣式,使用"類別+功能"的方式命名,如
.barnews { }
.barproduct { }
注意事項(xiàng):
1.一律小寫(xiě);
2.盡量用英文;
3.不加中杠和下劃線; (我倒是經(jīng)常加)
4.盡量不縮寫(xiě),除非一看就明白的單詞. (偷懶經(jīng)常縮寫(xiě))
主要的 master.css 模塊 module.css 基本共用 base.css
主題 themes.css 專欄 columns.css 打印 print.css
文字 font.css 表單 forms.css 補(bǔ)丁 mend.css
布局,版面 layout.css
者:Jiawei Pan
轉(zhuǎn)發(fā)鏈接:https://mp.weixin.qq.com/s/hDdF8rni6MX4U55l3DKLQw
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。