Code Monkey home page Code Monkey logo

blogs's Introduction

blogs's People

Contributors

licong96 avatar

Watchers

 avatar

blogs's Issues

浏览器输入URL到拿到HTML文档的过程

主干流程

  1. 从浏览器接收url到开启网络请求线程(这一部分可以展开浏览器的机制以及进程与线程之间的关系)
  2. 开启网络线程到发出一个完整的http请求(这一部分涉及到dns查询,tcp/ip请求,五层因特网协议栈等知识)
  3. 从服务器接收到请求到对应后台接收到请求(这一部分可能涉及到负载均衡,安全拦截以及后台内部的处理等等)
  4. 后台和前台的http交互(这一部分包括http头部、响应码、报文结构、cookie等知识,可以提下静态资源的cookie优化,以及编码解码,如gzip压缩等)
  5. 单独拎出来的缓存问题,http的缓存(这部分包括http缓存头部,etag,catch-control等)
  6. 浏览器接收到http数据包后的解析流程(解析html-词法分析然后解析成dom树、解析css生成css规则树、合并成render树,然后layout、painting渲染、复合图层的合成、GPU绘制、外链资源的处理、loaded和domcontentloaded等)
  7. CSS的可视化格式模型(元素的渲染规则,如包含块,控制框,BFC,IFC等概念)
  8. JS引擎解析过程(JS的解释阶段,预处理阶段,执行阶段生成执行上下文,VO,作用域链、回收机制等等)
  9. 其它(可以拓展不同的知识模块,如跨域,web安全,hybrid模式等等内容)

从浏览器接收url到开启网络请求线程

多进程的浏览器

浏览器是多进程的,有一个主控进程,以及每一个tab页面都会新开一个进程(某些情况下多个tab会合并进程)。
进程可能包括主控进程,插件进程,GPU,tab页(浏览器内核)等等。

  • Browser进程:浏览器的主进程(负责协调、主控),只有一个
  • GPU进程:最多一个,用于3D绘制
  • 浏览器渲染进程(内核):默认每个Tab页面一个进程,互不影响,控制页面渲染,脚本执行,事件处理等
  • 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建

多线程的浏览器渲染进程(内核)

每一个tab页面可以看作是浏览器内核进程,然后这个进程是多线程的,它有几大类子线程

  • GUI线程
  • JS引擎线程
  • 事件触发线程
  • 定时器线程
  • 网络请求线程

解析URL

输入URL后,会进行解析(URL的本质就是统一资源定位符)
URL一般包括几大部分:

  • protocol,协议头,譬如有http,ftp等
  • host,主机域名或IP地址
  • port,端口号
  • path,目录路径
  • query,即查询参数
  • fragment,即#后的hash值,一般用来定位到某个位置

开启网络线程到发出一个完整的http请求

  1. 构建请求
  2. 查找强缓存
  3. DNS解析
  4. 建立TCP连接
  5. 发送HTTP请求

1.构建请求

每次网络请求时都需要开辟单独的线程进行,譬如如果URL解析到http协议,就会新建一个网络线程去处理资源下载。

2.查找强缓存

先检查强缓存,如果命中直接使用,否则进入下一步。

3.DNS解析

由于我们输入的是域名,而数据包是通过IP地址传给对方的。因此我们需要得到域名对应的IP地址。

大致流程:

  • 浏览器提供了DNS数据缓存功能,如果浏览器有缓存,直接使用浏览器缓存,否则使用本机缓存,再没有的话就是用host
  • 如果本地没有,就向dns域名服务器查询(当然,中间可能还会经过路由,也有缓存等),查询到对应的IP

4.建立 TCP 连接

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

http的本质就是tcp/ip请求,需要了解3次握手规则建立连接以及断开连接时的四次挥手。

tcp将http长报文划分为短报文,通过三次握手与服务端建立连接,进行可靠传输

三次握手的步骤:(抽象派)

客户端:hello,你是server么?
服务端:hello,我是server,你是client么
客户端:yes,我是client

需要通过三次握手,才能确认双方的接收与发送能力是否正常。建立连接成功后,接下来就正式传输数据。

然后,待到断开连接时,需要进行四次挥手(因为是全双工的,所以需要四次挥手)

四次挥手的步骤:(抽象派)

主动方:我已经关闭了向你那边的主动通道了,只能被动接收了
被动方:收到通道关闭的信息
被动方:那我也告诉你,我这边向你的主动通道也关闭了
主动方:最后收到数据,之后双方无法通信

三次握手和四次挥手

tcp/ip的并发限制

浏览器对同一域名下并发的tcp连接是有限制的(2-10个不等),Chrome 6个。

而且在http1.0中往往一个资源下载就需要对应一个tcp/ip请求,http2.0已经实现复用。

所以针对这个瓶颈,又出现了很多的资源优化方案。

五层因特网协议栈

其实就是一个概念: 从客户端发出http请求到服务器接收,中间会经过一系列的流程。

简括就是:
从应用层的发送http请求,到传输层通过三次握手建立tcp/ip连接,再到网络层的ip寻址,再到数据链路层的封装成帧,最后到物理层的利用物理介质传输。

5.发送 HTTP 请求

现在TCP连接建立完毕,浏览器可以和服务器开始通信,即开始发送 HTTP 请求。浏览器发 HTTP 请求要携带三样东西: 请求行、请求头和请求体。

总结

总结

从服务器接收到请求到对应后台接收到请求

服务端在接收到请求时,内部会进行很多的处理。

对于大型的项目,由于并发访问量很大,所以往往一台服务器是吃不消的,所以一般会有若干台服务器组成一个集群,然后配合反向代理实现负载均衡

用户发起的请求都指向调度服务器(反向代理服务器,譬如安装了nginx控制负载均衡),然后调度服务器根据实际的调度算法,分配不同的请求给对应集群中的服务器执行,然后调度器等待实际服务器的HTTP响应,并将它反馈给用户。

后台的处理

  1. 一般有的后端是有统一的验证的,如安全拦截,跨域验证
  2. 如果这一步不符合规则,就直接返回了相应的http报文(如拒绝请求等)
  3. 然后当验证通过后,才会进入实际的后台代码,此时是程序接收到请求,然后执行(譬如查询数据库,大量计算等等)
  4. 等程序执行完毕后,就会返回一个http响应包(一般这一步也会经过多层封装)
  5. 然后就是将这个包从后端发送到前端,完成交互

后台和前台的http交互

前后端交互时,http报文作为信息的载体

http报文结构

报文一般包括了:通用头部,请求/响应头部,请求/响应体

通用头部

Request Url: 请求的web服务器地址

Request Method: 请求方式(Get、POST、OPTIONS、PUT、HEAD、DELETE、CONNECT、TRACE)

Status Code: 请求的返回状态码,如200代表成功

Remote Address: 请求的远程服务器地址(会转为IP)

状态码

200——表明该请求被成功地完成,所请求的资源发送回客户端
304——自从上次请求后,请求的网页未修改过,请客户端使用本地缓存
400——客户端请求有错(譬如可以是安全模块拦截)
401——请求未经授权
403——禁止访问(譬如可以是未登录时禁止)
404——资源未找到
500——服务器内部错误
503——服务不可用

cookie

cookie是浏览器的一种本地存储方式,一般用来帮助客户端和服务端通信的,常用来进行身份校验,结合服务端的session使用。

使用场景:

在登陆页面,用户登陆了

此时,服务端会生成一个session,session中有对于用户的信息(如用户名、密码等)

然后会有一个sessionid(相当于是服务端的这个session对应的key)

然后服务端在登录页面中写入cookie,值就是:jsessionid=xxx

然后浏览器本地就有这个cookie了,以后访问同域名下的页面时,自动带上cookie,自动检验,在有效时间内无需二次登陆。

长连接与短连接

  • 长连接:一个tcp/ip连接上可以连续发送多个数据包
  • 短连接:通信双方有数据交互时,就建立一个tcp连接,数据发送完成后,则断开此tcp连接

http 2.0

http的下一代规范

http2.0与http1.0的显著不同点:

  • http1.0中,每请求一个资源,都是需要开启一个tcp/ip连接的,所以对应的结果是,每一个资源对应一个tcp/ip请求,由于tcp/ip本身有并发数限制,所以当资源一多,速度就显著慢下来
  • http2.0中,一个tcp/ip请求可以请求多个资源,也就是说,只要一次tcp/ip请求,就可以请求若干个资源,分割成更小的帧请求,速度明显提升。

http2.0的一些特性:

  • 多路复用(即一个tcp/ip连接可以请求多个资源)
  • 首部压缩(http头部压缩,减少体积)
  • 服务器端推送(服务端可以对客户端的一个请求发出多个响应,可以主动通知客户端)
  • 二进制分帧(在应用层跟传送层之间增加了一个二进制分帧层,改进传输性能,实现低延迟和高吞吐量)
  • 请求优先级(如果流被赋予了优先级,它就会基于这个优先级来处理,由服务器决定需要多少资源来处理该请求。)

https

https就是安全版本的http

https与http的区别就是: 在请求前,会建立ssl链接,确保接下来的通信都是加密的,无法被轻易截取

http的缓存

前后端的http交互中,使用缓存能很大程度上的提升效率,而且基本上对性能有要求的前端项目都是必用缓存的

强缓存与协商缓存

缓存可以简单的划分成两种类型:强缓存(200 from cache)与协商缓存(304)

区别简述如下:

  • 强缓存(200 from cache)时,浏览器如果判断本地缓存未过期,就直接使用,无需发起http请求

  • 协商缓存(304)时,浏览器会向服务端发起http请求,然后服务端告诉浏览器文件未改变,让浏览器使用本地缓存

  • 对于协商缓存,使用Ctrl + F5强制刷新可以使得缓存无效

  • 对于强缓存,在未过期时,必须更新资源路径才能发起新的请求(更改了路径相当于是另一个资源了,这也是前端工程化中常用到的技巧)

缓存头部简述

通过不同的http头部控制区分强缓存和协商缓存。

属于强缓存控制的:

(http1.1)Cache-Control/Max-Age
(http1.0)Pragma/Expires

属于协商缓存控制的:

(http1.1)If-None-Match/E-tag
(http1.0)If-Modified-Since/Last-Modified

跨域

跨域资源共享 CORS 详解
ajax出现请求跨域错误问题,主要原因就是因为浏览器的“同源策略”。

CORS请求原理

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要同时满足以下两大条件,就属于简单请求。

  1. 请求方法是以下三种方法之一:HEAD,GET,POST
  2. HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type(只限于三个值application/x-www-form-urlencoded、 multipart/form-data、text/plain)

凡是不同时满足上面两个条件,就属于非简单请求。

get和post的区别

get和post虽然本质都是tcp/ip,但两者除了在http层面外,在tcp/ip层面也有区别。
get会产生一个tcp数据包,post两个

具体就是:

  • get请求时,浏览器会把headers和data一起发送出去,服务器响应200(返回数据),
  • post请求时,浏览器先发送headers,服务器响应100 continue,浏览器再发送data,服务器响应200(返回数据)。

ajax跨域的表现

第一种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且The response had HTTP status code 404

出现这种情况的原因如下:
本次ajax请求是“非简单请求”,所以请求前会发送一次预检请求(OPTIONS)
服务器端后台接口没有允许OPTIONS请求,导致无法找到对应接口地址

解决方案:  后端允许options请求
第二种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且The response had HTTP status code 405

这种现象和第一种有区别,这种情况下,后台方法允许OPTIONS请求,但是一些配置文件中(如安全配置),阻止了OPTIONS请求,才会导致这个现象

解决方案: 后端关闭对应的安全配置
第三种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且status 200

这种现象和第一种和第二种有区别,这种情况下,服务器端后台允许OPTIONS请求,并且接口也允许OPTIONS请求,但是头部匹配时出现不匹配现象

比如origin头部检查不匹配,比如少了一些头部的支持(如常见的X-Requested-With头部),然后服务端就会将response返回给前端,前端检测到这个后就触发XHR.onerror,导致前端控制台报错

解决方案: 后端增加对应的头部支持

第四种现象:heade contains multiple values '*,*'

表现现象是,后台响应的http头部信息有两个Access-Control-Allow-Origin:*

解决方案:
建议删除代码中手动添加的*,只用项目配置中的即可
建议删除IIS下的配置*,只用项目配置中的即可

如何解决ajax跨域

一般ajax跨域解决就是通过JSONP解决或者CORS解决,如以下:(注意,现在已经几乎不会再使用JSONP了,所以JSONP了解下即可)

CORS解决跨域问题

实际项目中,后端应该如何配置以解决问题(因为大量项目实践都是由后端进行解决的)

Web安全

XSS(跨站脚本攻击)

CSRF(跨站请求伪造)

来源: 页面加载的过程

浏览器相关

浏览器缓存

  • 强缓存
  • 协商缓存
  • 缓存位置

强缓存

不需要发送请求。

通过相应的字段来进行检查,HTTP/1.0使用Expires,HTTP/1.1使用Cache-Control。

Expires即过期时间。如果服务器的时间和浏览器的时间不一致,那服务器返回的这个过期时间可能就是不准确的。因此这种方式很快在后来的HTTP1.1版本中被抛弃了。

Cache-Control: max-age=3600,代表这个响应返回后在 3600 秒,也就是一个小时之内可以直接使用缓存。

协商缓存

强缓存失效之后,浏览器在请求头中携带相应的缓存tag来向服务器发请求,由服务器根据这个tag,来决定是否使用缓存,这就是协商缓存。

这样的缓存tag分为两种: Last-Modified 和 ETag。

Last-Modified:即最后修改时间。

ETag:是服务器根据当前文件的内容,给文件生成的唯一标识,只要里面的内容有改动,这个值就会变。

缓存位置

当强缓存命中或者协商缓存中服务器返回304的时候,我们直接从缓存中获取资源。那这些资源究竟缓存在什么位置呢?

浏览器中的缓存位置一共有四种,按优先级从高到低排列分别是:

  • Service Worker
  • Memory Cache
  • Disk Cache
  • Push Cache

缓存机制总结

首先通过Cache-Control验证强缓存是否可用。

  • 如果强缓存可用,直接使用
  • 否则进入协商缓存,即发送 HTTP 请求,服务器通过请求头中的If-Modified-Since或者If-None-Match字段检查资源是否更新
    • 若资源更新,返回资源和200状态码
    • 否则,返回304,告诉浏览器直接从缓存获取资源

浏览器的本地存储

  • Cookie
  • WebStorage
    • localStorage
    • sessionStorage
  • IndexedDB

Cookie

Cookie 最开始被设计出来其实并不是来做本地存储的,而是为了弥补HTTP在状态管理上的不足。

因为HTTP 协议是一个无状态协议,所以Cookie 的作用就是用来做状态存储的。

特点和缺陷:

  • 容量缺陷。Cookie 的体积上限只有4KB,只能用来存储少量的信息。
  • 性能缺陷。Cookie 紧跟域名,不管域名下面的某一个地址需不需要这个 Cookie ,请求都会携带上完整的 Cookie。
  • 安全缺陷。由于 Cookie 以纯文本的形式在浏览器和服务器中传递,很容易被非法用户截获,然后进行一系列的篡改。另外,在HttpOnly为 false 的情况下,Cookie 信息能直接通过 JS 脚本来读取。

localStorage

特点:

  • localStorage 的容量上限为5M,相比于Cookie的 4K 大大增加。
  • 只存在客户端,默认不参与与服务端的通信。

localStorage有较大容量和持久特性,可以利用localStorage存储一些内容稳定的资源。

sessionStorage

sessionStorage和localStorage基本相同,只有一个本质的区别,sessionStorage只是会话级别的存储,并不是持久化存储。会话结束,也就是页面关闭,这部分sessionStorage就不复存在了。

IndexedDB

IndexedDB是运行在浏览器中的非关系型数据库, 本质上是数据库,绝不是和刚才WebStorage的 5M 一个量级,理论上这个容量是没有上限的。

JS运行机制

JS执行

当拿到一段 JavaScript 代码时,浏览器或者 Node 环境首先要做的就是,传递给 JavaScript 引擎,并且要求它去执行。

在 ES3 和更早的版本中,JavaScript 本身还没有异步执行代码的能力,这也就意味着,宿主环境传递给 JavaScript 引擎一段代码,引擎就把代码直接顺次执行了,这个任务也就是宿主发起的任务。

但是,在 ES5 之后,JavaScript 引入了 Promise,这样,不需要浏览器的安排,JavaScript 引擎本身也可以发起任务了。采纳 JSC 引擎的术语,我们把宿主发起的任务称为宏观任务,把 JavaScript 引擎发起的任务称为微观任务。

每次的执行过程,其实都是一个宏观任务。我们可以大概理解:宏观任务的队列就相当于事件循环。
在宏观任务中,JavaScript 的 Promise 还会产生异步代码,JavaScript 必须保证这些异步代码在一个宏观任务中完成,因此,每个宏观任务中又包含了一个微观任务队列。

分析执行顺序

  1. 首先我们分析有多少个宏任务;
  2. 在每个宏任务中,分析有多少个微任务;
  3. 根据调用次序,确定宏任务中的微任务执行次序;
  4. 根据宏任务的触发规则和调用次序,确定宏任务的执行次序;
  5. 确定整个顺序。

Event loop(事件循环)

  1. 在执行栈中执行一个宏观任务。
  2. 遇到同步代码直接执行,遇到异步代码,进行区分宏观任务和微观任务,添加到对应的任务队列中。
  3. 当前宏观任务执行完毕后,立即执行下面的所有微观任务。(微观任务中优先级高的先执行)
  4. 微观任务全部执行完毕后,GUI接管渲染。
  5. 继续从任务队列中查找下一个宏观任务。

macro-task(宏任务)

宿主环境发起的任务。

  • 整体代码script
  • setTimeout & setInterval
  • I/O

micro-task(微任务)

JS引擎发起的任务。

  • Promise
  • process.nextTick
  • Object.observe
  • MutationObserver

Promise

Promise 是 JavaScript 语言提供的一种标准化的异步管理方式,它的总体**是,需要进行 io、等待或者其它异步操作的函数,不返回真实结果,而返回一个“承诺”,函数的调用方可以在合适的时机,选择等待这个承诺兑现(通过 Promise 的 then 方法的回调)。

Promise本身是同步的立即执行函数 new Promise直接执行。then后面的函数被分发到微任务Event Queue中。
如果俩个微任务的优先级相同,那么任务队列自上而下执行,但是Promise的优先级高于async,所以先执行Promise后面的回调函数。

async/await

async/await 是 ES2016 新加入的特性。
async 函数必定返回 Promise,我们把所有返回 Promise 的函数都可以认为是异步函数。
async 函数是一种特殊语法,特征是在 function 关键字之前加上 async 关键字,这样,就定义了一个 async 函数,我们可以在其中使用 await 来等待一个 Promise。

async 函数强大之处在于,它是可以嵌套的。我们在定义了一批原子操作的情况下,可以利用 async 函数组合出新的 async 函数。

generator/iterator

此外,generator/iterator 也常常被跟异步一起来讲,我们必须说明 generator/iterator 并非异步代码,只是在缺少 async/await 的时候,一些框架(最著名的要数 co)使用这样的特性来模拟 async/await。
但是 generator 并非被设计成实现异步,所以有了 async/await 之后,generator/iterator 来模拟异步的方法应该被废弃。

setTimeout的执行

这段代码什么意思?

 setTimeout(function() {
    console.log('执行')
 }, 3000)    

解释是::3秒后,setTimeout里的函数被会推入Event Queue,而Event Queue里的任务,只有在主线程空闲时才会执行。

所以只有满足这两个条件: (1)3秒后;(2)主线程空闲。才会执行该函数。

如果主线程执行内容很多,执行时间超过3秒,假如执行了4秒,那么这个函数只能在4秒后执行。

HTTP

HTTP 报文结构是怎样的?

请求部分:请求行、请求头、空行、请求体

响应部分:响应行、响应头、空行、响应体

请求行

方法 + 路径 + http版本

GET /home HTTP/1.1

响应行

http版本 + 状态码

HTTP/1.1 200 OK

请求头 & 响应头

key-val的值

空行

用来区分开请求头和请求体

问:如果说在头部中间故意加一个空行会怎么样?

答:那么空行后的内容全部被视为请求体。

请求体 & 响应体

就是具体的数据了,也就是body部分。请求报文对应请求体, 响应报文对应响应体。

有哪些请求方法?

  • GET: 通常用来获取资源
  • HEAD: 获取资源的元信息
  • POST: 提交数据,即上传数据
  • PUT: 修改数据
  • DELETE: 删除资源(几乎用不到)
  • CONNECT: 建立连接隧道,用于代理服务器
  • OPTIONS: 列出可对资源实行的请求方法,用来跨域请求
  • TRACE: 追踪请求-响应的传输路径

GET 和 POST 有什么区别?

  • 从缓存的角度,GET 请求会被浏览器主动缓存下来,留下历史记录,而 POST 默认不会。
  • 从编码的角度,GET 只能进行 URL 编码,只能接收 ASCII 字符,而 POST 没有限制。
  • 从参数的角度,GET 一般放在 URL 中,因此不安全,POST 放在请求体中,更适合传输敏感信息。
  • 非简单请求时,POST会有OPTIONS请求。

非简单请求

请求方法是以下三种方法之一:HEAD,GET,POST

HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type(只限于三个值application/x-www-form-urlencoded、 multipart/form-data、text/plain)

凡是不同时满足上面两个条件,就属于非简单请求。

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。"预检"请求用的请求方法是OPTIONS

HTTP 状态码

RFC 规定 HTTP 的状态码为三位数,被分为五类:

  • 1xx: 表示目前是协议处理的中间状态,还需要后续操作。
  • 2xx: 表示成功状态。
  • 3xx: 重定向状态,资源位置发生变动,需要重新请求。
  • 4xx: 请求报文有误。
  • 5xx: 服务器端发生错误。

HTTP 的特点和缺点

特点:

  • 灵活可扩展
  • 可靠传输
  • 请求-应答
  • 无状态

缺点:

  • 无状态
  • 明文传输
  • 队头阻塞

HTTP 如何处理大文件的传输?

HTTP 针对这一场景,采取了范围请求的解决方案,允许客户端仅仅请求一个资源的一部分。

前提是服务器要支持范围请求,要支持这个功能,就必须加上这样一个响应头: Accept-Ranges: none。

HTTP 中如何处理表单数据的提交?

有两种主要的表单提交的方式,体现在两种不同的Content-Type取值:

  • application/x-www-form-urlencoded
  • multipart/form-data

由于表单提交一般是POST请求,很少考虑GET,因此这里我们将默认提交的数据放在请求体中。

在实际的场景中,对于图片等文件的上传,基本采用multipart/form-data。

关于Cookie

前面说到了 HTTP 是一个无状态的协议,每次 http 请求都是独立、无关的,默认不需要保留状态信息。但有时候需要保存一些状态,怎么办呢?

HTTP 为此引入了 Cookie。

关于跨域

浏览器遵循同源政策(scheme(协议)、host(主机)和port(端口)都相同则为同源)。

非同源站点有这样一些限制:

  • 不能读取和修改对方的 DOM
  • 不读访问对方的 Cookie、IndexDB 和 LocalStorage
  • 限制 XMLHttpRequest 请求。(后面的话题着重围绕这个)

当浏览器向目标 URI 发 Ajax 请求时,只要当前 URL 和目标 URL 不同源,则产生跨域,被称为跨域请求。

解决跨域的几种方案

  • CORS
  • JSONP
  • Nginx
  • postMessage
  • WebSocket

CORS

CORS 其实是 W3C 的一个标准,全称是跨域资源共享。它需要浏览器和服务器的共同支持,服务器需要附加特定的响应头。

简单请求和非简单请求

浏览器根据请求方法和请求头的特定字段,将请求做了一下分类,具体来说规则是这样,凡是满足下面条件的属于简单请求:

请求方法为 GET、POST 或者 HEAD
请求头的取值范围: Accept、Accept-Language、Content-Language、Content-Type(只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)

浏览器画了这样一个圈,在这个圈里面的就是简单请求, 圈外面的就是非简单请求,然后针对这两种不同的请求进行不同的处理。

Nginx

Nginx 是一种高性能的反向代理服务器,相当于一个中间跳板,可以用来轻松解决跨域问题。

正向代理帮助客户端访问客户端自己访问不到的服务器,然后将结果返回给客户端。

反向代理拿到客户端的请求,将请求转发给其他的服务器,主要的场景是维持服务器集群的负载均衡,换句话说,反向代理帮其它的服务器拿到请求,然后选择一个合适的服务器,将请求转交给它。

HTTP/2 有哪些改进?

性能的提升主要在于两点:

  • 头部压缩
  • 多路复用

解决队头阻塞问题。

当然还有一些颠覆性的功能实现:

  • 设置请求优先级
  • 服务器推送

相关资料

HTTP之问

记录一下问题

html2canvas 兼容问题

"html2canvas": "1.0.0-rc.1" 版本比较稳定,IOS 13,没有兼容问题

React 原理部分

createElement 函数

一些问题

它是什么?

它有什么作用?

它接受哪些参数?这些参数都有什么作用?

它返回什么?

它是怎么实现的?

浏览器渲染原理与过程

浏览器渲染过程主要包含五步

  1. 浏览器将获取到的HTML文档构建成DOM树。
  2. 样式计算,构建层叠样式表CSSOM(CSS Object Model)(CSS 对象模型)。
  3. 将DOM树和CSS规则生成布局树(Layout Tree)
  4. 渲染树的每个元素包含的内容都是计算过的,它被称之为布局(layout)。
  5. 将渲染树的各个节点绘制到屏幕上,这一步被称为绘制(painting)。
  6. Composite:渲染层合并。

浏览器使用一种流式处理的方法,只需要一次绘制操作就可以布局所有的元素。

浏览器渲染网页的具体流程

1. 构建DOM树

当浏览器接收到服务器响应来的HTML文档后,会遍历文档节点,生成DOM树。当前节点的所有子节点都构建好后,才会去构建当前节点的下一个兄弟节点。

需要注意:

  • DOM树在构建的过程中可能会被JS的加载执行阻塞。
  • display: none 的元素也会在DOM树中
  • 注释也会在DOM树中
  • script标签也会在DOM树中

解析算法

这个算法分为两个阶段:

  1. 标记化。
  2. 建树。

对应的两个过程就是词法分析和语法分析。

标记化算法:这个算法输入为HTML文本,输出为HTML标记,也成为标记生成器。

建树算法:标记生成器会把每个标记的信息发送给建树器。建树器接收到相应的标记时,会创建对应的 DOM 对象。

2. 样式计算,构建CSSOM规则树

浏览器是无法直接识别 CSS 样式文本的,因此浏览器会解析CSS文件并生成CSSOM,每个CSS文件都被分析成一个StyleSheet对象,每个对象都包含CSS规则。CSS规则对象包含对应于CSS语法的选择器和声明对象以及其他对象。

  1. 格式化样式表
  2. 标准化样式属性

计算每个节点的具体样式

样式已经被格式化和标准化,接下来就可以计算每个节点的具体样式信息了。

主要就是两个规则: 继承和层叠。

每个子节点都会默认继承父节点的样式属性,如果父节点中没有找到,就会采用浏览器默认样式。

所有的样式值会被挂在到window.getComputedStyle当中,也就是可以通过JS来获取计算后的样式。

需要注意:

  • CSS解析可以与DOM解析同时进行。
  • CSS解析与script的执行互斥 。
  • 在Webkit内核中进行了script执行优化,只有在JS访问CSS时才会发生互斥。

3.生成布局树

现在已经生成了DOM树和DOM样式,接下来要做的就是通过浏览器的布局系统确定元素的位置,也就是要生成一棵布局树(Layout Tree)。

大致工作如下:

  1. 遍历生成的 DOM 树节点,并把他们添加到布局树中。
  2. 计算布局树节点的坐标位置。

需要注意:

  • display: none的元素不在Render Tree中
  • visibility: hidden的元素在Render Tree中

现在 Chrome 团队已经做了大量的重构,已经没有生成Render Tree(渲染树)的过程了。而布局树的信息已经非常完善,完全拥有Render Tree的功能。

参考:从Chrome源码看浏览器如何layout布局

总结

总结

渲染过程

接下来就来拆解下一个过程——渲染。
分为以下几个步骤:

  1. 建立图层树(Layer Tree)
  2. 生成绘制列表
  3. 生成图块并栅格化
  4. 显示器显示内容

1. 建立图层树

浏览器在构建完布局树之后,还会对特定的节点进行分层,构建一棵图层树(Layer Tree)。目的是为了处理一些复杂的场景,比如3D动画如何呈现出变换效果,当元素含有层叠上下文时如何控制显示和隐藏等等。

一般情况下,节点的图层会默认属于父亲节点的图层(这些图层也称为合成层)。那什么时候会提升为一个单独的合成层呢?

有两种情况需要分别讨论,一种是显式合成,一种是隐式合成。

显式合成

一、 拥有层叠上下文的节点。

HTML根元素本身就具有层叠上下文。
普通元素设置position不为static并且设置了z-index属性,会产生层叠上下文。
元素的 opacity 值不是 1
元素的 transform 值不是 none

二、需要剪裁的地方。

如果容器出现了滚动条,那么滚动条会被单独提升为一个图层。

隐式合成

简单来说就是层叠等级低的节点被提升为单独的图层之后,那么所有层叠等级比它高的节点都会成为一个单独的图层。

值得注意的是,当需要repaint时,只需要repaint本身,而不会影响到其他的层。

2.生成绘制列表

接下来渲染引擎会将图层的绘制拆分成一个个绘制指令,比如先画背景、再描绘边框......然后将这些指令按顺序组合成一个待绘制列表,相当于给后面的绘制操作做了一波计划。

3. 生成图块和生成位图

现在开始绘制操作,实际上在渲染进程中绘制操作是由专门的线程来完成的,这个线程叫合成线程。

绘制列表准备好了之后,渲染进程的主线程会给合成线程发送commit消息,把绘制列表提交给合成线程。然后合成线程会选择视口附近的图块,把它交给栅格化线程池生成位图。

4. 显示器显示内容

栅格化操作完成后,合成线程会生成一个绘制命令,即"DrawQuad",并发送给浏览器进程。

浏览器进程中的viz组件接收到这个命令,根据这个命令,把页面内容绘制到内存,也就是生成了页面,然后把这部分内存发送给显卡。

总结

总结

渲染层合并(Composite)

渲染层合并,由上一步可知,对页面中 DOM 元素的绘制是在多个层上进行的。在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。

默认只有一个复合图层,所有的DOM节点都是在这个复合图层下,如果开启了硬件加速功能,可以将某个节点变成复合图层。复合图层之间的绘制互不干扰,由GPU直接控制。

Chrome 中有不同类型的层

这里讨论的是 WebKit,描述的是 Chrome 的实现细节。
Chrome 拥有两套不同的渲染路径(rendering path):硬件加速路径和旧软件路径(older software path)
Chrome 中有不同类型的层: RenderLayer(渲染图层)和GraphicsLayer(复合图层),只有 GraphicsLayer 是作为纹理(texture)上传给GPU的。

RenderLayer(渲染图层/普通图层)

渲染图层,是页面普通的文档流。我们虽然可以通过绝对定位,相对定位,浮动定位脱离文档流,但它仍然属于默认复合层,共用同一个绘图上下文对象(GraphicsContext)。

GraphicsLayer(复合图层)

复合图层,又称图形层。它会单独分配系统资源,每个复合图层都有一个独立的绘图上下文对象(GraphicsContext)。(当然也会脱离普通文档流,这样一来,不管这个复合图层中怎么变化,也不会影响默认复合层里的回流重绘)

渲染图层与复合图层关系

某些特殊的渲染层会被提升为复合成层(Compositing Layers),复合图层拥有单独的 GraphicsLayer,而其他不是复合图层的渲染层,则和其第一个拥有 GraphicsLayer 父层共用一个。

复合图层的作用

硬件加速会使页面流畅。

  • 合成层的位图,会交由 GPU 合成,比 CPU 处理要快。
  • 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层
  • 对于 transform 和 opacity 效果,不会触发 layout 、layer和 paint,直接进入合成线程处理
  • CPU 和 GPU 之间的并行性,可以同时运行以创建高效的图形管道。

一个元素开启硬件加速后会变成合成层,可以独立于普通文档流中,改动后可以避免整个页面重绘,提升性能。

  1. 提升动画效果的元素 合成层的好处是不会影响到其他元素的绘制。
    使用 transform 或者 opacity 来实现动画效果, 这样只需要做合成层的合并就好了。

复合图层的使用注意事项

  • 但是尽量不要大量使用复合图层,否则由于资源消耗过度,页面反而会变的更卡。
  • 层爆炸。
  • 使用3D硬件加速提升动画性能时,最好给元素增加一个z-index属性,人为干扰复合层的排序,可以有效减少chrome创建不必要的复合层,提升渲染性能,移动端优化效果尤为明显。

复合图层创建标准

  • 3D转换:translate3d,translateZ依此类推;
  • transform和opacity经由Element.animate();
  • transform和opacity经由СSS过渡和动画;
  • 有合成层后代同时本身 fixed 定位
  • will-change(作用是提前告诉浏览器要变化,这样浏览器会开始做一些优化工作);
  • 拥有加速 CSS 过滤器的元素filter;
  • 元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)

关键渲染路径

关键渲染路径是指浏览器从最初接收请求来的HTML、CSS、javascript等资源,然后解析、构建树、渲染布局、绘制,最后呈现给用户能看到的界面这整个过程。

用户看到页面实际上可以分为两个阶段:页面内容加载完成和页面资源加载完成,分别对应于DOMContentLoaded和Load。

  • DOMContentLoaded 事件触发时,初始的 HTML 文档被完全加载和解析完成之后,无需等待样式表、图像完全加载。

  • Load 事件触发时,页面上所有的DOM,样式表,脚本,图片都已加载完成。

浏览器渲染引擎

  • webkit:是一款开源渲染引擎,它本来是为linux平台研发的,后来由Apple移植到Mac及Windows上。

渲染阻塞

1. JS阻塞页面

JS可以操作DOM来修改DOM结构,可以操作CSSOM来修改节点样式,这就导致了浏览器在遇到<script>标签时,DOM构建将暂停,直至脚本完成执行,然后继续构建DOM。如果脚本是外部的,会等待脚本下载完毕,再继续解析文档。现在可以在script标签上增加属性defer或者async。脚本解析会将脚本中改变DOM和CSS的地方分别解析出来,追加到DOM树和CSSOM规则树上。

每次去执行JavaScript脚本都会严重地阻塞DOM树的构建,如果JavaScript脚本还操作了CSSOM,而正好这个CSSOM还没有下载和构建,浏览器甚至会延迟脚本执行和构建DOM,直至完成其CSSOM的下载和构建。所以,script标签的位置很重要。

JS阻塞了构建DOM树,也阻塞了其后的构建CSSOM规则树,整个解析进程必须等待JS的执行完成才能够继续,这就是所谓的JS阻塞页面。

2. CSS阻塞渲染

由于CSSOM负责存储渲染信息,浏览器就必须保证在合成渲染树之前,CSSOM是完备的,这种完备是指所有的CSS(内联、内部和外部)都已经下载完,并解析完,只有CSSOM和DOM的解析完全结束,浏览器才会进入下一步的渲染,这就是CSS阻塞渲染。

CSS阻塞渲染意味着,在CSSOM完备前,页面将一直处理白屏状态,这就是为什么样式放在head中,仅仅是为了更快的解析CSS,保证更快的首次渲染。

需要注意的是,即便你没有给页面任何的样式声明,CSSOM依然会生成,默认生成的CSSOM自带浏览器默认样式。

当解析HTML的时候,会把新来的元素插入DOM树里面,同时去查找CSS,然后把对应的样式规则应用到元素上,查找样式表是按照从右到左的顺序去匹配的。

回流和重绘(reflow和repaint)

reflow(回流)

当浏览器发现布局发生了变化,这个时候就需要倒回去重新渲染,这个回退的过程叫reflow。

reflow会从html这个root frame开始递归往下,依次计算所有的结点几何尺寸和位置,以确认是渲染树的一部分发生变化还是整个渲染树。

reflow几乎是无法避免的,因为只要用户进行交互操作,就势必会发生页面的一部分的重新渲染,且通常我们也无法预估浏览器到底会reflow哪一部分的代码,因为他们会相互影响。

repaint(重绘)

repaint则是当我们改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸和位置没有发生改变。

需要注意的是,display:none会触发reflow,而visibility: hidden属性则只会触发repaint。

另外有些情况下,比如修改了元素的样式,浏览器并不会立刻reflow或repaint一次,而是会把这样的操作积攒一批,然后做一次reflow,这又叫异步reflow或增量异步reflow。

引起reflow

现代浏览器会对回流做优化,它会等到足够数量的变化发生,再做一次批处理回流。

  • 页面第一次渲染(初始化)
  • DOM树变化(如:增删节点)
  • Render树变化(如:padding改变)
  • 浏览器窗口resize
  • 获取元素的某些属性

引起repaint

reflow回流必定引起repaint重绘,重绘可以单独触发。

  • 背景色、颜色、字体改变(注意:字体大小发生变化时,会触发回流)

减少reflow、repaint触发次数

  • 用transform做形变和位移可以减少reflow
  • 避免逐个修改节点样式,尽量一次性修改
  • 使用DocumentFragment将需要多次修改的DOM元素缓存,最后一次性append到真实DOM中渲染
  • 可以将需要多次修改的DOM元素设置display:none,操作完再显示。(因为隐藏元素不在render树内,因此修改隐藏元素不会触发回流重绘)
  • 避免多次读取某些属性
  • 通过绝对位移将复杂的节点元素脱离文档流,形成新的Render Layer,降低回流成本

优化渲染效率的建议

  • 合法地去书写HTML和CSS ,且不要忘了文档编码类型
  • 样式文件应当在head标签中,而脚本文件在body结束前,这样可以防止阻塞的方式
  • 简化并优化CSS选择器,尽量将嵌套层减少到最小
  • DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作
  • 如果某个样式是通过重排得到的,那么最好缓存结果。避免下一次用到的时候,浏览器又要重排
  • 不要一条条地改变样式,而要通过改变class,或者csstext属性,一次性地改变样式
  • 尽量用transform来做形变和位移
  • 尽量使用离线DOM,而不是真实的网页DOM,来改变元素样式。比如,操作Document Fragment对象,完成后再把这个对象加入DOM。再比如,使用cloneNode()方法,在克隆的节点上进行操作,然后再用克隆的节点替换原始节点。
  • 先将元素设为display: none(需要1次重排和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要1次重排和重绘)。这样一来,你就用两次重新渲染,取代了可能高达100次的重新渲染。
  • position属性为absolute或fixed的元素,重排的开销会比较小,因为不用考虑它对其他元素的影响。
  • 只在必要的时候,才将元素的display属性为可见,因为不可见的元素不影响重排和重绘。另外,visibility : hidden的元素只对重绘有影响,不影响重排。
  • 使用window.requestAnimationFrame()、window.requestIdleCallback()这两个方法调节重新渲染。

来源:
浏览器渲染原理与过程
浏览器解析

JS执行上下文

执行上下文

JavaScript 标准把一段代码(包括函数),执行所需的所有信息定义为:“执行上下文”。

ES3 中的执行上下文

包含三个部分:

  • scope:作用域,也常常被叫做作用域链。
  • variable object:变量对象,用于存储变量的对象。
  • this value:this 值。

ES5中的执行上下文

在 ES5 中,改进了命名方式,把执行上下文最初的三个部分改为下面这个样子:

  • lexical environment:词法环境,当获取变量时使用。
  • variable environment:变量环境,当声明变量时使用。
  • this value:this 值。

ES2018中的执行上下文

在 ES2018 中,执行上下文又变成了这个样子,this 值被归入 lexical environment,但是增加了不少内容。

  • lexical environment:词法环境,当获取变量或者 this 值时使用。
  • variable environment:变量环境,当声明变量时使用。
  • code evaluation state:用于恢复代码执行位置。
  • Function:执行的任务是函数时使用,表示正在被执行的函数。
  • ScriptOrModule:执行的任务是脚本或者模块时使用,表示正在被执行的代码。
  • Realm:使用的基础库和内置对象实例。
  • Generator:仅生成器上下文有这个属性,表示当前生成器。

ES3中的变量对象

全局上下文中的变量对象就是全局对象。
函数上下文中,我们用活动对象(activation object, AO)来表示变量对象。

执行过程

执行上下文的代码会分成两个阶段进行处理:分析和执行,我们也可以叫做:

  1. 进入执行上下文
  2. 代码执行

1.当进入执行上下文时

变量对象会包括:

  1. 函数的所有形参 (如果是函数上下文)
  2. 函数声明
  3. 变量声明

2.代码执行

  1. 全局上下文的变量对象初始化是全局对象
  2. 函数上下文的变量对象初始化只包括 Arguments 对象
  3. 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值
  4. 在代码执行阶段,会再次修改变量对象的属性值

ES5及以后的词法环境

先梳理JS引擎编译执行过程大致的三个阶段

  1. 引擎进入到执行上下文的时候,会先把代码做词法分析。词法分析是指:登记变量声明、函数声明、函数声明的形参。
  2. 在分词结束以后,会做代码解析,引擎将 token 解析翻译成一个AST(抽象语法树)。
  3. 引擎生成CPU可以执行的机器码。

在第一步里有个词法分析,它用来登记变量声明、函数声明、函数声明的形参,后续代码执行的时候就知道去哪里拿变量的值和函数了,这个登记的地方就是Lexical Environment(词法环境)。

词法环境有两个组成部分

  1. 环境记录(Environment Record),这个就是真正登记变量的地方。
  2. 对外部词法环境的引用(outer),它是作用域链能够连起来的关键。

闭包

维基百科上面闭包的定义:闭包是一个record,它存储了一个函数和它的环境,这个环境存储了该函数的自由变量,JS的函数完全符合这个定义,所以跟闭包对应的概念就是“函数”。

JSBridge原理

JavaScript 调用 Native的方式

主要有两种:

  1. 注入API

  2. 拦截URL SCHEME。

1.1注入API

注入 API 方式的主要原理是:通过 WebView 提供的接口,向 JavaScript 的 Context(window)中注入对象或者方法,让 JavaScript 调用时,直接执行相应的 Native 代码逻辑,达到 JavaScript 调用 Native 的目的。

2.1拦截 URL SCHEME

拦截 URL SCHEME 的主要流程是:Web 端通过某种方式(例如 iframe.src)发送 URL Scheme 请求,之后 Native 拦截到请求并根据 URL SCHEME(包括所带的参数)进行相关操作。

Native 调用 JavaScript 的方式

将方法注册到Native中,从外部调用 JavaScript 中的方法, 方法必须在全局的 window 上。

罗列一下知识点

WKWebView 和 UIWebView

WKScriptMessageHandler(只适用于WKWebView,iOS8+)

IOS 8之前在 UIWebView 中使用 schema 方式

WKWebView优点

  1. WKWebView的内存开销比UIWebView小很多

  2. 官方宣称的高达60fps的滚动刷新率以及内置手势

  3. 支持了更多的HTML5特性

  4. 有Safari相同的JavaScript引擎

  5. 提供常用的属性,如加载网页进度的estimatedProgress属性

WKWebview本身提供了API实现JS交互,不需要借助JavaScriptCore或者webJavaScriptBridge。使用WKUserContentController实现js native交互。简单的说就是先注册约定好的方法,然后再调用。
注册一个方法,调用以及注册好的方法。

WebViewJavascriptBridge

性能优化

从上至下

性能优化不能只着眼于局部的代码,需要从端到端的业务场景建立体系来考虑。

  1. 现状评估和建立指标
  2. 技术方案
  3. 积极执行
  4. 结果评估和监控

现状评估和建立指标

正确地评估现状和建立指标是最关键的一步。

什么样的性能指标能更好地评估它的体验?
什么样的指标会影响业务价值呢?

重要指标:

  • 页面加载性能

页面加载性能

页面加载性能,跟用户的流失率有非常强的关联性,而用户流失率,正是公司业务非常看重的指标。

  1. 秒开率。一秒之内打开的用户占用户总量的百分比
  2. 白屏时间。地址栏输入网址后回车 - 浏览器出现第一个元素
  3. 首屏时间。浏览器第一屏渲染完成
  4. 用户可操作时间
  5. 页面总下载时间

网页的加载时间,不但跟体积有关系,还跟请求数有很大关系
涉及的并不仅是前端技术,有服务端、客户端、设计师团队,想做好性能优化,不能把自己限制在局部的视角,必须是整个业务一起考虑。

技术方案

浏览器相关优化:

  • 浏览器缓存
  • 浏览器页面渲染优化
  • 降低请求成本
  • 减少传输体积

代码本身上的优化

React相关优化

Webpack相关优化

积极执行

