主讲:云隐
一、 认识在浏览器运行态下的 JS
包含:
BOM、DOM、ECMAScript
(function (context, undefined) {
const _class = ['js', 'browser', 'vue'];
// 向全局中挂载
window.classArr = _class.map(item => item);
// 获取当前页面地址
const _url = location.href;
// 设置 tab 标题(一个浏览器的 tab 就是一个进程)
document.title = 'zhaowa class';
// 获取渲染节点
document.getElementById('app');
})(this);
追问:了解浏览器 JS 的执行态(分层)?
简述:
ECMAScript- 负责基础逻辑、数据处理;DOM- 对于浏览器视窗内,文本的相应操作;BOM- 对于浏览器本身区域能力的处理;
二、BOM - 浏览器对象模型
什么是浏览器对象模型
BOM:Browser Object Model(浏览器对象模型),浏览器模型提供了独立于内容的、可以与浏览器窗口进行滑动的对象结构,就是浏览器提供的 API。
其主要对象有:
window对象 ——BOM的核心,是js访问浏览器的接口,也是ES规定的Global对象;location对象:提供当前窗口中的加载的文档有关的信息和一些导航功能。既是window对象属性,也是document的对象属性window.locationdocument.location
navigation对象:获取浏览器的系统信息;screen对象:用来表示浏览器窗口外部的显示器的信息等;history对象:保存用户上网的历史信息;
1、Window
window 对象是整个浏览器对象模型的核心,其扮演着既是接口又是全局对象的角色,所有在全局作用域下声明的变量和内容最终都在变成 window 对象下的属性。比如:
var num = 123;
console.log(window.num); // 123
访问未声明的变量时,如果直接访问则会报错,而如果使用 window 进行访问,就像通过对象访问那样,会返回 undefined。
var name = oldName; // 报错
var name2 = window.oldName; // undefined
window.alert()window.confirm()window.prompt()window.open()window.onerror():用于监控错误window.setTimeout()window.setInterval()
setTimeout 和 setInterval
setTimeout 和 setInterval 他们都可以接受两个参数,第⼀个参数是⼀个回调函数,第二个参数是等待执行的时间。在等待时间结束之后,就会将回调函数放到 event loop 中进行执行。他们都返回⼀个 id,传入 clearTimeout 和 clearInterval 能够清除这次的定时操作。
var id = setTimeout(function () {
console.log('hello world');
}, 2000);
console.log(id); // 1
clearTimeout(id);
可视化工具网站:latentflip.com/loupe/
假设我们点击事件之后会触发 setInterval(func, 500),那么每隔 500ms 就会将 func 放入⼀次消息队列,如果此时主栈中有其他代码执行的话,就会等待其他代码执行之后再读取消息队列中的函数执行。但对于 setInterval,仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中,所以就会造成某个瞬间有次回调函数没有加进事件队列中去,造成丢帧。
使用
setTimeout 模拟之后的样子,每次执行完成之后再将下次的事件推入事件队列中:
使用
setTimeout 模拟 setInterval:
// 使用 setTimeout 模拟 setInterval
function _setTimeout() {
var timer = setTimeout(function () {
console.log(1);
_setTimeout();
clearTimeout(timer);
}, 1000);
}
_setTimeout();
alert,confirm,prompt 等交互相关 API
alert 会弹出⼀个警告框,而 confirm 和 prompt 则可以与用户交互,confirm 会弹出⼀个确认框,最终返回 true(用户点击确定)返回 false(用户点击取消)而 prompt 用户则可以输入⼀段文字,最终返回用户输出的结果。
这里使用了这类 API 之后,会导致页面 JS 停止执行,需要我们格外慎重。
2、Location
提供当前窗口中的加载的文档有关的信息和一些导航功能。既是 window 对象属性,也是 document 的对象属性。
空页面的时候(Chrome)
Location
例子:https://www.zhaowa.com/search?class=browser&id=2#comments
属性:
-
href:当前URL的全部; -
origin:返回⼀个URL的域名(https://www.zhaowa.com); -
protocol:返回⼀个URL协议(https:)【带冒号】; -
host:返回⼀个URL的主机名和端口(www.zhaowa.com); -
hostname:返回URL的主机名; -
port: 返回⼀个URL服务器使用的端口号; -
pathname: 返回的URL路径名(/search/); -
search:返回⼀个URL的查询部分(?class=browser&id=2); -
hash:返回⼀个URL的锚点部分(#comments);
方法:
location.assign(''):跳转到指定path,替换的是pathname;location.replace(newURL):用新的页面替换当前页面;replace()方法不会在History对象中生成一个新的记录。当使用该方法时,新的URL将覆盖History对象中的当前记录,浏览器不能返回到上一个页面了;location.replace("https://www.baidu.com/")
location.reload():重新载入当前页面;location.toString():产出当前地址字符串;
面试方向:
location本身api操作:- 提取相关信息
api间的对比,例如assign vs replace
- 路由相关:跳转、参数、操作
- 跳转:
hash和history跳转的区别,利用了什么api?hash为什么可以使浏览器不刷新?- 因为
hash只改变了#后面的内容,不会改变跟地址;
- 因为
- 参数:
- 通过
?的方式去处理,多参数的时候通过&去拼接; - 如何去读取参数?
- 如果浏览器中如果存数组、对象,
url如何去处理?(decode,encode)
- 通过
- 操作:
assign和replace怎么去做,带来的副作用是什么?- 场景题:返回的路径不对? —— 可返回(
history模式可返回),是否刷新(hash模式不会刷新)- 是不是使用了
replace,把前一次的历史记录清空了;- 如何规避:把
replace替换为assign;
- 如何规避:把
- 是不是跳转到了第三方页面,从第三方返回的时候,从外部链接返回来自己应用链接的时候,后台会动态指向一个默认的值,所以地址不对了;
- 如何规避:可以携带参数跳回,根据参数做判断,比如在路由的导航守卫中做预处理;
- 是不是使用了
- 跳转:
url处理:提取所有的参数,变成键值对的形式- 方案:1、正则;2、手写
js处理;
- 方案:1、正则;2、手写
- 理论类:
URI & URL区别URI:Uniform Resource Identifier统一资源标识符;URL:Uniform Resource Location统一资源定位符;
2、history
History 对象保存着用户上网的历史记录,从窗口被打开的那一刻算起,history 对象是用窗口的浏览历史用文档和文档状态列表的形式表示。在 HTML5 中,history 还与客户端路由息息相关。
属性:
history.state:存储当前页面的状态;history.length: 返回历史列表中的网址数;
方法:
-
history.go():加载history列表中的某个具体页面; -
history.back():加载history列表中的前⼀个URL,相当于go(-1); -
history.forward():加载history列表中的下⼀个URL,相当于go(1); -
history.pushState():替换地址栏地址,并且加入history列表,但并不会刷新页面;- 参数 1:
data - 参数 2:
title - 参数 3:
url history.pushState({ page: 1 }, "page-1", "/pushState");
- 参数 1:
-
history.replaceState():替换地址栏地址,替换当前页面在history列表中的记录,并不会刷新页面;- 参数 1:
data - 参数 2:
title - 参数 3:
url history.replaceState({ page: 2 }, "page-2", "/replaceState");
- 参数 1:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button onclick="pushState()">pushState</button>
<button onclick="replaceState()">replaceState</button>
<script>
function pushState() {
// 参数说明:data,title,url
history.pushState({ page: 1 }, 'page-1', '/pushState');
}
function replaceState() {
history.replaceState({ page: 2 }, 'page-2', '/replaceState');
}
window.onpopstate = function () {
console.log(history, history.state);
};
/*
每当处于激活状态的历史记录条目发生变化时,popstate 事件就会在对应 window 对象上触发。
如果当前处于激活状态的历史记录条目是由 history.pushState() 方法创建,或者由 history.replaceState() 方法修改过的,
则 popstate 事件对象的 state 属性包含了这个历史记录条目的 state 对象的一个拷贝。
注意:调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。
popstate 事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮(或者在 JavaScript 中调用 history.back()、history.forward()、history.go() 方法),
此外,a 标签的锚点也会触发该事件。
*/
</script>
</body>
</html>
面试方向:
- 路由方向:
history和hash的模式利弊;
3、navigator
浏览器系统信息大集合;
navigator.userAgent; // 获取当前用户的环境信息
// 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36'
面试方向:
userAgent读取信息- 浏览器兼容性
- 上报信息
- 剪切板、键盘
4、screen
表征显示区域 - 荧幕;
面试方向:判断区域大小
-
window视窗判断:- 全局入口处:
window.innerWidthwindow.innerHeight
- 文本处获取:
document.documentElement.clientWidthdocument.documentElement.clientHeightdocument.body.clientWidthdocument.body.clientHeight
- 全局入口处:
-
网页视图的
size:offsetHeight和clientHeight区别:offsetHeight = clientHeight + 滚动条 + 边框document.documentElement.offsetWidthdocument.documentElement.offsetHeightdocument.body.offsetWidthdocument.body.offsetHeight
-
动态定位:
scrollLeft / scrollTop:距离常规左 / 上滚动距离offsetLeft / offsetTop:距离常规左 / 上距离
-
el.getBoundingClientRect():方法返回元素的大小及其相对于视口的位置;el.getBoundingClientRect().topel.getBoundingClientRect().leftel.getBoundingClientRect().bottomel.getBoundingClientRect().right
-
兼容性 - 老版本
IE(IE9之前) 是会多出来2像素;
event 对象下的方法
preventDefault:取消事件的默认行为;
三、 Event 事件模型
浏览器事件模型中的过程主要分为三个阶段:捕获阶段、目标阶段、冒泡阶段。
event 对象下的属性
bubbles:表明事件是否冒泡;cancelable:表示是否可以取消事件的默认行为;currentTarget:事件当前正在处理的元素**【绑定的元素】**;- 和
this是一样的;
- 和
defaultPrevented:为true则代表已经调用了preventDefault函数;detail:事件细节;eventPhase:事件所处阶段- 1:代表捕获;
- 2:代表在事件目标;
- 3:代表冒泡;
type:事件类型(click等);
event 对象下的方法
<div id="app">
<p id="dom"></p>
</div>
// 事件的传递 - 冒泡和捕获
// 冒泡 - ms(微软)提出: p => div => body => HTML => document
// 捕获 - ns(网景)提出: document => HTML => body => div => p
el.addEventListener(event, function () {}, useCapture); // useCapture 是否捕获,默认值 false
// useCapture: false - 冒泡,默认值
// useCapture: true - 捕获
preventDefault:取消事件的默认行为;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<a href="/test" id="a">链接</a>
<script>
const $a = document.getElementById('a');
$a.addEventListener('click', function (event) {
event.preventDefault(); // 取消事件的默认行为,a 链接不会跳转了
});
</script>
</body>
</html>
stopPropagation:取消事件的进一步捕获或冒泡;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="parent">
<div id="child">child</div>
</div>
<script>
const $parent = document.getElementById('parent');
const $child = document.getElementById('child');
$parent.addEventListener('click', function (event) {
console.log('parent click');
});
$child.addEventListener('click', function (event) {
event.stopPropagation(); // 取消事件的进⼀步捕获或冒泡,此时 parent 的行为不会触发
console.log('child1 click');
});
$child.addEventListener('click', function (event) {
console.log('child1 click again');
});
/*
此时输出结果:
child1 click
child1 click again
*/
</script>
</body>
</html>
stopImmediatePropagation:取消事件的进一步捕获或冒泡,同时阻止事件处理程序调用;- 如果有多个相同类型事件的事件监听函数绑定到同一个元素,当该类型的事件触发时,它们会按照被添加的顺序执行。如果其中某个监听函数执行了
event.stopImmediatePropagation()方法,则当前元素剩下的监听函数将不会被执行;
- 如果有多个相同类型事件的事件监听函数绑定到同一个元素,当该类型的事件触发时,它们会按照被添加的顺序执行。如果其中某个监听函数执行了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="parent">
<div id="child">child</div>
</div>
<script>
const $parent = document.getElementById('parent');
const $child = document.getElementById('child');
$parent.addEventListener('click', function (event) {
console.log('parent click');
});
$child.addEventListener('click', function (event) {
event.stopImmediatePropagation(); // 后面的事件行为也取消了,例如:下面的 child2 click 就不会输出
console.log('child1 click');
});
$child.addEventListener('click', function (event) {
console.log('child2 click');
});
/*
此时输出结果:
child1 click
*/
</script>
</body>
</html>
追问:【三个方法要清楚,不能混】
- 如何阻止事件的传播
event.stopPropagation();
- 阻止默认事件 —— 例如
a标签event.preventDefault();
- 相同节点绑定多个同类事件
event.stopImmediatePropagation();
引申型面试核心: 兼容性 & 性能
- 兼容性:手写兼容性事件绑定(
IE的attachEvent和W3C的addEventListener) - 区别:
- 传参:
attachEvent对于事件名需要加上on - 执行顺序:
attachEvent- 后绑定先执行;addEventListener- 先绑定先执行; - 解绑:
detachEvent vs removeEventListener - 阻断:
event.cancelBubble = true vs event.stopPropgation() - 默认事件拦截:
event.returnValue = false vs event.preventDefault()
- 传参:
- 性能优化:使用事件代理
手写兼容性事件绑定
手写兼容性事件绑定 - attachEvent 和 addEventListener
class bindEvent {
constructor(element) {
this.element = element;
}
// 绑定
addEventListener = (type, handler) => {
if (this.element.addEventListener) {
this.element.addEventListener(type, handler, false); // 这里统一走冒泡形式,因为 IE 不支持捕获
} else if (this.element.attachEvent) {
this.element.attachEvent('on' + type, function () {
handler.call(element);
});
} else {
this.element['on' + type] = handler;
}
};
// 解绑
removeEventListener = (type, handler) => {
if (this.element.removeEventListener) {
this.element.removeEventListener(type, handler, false);
} else if (this.element.detachEvent) {
this.element.detachEvent('on' + type, function () {
handler.call(element);
});
} else {
this.element['on' + type] = null;
}
};
// 阻断
static stopPropagation(e) {
if (e.stopPropagation) {
e.stopPropagation();
} else {
e.cancelBubble = true;
}
}
// 默认拦截
static preventDefault(e) {
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
}
}
性能优化 - 事件代理
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
<div class="content"></div>
// 获取节点
var list = document.querySelector('list');
var li = list.getElementsByTagName('li');
// 硬碰硬写法
for (var i = 0; i < li.length; i++) {
li[i].addEventListener('click', function () {
// 业务逻辑...
});
}
- 缺点:
- 这种写法是对每一个节点上都绑定了
addEventListener,如果节点多的时候,要在每一个节点上绑定事件,损耗性能; - 如果是动态节点,节点的数量有可能会增加的,那么要在增加的逻辑上也要加上绑定事件;
- 这种写法是对每一个节点上都绑定了
// 代理后 - 利用事件传递(冒泡和捕获),减少了对 DOM 的事件绑定
function onClick(e) {
var e = e || window.event;
if (e.target.nodeName.toLowerCase() === 'li') {
// 业务逻辑...
}
}
// 此处利用了冒泡
list.addEventListener('click', onClick, false);
四、网络层
ajax 的原理:
// 1、实例化
const xhr = new XMLHttpRequest();
// 2、初始化建立
/*
method:请求方式 get/post
url:请求的地址
async:是否为异步请求
*/
xhr.open(method, url, async);
// 3、方法的发送请求 - send
/*
data 的处理:
get:可以不传或传入 null
post:encodeURIComponent 编码拼接
*/
xhr.send(data);
// 4、接收
xhr.onreadystatechange = () => {
/*
xhr.readyState 的状态:
0:尚未建立 open
1:已经调用 open 方法但未调用 send 方法
2:已经调用 open 方法但尚未收到返回
3:已经收到请求返回数据
4:请求已经完成
*/
/*
面试问题:状态 3 和 4 的区别?为什么要用 4 做判断?
状态 4 是已经请求完成,后台把请求结束,把 http 的状态码返回,并且标识请求成功,关闭链接,代表整个周期的完成;
状态 3 是后台把请求的数据返回回来,从此时开始到关闭整个链接之前,还是可以有其他操作的;
*/
if (xhr.readyState === 4) {
// 判断 http 状态码
// 为什么 304 也可以?
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
// callback(xhr.responseText)
}
}
};
// 超时时间
xhr.timeout = 30000;
// 超时回调
xhr.ontimeout = () => {
// 超时后的操作
};
面试方向:
TCP => HTTP/HTTPS;- 状态码
2xx 4xx 5xx | 3xx
- 浏览器缓存
- 强缓存(
Expires + cache-control) / 协商缓存(last-modified + Etag)
- 强缓存(
封装手写:
// 使用方式:
ajax({
url: 'reqUrl',
method: 'get',
async: true,
timeout: 30000,
data: {
payload: 'text'
}
})
.then(
res => {},
err => {}
)
.catch(err => {});
// 代码实现:
function ajax(options) {
const { url, method, async, data, timeout } = options;
const xhr = new XMLHttpRequest();
// 配置超时时间
if (timeout) {
xhr.timeout = timeout;
}
return new Promise((resolve, reject) => {
// 成功情况
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
// 判断 http 状态码
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
// 返回拦截器处理...
// ...
resolve(xhr.responseText);
} else {
reject();
}
}
};
// 失败情况
xhr.onerror = err => reject(err);
xhr.ontimeout = () => reject('请求超时 timeout');
// 传参处理
let _params = [];
let encodeData = '';
if (data instanceof Object) {
for (let key in data) {
// 参数编码
/*
了解一下:encodeURIComponent 和 encodeURI 区别?
*/
_params.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
}
encodeData = _params.join('&'); // name=tom&age=22
}
// method 判断连接
if (method === 'get') {
const index = url.indexOf('?');
if (index === -1) {
// 没有 ?
url += '?';
} else if (index !== url.length - 1) {
// 有 ?,并且 ? 后面有数据
// 'https://www.baidu.com/?name=tom'
url += '&';
}
/*
到这里的 url 有两种情况:
1:'https://www.baidu.com/?'
2:'https://www.baidu.com/?name=tom&'
*/
url += encodeData;
}
// 建立连接
xhr.open(method, url, async);
// 请求拦截器处理...
// ...
// 发送请求
if (method === 'get') {
xhr.send(null);
} else {
// post 请求
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
xhr.send(encodeData);
}
});
}
面试点:
content-type代表的是内容类型 => 浏览器 =>firefox chrome- 会涉及到浏览器的下载
- 文件下载兼容性问题(
firefox vs chrome)- 后台返回的数据没有指定类型:
firefox会优先把数据变成text/plain类型,当作普通文本解析;chrome会优先当作一个文件来处理,无论这个文件是html还是其他;
- 可能遇到兼容性的问题:当我们点击下载一个
word文档时,如果后台没有指定这是一个文档类型时,chrome会正常能下载成功,而firefox会把它当作普通文本在页面上直接渲染出来;
- 后台返回的数据没有指定类型:
ajax 的 ts 版本:
interface IOptions {
url: string;
type?: string;
data: any;
timeout?: number;
}
function formatUrl(json) {
let dataArr = [];
json.t = Math.random();
for (let key in json) {
dataArr.push(`${key}=${encodeURIComponent(json[key])}`);
}
return dataArr.join('&');
}
export function ajax(options: IOptions) {
return new Promise((resolve, reject) => {
if (!options.url) return;
options.type = options.type || 'GET';
options.data = options.data || {};
options.timeout = options.timeout || 10000;
let dataToUrlStr = formatUrl(options.data);
let timer;
// 1.创建
let xhr;
if ((window as any).XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
if (options.type.toUpperCase() === 'GET') {
// 2.连接
xhr.open('get', `${options.url}?${dataToUrlStr}`, true);
// 3.发送
xhr.send();
} else if (options.type.toUpperCase() === 'POST') {
// 2.连接
xhr.open('post', options.url, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
// 3.发送
xhr.send(options.data);
}
// 4.接收
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
clearTimeout(timer);
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
resolve(xhr.responseText);
} else {
reject(xhr.status);
}
}
};
if (options.timeout) {
timer = setTimeout(() => {
xhr.abort();
reject('超时');
}, options.timeout);
}
// xhr.timeout = options.timeout;
// xhr.ontimeout = () => {
// reject('超时');
// }
});
}
ES6 之后的 fetch API
在 ES6 之后,浏览器端新增了⼀个 fetch api, 他有以下的几个特点:
fetch api返回⼀个promise的结果;- 默认不带
cookie,需要使用配置credentials: "include";
fetch('https://example.com', {
credentials: 'include'
});
- 当网络故障时或请求被阻止时,才会标记为
reject。否则即使返回码是 500,也会resolve这个promise;
fetch('/ajax?foo=bar').then(function () {
console.log('请求发送成功');
});
- 需要借用
AbortController中止fetch;
const controller = new AbortController();
const signal = controller.signal;
fetch('https://slowmo.glitch.me/5000', { signal })
.then(r => r.json())
.then(response => console.log(response))
.catch(err => {
if (err.name === 'AbortError') {
console.log('Fetch was aborted');
} else {
console.log('Error', err);
}
});
// 在 2s 后中断请求,将触发 AbortError
setTimeout(() => controller.abort(), 2000);
这就是⼀个简单的请求,发送之后就会进入 resolve 状态。与普通的 ajax 请求不同的是,在服务端返回内容时,我们还需要调用⼀些方法才能拿到真正返回的结果。
response.text():返回字符串response.json():返回JSONresponse.blob():⼀般指返回文件对象response.arrayBuffer():返回一个二进制文件response.formData():返回表单格式内容
fetch('/ajax?foo=bar').then(function (response) {
response.text(); // 返回字符串
response.json(); // 返回 json
response.blob(); // ⼀般指返回文件对象
response.arrayBuffer(); // 返回一个二进制文件
response.formData(); // 返回表单格式内容
});
常见的 json 请求,我们需要再调用一次 response.json 来让 fetch API 返回的结果序列化为 json。
fetch('/ajaxPost')
.then(function (response) {
return response.json();
})
.then(function (result) {
console.log(result); // 这里是数据
});
五、浏览器原理
面试题: 从 url 输入到页面展示发生了什么?
这道题可以扩展的面很广,这里截取这道题的一部分,先限定一个前提:获取到资源 => 渲染出页面。
首先要了解的知识:
DOMCSSOM(CSS Object Model):把CSS代码解析成树形数据结构;Render Tree(渲染树):DOM + CSSOM生成树;Layout Module(布局模块):计算Render Tree每个节点具体的状态和位置;Painting(绘图):呈现到屏幕上;
回到题面上:
- 渲染流程:
Url 处理 => HTML 解析 - JS + DOM + CSSOM => render tree / js + css 执行 => layout => paintingUrl处理HTML解析,包括JS + DOM + CSSOMDOM + CSSOM生成render tree,此时js + css执行render tree阶段和js + css执行是并行发生的DOM + CSSOM可以直接合并render tree,是静态的js + css执行,是动态的
layoutpainting
- 纵向切分:
bytes(62 48 65 2C...) => characters(<html></html>) => Tokens(tag tree) => Nodes(html|head|body) => DOM | CSSOM
面试方向:
- 渲染流程
- 解析方式 => 引擎书写(
DSL) - 【重点】性能优化(下节课)