來聊聊前端的異步

異步

  • 出現的緣由
    • JavaScript語言的執行環境是"單線程"
    • 所謂"單線程",就是指一次只能只能完成一件任務。若是有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務,以此類推。
    • 單線程這種模式好處是實現起來比較簡單,執行環境單一。壞處是隻要有一個任務耗時很長,後面的任務都必須排隊等着,會拖延整個程序的執行。常見的瀏覽器無響應(假死),每每就是由於某一段JavaScript代碼長時間運行(好比死循環),致使整個頁面卡在這個地方,其餘任務沒法執行。
    • 爲了解決這個問題,JavaScript語言將任務的執行模式分紅兩種,同步和異步。
  • 異步的使用場景
    • Ajax操做
  • 異步編程的實現
    • 回調函數javascript

      • 優勢:簡單丶很容易理解和部署。
      • 缺點:不利於閱讀和維護,各部分之間高度耦合,流程會很混亂,並且每一個任務只能指定一個回調函數。
      • 代碼實現
        // 假若有兩個函數f1和f2 後者等待前者的執行結果
        // 若是f1是一個很耗時的任務,能夠考慮改寫f1,把f2寫成f1的回調函數
        
        function f2(c) {
            console.log(c, 'hehe');
        }
        function f1(callback) {
            setTimeout(function() {
                // f1任務代碼
                console.log('f1執行完');
                callback('f2開始執行', 'hehe');
            }, 1000);
        }
        f1(f2);
        複製代碼
    • 事件監聽css

      • 代碼實現
      • 優勢:比較容易理解,能夠綁定多個事件,每一個事件能夠指定多個回調函數,並且能夠"去耦合",有利於實現模塊化。
      • 缺點:整個程序都變成事件驅動,運行流程會變得很不流暢。
        f1.on('done', f2);
        複製代碼
    • 發佈/訂閱html

      • 定義:咱們假定,存在一個"信號中心",某個任務執行完成,就向信號中心"發佈"一個信號,其餘任務能夠向信號中心"訂閱"整個信號,從而知道何時本身能夠開始執行。這就叫作"發佈/訂閱模式",又稱"觀察者模式"。
      • 優勢:
        • 這種方法的性質和"事件監聽"相似,可是明顯優於後者。由於咱們能夠經過查看"消息中心",瞭解存在多少信號丶每一個信號有多少訂閱者,從而監控程序的運行。
        • 一是時間上的解耦,而是對象上的解耦
        • 便可用於異步編程中,也能夠用幫助咱們完成更鬆耦合的代碼編寫
      • 缺點:
        • 建立訂閱者自己須要消耗必定的時間和內存
        • 當訂閱一個消息時,也許次消息並無發生,但這個訂閱者會始終存在內存中
        • 觀察者模式弱化了對象之間的聯繫,這本是好事,但若是過分使用,對象與對象之間的聯繫也會隱藏的很深,會致使項目的難以追蹤維護和理解。
      • 使用場景
        • DOM事件
        • 自定義事件
      • 代碼實現
        function Event() {
            // 存儲不一樣的事件類型對應不一樣的處理函數,保證後續emmit能夠執行。
            this.cache = {};
        }
        // 綁定事件
        Event.prototype.on = function(type, handle) {
            if(!this.cache[type]) {
                this.cache[type] = [handle];
            }else {
                this.cache[type].push(handle);
            }
        }
        // 事件觸發
        Event.prototype.emmit = function() {
            var type = arguments[0],
                arg = [].slice.call(arguments, 1);
            for(var i = 0; i < this.cache[type].length; i++) {
                this.cache[type][i].apply(this, arg);
                if(this.cache[type][i].flag) {
                    this.cache[type].splice(i, 1);
                    if(this.cache[type][i].flag) {
                        this.cache[type].splice(i, 1);
                    }
                }
            }
        }
        // 解除某個事件類型
        Event.prototype.empty = function(type) {
            this.cache[type] = [];
        }
        // 解除某個事件
        Event.prototype.remove = function(type, handle) {
            this.cache[type] =  this.cache[type].filter((ele) => ele != handle);
        }
        // 綁定一次事件
        Event.prototype.once = function(type, handle) {
            if(!this.cache[type]) {
                this.cache[type] = [];
            } 
            // 作標記
            handle.flag = true;
            this.cache[type].push(handle);
        } 
          
          
        function detail1(time) {
            console.log('overtime1' + time);
        }
        function detail2(time) {
            console.log('overtime2' + time);
        }   
        var oE = new Event();
        oE.on('over', detail1);
        oE.on('over', detail2);
        oE.emmit('over', '2019-11-11');
        oE.remove('over', detail2);
        oE.emmit('over', 'second-11-11');
        複製代碼
    • Promise對象java

      • 定義
        • 是CommonJS工做組提出的一種規範,目的是爲了異步編程提供統一接口。
        • 簡單來講,它的思想就是,每個異步任務都返回一個Promise對象,該對象有一個then方法,容許指定回調函數。
      • 優勢:
        • 回調函數變成鏈式寫法,程序的流程能夠看得很清楚,並且有一整套流程的配套方法。
        • 並且,它還有一個前面三種方法都沒有的好處:若是一個任務已經完成,再添加回調函數,該回調函數會當即執行。因此,你不要擔憂是否錯過了某個事件或信號。
      • 缺點:就是編寫和理解,相對比較難。