结果评估和监控

做好数据的采集和展示。

利用浏览器Performace API

CSS世界(笔记)

CSS 世界的诞生就是为图文信息展示服务的

文档流
一套规则,决定元素按照什么机制进行布局。

内联盒模型
内容区域(content area)、内联盒子(inline box)、行框盒子(line box)、包含盒子(containing box)

containing box
替换元素img的样式content属性和src有一样的效果

三无准则
“无宽度,无图片,无浮动”
表现为“外部尺寸”的块级元素一旦设置了宽度,流动性就丢失了。

width
width作用于content-box 。
宽度分离,padding-box,border-box
box-sizing盒尺寸,决定width作用于哪个box
margin-box???

height
普通文档流中的元素,百分比高度值要想起作用, 其父级必须有一个可以生效的高度值。

max-width
!important 比直接在元素的 style 属性中设置 CSS 声明还要高,但是 max-width 会覆盖 width,超越!important

max-height默认值是auto,auto无法和具体值进行换算,可以设置具体指和一个更大的值制作动画

padding
给内联元素设置padding, 对视觉层和布局层具有双重影响,添加一个背景颜色可以看出来,所有类似“垂直方向 padding 对内联元素没有作用”的说法是不正确的。

React Hook

Hook解决的问题

  1. 在组件之间复用状态逻辑很难
  2. 复杂组件变得难以理解
  3. 可以不使用class

1.在组件之间复用状态逻辑很难

之前的相关解决方案是:高阶组件、render props。
缺点是组件会形成“嵌套地狱”。

现在,React为共享状态逻辑提供了更好的原生途径。使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使你在无需修改组件结构的情况下复用状态逻辑。

2.复杂组件变得难以理解

起初很简单的组件,会逐渐被状态逻辑和副作用充斥。相关的代码被进行了拆分,而不相关的代码却被组合在一起,这样会导致逻辑混乱,很容易产生BUG。

为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数。

3.难以理解的 class

Hook 使你在非 class 的情况下可以使用更多的 React 特性。

Hook是什么

Hook 就是 JavaScript 函数。更具体的来说Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。

  • 只能在函数最外层调用 Hook。
  • 只能在 React 的函数组件中调用 Hook。
  • 在自定义的 Hook 中调用。

React 怎么知道哪个 state 对应哪个 useState?

答案是 React 靠的是 Hook 调用的顺序。
只要 Hook 的调用顺序在多次渲染之间保持一致,React 就能正确地将内部 state 和对应的 Hook 进行关联。

自定义 Hook

通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。

自定义 Hook 必须以 “use” 开头吗?必须如此。这个约定非常重要。不遵循的话,由于无法判断某个函数是否包含对其内部 Hook 的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则。

useState

用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。

  • 函数式更新

  • 惰性初始 state。

    • 如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state。
  • 跳过 state 更新。

    • React 使用 Object.is 比较算法 来比较 state。

Effect Hook

useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。

与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。

React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect,因此会使得额外操作很方便。

第二个参数,依赖列表

推荐把函数逻辑移动到你的 effect 内部。这样就能很容易的看出来你的 effect 使用了哪些 props 和 state。

当 effect 执行时,我们会创建一个闭包,并将 state 的值保存在该闭包当中。要解决这个问题,我们可以使用 setState 的函数式更新形式。它允许我们指定 state 该 如何 改变而不用引用 当前 state。

React 是如何把对 Hook 的调用和组件联系起来的?

每个组件内部都有一个「记忆单元格」列表。它们只不过是我们用来存储一些数据的 JavaScript 对象。当你用 useState() 调用一个 Hook 的时候,它会读取当前的单元格(或在首次渲染时将其初始化),然后把指针移动到下一个。这就是多个 useState() 调用会得到各自独立的本地 state 的原因。

前端工程化

从本质上讲,所有Web应用都是一种运行在网页浏览器中的软件,这些软件的图形用户界面(Graphical User Interface,简称GUI)即为前端。
前端是GUI软件。

Webpack核心概念

  • Entry:入口,执行构建的第一步将从Entry开始。
  • Module:模块,在Webpack里一切皆模块,一个模块对应着一个文件。
  • Chunk:代码块,一个Chunk由多个模块组合而成,用于代码合并与分割。
  • Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
  • Plugin:扩展插件,在构建流程中的特定时机注入扩展逻辑来改变成想要的构建结果。
  • Output:输出结果。

主要流程

Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:

  1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
  3. 确定入口:根据配置中的 entry 找出所有的入口文件;
  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  5. 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
  8. 在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。

性能优化 - 本地构建速度

  1. 使用 DllPlugin
  2. 使用 HappyPack
  3. 使用 ParallelUglifyPlugin
  4. 开启模块热替换
  5. 开启多进程

性能优化 - 产出的代码

  1. 小图片base64格式
  2. bundle加hash,命中缓存
  3. 按需加载
  4. 提取公共代码
  5. CDN 加速
  6. 开启 Scope Hoisting
  7. 使用 Tree Shaking

层叠上下文

拥有层叠上下文属性的元素会生成一个新的层叠上下文对象,每个层叠上下文对象都是一个渲染图层,渲染图层与复合图层是不同的概念,渲染图层更像是一个纯二维的概念,无论其怎么层叠覆盖最终都归依于根层叠上下文。而复合图层则完全脱离根层叠上下文,相当于开辟新的位面。

层叠上下文

让HTML元素在2D平面堆叠出3D的视觉效果,根据层叠规则将哪个元素置于视觉最近处,哪个次之,以此类推。

形成层叠上下文

  • 文档根元素 ,称为“根层叠上下文”。
  • position属性的值不为static且z-index值不为auto或0
  • opacity属性值小于1的元素
  • flex布局的元素
  • transform值不为none
  • filter值不为none
  • will-change值设定任意属性且值为非初始化值

特性

  1. 层叠上下文可以包含在其他层叠上下文中,并且一起组建一个层叠上下文的层级。
  2. 每个层叠上下文都完全独立于它的兄弟元素:当处理层叠时只考虑子元素。
  3. 每个层叠上下文都是自包含的:当一个元素的内容发生层叠后,该元素将被作为整体在父级层叠上下文中按顺序进行层叠。
  4. 没有创建自己的层叠上下文的元素默认就在父层叠上下文中。

层叠顺序

层叠等级的比较只有在当前层叠上下文元素中才有意义。不同层叠上下文中比较层叠等级是没有意义的。

  1. background/border
  2. z-index < 0
  3. block
  4. float
  5. inline/inline-block
  6. z-index: auto/0
  7. z-index > 0

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.