专栏视角

通过浏览器搭建前端知识体系:
image.png

  1. 浏览器架构设计
  2. js引擎工作原理
  3. 页面工作原理
  4. 浏览器网络
  5. 浏览器安全

    浏览器中的页面循环系统 (6讲)

    19 | Promise:使用Promise,告别回调函数

    **
  • 1.1 js的异步编程模型

页面都是执行在主线程上,而一些耗时的任务,如:下载网络文件任务、获取摄像头等设备信息任务,会交给主线程之外的进程或线程去执行。
image.png
页面主线程发起了一个任务,交给了另一个进程去处理,主线程继续处理消息队列中的任务;等待进程将任务处理结果交给渲染线程的消息队列中排队,由循环事件系统取出消息队列中的任务进行处理,并执行相关的回调操作。
web页面的单线程架构决定了异步回调,而异步回调影响了我们的编码方式(多次异步回调导致代码逻辑不连贯、 不线性、不符合人的直觉)。


参考: 什么是回调函数满足下述3 个条件的就是回调函数:
作为实参传入函数1,自己定义的2,由别人或系统调用3的函数
回调函数有同步的:

  • Promise的excutor函数(在使用 new Promise() 创建一个新对象的时候,传递给Promise构造函数的那个函数参数); ```javascript //代码演示 let arr = [1,2,3]; arr.forEach(function (item) {//遍历回调,该同步回调函数不会放入列队,一上来就要执行完 console.log(item); }) console.log(‘forEach()之后’);
  1. 也有异步的:
  2. - 定时器回调
  3. - ajax回调
  4. - Promise的成功或失败的回调
  5. ```javascript
  6. //代码演示
  7. setTimeout(function () {//该异步回调函数,会放在队列中
  8. console.log('setTimeout()正在执行');
  9. },0);
  10. console.log('setTimeout()之后');
  11. //结果:先输出“setTimeout()之后”,最后输出“setTimeout()正在执行”

-


  • 1.2 封装异步代码,使处理逻辑变得线性

由于我们重点关注的是输入内容(请求信息)和输出内容(回复信息),至于中间的异步请求过程,我们不想在代码里面体现太多,因为这会干扰核心的代码逻辑。整体思路如下图所示:
image.png

  • 1.3 新的问题:回调地狱

    1. XFetch(makeRequest('https://time.geekbang.org/?category'),
    2. function resolve(response) {
    3. console.log(response)
    4. XFetch(makeRequest('https://time.geekbang.org/column'),
    5. function resolve(response) {
    6. console.log(response)
    7. XFetch(makeRequest('https://time.geekbang.org')
    8. function resolve(response) {
    9. console.log(response)
    10. }, function reject(e) {
    11. console.log(e)
    12. })
    13. }, function reject(e) {
    14. console.log(e)
    15. })
    16. }, function reject(e) {
    17. console.log(e)
    18. })

    存在2 个问题:

  • 第一是嵌套调用,下面的任务依赖上个任务的请求结果,并在上个任务的回调函数内部执行新的业务逻辑,这样当嵌套层次多了之后,代码的可读性就变得非常差了。

  • 第二是任务的不确定性,执行每个任务都有两种可能的结果(成功或者失败),所以体现在代码中就需要对每个任务的执行结果做两次判断,这种对每个任务都要进行一次额外的错误处理的方式,明显增加了代码的混乱程度。

解决思路:

  • 第一是消灭嵌套调用;
  • 第二是合并多个任务的错误处理。

2.Promise 解决了什么问题?
Promise 解决的是异步编码风格的(也就是上面提到的2个)问题,而不是一些其他的问题。
Promise是怎么消灭嵌套调用合并多个任务的错误处理的。

解决嵌套回调:

  1. promise实现了延迟绑定回调函数onResolve/onReject;
  2. 将回调函数 onResolve 的返回值穿透到最外层。

