来聊聊前端的异步

异步

  • 出现的缘由
    • 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

相关文章
相关标签/搜索