WebKit
Gecko
WebKit
💥Trident
Blink
用戶界面
。包括地址欄、前進/後退按鈕、書籤菜單等。除了瀏覽器主窗口顯示的您請求的頁面外,其餘顯示的各個部分都屬於用戶界面。javascript
瀏覽器引擎
。在用戶界面和呈現引擎之間傳送指令。html
渲染引擎
。負責顯示請求的內容。若是請求的內容是 HTML,它就負責解析 HTML 和 CSS 內容,並將解析後的內容顯示在屏幕上。前端
網絡層
。用於網絡調用,好比 HTTP 請求。其接口與平臺無關,併爲全部平臺提供底層實現。java
用戶界面後端
。用於繪製基本的窗口小部件,好比組合框和窗口。其公開了與平臺無關的通用接口,而在底層使用操做系統的用戶界面方法。node
JavaScript 解釋器。
用於解析和執行 JavaScript 代碼。git
數據存儲。
這是持久層。瀏覽器須要在硬盤上保存各類數據,例如 Cookie。新的 HTML 規範 (HTML5) 定義了「網絡數據庫」,這是一個完整(可是輕便)的瀏覽器內數據庫。程序員
::: tip NOTICEgithub
Cookie localStorage Session IndexDb :::web
一般一個瀏覽器會至少存在三個線程:JS引擎線程(用於處理JS)
、GUI渲染線程(用於頁面渲染)
、瀏覽器時間觸發線程(用於控制交互)
。 而由於JS能夠操做DOM元素,進而會影響到GUI的渲染結果,所以JS引擎線程與GUI渲染線程是互斥的。也就是說當JS引擎線程處於運行狀態時,GUI渲染線程將處於凍結狀態。 JS引擎是基於事件驅動,採用的是單線程運行機制。即JS引擎會只會順序的從任務列表中取任務,並執行。面試
(1)UI線程 GUI渲染線程(渲染頁面)
(2)JS線程 JS引擎線程(處理腳本)
(3)事件(觸發)線程 事件觸發線程(控制交互)
::: tip NOTICE
包括瀏覽器有時候會開闢新的線程好比用完就扔的Http請求線程(Ajax)等等其餘線程 它們共同構成了瀏覽器的UI線程 這些線程在UI線程的控制下井井有理的工做着 關於這個常駐線程網上觀點不一致,各個瀏覽器實現可能也不同,這裏就不深考究了 雖然我把js引擎線程放在了右下角,可是它是瀏覽器的主線程 並且它和GUI渲染線程
是水火不容的,不可以同時工做 道理很簡單,由於它們都要影響DOM,若是 js 線程想要某個DOM的樣式,渲染引擎必須中止工做
:::
🐯🙃🏃🎉⚠️
::: tip NOTICE 其中 SetTimeout :在指定的毫秒數後調用指定的代碼段;SetInternal:在指定的時間間隔內(ms)循環調用指定的代碼段。這兩個函數內都涉及到時間計數器,也就是都涉及到一個相似與MFC定時器。JS引擎自己就只能單線程運行,所以定時器須要由其餘的外部線程來啓動。因此對JS引擎而言,定時器線程能夠被視爲異步線程。但當定時器時間到達後,所觸發的事件則必須在任務列表中排隊,等候JS引擎的處理。 :::
關於setTimeout下面有一個例子,能夠幫助深刻理解:
setTimeout(function () { while (true) { } }, 1000);
setTimeout(function () { alert('end 2'); }, 2000);
setTimeout(function () { alert('end 1'); }, 100);
alert('end');
複製代碼
執行的結果是彈出‘end’‘end 1’,而後瀏覽器假死,就是不彈出‘end 2’。也就是說第一個settimeout裏執行的時候是一個死循環,這個直接致使了理論上比它晚一秒執行的第二個settimeout裏的函數被阻塞,這個和咱們平時所理解的異步函數多線程互不干擾是不符的。
爲何JavaScript是單線程 單線程就是同一時間只能幹一件事
那麼JavaScript多線程很差嗎,那效率多高啊 ? 很差
::: tip NOTICE
js設計出來就是爲了與用戶交互,處理DOM 若是JavaScript多線程了,那就必須處理多線程同步的問題
(依稀記得曾經被C++線程同步支配的恐怖 假如js是多線程,同一時間一個線程想要修改DOM,另外一個線程想要刪除DOM 問題就變得複雜許多,瀏覽器不知道聽誰的,若是引入「鎖」的機制,那就麻煩死了(那我就不學前端了( ̄_, ̄ )
這樣一個腳本語言
根本沒有必要搞得那麼複雜,因此JavaScript誕生起就是單線程執行
:::
雖然H5提出了Web Worker
,可是它不可以操做DOM
,仍是要委託給大哥js主線程解決
這些子線程徹底受主線程老大控制的(並且受不少限制),實際上並無改變JavaScript單線程的本質
咱們先來看看什麼是執行棧 棧是先進後出(FILO)的數據結構
執行棧
中存放正在執行的任務,每個任務叫作「幀」
舉個例子
function foo(c){
var a = 1;
bar(200);
}
function bar(d){
var b = 2;
}
foo(100);
複製代碼
咱們來看看執行棧發生了怎樣的變化
執行棧爲空棧
執行棧其實至關於js主線程(同步順序執行)
隊列是先入先出(FIFO)的數據結構
js線程中還存在着一個任務隊列
任務隊列包含了一系列待處理的任務 單線程就意味着全部任務須要一個接一個的執行,若是一個任務執行的時間太長,那後面的任務就不得不等着
::: tip NOTICE
就比如護士阿姨給排隊的小朋友打針,若是最前面的小朋友一直滾針,那就一直扎,後面的小朋友就得等着(這比喻好像不恰當) 但是若是最前面的小朋友暈針昏倒了 那麼護士阿姨不可能坐那裏了等到他醒來,應該先給後面的小朋友扎針 也就是至關於把那位小朋友「掛起」(異步) :::
因此,任務能夠分爲兩種
::: tip NOTICE
同步任務就是正在主線程執行棧中執行的任務(在屋子內打針的小朋友)
而異步任務是在任務隊列等候處理的任務(在屋子外等候打針的小朋友)
一旦執行棧中沒有任務了,它就會從執行隊列
中獲取任務執行 :::
任務隊列是一個事件的隊列
,IO設備(輸入/輸出設備)每完成一項任務,就會在任務隊列中添加事件處理
用戶觸發了事件
,也一樣會將回調添加到任務隊列中去
主線程執行異步任務
,即是執行回調函數(事件處理函數)
只要執行棧一空,排在執行隊列前面的處理函數便會被優先讀取執行 不過主線程會檢查時間,某些事件須要到了規定時間才能進入主線程處理(定時器事件)
Event Loop
主線程從執行隊列不斷地獲取任務,這個過程是循環不斷地,叫作「Event Loop」事件循環 同步任務老是會在異步任務以前執行 只有當前的腳本執行完,纔可以去拿任務隊列中的任務執行 前面也說到了,任務隊列中的事件能夠是定時器事件 定時器分爲兩種 setTimeout() 和 setInterval() 前者是定時執行一次,後者定時重複執行 第一個參數爲執行的回調函數,第二個參數爲間隔時間(ms)
來看這樣一個例子
setTimeout(function(){
console.log('timer');
}, 1000);
console.log(1);
console.log(2);
console.log(3);
複製代碼
這個沒什麼問題,瀏覽器打印的是
1 2 3 timer
複製代碼
可是這樣呢
setTimeout(function(){
console.log('timer');
}, 0);//0延時
console.log(1);
console.log(2);
console.log(3);
複製代碼
瀏覽器打印依然打印的是 1 2 3 timer
也許有同窗知道,舊版瀏覽器,setTimeout定時至少是10ms(即使你設置了0ms), H5新規範是定時至少4ms
,改變DOM也是至少16ms
咱們能夠暫且認爲是這個緣由 那麼我再改動一下代碼
setTimeout(function(){
console.log('timer');
}, 0);
var a = +new Date();
for(var i = 0; i < 1e5; i++){
console.log(1);
}
var b = +new Date();
console.log(b - a);
複製代碼
這回夠刺激了吧,輸出10w次,我瀏覽器都假死了(心疼我chrome) 不只如此,我還打印了循環所用時間 來看看控制檯
輸出了10w個1,用了將近7s timer依然在最後打印 這就證實了我前面說的話: 同步任務老是會在異步任務以前執行 只有我執行棧空了,纔會去你任務隊列中取任務執行
實例 最後我舉一個例子加深一下理解
demo.onclick = function(){
console.log('click');
}
function foo(a){
var b = 1;
bar(200);
}
function bar(c){
var d = 2;
click//僞代碼 此時觸發了click事件(這裏我僞裝程序運行到這裏手動點擊了demo)
setTimeout(function(){
console.log('timer');
}, 0);
}
foo(100);
複製代碼
怕你們蒙我就不寫Ajax了 Ajax若是處理結束後(經過Http請求線程),也會將回調函數放在任務隊列中 還有一點 click 那一行僞代碼我最開始是想用demo.click()模擬觸發事件 後來在測試過程當中,發現它好像跟真實觸發事件不太同樣 它應該是不經過觸發事件線程,而是存在於執行棧中,就至關於單純地執行click回調函數 不過這只是我本身的想法有待考證,不過這不是重點,重點是咱們理解這個過程,請你們不要吐槽我╰( ̄▽ ̄)╭
下面看看執行這段代碼時發生了什麼(主要說棧和隊列的問題,不會贅述預編譯過程)
這一次,完全弄懂 JavaScript 執行機制 本文的目的就是要保證你完全弄懂javascript的執行機制,若是讀完本文還不懂,能夠揍我。
不論你是javascript新手仍是老鳥,不管是面試求職,仍是平常開發工做,咱們常常會遇到這樣的狀況:給定的幾行代碼,咱們須要知道其輸出內容和順序。由於javascript是一門單線程語言,因此咱們能夠得出結論:
javascript是按照語句出現的順序執行的 看到這裏讀者要打人了:我難道不知道js是一行一行執行的?還用你說?稍安勿躁,正由於js是一行一行執行的,因此咱們覺得js都是這樣的:
let a = '1';
console.log(a);
let b = '2';
console.log(b);
複製代碼
然而實際上js是這樣的:
setTimeout(function(){
console.log('定時器開始啦')
});
new Promise(function(resolve){
console.log('立刻執行for循環啦');
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log('執行then函數啦')
});
console.log('代碼執行結束');
複製代碼
依照js是按照語句出現的順序
執行這個理念,我自信的寫下輸出結果:
//"定時器開始啦"
//"立刻執行for循環啦"
//"執行then函數啦"
//"代碼執行結束"
複製代碼
去chrome上驗證下,結果徹底不對,瞬間懵了,說好的一行一行執行的呢?
咱們真的要完全弄明白javascript的執行機制了。
::: tip NOTICE
宏任務 --> 微任務 --> 宏任務 --> 微任務 -->[]
:::
總體代碼script,setTimeout,setInterval, new Promise
Promise.then,process.nextTick
主要代碼結構就是宏任務
以後執行微任務
javascript是一門單線程語言,在最新的HTML5中提出了Web-Worker,但javascript是單線程這一核心仍未改變。因此一切javascript版的"多線程"都是用單線程模擬出來的,一切javascript多線程都是紙老虎!
既然js是單線程,那就像只有一個窗口的銀行,客戶須要排隊一個一個辦理業務,同理js任務也要一個一個順序執行。若是一個任務耗時過長,那麼後一個任務也必須等着。那麼問題來了,假如咱們想瀏覽新聞,可是新聞包含的超清圖片加載很慢,難道咱們的網頁要一直卡着直到圖片徹底顯示出來?所以聰明的程序員將任務分爲兩類:
當咱們打開網站時,網頁的渲染過程就是一大堆同步任務,好比頁面骨架和頁面元素的渲染。而像加載圖片音樂之類佔用資源大耗時久的任務,就是異步任務。關於這部分有嚴格的文字定義,但本文的目的是用最小的學習成本完全弄懂執行機制,因此咱們用導圖來講明:
導圖要表達的內容用文字來表述的話:
咱們不由要問了,那怎麼知道主線程執行棧爲空啊?js 引擎存在 monitoring process進程,會持續不斷的檢查主線程執行棧是否爲空,一旦爲空,就會去Event Queue那裏檢查是否有等待被調用的函數。
說了這麼多文字,不如直接一段代碼更直白:
let data = [];
$.ajax({
url:www.javascript.com,
data:data,
success:() => {
console.log('發送成功!');
}
})
console.log('代碼執行結束');
複製代碼
上面是一段簡易的ajax請求代碼:
ajax
進入Event Table,註冊回調函數success
。相信經過上面的文字和代碼,你已經對js的執行順序有了初步瞭解。接下來咱們來研究進階話題:setTimeout。
大名鼎鼎的setTimeout無需再多言,你們對他的第一印象就是異步能夠延時執行,咱們常常這麼實現延時3秒執行:
setTimeout(() => {
console.log('延時3秒');
},3000)
複製代碼
漸漸的setTimeout用的地方多了,問題也出現了,有時候明明寫的延時3秒,實際卻5,6秒才執行函數,這又咋回事啊?
先看一個例子:
setTimeout(() => {
task();
},3000)
console.log('執行console');
複製代碼
//執行console
//task()
複製代碼
setTimeout(() => {
task()
},3000)
sleep(10000000)
複製代碼
乍一看其實差很少嘛,但咱們把這段代碼在chrome執行一下,卻發現控制檯執行task()須要的時間遠遠超過3秒,說好的延時三秒,爲啥如今須要這麼長時間啊?
這時候咱們須要從新理解setTimeout的定義。咱們先說上述代碼是怎麼執行的:
Important
task()
進入Event Table
並註冊,計時開始。sleep
函數,很慢,很是慢,計時仍在繼續。timeout
完成,task()進入Event Queue
,可是sleep也太慢了吧,還沒執行完,只好等着。
sleep終於執行完
了,task()
終於從Event Queue進入了主線程執行。上述的流程走完,咱們知道setTimeout
這個函數,是通過指定時間後,把要執行的任務(本例中爲task())加入到Event Queue中,又由於是單線程任務要一個一個執行,若是前面的任務須要的時間過久,那麼只能等着,致使真正的延遲時間遠遠大於3秒
。
咱們還常常遇到setTimeout(fn,0)
這樣的代碼,0秒後執行又是什麼意思呢?是否是能夠當即執行呢?
答案是不會的,setTimeout(fn,0)的含義是,指定某個任務在主線程最先可得的空閒時間執行,意思就是不用再等多少秒了,只要主線程執行棧內的同步任務所有執行完成,
棧爲空就立刻執行。舉例說明:
//代碼1
console.log('先執行這裏');
setTimeout(() => {
console.log('執行啦')
},0);
//代碼2
console.log('先執行這裏');
setTimeout(() => {
console.log('執行啦')
},3000);
複製代碼
代碼1的輸出結果是:
//先執行這裏
//執行啦
複製代碼
代碼2的輸出結果是:
//先執行這裏
// ... 3s later
// 執行啦
複製代碼
關於setTimeout要補充的是,即使主線程爲空,0毫秒實際上也是達不到的。根據HTML的標準,最低是4毫秒
。有興趣的同窗能夠自行了解。
上面說完了setTimeout,固然不能錯過它的孿生兄弟setInterval。他倆差很少,只不事後者是循環的執行。 對於執行順序來講,setInterval會每隔指定的時間將註冊的函數置入 Event Queue
,若是前面的任務耗時過久,那麼一樣須要等待。
惟一須要注意的一點是,對於setInterval(fn,ms)
來講,咱們已經知道不是每過 ms秒會執行一次 fn,而是每過 ms秒,會有 fn進入 Event Queue
。一旦setInterval的回調函數fn
執行時間超過了延遲時間ms
,那麼就徹底看不出來有時間間隔了
。這句話請讀者仔細品味。
讓咱們想象一個意外狀況,好比說下面的setInterval
setInterval(function () {
func(i++);
}, 100)
複製代碼
咱們每100毫秒調用一次func函數,若是func的執行時間少於100毫秒的話,在遇到下一個100毫秒前就可以執行完:
但若是func的執行時間大於100毫秒,該觸發下一個func函數時以前的尚未執行完怎麼辦?答案以下圖所示,那麼第二個func會在隊列(這裏的隊列是指event loop)中等待,直到第一個函數執行完
若是第一個函數的執行時間特別長,在執行的過程當中本應觸發了許多個func怎麼辦,那麼全部這些應該觸發的函數都會進入隊列嗎?
不,只要發現隊列中有一個被執行的函數存在,那麼其餘的通通忽略。以下圖,在第300毫秒和400毫秒處的回調都被拋棄,一旦第一個函數執行完後,接着執行隊列中的第二個,即便這個函數已經「過期」好久了。
還有一點,雖然你在setInterval的裏指定的週期是100毫秒,但它並不能保證兩個函數之間調用的間隔必定是一百毫秒。在上面的狀況中,若是隊列中的第二個函數時在第450毫秒處結束的話,在第500毫秒時,它會繼續執行下一輪func,也就是說這之間的間隔只有50毫秒,而非週期100毫秒
傳統的定時器咱們已經研究過了,接着咱們探究 Promise與 process.nextTick(callback)的表現。
Promise的定義和功能本文再也不贅述,不瞭解的讀者能夠學習一下阮一峯老師的Promise。而process.nextTick(callback)相似node.js版的"setTimeout",在事件循環的下一次循環中調用 callback 回調函數。
咱們進入正題,除了廣義的同步任務和異步任務,咱們對任務有更精細的定義:
new Promise
不一樣類型的任務會進入對應的Event Queue
,好比setTimeout
和setInterval
會進入相同的Event Queue。
事件循環的順序,決定js代碼的執行順序。進入總體代碼(宏任務)後,開始第一次循環。接着執行全部的微任務。而後再次從宏任務開始,找到其中一個任務隊列執行完畢,再執行全部的微任務。聽起來有點繞,咱們用文章最開始的一段代碼說明:
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
}).then(function() {
console.log('then');
})
console.log('console');
複製代碼
Event Queue
中setTimeout對應的回調函數,當即執行。事件循環,宏任務,微任務的關係如圖所示:
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
複製代碼
Event Queue
中。咱們記爲then1。process1和then1兩個微任務
。好了,第一輪事件循環正式結束,這一輪的結果是輸出1,7,6,8。那麼第二輪時間循環從setTimeout1宏任務開始:
宏任務Event Queue | 微任務Event Queue |
---|---|
setTimeout2 | process2 |
setTimeout2 | then1 |
宏任務Event Queue | 微任務Event Queue |
---|---|
process3 | |
then3 |
整段代碼,共進行了三次事件循環,完整的輸出爲1,7,6,8,2,4,3,5,9,11,10,12。
(請注意,node環境下的事件監聽依賴 libuv與前端環境不徹底相同,輸出順序可能會有偏差)
(1)js的異步 咱們從最開頭就說javascript是一門單線程
語言,無論是什麼新框架新語法糖實現的所謂異步,其實都是用同步的方法去模擬的,緊緊把握住單線程這點很是重要
。
(2)事件循環Event Loop 事件循環是js實現異步的一種方法,也是js的執行機制。
(3)javascript的執行和運行 執行和運行有很大的區別,javascript在不一樣的環境下,好比node,瀏覽器,Ringo等等,執行方式是不一樣的。而運行大多指javascript解析引擎,是統一的。
(4)setImmediate 微任務和宏任務還有不少種類,好比setImmediate等等,執行都是有共同點的,有興趣的同窗能夠自行了解。
javascript是一門單線程語言
Event Loop 是 javascript 的執行機制
緊緊把握兩個基本點,以認真學習javascript爲中心,早日成爲像我同樣的大牛
皮一下, 很開心
Cookie 是小甜餅的意思。顧名思義,cookie 確實很是小,它的大小限制爲4KB
左右,是網景公司的前僱員 Lou Montulli 在1993年3月的發明。它的主要用途有保存登陸信息
,好比你登陸某個網站市場能夠看到「記住密碼」,這一般就是經過在 Cookie 中存入一段辨別用戶身份的數據來實現的。
localStorage 是 HTML5 標準中新加入的技術,它並非什麼劃時代的新東西。早在 IE 6 時代,就有一個叫 userData 的東西用於本地存儲,而當時考慮到瀏覽器兼容性,更通用的方案是使用 Flash。而現在,localStorage 被大多數瀏覽器所支持,若是你的網站須要支持 IE6+,那以 userData 做爲你的 polyfill 的方案是種不錯的選擇。
sessionStorage 與 localStorage 的接口相似,但保存數據的生命週期與 localStorage 不一樣
。作事後端開發的同窗應該知道 Session 這個詞的意思,直譯過來是「會話
」。而 sessionStorage 是一個前端的概念,它只是能夠將一部分數據在當前會話中保存下來,刷新頁面數據依舊存在
。但當頁面關閉後,sessionStorage 中的數據就會被清空。
特性 | Cookie | localStorage | sessionStorage |
---|---|---|---|
數據的生命期 | 通常由服務器生成,可設置失效時間。若是在瀏覽器端生成Cookie,默認是關閉瀏覽器後失效 | 除非被清除,不然永久保存 | 僅在當前會話下有效,關閉頁面或瀏覽器後被清除 |
存放數據大小 | 4K左右 | 通常爲5MB | 通常爲5MB |
與服務器端通訊 | 每次都會攜帶在HTTP頭中,若是使用cookie保存過多數據會帶來性能問題 | 僅在客戶端(即瀏覽器)中保存,不參與和服務器的通訊 | 僅在客戶端(即瀏覽器)中保存,不參與和服務器的通訊 |
易用性 | 須要程序員本身封裝,源生的Cookie接口不友好 | 源生接口能夠接受,亦可再次封裝來對 Object和 Array有更好的支持 | 源生接口能夠接受,亦可再次封裝來對 Object和 Array有更好的支持 |
做用域大小 | 同域 | 同域 | 同域 |
數據結構 | key-value(字符創-字符串) | key-value | key-value |
有了對上面這些差異的直觀理解,咱們就能夠討論三者的應用場景了。
由於考慮到每一個 HTTP 請求都會帶着 Cookie 的信息,因此 Cookie 固然是能精簡就精簡啦,比較經常使用的一個應用場景就是判斷用戶是否登陸。針對登陸過的用戶,服務器端會在他登陸時往 Cookie 中插入一段加密過的惟一辨識單一用戶的辨識碼,下次只要讀取這個值就能夠判斷當前用戶是否登陸啦。曾經還使用 Cookie 來保存用戶在電商網站的購物車信息,現在有了 localStorage,彷佛在這個方面也能夠給 Cookie 放個假了~
而另外一方面 localStorage 接替了 Cookie 管理購物車的工做,同時也能勝任其餘一些工做。好比HTML5遊戲一般會產生一些本地數據,localStorage 也是很是適用的。若是遇到一些內容特別多的表單,爲了優化用戶體驗,咱們可能要把表單頁面拆分紅多個子頁面,而後按步驟引導用戶填寫。這時候 sessionStorage 的做用就發揮出來了。
localStorage 和 sessionStorage 有着統一的API接口,這爲兩者的操做提供了極大的便利。下面以 localStorage 爲例來介紹一下 API 接口使用方法,一樣這些接口也適用sessionStorage
添加鍵值對:localStorage.setItem(key, value)
setItem 用於把值 value 存儲到鍵key上,除了使用 setItem ,還可使用 localStorage.key = value
或者 localStorage['key'] = value
這兩種形式。 另外須要注意的是,key和value值必須是字符串形式的,若是不是字符串,會調用它們相應的toString() 方法來轉換成字符串再存儲。 當咱們要存儲對象是,應先轉換成咱們可識別的字符串格式(好比JSON格式)再進行存儲。
// 把一個用戶名 ( lilei )存儲到 name 的鍵上
localStorage.setItem('name', 'lilei');
// localStorage.name = 'lilei';
// localStorage['name'] = 'lilei';
// 把一個用戶存儲到user的鍵上
localStorage.setItem('user', JSON.stringify({id:1, name:'lilei'}));
複製代碼
獲取鍵值:localStorage.getItem(key)
getItem 用於獲取鍵 key 對應的數據,和 setItem 同樣,getItem 也有兩種等效形式 value = localStorage.key和 value = localStorage['key']
。 獲取到的 value 值是字符串類型,若是須要其餘類型,要作手動的類型轉換。
// 獲取存儲到 name 的鍵上的值
var name = localStorage.getItem('name');
// var name = localStorage.name;
// var name = localStorage['name'];
// 獲取存儲到user的鍵上的值
var user = JSON.parse(localStorage.getItem('user'));
複製代碼
刪除鍵值對:localStorage.removeItem(key)
removeItem 用於刪除指定鍵的項, localStorage 沒有數據過時
的概念,全部數據若是失效了,須要開發者手動刪除。
var name = localStorage.getItem('name'); // 'lilei'
// 刪除存儲到 name 的鍵上的值
localStorage.removeItem('name');
name = localStorage.getItem('name'); // null
複製代碼
清除全部鍵值對:localStorage.clear()clear 用於刪除全部存儲的內容, 它和 removeItem不一樣的地方是 removeItem 刪除的是某一項,而clear是刪除全部。
// 清除 localStorage
localStorage.clear();
var len = localStorage.length; // 0
複製代碼
獲取 localStorage 的屬性名稱(鍵名稱):localStorage.key(index)key 方法用於獲取指定索引的鍵名稱。須要注意的是賦值早的鍵值對應的索引值大,賦值晚的鍵值對應的索引小,
key方法可用於遍歷 localStorage 存儲的鍵值。
localStorage.setItem('name','lilei');
var key = localStorage.key(0); // 'name'
localStorage.setItem('age', 10);
key = localStorage.key(0); // 'age'
key = localStorage.key(1); // 'name'
複製代碼
獲取 localStorage 中保存的鍵值對的數量:localStorage.length length 屬性用於獲取 localStorage 中鍵值對的數量。
localStorage.setItem('name','lilei');
var len = localStorage.len; // 1
localStorage.setItem('age', 10);
len = localStorage.len; // 2
複製代碼
storage 事件當存儲的數據發生變化時,會觸發 storage 事件
。 但要注意的是它不一樣於click類的事件會事件捕獲和冒泡
,storage 事件更像是一個通知,不可取消。
觸發這個事件會調用同域
下其餘窗口的storage事件,不過觸發storage的窗口(即當前窗口)不觸發這個事件(chrome 好像能夠❤️)
🚥(可用於多頁面通訊)
storage 的 event
對象的經常使用屬性以下:
oldValue:更新前的值。若是該鍵爲新增長,則這個屬性爲null。
newValue:更新後的值。若是該鍵被刪除,則這個屬性爲null。
url:原始觸發storage事件的那個網頁的網址。
key:存儲 store的 key名
複製代碼
function storageChanged(/*event */) {
console.log(arguments);
}
window.addEventListener('storage', storageChanged, false);
複製代碼
IndexedDB 能夠存儲很是多的數據,好比 Object,files,blobs 等,裏面的存儲結構是根據 Database 來進行存儲的。每一個 DB 裏面能夠有不一樣的 object stores。
概述
1.瀏覽器查找域名的 IP 地址
2.這一步包括 DNS 具體的查找過程,包括:瀏覽器緩存->系統緩存->路由器緩存>IP 根服務器>找到指定Ip
3.瀏覽器向 web 服務器發送一個 HTTP 請求
4.服務器的永久重定向響應(從 http://example.com 到 http://www.example.com)
5.瀏覽器跟蹤重定向地址
6.服務器處理請求
7.服務器返回一個 HTTP 響應
8.瀏覽器顯示 HTML
9.瀏覽器發送請求獲取嵌入在 HTML 中的資源(如圖片、音頻、視頻、CSS、JS等等)
10.瀏覽器發送異步請求
複製代碼
HTTP 請求返回的 HTML 傳遞到瀏覽器後,若是有 gzip 會先解壓,而後接下來最重要的問題是要知道它的編碼是什麼,好比一樣一個「中」字,在 UTF-8 編碼下它的內容實際上是「11100100 10111000 10101101」也就是「E4 B8 AD」,而在 GBK 下則是「11010110 11010000」,也就是「D6 D0」,如何才能知道文件的編碼?能夠有不少判斷方法:
<meta>
中的 charset 屬性值若是在這些地方都沒指明,瀏覽器就很難處理,在它看來就是一堆「0」和「1」,好比「中文」,它在 UTF-8 下有 6 個字節,若是按照 GBK 能夠當成「涓枃」這 3 個漢字來解釋,瀏覽器怎麼知道究竟是「中文」仍是「涓枃」呢?
不過正常人一眼就能認出「涓枃」是錯的,由於這 3 個字太不常見了,因此有人就想到經過判斷常見字的方法來檢測編碼,典型的好比 Mozilla 的 UniversalCharsetDetection,不過這東東誤判率也很高,因此仍是指明編碼的好。
這樣後續對文本的操做就是基於「字符」(Character)的了,一個漢字就是一個字符,不用再關心它到底是 2 個字節仍是 3 個字節。
BOM 爲瀏覽器窗口對象的一組 API。
用於操做瀏覽器的API
⚠️經過window 或者省略window 或者document 或者 event 鼠標事件
BOM是 browser object model的縮寫,簡稱瀏覽器對象模型
BOM提供了獨立於內容而與瀏覽器窗口進行交互的對象
因爲BOM主要用於管理窗口與窗口之間的通信,所以其核心對象是window
BOM由一系列相關的對象構成,而且每一個對象都提供了不少方法與屬性
BOM缺少標準,JavaScript語法的標準化組織是 ECMA,DOM的標準化組織是 W3C
BOM最初是 Netscape 瀏覽器標準的一部分
複製代碼
屬性
navigator
location
方法
history
方法
screen
Window 方法
Window 事件
複製代碼
window.onload 和 document.onload
document.onload 是dom 解析完成就會觸發
window.onload
複製代碼
1. window.history
操縱瀏覽器記錄
history.back(); // 等同於點擊瀏覽器的回退按鈕
history.go(-1); //等同於history.back()
2.🐯 window.innerHeight/innerWidth vs window.outerWidth/outerHeight
瀏覽器窗口的視口(viewport)高寬 vs 獲取完整窗口大小:
3. 🐯window.location
操做刷新按鈕和地址欄
設置或取得當前 URL 的主機名稱和端口
`http://www.baidu.com:80/qs/ans` // http://www.baidu.com:80
5. location.pathname [string]
設置或取得當前 URL 的路徑部分
🐯6. location.search [string]
查詢或設置當前URL的?號開始的字符串
🐯7.location.href
返回整個URL
8. location.hash
返回或設置#號開始的部分
9. location.origin
返回當前域名
10. window.navigator
只讀對象
11. window.screen
返回有關屏幕大小寬高以及顏色深度等信息
screen.availWidth | 可用的屏幕寬度
screen.availHeight | 可用的屏幕寬度
screen.width | 當前屏幕寬度 (分辨率)
screen.height | 當前屏幕高度 (分辨率值)
screen.colorDepth | 當前屏幕色彩深度(bit)
12. navigator.userAgent
得到HTTP請求的用戶帶頭的值
13. window.self
即window 儘可能少使用全局變量!!
14. window.top
返回最頂層頁面
15. window.resizeTo(width,height)
resizeBy(width/height)增量
16. event 事件位置
參照屏幕左上角: e.screenX, e.screenY
參照文檔顯示區左上角: e.clientX||e.x , e.clientY||e.y
參照所在父元素的左上角: e.offsetX , e.offsetY
17. location.replace(「url」) 當前頁面打開不可後退⚠️
location.replace vs history.replaceState⚠️
18. ⚠️從新加載當前頁面: location.reload(false/true/'') location.reload()
document.location.reload() vs location.reload()
19.定時器一次性: clearInterval(timer);time=null
定時器週期性: clearTimeout(timer);time=null
20. ️[btn/window].addEventListener(「事件名」,函數對象[命名函數, 匿名函數沒法取消會形成泄漏])
21. ️[btn/window].remove(「事件名」,函數對象[命名函數, 匿名函數沒法取消會形成泄漏])
22. 阻止冒泡:
e.stopPropagation(); e.cancelBubble[ie⚠️]
23. 取消事件(阻止默認行爲):
e.preventDefault
24. 建立cookie:
document.cookie="變量名=值;expires="+new Date().toGMTString();
複製代碼
http://www.github.com:8080/index.html?user=li-xinyang&lang=zh-CN#home
| | | | | |
protocol | | | | |
hostname port | | |
\ / pathname search hash
host
4. location.host [string]
複製代碼
方法 | 方法描述 |
---|---|
alert(), confirm() 返回真假, prompt() 返回用戶輸入值 | 三種對話框 |
setTimeout(), setInterval() | 計時器 |
open(), | close() |
::: tip 提示 NOTE:對話框會阻塞線程。
:::
打開或關閉窗口
var w = window.open('subwindow.html', 'subwin', 'width=300, height=300, status=yes, resizable=yes');
// 既可關閉窗口
w.close();
複製代碼
方法 | 方法描述 |
---|---|
load | 文檔和全部圖片完成加載時 |
unload | 離開當前文檔時 |
beforeunload | 和 unload 相似,可是它提供詢問用戶是否確認離開的機會 |
resize | 拖動改變瀏覽器窗口大小時 |
scroll | 拖動瀏覽器時 |
專用事件: window對象-事件
onload
事件:咱們須要在網頁文檔下載完畢(全部資源)
時執行的程序代碼,須要放在onload事件處理程序中去編寫。
onunload
事件:在網頁文檔卸載
完畢後,須要在網頁關閉時執行
的程序代碼,須要放在onunload事件處理程序中去編寫。
onbeforeunload
事件:對於瀏覽器準備卸載網頁文件時的事件,也就是onbeforeunload事件是發生在卸載網頁文件以前的事件,給用戶提供一個取消的機會。
樣例:
<body onload="alert('歡迎')" onunload="alert('再見!')" onbeforeunload="window.event.returnValue='請當心'">
</body>
複製代碼
效果:打開網頁,彈出一個「歡迎」的對話框,點擊右上角的叉叉,彈出是否關閉網頁的對話框,選擇「肯定」,網頁關閉,彈出一個「再見」的對話框。
// 測試javascript語句的執行順序:
<script>
alert("ok");
</script>
<body onload="alert('歡迎')" onunload="alert('再見!')"
onbeforeunload="window.event.returnValue='請當心'">
Hello
<script>
alert("ok2");
</script>
</body>
<script>
alert("ok3");
</script>
複製代碼
這個例子說明了,解析器是一邊讀文檔
,一邊解析裏面的內容
。而onload事件發生在瀏覽器將程序裝載完才發生
的事件。 若是網頁文檔很是大,裝載的時間很是長,爲了緩解用戶等待的焦急心情,能夠在開始放一個script
語句塊,顯示"正在下載"
,而後在中間的script語句塊中放一個定時器,顯示下載進度
。在最後顯示裝在完畢的信息。
通用事件: 鼠標和按鍵
onclick事件
:它是當用戶點擊某個Html元素時產生的事件
。咱們在 form表單的元素中常用到 onclick事件。
onmousemove
事件:它是當鼠標在某個html元素上移動
的時候所產生的事件,這個事件伴隨着鼠標的移動而不斷的重複發生。若是咱們要隨着鼠標的移動而不斷顯示出鼠標的新座標位置,就應該處理這個事件。
onmouseover事件
:它是指鼠標剛從外面移進到一個html元素的邊界
時,所產生的事件。除非鼠標又移出html元素且再移入,不然這個事件不會重複發生。
onmouseout事件
:它是當鼠標移出一個html元素的邊界時
所產生的事件。
onmousedown事件
:當鼠標的任何按鍵按下去
的時候產生的事件。
onmouseup事件
:當鼠標的任何按鍵彈起去
的時候產生的事件。
onkeydown
:當鍵盤的任何按鍵按下
去的時候產生的事件。
onkeyup事件
:當鍵盤的任何按鍵彈起的時候
產生的事件。
onkeypress事件
:當用戶按下一個數字或字母按鍵
所產生的事件。按某個鍵是指"按下彈起"。
TBD....
原文連接 劼哥stone ::: tip NOTICE 《JavaScript 闖關記》之垃圾回收和內存管理 JavaScript 具備自動垃圾收集機制(GC:Garbage Collection),也就是說,執行環境會負責管理代碼執行過程當中使用的內存。而在 C 和 C++ 之類的語言中,開發人員的一項基本任務就是手工跟蹤內存的使用狀況,這是形成許多問題的一個根源。
在編寫 JavaScript 程序時,開發人員不用再關心內存使用問題,所需內存的分配以及無用內存的回收徹底實現了自動管理。這種垃圾收集機制的原理其實很簡單:找出那些再也不繼續使用的變量,而後釋放其佔用的內存。
爲此,垃圾收集器會按照固定的時間間隔(或代碼執行中預約的收集時間),週期性地執行這一操做。
正由於垃圾回收器的存在,許多人認爲 JavaScript 不用太關心內存管理的問題,但若是不瞭解 JavaScript 的內存管理機制,咱們一樣很是容易成內存泄漏(內存沒法被回收)的狀況。 :::
// 1.對象
new Object();
new MyConstructor();
{ a: 4, b: 5 }
Object.create()
複製代碼
// 2.數組
new Array()
[ 1, 2, 3, 4 ]
複製代碼
// 3.字符串,JavaScript 的字符串和 .NET 同樣,使用資源池和 copy on write 方式管理字符串。
new String("hello hyddd")
"<p>" + e.innerHTML + "</p>"
複製代碼
// 4.函數
var x = function () { ... }
new Function(code)
複製代碼
// 5.閉包
function outer(name) {
var x = name
return function inner() {
return ", " + name
}
}
複製代碼
下面咱們來分析一下函數中局部變量的正常生命週期。
一般,很容易判斷變量是否還有存在的必要,但並不是全部狀況下都這麼容易就能得出結論(例如:使用閉包的時)。垃圾收集器必須跟蹤哪一個變量有用哪一個變量沒用,對於再也不有用的變量打上標記,以備未來收回其佔用的內存。用於標識無用變量的策略可能會因實現而異,但具體到瀏覽器中的實現,則一般有兩個策略:標記清除 和 引用計數。
JavaScript 中最經常使用的垃圾收集方式是 標記清除(mark-and-sweep
)。當變量進入環境(例如,在函數中聲明一個變量)時,就將這個變量標記爲「進入環境」
。從邏輯上講,永遠不能釋放進入環境的變量所佔用的內存,由於只要執行流進入相應的環境,就可能會用到它們。而當變量離開環境時,則將其標記爲「離開環境」
。
function test(){
var a = 10 ; // 被標記 ,進入環境
var b = 20 ; // 被標記 ,進入環境
}
test(); // 執行完畢 以後 a、b又被標離開環境,被回收
複製代碼
垃圾回收器在運行的時候會給存儲在內存中的全部變量都加上標記
(固然,可使用任何標記方式)。而後,它會去掉環境中的變量以及被環境中的變量引用的變量的標記
(例如,閉包)。而在此以後再被加上標記的變量將被視爲準備刪除的變量,緣由是環境中的變量已經沒法訪問到這些變量了
。最後,垃圾回收器完成內存清除工做,銷燬那些帶標記的值並回收它們所佔用的內存空間
。
這種方式的主要缺點就是若是某些對象被清理後,內存是不連續的,那麼就算內存佔用率不高,例如只有50%,可是因爲內存空隙太多,後來的大對象甚至沒法存儲到內存之中。
通常的處理方式都是在垃圾回收後進行整理操做,這種方法也叫 標記整理,整理的過程就是將不連續的內存向一端複製,使不連續的內存連續起來。
目前,IE9+、Firefox、Opera、Chrome 和 Safari 的 JavaScript 實現使用的都是 標記清除 式的垃圾收集策略(或相似的策略),只不過垃圾收集的時間間隔互有不一樣。
另外一種不太常見的垃圾收集策略叫作 引用計數(reference counting
)。引用計數的含義是跟蹤記錄每一個值被引用的次數。當聲明瞭一個變量並將一個引用類型值賦給該變量時,則這個值的引用次數就是1。若是同一個值又被賦給另外一個變量,則該值的引用次數加1。相反,若是包含對這個值引用的變量又取得了另一個值,則這個值的引用次數減1。當這個值的引用次數變成0時,則說明沒有辦法再訪問這個值了,於是就能夠將其佔用的內存空間回收回來。
這樣,當垃圾收集器下次再運行時,它就會釋放那些引用次數爲零的值所佔用的內存。
function test(){
var a = {} ; // a的引用次數爲0
var b = a ; // a的引用次數加1,爲1
var c = a; // a的引用次數再加1,爲2
var b = {}; // a的引用次數減1,爲1
}
複製代碼
早期不少瀏覽器使用引用計數策略,但很快它就遇到了一個嚴重的問題:循環引用。循環引用指的是對象 A 中包含一個指向對象 B 的指針,而對象 B 中也包含一個指向對象 A 的引用。請看下面這個例子:
function problem(){
var objectA = new Object();
var objectB = new Object();
objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}
複製代碼
在這個例子中,objectA 和 objectB 經過各自的屬性相互引用;也就是說,這兩個對象的引用次數都是2。在採用 標記清除 策略的實現中,因爲函數執行以後,這兩個對象都離開了做用域,所以這種相互引用不是個問題。但在採用 引用計數 策略的實現中,當函數執行完畢後,objectA 和 objectB 還將繼續存在,由於它們的引用次數永遠不會是0。假如這個函數被重複屢次調用,就會致使大量內存得不到回收。爲此,新一代瀏覽器都放棄了引用計數方式,轉而採用標記清除來實現其垃圾收集機制。但是,引用計數致使的麻煩並未就此終結。
咱們知道,IE 中有一部分對象並非原生 JavaScript 對象。例如,其 BOM 和 DOM 中的對象就是使用 C++ 以 COM(Component Object Model,組件對象模型)對象的形式實現的,而 COM 對象的垃圾收集機制採用的就是引用計數策略。所以,即便 IE 的 JavaScript 引擎是使用標記清除策略來實現的,但 JavaScript 訪問的 COM 對象依然是基於引用計數策略的。換句話說,只要在 IE 中涉及 COM 對象,就會存在循環引用的問題。下面這個簡單的例子,展現了使用 COM 對象致使的循環引用問題:
var element = document.getElementById("some_element");
var myObject = new Object();
myObject.element = element;
element.someObject = myObject;
複製代碼
這個例子在一個 DOM 元素(element)與一個原生 JavaScript 對象(myObject)之間建立了循環引用。其中,變量 myObject 有一個名爲 element 的屬性指向 element 對象;而變量 element 也有一個屬性名叫 someObject 回指 myObject。因爲存在這個循環引用,即便將例子中的 DOM 從頁面中移除,它也永遠不會被回收。
爲了不相似這樣的循環引用問題,最好是在不使用它們的時候手工斷開原生 JavaScript 對象與 DOM 元素之間的鏈接。例如,可使用下面的代碼消除前面例子建立的循環引用:
myObject.element = null;
element.someObject = null
複製代碼
將變量設置爲 null 意味着切斷變量與它此前引用的值之間的鏈接。當垃圾收集器下次運行時,就會刪除這些值並回收它們佔用的內存。
爲了解決上述問題,IE9 把 BOM 和 DOM 對象都轉換成了真正的 JavaScript 對象。這樣,就避免了兩種垃圾收集算法並存致使的問題,也消除了常見的內存泄漏現象。
IE6 的垃圾回收是根據內存分配量運行的,當環境中存在256個變量、4096個對象、64k的字符串任意一種狀況的時候就會觸發垃圾回收器工做,看起來很科學,不用按一段時間就調用一次,有時候會不必,這樣按需調用不是很好嗎?可是若是環境中就是有這麼多變量等一直存在,如今腳本如此複雜,那麼垃圾回收器會一直工做,這樣瀏覽器就無法兒玩兒了。
微軟在 IE7 中作了調整,觸發條件再也不是固定的,而是動態修改的,初始值和 IE6 相同,若是垃圾回收器回收的內存分配量低於程序佔用內存的15%,說明大部份內存不可被回收,設的垃圾回收觸發條件過於敏感,這時候把臨界條件翻倍,若是回收的內存高於85%,說明大部份內存早就該清理了,這時候則將各類臨界值重置回默認值。這一看似簡單的調整,極大地提高了 IE7 在運行包含大量 JavaScript 的頁面時的性能。
使用具有垃圾收集機制的語言編寫程序,開發人員通常沒必要操心內存管理的問題。可是,JavaScript 在進行內存管理及垃圾收集時面臨的問題仍是有點不同凡響。其中最主要的一個問題,就是分配給 Web 瀏覽器的可用內存數量一般要比分配給桌面應用程序的少。這樣作的目的主要是出於安全方面的考慮,目的是防止運行 JavaScript 的網頁耗盡所有系統內存而致使系統崩潰。內存限制問題不只會影響給變量分配內存,同時還會影響調用棧以及在一個線程中可以同時執行的語句數量。
所以,確保佔用最少的內存可讓頁面得到更好的性能。而優化內存佔用的最佳方式,就是爲執行中的代碼只保存必要的數據。一旦數據再也不有用,最好經過將其值設置爲 null 來釋放其引用——這個作法叫作 解除引用(dereferencing)。
這一作法適用於大多數全局變量和全局對象的屬性。局部變量會在它們離開執行環境時自動被解除引用,以下面這個例子所示:
function createPerson(name){
var localPerson = new Object();
localPerson.name = name;
return localPerson;
}
var globalPerson = createPerson("Nicholas");
// 手工解除globalPerson的引用
globalPerson = null;
複製代碼
因爲局部變量 localPerson 在 createPerson() 函數執行完畢後就離開了其執行環境, 所以無需咱們顯式地去爲它解除引用。可是對於全局變量 globalPerson 而言,則須要咱們在不使用它的時候手工爲它解除引用, 這也正是上面例子中最後一行代碼的目的。
不過,解除一個值的引用並不意味着自動回收該值所佔用的內存。解除引用的真正做用是讓值脫離執行環境,以便垃圾收集器下次運行時將其回收。
和其餘語言同樣,JavaScript 的垃圾回收策略也沒法避免一個問題:垃圾回收時,會中止響應其餘操做
, 這是爲了安全考慮。而 JavaScript 的垃圾回收在 100ms
甚至以上,對通常的應用還好,但對於 JavaScript 遊戲和動畫, 這種對連貫性要求比較高的應用,就麻煩了。這就是新引擎須要優化的點:避免垃圾回收形成的長時間中止響應
。
David 大叔主要介紹了2個優化方案,而這也是最主要的2個優化方案了:
這個和 Java 回收策略思想是一致的。目的是經過區分「臨時」與「持久」對象
;多回收「臨時對象區」(young generation), 少回收「持久對象區」(tenured generation),減小每次需遍歷的對象,從而減小每次GC的耗時。Chrome 瀏覽器所使用的 V8 引擎就是採用的分代回收策略。如圖:
這個方案的思想很簡單,就是「每次處理一點,下次再處理一點,如此類推」。這種方案,雖然耗時短,但中斷較多,帶來了上下文切換頻繁的問題。 Firefox 瀏覽器所使用的 JavaScript 引擎就是採用的增量回收策略。如圖:
由於每種方案都其適用場景和缺點,所以在實際應用中,會根據實際狀況選擇方案。例如:若是大量對象都是長期「存活」,則分代處理優點也不大。
查看 Chrome 瀏覽器下的 CG 過程
使用快捷鍵 F12 或者 Ctrl+Shift+J 打開 Chrome 瀏覽器的「開發者工具」。
選擇 Timeline 選項卡,在 Capture 選項中,只勾選 Memory。
設置完成後,點擊最左邊的 Record 按鈕,而後就能夠訪問網頁了。
複製代碼
打開一個網站,例如:www.taobao.com,當網頁加載完成後,點擊 Stop,等待分析結果。
而後在 Chart View 上尋找內存急速降低的部分,查看對應的 Event Log,能夠從中找到 GC 的日誌。 具體過程以下圖所示: