声明: 个人学习使用,全文手敲, 由于语雀性能问题, 拆分上下文档

45. 基础数据结构 栈与队列

1. 栈结构

特点: 后进先出 LIFO(last in first out)
要点: 只能在一端操作(顶端 front):包括增加(进栈)和删除(出栈)

栈结构的类

  1. class Stack {
  2. container = []
  3. enter(value) {
  4. this.container.unshift(value)
  5. }
  6. leave() {
  7. return this.container.shift()
  8. }
  9. size() {
  10. return this.container.length
  11. }
  12. value() {
  13. // 克隆一份的目的是为了防止,外部的操作修改内部的CONTAINER
  14. return this.container.slice(0)
  15. }
  16. }

练习题:

  1. 十进制转换二进制 ```javascript Number.prototype.decimal2binary = function decimal2binary() { // this -> num let decimal = this, sk = new Stack if (decimal === 0) return ‘0’ while (decimal > 0) { // n商数 m余数 let n = Math.floor(decimal / 2), m = decimal % 2 sk.enter(m) decimal = n } return sk.value().join(‘’) }

let num = 187956 console.log(num.decimal2binary()) //=>”101101111000110100”

  1. <a name="N5mYD"></a>
  2. #### 2. 队列结构
  3. 特点: 先进先出 FIFO <br />要点: 允许在队头(front)删除,允许在队尾(rear)插入<br />特殊:优先级队列<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1665156/1637819125189-9408fdeb-f560-4965-9977-2e098b13be4e.png#clientId=ue04ce2d3-1114-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=264&id=uf6620624&margin=%5Bobject%20Object%5D&name=image.png&originHeight=264&originWidth=529&originalType=binary&ratio=1&rotation=0&showTitle=false&size=68128&status=done&style=none&taskId=ufc613ecf-3f70-4eda-98f3-58912ba69f3&title=&width=529)<br />封装自己的队列
  4. ```javascript
  5. class Queue {
  6. container = [];
  7. // 进入 priority [praɪˈɒrəti]
  8. enter(element, priority=0) {
  9. let obj={
  10. element,
  11. priority
  12. };
  13. if(priority===0){
  14. this.container.push(obj);
  15. return;
  16. }
  17. let flag=false;
  18. // 优先级比较
  19. for(let i=this.container.length-1;i>=0;i--){
  20. let item=this.container[i];
  21. if(item.priority>=priority){
  22. this.container.splice(i1,0,obj);
  23. flag=true;
  24. break;
  25. }
  26. }
  27. !flag?this.container.push(obj):null;
  28. }
  29. // 离开
  30. leave() {
  31. return this.container.shift();
  32. }
  33. // 队列的长度
  34. size() {
  35. return this.container.length;
  36. }
  37. // 获取队列中的结果
  38. value() {
  39. return this.container.slice(0);
  40. }
  41. }

