2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】

作者:*5102

轉發鏈接:
https://juejin.im/post/5e9f0bdce51d4546f5791989

前言

本篇文章屬於知識總結型,歸納出許多比較零散的知識點,都是乾貨噢~

如果你是小白那麼這篇文章正好適合你,如果你是老手那麼不妨鞏固一下看看還有哪些邊角料沒補!

建議:適合有js基礎的小夥伴觀看,篇幅較長,建議先收藏再慢慢瀏覽

整整花了一週時間總結了一些比較重點也有些比較偏的知識,希望各位小夥伴慢慢品嚐,如果有不對的地方或者是需要優化的地方望請告知,儘量給大家呈現最有價值的文章。個人水平有限,還請各位大佬指點迷津。希望各位看了這篇文章能有自己的想法,在前端道路上還很漫長,與我一同探索吧!

2.6萬字JS乾貨分享總共分二部分知識點總結,分別為【基礎篇】、【實踐篇】,希望小夥們按照順序一次閱讀完。

上一篇《2.6萬字JS乾貨分享,帶你領略前端魅力【基礎篇】

九、同步與異步

同步

  • 基於js的單線程同時只能處理一件事情,而同步即是在主線程上排隊執行的任務,只有當前任務執行完成,才會進入下一個任務。同步執行的函數會在預期得到結果,也就是可以清楚什麼時候能得到返回值
  • 所有同步代碼只會進入調用棧,同步代碼會阻塞主線程的執行,而且會優先與其他非同步代碼執行

異步

  • 異步是指當前執行的代碼會進入異步線程處理之後才會再由主線程處理回調
  • 異步的結果不是馬上能夠得到,而是會在將來的某個時間點獲取到
  • 通常異步代碼所要經過的步驟比同步代碼多,由於異步代碼不是直接放在調用棧中執行,而是要派發(可能不需要)給其他線程處理,等處理完成後的回調放在某個地方存儲(比如任務隊列),等到同步隊列執行完成之後才會取回異步回調代碼進行執行

異步、單線程與EventLoop

先看一張圖,有個大體架構


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】


  • js主線程處理當前正在執行的代碼,它會執行當前調用棧棧頂的執行上下文,從堆空間(一般是存儲對象)和棧空間(一般存儲非對象值以及對象引用)取數據,進而處理當前調用棧所用到的數據
  • 所有的同步代碼會按照代碼順序壓入調用棧中等待主線程執行,如果代碼中遇到了異步代碼,則會根據異步類型拋給異步線程執行
  • 異步類型,主要分為微任務與宏任務
  • 任務隊列其實本質就是一塊內存空間,裡面的任務是依據FIFO先進先出的規則來執行,所有異步代碼執行完畢的回調都是加入到異步任務隊列中等待主線程的調用
  • 異步可以提高cpu的利用率

微任務

  • 微任務隊列與宏任務隊列的區別就在於,主線程對於其中的任務調度的區別,主進程會優先執行微任務隊列中的全部任務,當微任務中的全部任務執行完畢才會進而轉到宏任務執行
  • 微任務可以由這些方法關鍵字調用產生Promise、async、await、MutaionObserver、process.nextTick(Node.js環境)
  • 如果調用微任務方法時,方法內部包含其他線程干預處理時,會拋給指定線程執行,而主線程繼續執行下面的代碼,等到其他線程處理完成之後,如果有回調函數則會把回調加入到指定異步類型(這裡為微任務隊列)的隊列中排隊等待主線程執行
  • 微任務與宏任務的主要區別在於,主線程優先執行全部微任務,待執行完成之後才會挨個執行宏任務

宏任務

  • 一般的宏任務隊列存放的是WebApis的回調,WebApis中包含許多線程,GUI渲染線程(與js主線程互斥不能同時執行)、事件觸發線程、定時器線程、異步網絡請求線程
  • 宏任務存放由異步WebApis產生的回調函數,但優先級低於微任務

js單線程

  • js單線程設計之初就是為了簡化代碼,解決DOM衝突,如果js為多線程語言,那麼有可能產生多個線程同時操作DOM的情況,那麼將會導致js操作同個DOM引起衝突,介於多線程的鎖機制來解決衝突,但又使得js的代碼複雜度提高
  • 基於js單線程的設計,進而引出異步執行的方式,使得js具有類似多線程程的效果,但不管異步還是同步,js永遠都只有一個線程在執行

EventLoop

  • 事件循環機制是針對於主線程的調度方式
  • 可以理解為主線程在尋找任務執行的過程就是事件循環,其尋找方式就是調用機制
  • 先了解一下瀏覽器是如何執行js代碼的 通常瀏覽器在最開始運行js代碼的入口就是html中的script標籤所涵蓋的代碼 當GUI渲染線程解析到script標籤,則會把標籤所涵蓋的js代碼加入到宏任務隊列中 首先js引擎(如V8引擎)先取第一個宏任務,即script的代碼塊,然後主線程在調用棧中解析js代碼 等所有代碼解析完成之後開始運行js代碼 如果遇到同步代碼直接執行 遇到異步代碼,如果是宏任務類型即異步WebApis處理的異步代碼,那麼將會通知WebApis在對應的線程中處理異步任務,此時js主線程繼續執行下面的代碼,在其他線程處理完畢之後如果有回調函數,則異步線程會將回調函數加入到宏任務隊列尾部, 如果是微任務類型的異步代碼,也同宏任務處理,只不過是把回調函數加入到微任務隊列中,其執行的優先級高於宏任務隊列 當同步代碼全部執行完成,主線程將會一直檢測任務隊列,如果有異步微任務則執行完全部的微任務 進一步執行瀏覽器渲染進程繪製頁面,之後就是開始下一輪的事件循環,就又回到取宏任務執行 這裡注意,所有的微任務都是由宏任務中執行的代碼產生,一開始只有宏任務隊列有任務

以下展示的是事件循環大致流程


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】


以下為主線程判斷邏輯


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】


前端異步的場景

  • 前端異步主要用於代碼可能會發生等待,而且等待過程不能阻塞主線程運行的情況
  • 通常WebApis接口都是異步調用的,由於需要其他線程的處理,就需要等待其返回結果,那麼js主線程就沒必要一直等待,這樣就需要使用異步來進行處理
  • 比如定時器任務setTimeout、setInterval、ajax請求、圖片動態加載、DOM事件觸發這些都屬於瀏覽器執行的異步任務;如js中的Promise、async、await屬於js語言自身的異步操作這些都可以實現異步
  • 當需要動態加載圖片的時候就需要用到異步;當需要執行的js的同步代碼需要長時間佔用的主線程時可以使用異步方式拆分為多個步驟執行,這樣可以避免瀏覽器頁面長時間無響應或者卡頓
  • 當需要執行很長一段時間才能得到結果的代碼時也可以使用html5中的Web worker在瀏覽器渲染進程下新開一個線程用來專門執行此代碼,通過postMessage來返回運行結果這樣也不會佔用js主線程,但是這個線程無法操作DOM和BOM

WebWorker多線程

  • 基於js單線程的侷限性,如果執行一個很耗時間的函數,那麼主線程將會被長時間佔用,因此導致事件循環暫停,使得瀏覽器無法及時渲染和響應,那麼將會造成頁面崩潰,用戶體驗下降,所以html5支持了webworker
  • webwork簡單理解就是可以讓特定的js代碼在其他線程中執行,等執行結束後返回結果給主線程接收即可
  • 比如在js中需要實現一個識別圖片的算法,而且此算法需要很長的計算時間,如果讓js主線程來執行將會導致上述發生的事情,那麼正好可以使用webwork技術來實現。
  • 創建一個webworker文件,其中寫入算法代碼,在最後調用postMessage(result)方法返回結果給主線程,js主代碼中通過w=new Worker(文件路徑)來創建一個渲染進程的webworker子線程實例,通過w.onmessage=function(e){console.log(e.data)}給其添加一個事件監聽器,當webworker中傳遞消息給js主線程時會在此回調函數中執行,通過調用w.terminate()終止webworker線程
  • webworker線程與js主線程最大的區別就在於webworker線程無法操作window與document對象
