習目標:了解JavaScript是如何與HTML結合來創(chuàng)建動態(tài)網(wǎng)頁,網(wǎng)頁中嵌入JavaScript的不同方式,JavaScript的內容類型及其與<script>的關系
<script>是由Netscape創(chuàng)造出來,后來加到HTML規(guī)范中的。
<script>有8個屬性:
1、async:表示立即開始下載腳本,但不能阻止其他頁面動作,比如下載資源或者等待其他腳本加載。只對外部腳本文件有效。
2、charset:使用src屬性指定代碼字符集。這個屬性很少用,因為大多數(shù)瀏覽器不在乎它的值。
3、crossorigin;配置資源請求的CORS(跨源資源共享)設置。默認情況下不使用CORS。crossorigin = “anonymous”配置文件請求不用設置憑據(jù)標志。crossorigin = ”use-credentials“設置憑據(jù)標志,意味著出站請求會包含憑據(jù)。
4、defer:表示腳本可以延遲到文檔全部解析和顯示后再執(zhí)行。新版本中只能用于外部腳本。
5、integrity:允許比對接收到的資源和指定的加密簽名以驗證子資源完整性(SRI,Subresource integrity),如果驗證簽名不匹配則腳本不會執(zhí)行。這個屬性可以用于確保內容分發(fā)網(wǎng)絡(CDN,Content Delivery Network)不會提供惡意內容。
6、language:此屬性已被廢止。
7、src:表示包含外部要執(zhí)行的代碼的外部文件。
8、type:代替language,表示代碼塊中腳本語言的內容類型(也稱為MIME類型),按照慣例這個值始終都是”text/JavaScript“,盡管”text/JavaScript“和”text/ecmascript“都已經(jīng)廢棄。JavaScript文件的MIME類型通常是”application/x-javascript“,不過給type屬性這個值的話可能會導致腳本被忽略。在非IE的瀏覽器中有效的值還有”application/JavaScript“和”application/ecmascript"。如果這個值是module,則代碼會被當成是ES6模塊,而且只有這時候代碼中才能出現(xiàn)import和export關鍵字。
使用<script>的方式有內聯(lián)和外嵌兩種,只要把code寫入<script>code</script>中就好,code中要是包含字符串“<script>”,只要加上轉義字符“\”即可。
如果要外嵌JavaScript代碼只要使用src屬性來鏈接外部文件即可如:
<script src=“example.js”></script>
XHTML 文檔中,可以忽略結束標簽寫成<script src=“example.js”/>即可,但是這在HTML中不能使用。
過去把JavaScript和CSS一起寫在head中,但是這意味著必須下載所有code并解析和解釋完成后才開始渲染頁面,對于JavaScript很多的頁面會導致頁面渲染速度過慢,為解決這個問題,JavaScript一般寫在body元素的頁面內容的最后邊,如下
<html>
<head></head>
<body>
message
<script>code<\script>
<\body>
</html>
在外聯(lián)JavaScript時可以使用defer屬性來推遲腳本的運行。可以寫成:
<html>
<head>
<script defer src = "example.js">code<\script>
</head>
<body>
message
<\body>
</html>
async屬性從腳本處理方式上與defer類似,但是不同的是標記async的腳本并不能保證腳本按照他們的出現(xiàn)順序執(zhí)行,比如:
<html>
<head>
<script sync src = "example1.js">code<\script>
<script sync src = "example2.js">code<\script>
</head>
<body>
message
<\body>
</html>
不能保證example1比example2先執(zhí)行。
除了<script>以外還可以用其他方式加載腳本。因為JavaScript可以使用DOM API,所以通過向DOM中動態(tài)地加入script元素同樣可以加載指定腳本。只要創(chuàng)建一個script元素并將其添加到DOM即可。
let script = document.createElement('script');
script.src = 'gibberish.js';
document.head.appendChild(script);
當然,在把 HTMLElement 元素添加到 DOM 且執(zhí)行到這段代碼之前不會發(fā)送請求。默認情況下,以這種方式創(chuàng)建的<script>元素是以異步方式加載的,相當于添加了 async 屬性。不過這樣做可能會有問題,因為所有瀏覽器都支持 createElement()方法,但不是所有瀏覽器都支持 async 屬性。因此,如果要統(tǒng)一動態(tài)腳本的加載行為,可以明確將其設置為同步加載:
let script = document.createElement('script');
script.src = 'gibberish.js';
script.async = false;
document.head.appendChild(script);
以這種方式獲取的資源對瀏覽器預加載器是不可見的。這會嚴重影響它們在資源獲取隊列中的優(yōu)先級。根據(jù)應用程序的工作方式以及怎么使用,這種方式可能會嚴重影響性能。要想讓預加載器知道這些動態(tài)請求文件的存在,可以在文檔頭部顯式聲明它們:
<link rel="preload" href="gibberish.js">
可擴展超文本標記語言(XHTML,Extensible HyperText Markup Language)是將 HTML 作為 XML的應用重新包裝的結果。與 HTML 不同,在 XHTML 中使用 JavaScript 必須指定 type 屬性且值為text/javascript,HTML 中則可以沒有這個屬性。XHTML 雖然已經(jīng)退出歷史舞臺,但實踐中偶爾可能也會遇到遺留代碼,為此本節(jié)稍作介紹。在 XHTML 中編寫代碼的規(guī)則比 HTML 中嚴格,這會影響使用<script>元素嵌入 JavaScript 代碼。下面的代碼塊雖然在 HTML 中有效,但在 XHML 中是無效的。
<script type="text/javascript">
function compare(a, b) {
if (a < b) {
console.log("A is less than B");
} else if (a > b) {
console.log("A is greater than B");
} else {
console.log("A is equal to B");
}
}
</script>
在 HTML 中,解析<script>元素會應用特殊規(guī)則。XHTML 中則沒有這些規(guī)則。這意味著 a < b語句中的小于號(<)會被解釋成一個標簽的開始,并且由于作為標簽開始的小于號后面不能有空格,這會導致語法錯誤。避免 XHTML 中這種語法錯誤的方法有兩種。第一種是把所有小于號(<)都替換成對應的 HTML實體形式(<)。結果代碼就是這樣的:
<script type="text/javascript">
function compare(a, b) {
if (a < b) {
console.log("A is less than B");
} else if (a > b) {
console.log("A is greater than B");
} else {
console.log("A is equal to B");
}
}
</script>
這樣代碼就可以在 XHTML 頁面中運行了。不過,缺點是會影響閱讀。好在還有另一種方法。第二種方法是把所有代碼都包含到一個 CDATA 塊中。在 XHTML(及 XML)中,CDATA 塊表示文檔中可以包含任意文本的區(qū)塊,其內容不作為標簽來解析,因此可以在其中包含任意字符,包括小于號,并且不會引發(fā)語法錯誤。使用 CDATA 的格式如下:
<script type="text/javascript"><![CDATA[
function compare(a, b) {
if (a < b) {
console.log("A is less than B");
} else if (a > b) {
console.log("A is greater than B");
} else {
console.log("A is equal to B");
}
}
]]></script>
在兼容 XHTML 的瀏覽器中,這樣能解決問題。但在不支持 CDATA 塊的非 XHTML 兼容瀏覽器中則不行。為此,CDATA 標記必須使用 JavaScript 注釋來抵消:
<script type="text/javascript">
//<![CDATA[
function compare(a, b) {
if (a < b) {
console.log("A is less than B");
} else if (a > b) {
console.log("A is greater than B");
} else {
console.log("A is equal to B");
}
}
//]]>
</script>
這種格式適用于所有現(xiàn)代瀏覽器。雖然有點黑科技的味道,但它可以通過 XHTML 驗證,而且對XHTML 之前的瀏覽器也能優(yōu)雅地降級。
自 1995 年 Netscape 2 發(fā)布以來,所有瀏覽器都將 JavaScript 作為默認的編程語言。type 屬性使用一個 MIME 類型字符串來標識<script>的內容,但 MIME 類型并沒有跨瀏覽器標準化。即使瀏覽器默認使用 JavaScript,在某些情況下某個無效或無法識別的 MIME 類型也可能導致瀏覽器跳過(不執(zhí)行)相關代碼。因此,除非你使用 XHTML 或<script>標簽要求或包含非 JavaScript 代碼,最佳做法是不指定 type 屬性。在最初采用 script 元素時,它標志著開始走向與傳統(tǒng) HTML 解析不同的流程。對這個元素需要應用特殊的解析規(guī)則,而這在不支持 JavaScript 的瀏覽器(特別是 Mosaic)中會導致問題。不支持的瀏覽器會把<script>元素的內容輸出到頁面上,從而破壞頁面的外觀。Netscape 聯(lián)合 Mosaic 拿出了一個解決方案,對不支持 JavaScript 的瀏覽器隱藏嵌入的 JavaScript 代碼。最終方案是把腳本代碼包含在一個 HTML 注釋中,像這樣:
<script><!--
function sayHi(){
console.log("Hi!");
}
//--></script>
使用這種格式,Mosaic 等瀏覽器就可以忽略<script>標簽中的內容,而支持 JavaScript 的瀏覽器則必須識別這種模式,將其中的內容作為 JavaScript 來解析。雖然這種格式仍然可以被所有瀏覽器識別和解析,但已經(jīng)不再必要,而且不應該再使用了。在XHTML 模式下,這種格式也會導致腳本被忽略,因為代碼處于有效的 XML 注釋當中。
雖然可以直接在 HTML 文件中嵌入 JavaScript 代碼,但通常認為最佳實踐是盡可能將 JavaScript 代碼放在外部文件中。不過這個最佳實踐并不是明確的強制性規(guī)則。推薦使用外部文件的理由如下。
? 可維護性。JavaScript 代碼如果分散到很多 HTML 頁面,會導致維護困難。而用一個目錄保存所有 JavaScript 文件,則更容易維護,這樣開發(fā)者就可以獨立于使用它們的 HTML 頁面來編輯代碼。
? 緩存。瀏覽器會根據(jù)特定的設置緩存所有外部鏈接的 JavaScript 文件,這意味著如果兩個頁面都用到同一個文件,則該文件只需下載一次。這最終意味著頁面加載更快。
? 適應未來。通過把 JavaScript 放到外部文件中,就不必考慮用 XHTML 或前面提到的注釋黑科技。包含外部 JavaScript 文件的語法在 HTML 和 XHTML 中是一樣的。在配置瀏覽器請求外部文件時,要重點考慮的一點是它們會占用多少帶寬。在 SPDY/HTTP2 中,預請求的消耗已顯著降低,以輕量、獨立 JavaScript 組件形式向客戶端送達腳本更具優(yōu)勢。比如,第一個頁面包含如下腳本:
<script src="mainA.js"></script>
<script src="component1.js"></script>
<script src="component2.js"></script>
<script src="component3.js"></script>
...
后續(xù)頁面可能包含如下腳本:
<script src="mainB.js"></script>
<script src="component3.js"></script>
<script src="component4.js"></script>
<script src="component5.js"></script>
...
在初次請求時,如果瀏覽器支持 SPDY/HTTP2,就可以從同一個地方取得一批文件,并將它們逐個放到瀏覽器緩存中。從瀏覽器角度看,通過 SPDY/HTTP2 獲取所有這些獨立的資源與獲取一個大JavaScript 文件的延遲差不多。在第二個頁面請求時,由于你已經(jīng)把應用程序切割成了輕量可緩存的文件,第二個頁面也依賴的某些組件此時已經(jīng)存在于瀏覽器緩存中了。當然,這里假設瀏覽器支持 SPDY/HTTP2,只有比較新的瀏覽器才滿足。如果你還想支持那些比較老的瀏覽器,可能還是用一個大文件更合適。
IE5.5 發(fā)明了文檔模式的概念,即可以使用 doctype 切換文檔模式。最初的文檔模式有兩種:混雜模式(quirks mode)和標準模式(standards mode)。前者讓 IE 像 IE5 一樣(支持一些非標準的特性),后者讓 IE 具有兼容標準的行為。雖然這兩種模式的主要區(qū)別只體現(xiàn)在通過 CSS 渲染的內容方面,但對JavaScript 也有一些關聯(lián)影響,或稱為副作用。本書會經(jīng)常提到這些副作用。
IE 初次支持文檔模式切換以后,其他瀏覽器也跟著實現(xiàn)了。隨著瀏覽器的普遍實現(xiàn),又出現(xiàn)了第三種文檔模式:準標準模式(almost standards mode)。這種模式下的瀏覽器支持很多標準的特性,但是沒有標準規(guī)定得那么嚴格。主要區(qū)別在于如何對待圖片元素周圍的空白(在表格中使用圖片時最明顯)。
混雜模式在所有瀏覽器中都以省略文檔開頭的 doctype 聲明作為開關。這種約定并不合理,因為混雜模式在不同瀏覽器中的差異非常大,不使用黑科技基本上就沒有瀏覽器一致性可言。標準模式通過下列幾種文檔類型聲明開啟:
<!-- HTML 4.01 Strict -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<!-- XHTML 1.0 Strict -->
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- HTML5 -->
<!DOCTYPE html>
準標準模式通過過渡性文檔類型(Transitional)和框架集文檔類型(Frameset)來觸發(fā):
<!-- HTML 4.01 Transitional -->
<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<!-- HTML 4.01 Frameset -->
<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Frameset//EN"
"http://www.w3.org/TR/html4/frameset.dtd">
<!-- XHTML 1.0 Transitional -->
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- XHTML 1.0 Frameset -->
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Frameset//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
準標準模式與標準模式非常接近,很少需要區(qū)分。人們在說到“標準模式”時,可能指其中任何一個。而對文檔模式的檢測(本書后面會討論)也不會區(qū)分它們。本書后面所說的標準模式,指的就是除混雜模式以外的模式。
針對早期瀏覽器不支持 JavaScript 的問題,需要一個頁面優(yōu)雅降級的處理方案。最終,<noscript>元素出現(xiàn),被用于給不支持 JavaScript 的瀏覽器提供替代內容。雖然如今的瀏覽器已經(jīng) 100%支持JavaScript,但對于禁用 JavaScript 的瀏覽器來說,這個元素仍然有它的用處。<noscript>元素可以包含任何可以出現(xiàn)在<body>中的 HTML 元素,<script>除外。在下列兩種情況下,瀏覽器將顯示包含在<noscript>中的內容:
? 瀏覽器不支持腳本;
? 瀏覽器對腳本的支持被關閉。任何一個條件被滿足,包含在<noscript>中的內容就會被渲染。否則,瀏覽器不會渲染<noscript>中的內容。
下面是一個例子:
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
<script defer="defer" src="example1.js"></script>
<script defer="defer" src="example2.js"></script>
</head>
<body>
<noscript>
<p>This page requires a JavaScript-enabled browser.</p>
</noscript>
</body>
</html>
這個例子是在腳本不可用時讓瀏覽器顯示一段話。如果瀏覽器支持腳本,則用戶永遠不會看到它。
JavaScript 是通過<script>元素插入到 HTML 頁面中的。這個元素可用于把 JavaScript 代碼嵌入到HTML 頁面中,跟其他標記混合在一起,也可用于引入保存在外部文件中的 JavaScript。本章的重點可以總結如下。
? 要包含外部 JavaScript 文件,必須將 src 屬性設置為要包含文件的 URL。文件可以跟網(wǎng)頁在同一臺服務器上,也可以位于完全不同的域。
? 所有<script>元素會依照它們在網(wǎng)頁中出現(xiàn)的次序被解釋。在不使用 defer 和 async 屬性的情況下,包含在<script>元素中的代碼必須嚴格按次序解釋。
? 對不推遲執(zhí)行的腳本,瀏覽器必須解釋完位于<script>元素中的代碼,然后才能繼續(xù)渲染頁面的剩余部分。為此,通常應該把<script>元素放到頁面末尾,介于主內容之后及</body>標簽之前。
? 可以使用 defer 屬性把腳本推遲到文檔渲染完畢后再執(zhí)行。推遲的腳本原則上按照它們被列出的次序執(zhí)行。
? 可以使用 async 屬性表示腳本不需要等待其他腳本,同時也不阻塞文檔渲染,即異步加載。異步腳本不能保證按照它們在頁面中出現(xiàn)的次序執(zhí)行。
? 通過使用<noscript>元素,可以指定在瀏覽器不支持腳本時顯示的內容。如果瀏覽器支持并啟用腳本,則<noscript>元素中的任何內容都不會被渲染。
本文主要理理js模塊化相關知識。
涉及到內聯(lián)腳本、外聯(lián)腳本、動態(tài)腳本、阻塞、defer、async、CommonJS、AMD、CMD、UMD、ES Module。順帶探究下Vite。
假設你是一個前端新手,現(xiàn)在入門,那么我們創(chuàng)建一個html頁面,需要新建一個index.html文件:
<!DOCTYPE html>
<html>
<head>
<title>test</title>
</head>
<body>
<p id="content">hello world</p>
</body>
</html>
如果需要在頁面中執(zhí)行javascript代碼,我們就需要在 HTML 頁面中插入 <script> 標簽。
有2種插入方式:
1、放在<head>中
2、放在<body>中
比如,點擊hello world之后,在hello world后面加3個感嘆號的功能,我們在head中加入script標簽,并給hello world綁定點擊事件:
<!DOCTYPE html>
<html>
<head>
<title>test</title>
<script>
function myFunction() {
document.getElementById('content').innerHTML = 'hello world!!!'
}
</script>
</head>
<body>
<p id="content" onclick="myFunction()">hello world</p>
</body>
</html>
如果加在body中,一般放在body的最后面:
<!DOCTYPE html>
<html>
<head>
<title>test</title>
</head>
<body>
<p id="content" onclick="myFunction()">hello world</p>
<script>
function myFunction() {
document.getElementById('content').innerHTML = 'hello world!!!'
}
</script>
</body>
</html>
簡單的邏輯我們可以用這2種方式寫,這種方式叫做內聯(lián)腳本。
當邏輯復雜時,我們可以把上面的script標簽中的代碼抽取出來,比如在html的同級目錄創(chuàng)建一個js文件夾,里面新建一個a.js的文件。
a.js中寫上面script標簽中的代碼:
function myFunction() {
document.getElementById('content').innerHTML = 'hello world!!!'
}
上面的script標簽則可以改成:
<script src="./js/a.js"></script>
上面的2種寫法,瀏覽器在加載html時,遇到script標簽,會停止解析html。
內聯(lián)腳本會立刻執(zhí)行;外聯(lián)腳本會先下載再立刻執(zhí)行。
等腳本執(zhí)行完畢才會繼續(xù)解析html。
(html解析到哪里,頁面就能顯示到哪里,用戶也能看到哪里)
比如下面的代碼:
<p>...content before script...</p>
<script src="./js/a.js"></script>
<p>...content after script...</p>
解析到第一個p標簽,我們能看到...content before script...顯示在了頁面中,然后瀏覽器遇到script標簽,會停止解析html,而去下載a.js并執(zhí)行,執(zhí)行完a.js才會繼續(xù)解析html,然后頁面中才會出現(xiàn)...content after script...。
我們可以通過Chrome的Developer Tools分析一下index.html加載的時間線:
這會導致2個問題:
1、腳本無法訪問它下面的dom;
2、如果頁面頂部有個笨重的腳本,在它執(zhí)行完之前,用戶都看不到完整的頁面。
對于問題2,我們可以把腳本放在頁面底部,這樣它可以訪問到上面的dom,且不會阻塞頁面的顯示:
<body>
...all content is above the script...
<script src="./js/a.js"></script>
</body>
但這不是最好的辦法,我們接著往下看。
我們給script標簽加defer屬性,就像下面這樣:
<p>...content before script...</p>
<script defer src="./js/a.js"></script>
<p>...content after script...</p>
defer 特性告訴瀏覽器不要等待腳本。于是,瀏覽器將繼續(xù)解析html,腳本會并行下載,然后等 DOM 構建完成后,腳本才會執(zhí)行。
這樣script標簽不再阻塞html的解析。
這時再看時間線:
需要注意的是,具有 defer 特性的腳本保持其相對順序。
比如:
<script defer src="./js/a.js"></script>
<script defer src="./js/b.js"></script>
上面的2個腳本會并行下載,但是不論哪個先下載完成,都是先執(zhí)行a.js,a.js執(zhí)行完才會執(zhí)行b.js。
這時,如果b.js依賴a.js,這種寫法將很有用。
另外需要注意的是,defer 特性僅適用于外聯(lián)腳本,即如果 script標簽沒有 src屬性,則會忽略 defer 特性。
我們可以給script標簽加async屬性,就像下面這樣:
<script async src="./js/a.js"></script>
這會告訴瀏覽器,該腳本完全獨立。
獨立的意思是,DOM 和其他腳本不會等待它,它也不會等待其它東西。async 腳本就是一個會在加載完成時立即執(zhí)行的完全獨立的腳本。
這時再看時間線:
可以看到,雖然下載a.js不阻塞html的解析,但是執(zhí)行a.js會阻塞。
還需要注意多個async時的執(zhí)行順序,比如下面這段代碼:
<p>...content before script...</p>
<script async src="./js/a.js"></script>
<script async src="./js/b.js"></script>
<p>...content after script...</p>
兩個p標簽的內容會立刻顯示出來,a.js和b.js則并行下載,且下載成功后立刻執(zhí)行,所以多個async時的執(zhí)行順序是誰先下載成功誰先執(zhí)行。
一些比較獨立的腳本,比如性能監(jiān)控,就很適合用這種方式加載。
另外,和defer一樣,async 特性也僅適用于外聯(lián)腳本。
我們可以動態(tài)地創(chuàng)建一個script標簽并append到文檔中。
let script = document.createElement('script')
script.src = '/js/a.js'
document.body.append(script)
append后腳本就會立刻開始加載,表現(xiàn)默認和加了async屬性一致。
我們可以顯示的設置script.async = false來改變這個默認行為,那么這時表現(xiàn)就和加了defer屬性一致。
上面的這些寫法,當script標簽變多時,容易導致全局作用域污染,還要維護書寫順序,要解決這個問題,需要一種將 JavaScript 程序拆分為可按需導入的單獨模塊的機制,即js模塊化,我們接著往下看。
很長一段時間 JavaScript 沒有模塊化的概念,直到 Node.js 的誕生,把 JavaScript 帶到服務端,這時,CommonJS誕生了。
CommonJS定義了三個全局變量:
require,exports,module
require 讀入并執(zhí)行一個 js 文件,然后返回其 exports 對象;
exports 對外暴露模塊的接口,可以是任何類型,指向 module.exports;
module 是當前模塊,exports 是 module 上的一個屬性。
Node.js 使用了CommonJS規(guī)范。
比如:
// a.js
let name = 'Lily'
export.name = name
// b.js
let a = require('a.js')
console.log(a.name) // Lily
由于CommonJS不適合瀏覽器端,于是出現(xiàn)了AMD和CMD規(guī)范。
AMD(Asynchronous Module Definition) 是 RequireJS 在推廣過程中對模塊定義的規(guī)范化產(chǎn)出。
基本思想是,通過 define 方法,將代碼定義為模塊。當這個模塊被 require 時,開始加載依賴的模塊,當所有依賴的模塊加載完成后,開始執(zhí)行回調函數(shù),返回該模塊導出的值。
使用時,需要先引入require.js:
<script src="require.js"></script>
<script src="a.js"></script>
然后可以這樣寫:
// a.js
define(function() {
let name = 'Lily'
return {
name
}
})
// b.js
define(['a.js'], function(a) {
let name = 'Bob'
console.log(a.name) // Lily
return {
name
}
})
CMD(Common Module Definition) 是 Sea.js 在推廣過程中對模塊定義的規(guī)范化產(chǎn)出。
使用時,需要先引入sea.js:
<script src="sea.js"></script>
<script src="a.js"></script>
然后可以這樣寫:
// a.js
define(function(require, exports, module) {
var name = 'Lily'
exports.name = name
})
// b.js
define(function(require, exports, module) {
var name = 'Bob'
var a = require('a.js')
console.log(a.name) // 'Lily'
exports.name = name
})
UMD (Universal Module Definition) 目的是提供一個前后端跨平臺的解決方案(兼容全局變量、AMD、CMD和CommonJS)。
實現(xiàn)很簡單,判斷不同的環(huán)境,然后以不同的方式導出模塊:
(function (root, factory) {
if (typeof define === 'function' && (define.amd || define.cmd)) {
// AMD、CMD
define([], factory);
} else if (typeof module !== 'undefined' && typeof exports === 'object') {
// Node、CommonJS
module.exports = factory();
} else {
// 瀏覽器全局變量
root.moduleName = factory();
}
}(this, function () {
// 只需要返回一個值作為模塊的export
// 這里我們返回了一個空對象
// 你也可以返回一個函數(shù)
return {};
}));
AMD 和 CMD 是社區(qū)的開發(fā)者們制定的模塊加載方案,并不是語言層面的標準。從 ES6 開始,在語言標準的層面上,實現(xiàn)了模塊化功能,而且實現(xiàn)得相當簡單,完全可以取代上文的規(guī)范,成為瀏覽器和服務器通用的模塊解決方案。
ES6 的模塊自動采用嚴格模式。模塊功能主要由兩個命令構成:export和import。
export命令用于規(guī)定模塊的對外接口;
import命令用于輸入其他模塊提供的功能。
比如上面的代碼,我們可以這樣寫:
// a.js
const name = 'Lily'
export {
name
}
// 等價于
export const name = 'Lily'
// b.js
import { name } from 'a.js'
console.log(name) // Lily
// b.js
import * as a from 'a.js'
console.log(a.name) // Lily
此外,還可以用export default默認導出的寫法:
// a.js
const name = 'Lily'
export default {
name
}
// b.js
import a from 'a.js'
console.log(a.name) // Lily
如果只想運行a.js,可以只import:
// b.js
import 'a.js'
我們可以給script標簽加type=module讓瀏覽器以 ES Module 的方式加載腳本:
<script type="module" src="./js/b.js"></script>
這時,script標簽會默認有defer屬性(也可以設置成async),支持內聯(lián)和外聯(lián)腳本。
這時我們運行打開index.html,會發(fā)現(xiàn)瀏覽器報錯了:
這是因為 type=module 的 script 標簽加強了安全策略,瀏覽器加載不同域的腳本資源時,如果服務器未返回有效的 Allow-Origin 相關 CORS 頭,會禁止加載改腳本。而這里啟動的index.html是一個本地文件(地址是file://路徑),將會遇到 CORS 錯誤,需要通過一個服務器來啟動 HTML 文件。
在瀏覽器支持 ES Module 之前,我們用工具實現(xiàn)JavaScript模塊化的開發(fā),比如webpack、Rollup 和 Parcel 。但是當項目越來越大后,本地熱更新越來越慢,而 Vite 旨在利用ESM解決上述問題。
Vite使用簡單,可以去官網(wǎng)(https://cn.vitejs.dev/)看看。
老的規(guī)范了解即可,未來是ES Module的,用Vite可以極大的提升開發(fā)時的體驗,生產(chǎn)環(huán)境用Rollup打包。
https://cn.vuejs.org/index.html
需要你做一下預習:https://cn.vuejs.org/v2/guide/index.html
數(shù)據(jù)驅動。如果我們要改變頁面效果,不再需要直接操作dom元素,只需要改變數(shù)據(jù)就好。數(shù)據(jù)改變之后框架會自動的幫我們進行頁面更新。
js最初的出現(xiàn)就是為了解決一個頁面中彈出一個提示,或者做一個簡單的計算。當時的環(huán)境下,瀏覽器可用的內存很小,為了解決這些問題,js語言必須簡單、沒有太復雜的數(shù)據(jù)結構、占用內存小。
但是隨著時間的發(fā)展,網(wǎng)頁的功能越來越復雜,需要的交互越來越多,js需要做的事情就更多。隨著時代的發(fā)展,瀏覽器的廠家也越來越多,每家瀏覽器對js語法的支持也不一樣。ECMA這個組織,建立一個統(tǒng)一的標準,在不停的制定一些語言語法的規(guī)范。
語言在發(fā)展的過程中,會吸取或者借鑒一下同行的一些優(yōu)勢,來完善自身。
為了解決業(yè)務場景的復雜化,出現(xiàn)了很多框架或者開發(fā)模式:
jQuery是前期出現(xiàn)的一個神級的插件,它提供了一個標準的元素選擇方案,讓我們可以快速的做元素選擇。選中之后做后序的各種操作。它統(tǒng)一了各個瀏覽器中js語法的差異,使用jQuery寫代碼就不需要考慮各個版本瀏覽器中語法的差別
MVC框架Backbone,是早期經(jīng)典的前端開發(fā)框架(jQuery+underscore.js+backbone.js+require.js);做SPA單頁面應用程序開發(fā)
angular.js,是google的。分為兩類:angular.js和angular
react.js,是facebook出的框架,目前是全球使用最廣泛的。國內十家公司的react可能有十種寫法
vue.js,是一個個人項目,目前是國內使用比較廣泛的。國內十家公司的vue只能有一種寫法
https://cli.vuejs.org/zh/
npm install -g @vue/cli # 全局安裝vue腳手架,希望你成功
剛才我初始化項目時選擇的內容
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, CSS Pre-processors, Linter
? Choose a version of Vue.js that you want to start the project with 2.x
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
? Pick a linter / formatter config: Basic
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
翻譯之后的中文版本
? Please pick a preset: Manually select features(選擇使用手動方式創(chuàng)建項目)
? Check the features needed for your project: Choose Vue version, Babel, CSS Pre-processors, Linter(我現(xiàn)擇了四項:1. 手動選擇vue的版本【必選】,2.安裝babel【必選】,3.css預處理,后面可以選擇使用sass或者less等預處理語言,4.選擇了代碼規(guī)范性檢測,寫代碼不符合規(guī)范時報錯)
? Choose a version of Vue.js that you want to start the project with 2.x(選擇vue2)
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)(選擇使用sass)
? Pick a linter / formatter config: Basic(選擇lint規(guī)范性檢測的基礎配置)
? Pick additional lint features: Lint on save(在保存的時候檢測代碼)
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files(把每一個插件的配置文件單獨放置)
? Save this as a preset for future projects? No(以后都不使用這種配置)
npm的配置文件,路徑在windows的c:/users/你的用戶名/.npmrc
registry=https://registry.npm.taobao.org/
init.author.email=你的郵箱
init.author.name=你的名字
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
vue中的路由插件,路由的作用是實現(xiàn)頁面跳轉。簡單點理解就是再瀏覽器中訪問指定的地址的時候展示的組件或者頁面內容
https://router.vuejs.org/zh/
路由分兩種模式:hash和history
區(qū)別:hash瀏覽器支持性好,不需要做額外的配置,可以直接使用;history模式再發(fā)布的時候需要做特殊的設置,在web服務器上做了配置之后才能使用;hash模式的路由,地址路徑中有#進行分割,#后面的表示路徑,history模式中沒有#
npm i vue-router # 安裝路由插件
import Vue from 'vue'
import VueRouter from 'vue-router'
// 可以直接再組件中使用router-view和router-link等路由內置組件和對象($route和$router)
Vue.use(VueRouter)
const router = new VueRouter({
routes: [] // 路由表,或者叫路由數(shù)據(jù),就是我們網(wǎng)文指定地址時候展示的組件
})
參數(shù)傳遞之后,在對應的頁面使用$route屬性可以直接獲取參數(shù)
https://router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E7%BB%84%E4%BB%B6%E5%86%85%E7%9A%84%E5%AE%88%E5%8D%AB,抽空了看看
beforeEach
afterEach
children
在一個組件中放多個router-view,通過name屬性進行命名指定
在定義路由的時候使用components屬性指定展示的組件,可以通過設置屬性名為router-view的name屬性,屬性值為對應的組件的方式實現(xiàn)
new Router({
routes: [{
path: '/demo',
name: 'Demo',
components: {
default: ()=>import('.....'),
first: ()=>import('...')
}
}]
})
https://router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E7%BB%84%E4%BB%B6%E5%86%85%E7%9A%84%E5%AE%88%E5%8D%AB
beforeRouteUpdate(to, from, next) {
// 在當前路由改變,但是該組件被復用時調用
// 舉例來說,對于一個帶有動態(tài)參數(shù)的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,
// 由于會渲染同樣的 Foo 組件,因此組件實例會被復用。而這個鉤子就會在這個情況下被調用。
// 可以訪問組件實例 `this`
},
https://cn.vuejs.org/v2/api/#%E5%85%A8%E5%B1%80-API
https://vuex.vuejs.org/zh/
vuex是vue中的一個狀態(tài)管理插件,通俗的講就是一個全局的數(shù)據(jù)管理工具。作用是實現(xiàn)項目中數(shù)據(jù)的集中式管理。
vuex是遵循單向數(shù)據(jù)流機制的:就是數(shù)據(jù)是單向流動的,分為三部分(view,state,action)
在view視圖中,通過dispatch派發(fā)一個action改變數(shù)據(jù),數(shù)據(jù)改變之后view視圖重新渲染
vuex中的數(shù)據(jù)流向:在組件中通過dispatch派發(fā)一個action,在action中獲取數(shù)據(jù),然后通過commit提交一個mutation改變數(shù)據(jù),數(shù)據(jù)改變之后組件重新渲染
他們可以接收的參數(shù)為:
mapXxx('命名空間', [數(shù)組])
mapXxx('命名空間', {對象})
如果沒有命名空間空間參數(shù),表示獲取根節(jié)點上的內容
你有沒有用過vuex?
兩種回答方式:
vuex不是項目開發(fā)的時候必選的一個插件,但是在需要用的時候你要知道它的存在,它的作用就是顯示數(shù)據(jù)在不同的組件之間進行共享的。
手機app
管理后臺
https://lurongtao.gitee.io/felixbooks-interview2/
vue.config.js,所有的相關配置信息都在vue-cli的官網(wǎng)上:https://cli.vuejs.org/zh/config/#vue-config-js
https://webpack.docschina.org/ webpack官網(wǎng),作為了解
module.exports = {
publicPath: "./", // 表示打包之后資源文件的加載路徑
// 再做性能優(yōu)化的時候,需要做到
/**
* 1. 路由文件的懶加載,使用 ()=> import('xxx')的方式引入,可以把路由組件單獨打包成js文件,在需要使用的時候再引入
* 2. 使用cdn的方式引入第三方資源庫
*
* **/
// configureWebpack,對webpack工具做額外的設置
configureWebpack: {
externals: {
// 屬性名是js源代碼中引入的時候使用的包名,屬性值是引入js文件后再瀏覽器中可以直接使用的名字
vue: "Vue",
vuex: "Vuex",
"vue-router": "VueRouter",
axios: "axios",
"element-ui": "ELEMENT",
},
},
// 腳手架內置了一個node的開發(fā)服務器,可以直接讓我們通過網(wǎng)絡路徑訪問代碼
devServer: {
// port: 998, // 改變開發(fā)服務器的端口號
proxy: {
// 訪問以/api開頭的地址時做一個代理轉發(fā)
// 代理只有再開發(fā)的時候有用,打包之后就沒用了
"/api": {
target: "https://papi.jiemian.com/page/api", // 目標服務器
ws: true, // 開啟ws
changeOrigin: true, // 改變origin
pathRewrite: { "^/api": "" }, // 路徑重寫,把/api替換成空白
},
},
},
};
是一個基于vue語法的服務器端渲染(SSR)框架。使用vue語法編寫多頁面應用程序,就是每一次路由跳轉打開的都是一個新的html文件。它解決了SPA單頁面應用程序的一個通病(最怕刷新)。
https://www.nuxtjs.cn/
yarn是facebook出的一款包管理工具,和npm一樣的功能
https://yarnpkg.com/
安裝使用
npm i yarn -g # 全局安裝yarn
yarn add xx # 安裝模塊,相當于 npm i xx
yarn remove xx # 刪除模塊,相當于 npm uninstall xx
.nuxt打包文件、nuxt.config.js配置文件、package.json依賴配置文件、static靜態(tài)文件放在服務器
*請認真填寫需求信息,我們會在24小時內與您取得聯(lián)系。