解决合并多任务的错误处理:

  1. Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被 onReject 函数处理或 catch 语句捕获为止。
    1. const PENDING = "pending";
    2. const RESOLVED = "resolved";
    3. const REJECTED = "rejected";
    4. function MyPromise(fn) {
    5. // 保存初始化状态
    6. var self = this;
    7. // 初始化状态
    8. this.state = PENDING;
    9. // 用于保存 resolve 或者 rejected 传入的值
    10. this.value = null;
    11. // 用于保存 resolve 的回调函数
    12. this.resolvedCallbacks = [];
    13. // 用于保存 reject 的回调函数
    14. this.rejectedCallbacks = [];
    15. // 状态转变为 resolved 方法
    16. function resolve(value) {
    17. // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变
    18. if (value instanceof MyPromise) {
    19. return value.then(resolve, reject);
    20. }
    21. // 保证代码的执行顺序为本轮事件循环的末尾
    22. setTimeout(() => {
    23. // 只有状态为 pending 时才能转变,
    24. if (self.state === PENDING) {
    25. // 修改状态
    26. self.state = RESOLVED;
    27. // 设置传入的值
    28. self.value = value;
    29. // 执行回调函数
    30. self.resolvedCallbacks.forEach(callback => {
    31. callback(value);
    32. });
    33. }
    34. }, 0);
    35. }
    36. // 状态转变为 rejected 方法
    37. function reject(value) {
    38. // 保证代码的执行顺序为本轮事件循环的末尾
    39. setTimeout(() => {
    40. // 只有状态为 pending 时才能转变
    41. if (self.state === PENDING) {
    42. // 修改状态
    43. self.state = REJECTED;
    44. // 设置传入的值
    45. self.value = value;
    46. // 执行回调函数
    47. self.rejectedCallbacks.forEach(callback => {
    48. callback(value);
    49. });
    50. }
    51. }, 0);
    52. }
    53. // 将两个方法传入函数执行
    54. try {
    55. fn(resolve, reject);
    56. } catch (e) {
    57. // 遇到错误时,捕获错误,执行 reject 函数
    58. reject(e);
    59. }
    60. }
    61. MyPromise.prototype.then = function(onResolved, onRejected) {
    62. // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
    63. onResolved =
    64. typeof onResolved === "function"
    65. ? onResolved
    66. : function(value) {
    67. return value;
    68. };
    69. onRejected =
    70. typeof onRejected === "function"
    71. ? onRejected
    72. : function(error) {
    73. throw error;
    74. };
    75. // 如果是等待状态,则将函数加入对应列表中
    76. if (this.state === PENDING) {
    77. this.resolvedCallbacks.push(onResolved);
    78. this.rejectedCallbacks.push(onRejected);
    79. }
    80. // 如果状态已经凝固,则直接执行对应状态的函数
    81. if (this.state === RESOLVED) {
    82. onResolved(this.value);
    83. }
    84. if (this.state === REJECTED) {
    85. onRejected(this.value);
    86. }
    87. };
    1. then(onFulfilled, onReject){
    2. // 保存前一个promise的this
    3. const self = this;
    4. return new MyPromise((resolve, reject) => {
    5. // 封装前一个promise成功时执行的函数
    6. let fulfilled = () => {
    7. try{
    8. const result = onFulfilled(self.value); // 承前
    9. return result instanceof MyPromise? result.then(resolve, reject) :
    10. resolve(result); //启后
    11. }catch(err){
    12. reject(err)
    13. }
    14. }
    15. // 封装前一个promise失败时执行的函数
    16. let rejected = () => {
    17. try{
    18. const result = onReject(self.reason);
    19. return result instanceof MyPromise? result.then(resolve, reject) :
    20. reject(result);
    21. }catch(err){
    22. reject(err)
    23. }
    24. }
    25. switch(self.status){
    26. case PENDING:
    27. self.onFulfilledCallbacks.push(fulfilled);
    28. self.onRejectedCallbacks.push(rejected);
    29. break;
    30. case FULFILLED:
    31. fulfilled();
    32. break;
    33. case REJECT:
    34. rejected();
    35. break;
    36. }
    37. })
    38. }