<code>// test.html(主線程)
const w= new Worker('postMessage.js')
w.onmessage=function(e){
  console.log(e.data);
}
w.postMessage('b') // b is cat
w.terminate() // 手動關閉子線程
----------------------------------------------
// postMessage.js(worker線程)
this.addEventListener('message', (e) => {
  switch (e.data) {
    case 'a': this.postMessage(e.data+' is tom')
      break;
    case 'b': this.postMessage(e.data + ' is cat')
      break;
    default:  this.postMessage(e.data + " i don't know")
    this.close() // 自身關閉
      break;
  }
})
複製代碼/<code> 

十、AMD、CMD、CommonJS與ES6模塊化

模塊化的引入主要是用於解決命名衝突、代碼複用、代碼可讀性、依賴管理等

AMD異步模塊定義

  • AMD全稱Asynchronous Module Definition異步模塊定義
  • AMD並非原生js支持,是RequireJS模塊化開發當中推廣的產物,AMD依賴於RequireJS函數庫,打包生成對應效果的js代碼
  • RequireJS主要用於解決多個js文件之間的依賴關係、瀏覽器加載大量js代碼導致無響應、異步加載模塊
  • RequireJS通過define(id?,dependencies?,factory)定義模塊,id可選,為定義模塊的標識,默認為模塊文件名不包括後綴,dependencies可選,是當前模塊依賴的模塊路徑數組,factory為工廠方法,初始化模塊的函數或者對象,如果為函數將會只執行一次,如果是對象將作為模塊的輸出
  • 通過require(dependencies,factory)導入模塊,其中dependencies為需要導入的模塊路徑數組,factory為當模塊導入之後的回調函數,此函數的參數列表為對應導入的模塊
  • 通過require.config(配置對象)配置各模塊路徑和引用名
<code>require.config({
  baseUrl: "js/lib",
  paths: {
    "jquery": "jquery.min",  //實際路徑為js/lib/jquery.min.js
    "underscore": "underscore.min",
  }
})
複製代碼/<code>

CMD通用模塊定義

  • CMD全稱Common Module Definition通用模塊定義
  • 同AMD,CMD也有一個函數庫SeaJS與RequireJS類似的功能
  • CMD推崇一個文件一個模塊,推崇依賴就近,定義模塊define(id?,deps?,factory),id同AMD,deps一般不在其中寫依賴,而是在factory中在需要使用的時候引入模塊,factory函數接收3各參數,參數一require方法,用來內部引入模塊的時候調用,參數二exports是一個對象,用來向外部提供模塊接口,參數三module也是一個對象上面存儲了與當前模塊相關聯的一些屬性和方法
  • 通過seajs.use(deps,func)加載模塊,deps為引入到模塊路徑數組,func為加載完成後的回調函數

AMD、CMD的主要區別在於

AMD推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊 CMD推崇就近依賴,只有在用到某個模塊的時候再去require

CommonJS

  • CommonJS模塊規範,通常用於Nodejs中的模塊化
  • 擁有4個環境變量modul、exports、require、global
  • 通過module.exports(不推薦exports)導出模塊對象,通過require(模塊路徑)加載模塊
  • 當一個模塊同時存在exports和module.exports時後者覆蓋前者
  • 規範中__dirname代表當前模塊文件所在的文件夾路徑,__filename代表當前模塊文件夾路徑+文件名
  • CommonJS通過同步的方式加載模塊,其輸出的模塊是一個拷貝對象,所以修改原的模塊不會對被引入的模塊內部產生影響,且模塊在代碼運行的時候加載

ES6模塊化

  • es6引入的export與import用於解決js自身不具備模塊功能的缺陷
  • 通過export或者export default導出模塊接口,通過import xxx from '路徑',導入模塊
  • 對於export導出的接口可以使用import {接口} from '路徑',通過解構的方式按需導入
  • 對於export default默認導出的,可以使用import xxx from '路徑',來導入默認導出的接口,xxx可以是自定義名稱,且一個模塊只能有一個默認導出,可以有多個export
  • 還可以通過別名的方式設置導出和導入的接口名,如export {a as foo},把foo作為a的別名導出,import foo as b from 路徑,把b當作foo的別名導入
  • es6模塊是在代碼編譯時輸出接口即編譯時加載,es6是通過命令來指定導出和加載,且導出的是模塊中的只讀引用,如果原始模塊中的值被改變了,那麼加載的值也會隨之改變,所以是動態引用

十一、script標籤之async與defer

使用async屬性

  • 如果script標籤設置了這個值,則說明引入的js需要異步加載和執行,注意此屬性只適用於外部引入的js
  • 在有async的情況下腳本異步加載和執行,並且不會阻塞頁面加載,但是也並不會保證其加載的順序,如果多個async優先執行,則先加載好的js文件,所以使用此方式加載的js文件最好不要包含其他依賴

使用defer屬性

  • 如果使用此屬性,也將會使js異步加載執行,且會在文檔被解析完成後執行,這樣就不會阻塞頁面加載,但是它將會按照原來的執行順序執行,對於有依賴關係的也可使用
  • html4.0中定義了defer,html5.0中定義了async

不同情況

  • 如果只有async,那麼腳本在下載完成後異步執行。
  • 如果只有defer,那麼腳本會在頁面解析完畢之後執行。
  • 如果都沒有,那麼腳本會在頁面中馬上解執行,停止文檔解析阻塞頁面加載
  • 如果都有那麼同async,當然此情況一般用於html的版本兼容下,如果沒有async則defer生效
  • 不過還是推薦直接把script標籤放在body底部

十二、改變數組本身的api

  1. pop() 尾部彈出一個元素
  2. push() 尾部插入一個元素
  3. shift() 頭部彈出一個元素
  4. unshift() 頭部插入一個元素
  5. sort([func]) 對數組進行排序,func有2各參數,其返回值小於0,那麼參數1被排列到參數2之前,反之參數2排在參數1之前
  6. reverse() 原位反轉數組中的元素
  7. splice(pos,deleteCount,...item) 返回修改後的數組,從pos開始刪除deleteCount個元素,並在當前位置插入items
  8. copyWithin(pos[, start[, end]]) 複製從start到end(不包括end)的元素,到pos開始的索引,返回改變後的數組,淺拷貝
  9. arr.fill(value[, start[, end]]) 從start到end默認到數組最後一個位置,不包括end,填充val,返回填充後的數組

其他數組api不改變原數組

十三、window之location、navigator

location對象

  • location為全局對象window的一個屬性,且window.location===document.location,其中的屬性都是可讀寫的,但是隻有修改hrefhash才有意義,href會重新定位到一個URL,hash會跳到當前頁面中的anchor名字的標記(如果有),而且頁面不會被重新加載
<code>// 這行代碼將會使當前頁面重定向到http://www.baidu.com
window.location.href = 'http://www.baidu.com'
----------------------------------------------
// 如果使用hash並且配合input輸入框,那麼當頁面刷新之後,鼠標將會自動聚焦到對應id的input輸入框,

 
複製代碼/<code>

先看下其擁有的屬性


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】


