您现在的位置是:首页 >学无止境 >2023年前端面试高频考点之 通信(渲染、http、缓存、异步、跨域)网站首页学无止境

2023年前端面试高频考点之 通信(渲染、http、缓存、异步、跨域)

参宿7 2024-10-02 12:01:05
简介域名解析端口

目录

浏览器从输入url到渲染页面 过程⭐⭐⭐

Http和Https区别⭐⭐⭐

GET和POST发送请求⭐⭐⭐

异同

 http版本⭐⭐⭐

http状态码⭐⭐⭐

TCP⭐⭐⭐

三次握手

四次挥手

流量控制(滑动窗口机制)

拥塞控制

keep-alive持久连接

TCP⭐⭐⭐

三次握手

四次挥手

流量控制(滑动窗口机制)

拥塞控制

keep-alive持久连接

异步⭐⭐⭐

 Promise(ES6 解决地狱回调)⭐⭐⭐

promise调用then,

兼容同步任务

Promise.prototype.catch()

Promise.prototype.finally()

Promise.resolve()

Promise.reject()

Promise.all()

Promise.race()

Promise.all()哪怕一个请求失败了也能得到其余正确的请求结果的解决方案⭐⭐

Mypromise

fetch(ES6 拉取网络资源)

 async/await函数

跨域通信⭐⭐⭐


浏览器从输入url到渲染页面 过程⭐⭐⭐

查找缓存

  • 合成 URL

浏览区会判断用户输入是合法 URL(Uniform Resource Locator,统一资源定位器),比如用户输入的是搜索的关键词,默认的搜索引擎会合成新的,

如果符合url规则会根据url协议,在这段内容加上协议合成合法的url 

  • 查找缓存

网络进程获取到 URL,

先去本地缓存中查找是否有缓存资源,如果有则拦截请求,直接将缓存资源返回给浏览器进程;

否则,进入网络请请求阶段;    

  • DNS 解析:(域名系统Domain Name System)

DNS 查找数据缓存服务中是否缓存过当前域名信息,有则直接返回;

否则,会进行 DNS 解析返回域名对应的 IP 和端口号

如果没有指定端口号,http 默认 80 端口https 默认 443

如果是 https 请求,还需要建立 TLS 连接;(传输层安全性协议Transport Layer Security)

TCP连接

  • 建立 TCP 连接:

TCP 三次握手与服务器建立连接,然后进行数据的传输;

  • 发送 HTTP 请求