3.Promise 与微任务
使用微任务,可以达到延时(异步)调用onResolve的目的,也能提升代码的执行效率(实时性)。


面试视角

浏览器原理【转载,原作者不明】

一、浏览器安全


🚩1. 什么是 XSS 攻击?
(1)概念
(2)攻击类型
🚩2. 如何防御 XSS 攻击?
🚩3. 什么是 CSRF 攻击?
(1)概念
(2)攻击类型
🚩4. 如何防御 CSRF 攻击?
5. 什么是中间人攻击?如何防范中间人攻击?
6. 有哪些可能引起前端安全的问题?
7. 网络劫持有哪几种,如何防范?


二、进程与线程


🚡1. 进程与线程的概念
🚩2. 进程和线程的区别
3. 浏览器渲染进程的线程有哪些
🚩4. 进程之前的通信方式—-了解即可
5. 僵尸进程和孤儿进程是什么?—-了解即可(或者不需要看)
🚩6. 死锁产生的原因? 如果解决死锁的问题?—-了解即可(或者不需要看)
🚡7. 如何实现浏览器内多个标签页之间的通信?
8. 对Service Worker的理解


三、浏览器缓存


🚩1. 对浏览器的缓存机制的理解
🚡2. 浏览器资源缓存的位置有哪些?
🚩3. 协商缓存和强缓存的区别
(1)强缓存
(2)协商缓存
4. 为什么需要浏览器缓存?
5. 点击刷新按钮或者按 F5、按 Ctrl+F5 (强制刷新)、地址栏回车有什么区别?


四、浏览器组成

  1. 对浏览器的理解
    🚡2. 对浏览器内核的理解
    🚡3. 常见的浏览器内核比较
    🚩4. 常见浏览器所用内核—-了解即可
    🚡5. 浏览器的主要组成部分


    五、浏览器渲染原理


    🚩1. 浏览器的渲染过程
    🚩2. 浏览器渲染优化
    🚡3. 渲染过程中遇到 JS 文件如何处理?

  2. 什么是文档的预解析?

  3. CSS 如何阻塞文档解析?
  4. 如何优化关键渲染路径?
    🚡7. 什么情况会阻塞渲染?


    六、浏览器本地存储


    🚩1. 浏览器本地存储方式及使用场景
    (1)Cookie
    (2)LocalStorage
    (3)SessionStorage
    🚡2. Cookie有哪些字段,作用分别是什么
    🚩3. Cookie、LocalStorage、SessionStorage区别
    🚩4. 前端储存的⽅式有哪些?

  5. IndexedDB有哪些特点?—-了解即可


    七、浏览器同源策略


    🚩1. 什么是同源策略
    🚩2. 如何解决跨越问题
    (1)CORS
    减少OPTIONS请求次数:
    CORS中Cookie相关问题:
    (2)JSONP
    (3)postMessage 跨域
    (4)nginx代理跨域
    (5)nodejs 中间件代理跨域
    (6)document.domain + iframe跨域
    (7)location.hash + iframe跨域
    (8)window.name + iframe跨域
    (9)WebSocket协议跨域

  6. 正向代理和反向代理的区别—-了解即可

  7. Nginx的概念及其工作原理—-了解即可(或者不需要看)


    八、浏览器事件机制


    🚩1. 事件是什么?事件模型?

  8. 如何阻止事件冒泡
    🚩3. 对事件委托的理解
    (1)事件委托的概念
    (2)事件委托的特点
    (3)局限性
    🚩4. 事件委托的使用场景

  9. 同步和异步的区别
    🚩6. 对事件循环的理解
    🚩7. 宏任务和微任务分别有哪些
  10. 什么是执行栈
    🚩9. Node 中的 Event Loop 和浏览器中的有什么区别?process.nextTick 执行顺序?
  11. 事件触发的过程是怎样的


    九、浏览器垃圾回收机制—-了解即可


    🚩1. V8的垃圾回收机制是怎样的
    🚡2. 哪些操作会造成内存泄漏?