练习题:

  1. 击鼓传花
    N个人一起玩游戏,围成一圈,从1开始数数,数到M的人自动淘汰;最后剩下的人会取得胜利,问最后剩下的是原来的哪一位? ```javascript const arr = [1, 2, 3, 4, 5, 6, 7, 8] const out = function (arr, num) { let winner = arr.length let count = 1 while (winner > 1) { const first = arr.shift() if (count++ !== num) { arr.push(first) } else { count = 1 } winner = arr.length } return arr[0] }

console.log(out(arr, 5)) // 3

  1. <a name="hUm0x"></a>
  2. ## 46. 浏览器底层渲染机制
  3. 理论知识: 一个页面从服务器访问回来后, 浏览器拿到的是页面源代码, 之后需要做的一些事情
  4. <a name="g0qep"></a>
  5. #### 1. 渲染流程
  6. 以下步骤1,2,3,4,5 都是由GUI渲染线程来完成
  7. 1. 生成DOM Tree : 对html的处理
  8. 1. 基于http获取的是流文件(进制编码)
  9. 1. 把进制编码编译为具体的字符
  10. 1. 按照Token令牌进行解析:(词法解析的过程)
  11. 1. 生成具体的节点(元素节点/文本节点)
  12. 1. 按照相互的嵌套关系生成一个DOM树(节点树)
  13. 1. [undefined.undefined](https://cdn.nlark.com/yuque/0/2021/png/1665156/1638774057201-933bb8af-b92a-47a0-812e-d33bce56f67d.png?_lake_card=%7B%22src%22%3A%22https%3A%2F%2Fcdn.nlark.com%2Fyuque%2F0%2F2021%2Fpng%2F1665156%2F1638774057201-933bb8af-b92a-47a0-812e-d33bce56f67d.png%22%2C%22name%22%3A%22undefined.undefined%22%2C%22size%22%3A128172%2C%22type%22%3A%22image%2Fpng%22%2C%22ext%22%3A%22png%22%2C%22status%22%3A%22done%22%2C%22taskId%22%3A%22uf286bfdb-f5e1-41bc-be9a-284c60ad22e%22%2C%22taskType%22%3A%22transfer%22%2C%22id%22%3A%22u36f560b3%22%2C%22card%22%3A%22file%22%7D)
  14. 2. 生成CSSOM tree : 对css的处理
  15. 1. 操作顺序跟Dom tree一样
  16. 1. [undefined.undefined](https://cdn.nlark.com/yuque/0/2021/png/1665156/1638774057206-72d12046-c405-405f-81ec-2bcfb7333a82.png?_lake_card=%7B%22src%22%3A%22https%3A%2F%2Fcdn.nlark.com%2Fyuque%2F0%2F2021%2Fpng%2F1665156%2F1638774057206-72d12046-c405-405f-81ec-2bcfb7333a82.png%22%2C%22name%22%3A%22undefined.undefined%22%2C%22size%22%3A71103%2C%22type%22%3A%22image%2Fpng%22%2C%22ext%22%3A%22png%22%2C%22status%22%3A%22done%22%2C%22taskId%22%3A%22u5c19f5f7-63c2-428c-b691-5337884b021%22%2C%22taskType%22%3A%22transfer%22%2C%22id%22%3A%22u16db159c%22%2C%22card%22%3A%22file%22%7D)
  17. 3. 生成RENDER Tree (渲染树)
  18. 1. DOM Tree + CSSOM Tree = RENDER Tree
  19. 1. 对于开始设置为display:none样式的元素是不会再渲染树种生成,
  20. 1. [undefined.undefined](https://cdn.nlark.com/yuque/0/2021/png/1665156/1638774057195-0b8b4ff3-24f6-47c4-9100-72159e3a308f.png?_lake_card=%7B%22src%22%3A%22https%3A%2F%2Fcdn.nlark.com%2Fyuque%2F0%2F2021%2Fpng%2F1665156%2F1638774057195-0b8b4ff3-24f6-47c4-9100-72159e3a308f.png%22%2C%22name%22%3A%22undefined.undefined%22%2C%22size%22%3A127680%2C%22type%22%3A%22image%2Fpng%22%2C%22ext%22%3A%22png%22%2C%22status%22%3A%22done%22%2C%22taskId%22%3A%22ube4f1a55-d0d5-4480-bb7b-72bd4e8c935%22%2C%22taskType%22%3A%22transfer%22%2C%22id%22%3A%22u7b70f362%22%2C%22card%22%3A%22file%22%7D)
  21. 4. 布局/回流/重排: Layout
  22. 1. 按照渲染树计算出每个元素在视口中的位置和大小
  23. 5. 分层
  24. 1. 按照计算出来的样式进行分层
  25. 1. 定位, position: absolute / fixed
  26. 1. 设置透明度, opacity
  27. 1. 设置滤镜 filter
  28. 1. 文本假如超过盒子大小, 使用样式进行切割也会分层
  29. 6. 绘制/重绘 Painting
  30. 1. 把生成的绘制列表提交给“合成线程”
  31. 1. “合成线程”进行我们最后的绘制,呈现在浏览器的页面上
  32. <a name="fjGh7"></a>
  33. #### 2. GUI渲染线程解析的过程
  34. 自上而下解析完所有的HTML标签/个钟节点后, DOM tree就生成了, 但是过程中还会遇到一些比较特殊的
  35. 1. <link href='xxx.css' /> 外链式样式
  36. 1. 浏览器会分配一个新的HTTP网络线程去加载资源文件
  37. 1. 这样不会阻碍DOM树的渲染
  38. 2. <style>...</style> 内嵌式样式
  39. 1. 不用去请求信的资源文件, 但是此时样式还没有处理, 浏览器会做一个记录,
  40. 1. 会把所有css请求回来, 再按照先后顺序进行渲染, 从而生成CSSOM树
  41. 3. @import 'xxx.css' 导入式样式
  42. 1. 虽然也会分配网络HTTP线程去加载资源文件
  43. 1. 但是此时GUI线程会被阻塞, 阻碍DOM树的渲染, 需要等资源加载回来会才会继续渲染DOM
  44. 4. <script>....</script> 内嵌式脚本
  45. 1. 立即执行JS, 阻碍DOM树的渲染
  46. 5. <script src='xxx.js'> 外链式脚本
  47. 1. 发起HTTP请求, , 阻碍DOM树的渲染
  48. 1. 加载回来后立即执行JS
  49. 1. 如果JS中没有采用异步, 直接获取DOM元素, 而DOM此时还没有渲染,脚本是获取不到
  50. 1. [undefined.undefined](https://cdn.nlark.com/yuque/0/2021/png/1665156/1638774057232-23bbaa2d-8c07-4f87-a27f-c7f90bd8f3e0.png?_lake_card=%7B%22src%22%3A%22https%3A%2F%2Fcdn.nlark.com%2Fyuque%2F0%2F2021%2Fpng%2F1665156%2F1638774057232-23bbaa2d-8c07-4f87-a27f-c7f90bd8f3e0.png%22%2C%22name%22%3A%22undefined.undefined%22%2C%22size%22%3A35126%2C%22type%22%3A%22image%2Fpng%22%2C%22ext%22%3A%22png%22%2C%22status%22%3A%22done%22%2C%22taskId%22%3A%22ua05dc163-1c4a-4b0d-9c93-5529dd18b55%22%2C%22taskType%22%3A%22transfer%22%2C%22id%22%3A%22u7fe7adce%22%2C%22card%22%3A%22file%22%7D)
  51. 6. img/video/Audio
  52. 1. 老版浏览器会阻塞DOM渲染
  53. 1. 新版浏览器虽然不会阻塞DOM渲染, 但是图片资源的请求会占用HTTP线程
  54. 1. 浏览器同时只能开6~7个HTTP线程,这样图片/音视频资源加载本来就会慢一些,会影响其他资源link/script等的加载
  55. 优化方向 : 网络层, CRP, webpack, 安全, 代码层
  56. <a name="j9j5e"></a>
  57. ## 47. CRP(critical rendering path)
  58. CRP(critical rendering path)浏览器关键路径节点优化
  59. 1. 不用@import
  60. 1. link放到HEAD中(尽可能提前去加载资源文件,这样等DOM树渲染完,资源可能也加载回来了):
  61. 1. 当代浏览器的机制越发完善,Webkit浏览器预测解析:chrome的预加载扫描器html-preload-scanner通过扫描节点中的 “src” , “link”等属性,找到外部连接资源后进行预加载,避免了资源加载的等待时间,同样实现了提前加载以及加载和执行分离
  62. 3. 如果CSS代码比较少,尽可能使用内嵌式,可以减少HTTP请求;但是如果样式比较多,采用内嵌式,第一次加载HTML都会浪费很长的时间,这样还不如基于link分开加载;移动端开发都是内嵌有先(当然也要考虑CSS代码量的);
  63. 3. 减少DOM或者减少DOM的层级嵌套,以及标签语义化...(当代前端开发,开始只把首屏的结构/内容写出来,渲染只是首屏的,当首屏加载完,页面滚动的时候,再基于JS创建其他屏幕的结构和内容 => 骨架屏/SSR =>客户端骨架屏,开始首屏结构都没有,只有一个loading或者占位图而已...)
  64. 3. 把script放到页面的底部(先渲染DOM TREE,再执行JS,也可以获取到DOM元素了),也可以基于事件DOMContentLoaded/load等到结构加载完再去获取DOM元素
  65. 3. async / defer 给script设置的属性
  66. 1. async是开辟HTTP线程加载资源文件,此时DOM TREE继续,但是资源文件一但加载回来,停止DOM TREE,先执行JS代码(不考虑JS引入顺序,谁先加载回来谁先执行)
  67. 1. defer也是开辟HTTP线程加载资源文件,即使资源文件加载回来,也会等待DOM树渲染完,然后按照JS的导入顺序依次执行JS(不兼容低版本浏览器)
  68. 7. 图片优化
  69. 1. 合并(Sripte)
  70. 1. BASE64(好用但是慎用)
  71. 1. iconfont(CSS绘制)
  72. 1. svg
  73. 1. 图片的懒加载
  74. <a name="rqqpF"></a>
  75. ## 48. 回流 重绘
  76. 传统操作Dom消耗性能的原因: DOM的回流/重绘
  77. <a name="e3M28"></a>
  78. #### 1. 回流
  79. 当页面的布局或者几何信息发生变化, 浏览器可能需要重新创建DOM树或者重新计算每一个元素在视口中的位置和大小(重新layout), 重新计算完成后, 让浏览器重新渲染, 触发回流必然会引发重绘
  80. 触发回流的条件:
  81. 1. DOM元素的增删改导致DOM结构发生变化
  82. 1. DOM的样式: 如大小, 位置,发生变化
  83. 1. 浏览器窗口大小改变(视口改变)
  84. 1. 页面第一次加载必然会有一次回流
  85. 1. ...
  86. <a name="ZNGCO"></a>
  87. #### 2. 重绘
  88. 元素样式发生改变, 但是几何信息和结构信息没有改变, 此时不需要回流, 只需要浏览器把改变的元素重新渲染即可<br />触发回流的条件
  89. 1. color
  90. 1. background
  91. 1. ...
  92. <a name="CJctK"></a>
  93. #### 3. 练习题
  94. 优化思路:不是创建一个span就放置在页面中,而是把5个创建好,整体添加到页面中
  95. 1. createDocumentFragment 文档碎片
  96. 1. 字符串拼接, 最后innerHtml赋值
  97. ```javascript
  98. // 向#box盒子中动态插入5个span
  99. // 当前代码会引发5次回流
  100. for (let i = 1; i <= 5; i++) {
  101. let span = document.createElement('span')
  102. span.innerHTML = i
  103. box.appendChild(span)
  104. }
  105. // 优化1
  106. let frag = document.createDocumentFragment()
  107. for (let i = 1; i <= 5; i++) {
  108. let span = document.createElement('span');
  109. span.innerHTML = i;
  110. frag.appendChild(span);
  111. }
  112. box.appendChild(frag)
  113. frag = null // 最后清除变量
  114. // 优化2
  115. let str = ''
  116. for (let i = 1; i <= 5; i++) {
  117. str += `<span>${i}</span>`
  118. }
  119. box.innerHTML = str