這裡補充一個origin屬性,返回URL協議+服務器名稱+端口號 (location.origin == location.protocol + '//' + location.host)

  • 可以通過上述屬性來獲取URL中的指定部分,或者修改href於hash達到重新定位與跳轉
  • 添加hash改變監聽器,來控制hash改變時執行的代碼
<code>window.addEventListener("hashchange", funcRef);
// 或者
window.onhashchange = funcRef;
複製代碼/<code>

location方法


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】


  • assign(url),通過調用window.location.assign方法來打開指定url的新頁面window.location.assign('http://www.baidu.com')在當前頁面打開百度,可回退
  • replace(url),在當前頁面打開指定url,不可回退
  • reload([Boolean]),調用此方法將會重新加載當前頁面,如果參數為false或者不填,則會以最優的方式重新加載頁面,可能從緩存中取資源,如果參數為true則會從服務器重新請求加載資源

navigator對象

  • window.navigator對象包含有關瀏覽器的信息,可以用它來查詢一些關於運行當前腳本的應用程序的相關信息
<code>document.write("瀏覽器的代碼名:" + navigator.appCodeName + "
"); document.write("瀏覽器的名稱:" + navigator.appName + "
"); document.write("當前瀏覽器的語言:" + navigator.browserLanguage + "
"); document.write("瀏覽器的平臺和版本信息:" + navigator.appVersion + "
"); document.write("瀏覽器中是否啟用 cookie :" + navigator.cookieEnabled + "
"); document.write("運行瀏覽器的操作系統平臺 :" + navigator.platform + "
"); 複製代碼/<code>
  • navigator.appCodeName 只讀,任何瀏覽器中,總是返回 'Gecko'。該屬性僅僅是為了保持兼容性。
  • navigator.appName 只讀,返回瀏覽器的官方名稱。不要指望該屬性返回正確的值。
  • navigator.appVersion 只讀,返回一個字符串,表示瀏覽器的版本。不要指望該屬性返回正確的值。
  • navigator.platform 只讀,返回一個字符串,表示瀏覽器的所在系統平臺。
  • navigator.product 只讀,返回當前瀏覽器的產品名稱(如,"Gecko")。
  • navigator.userAgent 只讀,返回當前瀏覽器的用戶代理字符串(user agent string)

如下在不同瀏覽器打印的信息

<code>/*
chrome:
    Mozilla/5.0
    (Macintosh; Intel Mac OS X 10_12_6)
    AppleWebKit/537.36 (KHTML, like Gecko)
    Chrome/61.0.3163.91 Safari/537.36
safari:
    Mozilla/5.0
    (Macintosh; Intel Mac OS X 10_12_6)
    AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0
    Safari/604.1.38
ios11劉海X:
    Mozilla/5.0
    (iPhone; CPU iPhone OS 11_0 like Mac OS X)
    AppleWebKit/604.1.38 (KHTML, like Gecko)
    Version/11.0 Mobile/15A372 Safari/604.1
ipad:
    Mozilla/5.0
    (iPad; CPU OS 9_1 like Mac OS X)
    AppleWebKit/601.1.46 (KHTML, like Gecko)
    Version/9.0 Mobile/13B143 Safari/601.1
galxy sansum:
    Mozilla/5.0
    (Linux; Android 5.0; SM-G900P Build/LRX21T)
    AppleWebKit/537.36 (KHTML, like Gecko)
    Chrome/61.0.3163.91 Mobile Safari/537.36
安裝uc瀏覽器:
    Mozilla/5.0
    (Linux; U; Android 6.0.1; zh-CN; Mi Note 2 Build/MXB48T)
    AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0
    Chrome/40.0.2214.89 UCBrowser/11.4.9.941 Mobile Safari/537.36
winphone:
    Mozilla/5.0
    (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E)
    AppleWebKit/537.36 (KHTML, like Gecko) 
    Chrome/61.0.3163.91 Mobile Safari/537.36
hybrid方法的可能:
    Mozilla/5.0
    (iPhone; CPU iPhone OS 11_0 like Mac OS X)
    AppleWebKit/604.1.38 (KHTML, like Gecko)
    Mobile/15A372 weibo/80011134
*/
複製代碼/<code>

十四、ajax與fetch

ajax

  • ajax全稱Asynchronous JavaScript And XML也就是異步js與xml,它可以讓頁面在不刷新的情況下發起請求獲取數據
  • 使用window.XMLHttpRequest構造器實例化一個網絡請求對象const XHR = new XMLHttpRequest()
  • XHR.open(method, url, [ async, [ user, [ password]]])此方法用來發送一個請求,method為請求方法,url為請求地址,async為boolean值默認為true即使用異步請求,user和password在請求需要用戶和密碼的時候使用
  • XHR.send(body)參數為發生請求主體內容,其格式可以為FormData、ArrayBuffer、Document、序列化字符串,在收到響應後,響應的數據會自動填充XHR對象的屬性
  • 當需要設置請求頭時可以調用XHR.setRequestHeader(header,value)設置請求頭的類型與值,當以post方式發起請求就用設置XHR.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')此請求頭,值可更改
  • 通過監聽實例的onreadystatechange屬性方法,當readyState的值改變的時候會觸發onreadystatechange對應的回調函數XHR.onreadystatechange = function () { }
  • 請求狀態readyState有5個值,對應5個請求狀態,只讀 0 表示 請求還未初始化,尚未調用 open() 方法。 1 表示 已建立服務器鏈接,open() 方法已經被調用。 2 表示 請求已接受,send() 方法已經被調用,並且頭部和狀態已經可獲得。 3 表示 正在處理請求,下載中; responseText 屬性已經包含部分數據。 4 表示 完成,下載操作已完成。
  • 還有status屬性,它是這次請求中的響應數字狀態碼,即為我們平時看到的1xx、2xx、3xx、4xx、5xx表示此次請求的狀態結果,在還未發起請求和出錯時都為0,只讀
  • XHR.responseText屬性為此次響應的數據,為字符串,可能是JSON格式需要JSON.parse解析
  • XHR.responseXML屬性為xml形式的數據,可以通過XHR.responseType = 'document'和XHR.overrideMimeType('text/xml')來解析為XML
  • XHR.withCredentials屬性設置為boolean值,通過此屬性來設置是否使用cookies、authorization等憑證字段
  • XHR.timeout通過此屬性來設置請求超時時間
  • XHR.ontimeout通過此屬性來設置請求超時的回調函數,函數的參數為事件對象
  • XHR.abort()此方法用來終止網絡請求
  • XHR.getAllResponseHeaders()此方法用來獲取所有的響應頭
  • XHR.getResponseHeader(name)此方法用來獲取指定的響應頭
  • 還有6個關於進度的事件 loadstart 在收到響應的第一個字節觸發 progress 在接收期間不斷觸發 error 發生錯誤 abort 調用abort方法而終止 load 接收到完整數據,可代替readystatechange與readyState判斷 loadend 在通信完成或abort error load事件後觸發
  • 通過XHR.addEventListener(eventname,callback)方法添加對應的事件監聽,其回調函數接收一個事件對象參數
  • progress事件對象有3個屬性用於查看當前進度相關信息,lengthComputable為boolean值,表示進度是否可用,position表示已經接收的字節數,totalSize表示總需要傳輸的內容長度即Content-Length字節數,通常在分片傳輸內容的時候用到

簡單的發起一次請求

<code>// 最簡單的發起一個請求
const XHR = new XMLHttpRequest()
XHR.open('get','http://127.0.0.1:3000/test?key=value')
XHR.send()
XHR.addEventListener('load',(e)=>{
  // 服務端返回的是查詢參數
  console.log(XHR.response) // {"key":"value"}
})
複製代碼/<code>

基於XMLHttpRequest封裝一個請求方法

<code>// 發送的數據
const data = {
  name: 'tom'
}
// 請求配置
const config = {
  type: "post",
  url: "http://127.0.0.1:3000/test",
  data: data,
  dataType: 'application/json',
  success: function (res) {
    console.log(res);
  },
  error: function (e) {
    console.log(e);
  }
}
// 請求構造器
function Ajax(conf) {
  this.type = conf.type || 'get'
  this.url = conf.url || ''
  this.data = conf.data || {}
  this.dataType = conf.dataType || ''
  this.success = conf.success || null
  this.error = conf.error || null
}
// send方法
Ajax.prototype.send = function () {
  if (this.url === '') return
  const XHR = new XMLHttpRequest()
  XHR.addEventListener('load', () => {
    if (XHR.status >= 200 && XHR.status < 300 || XHR.status == 304) {
      typeof this.success === 'function' && this.success(XHR.response)
    }
  })
  XHR.addEventListener('error', (e) => {
    typeof this.error === 'function' && this.error(e)
  })
  if (this.type.toLowerCase() === 'get') {
    XHR.open('get', this.url)
    XHR.send(null)
  } else {
    XHR.open(this.type, this.url)
    XHR.setRequestHeader('Content-Type', this.dataType || 'application/x-www-form-urlencoded')
    let data = this.data
    if (this.dataType === 'application/json') {
      data = JSON.stringify(this.data)
    }
    XHR.send(data)
  }
}
// 發送請求
const ajax = new Ajax(config).send()
複製代碼/<code>

由於網絡請求模塊封裝較繁瑣,這裡就簡單封裝了一下,僅供參考(。^▽^)

fetch

  • fetch API提供了js接口,用於替代XMLHttpRequest方式的網絡請求,fetch()全局方法使用起來比XHR更加方便
  • fetch方法接受2個參數,參數1為請求url或 Request 對象,參數2為可選配置對象
<code>// fetch方法返回一個Promise對象,可用then方法接收結果,用catch方法捕獲異常,同Promise使用
// 配置對象具體配置
const config = {
  method: 'GET',      // 請求方法
  headers: {          // 頭信息
    'user-agent': 'Mozilla/4.0 MDN Example',
    'content-type': 'application/json'
  },
  body: JSON.stringify({  // 請求的 body 信息,Blob, FormData 等
    data: 1
  }),
  mode: 'cors',             // 請求的模式,cors、 no-cors 或 same-origin
  credentials: 'include',   // omit、same-origin 或 include。為了在當前域名內自動發送 cookie, 必須提供這個選項
  cache: 'no-cache',        // default 、 no-store 、 reload 、 no-cache 、 force-cache 或者 only-if-cached
  redirect: 'follow',       // 可用的 redirect 模式: follow (自動重定向), error (如果產生重定向將自動終止並且拋出一個錯誤), 或者 manual (手動處理重定向).
  referrer: 'no-referrer',  // no-referrer、client或一個 URL。默認是 client。
  referrerPolicy: 'no-referrer', // 指定 referer HTTP頭
  integrity: 'sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=', // 包括請求的  subresource integrity 值
}
// 發起請求
fetch('http://biadu.com' [, config])
複製代碼/<code>
  • then的回調函數接受一個Response對象參數,其對象擁有9個屬性,8個方法
  • 9個屬性 type 只讀 包含Response的類型 (例如, basic, cors) url 只讀 包含Response的URL useFinalURL 包含了一個布爾值來標示這是否是該Response的最終URL status 只讀 包含Response的狀態碼 ok 只讀 包含了一個布爾值來標示該Response成功(狀態碼200-299) redirected 只讀 表示該Response是否來自一個重定向,如果是的話,它的URL列表將會有多個 statusText 只讀 包含了與該Response狀態碼一致的狀態信息 headers 只讀 包含此Response所關聯的Headers 對象 bodyUsed Body 只讀 包含了一個布爾值來標示該Response是否讀取過Body
  • 8個方法 clone 創建一個Response對象的克隆 error 返回一個綁定了網絡錯誤的新的Response對象 redirect(url, status) 用另一個URL創建一個新的 response arrayBuffer 接受一個 Response 流, 並等待其讀取完成. 並 resolve 一個 ArrayBuffer 對象 blob blob()方法使用一個 Response 流,並將其讀取完成 formData 將 Response 對象中的所承載的數據流讀取並封裝成為一個對象 json 使用一個 Response 流,並將其讀取完成。解析結果是將文本體解析為 JSON text 提供了一個可供讀取的"返回流", 它返回一個包含USVString對象,編碼為UTF-8

十五、WebSocket

  • WebSocket是一種在單個TCP連接上進行全雙工通信的協議,即連接雙方可以同時實時收發數據,它可以在用戶的瀏覽器和服務器之間打開雙工、雙向通訊會話。
  • WebSocket API提供全局方法WebSocket(url[, protocols])創建實例,參數1 對方絕對url其url以ws://或者wss://(加密)開頭,參數2 protocols是單協議或者包含協議的字符串數組
<code>// 必須傳入絕對URL,可以是任何網站
const s = new WebSocket('ws://www.baidu.com') 
s.readyState    // 0 建立連接 1 已經建立 2 正在關閉 3 連接已關閉或者沒有鏈接成功
s.send('hello') // 發送的數據必須是純文本
s.onopen = function () {}
s.onerror = function () {}
s.onmessage = function (event) {
  // 當接收到消息時
  console.log(event.data) // 數據是純字符
}
s.close()   // 關閉連接
s.onclose = function (event) {
  /*
    * event.wasClean 是否明確的關閉 
    * event.code 服務器返回的數值狀態碼
    * event.reason 字符串,服務器返回的消息
    */
}
複製代碼/<code>
  • 10個屬性 binaryType 返回websocket連接所傳輸二進制數據的類型(blob, arraybuffer) bufferedAmount 只讀 返回已經被send()方法放入隊列中但還沒有被髮送到網絡中的數據的字節數。一旦隊列中的所有數據被髮送至網絡,則該屬性值將被重置為0。但是,若在發送過程中連接被關閉,則屬性值不會重置為0。 extensions 只讀 返回服務器選擇的擴展名。這當前只是空字符串或連接協商的擴展列表 onclose 用於指定連接失敗後的回調函數 onmessage 用於指定當從服務器接受到信息時的回調函數 onopen 用於指定連接成功後的回調函數 protocol 只讀 服務器選擇的下屬協議 readyState 只讀 當前的鏈接狀態,共4個 0 建立連接 1 已經連接 2 正在關閉 3 連接已經關閉或者沒有連接成功 url 只讀 WebSocket 的絕對路徑
  • 2個方法 close(code, reason) 數字狀態碼 可選 默認 1005和一個可選的類可讀的字符串,它解釋了連接關閉的原因。 send(data) 向服務器發送數據(ArrayBuffer,Blob等)

十六、短輪詢、長輪詢與WebSocket

短輪詢

  • http 短輪詢是server收到請求不管是否有數據到達都直接響應http請求,服務端響應完成,就會關閉這個TCP連接;如果瀏覽器收到的數據為空,則隔一段時間,瀏覽器又會發送相同的http請求到server以獲取數據響應
  • 缺點:消息交互的實時性較低(server端到瀏覽器端的數據反饋效率低)

簡單演示

<code>const xhr = new XMLHttpRequest()
// 每秒發送一次短輪詢
const id = setInterval(() => {
  xhr.open('GET', 'http://127.0.0.1:3000/test?key=value')
  xhr.addEventListener('load', (e) => {
    if (xhr.status == 200) {
      // 處理數據
      console.log(xhr.response)
      // 如果不需要可以關閉
      clearInterval(id)
    }
  })
  xhr.send()
}, 1000)
複製代碼/<code>

長輪詢

  • http 長輪詢是server收到請求後如果有數據,立刻響應請求;如果沒有數據就會停留一段時間,這段時間內,如果server請求的數據到達(如查詢數據庫或數據的邏輯處理完成),就會立刻響應;如果這段時間過後,還沒有數據到達,則以空數據的形式響應http請求;若瀏覽器收到的數據為空,會再次發送同樣的http請求到server
  • 缺點:server 沒有數據到達時,http連接會停留一段時間,這會造成服務器資源浪費

簡單演示

<code>function ajax() {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', 'http://127.0.0.1:3000/test?key=value');
  xhr.addEventListener('load', (e) => {
    if (xhr.status == 200) {
      // 處理數據
      console.log(xhr.response)
      // 如果不需要可以關閉
      if (xhr.response != '') return
      ajax()
    }
  })
  xhr.send();
}
複製代碼/<code>

相同點

  • 當server的數據不可達時,基於http長輪詢和短輪詢的http請求,都會停留一段時間
  • 都是用於實時從服務器獲取數據更新

不同點

  • http長輪詢是在服務器端的停留,而http短輪詢是在瀏覽器端的停留
  • 短輪詢隔一段時間向服務器發起請求,不管服務器數據有沒有變化都直接返回結果,長輪詢則在服務器數據有發生變化的時候才返回結果,如果在一定時間沒有變化那麼將會超時自動關閉連接


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】

Web Socket

  • 為了解決http無狀態,被動性,以及輪詢問題,html5新推出了websocket協議,瀏覽器和服務器只需完成一次握手,兩者即可建立持久性連接,並進行雙向通信
  • 基於http進行握手,發生加密數據,保持連接不斷開
  • 優點: 較少的控制開銷,在進行客戶端與服務器的數據交換時,用於協議控制的數據包頭較小 更強的實時性,全雙工通信,不必侷限於一方發起的請求,服務器與客戶端可以隨時發送數據,延遲更少 有狀態的連接,websocket在通信之前需要雙方建立連接,才能進行通信,而http協議在每次請求都要攜帶狀態信息 基於二進制數據傳輸,websocket定義了二進制幀,可以處理二進制內容,相比於文本傳輸,提高了效率 支持自定義子協議,可以自行擴展協議,如部分瀏覽器支持壓縮等 更好的壓縮效果,Websocket在適當的擴展支持下,可以沿用之前內容的上下文,在傳遞類似的數據時,可以顯著地提高壓縮率

十七、長連接與短連接

短連接

  • HTTP/1.0中默認使用短連接,也就是說,客戶端和服務器每進行一次HTTP操作,就建立一次連接,任務結束就中斷連接
  • 當客戶端瀏覽器訪問的某個HTML或其他類型的Web頁中包含有其他的Web資源(如JavaScript文件、圖像文件、CSS文件等),每遇到這樣一個Web資源,瀏覽器就會重新建立一個HTTP會話
  • 短連接的操作步驟是:建立連接——數據傳輸——關閉連接...建立連接——數據傳輸——關閉連接
  • 像WEB網站的http服務一般都用短連接,併發量大,但每個用戶無需頻繁操作情況下需用短連接

長連接

  • 從HTTP/1.1起,默認使用長連接,用以保持連接特性。使用長連接的HTTP協議,會在響應頭加入這行代碼Connection:keep-alive
  • 在使用長連接的情況下,當一個網頁打開完成後,客戶端和服務器之間用於傳輸HTTP數據的TCP連接不會關閉,客戶端再次訪問這個服務器時,會繼續使用這一條已經建立的連接
  • keep-alive不會永久保持連接,它有一個保持時間,可以在不同的服務器軟件(如Apache)中設定這個時間。實現長連接需要客戶端和服務端都支持長連接
  • 長連接的操作步驟是:建立連接——數據傳輸...(保持連接)...數據傳輸——關閉連接
  • 長連接多用於操作頻繁,點對點的通訊,而且連接數不能太多情況

長短輪詢和長短連接區別

  • HTTP協議的長連接和短連接,實質上是TCP協議的長連接和短連接
  • 長短連接通過雙方請求響應頭是否設置Connection:keep-alive來決定使用,而是否輪詢,是根據服務端的處理方式來決定的,與客戶端沒有關係
  • 實現方式不同,長短連接通過協議來實現,而長短輪詢通過服務器編程手動實現

十八、存儲

Cookie

  • cookie是由服務器發送給客戶端用於存儲少量信息,以鍵值對形式存儲{key:value}


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】


  • 客戶端請求服務器時,如果服務器需要記錄該用戶狀態,就使用response向客戶端瀏覽器頒發一個Cookie。而客戶端瀏覽器會把Cookie保存起來。當瀏覽器再請求 服務器時,瀏覽器把請求的網址連同該Cookie一同提交給服務器。服務器通過檢查該Cookie來獲取用戶狀態
  • cookie是不可跨域,但是隻在域名不同的情況下不支持跨域,忽略協議與端口,https://localhost:80/和http://localhost:8080/的Cookie是共享的,可以通過domain設置域,path設置域下的共享路徑
  • cookie屬性 name 表示設置的cookie名也就是key,不能重複,不可更改 value 表示設置cookie的值 domain 表示cookie綁定的域名,默認綁定當前域,多級域名不可交換cookie,如果設置以點開頭的域名,則所有子域名可以訪問,如設置.baidu.com,則a.baidu.com可訪問其上級域名的cookie path 表示cookie所能使用的路徑,默認'/'路徑,只要滿足當前匹配路徑以及子路徑都可以共享cookie maxAge 表示cookie失效時間,單位秒,正數為失效時間,負數表示當前cookie在瀏覽器關閉時失效,0表示刪除cookie secure 表示cookie是否使用安全協議傳輸如HTTPS、SSL,默認不使用,只在HTTPS等安全協議下有效,這個屬性並不能對客戶端的cookie進行加密,不能保證絕對的安全性 version 當前cookie使用的版本號,0 表示遵循Netscape的Cookie規範(多數),1表示遵循W3C的RFC2109規範(較嚴格),默認為0 same-site 規定瀏覽器不能在跨域請求中攜帶 Cookie,減少CSRF攻擊 HttpOnly 如果這個屬性設置為true,就不能通過js腳本來獲取cookie的值,用來限制非HTTP協議程序接口對客戶端Cookie進行訪問,可以有效防止XSS攻擊(跨站腳本攻擊,代碼注入攻擊)
  • 前端通過document.cookie對cookie進行讀寫操作
  • 創建cookie就是後端的事情了

Session

  • session 表示服務器與客戶端的一次會話過程,session對象存儲特定用戶的屬性及配置信息
  • 當用戶在應用程序的 Web 頁之間跳轉時,存儲在session 對象中的變量將不會丟失,而是在整個用戶會話中一直存在下去。當客戶端關閉會話,或者 session 超時失效時會話結束


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】


  • 用戶第一次請求服務器的時候,服務器根據用戶提交的相關信息,創建創建對應的 session ,請求返回時將此 session 的唯一標識信息 sessionID 返回給瀏覽器,瀏覽器接收到服務器返回的 sessionID 信息後,會將此信息存入到 Cookie 中,同時 Cookie 記錄此 sessionID 屬於哪個域名
  • 當用戶第二次訪問服務器的時候,請求會自動判斷此域名下是否存在 Cookie 信息,如果存在自動將 Cookie 信息也發送給服務端,服務端會從 Cookie 中獲取 sessionID,再根據 sessionID 查找對應的 session 信息,如果沒有找到說明用戶沒有登錄或者登錄失效,如果找到 session 證明用戶已經登錄可執行後面操作
  • session 的運行依賴 session id,而 session id 是存在 Cookie中的

cookie與session的區別

  • cookie數據存放在客戶的瀏覽器上,session數據放在服務器上
  • cookie不是很安全,別人可以分析存放在本地的cookie並進行cookie欺騙,考慮到安全應當使用session。用戶驗證這種場合一般會用 session
  • session保存在服務器,客戶端不知道其中的信息;反之,cookie保存在客戶端,服務器能夠知道其中的信息
  • session會在一定時間內保存在服務器上,當訪問增多,會比較佔用你服務器的性能,考慮到減輕服務器性能方面,應當使用cookie
  • session中保存的是對象,cookie中保存的是字符串
  • session不能區分路徑,同一個用戶在訪問一個網站期間,所有的session在任何一個地方都可以訪問到,而cookie中如果設置了路徑參數,那麼同一個網站中不同路徑下的cookie互相是訪問不到的
  • session: 是在服務端保存的一個數據結構,用來跟蹤用戶的狀態,這個數據可以保存在集群、數據庫、文件中
  • cookie: 是客戶端保存用戶信息的一種機制,用來記錄用戶的一些信息,也是實現session的一種方式

本地存儲localStorage與sessionStorage

localStorage

  • localStorage瀏覽器api,用於存儲本地數據,可持久化,永不過期,除非主動刪除

基本使用

<code>localStorage.setItem("b", "isaac");  //設置b為"isaac"
localStorage.getItem("b");           //獲取b的值,為"isaac"
localStorage.key(0);                 //獲取第0個數據項的鍵名,此處即為“b”
localStorage.removeItem("b");        //清除c的值
localStorage.clear();                //清除當前域名下的所有localStorage數據
複製代碼/<code> 
  • localStorage只要在相同的協議、相同的主機名、相同的端口下,就能讀取/修改到同一份localStorage數據,一般用於跨頁面共享數據
  • 可通過window.addEventListener("storage", function(e){}設置localStorage事件監聽,當存儲區域的內容發生改變時,將會調用回調

sessionStorage

  • sessionStorage用於本地存儲一個會話(session)中的數據,這些數據只有在同一個會話中的頁面才能訪問並且當會話結束後數據也隨之銷燬。因此sessionStorage不是一種持久化的本地存儲,僅僅是會話級別的存儲
<code>sessionStorage.setItem(name, num);    //存儲數據
sessionStorage.setItem('value2', 119);
sessionStorage.valueOf();             //獲取全部數據
sessionStorage.getItem(name);         //獲取指定鍵名數據
sessionStorage.sessionData;           //sessionStorage是js對象,也可以使用key的方式來獲取值
sessionStorage.removeItem(name);      //刪除指定鍵名數據
sessionStorage.clear();
複製代碼/<code>
  • 使用方式與localStorage類似
  • 僅在當前網頁會話下有效,關閉頁面或瀏覽器後就會被清除
  • 主要用於存儲當前頁面獨有的數據,不與瀏覽器其他頁面共享

區別

  • 數據存儲方面 cookie數據始終在同源的http請求中攜帶(即使不需要),即cookie在瀏覽器和服務器間來回傳遞。cookie數據還有路徑(path)的概念,可以限制cookie只屬於某個路徑下 sessionStorage和localStorage不會自動把數據發送給服務器,僅在本地保存。
  • 存儲數據大小 存儲大小限制也不同,cookie數據不能超過4K,同時因為每次http請求都會攜帶cookie、所以cookie只適合保存很小的數據,如會話標識。 sessionStorage和localStorage雖然也有存儲大小的限制,但比cookie大得多,可以達到5M或更大
  • 數據存儲有效期 sessionStorage:僅在當前瀏覽器窗口關閉之前有效; localStorage:始終有效,窗口或瀏覽器關閉也一直保存,本地存儲,因此用作持久數據; cookie:只在設置的cookie過期時間之前有效,即使窗口關閉或瀏覽器關閉
  • 作用域不同 sessionStorage不在不同的瀏覽器窗口中共享,即使是同一個頁面; localStorage在所有同源窗口中都是共享的;也就是說只要瀏覽器不關閉,數據仍然存在 cookie: 也是在所有同源窗口中都是共享的.也就是說只要瀏覽器不關閉,數據仍然存在


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】


十九、跨域

jsonp

  • jsonp是一種跨域通信手段,通過script標籤的src屬性實現跨域,由於瀏覽器同源策略,並不會截斷script的跨域響應
  • 通過將前端方法名作為參數傳遞到服務器端,然後由服務器端注入參數之後再返回,實現服務器端向客戶端通信
  • 由於使用script標籤的src屬性,因此只支持get方法

來實現一下吧

<code>// 前端準備
// 定義回調函數
function fn(arg) {
  // arg為服務端傳來的數據
  console.log(`客戶端獲取的數據:${arg}`)
}
// 創建script標籤
const s = document.createElement('script')
// 給script標籤的src屬性賦值,值為請求url,查詢參數callback,需與後端對應
// fn為前端回調函數名
s.src = `http://127.0.0.1:3000/test?callback=fn`
// 向html添加此標籤,添加完成之後瀏覽器自動請求script的src對應的網址
document.getElementsByTagName('head')[0].appendChild(s);
// 等待瀏覽器收到響應之後,將會自動執行響應內容的代碼
----------------------------------------------
// 後端準備
// nestjs(ts)處理
@Controller('test') //api
export class TestController {
  @Get() //get方式請求
  //取url中的查詢參數,即?之後的鍵值對,鍵與值對應query對象參數的鍵與值
  callback(@Query() query) {  
    // 返回的數據
    const data = '我是服務端返回的數據';
    // 取查詢參數,這裡的callback要與前端?之後的鍵名一致,fn即fn函數名
    const fn = query.callback;
    // 返回結果,格式:函數名(服務器的數據),注意這裡需要序列化成字符串,如果參數本身是字符串那麼要加引號,前端並不知道data是字符串
    return `${fn}('${data}')`;
  }
}

// express(js)處理,同上
router.get('/test', async (req, res) => {
  const data = '我是服務器返回的數據'
  // req.query為查詢參數列表
  const fn = req.query.callback
  // 返回數據
  res.send(`${fn}('${data}')`)
})
複製代碼/<code> 

響應內容


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】


CORS

  • 跨域資源共享cors,它使用額外的 HTTP 頭來告訴瀏覽器,讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不同源服務器上的指定的資源
  • 需要服務端與客戶端同時支持cors跨域方式才能進行跨域請求,服務端通過設置Access-Control-Allow-Origin:*即可開啟cors允許跨域請求,使用通配符*表示允許所有不同域的源訪問資源,也可單獨設置指定允許的源域名
  • 使用cors跨域時,將會在發起請求時出現2種情況:
  • 簡單請求,需滿足以下條件 使用get、head、post方式發起的請求 Content-Type 的值僅限於下列三者之一: text/plain multipart/form-data application/x-www-form-urlencoded 不滿足這些條件即為預檢請求
  • 預檢請求 需預檢的請求要求必須首先使用OPTIONS方法發起一個預檢請求到服務器,以獲知服務器是否允許該實際請求 預檢請求的使用,可以避免跨域請求對服務器的用戶數據產生未預期的影響 當滿足以下條件之一,將會發送預檢請求 使用了下面任一 HTTP 方法: PUT DELETE CONNECT OPTIONS TRACE PATCH 人為設置了對 CORS 安全的首部字段集合之外的其他首部字段。該集合為: Accept Accept-Language Content-Language Content-Type (需要注意額外的限制) DPR Downlink Save-Data Viewport-Width Width Content-Type 的值不屬於下列之一: application/x-www-form-urlencoded multipart/form-data text/plain 滿足以上條件之一將會發起預檢請求,總共會發起2次請求,第一次為OPTIONS方式的請求,用來確定服務器是否支持跨域,如果支持,再發起第二次實際請求,否則不發送第二次請求

postMessage

  • postMessage可用於不同頁面之間的跨域傳遞數據
  • postMessage(data,origin[, source])data為發送的數據只能發送字符串信息,origin發送目標源,指定哪些窗口能接收到消息事件,如果origin設置為*則表示無限制,source為發送消息窗口的window對象引用,
<code> 

 
----------------------------------------------
 
 
複製代碼/<code>
  • event對象的幾個重要屬性 data 指的是從其他窗口發送過來的消息對象 type 指的是發送消息的類型 source 指的是發送消息的窗口對象 origin 指的是發送消息的窗口的源

window.name

  • 由於window.name屬於全局屬性,在html中的iframe加載新頁面(可以是跨域),通過iframe設置的src指向的源中更改name的值,同時主頁面中的name也隨之更改,但是需要給iframe中的window設置為about:blank或者同源頁面即可
  • iframe使用之後應該刪除,name的值只能為string類型,且數據量最大支持2MB
<code> 
// 封裝應該用於獲取數據的函數
function foo(url, func) {
  let isFirst = true
  const ifr = document.createElement('iframe')
  loadFunc = () => {
    if (isFirst) {
      // 設置為同源
      ifr.contentWindow.location = 'about:blank'
      isFirst = false
    } else {
      func(ifr.contentWindow.name)
      ifr.contentWindow.close()
      document.body.removeChild(ifr)
    }
  }
  ifr.src = url
  ifr.style.display = 'none'
  document.body.appendChild(ifr)
  // 加載之後的回調
  ifr.onload = loadFunc
}
foo(`http://127.0.0.1:5501/name.html`, (data) => {
  console.log(data) //
})
----------------------------------------------
 
const obj = { name: "iframe" }
// 修改name的值,必須為string類型
window.name = JSON.stringify(obj);
複製代碼/<code>

document.domain

  • document.domain的值對應當前頁面的域名
  • 通過對domain設置當前域名來實現跨域,不過僅限於域名不同,但是又要屬於同一個基礎域名下,如http://a.baidu.com與http://b.baidu.com這2個子域名之間才能使用domain跨域,一般用於子域名之間的跨域訪問
  • domain只能賦值為當前域名或者其基礎域名,即上級域名
<code> 
 
----------------------------------------------
 
 
複製代碼/<code>
  • 主要就是通過設置為同源域名(只能為其基礎域名),通過iframe操作另一個頁面的內容

nginx反向代理

  • nginx反向代理,代理從客戶端來的請求,轉發到其代理源
  • 通過配置nginx的配置文件實現代理到不同源
<code>// nginx.conf配置
server {
  listen 80;  // 監聽端口
  server_name  www.baidu.com; // 匹配來源
  location / {  //匹配路徑
    // 反向代理到http://127.0.0.1:3000
    proxy_pass http://127.0.0.1:3000;
    // 默認入口文件
    index  index.html index.htm index.jsp;
}
複製代碼/<code>
  • nginx反向代理還能實現負載均衡

二十、setTimeout與setInterval

setTimeout

  • setTimeout屬於webApi的一部分,可以實現延時調用,屬於異步宏任務,一次性使用
  • setTimeout(func|code, [delay], [arg1], [arg2], ...) 參數1為想要執行的函數或代碼字符串,參數2為延遲執行時間,單位毫秒默認0,參數3及之後的參數為參數1為函數時傳入的參數,調用之後會返回一個定時器id
  • 此方法只執行一次,可以使用clearTimeout(id)清除定時器來取消回調
  • 看一下setTimeout的延遲執行機制


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】

  • 以上使用嵌套setTimeout來實現循環調用,可以從中看出setTimeout計時是從上一個setTimeout回調執行之後開始的,看看代碼效果


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】

  • 上圖計算的是2次調用回調之間的間隔,不包括回調執行時間,可以看出在開啟定時器之後到執行回調的時間確實是參數2所設置的值,延遲時間與回調函數執行時間無關;
  • 簡單來講setTimeout的延遲時間不包括自身回調所佔用的時間

也就是說setTimeout是在上一次回調執行之後才開啟的定時

setInterval

  • setInterval同樣也是webApi的一部分,主要用來定時循環執行代碼
  • 不同於setTimeout,此定時器的延遲執行機制有所不同
  • setInterval(func|code, [delay], [arg1], [arg2], ...),參數列表同setTimeout,參數2為每次循環時間


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】

  • 從上圖可以先得出結論,setInterval的延遲執行時間包含自身回調執行所佔用的時間,看看代碼效果


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】

  • 上圖計算的是2次調用回調之間的間隔,不包括回調執行時間,可以看出setInterval在2次執行之間的延遲受到了回調的影響,再驗證一下


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】

  • 此次我把回調執行時間也算在計時之內,現在看來setInterval的定時時間確實包含了自身回調所佔用的時間

由於這2個api都屬於異步宏任務,在執行的時候都會進入任務隊列,如果隊列前的任務執行時間較長,那麼也會影響到定時器的執行時機

在瀏覽器中alert、confirm、prompt都會阻塞js主線程執行,直到彈窗消失,但是定時器還會繼續執行;定時器並不能達到0延遲,最小延遲限制在4ms

二十一、requestAnimationFrame

  • 在requestAnimationFrame還未出來之前,大多數使用定時器完成js動畫,但是由於定時器不準確,而且每次更新動畫的時候不能保證與瀏覽器渲染同步,這樣將會導致畫面的不流暢
  • 由於目前主流屏幕的固定刷新頻率一般為60HZ即一秒60幀,每次刷新間隔為1000/60ms,為了使瀏覽器得到最好的渲染效果,瀏覽器每次渲染應該與屏幕刷新率保持一致,那麼對於js動畫而言,最好的更新時機應該與瀏覽器儘量保持一致
  • 當每次瀏覽器將要重繪之前,把要執行更新的動畫更新完成,那麼當瀏覽器渲染的時候將會保持最新的動畫,這就是requestAnimationFrame所做的事情
  • requestAnimationFrame(callback) 的參數就是每次渲染前需要執行的動畫更新函數,當瀏覽器將要重繪畫面時就會執行這個回調函數,這個回調函數接受一個參數,即從當前頁面加載之後到現在所經過的毫秒數
  • 此api將會與瀏覽器渲染同步,即瀏覽器渲染幾次這個api將會執行幾次,那麼就達到了不掉幀的效果,畫面效果就更加流程
  • requestAnimationFrame執行時機在事件循環機制中處於微任務隊列之後,瀏覽器渲染之前,瀏覽器渲染之後就會進入下一次的事件循環(宏任務開始,瀏覽器渲染結束)
  • 如果使用定時器進行js動畫操作,那麼首先將會導致動畫更新與瀏覽器每次重繪時機不匹配,造成卡頓,其次過於頻繁的更新動畫還會導致不必要的性能開銷,且並非能夠達到更好的效果
  • 簡單說使用requestAnimationFrame更新的動畫與瀏覽器保持同步,不會掉幀,除非瀏覽器掉幀或者,js主線程阻塞導致瀏覽器無法正常渲染,使用定時器更新動畫,如果頻率高了會影響性能,且達不到更好的效果,如果頻率低了將會有不連貫的感覺


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】


  • 從上圖可以看出確實是每幀執行一次,不過要注意,調用一次requestAnimationFrame只會執行一次,如果需要持續執行需要在回調函數內繼續調用

二十二、事件

DOM0事件

  • DOM0事件並非w3c標準,在DOM標準形成之前的事件模型就是我們所說的0級DOM
  • 添加DOM0事件,都是把一個函數賦值給文檔元素,在事件監聽函數被調用時,將會做為產生事件的元素方法調用,所以this指向目標元素,簡單說就是直接把回調函數作為文檔元素的一個方法調用
  • 刪除DOM0事件只需把事件賦值為null即可
<code>document.getElementById("btn").onclick = function () {}
----------------------------------------------

複製代碼/<code>
  • 如果回調方法返回一個false則會阻止瀏覽器事件的默認行為
  • DOM0事件在事件捕獲階段,無法接收事件,即沒無法觸發事件捕獲,但是能夠正常觸發冒泡
  • 由於DOM0事件的回調屬於文檔元素的方法,導致無法添加多個同名事件,不過看來兼容性最好

DOM2事件

  • 由於w3c推出的1級DOM標準中並沒有定義事件相關的內容,所以沒有所謂的1級DOM事件模型
  • 在2級DOM中除了定義了一些DOM相關的操作之外還定義了一個事件模型 ,這個標準下的事件模型就是我們所說的2級DOM事件模型
  • 2級DOM定義了事件傳播,在事件傳播過程中將會經歷3個階段: capturing階段,即事件捕獲階段,在某個DOM上觸發事件時,事件會先從Document對象 沿著dom數向下傳遞直到觸發節點,此過程就是事件捕獲階段,在此過程中可以捕獲傳播的事件 目標元素的事件處理階段,此階段事件到達觸發目標,調用回調處理事件 bubbling階段,即事件冒泡階段,在目標元素處理完成之後,此事件還會向上冒泡,回傳到Document,此階段與捕獲階段相反
  • 以上就是事件在觸發之後的傳播過程,可以配合下圖理解


2.6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】


  • DOM2 註冊事件,可以通過addEventListener(eventName,callback,isCapturing)方法為元素設置事件監聽器,參數1為註冊事件名不帶on開頭的string類型,參數2為觸發事件的回調函數,接受一個事件對象參數,參數3為是否在捕獲階段觸發,默認為false
  • 通過removeEventListener(eventName,callback,isCapturing)方法移除指定事件名、回調、是否捕獲的事件,匿名回調無法刪除
  • 可給一個元素添加多個相同的事件,通過不同的回調實現不同效果
  • DOM2中的回調函數中的this指向,由瀏覽器決定,w3c標準中並未規定其指向,一般情況this指向window
  • 回調函數event對象參數
  • 屬性 type 發生事件的類型 target 發生事件的階段,為觸發事件的對象,可以與currentTarget不同 currentTarget 正在處理事件的節點,即註冊此回調函數的元素 clientX,clientY鼠標相對瀏覽器的x座標與y座標 screenX,screenY鼠標相對於顯示器左上角x,y座標
  • 方法 stopPropagation() 阻止當前事件的進一步傳播 preventDefault() 阻止瀏覽器執行與世界相關的默認動作,與DOM0返回false相同
  • 觸發時機 document 往 target節點傳播,捕獲前進,遇到註冊的捕獲事件立即觸發執行 到達target節點,觸發事件(對於target節點上,是先捕獲還是先冒泡則捕獲事件和冒泡事件的註冊順序,先註冊先執行) target節點 往 document 方向傳播,冒泡前進,遇到註冊的冒泡事件立即觸發

事件代理

  • 事件代理又或是事件委託,通過事件冒泡機制,使用單一父節點來操作多個子節點的響應,簡單講就是把所有子節點的事件去除,只給父節點註冊事件,那麼就可以通過事件冒泡機制來處理子節點的響應
  • 基於事件委託可以減少事件註冊,節省內存,簡化dom節點於事件的更新
<code>
  • a
  • b
  • c
----------------------------------------------
  • a
  • b

  • c

複製代碼/<code>
  • 以上就是幾個簡單的事件代理的例子,事件代理能夠在我們平時開發中減少很多不必要的代碼,優化事件系統,但是在使用的過程也要注意相應的問題
  • 事件代理基於冒泡機制,如果代理層級過多,且在冒泡階段如果被某層阻止冒泡那麼父級將不會收到事件
  • 理論上委託會導致瀏覽器頻繁調用處理函數,雖然很可能不需要處理,所以建議就近委託
  • 如果事件代理了許多情況那麼要做好完善邏輯分析,避免一些誤判的情況

總結

以上總結可能沒有什麼順序,但是每章節都是針對性的講解,零散的知識點較多,希望看完這篇文章能擴展你的知識面,也許某方面講的不是很詳細,如果感興趣可以找些針對性的文章進行深入瞭解。

部分內容並非原創,還是要感謝前輩的總結,如果本文影響到您的利益,那麼還請事先告知,在寫本文時的初衷就是想給更多學習前端的小夥伴拓展知識,夯實基礎,共同進步,也為了以後方便複習使用

總結不易,如需轉載請註明出處,感謝!

求點贊

如果本文對你有所幫助,就請點個贊支持一下吧,讓更多人看到,你的支持就是我堅持寫作下去的動力,如果喜歡我的文章,那麼還請關注後續的文章吧~ ψ(`∇´)ψ

已完結

推薦JavaScript經典實例學習資料文章

前端開發規範:命名規範、html規範、css規範、js規範

【規範篇】前端團隊代碼規範最佳實踐

100個原生JavaScript代碼片段知識點詳細彙總【實踐】

關於前端174道 JavaScript知識點彙總(一)

關於前端174道 JavaScript知識點彙總(二)

關於前端174道 JavaScript知識點彙總(三)

幾個非常有意思的javascript知識點總結【實踐】

都2020年了,你還不會JavaScript 裝飾器?

JavaScript實現圖片合成下載

70個JavaScript知識點詳細總結(上)【實踐】

70個JavaScript知識點詳細總結(下)【實踐】

開源了一個 JavaScript 版敏感詞過濾庫

送你 43 道 JavaScript 面試題

3個很棒的小眾JavaScript庫,你值得擁有

手把手教你深入鞏固JavaScript知識體系【思維導圖】

推薦7個很棒的JavaScript產品步驟引導庫

Echa哥教你徹底弄懂 JavaScript 執行機制

一個合格的中級前端工程師需要掌握的 28 個 JavaScript 技巧

深入解析高頻項目中運用到的知識點彙總【JS篇】

JavaScript 工具函數大全【新】

從JavaScript中看設計模式(總結)

身份證號碼的正則表達式及驗證詳解(JavaScript,Regex)

瀏覽器中實現JavaScript計時器的4種創新方式

Three.js 動效方案

手把手教你常用的59個JS類方法

127個常用的JS代碼片段,每段代碼花30秒就能看懂-【上】

深入淺出講解 js 深拷貝 vs 淺拷貝

手把手教你JS開發H5遊戲【消滅星星】

深入淺出講解JS中this/apply/call/bind巧妙用法【實踐】

手把手教你全方位解讀JS中this真正含義【實踐】

書到用時方恨少,一大波JS開發工具函數來了

乾貨滿滿!如何優雅簡潔地實現時鐘翻牌器(支持JS/Vue/React)

手把手教你JS 異步編程六種方案【實踐】

讓你減少加班的15條高效JS技巧知識點彙總【實踐】

手把手教你JS開發H5遊戲【黃金礦工】

手把手教你JS實現監控瀏覽器上下左右滾動

JS 經典實例知識點整理彙總【實踐】

作者:*5102

轉發鏈接:
https://juejin.im/post/5e9f0bdce51d4546f5791989


分享到:


相關文章: