(1)line-height的概念:
(2)line-height 的赋值方式:
单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。仍然用 setTimeout
来模拟异步操作:
/** * 传入参数 n,表示这个函数执行的时间(毫秒) * 执行的结果是 n + 200,这个值将用于下一步骤 */
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
现在用 Promise 方式来实现这三个步骤的处理:
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
输出结果 result
是 step3()
的参数 700 + 200
= 900
。doIt()
顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500
毫秒,和 console.time()/console.timeEnd()
计算的结果一致。
如果用 async/await 来实现呢,会是这样:
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样
HTTPS 要比 HTTPS 多了 secure 安全性这个概念,实际上, HTTPS 并不是一个新的应用层协议,它其实就是 HTTP + TLS/SSL 协议组合而成,而安全性的保证正是 SSL/TLS 所做的工作。
SSL
安全套接层(Secure Sockets Layer)
TLS
(传输层安全,Transport Layer Security)
现在主流的版本是 TLS/1.2, 之前的 TLS1.0、TLS1.1 都被认为是不安全的,在不久的将来会被完全淘汰。
HTTPS 就是身披了一层 SSL 的 HTTP 。
那么区别有哪些呢👇
我觉得记住以下两点HTTPS主要作用就行👇
HTTPS的缺点
参考:前端进阶面试题详细解答
UDP | TCP | |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输(数据顺序和正确性),使用流量控制和拥塞控制 |
连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 只能是一对一通信 |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
适用场景 | 适用于实时应用,例如视频会议、直播 | 适用于要求可靠传输的应用,例如文件传输 |
这里你谈
promise
的时候,除了将他解决的痛点以及常用的API
之外,最好进行拓展把eventloop
带进来好好讲一下,microtask
(微任务)、macrotask
(任务) 的执行顺序,如果看过promise
源码,最好可以谈一谈 原生Promise
是如何实现的。Promise
的关键点在于callback
的两个参数,一个是resovle
,一个是reject
。还有就是Promise
的链式调用(Promise.then()
,每一个then
都是一个责任人)
Promise
是 ES6
新增的语法,解决了回调地狱的问题。Promise
看成一个状态机。初始是 pending
状态,可以通过函数 resolve
和 reject
,将状态转变为 resolved
或者 rejected
状态,状态一旦改变就不能再次变化。then
函数会返回一个 Promise
实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise
规范规定除了 pending
状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then
调用就失去意义了。 对于 then
来说,本质上可以把它看成是 flatMap
1. Promise 的基本情况
简单来说它就是一个容器,里面保存着某个未来才会结束的事件(通常是异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息
一般 Promise 在执行过程中,必然会处于以下几种状态之一。
pending
):初始状态,既没有被完成,也没有被拒绝。fulfilled
):操作成功完成。rejected
):操作失败。待定状态的
Promise
对象执行的话,最后要么会通过一个值完成,要么会通过一个原因被拒绝。当其中一种情况发生时,我们用Promise
的then
方法排列起来的相关处理程序就会被调用。因为最后Promise.prototype.then
和Promise.prototype.catch
方法返回的是一个Promise
, 所以它们可以继续被链式调用
关于 Promise 的状态流转情况,有一点值得注意的是,内部状态改变之后不可逆,你需要在编程过程中加以注意。文字描述比较晦涩,我们直接通过一张图就能很清晰地看出 Promise 内部状态流转的情况
从上图可以看出,我们最开始创建一个新的 Promise
返回给 p1
,然后开始执行,状态是 pending,当执行 resolve
之后状态就切换为 fulfilled
,执行 reject
之后就变为 rejected
的状态
2. Promise 的静态方法
all 方法
Promise.all(iterable)
Array
。promise
的结果很有用,在 ES6 中可以将多个 Promise.all
异步请求并行操作,返回结果一般有下面两种情况。// 在一个页面中需要加载获取轮播列表、获取店铺列表、获取分类列表这三个操作,页面需要同时发出请求进行页面渲染,这样用 `Promise.all` 来实现,看起来更清晰、一目了然。
//1.获取轮播数据列表
function getBannerList(){
return new Promise((resolve,reject)=>{
setTimeout(function(){
resolve('轮播数据')
},300)
})
}
//2.获取店铺列表
function getStoreList(){
return new Promise((resolve,reject)=>{
setTimeout(function(){
resolve('店铺数据')
},500)
})
}
//3.获取分类列表
function getCategoryList(){
return new Promise((resolve,reject)=>{
setTimeout(function(){
resolve('分类数据')
},700)
})
}
function initLoad(){
Promise.all([getBannerList(),getStoreList(),getCategoryList()])
.then(res=>{
console.log(res)
}).catch(err=>{
console.log(err)
})
}
initLoad()
allSettled
方法Promise.allSettled
的语法及参数跟 Promise.all
类似,其参数接受一个 Promise
的数组,返回一个新的 Promise
。唯一的不同在于,执行完之后不会失败
,也就是说当 Promise.allSettled
全部处理完成后,我们可以拿到每个 Promise
的状态,而不管其是否处理成功allSettled
实现的一段代码const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// 返回结果:
// [
// { status: 'fulfilled', value: 2 },
// { status: 'rejected', reason: -1 }
// ]
从上面代码中可以看到,
Promise.allSettled
最后返回的是一个数组,记录传进来的参数中每个 Promise 的返回值,这就是和 all 方法不太一样的地方。
any
方法Promise.any(iterable)
iterable
可迭代的对象,例如 Array
。any
方法返回一个 Promise
,只要参数 Promise
实例有一个变成 fulfilled
状态,最后 any
返回的实例就会变成 fulfilled
状态;如果所有参数 Promise
实例都变成 rejected
状态,包装实例就会变成 rejected
状态。const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const anyPromise = Promise.any([resolved, rejected]);
anyPromise.then(function (results) {
console.log(results);
});
// 返回结果:
// 2
从改造后的代码中可以看出,只要其中一个
Promise
变成fulfilled
状态,那么any
最后就返回这个p romise
。由于上面resolved
这个 Promise 已经是resolve
的了,故最后返回结果为2
race
方法Promise.race(iterable)
iterable
可迭代的对象,例如 Array
。race
方法返回一个 Promise
,只要参数的 Promise
之中有一个实例率先改变状态,则 race
方法的返回状态就跟着改变。那个率先改变的 Promise
实例的返回值,就传递给 race
方法的回调函数//请求某个图片资源
function requestImg(){
var p = new Promise(function(resolve, reject){
var img = new Image();
img.onload = function(){ resolve(img); }
img.src = 'http://www.baidu.com/img/flexible/logo/pc/result.png';
});
return p;
}
//延时函数,用于给请求计时
function timeout(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){ reject('图片请求超时'); }, 5000);
});
return p;
}
Promise.race([requestImg(), timeout()])
.then(function(results){
console.log(results);
})
.catch(function(reason){
console.log(reason);
});
// 从上面的代码中可以看出,采用 Promise 的方式来判断图片是否加载成功,也是针对 Promise.race 方法的一个比较好的业务场景
promise手写实现,面试够用版:
function myPromise(constructor){
let self=this;
self.status="pending" //定义状态改变前的初始状态
self.value=undefined;//定义状态为resolved的时候的状态
self.reason=undefined;//定义状态为rejected的时候的状态
function resolve(value){
//两个==="pending",保证了状态的改变是不可逆的
if(self.status==="pending"){
self.value=value;
self.status="resolved";
}
}
function reject(reason){
//两个==="pending",保证了状态的改变是不可逆的
if(self.status==="pending"){
self.reason=reason;
self.status="rejected";
}
}
//捕获构造异常
try{
constructor(resolve,reject);
}catch(e){
reject(e);
}
}
// 定义链式调用的then方法
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case "resolved":
onFullfilled(self.value);
break;
case "rejected":
onRejected(self.reason);
break;
default:
}
}
Generator
函数的语法糖。有更好的语义、更好的适用性、返回值是Promise
。
async => *
await => yield
// 基本用法
async function timeout (ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
async function asyncConsole (value, ms) {
await timeout(ms)
console.log(value)
}
asyncConsole('hello async and await', 1000)
下面来看一个使用 await
的代码。
var a = 0
var b = async () => {
a = a + await 10
console.log('2', a) // -> '2' 10
a = (await 10) + a
console.log('3', a) // -> '3' 20
}
b()
a++
console.log('1', a) // -> '1' 1
b
先执行,在执行到 await 10
之前变量 a
还是 0
,因为在 await
内部实现了 generators
,generators
会保留堆栈中东西,所以这时候 a = 0
被保存了下来await
是异步操作,遇到await
就会立即返回一个pending
状态的Promise
对象,暂时返回执行代码的控制权,使得函数外的代码得以继续执行,所以会先执行 console.log('1', a)
a = 10
优缺点:
async/await
的优势在于处理 then 的调用链,能够更清晰准确的写出代码,并且也能优雅地解决回调地狱问题。当然也存在一些缺点,因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。
async原理
async/await
语法糖就是使用Generator
函数+自动执行器来运作的
// 定义了一个promise,用来模拟异步请求,作用是传入参数++
function getNum(num){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num+1)
}, 1000)
})
}
//自动执行器,如果一个Generator函数没有执行完,则递归调用
function asyncFun(func){
var gen = func();
function next(data){
var result = gen.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}
next();
}
// 所需要执行的Generator函数,内部的数据在执行完成一步的promise之后,再调用下一步
var func = function* (){
var f1 = yield getNum(1);
var f2 = yield getNum(f1);
console.log(f2) ;
};
asyncFun(func);
promise
是否完成,如果已经完成,将结果传入下一个函数,继续重复此步骤next()
方法返回值的 value
属性为一个 Promise
对象,所以我们为其添加 then
方法, 在 then
方法里面接着运行 next
方法挪移遍历器指针,直到 Generator
函数运行完成单进程浏览器时代
单进程浏览器是指浏览器的所有功能模块都是运行在同一个进程里,这些模块包含了网络、插件、JavaScript运行环境、渲染引擎和页面等。其实早在2007年之前,市面上浏览器都是单进程的
不稳定,不流畅,而且不安全
多进程浏览器时代
进程是相互隔离的
,所以当一个页面或者插件崩溃时,影响到的仅仅是当前的页面进程或者插件进程,并不会影响到浏览器和其他页面,这就完美地解决了页面或者插件的崩溃会导致整个浏览器崩溃,也就是不稳定的问题最新的Chrome浏览器包括:
1个浏览器(Browser)主进程
、1个 GPU 进程
、1个网络(NetWork)进程
、多个渲染进程
和多个插件进程
HTML、CSS
和 JavaScript 转换为用户可以与之交互的网页,排版引擎Blink和JavaScript引擎V8都是运行在该进程中,默认情况下,Chrome会为每个Tab标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。状态码的类别:
类别 | 原因 | 描述 |
---|---|---|
1xx | Informational(信息性状态码) | 接受的请求正在处理 |
2xx | Success(成功状态码) | 请求正常处理完毕 |
3xx | Redirection(重定向状态码) | 需要进行附加操作一完成请求 |
4xx | Client Error (客户端错误状态码) | 服务器无法处理请求 |
5xx | Server Error(服务器错误状态码) | 服务器处理请求出错 |
状态码2XX表示请求被正常处理了。
200 OK表示客户端发来的请求被服务器端正常处理了。
该状态码表示客户端发送的请求已经在服务器端正常处理了,但是没有返回的内容,响应报文中不包含实体的主体部分。一般在只需要从客户端往服务器端发送信息,而服务器端不需要往客户端发送内容时使用。
该状态码表示客户端进行了范围请求,而服务器端执行了这部分的 GET 请求。响应报文中包含由 Content-Range 指定范围的实体内容。
3XX 响应结果表明浏览器需要执行某些特殊的处理以正确处理请求。
永久重定向。 该状态码表示请求的资源已经被分配了新的 URI,以后应使用资源指定的 URI。新的 URI 会在 HTTP 响应头中的 Location 首部字段指定。若用户已经把原来的URI保存为书签,此时会按照 Location 中新的URI重新保存该书签。同时,搜索引擎在抓取新内容的同时也将旧的网址替换为重定向之后的网址。
使用场景:
临时重定向。 该状态码表示请求的资源被分配到了新的 URI,希望用户(本次)能使用新的 URI 访问资源。和 301 Moved Permanently 状态码相似,但是 302 代表的资源不是被永久重定向,只是临时性质的。也就是说已移动的资源对应的 URI 将来还有可能发生改变。若用户把 URI 保存成书签,但不会像 301 状态码出现时那样去更新书签,而是仍旧保留返回 302 状态码的页面对应的 URI。同时,搜索引擎会抓取新的内容而保留旧的网址。因为服务器返回302代码,搜索引擎认为新的网址只是暂时的。
使用场景:
该状态码表示由于请求对应的资源存在着另一个 URI,应使用 GET 方法定向获取请求的资源。
303 状态码和 302 Found 状态码有着相似的功能,但是 303 状态码明确表示客户端应当采用 GET 方法获取资源。
303 状态码通常作为 PUT 或 POST 操作的返回结果,它表示重定向链接指向的不是新上传的资源,而是另外一个页面,比如消息确认页面或上传进度页面。而请求重定向页面的方法要总是使用 GET。
注意:
浏览器缓存相关。 该状态码表示客户端发送附带条件的请求时,服务器端允许请求访问资源,但未满足条件的情况。304 状态码返回时,不包含任何响应的主体部分。304 虽然被划分在 3XX 类别中,但是和重定向没有关系。
带条件的请求(Http 条件请求):使用 Get方法 请求,请求报文中包含(if-match
、if-none-match
、if-modified-since
、if-unmodified-since
、if-range
)中任意首部。
状态码304并不是一种错误,而是告诉客户端有缓存,直接使用缓存中的数据。返回页面的只有头部信息,是没有内容部分的,这样在一定程度上提高了网页的性能。
307表示临时重定向。 该状态码与 302 Found 有着相同含义,尽管 302 标准禁止 POST 变成 GET,但是实际使用时还是这样做了。
307 会遵守浏览器标准,不会从 POST 变成 GET。但是对于处理请求的行为时,不同浏览器还是会出现不同的情况。规范要求浏览器继续向 Location 的地址 POST 内容。规范要求浏览器继续向 Location 的地址 POST 内容。
4XX 的响应结果表明客户端是发生错误的原因所在。
该状态码表示请求报文中存在语法错误。当错误发生时,需修改请求的内容后再次发送请求。另外,浏览器会像 200 OK 一样对待该状态码。
该状态码表示发送的请求需要有通过 HTTP 认证(BASIC 认证、DIGEST 认证)的认证信息。若之前已进行过一次请求,则表示用户认证失败
返回含有 401 的响应必须包含一个适用于被请求资源的 WWW-Authenticate 首部用以质询(challenge)用户信息。当浏览器初次接收到 401 响应,会弹出认证用的对话窗口。
以下情况会出现401:
该状态码表明请求资源的访问被服务器拒绝了,服务器端没有必要给出详细理由,但是可以在响应报文实体的主体中进行说明。进入该状态后,不能再继续进行验证。该访问是永久禁止的,并且与应用逻辑密切相关。
IIS 定义了许多不同的 403 错误,它们指明更为具体的错误原因:
该状态码表明服务器上无法找到请求的资源。除此之外,也可以在服务器端拒绝请求且不想说明理由时使用。
以下情况会出现404:
该状态码表示客户端请求的方法虽然能被服务器识别,但是服务器禁止使用该方法。GET 和 HEAD 方法,服务器应该总是允许客户端进行访问。客户端可以通过 OPTIONS 方法(预检)来查看服务器允许的访问方法, 如下
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
5XX 的响应结果表明服务器本身发生错误.
该状态码表明服务器端在执行请求时发生了错误。也有可能是 Web 应用存在的 bug 或某些临时的故障。
该状态码表明扮演网关或代理角色的服务器,从上游服务器中接收到的响应是无效的。注意,502 错误通常不是客户端能够修复的,而是需要由途经的 Web 服务器或者代理服务器对其进行修复。以下情况会出现502:
该状态码表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。如果事先得知解除以上状况需要的时间,最好写入 RetryAfter 首部字段再返回给客户端。
使用场景:
该状态码表示网关或者代理的服务器无法在规定的时间内获得想要的响应。他是HTTP 1.1中新加入的。
使用场景:代码执行时间超时,或者发生了死循环。
(1)2XX 成功
(2)3XX 重定向
(3)4XX 客户端错误
(4)5XX 服务器错误
由于TCP的下层网络(网络层)可能出现丢失、重复或失序的情况,TCP协议提供可靠数据传输服务。为保证数据传输的正确性,TCP会重传其认为已丢失(包括报文中的比特错误)的包。TCP使用两套独立的机制来完成重传,一是基于时间,二是基于确认信息。
TCP在发送一个数据之后,就开启一个定时器,若是在这个时间内没有收到发送数据的ACK确认报文,则对该报文进行重传,在达到一定次数还没有成功时放弃并发送一个复位信号。
function Person(name) {
this.name = name
}
var p2 = new Person('king');
console.log(p2.__proto__) //Person.prototype
console.log(p2.__proto__.__proto__) //Object.prototype
console.log(p2.__proto__.__proto__.__proto__) // null
console.log(p2.__proto__.__proto__.__proto__.__proto__)//null后面没有了,报错
console.log(p2.__proto__.__proto__.__proto__.__proto__.__proto__)//null后面没有了,报错
console.log(p2.constructor)//Person
console.log(p2.prototype)//undefined p2是实例,没有prototype属性
console.log(Person.constructor)//Function 一个空函数
console.log(Person.prototype)//打印出Person.prototype这个对象里所有的方法和属性
console.log(Person.prototype.constructor)//Person
console.log(Person.prototype.__proto__)// Object.prototype
console.log(Person.__proto__) //Function.prototype
console.log(Function.prototype.__proto__)//Object.prototype
console.log(Function.__proto__)//Function.prototype
console.log(Object.__proto__)//Function.prototype
console.log(Object.prototype.__proto__)//null
这道义题目考察原型、原型链的基础,记住就可以了。
题目描述:实现一个快排
实现代码如下:
function quickSort(arr) {
if (arr.length < 2) {
return arr;
}
const cur = arr[arr.length - 1];
const left = arr.filter((v, i) => v <= cur && i !== arr.length - 1);
const right = arr.filter((v) => v > cur);
return [...quickSort(left), cur, ...quickSort(right)];
}
// console.log(quickSort([3, 6, 2, 4, 1]));
它是一种异步通信的方法,通过直接由 js 脚本向服务器发起 http 通信,然后根据服务器返回的数据,更新网页的相应部分,而不用刷新整个页面的一种方法。
面试手写(原生):
//1:创建Ajax对象
var xhr = window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');// 兼容IE6及以下版本
//2:配置 Ajax请求地址
xhr.open('get','index.xml',true);
//3:发送请求
xhr.send(null); // 严谨写法
//4:监听请求,接受响应
xhr.onreadysatechange=function(){
if(xhr.readySate==4&&xhr.status==200 || xhr.status==304 )
console.log(xhr.responsetXML)
}
jQuery写法
$.ajax({
type:'post',
url:'',
async:ture,//async 异步 sync 同步
data:data,//针对post请求
dataType:'jsonp',
success:function (msg) {
},
error:function (error) {
}
})
promise 封装实现:
// promise 封装实现:
function getJSON(url) {
// 创建一个 promise 对象
let promise = new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
// 新建一个 http 请求
xhr.open("GET", url, true);
// 设置状态的监听函数
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 当请求成功或失败时,改变 promise 的状态
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
// 设置错误监听函数
xhr.onerror = function() {
reject(new Error(this.statusText));
};
// 设置响应的数据类型
xhr.responseType = "json";
// 设置请求头信息
xhr.setRequestHeader("Accept", "application/json");
// 发送 http 请求
xhr.send(null);
});
return promise;
}
渐进式网络应用(PWA)
是谷歌在2015年底提出的概念。基本上算是web应用程序,但在外观和感觉上与原生app
类似。支持PWA
的网站可以提供脱机工作、推送通知和设备硬件访问等功能。
Service Worker
是浏览器在后台独立于网页运行的脚本,它打开了通向不需要网页或用户交互的功能的大门。 现在,它们已包括如推送通知和后台同步等功能。 将来,Service Worker
将会支持如定期同步或地理围栏等其他功能。 本教程讨论的核心功能是拦截和处理网络请求,包括通过程序来管理缓存中的响应。
描述:使用setTimeout
模拟实现setInterval
的功能。
实现:
const mySetInterval(fn, time) {
let timer = null;
const interval = () => {
timer = setTimeout(() => {
fn(); // time 时间之后会执行真正的函数fn
interval(); // 同时再次调用interval本身
}, time)
}
interval(); // 开始执行
// 返回用于关闭定时器的函数
return () => clearTimeout(timer);
}
// 测试
const cancel = mySetInterval(() => console.log(1), 400);
setTimeout(() => {
cancel();
}, 1000);
// 打印两次1
1. 浏览器如何渲染网页
概述:浏览器渲染一共有五步
HTML
并构建 DOM
树。CSS
构建 CSSOM
树。DOM
与 CSSOM
合并成一个渲染树。GPU
绘制,合成图层,显示在屏幕上第四步和第五步是最耗时的部分,这两步合起来,就是我们通常所说的渲染
具体如下图过程如下图所示
渲染
重新渲染需要重复之前的第四步(重新生成布局)+第五步(重新绘制)或者只有第五个步(重新绘制)
CSSOM
树时,会阻塞渲染,直至 CSSOM
树构建完成。并且构建 CSSOM
树是一个十分消耗性能的过程,所以应该尽量保证层级扁平,减少过度层叠,越是具体的 CSS
选择器,执行速度越慢HTML
解析到 script
标签时,会暂停构建 DOM
,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS
文件。并且CSS
也会影响 JS
的执行,只有当解析完样式表才会执行 JS
,所以也可以认为这种情况下,CSS
也会暂停构建 DOM
2. 浏览器渲染五个阶段
2.1 第一步:解析HTML标签,构建DOM树
在这个阶段,引擎开始解析
html
,解析出来的结果会成为一棵dom
树dom
的目的至少有2
个
getElementById
等等)当解析器到达script标签的时候,发生下面四件事情
html
解析器停止解析,js
引擎,执行js
代码html
解析器的控制权由此可以得到第一个结论1
<script>
标签是阻塞解析的,将脚本放在网页尾部会加速代码渲染。defer
和async
属性也能有助于加载外部脚本。defer
使得脚本会在dom
完整构建之后执行;async
标签使得脚本只有在完全available
才执行,并且是以非阻塞的方式进行的2.2 第二步:解析CSS标签,构建CSSOM树
html
解析器碰到脚本后会做的事情,接下来我们看下html
解析器碰到样式表会发生的情况js
会阻塞解析,因为它会修改文档(document
)。css
不会修改文档的结构,如果这样的话,似乎看起来css
样式不会阻塞浏览器html
解析。但是事实上 css
样式表是阻塞的。阻塞是指当cssom
树建立好之后才会进行下一步的解析渲染通过以下手段可以减轻cssom带来的影响
script
脚本放在页面底部css
样式表media type
和media query
区分,这样有助于我们将css
资源标记成非阻塞渲染的资源。2.3 第三步:把DOM和CSSOM组合成渲染树(render tree)
2.4 第四步:在渲染树的基础上进行布局,计算每个节点的几何结构
布局(
layout
):定位坐标和大小,是否换行,各种position
,overflow
,z-index
属性
2.5 调用 GPU 绘制,合成图层,显示在屏幕上
将渲染树的各个节点绘制到屏幕上,这一步被称为绘制
painting
3. 渲染优化相关
3.1 Load 和 DOMContentLoaded 区别
Load
事件触发代表页面中的 DOM
,CSS
,JS
,图片已经全部加载完毕。DOMContentLoaded
事件触发代表初始的 HTML
被完全加载和解析,不需要等待 CSS
,JS
,图片加载3.2 图层
一般来说,可以把普通文档流看成一个图层。特定的属性可以生成一个新的图层。不同的图层渲染互不影响,所以对于某些频繁需要渲染的建议单独生成一个新图层,提高性能。但也不能生成过多的图层,会引起反作用。
通过以下几个常用属性可以生成新图层
3D
变换:translate3d
、translateZ
will-change
video
、iframe
标签opacity
动画转换position: fixed
3.3 重绘(Repaint)和回流(Reflow)
重绘和回流是渲染步骤中的一小节,但是这两个步骤对于性能影响很大
color
就叫称为重绘回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流
以下几个动作可能会导致性能问题
window
大小很多人不知道的是,重绘和回流其实和 Event loop 有关
Event loop
执行完Microtasks
后,会判断 document
是否需要更新。因为浏览器是 60Hz
的刷新率,每 16ms
才会更新一次。resize
或者 scroll
,有的话会去触发事件,所以 resize
和 scroll
事件也是至少 16ms
才会触发一次,并且自带节流功能。media query
requestAnimationFrame
回调IntersectionObserver
回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好requestIdleCallback
回调常见的引起重绘的属性
color
border-style
visibility
background
text-decoration
background-image
background-position
background-repeat
outline-color
outline
outline-style
border-radius
outline-width
box-shadow
background-size
3.4 常见引起回流属性和方法
任何会改变元素几何信息(元素的位置和尺寸大小)的操作,都会触发重排,下面列一些栗子
DOM
元素;input
框中输入文字resize
事件发生时offsetWidth
和 offsetHeight
属性style
属性的值回流影响的范围
由于浏览器渲染界面是基于流失布局模型的,所以触发重排时会对周围DOM重新排列,影响的范围有两种
html
开始对整个渲染树进行重新布局。全局范围回流
<body>
<div class="hello">
<h4>hello</h4>
<p><strong>Name:</strong>BDing</p>
<h5>male</h5>
<ol>
<li>coding</li>
<li>loving</li>
</ol>
</div>
</body>
当
p
节点上发生reflow
时,hello
和body
也会重新渲染,甚至h5
和ol
都会收到影响
局部范围回流
用局部布局来解释这种现象:把一个
dom
的宽高之类的几何信息定死,然后在dom
内部触发重排,就只会重新渲染该dom
内部的元素,而不会影响到外界
3.5 减少重绘和回流
使用
translate
替代top
<div class="test"></div>
<style>
.test {
position: absolute;
top: 10px;
width: 100px;
height: 100px;
background: red;
}
</style>
<script>
setTimeout(() => {
// 引起回流
document.querySelector('.test').style.top = '100px'
}, 1000)
</script>
visibility
替换 display: none
,因为前者只会引起重绘,后者会引发回流(改变了布局)DOM
离线后修改,比如:先把 DOM
给 display:none
(有一次 Reflow)
,然后你修改100
次,然后再把它显示出来DOM
结点的属性值放在一个循环里当成循环里的变量for(let i = 0; i < 1000; i++) {
// 获取 offsetTop 会导致回流,因为需要去获取正确的值
console.log(document.querySelector('.test').style.offsetTop)
}
table
布局,可能很小的一个小改动会造成整个 table
的重新布局requestAnimationFrame
CSS
选择符从右往左匹配查找,避免 DOM
深度过深video
标签,浏览器会自动将该节点变为图层。涉及面试题:事件的触发过程是怎么样的?知道什么是事件代理嘛?
1. 简介
事件流是一个事件沿着特定数据结构传播的过程。冒泡和捕获是事件流在
DOM
中两种不同的传播方法
事件流有三个阶段
事件捕获
事件捕获(
event capturing
):通俗的理解就是,当鼠标点击或者触发dom
事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件
事件冒泡
事件冒泡(dubbed bubbling):与事件捕获恰恰相反,事件冒泡顺序是由内到外进行事件传播,直到根节点
无论是事件捕获还是事件冒泡,它们都有一个共同的行为,就是事件传播
2. 捕获和冒泡
<div id="div1">
<div id="div2"></div>
</div>
<script>
let div1 = document.getElementById('div1');
let div2 = document.getElementById('div2');
div1.onClick = function(){
alert('1')
}
div2.onClick = function(){
alert('2');
}
</script>
当点击
div2
时,会弹出两个弹出框。在ie8/9/10
、chrome
浏览器,会先弹出”2”再弹出“1”,这就是事件冒泡:事件从最底层的节点向上冒泡传播。事件捕获则跟事件冒泡相反W3C的标准是先捕获再冒泡,
addEventListener
的第三个参数决定把事件注册在捕获(true
)还是冒泡(false
)
3. 事件对象
4. 事件流阻止
在一些情况下需要阻止事件流的传播,阻止默认动作的发生
event.preventDefault()
:取消事件对象的默认动作以及继续传播。event.stopPropagation()/ event.cancelBubble = true
:阻止事件冒泡。事件的阻止在不同浏览器有不同处理
IE
下使用 event.returnValue= false
,IE
下则使用 event.preventDefault()
进行阻止preventDefault与stopPropagation的区别
preventDefault
告诉浏览器不用执行与事件相关联的默认动作(如表单提交)stopPropagation
是停止事件继续冒泡,但是对IE9以下的浏览器无效5. 事件注册
addEventListener
注册事件,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值 useCapture
参数来说,该参数默认值为 false
。useCapture
决定了注册的事件是捕获事件还是冒泡事件stopPropagation
来阻止事件的进一步传播。通常我们认为 stopPropagation
是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。stopImmediatePropagation
同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件node.addEventListener('click',(event) =>{
event.stopImmediatePropagation()
console.log('冒泡')
},false);
// 点击 node 只会执行上面的函数,该函数不会执行
node.addEventListener('click',(event) => {
console.log('捕获 ')
},true)
6. 事件委托
js
中性能优化的其中一个主要思想是减少dom
操作。假设有
100
个li
,每个li
有相同的点击事件。如果为每个Li
都添加事件,则会造成dom
访问次数过多,引起浏览器重绘与重排的次数过多,性能则会降低。 使用事件委托则可以解决这样的问题
原理
实现事件委托是利用了事件的冒泡原理实现的。当我们为最外层的节点添加点击事件,那么里面的
ul
、li
、a
的点击事件都会冒泡到最外层节点上,委托它代为执行事件
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
window.onload = function(){
var ulEle = document.getElementById('ul');
ul.onclick = function(ev){
//兼容IE
ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase() == 'li'){
alert( target.innerHTML);
}
}
}
</script>
UDP在传输数据之前不需要先建立连接,远地主机的运输层在接收到UDP报文后,不需要确认,提供不可靠交付。总结就以下四点:
XSS 攻击指的是跨站脚本攻击,是一种代码注入攻击。攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie 等。
XSS 的本质是因为网站没有对恶意代码进行过滤,与正常的代码混合在一起了,浏览器没有办法分辨哪些脚本是可信的,从而导致了恶意代码的执行。
攻击者可以通过这种攻击方式可以进行以下操作:
XSS 可以分为存储型、反射型和 DOM 型:
1)存储型 XSS 的攻击步骤:
这种攻击常⻅于带有⽤户保存数据的⽹站功能,如论坛发帖、商品评论、⽤户私信等。
2)反射型 XSS 的攻击步骤:
反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库⾥,反射型 XSS 的恶意代码存在 URL ⾥。
反射型 XSS 漏洞常⻅于通过 URL 传递参数的功能,如⽹站搜索、跳转等。 由于需要⽤户主动打开恶意的 URL 才能⽣效,攻击者往往会结合多种⼿段诱导⽤户点击。
3)DOM 型 XSS 的攻击步骤:
DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执⾏恶意代码由浏览器端完成,属于前端JavaScript ⾃身的安全漏洞,⽽其他两种 XSS 都属于服务端的安全漏洞。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。