4. 浏览器渲染队列机制

在一个上下文中, 从上往下执行, 遇到设置元素几何信息会放入队列中,

  1. 如果遇到访问元素几何信息, 会马上执行队列中的操作, 并触发回流&重绘, 然后继续往下代码执行….
  2. 上下文执行完也会触发回流&重绘

技巧: 利用访问元素几何信息立刻触发回流机制
利用访问元素几何信息立马触发回流特点.gif

// 需求: 点击盒子, 先到 0, 0 坐标,  再缓慢到left: 400位置
let box = document.getElementById('box')
box.onclick = function () {
  //立即回到这个位置
  box.style.transitionDuration = '0s'
  box.style.top = 0
  box.style.left = 0
  box.offsetLeft // 利用访问几何信息,立马执行回流机制
  //让其有动画效果
  box.style.transitionDuration = '1s'
  box.style.left = '400px'
}

49. eventLoop

js本身是单线程: 浏览器只分配一个县城供给js代码自上而下执行, 所以大部分代码都是同步的, 但是也有一些代码是异步的

浏览器:

  1. setTimeout/setInterval(定时器)
  2. 事件绑定, 事件监听
  3. ajax/fetch请求, ajax可以设置为同步(http线程)
  4. Promise中的异步操作
  5. async/await , generate