浏览器首先会向服务器发送请求行,它包含了请求方法、请求 URI (统一资源标识符Uniform Resource Identifier HTTP 协议的版本

还会发送请求头,告诉服务器一些浏览器的相关信息,比如浏览器内核,请求域名

  • 服务器处理请求

服务器首先返回响应头+响应行响应行包括协议版本和状态码

  • 页面渲染:

查看响应头的信息,做不同的处理,比如重定向,存储cookie 看看content-type的值,根据不同的资源类型来用不同的解析方式

渲染详情可见2023年最全前端面试题考点HTML5+CSS3+JS_参宿7的博客-CSDN博客

  • 断开 TCP 连接:

数据传输完成,正常情况下 TCP 将四次挥手断开连接

Http和Https区别⭐⭐⭐

1.`HTTP` 的URL 以http:// 开头,而HTTPS 的URL 以https:// 开头
2.``HTTP` 无法加密,而HTTPS 对传输的数据进行加密,安全
3.`HTTP` 标准端口是80 ,而 HTTPS 的标准端口是443
4.`在OSI` 网络模型中,HTTP工作于应用层,而HTTPS 的安全传输机制工作在传输层

HTTPS是密文传输数据,HTTP是明文传输数据。

HTTPS协议 = HTTP协议 + SSl/TLS协议

用SSL/TLS对数据进行加密和解密

SSL的全称是Secure Sockets Layer,即安全套接层协议

TLS的全称是Transport Layer Security,即安全传输层协议

对数据进行对称加密,对 对称加密所要使用的秘钥 进行非对称加密传输。

  • 服务端的公钥和私钥,用来进行非对称加密
  • 客户端生成的随机秘钥,用来进行对称加密

GET和POST发送请求⭐⭐

HTTP协议中的两种发送请求的方法。

异同

:GET和POST本质上就是TCP链接

数据包数量:GET产生一个TCP数据包;POST产生两个TCP数据包

(并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。)

过程

对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);

对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

对于POST请求方式,可以将请求数据打包在请求体中,

并通过Headers头部信息里的“Content-Type”字段指定请求体的数据类型为JSON,并

且在服务端返回相应头信息的时候也指定返回类型为JSON。

以下是一个使用POST方法请求并返回JSON类型数据的示例代码:

fetch('yourUrl', { 
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(requestData)
})
.then(function(response) {
  return response.json(); // 解析并返回JSON格式数据
})
.then(function(json) {
  console.log('请求成功返回结果', json);    
})
.catch(function (error) {
  console.log("请求出现了问题::", error);
});

其中 requestData 是一个 JavaScript 对象,可以通过 JSON.stringify() 方法将其转换为 JSON 字符串,然后放到 POST 请求的 Body 中。

应用

在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。

在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。

因为GET一般用于查询信息,POST一般用于提交某种信息进行某些修改操作(私密性的信息如注册、登陆)

所以GET在浏览器回退不会再次请求,POST会再次提交请求

因为GET在浏览器回退不会再次请求,POST会再次提交请求

所以GET请求会被浏览器主动缓存,POST不会,要手动设置
       GET请求参数会被完整保留在浏览器历史记录里,POST中的参数不会

因为 GET请求参数会被完整保留在浏览器历史记录里
所以GET请求在URL中传送的参数是有长度限制的,而POST没有限制

因为GET参数通过URL传递,POST放在Request body
所以GET参数暴露在地址栏不安全,POST放在报文内部更安全

 http版本⭐⭐⭐

1.0->1.1(一次传输多个文件,默认Connection: keep-alive

http1.x解析基于文本

http2.0采用二进制格式,新增特性 多路复用、header压缩、服务端推送(静态html资源)

http状态码⭐⭐⭐

状态码是由3位数组成,第一个数字定义了响应的类别,且有五种可能取值:
    1xx Informational(信息状态码)      接受请求正在处理
    2xx Success(成功状态码)            请求正常处理完毕

    3xx Redirection(重定向状态码)      需要附加操作已完成请求

    4xx Client Error(客户端错误状态码)  服务器无法处理请求
    5xx Server Error(服务器错误状态码)  服务器处理请求出错

常见状态码:

    200 响应成功
    204 返回无内容

    301永久重定向  (请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。)
    302临时重定向(服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。)
    304资源缓存(自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。)

    400 错误请求(请求格式错误,服务器不理解请求的语法。)
    422 无法处理(请求格式正确,但是由于含有语义错误,无法响应)
    401 未授权(请求要求身份验证。)
    403服务器禁止访问
    404服务器找不到请求的网页
    

    500 502服务器内部错误
    504 服务器繁忙

TCP⭐⭐⭐

三次握手

四次挥手

TIME-WAIT:2 MSL (Maximum segment lifetime) 最长报文最大生存时间

流量控制(滑动窗口机制

让发送方的发送速率不要太快,要让接收方来得及接收。

还可接收的窗口是 rwnd = 400 ”(receiver window) 。

发送方的发送窗口不能超过接收方给出的接收窗口的数值。TCP的窗口单位是字节,不是报文段。

拥塞控制

拥塞:资源供不应求

  • 慢开始、拥塞避免
  • 快重传、快恢复

keep-alive持久连接

为了能够提升效率,在 HTTP 1.1 规范中把 Connection 头写入了标准,并且默认启用。

浏览器 或 服务器在HTTP头部加上 Connection: keep-alive,TCP 就会一直保持连接。

它会在隔开一段时间之后发送几次没有数据内容的网络请求来判断当前连接是不是应该继续保留。

可能会造成大量的无用途连接,白白占用系统资源

TCP⭐⭐⭐

三次握手

四次挥手

TIME-WAIT:2 MSL (Maximum segment lifetime) 最长报文最大生存时间

流量控制(滑动窗口机制

让发送方的发送速率不要太快,要让接收方来得及接收。

还可接收的窗口是 rwnd = 400 ”(receiver window) 。

发送方的发送窗口不能超过接收方给出的接收窗口的数值。TCP的窗口单位是字节,不是报文段。

拥塞控制

拥塞:资源供不应求

  • 慢开始、拥塞避免
  • 快重传、快恢复

keep-alive持久连接

为了能够提升效率,在 HTTP 1.1 规范中把 Connection 头写入了标准,并且默认启用。

浏览器 或 服务器在HTTP头部加上 Connection: keep-alive,TCP 就会一直保持连接。

它会在隔开一段时间之后发送几次没有数据内容的网络请求来判断当前连接是不是应该继续保留。

可能会造成大量的无用途连接,白白占用系统资源

异步⭐⭐⭐

  • 异步是通过一次次的循环事件队列来实现的.例如 setTimeout setInterval xmlHttprequest 等
  • 同步阻塞式的IO,在高并发环境会是一个很大的性能问题,

所以同步一般只在基础框架的启动时使用,用来加载配置文件,初始化程序什么的.

node单线程的,网络请求,浏览器事件等操作都需要使用异步的方法。

 Promise(ES6 解决地狱回调)⭐⭐⭐

回调函数
当一个函数作为参数传入另一个函数中,并且它不会立即执行,只有当满足一定条件后该函数才可以执行,这种函数就称为回调函数,例如setTimeout

回调地狱:回调函数套回调函数,回调的多重嵌套,会导致代码可读低、编写费劲、容易出错,故而被称为 callback hell

Promise:ES6异步操作的一种解决方案

Promise 构造函数接受一个函数作为参数,该函数是同步的并且会被立即执行,称为起始函数

起始函数包含两个函数参数 resolvereject,分别表示 Promise 成功和失败的状态。

起始函数执行成功时,它应该调用 resolve 函数并传递成功的结果。

起始函数执行失败时,它应该调用 reject 函数并传递失败的原因。

promise 有三个状态:

pending[待定]初始状态,fulfilled[实现]操作成功,rejected[被否决]操作失败

如果不使用 resove 和 reject 两个函数 状态为pendding

Promise 构造函数返回一个 Promise 对象,该对象具有以下几个方法:

  • then:用于处理 Promise 成功状态的回调函数,参数为resolve传递的参数。
  • catch:用于处理 Promise 失败状态的回调函数,参数为reject传递的参数。
  • finally:无论 Promise 是成功还是失败,都会执行的回调函数。
const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    if (Math.random() < 0.5) {
      resolve('success');
    } else {
      reject('error');
    }
  }, 1000);
});
 
promise.then(result => {
  console.log(result);
}).catch(error => {
  console.log(error);
});
new Promise(function (resolve, reject) {
    var a = 0;
    var b = 1;
    if (b == 0) reject("Divide zero");
    else resolve(a / b);
}).then(function (value) {
    console.log("a / b = " + value);
}).catch(function (err) {
    console.log(err);
}).finally(function () {
    console.log("End");
});

promise调用then,

如果非promise,会将值包装成promise

new Promise(function (resolve, reject) {
    setTimeout(function () {
        console.log("First");
        resolve();
    }, 1000);
}).then(function () {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log("Second");
            resolve();
        }, 4000);
    });
}).then(function () {
    setTimeout(function () {
        console.log("Third");
    }, 3000);
});
new Promise(function (resolve, reject) {
    console.log(1111);
    resolve(2222);
}).then(function (value) {
    console.log(value);
    return 3333;
}).then(function (value) {
    console.log(value);
    throw "An error";
}).catch(function (err) {
    console.log(err);
});
// then方法
then(resolveFn, rejectFn) {
  //return一个新的promise
  return new MyPromise((resolve, reject) => {
    //把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
    const fulfilledFn = value => {
      try {
        //执行第一个(当前的)Promise的成功回调,并获取返回值
        let x = resolveFn(value)
        //分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
        //这里resolve之后,就能被下一个.then()的回调获取到返回值,从而实现链式调用
        x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
      } catch (error) {
        reject(error)
      }
    }
    //把后续then收集的依赖都push进当前Promise的成功回调队列中(_resolveQueue), 这是为了保证顺序调用
    this._resolveQueue.push(fulfilledFn)

    //reject同理
    const rejectedFn  = error => {
      try {
        let x = rejectFn(error)
        x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
      } catch (error) {
        reject(error)
      }
    }
    this._rejectQueue.push(rejectedFn)
  })
}
  • 值穿透:根据规范,如果 then() 接收的参数不是function,那么我们应该忽略它。

如果没有忽略,当then()回调不为function时将会抛出异常,导致链式调用中断

  • 处理状态为resolve/reject的情况:其实我们上边 then() 的写法是对应状态为pending的情况,但是有些时候,resolve/reject 在 then() 之前就被执行(比如Promise.resolve().then()),如果这个时候还把then()回调push进resolve/reject的执行队列里,那么回调将不会被执行,因此对于状态已经变为fulfilledrejected的情况,我们直接执行then回调
// then方法,接收一个成功的回调和一个失败的回调
  then(resolveFn, rejectFn) {
    // 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
    typeof resolveFn !== 'function' ? resolveFn = value => value : null
    typeof rejectFn !== 'function' ? rejectFn = reason => {
      throw new Error(reason instanceof Error? reason.message:reason);
    } : null
  
    // return一个新的promise
    return new MyPromise((resolve, reject) => {
      // 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
      const fulfilledFn = value => {
        try {
          // 执行第一个(当前的)Promise的成功回调,并获取返回值
          let x = resolveFn(value)
          // 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        } catch (error) {
          reject(error)
        }
      }
  
      // reject同理
      const rejectedFn  = error => {
        try {
          let x = rejectFn(error)
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        } catch (error) {
          reject(error)
        }
      }
  
      switch (this._status) {
        // 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
        case PENDING:
          this._resolveQueue.push(fulfilledFn)
          this._rejectQueue.push(rejectedFn)
          break;
        // 当状态已经变为resolve/reject时,直接执行then回调
        case FULFILLED:
          fulfilledFn(this._value)    // this._value是上一个then回调return的值(见完整版代码)
          break;
        case REJECTED:
          rejectedFn(this._value)
          break;
      }
    })
  }

兼容同步任务

executor是异步任务:

Promise的执行顺序是new Promise -> then()收集回调 -> resolve/reject执行回调

executor是同步任务:new Promise -> resolve/reject执行回调 -> then()收集回调,resolve的执行跑到then之前去了,

为了兼容这种情况,给resolve/reject执行回调的操作包一个setTimeout,让它异步执行。

//Promise/A+规定的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  // 构造方法接收一个回调
  constructor(executor) {
    this._status = PENDING     // Promise状态
    this._value = undefined    // 储存then回调return的值
    this._resolveQueue = []    // 成功队列, resolve时触发
    this._rejectQueue = []     // 失败队列, reject时触发

    // 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
    let _resolve = (val) => {
      //把resolve执行回调的操作封装成一个函数,放进setTimeout里,以兼容executor是同步代码的情况
      const run = () => {
        if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
        this._status = FULFILLED              // 变更状态
        this._value = val                     // 储存当前value

        // 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
        // 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
        while(this._resolveQueue.length) {    
          const callback = this._resolveQueue.shift()
          callback(val)
        }
      }
      setTimeout(run)
    }
    // 实现同resolve
    let _reject = (val) => {
      const run = () => {
        if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
        this._status = REJECTED               // 变更状态
        this._value = val                     // 储存当前value
        while(this._rejectQueue.length) {
          const callback = this._rejectQueue.shift()
          callback(val)
        }
      }
      setTimeout(run)
    }
    // new Promise()时立即执行executor,并传入resolve和reject
    executor(_resolve, _reject)
  }

  // then方法,接收一个成功的回调和一个失败的回调
  then(resolveFn, rejectFn) {
    // 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
    typeof resolveFn !== 'function' ? resolveFn = value => value : null
    typeof rejectFn !== 'function' ? rejectFn = reason => {
      throw new Error(reason instanceof Error? reason.message:reason);
    } : null
  
    // return一个新的promise
    return new MyPromise((resolve, reject) => {
      // 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
      const fulfilledFn = value => {
        try {
          // 执行第一个(当前的)Promise的成功回调,并获取返回值
          let x = resolveFn(value)
          // 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        } catch (error) {
          reject(error)
        }
      }
  
      // reject同理
      const rejectedFn  = error => {
        try {
          let x = rejectFn(error)
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        } catch (error) {
          reject(error)
        }
      }
  
      switch (this._status) {
        // 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
        case PENDING:
          this._resolveQueue.push(fulfilledFn)
          this._rejectQueue.push(rejectedFn)
          break;
        // 当状态已经变为resolve/reject时,直接执行then回调
        case FULFILLED:
          fulfilledFn(this._value)    // this._value是上一个then回调return的值(见完整版代码)
          break;
        case REJECTED:
          rejectedFn(this._value)
          break;
      }
    })
  }
}

Promise.prototype.catch()

catch()方法返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。

//catch方法其实就是执行一下then的第二个回调
catch(rejectFn) {
  return this.then(undefined, rejectFn)
}

Promise.prototype.finally()

finally()方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。在finally之后,还可以继续then。并且会将值原封不动的传递给后面的then

//finally方法
finally(callback) {
  return this.then(
    value => MyPromise.resolve(callback()).then(() => value),             // MyPromise.resolve执行回调,并在then中return结果传递给后面的Promise
    reason => MyPromise.resolve(callback()).then(() => { throw reason })  // reject同理
  )
}
复制代码

MyPromise.resolve(callback())的意义:这个写法其实涉及到一个finally()的使用细节,finally()如果return了一个reject状态的Promise,将会改变当前Promise的状态,这个MyPromise.resolve就用于改变Promise状态,在finally()没有返回reject态Promise或throw错误的情况下,去掉MyPromise.resolve也是一样的

Promise.resolve()

Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。如果该值为promise,返回这个promise;如果这个值是thenable(即带有"then" 方法)),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。

//静态的resolve方法
static resolve(value) {
  if(value instanceof MyPromise) return value // 根据规范, 如果参数是Promise实例, 直接return这个实例
  return new MyPromise(resolve => resolve(value))
}
复制代码

Promise.reject()

Promise.reject()方法返回一个带有拒绝原因的Promise对象。

//静态的reject方法
static reject(reason) {
  return new MyPromise((resolve, reject) => reject(reason))
}

Promise.all()

Promise.all(iterable)方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve)

如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果

//静态的all方法
static all(promiseArr) {
  let index = 0
  let result = []
  return new MyPromise((resolve, reject) => {
    promiseArr.forEach((p, i) => {
      //Promise.resolve(p)用于处理传入值不为Promise的情况
      MyPromise.resolve(p).then(
        val => {
          index++
          result[i] = val
          //所有then执行后, resolve结果
          if(index === promiseArr.length) {
            resolve(result)
          }
        },
        err => {
          //有一个Promise被reject时,MyPromise的状态变为reject
          reject(err)
        }
      )
    })
  })
}
复制代码

Promise.race()

Promise.race(iterable)方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

static race(promiseArr) {
  return new MyPromise((resolve, reject) => {
    //同时执行Promise,如果有一个Promise的状态发生改变,就变更新MyPromise的状态
    for (let p of promiseArr) {
      MyPromise.resolve(p).then(  //Promise.resolve(p)用于处理传入值不为Promise的情况
        value => {
          resolve(value)        //注意这个resolve是上边new MyPromise的
        },
        err => {
          reject(err)
        }
      )
    }
  })
}

Promise.all()哪怕一个请求失败了也能得到其余正确的请求结果的解决方案

await(串行):如果在一个async的方法中,有多个await操作的时候,程序会变成完全的串行操作,一个完事等另一个但是为了发挥node的异步优势,

当异步操作之间不存在结果的依赖关系时,可以使用promise.all来实现并行,all中的所有方法是一同执行的。

执行后的结果:

async函数中,如果有多个await关键字时,如果有一个await的状态变成了rejected,那么后面的操作都不会继续执行,promise也是同理

await的返回结果就是后面promise执行的结果,可能是resolves或者rejected的值使用场景循环遍历方便了代码需要同步的操作(文件读取,数据库操作等)
 

promise.all并行(同时)执行promise,当其中任何一个promise 出现错误的时候都会执行reject,导致其它正常返回的数据也无法使用

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');//setTimeout(function[, delay, arg1, arg2, ...]);
});

Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// Expected output: Array [3, 42, "foo"]

map的每一项都是promise,catch方法返回值会被promise.reslove()包裹,这样传进promise.all的数据都是resolved状态的。

let p1 = Promise.resolve(1)
let p2 = Promise.resolve(2)
let p3 = Promise.resolve(3)
let p4 = Promise.resolve(4)
let p5 = Promise.reject("error")
let arr = [p1,p2,p3,p4,p5];

let all = Promise.all(arr.map((promise)=>promise.catch((e)=>{console.log("错误信息"+e)})))
all.then(res=>{console.log(res)}).catch(err=>console.log(err));

Mypromise

//Promise/A+规范的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  // 构造方法接收一个回调
  constructor(executor) {
    this._status = PENDING     // Promise状态
    this._resolveQueue = []    // 成功队列, resolve时触发
    this._rejectQueue = []     // 失败队列, reject时触发

    // 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
    let _resolve = (val) => {
      if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
      this._status = FULFILLED              // 变更状态

      // 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
      // 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
      while(this._resolveQueue.length) {    
        const callback = this._resolveQueue.shift()
        callback(val)
      }
    }
    // 实现同resolve
    let _reject = (val) => {
      if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
      this._status = REJECTED               // 变更状态
      while(this._rejectQueue.length) {
        const callback = this._rejectQueue.shift()
        callback(val)
      }
    }
    // new Promise()时立即执行executor,并传入resolve和reject
    executor(_resolve, _reject)
  }

  // then方法,接收一个成功的回调和一个失败的回调
  then(resolveFn, rejectFn) {
    this._resolveQueue.push(resolveFn)
    this._rejectQueue.push(rejectFn)
  }
}

【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理) - 掘金

fetch(ES6 拉取网络资源)

以前发送请求,使用ajax或者axios,现在还可以使用fetch。这个是window自带的方法,和xhr是一个级别的。(xhr=new XMLHttpRequest())

fetch(input[, init]);

// url (必须), options (可选)
fetch('/some/url', {method: 'get'})

.then(function(response) {

})

.catch(function(err) {
    // 出错了;等价于 then 的第二个参数,但这样更好用更直观 :(

});

第二个then接收的才是后台传过来的真正的数据

window.fetch(url, { method: 'get'})   //fetch() 返回响应的promise 
    // 第一个then  设置请求的格式
        .then(res => return res.json())  // 第一层then返回的是:响应的报文对象  
        .then((data) => {             //第二层:如果想要使用第一层的数据,必须把数据序列化  
                                       res.json() 返回的对象是一个 Promise对象再进行then()
         <!-- data为真正数据 -->
    }).catch(e => console.log("Oops, error", e))

Fetch()方法介绍_小钢炮vv的博客-CSDN博客_fetch

 async/await函数

异步函数实际上原理与 Promise 原生 API 的机制是一模一样的,只不过更便于阅读。

相当于promise用法的语法糖,async/await实际上是对Generator(生成器)的封装

ES6 引入了 Generator 函数,被ES7 提出的async/await取代了,

将Generator函数的星号 * 替换成async,将yield替换成await。

相对于Generator函数的改进:自带执行器,会自动执行。

await规定了异步操作只能一个一个排队执行,从而达到用同步方式,执行异步操作的效果

Promise.resolve(a)
  .then(b => {
    // do something
  })
  .then(c => {
    // do something
  })

//等价于
async () => {
  const a = await Promise.resolve(a);
  const b = await Promise.resolve(b);
  const c = await Promise.resolve(c);
}

async关键字+函数,表明该函数内部有异步操作

函数返回的是一个状态为fulfilledPromise对象

如果结果是,会经过Promise包装返回。
如果是promise则会等待promaise 返回结果,否则,就直接返回对应的值,

await 操作符+promise对象用于组成表达式

awai+,就会转到一个立即resolve的Promise对象。

async function asyncFunc() {
    let value = await new Promise(
        function (resolve, reject) {
            resolve("Return value");
        }
    );
    console.log(value);
}
asyncFunc();

await只能在async函数中出现, 普通函数直接使用会报语法错误SyntaxError

await语句后的Promise对象变成reject状态时,那么整个async函数会中断,后面的程序不会继续执行

处理异常的机制将用 try-catch 块实现

async function asyncFunc() {
    try {
        await new Promise(function (resolve, reject) {
            throw "Some error"; // 或者 reject("Some error")
        });
    } catch (err) {
        console.log(err);
        // 会输出 Some error
    }
}
asyncFunc();

async和await的讲解_傲娇的koala的博客-CSDN博客_async await

跨域通信⭐⭐⭐

所谓同源(域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)

同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能

如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。(跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。)

同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。

服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。)

服务器与服务器之间是可以通信的不受同源策略的影响:Nginx反向代理,proxy代理

同源策略限制内容有:

  • Cookie、LocalStorage、IndexedDB 等存储性内容
  • DOM 节点
  • AJAX 请求发送后,结果被浏览器拦截了

允许跨域加载资源的标签:

  • script标签的跨域功能:<img src=XXX><script src=XXX>
  • 规定外部脚本文件的 URL:<link href=XXX>

跨域的解决方案思路两种,绕过去和cors;

  • iframe方式可传递数据,但组织和控制代码逻辑太复杂,鸡肋;
  • jsonp适合加载不同域名的js、css,img静态资源,现在浏览器兼容性高了,以及受限于仅get方式,逐步淘汰了;
  • Nginx反向代理和nodejs中间件跨域原理都相似,是绕过去的方式,是从古至今通用的没完解决方案,适合前后端分离的前端项目调后端接口。都是搭建一个服务器,直接在服务器端请求HTTP接口,缺点也许是服务器压力大一点,实际中那点压力根本不是大问题;同时反向代理更适合内部应用间访问和共享;
  • cors才是真正的称得上跨域请求解决方案(支持所有类型的HTTP请求,但浏览器IE10以下不支持)适合做ajax各种跨域请求;
  • websocket都是HTML5新特性兼容性不是很好,只适用于主流浏览器和IE10+。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。