声明: 个人学习使用,全文手敲, 由于语雀性能问题, 拆分上下文档
45. 基础数据结构 栈与队列
1. 栈结构
特点: 后进先出 LIFO(last in first out)
要点: 只能在一端操作(顶端 front):包括增加(进栈)和删除(出栈)
栈结构的类
class Stack {
container = []
enter(value) {
this.container.unshift(value)
}
leave() {
return this.container.shift()
}
size() {
return this.container.length
}
value() {
// 克隆一份的目的是为了防止,外部的操作修改内部的CONTAINER
return this.container.slice(0)
}
}
练习题:
- 十进制转换二进制 ```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”
<a name="N5mYD"></a>
#### 2. 队列结构
特点: 先进先出 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 />封装自己的队列
```javascript
class Queue {
container = [];
// 进入 priority [praɪˈɒrəti]
enter(element, priority=0) {
let obj={
element,
priority
};
if(priority===0){
this.container.push(obj);
return;
}
let flag=false;
// 优先级比较
for(let i=this.container.length-1;i>=0;i--){
let item=this.container[i];
if(item.priority>=priority){
this.container.splice(i1,0,obj);
flag=true;
break;
}
}
!flag?this.container.push(obj):null;
}
// 离开
leave() {
return this.container.shift();
}
// 队列的长度
size() {
return this.container.length;
}
// 获取队列中的结果
value() {
return this.container.slice(0);
}
}
练习题:
- 击鼓传花
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
<a name="hUm0x"></a>
## 46. 浏览器底层渲染机制
理论知识: 一个页面从服务器访问回来后, 浏览器拿到的是页面源代码, 之后需要做的一些事情
<a name="g0qep"></a>
#### 1. 渲染流程
以下步骤1,2,3,4,5 都是由GUI渲染线程来完成
1. 生成DOM Tree : 对html的处理
1. 基于http获取的是流文件(进制编码)
1. 把进制编码编译为具体的字符
1. 按照Token令牌进行解析:(词法解析的过程)
1. 生成具体的节点(元素节点/文本节点)
1. 按照相互的嵌套关系生成一个DOM树(节点树)
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)
2. 生成CSSOM tree : 对css的处理
1. 操作顺序跟Dom tree一样
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)
3. 生成RENDER Tree (渲染树)
1. DOM Tree + CSSOM Tree = RENDER Tree
1. 对于开始设置为display:none样式的元素是不会再渲染树种生成,
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)
4. 布局/回流/重排: Layout
1. 按照渲染树计算出每个元素在视口中的位置和大小
5. 分层
1. 按照计算出来的样式进行分层
1. 定位, position: absolute / fixed
1. 设置透明度, opacity
1. 设置滤镜 filter
1. 文本假如超过盒子大小, 使用样式进行切割也会分层
6. 绘制/重绘 Painting
1. 把生成的绘制列表提交给“合成线程”
1. “合成线程”进行我们最后的绘制,呈现在浏览器的页面上
<a name="fjGh7"></a>
#### 2. GUI渲染线程解析的过程
自上而下解析完所有的HTML标签/个钟节点后, DOM tree就生成了, 但是过程中还会遇到一些比较特殊的
1. <link href='xxx.css' /> 外链式样式
1. 浏览器会分配一个新的HTTP网络线程去加载资源文件
1. 这样不会阻碍DOM树的渲染
2. <style>...</style> 内嵌式样式
1. 不用去请求信的资源文件, 但是此时样式还没有处理, 浏览器会做一个记录,
1. 会把所有css请求回来, 再按照先后顺序进行渲染, 从而生成CSSOM树
3. @import 'xxx.css' 导入式样式
1. 虽然也会分配网络HTTP线程去加载资源文件
1. 但是此时GUI线程会被阻塞, 阻碍DOM树的渲染, 需要等资源加载回来会才会继续渲染DOM
4. <script>....</script> 内嵌式脚本
1. 立即执行JS, 阻碍DOM树的渲染
5. <script src='xxx.js'> 外链式脚本
1. 发起HTTP请求, , 阻碍DOM树的渲染
1. 加载回来后立即执行JS
1. 如果JS中没有采用异步, 直接获取DOM元素, 而DOM此时还没有渲染,脚本是获取不到
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)
6. img/video/Audio
1. 老版浏览器会阻塞DOM渲染
1. 新版浏览器虽然不会阻塞DOM渲染, 但是图片资源的请求会占用HTTP线程
1. 浏览器同时只能开6~7个HTTP线程,这样图片/音视频资源加载本来就会慢一些,会影响其他资源link/script等的加载
优化方向 : 网络层, CRP, webpack, 安全, 代码层
<a name="j9j5e"></a>
## 47. CRP(critical rendering path)
CRP(critical rendering path)浏览器关键路径节点优化
1. 不用@import
1. link放到HEAD中(尽可能提前去加载资源文件,这样等DOM树渲染完,资源可能也加载回来了):
1. 当代浏览器的机制越发完善,Webkit浏览器预测解析:chrome的预加载扫描器html-preload-scanner通过扫描节点中的 “src” , “link”等属性,找到外部连接资源后进行预加载,避免了资源加载的等待时间,同样实现了提前加载以及加载和执行分离
3. 如果CSS代码比较少,尽可能使用内嵌式,可以减少HTTP请求;但是如果样式比较多,采用内嵌式,第一次加载HTML都会浪费很长的时间,这样还不如基于link分开加载;移动端开发都是内嵌有先(当然也要考虑CSS代码量的);
3. 减少DOM或者减少DOM的层级嵌套,以及标签语义化...(当代前端开发,开始只把首屏的结构/内容写出来,渲染只是首屏的,当首屏加载完,页面滚动的时候,再基于JS创建其他屏幕的结构和内容 => 骨架屏/SSR =>客户端骨架屏,开始首屏结构都没有,只有一个loading或者占位图而已...)
3. 把script放到页面的底部(先渲染DOM TREE,再执行JS,也可以获取到DOM元素了),也可以基于事件DOMContentLoaded/load等到结构加载完再去获取DOM元素
3. async / defer 给script设置的属性
1. async是开辟HTTP线程加载资源文件,此时DOM TREE继续,但是资源文件一但加载回来,停止DOM TREE,先执行JS代码(不考虑JS引入顺序,谁先加载回来谁先执行)
1. defer也是开辟HTTP线程加载资源文件,即使资源文件加载回来,也会等待DOM树渲染完,然后按照JS的导入顺序依次执行JS(不兼容低版本浏览器)
7. 图片优化
1. 合并(Sripte)
1. BASE64(好用但是慎用)
1. iconfont(CSS绘制)
1. svg
1. 图片的懒加载
<a name="rqqpF"></a>
## 48. 回流 重绘
传统操作Dom消耗性能的原因: DOM的回流/重绘
<a name="e3M28"></a>
#### 1. 回流
当页面的布局或者几何信息发生变化, 浏览器可能需要重新创建DOM树或者重新计算每一个元素在视口中的位置和大小(重新layout), 重新计算完成后, 让浏览器重新渲染, 触发回流必然会引发重绘
触发回流的条件:
1. DOM元素的增删改导致DOM结构发生变化
1. DOM的样式: 如大小, 位置,发生变化
1. 浏览器窗口大小改变(视口改变)
1. 页面第一次加载必然会有一次回流
1. ...
<a name="ZNGCO"></a>
#### 2. 重绘
元素样式发生改变, 但是几何信息和结构信息没有改变, 此时不需要回流, 只需要浏览器把改变的元素重新渲染即可<br />触发回流的条件
1. color
1. background
1. ...
<a name="CJctK"></a>
#### 3. 练习题
优化思路:不是创建一个span就放置在页面中,而是把5个创建好,整体添加到页面中
1. createDocumentFragment 文档碎片
1. 字符串拼接, 最后innerHtml赋值
```javascript
// 向#box盒子中动态插入5个span
// 当前代码会引发5次回流
for (let i = 1; i <= 5; i++) {
let span = document.createElement('span')
span.innerHTML = i
box.appendChild(span)
}
// 优化1
let frag = document.createDocumentFragment()
for (let i = 1; i <= 5; i++) {
let span = document.createElement('span');
span.innerHTML = i;
frag.appendChild(span);
}
box.appendChild(frag)
frag = null // 最后清除变量
// 优化2
let str = ''
for (let i = 1; i <= 5; i++) {
str += `<span>${i}</span>`
}
box.innerHTML = str
4. 浏览器渲染队列机制
在一个上下文中, 从上往下执行, 遇到设置元素几何信息会放入队列中,
- 如果遇到访问元素几何信息, 会马上执行队列中的操作, 并触发回流&重绘, 然后继续往下代码执行….
- 上下文执行完也会触发回流&重绘
技巧: 利用访问元素几何信息立刻触发回流机制
利用访问元素几何信息立马触发回流特点.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代码自上而下执行, 所以大部分代码都是同步的, 但是也有一些代码是异步的
浏览器:
- setTimeout/setInterval(定时器)
- 事件绑定, 事件监听
- ajax/fetch请求, ajax可以设置为同步(http线程)
- Promise中的异步操作
- async/await , generate
- …
node:
- progress,nextTick
- setImmediate
- FS进行I/O操作可以是异步
JS异步操作的运行机制 :事件队列Event queue 和事件循环EventLoop
微任务 -> 宏任务
练习题
- 宏任务执行顺序,谁时间先到达,谁先执行
- 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按照以下维度
- 实例
- [[PromiseStatus]] promise状态: pedding, fulfilled/resolved, rejected
- [[PromiseValue]] promise的值,
- Promise.prototype
- then .then注入回调方法的时候, 我们可以写, 也可以不写, 但是如果状态一旦确定, 想去执行.then 的某个方法, 如果方法没有没注册, 则会向下顺延(找下一个then中注册中对应的方法)
- catch .catch(reason=>{}) === .then(null,reason=>{})
- finally
- 普通对象
- reject 返回失败函数
- resolve 返回成功函数
- all 等待所有promise实例都是成功, 整体返回的实例才成功(并以传入数组返回), 如果有一个失败, 整体都失败, 并返回失败的promise
- race 等待最新有返回值的promise实例, 此实例成功/失败决定最后的成功/失败, (赛跑, 谁快返回谁)
- 语法
- new Promise([executor]) executor是一个可执行的函数
- executor: resolve和reject 传递executor函数的参数
- promise 初始状态是pending, 初始值是undfined
- resolve[value] 修改promise状态为fulfilled/resolved 成功状态, 并且修改其值value
- reject[reason] 修改promise状态为rejected失败状态, 并且修改值为reason
- 如果executor函数中执行报错, 也会返回失败状态, 并且把错误信息返回到reason
- 一旦状态从pending发生改变, 都不能再次更改状态
- 本质
- 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实例,则按照新实例的状态处理
要点:
- 方法中想用await,必须把方法基于async修饰
- 回显把await 后的方法执行
- 异步性:它会把当前上下文,await下面的代码整体都当作一个异步的微任务,放置在EventQueue中
- await只是处理promise实例是成功状态的,如果返回转态是成功,则value拿到的是[[PromiseValue]],并且把之前存储的异步任务拿到栈中让主线程把其执行
- await处理后,立即把函数执行,哪怕函数立即返回成功或者失败的状态,await也没有把其立即处理,而是先等同步的都执行完,再去执行这些异步的任务
- 缺点: 对于await处理的时候,如果返回的是失败的promise,则await下面代码不再执行,失败情况下没有处理,浏览器抛出一个异常信息,此时我们可以基于try/catch捕获到异常,从而进行异常的处理
58. eventloop/eventQueue练习题
题目1
- 难度: 简单
图解: 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
难度: 一般
- 图解: eventloop题目2.png
```javascript
function func1(){
console.log(‘func1 start’);
return new Promise(resolve=>{
}); } function func2(){ console.log(‘func2 start’); return new Promise(resolve=>{resolve('OK');
}); }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项目
弊端:
- 重后台, 轻前端(分工不均匀, 前端弱化)
- 服务器压力太大
- 无法实现局部刷新, (某个区域的数据需要更新, 则重新整个页面刷新 => 全局刷新)
优点:
- 有利于SEO优化, 页面源代码中可以看到所有动态绑定的内容 (chrome可以ctrl+u) 查看源代码
- 如果服务器的抗压能力很强,处理速度很快, 则页面渲染速度很快
2. 客户端渲染
弊端:
- 不利于SEO优化, 基于JS完成的数据绑定, 页面源代码中式不存在的 (chrome可以ctrl+u) 查看源代码
- 需要两次及以上请求才能看到完整的页面(占用请求) 一次是资源页面的请求, 一次是数据的请求, 如果网络很慢, 则白屏时间较长(接口pending时间)
优点:
- 前端和后端工作量相对平衡
- 减轻服务器压力
- 可以实现局部刷新
- 能够实现的交互效果会更多
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
- 协议、 用于客户端与服务器数据传输的协议
- http 超文本传输协议
- https 在http基础上进行了安全设置(SSL/TSL) 证书认证
- ftp 主要用于客户端电脑和服务器端的文件传输
- …
- 登录信息、
- 域名、
- 端口号, 每一个协议都有默认端口号
- http -> 80
- https -> 443
- ftp -> 21
- 请求资源文件路径、
- 查询字符串、
- 片段标识符(hash值)
URL的编码: 对于一些特殊的字符, 我们在客户端和服务器传递的时候需要进行编码与解码
- encodeURI / decodeURI : 主要对地址中的空格或中文进行编码
- encodeURIComponent / decodeURIComponent : 在encodeURI的基础上对/等特殊字符也会进行编码解码
- escape / unescape 主要用于客户端不同页面之间数据传输信息编码解码.例如(cookie)
2. 缓存检查
缓存的位置可分为Memory Cache内存缓存 > Disk Cache 磁盘缓存 , 内存缓存优先于磁盘缓存
强缓存 Expires / Cache-Control, 浏览器对于强缓存的处理, 根据第一次请求资源时返回的响应头来确定.
Expires: 缓存过期时间,用来指定资源到期的时间, 针对HTTP1.0
Cache-Control: cache-control: max-age=2592000 在第一次拿到资源后30天有效, 再次发送请求时会读取缓存位置的资源针对HTTP1.1
http1.1 优先于 http1.0
- 协商缓存 Last-Modified / ETag
协商缓存就是在强缓存失效后, 浏览器携带缓存标识向服务器发起请求, 由服务器根据标识决定是否使用缓存的过程
协商缓存生效则会返回304/Not Modified
协商缓存失效则返回200和请求结果
- http1.0使用Last-Modified 和If-Modified-Since
- http1.1使用ETag 合 IF-None-Match
- 当第一次请求时,服务器会返回对应协议的标识, 浏览器会把请求头信息缓存下来, 当下次请求是会携带If-Modified-since/IF-None-Match在请求头中, 对应的值分别是Last-Modified / ETag, 服务器判断是否缓存并且返回新的资源或者返回304
- 强缓存优缺点
- 优点: 减少对服务器的请求, 加载资源更快, 页面渲染也就快了
- 缺点: 当我们服务器更新文件后, 还在强缓存有效期内, 导致客户端无法及时更新文件
- 解决方案: 1. 使用webpack在文件名添加hash值
- 解决方案: 2. 在文件后添加一个时间戳
3. DNS解析
DNS服务器(域名解析服务器)
- 部署服务器后, 服务器有一个外网IP地址, 基于外网IP可以找到服务器
- 外网IP别人记不住, 但是可以记住域名
- 域名解析服务器DNS, 记录了域名主机地址IP相对应的信息
4. TCP三次握手
seq序号: 用来表示从TCP源端向目的端发送的字节流, 发起方发送数据时,对此进行标记
ack确认序号: 只有ACK标志为1时, 确认序号字段才有效 ask = seq+1
标志位: 共6个
- URG: 紧急指针有效
- ACK: 确认序号有效
- PSH: 接收方应该尽快将这个报文交给应用层
- RST: 重置连接
- SYN: 发起一个新连接
- FIN: 释放一个链接
为什么是三次握手, 而不是两次, / 四次
因为三次握手是一个有效建立连接的方式, 少了双方信息获取不到位, 多了没意义
[两次]
“喂,你听得到吗?”
“我听得到呀”
“喂,你听得到吗?”
“草,我听得到呀!!!!”
“你TM能不能听到我讲话啊!!喂!”
[四次]
“喂,你听得到吗?”
“我听得到呀,你听得到我吗?”
“我能听到你,你能听到我吗?”
“……不想跟傻逼说话”
5. 数据传输
- HTTP报文信息
- 请求头,
- 请求主体
- 响应状态码
- 2
, 3, 4, 5
- 2
并发性
- 一个服务器源能同时支持4-7个并发
6. TCP四次挥手
TCP四次挥手.png
- 一个服务器源能同时支持4-7个并发
为什么连接的时候是三次握手,关闭的时候却是四次握手?
因为当服务器端收到客户端的SYN连接请求报文后, 可以直接发送SYN+ACK报文. 其中ACK报文是用来应答的, SYN报文是用来同步的,
但是关闭连接时, 服务器端接收到FIN报文时, 很可能不会立刻关闭socket, 所以先回复一个ACK报文告诉客户端, 已收到FIN报文信息了, 需要等待服务器端所有数据组装完后, 才能发送FIN报文, 因此不能一起发送
7. 页面渲染
回顾46. 浏览器底层渲染机制
8. HTTP1.0 和HTTP1.1的一些区别
- 缓存处理, 使用的策略不一样
- http1.0 使用Expires / IF-Modified
- http1.1 使用 ETag / IF-None-Match
- 宽带优化及网络连接使用
- 错误通知管理
- Host头处理
- 长连接
9. HTTP1.x 和HTTP2.0相比的新特性有哪些
- 新的二进制格式, (Binary Format)
- http1.x 是基于文本格式
- http2.0 采用二进制格式, 这样在传输数据上就能更加健壮
- 多路复用 MultiPlexing
- 即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面
- header压缩
- 服务器推送(server push)
- 例如, 发送请求一个index.html文件 在客户端收到index.html的同事, 会把index.js / index.css同事推送给客户端
10. 优化汇总
- 利用缓存
- 对于静态资源文件实现强缓存和协商缓存
- 对于不经常更新的接口数据采用本地存储(cooke, localStorage, sessionStorage)
- DNS优化
- 分服务器部署, 增加http并发性
- DNS Prefetch
- TCP的三次握手和四次挥手
- 采用Connection: keep-alive
- 数据传输
- 减少数据传输的大小
- 内存或者数据压缩(webpack等..)
- 服务器开启Gzip压缩(一般能压缩60%)
- 大批量请求进行分批加载, 例如: 分页, 底部加载等, 确保首次请求数据量少
- 减少http请求的次数
- 资源文件合并
- 字体图标
- 雪碧图
- 图片base64
- 减少数据传输的大小
- CND服务器”地域分布式”
- 采用更高的HTTP传输如http2.0, http1.1
- 尽量减少白屏的时间,
- loading效果
- 骨架屏
- 图片懒加载, 延迟加载