node:

  1. progress,nextTick
  2. setImmediate
  3. FS进行I/O操作可以是异步

JS异步操作的运行机制 :事件队列Event queue 和事件循环EventLoop
微任务 -> 宏任务

练习题

  1. 宏任务执行顺序,谁时间先到达,谁先执行
  2. eventLoop eventqueue.png
    setTimeout(() => {
     console.log(1);
    }, 20)
    console.log(2);
    setTimeout(() => {
     console.log(3);
    }, 10);
    console.log(4);
    console.time("11");
    // 约70ms
    for( let  i = 0 ;i<900000000;i++){}
    console.timeEnd("11");
    console.log(5);
    setTimeout(() => {
     console.log(6);
    }, 8);
    console.log(7);
    setTimeout(() => {
     console.log(8);
    }, 15)
    console.log(9);
    

56. promise

Promise 是ES6新增的内置类, 是一个”承诺”的设计模式, 主要目的是用来解决JS异步编程中”回调地狱”,能够有效的管控异步编程
以面向对象的方式学习Promise按照以下维度

  1. 实例
    1. [[PromiseStatus]] promise状态: pedding, fulfilled/resolved, rejected
    2. [[PromiseValue]] promise的值,
  2. Promise.prototype
    1. then .then注入回调方法的时候, 我们可以写, 也可以不写, 但是如果状态一旦确定, 想去执行.then 的某个方法, 如果方法没有没注册, 则会向下顺延(找下一个then中注册中对应的方法)
    2. catch .catch(reason=>{}) === .then(null,reason=>{})
    3. finally
  3. 普通对象
    1. reject 返回失败函数
    2. resolve 返回成功函数
    3. all 等待所有promise实例都是成功, 整体返回的实例才成功(并以传入数组返回), 如果有一个失败, 整体都失败, 并返回失败的promise
    4. race 等待最新有返回值的promise实例, 此实例成功/失败决定最后的成功/失败, (赛跑, 谁快返回谁)
  4. 语法
    1. new Promise([executor]) executor是一个可执行的函数
    2. executor: resolve和reject 传递executor函数的参数
    3. promise 初始状态是pending, 初始值是undfined
    4. resolve[value] 修改promise状态为fulfilled/resolved 成功状态, 并且修改其值value
    5. reject[reason] 修改promise状态为rejected失败状态, 并且修改值为reason
    6. 如果executor函数中执行报错, 也会返回失败状态, 并且把错误信息返回到reason
    7. 一旦状态从pending发生改变, 都不能再次更改状态
  5. 本质
    1. Promise是用来管控异步编程, new Promise本身不是异步的, 执行他的时候会立即把executor函数执行, (只不过我们经常在executor中管控一个异步操作)

练习题:

// 设计一个等待函数,等待N时间后执行要做的事情
const  delay = function (interval = 1000) {
    return new Promise(resolve => {
        let timer = setTimeout(() => {
            clearTimeout(timer)
            timer = null
            resolve()
        }, interval)
    })
}

57. async/await

async / await 是ES7中提供的(generate语法糖), 用async修饰一个函数,函数返回的结果都会变为一个promise实例,
状态:大多都是成功的,如果代码执行报错,返回失败,再如果手动返回一个新的promise实例,则按照新实例的状态处理
要点:

  1. 方法中想用await,必须把方法基于async修饰
  2. 回显把await 后的方法执行
  3. 异步性:它会把当前上下文,await下面的代码整体都当作一个异步的微任务,放置在EventQueue中
  4. await只是处理promise实例是成功状态的,如果返回转态是成功,则value拿到的是[[PromiseValue]],并且把之前存储的异步任务拿到栈中让主线程把其执行
  5. await处理后,立即把函数执行,哪怕函数立即返回成功或者失败的状态,await也没有把其立即处理,而是先等同步的都执行完,再去执行这些异步的任务
  6. 缺点: 对于await处理的时候,如果返回的是失败的promise,则await下面代码不再执行,失败情况下没有处理,浏览器抛出一个异常信息,此时我们可以基于try/catch捕获到异常,从而进行异常的处理

58. eventloop/eventQueue练习题

题目1

  1. 难度: 简单
  2. 图解: eventloop题目1.png

    async function async1() {
     console.log('async1 start');
     await async2();
     console.log('async1 end');
    }
    async function async2() {
     console.log('async2');
    }
    console.log('script start');
    setTimeout(function() {
     console.log('setTimeout');
    }, 0)
    async1();
    new Promise(function(resolve) {
     console.log('promise1');
     resolve();
    }).then(function() {
     console.log('promise2');
    });
    console.log('script end');
    

    题目2

  3. 难度: 一般

  4. 图解: eventloop题目2.png ```javascript function func1(){ console.log(‘func1 start’); return new Promise(resolve=>{
     resolve('OK');
    
    }); } function func2(){ console.log(‘func2 start’); return new Promise(resolve=>{
     setTimeout(()=>{
         resolve('OK');
     },10);
    
    }); }

console.log(1); setTimeout(async () => { console.log(2); await func1(); console.log(3); }, 20); for (let i = 0; i < 90000000; i++) {} //循环大约要进行80MS左右 console.log(4); func1().then(result=>{ console.log(5); }); func2().then(result=>{ console.log(6); }); setTimeout(() => { console.log(7); }, 0); console.log(8);

<a name="FfUjW"></a>
#### 题目3
难度: 还没弄明白<br />解析: [promise题目3](https://juejin.cn/post/6945319439772434469)
```javascript
Promise.resolve().then(() => {
    console.log(0);
    return Promise.resolve(4);
}).then((res) => {
    console.log(res)
})

Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() =>{
    console.log(6);
})

核心都是ajax的操作:
JQ中的$.ajax是帮我们封装好的ajax库;axios也是基于Promise封装的ajax库,
fetch是浏览器内置的发送请求的类(天生就是Promise管控的)

59. promiseA+规范 重写promise

(function () {
    // constructor
    function MyPromise(executor) {
        // 参数合法校验
        if (typeof executor !== "function") {
            throw new TypeError('MyPromise resolver ' + executor + ' is not a function');
        }

        // 设置实例的私有属性
        var _this = this;
        this.PromiseStatus = 'pending';
        this.PromiseValue = undefined;
        this.resolveFunc = function () {};
        this.rejectFunc = function () {};

        // 修改实例的状态和value:只有当前状态为pending才能修改状态
        function change(status, value) {
            if (_this.PromiseStatus !== "pending") return;
            _this.PromiseStatus = status;
            _this.PromiseValue = value;
            // 通知基于.then注入的某个方法执行(异步的)
            var delayTimer = setTimeout(function () {
                clearTimeout(delayTimer);
                delayTimer = null;

                var status = _this.PromiseStatus,
                    value = _this.PromiseValue;
                status === "fulfilled" ?
                    _this.resolveFunc.call(_this, value) :
                    _this.rejectFunc.call(_this, value);
            }, 0);
        }

        // new MyPromise的时候会立即把executor函数执行
        // executor函数执行出现错误,也会把实例的状态改为失败,且value是失败的原因
        try {
            executor(function resolve(value) {
                change('fulfilled', value);
            }, function reject(reason) {
                change('rejected', reason);
            });
        } catch (err) {
            change('rejected', err.message);
        }
    }

    // MyPromise.prototype
    MyPromise.prototype.then = function (resolveFunc, rejectFunc) {
        // 参数不传默认值的处理:目的是实现状态的顺延
        if (typeof resolveFunc !== "function") {
            resolveFunc = function (value) {
                return MyPromise.resolve(value);
            };
        }
        if (typeof rejectFunc !== "function") {
            rejectFunc = function (reason) {
                return MyPromise.reject(reason);
            };
        }

        var _this = this;
        return new MyPromise(function (resolve, reject) {
            // 我们返回的新实例的成功和失败(执行resolve/reject)
            // 由resolveFunc/rejectFunc执行是否报错来决定(或者返回值是否为新的MyPromise实例来决定)
            _this.resolveFunc = function (value) {
                try {
                    var x = resolveFunc.call(_this, value);
                    x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
                } catch (err) {
                    reject(err.message);
                }
            };
            _this.rejectFunc = function (reason) {
                try {
                    var x = rejectFunc.call(_this, reason);
                    x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
                } catch (err) {
                    reject(err.message);
                }
            };
        });
    };
    MyPromise.prototype.catch = function (rejectFunc) {
        return this.then(null, rejectFunc);
    };

    // 把MyPromise当作对象
    MyPromise.resolve = function (value) {
        return new MyPromise(function (resolve) {
            resolve(value);
        });
    };
    MyPromise.reject = function (reason) {
        return new MyPromise(function (_, reject) {
            reject(reason);
        });
    };
    MyPromise.all = function (promiseArr) {
        return new MyPromise(function (resolve, reject) {
            var index = 0,
                values = [];
            for (var i = 0; i < promiseArr.length; i++) {
                // 利用闭包的方式保存循环的每一项索引
                (function (i) {
                    var item = promiseArr[i];
                    // 如果当前项不是Promise:直接算作当前项成功
                    !(item instanceof MyPromise) ? item = MyPromise.resolve(item): null;
                    item.then(function (value) {
                        index++;
                        values[i] = value;
                        if (index >= promiseArr.length) {
                            // 所有的实例都是成功的
                            resolve(values);
                        }
                    }).catch(function (reason) {
                        // 只要有一个失败,整体就是失败的
                        reject(reason);
                    });
                })(i);
            }
        });
    };

    window.MyPromise = MyPromise;
})()

73. 服务器渲染/客户端渲染

1. 服务器渲染

常用于后端项目 如 Java-Web项目
弊端:

  1. 重后台, 轻前端(分工不均匀, 前端弱化)
  2. 服务器压力太大
  3. 无法实现局部刷新, (某个区域的数据需要更新, 则重新整个页面刷新 => 全局刷新)

优点:

  1. 有利于SEO优化, 页面源代码中可以看到所有动态绑定的内容 (chrome可以ctrl+u) 查看源代码
  2. 如果服务器的抗压能力很强,处理速度很快, 则页面渲染速度很快

服务器渲染流程图.png

2. 客户端渲染

弊端:

  1. 不利于SEO优化, 基于JS完成的数据绑定, 页面源代码中式不存在的 (chrome可以ctrl+u) 查看源代码
  2. 需要两次及以上请求才能看到完整的页面(占用请求) 一次是资源页面的请求, 一次是数据的请求, 如果网络很慢, 则白屏时间较长(接口pending时间)

优点:

  1. 前端和后端工作量相对平衡
  2. 减轻服务器压力
  3. 可以实现局部刷新
  4. 能够实现的交互效果会更多

客户端渲染流程图.png

74. 原生AJAX

//创建ajax对象
let xhr = new XMLHttpRequest;
/**
 * @param methods  请求方法
 * @param {url}  请求的url
 * @param {async}  是否为异步
 */
//open 把状态改为1
xhr.open("get", "url....", true);
/**
 * Ajax的状态  xhr.readyState
 *         UNSENT    0    创建完XHR默认是0
 *         OPENED    1    已经完成OPEN操作
 *         HEADERS_RECEIVED    2  服务器已经把响应头信息返回了
 *         LOADING    3    响应主体正在返回中
 *         DONE     4    响应主体已经返回
 * 
 * XHR.OPEN第三个参数控制的同步异步指的是:
 * 从send发送请求,任务开始,一直到AJAX状态为4才算结束
 * 同步指:在此过程所有任务都不会处理
 * 异步指:在此过程该干啥就干啥
 */
xhr.onreadystatechange = function(){
    //监听到状态啊改变后才会触发的事件 ,每次改变状态触发一遍
    //2.3.4
    console.log(xhr.readyState);
}
//状态为4才能执行
xhr.send();

79. 前端性能优化-鉴权网络篇

在浏览器输入URl地址到看到页面中间经历了啥??

1. URL解析

一个完整的URL地址可由 完整URL.png

  1. 协议、 用于客户端与服务器数据传输的协议
    1. http 超文本传输协议
    2. https 在http基础上进行了安全设置(SSL/TSL) 证书认证
    3. ftp 主要用于客户端电脑和服务器端的文件传输
  2. 登录信息、
  3. 域名、
  4. 端口号, 每一个协议都有默认端口号
    1. http -> 80
    2. https -> 443
    3. ftp -> 21
  5. 请求资源文件路径、
  6. 查询字符串、
  7. 片段标识符(hash值)

URL的编码: 对于一些特殊的字符, 我们在客户端和服务器传递的时候需要进行编码与解码

  1. encodeURI / decodeURI : 主要对地址中的空格或中文进行编码
  2. encodeURIComponent / decodeURIComponent : 在encodeURI的基础上对/等特殊字符也会进行编码解码
  3. escape / unescape 主要用于客户端不同页面之间数据传输信息编码解码.例如(cookie)

2. 缓存检查

  1. 缓存的位置可分为Memory Cache内存缓存 > Disk Cache 磁盘缓存 , 内存缓存优先于磁盘缓存

  2. 强缓存 Expires / Cache-Control, 浏览器对于强缓存的处理, 根据第一次请求资源时返回的响应头来确定.

强缓存 (1).pngExpires: 缓存过期时间,用来指定资源到期的时间, 针对HTTP1.0
Cache-Control: cache-control: max-age=2592000 在第一次拿到资源后30天有效, 再次发送请求时会读取缓存位置的资源针对HTTP1.1
http1.1 优先于 http1.0

  1. 协商缓存 Last-Modified / ETag

协商缓存就是在强缓存失效后, 浏览器携带缓存标识向服务器发起请求, 由服务器根据标识决定是否使用缓存的过程
协商缓存 (1).png
协商缓存生效则会返回304/Not Modified
协商缓存失效则返回200和请求结果

  1. http1.0使用Last-Modified 和If-Modified-Since
  2. http1.1使用ETag 合 IF-None-Match
  3. 当第一次请求时,服务器会返回对应协议的标识, 浏览器会把请求头信息缓存下来, 当下次请求是会携带If-Modified-since/IF-None-Match在请求头中, 对应的值分别是Last-Modified / ETag, 服务器判断是否缓存并且返回新的资源或者返回304
  1. 强缓存优缺点
    1. 优点: 减少对服务器的请求, 加载资源更快, 页面渲染也就快了
    2. 缺点: 当我们服务器更新文件后, 还在强缓存有效期内, 导致客户端无法及时更新文件
    3. 解决方案: 1. 使用webpack在文件名添加hash值
    4. 解决方案: 2. 在文件后添加一个时间戳

3. DNS解析

DNS服务器(域名解析服务器)

  1. 部署服务器后, 服务器有一个外网IP地址, 基于外网IP可以找到服务器
  2. 外网IP别人记不住, 但是可以记住域名
  3. 域名解析服务器DNS, 记录了域名主机地址IP相对应的信息

4. TCP三次握手

seq序号: 用来表示从TCP源端向目的端发送的字节流, 发起方发送数据时,对此进行标记
ack确认序号: 只有ACK标志为1时, 确认序号字段才有效 ask = seq+1
标志位: 共6个

  1. URG: 紧急指针有效
  2. ACK: 确认序号有效
  3. PSH: 接收方应该尽快将这个报文交给应用层
  4. RST: 重置连接
  5. SYN: 发起一个新连接
  6. FIN: 释放一个链接

TCP三次握手 .png

为什么是三次握手, 而不是两次, / 四次
因为三次握手是一个有效建立连接的方式, 少了双方信息获取不到位, 多了没意义
[两次]
“喂,你听得到吗?”
“我听得到呀”
“喂,你听得到吗?”
“草,我听得到呀!!!!”
“你TM能不能听到我讲话啊!!喂!”

[四次]
“喂,你听得到吗?”
“我听得到呀,你听得到我吗?”
“我能听到你,你能听到我吗?”
“……不想跟傻逼说话”

5. 数据传输

  1. HTTP报文信息
    1. 请求头,
    2. 请求主体
  2. 响应状态码
    1. 2, 3, 4, 5
  3. 并发性

    1. 一个服务器源能同时支持4-7个并发

      6. TCP四次挥手

      TCP四次挥手.png
  4. 为什么连接的时候是三次握手,关闭的时候却是四次握手?

因为当服务器端收到客户端的SYN连接请求报文后, 可以直接发送SYN+ACK报文. 其中ACK报文是用来应答的, SYN报文是用来同步的,
但是关闭连接时, 服务器端接收到FIN报文时, 很可能不会立刻关闭socket, 所以先回复一个ACK报文告诉客户端, 已收到FIN报文信息了, 需要等待服务器端所有数据组装完后, 才能发送FIN报文, 因此不能一起发送

http1.1.默认开启keep-alive

7. 页面渲染

回顾46. 浏览器底层渲染机制

8. HTTP1.0 和HTTP1.1的一些区别

  1. 缓存处理, 使用的策略不一样
    1. http1.0 使用Expires / IF-Modified
    2. http1.1 使用 ETag / IF-None-Match
  2. 宽带优化及网络连接使用
  3. 错误通知管理
  4. Host头处理
  5. 长连接

9. HTTP1.x 和HTTP2.0相比的新特性有哪些

  1. 新的二进制格式, (Binary Format)
    1. http1.x 是基于文本格式
    2. http2.0 采用二进制格式, 这样在传输数据上就能更加健壮
  2. 多路复用 MultiPlexing
    1. 即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面
  3. header压缩
  4. 服务器推送(server push)
    1. 例如, 发送请求一个index.html文件 在客户端收到index.html的同事, 会把index.js / index.css同事推送给客户端

10. 优化汇总

  1. 利用缓存
    1. 对于静态资源文件实现强缓存和协商缓存
    2. 对于不经常更新的接口数据采用本地存储(cooke, localStorage, sessionStorage)
  2. DNS优化
    1. 分服务器部署, 增加http并发性
    2. DNS Prefetch
  3. TCP的三次握手和四次挥手
    1. 采用Connection: keep-alive
  4. 数据传输
    1. 减少数据传输的大小
      1. 内存或者数据压缩(webpack等..)
      2. 服务器开启Gzip压缩(一般能压缩60%)
      3. 大批量请求进行分批加载, 例如: 分页, 底部加载等, 确保首次请求数据量少
    2. 减少http请求的次数
      1. 资源文件合并
      2. 字体图标
      3. 雪碧图
      4. 图片base64
  5. CND服务器”地域分布式”
  6. 采用更高的HTTP传输如http2.0, http1.1
  7. 尽量减少白屏的时间,
    1. loading效果
    2. 骨架屏
    3. 图片懒加载, 延迟加载