<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                請注意:本書已遷移至獨立站點,請移步訪問:[前端面試寶典](https://www.mianshibook.com/) ## 事件機制 ### 事件觸發三階段 事件觸發有三個階段 * `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 ) ~~~ ### 事件代理 如果一個節點中的子節點是動態生成的,那么子節點需要注冊事件的話應該注冊在父節點上 ~~~ <ul id="ul"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ul> <script> let ul = document.querySelector('##ul') ul.addEventListener('click', event => { console.log(event.target) }) </script> ~~~ 事件代理的方式相對于直接給目標注冊事件來說,有以下優點 * 節省內存 * 不需要給子節點注銷事件 ## 跨域 因為瀏覽器出于安全考慮,有同源策略。也就是說,如果協議、域名或者端口有一個不同就是跨域,Ajax 請求會失敗。 我們可以通過以下幾種常用方法解決跨域的問題 ### JSONP JSONP 的原理很簡單,就是利用`<script>`標簽沒有跨域限制的漏洞。通過`<script>`標簽指向一個需要訪問的地址并提供一個回調函數來接收數據當需要通訊時。 ~~~ <script src="http://domain/api?param1=a&param2=b&callback=jsonp"></script> <script> function jsonp(data) { console.log(data) } </script> ~~~ 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 順序是這樣的 1. 執行同步代碼,這屬于宏任務 2. 執行棧為空,查詢是否有微任務需要執行 3. 執行所有微任務 4. 必要的話渲染 UI 5. 然后開始下一輪 Event loop,執行宏任務中的異步代碼 通過上述的 Event loop 順序可知,如果宏任務中的異步代碼有大量的計算并且需要操作 DOM 的話,為了更快的 界面響應,我們可以把操作 DOM 放入微任務中。 ### Node 中的 Event loop Node 中的 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/O I/O 階段會執行除了 close 事件,定時器和`setImmediate`的回調 ### idle, prepare idle, prepare 階段內部實現 ### poll poll 階段很重要,這一階段中,系統會做兩件事情 1. 執行到點的定時器 2. 執行 poll 隊列中的事件 并且當 poll 中沒有定時器的情況下,會發現以下兩件事情 * 如果 poll 隊列不為空,會遍歷回調隊列并同步執行,直到隊列為空或者系統限制 * 如果 poll 隊列為空,會有兩件事發生 * 如果有`setImmediate`需要執行,poll 階段會停止并且進入到 check 階段執行`setImmediate` * 如果沒有`setImmediate`需要執行,會等待回調被加入到隊列中并立即執行回調 如果有別的定時器需要被執行,會回到 timer 階段執行回調。 ### check check 階段執行`setImmediate` ### close callbacks close 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 Worker > 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 已經啟動了![](https://user-gold-cdn.xitu.io/2018/3/28/1626b1e8eba68e1c?w=1770&h=722&f=png&s=192277) 在 Cache 中也可以發現我們所需的文件已被緩存 ![](https://user-gold-cdn.xitu.io/2018/3/28/1626b20dfc4fcd26?w=1118&h=728&f=png&s=85610) 當我們重新刷新頁面可以發現我們緩存的數據是從 Service Worker 中讀取的 ![](https://user-gold-cdn.xitu.io/2018/3/28/1626b20e4f8f3257?w=2818&h=298&f=png&s=74833) ## 渲染機制 瀏覽器的渲染機制一般分為以下幾個步驟 1. 處理 HTML 并構建 DOM 樹。 2. 處理 CSS 構建 CSSOM 樹。 3. 將 DOM 與 CSSOM 合并成一個渲染樹。 4. 根據渲染樹來布局,計算每個節點的位置。 5. 調用 GPU 繪制,合成圖層,顯示在屏幕上。 ![](https://user-gold-cdn.xitu.io/2018/4/11/162b2ab2ec70ac5b?w=900&h=352&f=png&s=49983) 在構建 CSSOM 樹時,會阻塞渲染,直至 CSSOM 樹構建完成。并且構建 CSSOM 樹是一個十分消耗性能的過程,所以應該盡量保證層級扁平,減少過度層疊,越是具體的 CSS 選擇器,執行速度越慢。 當 HTML 解析到 script 標簽時,會暫停構建 DOM,完成后才會從暫停的地方重新開始。也就是說,如果你想首屏渲染的越快,就越不應該在首屏就加載 JS 文件。并且 CSS 也會影響 JS 的執行,只有當解析完樣式表才會執行 JS,所以也可以認為這種情況下,CSS 也會暫停構建 DOM。 ![](https://user-gold-cdn.xitu.io/2018/7/8/1647838a3b408372?w=1676&h=688&f=png&s=154480) ![](https://user-gold-cdn.xitu.io/2018/7/8/16478388e773b16a?w=1504&h=760&f=png&s=123231) ### 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 有關。 1. 當 Event loop 執行完 Microtasks 后,會判斷 document 是否需要更新。因為瀏覽器是 60Hz 的刷新率,每 16ms 才會更新一次。 2. 然后判斷是否有`resize`或者`scroll`,有的話會去觸發事件,所以`resize`和`scroll`事件也是至少 16ms 才會觸發一次,并且自帶節流功能。 3. 判斷是否觸發了 media query 4. 更新動畫并且發送事件 5. 判斷是否有全屏操作事件 6. 執行`requestAnimationFrame`回調 7. 執行`IntersectionObserver`回調,該方法用于判斷元素是否可見,可以用于懶加載上,但是兼容性不好 8. 更新界面 9. 以上就是一幀中可能會做的事情。如果在一幀中有空閑時間,就會去執行`requestIdleCallback`回調。 以上內容來自于[HTML 文檔](https://html.spec.whatwg.org/multipage/webappapis.html##event-loop-processing-model) ### 減少重繪和回流 * 使用`translate`替代`top` ~~~ <div class="test"></div> <style> .test { position: absolute; top: 10px; width: 100px; height: 100px; background: red; } </style> <script> setTimeout(() => { // 引起回流 document.querySelector('.test').style.top = '100px' }, 1000) </script> ~~~ * 使用`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`標簽,瀏覽器會自動將該節點變為圖層。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看