题目:浏览器从加载页面到渲染页面的过程。
① 加载过程
要点如下:
DNS
服务器解析域名的IP
地址TCP
握手连接IP
指向的服务器发送HTTP
请求HTTP
请求② 渲染过程
要点如下:
HTML
代码生成DOM
树CSS
生成CSSDOM
<script>
,会阻塞渲染这个过程要注意<link>
标签位置,以及<script>
标签位置和HTML
提供的async
defer
属性
浏览器中常见的线程有:渲染线程、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
等节点自动变为图层transform: translate3d(0, 0, 0)
will-change
属性onload
和DOMContentLoaded
触发的先后顺序是什么?
页面声明周期的变化,会触发document
上的readystatechange
事件,用户可以通过document.readyState
拿到当前的状态。
// 初始时候的readyStateconsole.log(document.readyState);// 每次改变都打印readyStatedocument.addEventListener("readystatechange", () =>console.log(document.readyState););
上面的代码在 Chrome 中的输出是:
所以,DOMContentLoaded
是在onload
前进行的。
DOMContentLoaded
事件在 DOM 树构建完毕后被触发,我们可以在这个阶段使用 js 去访问元素。async
和defer
的脚本可能还没有执行。load
事件在页面所有资源被加载完毕后触发,通常我们不会用到这个事件,因为我们不需要等那么久。beforeunload
在用户即将离开页面时触发,它返回一个字符串,浏览器会向用户展示并询问这个字符串以确定是否离开。unload
在用户已经离开时触发,我们在这个阶段仅可以做一些没有延迟的操作,由于种种限制,很少被使用。document.addEventListener("DOMContentLoaded", () => {console.log("DOMContentLoaded");});window.addEventListener("load", () => {console.log("load");});window.addEventListener("beforeunload", () => {console.log("will unload");});/*window.addEventListener("unload", () => {console.log("unload");});*/
①property
指的是属性:DOM 节点本质是 JS 对象,因此 property 可以理解成 JS 对象上的属性。而 property 改变,就是直接改变 JS 对象的属性。
比如<p>
上有 style、className、nodeName 和 nodeType 等属性。
let p = $("p");console.log(p.style.width, p.className);console.log(p.nodeName, p.nodeType);
②attribute
attribute 是指 HTML 的属性,改变 attribute 就是针对 HTML 属性的 set 和 get,和 JS 对象无关。
常用的 API 就是:getAttribute
和setAttribute
。常见的用法是setAttribute()
来设置元素的style
。
let p = $("p");p.setAttribute("data-name", "yuanxin");console.log(p.getAttribute("data-name"));
cookie:
localStorage:大小限制为 5MB,用于永久存储信息,也可以用于缓存 ajax 信息用于离线应用。它保存在浏览器,不参与与服务器的通信。
sessionStorage:与 localStorage 类似,不同的是信息不是永久存储,仅在当前会话下有效。关闭标签或者浏览器,都会清除。
题目:不借助任何库实现
XMLHttpRequest
let xhr = new XMLHttpRequest();// readyState 为 4 和 status 为 200 的时候,是正常情况// Step1: 监听状态xhr.onreadystatechange = () => {if (xhr.readyState === 4) {xhr.status === 200 && console.log(xhr.responseText);}};// xhr.open(method: [get, post], url: string, async: [true, false])// async: 默认是 true; 代表异步请求// 如果async = false, 那么 xhr.send() 会阻塞// Step2: 打开请求xhr.open("GET","http://localhost:5050/search/song?key=周杰伦&page=1&limit=10&vendor=qq");// Step3: 发送请求xhr.send();
题目:介绍和使用
fetch()
淘汰了写法不舒服的XMLHttpRequest
,本身支持Promise
回调,是 ES6 下的最佳 AJAX 实践。但是浏览器兼容不是太好,但几年后,估计就只剩它了!
const api = "http://localhost:5050/search/song";const formData = new FormData();formData.append("key", "周杰伦");formData.append("page", 1);formData.append("limit", 10);formData.append("vendor", "qq");fetch(api, {method: "POST",body: formData}).then(res => res.json()).then(json => console.log(json));
注意:koabodyparser
不支持FormData
解析(换用koa-better-body
)。那么请用如下代码。
const api = "http://localhost:5050/search/song";fetch(api, {method: "POST",body: JSON.stringify({key: "周杰伦",page: 1,limit: 10,vendor: "qq"}),headers: new Headers({"Content-Type": "application/json"})}).then(res => res.json()).then(json => console.log(json));
题目:如何实现跨域?
目前我已知的方法有三个:
<script>
标签实现,但是只能实现GET
请求proxy
选项,启动一个前端服务器,实现代理转发代理转发请见《webpack4 系列教程》,CORS 请见 Koa 部分。这里实现一下 JSONP。
注意:src
的params
中callback
属性,指定的是回调函数。实例的回调函数是:handleResponse()
// 定义回调函数const handleResponse = data => {console.log(data);};// 构造 <script> 标签let script = document.createElement("script");script.src ="https://api.douban.com/v2/book/search?q=javascript&count=1&callback=handleResponse";// 向document中添加 <script> 标签,并且发送GET请求document.body.appendChild(script);