问题一览
- 加载页面和渲染过程
- 渲染线程和 JS 引擎线程
- 重绘和回流
- 页面生命周期
- property 和 attribute 区别
- cookie、localStorage 以及 sessionStorage
- AJAX && 跨域
加载页面和渲染过程
题目:浏览器从加载页面到渲染页面的过程。
① 加载过程
要点如下:
DNS
服务器解析域名的IP
地址- 建立
TCP
握手连接 - 向
IP
指向的服务器发送HTTP
请求 - 服务器收到、处理并返回
HTTP
请求 - 浏览器获取返回内容
② 渲染过程
要点如下:
- 根据
HTML
代码生成DOM
树 - 根据
CSS
生成CSSDOM
- 将 DOM 树和 CSSOM 整合成 RenderTree
- 根据 RenderTree 开始渲染和展示
- 遇到
script
标签,会阻塞渲染
这个过程要注意<link>
标签位置,以及script
标签位置和HTML
提供的async
defer
属性
渲染线程和 JS 引擎线程
浏览器中常见的线程有:渲染线程、JS 引擎线程、HTTP 线程等等。
例如,当我们打开一个 Ajax 请求的时候,就启动了一个 HTTP 线程。
同样地,我们可以用线程的只是解释:为什么直接操作 DOM 会变慢,性能损耗更大?因为 JS 引擎线程和渲染线程是互斥的。而直接操作 DOM 就会涉及到两个线程互斥之间的通信,所以开销更大。
除此之外,这还能解释为什么script
标签为什么会阻塞 DOM 树渲染,毕竟 JS 是可以修改 DOM 的,如果 JS 执行的时候 UI 也工作,就有可能导致不安全的渲染。
重绘和回流
重绘(repaint)和回流(reflow)会在样式节点变动时候出现,回流所需要的成本更高,回流一定会引重绘。
重绘是只一些元素更新属性,这些属性只影响外观,不影响布局。比如背景颜色、字体颜色等等。
回流是元素的尺寸、布局、可见等属性发生改变。会导致渲染树重新构造。比如窗口字体大小变化、样式表改动、元素内容(尤其是输入控件)、css 伪类激活、offsetWidth 等属性计算。
如何减少重绘回流?
- 避免逐项更改样式。一次性更改**
style
属性,或者直接定义class
**属性 - 避免直接插入
DOM
。在**documentFragment
上操作,然后再插入document
**中 - 避免循环读取
offsetWidth
等属性。循环外存取 - 避免复杂动画。利用绝对定位将其脱离文档流
- 避免
CSS
选择符层级太多。尽量平级类名,参考 scss 中的**&
**的用法 - 为频繁重绘或者回流的节点设置图层:
iframe
、video
等节点自动变为图层- 通过 3d 动画出发:
transform: translate3d(0, 0, 0)
- 提前通知浏览器:
will-change
属性
页面生命周期
onload
和DOMContentLoaded
触发的先后顺序是什么?
页面声明周期的变化,会触发document
上的readystatechange
事件,用户可以通过document.readyState
拿到当前的状态。
1 | // 初始时候的readyState |
上面的代码在 Chrome 中的输出是:
- loading:加载 document
- interactive:document 加载成功,DOM 树构建完成
- complete:图像,样式表和框架之类的子资源完成加载
所以,**DOMContentLoaded
是在onload
**前进行的。
DOMContentLoaded
事件在 DOM 树构建完毕后被触发,我们可以在这个阶段使用 js 去访问元素。async
和defer
的脚本可能还没有执行。- 图片及其他资源文件可能还在下载中。
load
事件在页面所有资源被加载完毕后触发,通常我们不会用到这个事件,因为我们不需要等那么久。beforeunload
在用户即将离开页面时触发,它返回一个字符串,浏览器会向用户展示并询问这个字符串以确定是否离开。unload
~~~~在用户已经离开时触发,我们在这个阶段仅可以做一些没有延迟的操作,由于种种限制,很少被使用。
1 | document.addEventListener("DOMContentLoaded", () => { |
property 和 attribute 区别
①property
指的是属性:DOM 节点本质是 JS 对象,因此 property 可以理解成 JS 对象上的属性。而 property 改变,就是直接改变 JS 对象的属性。
比如<p>
上有 style、className、nodeName 和 nodeType 等属性。
1 | let p = $("p"); |
②attribute
attribute 是指 HTML 的属性,改变 attribute 就是针对 HTML 属性的 set 和 get,和 JS 对象无关。
常用的 API 就是:getAttribute
和setAttribute
。常见的用法是setAttribute()
来设置元素的style
。
1 | let p = $("p"); |
cookie、localStorage 以及 sessionStorage
cookie:
- 大小限制为 4kb,主要用来保存登陆信息,一般会存储一段表示用户信息的数据。
- 生命周期上,一般是服务器设置失效时间;如果是浏览器生成,默认是关闭浏览器后失效。
- 每次会被携带在 http 头中,所以数据量过大的时候有性能问题。
localStorage:大小限制为 5MB,用于永久存储信息,也可以用于缓存 ajax 信息用于离线应用。它保存在浏览器,不参与与服务器的通信。
sessionStorage:与 localStorage 类似,不同的是信息不是永久存储,仅在当前会话下有效。关闭标签或者浏览器,都会清除。
AJAX
XMLHttpRequest
题目:不借助任何库实现XMLHttpRequest
1 | let xhr = new XMLHttpRequest(); |
Fetch API
题目:介绍和使用fetch()
淘汰了写法不舒服的XMLHttpRequest
,本身支持Promise
回调,是 ES6 下的最佳 AJAX 实践。但是浏览器兼容不是太好,但几年后,估计就只剩它了!
1 | const api = "<http://localhost:5050/search/song>"; |
注意:koabodyparser
不支持FormData
解析(换用koa-better-body
)。那么请用如下代码。
1 | const api = "<http://localhost:5050/search/song>"; |
实现跨域
题目:如何实现跨域?
目前我已知的方法有三个:
- JSONP:通过
script
标签实现,但是只能实现GET
请求 - 代理转发:Webpack 的 dev 模式,配合
proxy
选项,启动一个前端服务器,实现代理转发 - CORS:后端允许跨域资源共享,这是我最推荐的一种方法
代理转发请见《webpack4 系列教程》,CORS 请见 Koa 部分。这里实现一下 JSONP。
注意:src
的params
中callback
属性,指定的是回调函数。实例的回调函数是:handleResponse()
1 | // 定义回调函数 |