Promise詳解

  • 定義:Promise是異步編程的一種解決方案,所謂的Promise簡單來講就是一個容器,裏面保存着將來纔會結束的事件的結果。
  • 特色
    • 對象的狀態不受外界影響,Promise對象表明一種異步操做,有三種狀態:Pending(進行中)丶Resolve(已完成)丶Rejected(已失敗),只有異步操做的結果能夠改變狀態,其它的任何操做都不能改變狀態。
    • 一旦狀態改變了,就不會再變了,任什麼時候候均可以獲得這個結果。Promise對象的狀態只有兩種可能:Pending->Resolve或Pending->Resolve,只要這兩種狀況發生了,而且會一直保持這個結果,這與事件監聽不一樣,事件的特色是不一樣時間。
  • 缺點:
    • 首先沒法取消Promise,一旦建立它就會當即執行,中途沒法取消。
    • 其次,若是還不設置回調函數,Promise內部拋出的錯誤,不會反映到外部
    • 最後,當處於Pending狀態時,沒法得知目前進展到哪個階段了。
  • 源碼實現
    • 功能
      • 同步
      • 異步
      • then鏈式操做
        • 處理返回普通值
        • 處理返回Promise值
      • then異步操做
      • then捕捉錯誤
      • 空then
      • Promise.all():所有成功才成功,一個失敗所有失敗
      • Promise.race():哪一個狀態改變了,P的狀態就改變了
      function MyPromise(executor) {
          var self = this;
          self.status = 'pending';
      
          self.resolveValue = null;
          self.rejectReason = null;
      
          self.resolveCallBackList = [];
          self.RejectCallBackList = [];
      
          function resolve(value) {
              if(self.status === 'pending') {
                  self.status = 'Fulfilled';
                  self.resolveValue = value;
                  self.resolveCallBackList.forEach(function(ele) {
                      ele();
                  });
              }
          }
      
          function reject(reason) {
              if(self.status === 'pending') {
                  self.status = 'Rejected';
                  self.rejectReason = reason;
                  self.RejectCallBackList.forEach(function(ele) {
                      ele();
                  })
              }
          }
      
          try {
              executor(resolve, reject);
          }catch(e) {
              reject(e);
          }
      }
      
      
      function ResolutionReturnPromise(nextPromise, returnValue, res, rej) {
          if(returnValue instanceof MyPromise) {
              returnValue.then(function() {
                  res(val);
              }, function(reason) {
                  rej(reason);
              })
          }else {
              res(returnValue);
          }
      }
      
      MyPromise.prototype.then = function(onFulfilled, onRejected) {
          if(!onFulfilled) {
              onFulfilled = function(val) {
                  return val;
              }
          }
          if(!onRejected) {
              onRejected = function(reason) {
                  throw new Error(reason);
              }
          }
      
          var self = this;
      
          var nextPromise = new MyPromise(function(res, rej) {
              if(self.status === 'Fulfilled') {
                  setTimeout(function() {
                      try {
                          var nextResolveValue = onFulfilled(self.resolveValue);
                          ResolutionReturnPromise(nextPromise, nextResolveValue, res, rej);
                      }catch(e) {
                          rej(e);
                      }
                  }, 0);
              }
              if(self.status === 'Rejected') {
                  setTimeout(function() {
                      try{
                          var nextResolveValue = onRejected(self.rejectReason);
                          ResolutionReturnPromise(nextPromise, nextRejectValue, res, rej);
                      }catch(e) {
                          rej(e);
                      }
                  })
              }
      
              if(self.status === 'pending') {
                  self.resolveCallBackList.push(function() {
                      setTimeout(function() {
                          try {
                              var nextResolveValue = onFulfilled(self.resolveValue);
                              ResolutionReturnPromise(nextPromise, nextResolveValue, res, rej);
                          }catch(e) {
                              rej(e);
                          }
                      }, 0)
                  })
              }
          })
      
      
          return nextPromise;
      }
      
      MyPromise.race = function(promiseArr) {
          return new Promise(function(resolve, reject) {
              promiseArr.forEach(function(promise, index) {
                  promise.then(resolve, reject);
              })
          })
      }
      複製代碼
  • 使用場景
    • Ajax請求
      • 定義:
        • Ajax是Asynchronous javascript and xml的縮寫,用JavaScript以異步的形式操做XML(如今操做的是JSON)。隨着谷歌地圖的橫空出世,這種不須要刷新頁面就能夠與服務器通信的方式很快被人們所知。在傳統的Web模型中,客戶端向服務端發送一個請求,服務端會返回整個頁面。
        • 咱們前面學習的form表單來傳輸數據的方式就屬於傳統的Web模型,當咱們點擊submit按鈕以後,整個頁面就會被刷新一下。form表單有三個很重要的屬性,分別是method丶action和enctype。method是數據傳輸的方式,通常是GET或者POST,action是咱們要把數據傳送到的地址,enctype的默認值是"application/x-www-form-urlencoded",即在發送前編碼全部字符,這個屬性值即便咱們不寫也是默認這個的。可是當咱們在使用包含文件上傳控件的表單的時候,這個值就必須更改爲"multipart/form-data",即不對字符進行編碼。而在Ajax模型中,數據在客戶端與服務器之間獨立傳輸,服務器再也不返回整個頁面。
      • 優勢
        • 頁面無刷新,在頁面內與服務器進行通訊,給用戶的體驗更好。
        • 使用異步的形式與服務器進行通訊,不須要打斷用戶的操做,給用戶的體驗更好。
        • 減輕服務器的負擔。
        • 不須要插件或者小程序
      • 缺點
        • 不支持瀏覽器的後退機制
        • 安全問題,跨站點腳本攻擊丶sql注入攻擊
        • 對搜索引擎支持較弱
        • 不支持移動端設備
        • 違背了url和資源定位的初衷
      • 對象屬性
        • onreadystatechange:狀態改變觸發器
        • readyState:對象狀態
          • 0:表示爲初始化,此時已經建立了一個XMLHttpRequest對象
          • 1:表示讀取中,此時代碼已經調用XMLHttpRequest的open方法而且XMLHttpRequest已經將請求發送到服務器。
          • 2:表示已讀取,此時已經經過open方法把一個請求發送到服務端,可是還沒收到。
          • 3:表明交互中,此時已經收到http響應頭部信息,可是消息主體信息尚未徹底接收。
          • 4:表明完成,此時響應已經被徹底接受
        • responseText:服務器進程返回數據的文本版本
        • responseXML:服務器進程返回數據的兼容DOM的XML文本對象
        • status:服務器返回的狀態碼
      • 代碼實現
        function AJAX(json) {
            var url = json.url,
                method = json.method,
                flag = json.flag,
                data = json.data,
                callBack = json.callBack,
                xhr = null;
            // 1. 建立異步對象    
            if(window.XMLHttpRequest) {
                // 通常主流瀏覽器支持這個
                xhr = new window.XMLHttpRequest();
            }else {
                // IE6如下用這個
                xhr = new ActiveXObject('Microsoft.XMLHTTP');
            } 
            // 2. 讓異步對象監聽接收服務器的響應數據
            xhr.onreadystatechange = function() {
                if(xhr.readyState === 4 && xhr.status === 200) {
                    // 數據已經可用了
                    callBack(xhr.responseText);
                }
            } 
            // 創建對服務器的調用
            if(method === 'get') {
                url += '?' + data + new Data().getTime();
                // 3. 設置請求方式
                xhr.open('get', url, flag);
                xhr.send();
            }else {
                xhr.open('post', url, flag); 
                // 4. 設置請求頭
                xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                // 5. 設置請求體
                xhr.send(data);
            }
        }
        複製代碼
    • 自定義彈窗處理
    • 圖片加載

