摘要:決定了注冊的事件是捕獲事件還是冒泡事件。瀏覽器會自動進行通信,實現通信的關鍵是后端。該方式只能用于二級域名相同的情況下,比如和適用于該方式。中的中的和瀏覽器中的不相同。
事件機制 事件觸發三階段
事件觸發有三個階段
window 往事件觸發處傳播,遇到注冊的捕獲事件會觸發
傳播到事件觸發處時觸發注冊的事件
從事件觸發處往 window 傳播,遇到注冊的冒泡事件會觸發
事件觸發一般來說會按照上面的順序進行,但是也有特例,如果給一個目標節點同時注冊冒泡和捕獲事件,事件觸發會按照注冊的順序執行。
// 以下會先打印冒泡然后是捕獲 node.addEventListener("click",(event) =>{ console.log("冒泡") },false); node.addEventListener("click",(event) =>{ console.log("捕獲 ") },true)注冊事件
通常我們使用 addEventListener 注冊事件,該函數的第三個參數可以是布爾值,也可以是對象。對于布爾值 useCapture 參數來說,該參數默認值為 false 。useCapture 決定了注冊的事件是捕獲事件還是冒泡事件。對于對象參數來說,可以使用以下幾個屬性
capture,布爾值,和 useCapture 作用一樣
once,布爾值,值為 true 表示該回調只會調用一次,調用后會移除監聽
passive,布爾值,表示永遠不會調用 preventDefault
一般來說,我們只希望事件只觸發在目標上,這時候可以使用 stopPropagation 來阻止事件的進一步傳播。通常我們認為 stopPropagation 是用來阻止事件冒泡的,其實該函數也可以阻止捕獲事件。stopImmediatePropagation 同樣也能實現阻止事件,但是還能阻止該事件目標執行別的注冊事件。
node.addEventListener("click",(event) =>{ event.stopImmediatePropagation() console.log("冒泡") },false); // 點擊 node 只會執行上面的函數,該函數不會執行 node.addEventListener("click",(event) => { console.log("捕獲 ") },true)事件代理
如果一個節點中的子節點是動態生成的,那么子節點需要注冊事件的話應該注冊在父節點上
事件代理的方式相對于直接給目標注冊事件來說,有以下優點
節省內存
不需要給子節點注銷事件
跨域因為瀏覽器出于安全考慮,有同源策略。也就是說,如果協議、域名或者端口有一個不同就是跨域,Ajax 請求會失敗。
我們可以通過以下幾種常用方法解決跨域的問題
JSONPJSONP 的原理很簡單,就是利用 標簽沒有跨域限制的漏洞。通過 標簽指向一個需要訪問的地址并提供一個回調函數來接收數據當需要通訊時。
JSONP 使用簡單且兼容性不錯,但是只限于 get 請求。
在開發中可能會遇到多個 JSONP 請求的回調函數名是相同的,這時候就需要自己封裝一個 JSONP,以下是簡單實現
function jsonp(url, jsonpCallback, success) { let script = document.createElement("script"); script.src = url; script.async = true; script.type = "text/javascript"; window[jsonpCallback] = function(data) { success && success(data); }; document.body.appendChild(script); } jsonp( "http://xxx", "callback", function(value) { console.log(value); } );CORS
CORS需要瀏覽器和后端同時支持。IE 8 和 9 需要通過 XDomainRequest 來實現。
瀏覽器會自動進行 CORS 通信,實現CORS通信的關鍵是后端。只要后端實現了 CORS,就實現了跨域。
服務端設置 Access-Control-Allow-Origin 就可以開啟 CORS。 該屬性表示哪些域名可以訪問資源,如果設置通配符則表示所有網站都可以訪問資源。
document.domain該方式只能用于二級域名相同的情況下,比如 a.test.com 和 b.test.com 適用于該方式。
只需要給頁面添加 document.domain = "test.com" 表示二級域名都相同就可以實現跨域
postMessage這種方式通常用于獲取嵌入頁面中的第三方頁面數據。一個頁面發送消息,另一個頁面判斷來源并接收消息
// 發送消息端 window.parent.postMessage("message", "http://test.com"); // 接收消息端 var mc = new MessageChannel(); mc.addEventListener("message", (event) => { var origin = event.origin || event.originalEvent.origin; if (origin === "http://test.com") { console.log("驗證通過") } });Event loop
眾所周知 JS 是門非阻塞單線程語言,因為在最初 JS 就是為了和瀏覽器交互而誕生的。如果 JS 是門多線程的語言話,我們在多個線程中處理 DOM 就可能會發生問題(一個線程中新加節點,另一個線程中刪除節點),當然可以引入讀寫鎖解決這個問題。
JS 在執行的過程中會產生執行環境,這些執行環境會被順序的加入到執行棧中。如果遇到異步的代碼,會被掛起并加入到 Task(有多種 task) 隊列中。一旦執行棧為空,Event Loop 就會從 Task 隊列中拿出需要執行的代碼并放入執行棧中執行,所以本質上來說 JS 中的異步還是同步行為。
console.log("script start"); setTimeout(function() { console.log("setTimeout"); }, 0); console.log("script end");
以上代碼雖然 setTimeout 延時為 0,其實還是異步。這是因為 HTML5 標準規定這個函數第二個參數不得小于 4 毫秒,不足會自動增加。所以 setTimeout 還是會在 script end 之后打印。
不同的任務源會被分配到不同的 Task 隊列中,任務源可以分為 微任務(microtask) 和 宏任務(macrotask)。在 ES6 規范中,microtask 稱為 jobs,macrotask 稱為 task。
console.log("script start"); setTimeout(function() { console.log("setTimeout"); }, 0); new Promise((resolve) => { console.log("Promise") resolve() }).then(function() { console.log("promise1"); }).then(function() { console.log("promise2"); }); console.log("script end"); // script start => Promise => script end => promise1 => promise2 => setTimeout
以上代碼雖然 setTimeout 寫在 Promise 之前,但是因為 Promise 屬于微任務而 setTimeout 屬于宏任務,所以會有以上的打印。
微任務包括 process.nextTick ,promise ,Object.observe ,MutationObserver
宏任務包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering
很多人有個誤區,認為微任務快于宏任務,其實是錯誤的。因為宏任務中包括了 script ,瀏覽器會先執行一個宏任務,接下來有異步代碼的話就先執行微任務。
所以正確的一次 Event loop 順序是這樣的
執行同步代碼,這屬于宏任務
執行棧為空,查詢是否有微任務需要執行
執行所有微任務
必要的話渲染 UI
然后開始下一輪 Event loop,執行宏任務中的異步代碼
通過上述的 Event loop 順序可知,如果宏任務中的異步代碼有大量的計算并且需要操作 DOM 的話,為了更快的 界面響應,我們可以把操作 DOM 放入微任務中。
Node 中的 Event loopNode 中的 Event loop 和瀏覽器中的不相同。
Node 的 Event loop 分為6個階段,它們會按照順序反復運行
┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<──connections─── │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘timer
timers 階段會執行 setTimeout 和 setInterval
一個 timer 指定的時間并不是準確時間,而是在達到這個時間后盡快執行回調,可能會因為系統正在執行別的事務而延遲。
下限的時間有一個范圍:[1, 2147483647] ,如果設定的時間不在這個范圍,將被設置為1。
I/OI/O 階段會執行除了 close 事件,定時器和 setImmediate 的回調
idle, prepareidle, prepare 階段內部實現
pollpoll 階段很重要,這一階段中,系統會做兩件事情
執行到點的定時器
執行 poll 隊列中的事件
并且當 poll 中沒有定時器的情況下,會發現以下兩件事情
如果 poll 隊列不為空,會遍歷回調隊列并同步執行,直到隊列為空或者系統限制
如果 poll 隊列為空,會有兩件事發生
如果有 setImmediate 需要執行,poll 階段會停止并且進入到 check 階段執行 setImmediate
如果沒有 setImmediate 需要執行,會等待回調被加入到隊列中并立即執行回調
如果有別的定時器需要被執行,會回到 timer 階段執行回調。
checkcheck 階段執行 setImmediate
close callbacksclose callbacks 階段執行 close 事件
并且在 Node 中,有些情況下的定時器執行順序是隨機的
setTimeout(() => { console.log("setTimeout"); }, 0); setImmediate(() => { console.log("setImmediate"); }) // 這里可能會輸出 setTimeout,setImmediate // 可能也會相反的輸出,這取決于性能 // 因為可能進入 event loop 用了不到 1 毫秒,這時候會執行 setImmediate // 否則會執行 setTimeout
當然在這種情況下,執行順序是相同的
var fs = require("fs") fs.readFile(__filename, () => { setTimeout(() => { console.log("timeout"); }, 0); setImmediate(() => { console.log("immediate"); }); }); // 因為 readFile 的回調在 poll 中執行 // 發現有 setImmediate ,所以會立即跳到 check 階段執行回調 // 再去 timer 階段執行 setTimeout // 所以以上輸出一定是 setImmediate,setTimeout
上面介紹的都是 macrotask 的執行情況,microtask 會在以上每個階段完成后立即執行。
setTimeout(()=>{ console.log("timer1") Promise.resolve().then(function() { console.log("promise1") }) }, 0) setTimeout(()=>{ console.log("timer2") Promise.resolve().then(function() { console.log("promise2") }) }, 0) // 以上代碼在瀏覽器和 node 中打印情況是不同的 // 瀏覽器中一定打印 timer1, promise1, timer2, promise2 // node 中可能打印 timer1, timer2, promise1, promise2 // 也可能打印 timer1, promise1, timer2, promise2
Node 中的 process.nextTick 會先于其他 microtask 執行。
setTimeout(() => { console.log("timer1"); Promise.resolve().then(function() { console.log("promise1"); }); }, 0); process.nextTick(() => { console.log("nextTick"); }); // nextTick, timer1, promise1存儲 cookie,localStorage,sessionStorage,indexDB
特性 | cookie | localStorage | sessionStorage | indexDB |
---|---|---|---|---|
數據生命周期 | 一般由服務器生成,可以設置過期時間 | 除非被清理,否則一直存在 | 頁面關閉就清理 | 除非被清理,否則一直存在 |
數據存儲大小 | 4K | 5M | 5M | 無限 |
與服務端通信 | 每次都會攜帶在 header 中,對于請求性能影響 | 不參與 | 不參與 | 不參與 |
從上表可以看到,cookie 已經不建議用于存儲。如果沒有大量數據存儲需求的話,可以使用 localStorage 和 sessionStorage 。對于不怎么改變的數據盡量使用 localStorage 存儲,否則可以用 sessionStorage 存儲。
對于 cookie,我們還需要注意安全性。
屬性 | 作用 |
---|---|
value | 如果用于保存用戶登錄態,應該將該值加密,不能使用明文的用戶標識 |
http-only | 不能通過 JS 訪問 Cookie,減少 XSS 攻擊 |
secure | 只能在協議為 HTTPS 的請求中攜帶 |
same-site | 規定瀏覽器不能在跨域請求中攜帶 Cookie,減少 CSRF 攻擊 |
Service workers 本質上充當Web應用程序與瀏覽器之間的代理服務器,也可以在網絡可用時作為瀏覽器和網絡間的代理。它們旨在(除其他之外)使得能夠創建有效的離線體驗,攔截網絡請求并基于網絡是否可用以及更新的資源是否駐留在服務器上來采取適當的動作。他們還允許訪問推送通知和后臺同步API。
目前該技術通常用來做緩存文件,提高首屏速度,可以試著來實現這個功能。
// index.js if (navigator.serviceWorker) { navigator.serviceWorker .register("sw.js") .then(function(registration) { console.log("service worker 注冊成功"); }) .catch(function(err) { console.log("servcie worker 注冊失敗"); }); } // sw.js // 監聽 `install` 事件,回調中緩存所需文件 self.addEventListener("install", e => { e.waitUntil( caches.open("my-cache").then(function(cache) { return cache.addAll(["./index.html", "./index.js"]); }) ); }); // 攔截所有請求事件 // 如果緩存中已經有請求的數據就直接用緩存,否則去請求數據 self.addEventListener("fetch", e => { e.respondWith( caches.match(e.request).then(function(response) { if (response) { return response; } console.log("fetch source"); }) ); });
打開頁面,可以在開發者工具中的 Application 看到 Service Worker 已經啟動了
在 Cache 中也可以發現我們所需的文件已被緩存
當我們重新刷新頁面可以發現我們緩存的數據是從 Service Worker 中讀取的
渲染機制瀏覽器的渲染機制一般分為以下幾個步驟
處理 HTML 并構建 DOM 樹。
處理 CSS 構建 CSSOM 樹。
將 DOM 與 CSSOM 合并成一個渲染樹。
根據渲染樹來布局,計算每個節點的位置。
調用 GPU 繪制,合成圖層,顯示在屏幕上。
在構建 CSSOM 樹時,會阻塞渲染,直至 CSSOM 樹構建完成。并且構建 CSSOM 樹是一個十分消耗性能的過程,所以應該盡量保證層級扁平,減少過度層疊,越是具體的 CSS 選擇器,執行速度越慢。
當 HTML 解析到 script 標簽時,會暫停構建 DOM,完成后才會從暫停的地方重新開始。也就是說,如果你想首屏渲染的越快,就越不應該在首屏就加載 JS 文件。并且 CSS 也會影響 JS 的執行,只有當解析完樣式表才會執行 JS,所以也可以認為這種情況下,CSS 也會暫停構建 DOM。
Load 和 DOMContentLoaded 區別Load 事件觸發代表頁面中的 DOM,CSS,JS,圖片已經全部加載完畢。
DOMContentLoaded 事件觸發代表初始的 HTML 被完全加載和解析,不需要等待 CSS,JS,圖片加載。
圖層一般來說,可以把普通文檔流看成一個圖層。特定的屬性可以生成一個新的圖層。不同的圖層渲染互不影響,所以對于某些頻繁需要渲染的建議多帶帶生成一個新圖層,提高性能。但也不能生成過多的圖層,會引起反作用。
通過以下幾個常用屬性可以生成新圖層
3D 變換:translate3d、translateZ
will-change
video、iframe 標簽
通過動畫實現的 opacity 動畫轉換
position: fixed
重繪(Repaint)和回流(Reflow)重繪和回流是渲染步驟中的一小節,但是這兩個步驟對于性能影響很大。
重繪是當節點需要更改外觀而不會影響布局的,比如改變 color 就叫稱為重繪
回流是布局或者幾何屬性需要改變就稱為回流。
回流必定會發生重繪,重繪不一定會引發回流。回流所需的成本比重繪高的多,改變深層次的節點很可能導致父節點的一系列回流。
所以以下幾個動作可能會導致性能問題:
改變 window 大小
改變字體
添加或刪除樣式
文字改變
定位或者浮動
盒模型
很多人不知道的是,重繪和回流其實和 Event loop 有關。
當 Event loop 執行完 Microtasks 后,會判斷 document 是否需要更新。因為瀏覽器是 60Hz 的刷新率,每 16ms 才會更新一次。
然后判斷是否有 resize 或者 scroll ,有的話會去觸發事件,所以 resize 和 scroll 事件也是至少 16ms 才會觸發一次,并且自帶節流功能。
判斷是否觸發了 media query
更新動畫并且發送事件
判斷是否有全屏操作事件
執行 requestAnimationFrame 回調
執行 IntersectionObserver 回調,該方法用于判斷元素是否可見,可以用于懶加載上,但是兼容性不好
更新界面
以上就是一幀中可能會做的事情。如果在一幀中有空閑時間,就會去執行 requestIdleCallback 回調。
以上內容來自于 HTML 文檔
減少重繪和回流
使用 translate 替代 top
使用 visibility 替換 display: none ,因為前者只會引起重繪,后者會引發回流(改變了布局)
把 DOM 離線后修改,比如:先把 DOM 給 display:none (有一次 Reflow),然后你修改100次,然后再把它顯示出來
不要把 DOM 結點的屬性值放在一個循環里當成循環里的變量
for(let i = 0; i < 1000; i++) { // 獲取 offsetTop 會導致回流,因為需要去獲取正確的值 console.log(document.querySelector(".test").style.offsetTop) }
不要使用 table 布局,可能很小的一個小改動會造成整個 table 的重新布局
動畫實現的速度的選擇,動畫速度越快,回流次數越多,也可以選擇使用 requestAnimationFrame
CSS 選擇符從右往左匹配查找,避免 DOM 深度過深
將頻繁運行的動畫變為圖層,圖層能夠阻止該節點回流影響別的元素。比如對于 video 標簽,瀏覽器會自動將該節點變為圖層。
文章來源:
https://github.com/InterviewMap/CS-Interview-Knowledge-Map/blob/master/Browser/browser-ch.md
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108942.html
摘要:是棧,它繼承于。滿二叉樹除了葉結點外每一個結點都有左右子葉且葉子結點都處在最底層的二叉樹。沒有鍵值相等的節點。這是數據庫選用樹的最主要原因。 在我們學習Java的時候,很多人會面臨我不知道繼續學什么或者面試會問什么的尷尬情況(我本人之前就很迷茫)。所以,我決定通過這個開源平臺來幫助一些有需要的人,通過下面的內容,你會掌握系統的Java學習以及面試的相關知識。本來是想通過Gitbook的...
本文收集學習過程中使用到的資源。 持續更新中…… 項目地址 https://github.com/abc-club/f... 目錄 vue react react-native Weex typescript Taro nodejs 常用庫 css js es6 移動端 微信公眾號 小程序 webpack GraphQL 性能與監控 高質文章 趨勢 動效 數據結構與算法 js core 代碼規范...
摘要:的屬性在瀏覽器的控制臺中,可以直接輸入來查看。可以在瀏覽器的控制臺中看出哪些是類型的,下帶綠色對勾的即是,如圖只要是類型的在控制臺通過是獲取不到的,也不能進行修改。當會話過期或被放棄后,服務器將終止該會話。在中,用取代了。 本文由云+社區發表 在前端面試中,有一個必問的問題:請你談談cookie和localStorage有什么區別啊? localStorage是H5中的一種瀏覽器本地存...
閱讀 1768·2023-04-26 01:44
閱讀 1210·2021-11-12 10:34
閱讀 1578·2021-09-09 09:33
閱讀 1729·2019-08-30 15:44
閱讀 2892·2019-08-30 13:49
閱讀 2189·2019-08-29 15:26
閱讀 943·2019-08-26 13:30
閱讀 1409·2019-08-23 18:15