JavaScript是一種程序設(shè)計語言,一般用在瀏覽器里邊,而在網(wǎng)頁力編譯代碼的形式出現(xiàn)來控制網(wǎng)頁的效果。
目前成都創(chuàng)新互聯(lián)已為上千多家的企業(yè)提供了網(wǎng)站建設(shè)、域名、虛擬空間、成都網(wǎng)站托管、企業(yè)網(wǎng)站設(shè)計、伊川網(wǎng)站維護等服務(wù),公司將堅持客戶導向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。
JavaScript是采用的JAVA編程語言的語法,但是比JAVA結(jié)構(gòu)簡單得多,比較容易掌握,但是其內(nèi)部實現(xiàn)并不一樣,所以有人說JAVA和javascript近親,這是不對的
JSP,是java的web形式,也就是說,jsp也是用來做網(wǎng)頁的,但是jsp里邊寫的是java代碼,java代碼編譯后負責生成HTML代碼,生成的代碼到客戶端也就是你的瀏覽器,你的瀏覽器看到的只是生成代碼,而不是jsp源代碼
由于JavaScript是在網(wǎng)頁里邊直接進行控制的,所以比如說你的某一個操作是不符合條件的,但是為了達到某種效果,而你這種操作不需要讓服務(wù)器知道,你就可以使用這種方法。
相對的JSP的操作,你必須把你的操作轉(zhuǎn)交到服務(wù)器去處理
比如說jsp里邊
%
out.print("tr");
for( i=0; i5; i++)
{
out.print("td/td");
}
out.print("/tr");
%
而你看到的代碼只能是
tr
td/td
td/td
td/td
td/td
td/td
/tr
而javascript如果寫在頁面里邊,你是可以看到實際代碼的.
在我們的工作和學習當中,到處充滿了異步的身影,到底什么是異步,什么是異步編程,為什么要用異步編程,以及經(jīng)典的異步編程有哪些,在工作中的場景又有什么,我們一點點深入的去學習。
什么是異步編程?
有必要了解一下,什么是異步編程,為什么要異步編程。
先說一個概念異步與同步。介紹異步之前,回顧一下,所謂同步編程,就是計算機一行一行按順序依次執(zhí)行代碼,當前代碼任務(wù)耗時執(zhí)行會阻塞后續(xù)代碼的執(zhí)行。
同步編程,即是一種典型的請求-響應(yīng)模型,當請求調(diào)用一個函數(shù)或方法后,需等待其響應(yīng)返回,然后執(zhí)行后續(xù)代碼。
一般情況下,同步編程,代碼按序依次執(zhí)行,能很好的保證程序的執(zhí)行,但是在某些場景下,比如讀取文件內(nèi)容,或請求服務(wù)器接口數(shù)據(jù),需要根據(jù)返回的數(shù)據(jù)內(nèi)容執(zhí)行后續(xù)操作,讀取文件和請求接口直到數(shù)據(jù)返回這一過程是需要時間的,網(wǎng)絡(luò)越差,耗費時間越長。
如果按照同步編程方式實現(xiàn),在等待數(shù)據(jù)返回這段時間,JavaScript是不能處理其他任務(wù)的,此時頁面的交互,滾動等任何操作也都會被阻塞,這顯然是及其不友好,不可接受的,而這正是需要異步編程大顯身手的場景。
我們想通過Ajax請求數(shù)據(jù)來渲染頁面,這是一個在我們前端當中很常見渲染頁面的方式?;久總€頁面都會都這樣的過程。在這里用同步的方式請求頁面會怎么樣?瀏覽器鎖死,不能進行其他操作。而且每當發(fā)送新的請求,瀏覽器都會鎖死,用戶體驗極差。
在瀏覽器中同步執(zhí)行將會是上面的這個樣子,任務(wù)1做完才能做任務(wù)2,任務(wù)2做完才會做任務(wù)3。這里面體現(xiàn)出同步編程的有序的特點。只能1,2,3不能1,3,2。但是我們的代碼邏輯中可以存在多任務(wù)同時執(zhí)行的過程。在我們生活中,煮飯和燒水可以同時去做,同樣在我們編程中也需要這樣的邏輯。
在計算機中有多線程的概念,什么意思呢,每一個線程做一件事,像下面這樣。
在不同的線程中可以執(zhí)行不同的任務(wù)。
但是我們的JavaScript是單線程的,這里的單線程,強調(diào)的執(zhí)行線程是單線程。后面也是有線程池的,線程以及線程池的概念在這里就不多說了。想了解的同學可以看看操作系統(tǒng)相關(guān)書籍。
JavaScript語言執(zhí)行環(huán)境是單線程的,單線程在程序執(zhí)行時,所走的程序路徑按照連續(xù)順序排下來,前面的必須處理好,后面的才會執(zhí)行。
但是我們也需要類似多線程機制的這種執(zhí)行方式。但是JavaScript還是單線程的,我們需要異步執(zhí)行,異步執(zhí)行會使得多個任務(wù)并發(fā)執(zhí)行。
并行與并發(fā)。前文提到多線程的任務(wù)可以并行執(zhí)行,而JavaScript單線程異步編程可以實現(xiàn)多任務(wù)并發(fā)執(zhí)行,這里有必要說明一下并行與并發(fā)的區(qū)別。
并行,指同一時刻內(nèi)多任務(wù)同時進行。邊煮飯,邊燒水,可以同時進行并發(fā),指在同一時間段內(nèi),多任務(wù)同時進行著,但是某一時刻,只有某一任務(wù)執(zhí)行。邊吃飯邊喝水,同一時間點只能喝水和吃飯。
接下來說一說異步機制
并發(fā)模型
目前,我們已經(jīng)知道,JavaScript執(zhí)行異步任務(wù)時,不需要等待響應(yīng)返回,可以繼續(xù)執(zhí)行其他任務(wù),而在響應(yīng)返回時,會得到通知,執(zhí)行回調(diào)或事件處理程序。那么這一切具體是如何完成的,又以什么規(guī)則或順序運作呢?接下來我們需要解答這個問題?;卣{(diào)和事件處理程序本質(zhì)上并無區(qū)別,只是在不同情況下,不同的叫法。
前文已經(jīng)提到,JavaScript異步編程使得多個任務(wù)可以并發(fā)執(zhí)行,而實現(xiàn)這一功能的基礎(chǔ)是JavaScript擁有一個基于事件循環(huán)的并發(fā)模型。
堆棧與隊列
介紹JavaScript并發(fā)模型之前,先簡單介紹堆棧和隊列的區(qū)別:
堆(heap):內(nèi)存中某一未被阻止的區(qū)域,通常存儲對象(引用類型);
棧(stack):后進先出的順序存儲數(shù)據(jù)結(jié)構(gòu),通常存儲函數(shù)參數(shù)和基本類型值變量(按值訪問);
隊列(queue):先進先出順序存儲數(shù)據(jù)結(jié)構(gòu)。
事件循環(huán)(EventLoop):JavaScript引擎負責解析,執(zhí)行JavaScript代碼,但它并不能單獨運行,通常都得有一個宿主環(huán)境,一般如瀏覽器或Node服務(wù)器,前文說到的單線程是指在這些宿主環(huán)境創(chuàng)建單一線程,提供一種機制,調(diào)用JavaScript引擎完成多個JavaScript代碼塊的調(diào)度,執(zhí)行(是的,JavaScript代碼都是按塊執(zhí)行的),這種機制就稱為事件循環(huán)(EventLoop)。
JavaScript執(zhí)行環(huán)境中存在的兩個結(jié)構(gòu)需要了解:
消息隊列(messagequeue),也叫任務(wù)隊列(taskqueue):存儲待處理消息及對應(yīng)的回調(diào)函數(shù)或事件處理程序;
執(zhí)行棧(executioncontextstack),也可以叫執(zhí)行上下文棧:JavaScript執(zhí)行棧,顧名思義,是由執(zhí)行上下文組成,當函數(shù)調(diào)用時,創(chuàng)建并插入一個執(zhí)行上下文,通常稱為執(zhí)行棧幀(frame),存儲著函數(shù)參數(shù)和局部變量,當該函數(shù)執(zhí)行結(jié)束時,彈出該執(zhí)行棧幀;
注:關(guān)于全局代碼,由于所有的代碼都是在全局上下文執(zhí)行,所以執(zhí)行棧頂總是全局上下文就很容易理解,直到所有代碼執(zhí)行完畢,全局上下文退出執(zhí)行棧,棧清空了;也即是全局上下文是第一個入棧,最后一個出棧。
任務(wù)
分析事件循環(huán)流程前,先闡述兩個概念,有助于理解事件循環(huán):同步任務(wù)和異步任務(wù)。
任務(wù)很好理解,JavaScript代碼執(zhí)行就是在完成任務(wù),所謂任務(wù)就是一個函數(shù)或一個代碼塊,通常以功能或目的劃分,比如完成一次加法計算,完成一次ajax請求;很自然的就分為同步任務(wù)和異步任務(wù)。同步任務(wù)是連續(xù)的,阻塞的;而異步任務(wù)則是不連續(xù),非阻塞的,包含異步事件及其回調(diào),當我們談及執(zhí)行異步任務(wù)時,通常指執(zhí)行其回調(diào)函數(shù)。
Web Worker 可能是在 JavaScript 中唯一可以真正實現(xiàn)多線程的方法了。我們需要按照下面的方式創(chuàng)建 worker :
?
1
const worker = newWorker("worker.js");
上面就定義了一個 Worker 實例,然后你可以通過 postMessage 與 worker 通信,就像和 iFrame
通信一樣,只不過不存在跨域的問題,不需要驗證跨域。
?
1
worker.postMessage(num);
在 worker 代碼中,你需要監(jiān)聽這些事件:
?
1
2
3
onmessage = (e) = {
// e.data will contain the value passed
};
這種方式是雙向的,所以你也可以從 worker 中 postMessage 給我們的主程序。
在 worker 代碼中:
?
1
postMessage(result);
在主程序中:
?
1
worker.onmessage = (e) = {}
這就是 worker 最基本的用法。
異常處理
在你的 worker 代碼中,有很多種方式來處理異常,比如你可以 catch 之后通過 postMessage
傳遞,這樣可能需要多些一些代碼,但是確實最有效也最安全的。
另一種方式是用 onerror 事件,這種方式可以捕捉所有未處理的異常,并且交給調(diào)用方來決定如何處理。調(diào)用方式很簡單:
?
1
worker.onerror = (e) = {};
為了調(diào)試方便,異常對象中還有一些額外的字段比如:filename,lineno,colno.
回收
將不需要的 worker 回收是非常重要的,worker 會生成真正的操作系統(tǒng)線程,如果你發(fā)現(xiàn)與很多 worker
線程同時運行,你可以通過很簡單的殺掉瀏覽器進程。
你有兩種方式殺掉 worker 進程:在 worker 里和在 worker 外。我認為最好的處理 worker
生命周期的地方是在主頁面里,但這也要取決于你代碼的具體情況。
殺掉一個 worker 實例,在外部可以直接調(diào)用
terminate()方法,這種方法可以立即殺掉它,釋放所有它正在使用的資源,如果它正在運行,也會立即終止。
如果你想要讓 worker 自己去管理它的生命周期,可以直接在 worker 代碼中調(diào)用stop()方法。
不管使用哪種方法,worker 都會停止,銷毀所有資源。
如果你想使用一種“一次性”的 worker,比如需要做一些復雜運算之后就不再使用了,也要確保在 onerror
事件中去銷毀它們,這樣有利于規(guī)避一些難以發(fā)現(xiàn)的問題。
?
1
2
3
4
5
6
7
8
worker.onerror = (e) = {
worker.terminate();
reject(e);
};
worker.onmessage = (e) = {
worker.terminate();
resolve(e.data);
}
二、行內(nèi) Workers
有些時候?qū)?worker 代碼寫到一個外部文件可能會使原本簡單的問題變得復雜,幸運的是,workers 也可以用一個 Blob 來初始化。
寫一個行內(nèi) worker ,參考如下代碼段:
?
1
2
3
4
5
6
7
8
!-- --
script id="worker" type="javascript/worker"
// Put your worker code here
/script
const code = URL.createObjectURL(new Blob([
document.getElementById("worker").textContent
]));
const worker = new Worker(code);
這樣你就創(chuàng)建了一個全局的 ObjectURL,但別忘了當不需要的時候要銷毀它:
?
1
2
worker.terminate();
URL.revokeObjectURL(code);
三、Workers 嵌套
理論上,你可以嵌套使用 worker,就像在主線程中定義一個 worker 一樣。這里有一個簡單的 例子。但是不幸的是在 Chrome 中一直存在一個
bug ,讓我們不能愉快的玩耍,或許以后這個 bug 會修復,但是目前來說還是沒有太多進展,所以你最好不要使用。
數(shù)據(jù)傳遞
在 worker
數(shù)據(jù)傳遞的過程中有些需要注意的邊緣情況。你可以傳遞數(shù)值,字符串,數(shù)組,也可以傳遞序列化/反序列化的對象。然而,你卻不應(yīng)該依賴序列化來保持數(shù)據(jù)結(jié)構(gòu),實際上,postMessage
用到了一種 數(shù)據(jù)克隆算法,它會生成一些額外的屬性比如 RegExps 和 Blobs 以及一些循環(huán)引用。
這就是說,你需要將你要傳遞的數(shù)據(jù)最小化。你不可以傳遞 functions ,即使是支持的類型也會有一些限制,這些也很容易產(chǎn)生一些難以發(fā)現(xiàn)的
bug。如果你將你的 API 定義為只傳遞字符串,數(shù)值,數(shù)組和對象的話,那你可能會避過這些問題。
循環(huán)引用
如果你有一個很復雜的對象,那么里面很可能存在循環(huán)引用,這時如果你將它序列化成 JSON,你將會得到一個 TypeError:
Converting circular structure to JSON.
?
1
2
3
4
let a = {};
let b = {a};
a.b = b;
JSON.stringify({a,b}); // Error
然而你可以在 postMessage 中放心的使用,從而你就可以在 worker 中使用。
Transferable objects
為了防止同時修改同一變量的場景,你傳遞給 postMessage
的所有變量都會復制一份,這樣確保了你多個線程不會修改同一個變量。但如果你想要傳一個非常大的數(shù)據(jù)的話,你就會發(fā)現(xiàn)復制操作是很慢的。比如,如果你在做一些圖片相關(guān)的運算,你可能會傳遞整個圖片信息,就可能會遇到復制性能的瓶頸。
好在有 transferable object ,用 transfer 來代替 copy,比如ArrayBuffer
是transferable對象,而我們可以把任何類型的對象放在 ArrayBuffer 中。
如果你 transfer 一個對象,之前擁有它的線程被鎖定權(quán)限,它確保了數(shù)據(jù)沒有復制之前,不會被同時修改。
這時 postMessage 的代碼段就有點尷尬了:
?
1
2
3
4
const ab = new ArrayBuffer(100);
console.log(ab.byteLength); // 100
worker.postMessage(ab, [ab]);
console.log(ab.byteLength); // 0
確保在 postMessage 中傳遞第二個參數(shù),否則數(shù)據(jù)將會被復制。
?
1
2
3
4
const ab = new ArrayBuffer(100);
console.log(ab.byteLength); // 100
worker.postMessage(ab);
console.log(ab.byteLength); // 100
四、Webpack
在 Webpack 中使用 Web worker 時,你需要用 worker-loader。將它添加到 package.json 中的
devDependencies,然后運行 npm install,就可以了。
用到 worker 時,只需要 require 它。
?
1
2
3
const workerCode = require("worker!./worker.js");
...
const worker = new workerCode();
這樣就初始化了 worker,然后就像上面講的一樣使用 worker。
如果需要使用行內(nèi) worker,你需要傳遞 inline 參數(shù)給 loader。
?
1
2
3
const workerCode = require("worker?inline!./worker.js");
...
const worker = new workerCode();
在 worker 中你也可以 import 模塊。
?
1
2
3
import fibonacci from "./fibonacci.js";
...
const result = fibonacci(num);
缺點
在 Webpack 中使用 worker 很簡單,但是在使用時也有一些坑值得你注意。
首先,無法將代碼共用部分提取出來。如果你的 worker 中依賴一段共用代碼,你只能把代碼添加到 worker
中,不管其他地方是否也用到同樣的代碼。而且如果你多個 worker 要用同樣的庫,你也需要在每個 worker 中引入它們。
你可能會想如果你不用 worker-loader,然后用CommonsChunkPlugin指定一個新的入口,可能會解決這個問題。但是不幸的是
worker 不像是瀏覽器 window ,一些 feature 不可用,所以一些代碼必須要引入。
同時,用行內(nèi) worker 也不會解決問題,共用的代碼依然會出現(xiàn)在多個地方。
第二點缺點是,行內(nèi) worker 可能會導致
ObjectURLs內(nèi)存泄露.它們被創(chuàng)建出來以后就不會被釋放。這雖然不是一個大問題,但是如果你有很多“一次性” worker 的話,就會影響性能。
綜上所述,我個人建議是使用標準的 worker,注意在 worker 中引入了什么。還要注意使用緩存。
五、IFrames Web worker
IFrames Web worker 和 IFrame 很像,而且印象中 IFrame 也可以實現(xiàn)多線程。但是 IFrame 存在一些不是線程安全
API,比如 DOM 相關(guān),瀏覽器不能為他們生成新的線程,參考這里.
在 IFrame 跨域中,很多 API 它都沒有權(quán)限,也只能通過 postMessage,就像 Web Worker
一樣。理論上,瀏覽器可以在不同的線程中運行 IFrame,也就可以用 IFrame 實現(xiàn)多線程。
但是實際并非如此,它還是單線程的,瀏覽器不會給它們額外的線程。
async await都是通過promise 來實現(xiàn),可以同時并行多個任務(wù)
直接同步方式的話
假設(shè)你要得到10本書的JSON文件采取同步方式,那么是等待書本一個一個的獲取
但是如果是async await的話可以直接類似
async function book(u){
let data = ?await getJson(u)
$dom.parse(data)
}
lz可以跑下下列代碼
var?hold?=?function?()?{
return?new?Promise(function?(resolve,?reject)?{
resolve();
})
};
async?function?count(i){
await?hold()
console.log(i)
}
for(var?i?=?0?;i??10?;?i++)
?count(i);
console.log("run")
實際上是run 先跑
1、defer 和 async 在網(wǎng)絡(luò)讀?。_本下載)這塊兒是一樣的,都是異步的(相較于 HTML 解析)
2、兩者的差別:在于腳本下載完之后何時執(zhí)行,顯然 defer 是最接近我們對于應(yīng)用腳本加載和執(zhí)行的要求的。defer是立即下載但延遲執(zhí)行,加載后續(xù)文檔元素的過程將和腳本的加載并行進行(異步),但是腳本的執(zhí)行要在所有元素解析完成之后,DOMContentLoaded 事件觸發(fā)之前完成。async是立即下載并執(zhí)行,加載和渲染后續(xù)文檔元素的過程將和js腳本的加載與執(zhí)行并行進行(異步)。
3、關(guān)于 defer,我們還要記住的是它是按照加載順序執(zhí)行腳本的
4、標記為async的腳本并不保證按照指定它們的先后順序執(zhí)行。對它來說腳本的加載和執(zhí)行是緊緊挨著的,所以不管你聲明的順序如何,只要它加載完了就會立刻執(zhí)行。
5、async 對于應(yīng)用腳本的用處不大,因為它完全不考慮依賴(哪怕是最低級的順序執(zhí)行),不過它對于那些可以不依賴任何腳本或不被任何腳本依賴的腳本來說卻是非常合適的。
分享標題:javascript并行,深入理解并行編程 豆瓣
地址分享:http://m.rwnh.cn/article36/phjgpg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供面包屑導航、網(wǎng)站改版、網(wǎng)站制作、微信公眾號、網(wǎng)站設(shè)計、云服務(wù)器
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)