Generator

  • 定義:生成器,自己是函數,執行後返回迭代對象,函數內部要配合yeild使用Generator函數,會分段執行,遇到yeild即暫停。
  • 特色
    • function和函數名之間須要帶*
    • 函數體內部yeild表達式,產出不一樣的內部狀態(值)
    • 能夠交出函數的執行權
  • 做用:配合Promise解決回調地獄問題
  • 配合庫
    • Co
  • 使用場景
    • 能夠在任意對象上部署Iterator接口,配合Promise和Co庫解決回調地獄的問題。

async

  • 定義:是Generator語法糖,經過babel編譯後能夠看出它就是Generator+Promise+Co遞歸思想實現的,配合await使用。
  • 目的:優雅解決異步操做的問題
    • 異步編程會出現問題
      • 回調地獄
        // 這樣能層層回調的事情,你會發現要寫多個回調函數,一堆.then不夠優雅,
        //     有沒有寫法寫起來像同步很優雅,最後也是經過異步的方式來搞定層層回調的結果。
        function readFile(path) {
            return new Promise((res, rej) => {
                fs.readFile(path, 'utf-8', (err, data) => {
                    if(err) {
                        rej(err); 
                    }else {
                        res(data);
                    }
                })
            })
        };
        readFile('https://api.github.com/users/superman66')
          .then((val) => {
              return readFile(val);
          }, () => {}).then((val) => {
              return readFile(val);
          })
          .then((val) => {
            console.log(val);
          });
        
        // async寫法
        async function read(url) {
            let val1 = await readFile(url);
            let val2 = await readFile(val1);
            let val3 = await readFile(val2);
        }
        read('https://api.github.com/users/superman66')
          .then((val) => console.log(val));
        複製代碼
      • 解決了try catch能夠異步的方式捕獲異常
        async function read(url) {
            try {
                let val1 = await readFile(url);
                let val2 = await readFile(val1);
                let val3 = await readFile(val2);
            }catch(e) {
                console.log(e);
            }
        }
        readFile('./data/number.txt').then((val) => console.log(val));
        複製代碼
      • 解決同步併發異步的結果
        • Promise.all()
          // 以前經過解決同步併發異步的結果是使用Promise.all()去解決的,首先Promise.all()使用的時候須要傳遞多個promise對象
          //     其次他是所有成功才成功,一個失敗所有失敗
          Promise.all([readFile('./data/number1.txt), readFile('./readFile/number2.txt'), readFile('./data/number3.txt')])
              .then((val) => console.log(val), (reason) => console.log(reason));
          複製代碼
        • async
          async function read1() {
              let val1 = null;
              try{
                  val1 = await readFile('./data/number1.txt');
                  console.log(val1);
              }catch(e) {
                  console.log(e);
              }
          }
          async function read2() {
              let val2 = null;
              try{
                  val2 = await readFile('./data/number2.txt');
                  console.log(val2); 
              }catch(e) {
                  console.log(e);
              }
          }
          async function read3() {
              let val3 = null;
              try {
                  val3 = await readFile('./data/number3.txt');
                  console.log(val3);
              }catch(e) {
                  console.log(e);
              }
          }
          function readAll(...args) {
                  args.forEach((ele) => {
                      ele();
                  })
          }
          readAll(read1, read2, read3);
          複製代碼
  • 異步編程的最高境界,就是根本不用關心它是否是異步。一句話,async函數就是Generator函數的語法糖。
  • 優勢:
    • 內置執行器
      • Genarator函數的執行必須依靠執行器,因此纔有Co函數庫,而async函數自帶執行器,也就是說,async函數的執行,與普通的函數如出一轍,只要一行。
    • 更好的語義化
      • async和await比起星號與yield語義化更清楚了,async表示函數裏有異步操做,async表示緊跟在後面的表達式須要等待結果。
    • 更廣的適應性
      • co函數庫約定,yield命令後面只能是Thunk函數或Promise對象,而async函數的await命令後面,能夠跟Promise對象和原始類型的值(數值丶字符串丶布爾值 但這時等同於同步操做)
    • 返回值是Promise
      • async函數返回值是Promise對象,比Generator函數返回的Iterator對象方便,能夠直接使用then()方法進行調用。
  • async函數是很是新的語法功能,新到都不屬於ES6,而是屬於ES7。目前,它任處於提案階段,可是轉碼器Babel和regenerator已經支持,轉碼後就能使用。
  • 注意點
    • await命令後面的Promise對象,運行結果多是rejected,因此最好把await命令放在try...catch代碼塊中。
    • await命令只能用在async函數中,若是用在普通函數,就會報錯,上面的代碼會報錯,由於await函數用在普通函數之中了,可是,若是將foreach方法的參數改爲async函數,也會有問題。上面的代碼可能不會正常工做,緣由是這時三個db.post操做將是併發執行,也就是同時執行,而不是繼發執行,正確的寫法是採用for循環
    • 若是確實但願多個請求併發執行,可使用Promise.all方法。

Rest參數

  • 目的:
    • Rest參數用於獲取函數的多餘參數,組成一個數組,放在形參的最後,這樣就不須要arguments對象了。
    • 主要用於處理不定參數
    funtion fn(a, b, ...args) {
          // ...
    }
    複製代碼
  • Rest參數和arguments對象區別:
    • rest參數只包括那些沒有給出名稱的參數,arguments包含全部參數。
    • arguments對象不是真正的數組,而rest參數是數組實例,能夠直接應用sort,map,等方法
    • arguments對象擁有一些本身額外的功能(好比callee)
    • Rest參數簡化了使用arguments獲取多餘參數的方法
  • 注意:
    • rest參數以後不能再有其餘參數,不然會報錯。
    • 函數的length屬性,不包括rest參數
    • Rest參數能夠被結構化(通俗一點,將rest參數的數據解析後一一對應),不要忘記參數用[]括起來,由於它是數組。
      function fn(...[a, b, c]) {
          console.log(a + b + c);
      }
      fn(1);
      fn(1, 2, 3);
      fn(1, 2, 3, 4);
      複製代碼

Iterator

  • 目的:ES6引入這個Iterator是了這些數據有統一的遍歷或者進行for of ...Array.from等操做,方便咱們寫代碼的時候不用大範圍的重構。
  • 定義
    • Iterator的思想取自於咱們的迭代模式
    • forEach能夠迭代數組,for in能夠迭代對象,$.each只能迭代數組和對象,Iterator還能迭代Set和Map。
    • Set和Map原型上都有Symbol.Iterator這個迭代口,只要有這個接口均可以被for..of迭代
      // 把不可迭代數據變成可迭代數據
      let obj = {
          0: 'a',
          1: 'b',
          2: 'c',
          length: 3,
          [Symbol.iterator]: function() {
              let curIndex = 0;
              let next = () => {
                  return {
                      value: this[curIndex],
                      done: this.length == ++ curIndex
                  }
              }
              return {
                  next
              }
          }
      }
      console.log([...obj])
      for(let p of obj) {
          console.log(p);
      }
      複製代碼
    • Generator要去生成一個迭代對象,爲何生成迭代對象呢,就是根據Symbol.iterator。
  • 分類
    • 內部迭代器
      • Array.prototype.forEach就是咱們所說的迭代器,它就是用迭代模式的思想作出的函數,本質是內部迭代器。
    • 外部迭代器
      • 代碼實現(ES5實現)
        function OuterInterator() {
            let curIndex = 0;
            let next = () => {
                return {
                    value: o[curIndex],
                    done: o.length == ++ curIndex;
                }
            }
            return {
                next
            }
        }
        let oIt = OuterIterator(arr);
        複製代碼

Symbol

  • 特色
    • 惟一性
    • arr丶Set丶Map丶arguments丶nodelist都有這個屬性Symbol.iterator 這個屬性等於iterator迭代函數

異步加載JavaScript

  • JS加載的缺點:加載工具方法不必阻塞文檔,過多的JS加載會影響頁面效率,一旦網速很差,那麼整個網站都將等待JS加載而不進行後續等渲染工做
  • 目的:有些工具方法須要按需加載,用到再加載,不用不加載。
  • JavaScript異步加載的三種方案
    • defere異步加載
      • 但要等到dom文檔所有解析完纔會被執行,只有IE能用。(執行時不阻塞頁面加載)(支持IE)
    • async異步加載
      • 加載完就執行,async只能加載外部腳本,不能把JS寫在script標籤裏面(執行時不阻塞頁面加載)
    • 建立script,插入到DOM中,加載完callBack
      • 代碼實現
        function loadScript(url, callback) {
            var script = document.createElement('script'),
                script.type = 'text/javaScript';
            if(script.readyState) { // IE
                if(script.onreadystatechange === 'complete' || script.onreadystatechange === 'loaded') {
                    callback();
                }
            }else { // FireFox丶Safari丶Chrome and Opera 
                script.onload = function() {
                    callback();
                }
            }    
            script.url = url;
            document.head.appendChild(script);
        }
        複製代碼

異步加載CSS

  • 經過JS動態插入link標籤來異步載入CSS代碼
    var myLink = document.createElement('link');
    myLink.rel = 'stylesheet';
    'myLink.href = './index.css';
    documemt.head.insertBefore(myLink, document.head.childNodes[document.head.childNodes.length - 1].nextSibling);
    複製代碼
  • 利用link上的media屬性
    • 將它設置爲和用戶當前瀏覽器環境不匹配的值,好比:media="print",甚至能夠設置爲一個徹底無效的值media="jscourse"之類的。
    • 這樣的話,瀏覽器就會認爲CSS文件優先級很是低,就會在不阻塞的狀況下進行加載。可是爲了讓CSS規則失效,最後仍是要將media值改對才行。
      <link rel="style" href="css.style" media="jscourse" onload="this.media='all'">
      複製代碼
  • rel="preload"
    • 經過preload屬性值就是告訴瀏覽器這個資源隨後會用到,請提早加載好。因此你看它加載完畢後,仍是須要將rel值改回去,這才能讓CSS生效。
    • 語義更好一些
    • as="style"這個屬性,因此preload不只僅能夠用在CSS文件上,而是能夠用在絕大多數的資源文件上。好比JS文件
      <link rel="preload" href="sccriptfile.js" as="script">
      
      // 要用的時候 就建立一個script標籤加載它,這個時候直接從緩存中拿到這個文件了,由於提早加載好了
      var oScript = document.createElement('script');
      script.src = 'index.js';
      document.body.appendChild(script); 
      複製代碼
    • Chrome完美支持,其餘不支持。

參考連接:www.cnblogs.com/cjx-work/p/… juejin.im/post/596e14…node

相關文章
相關標籤/搜索