https://juejin.im/post/6844904146168987661#heading-65
三栏布局
题目:假设高度已知,请写出三栏布局,其中左栏、右栏宽度各为 300px,中间自适应。
解答:可以有很多种布局方式,这里列出五种:float布局,absolute布局,flex布局,table布局,grid布局,代码如下:
1 | <!DOCTYPE html> |
每种布局的优缺点
- float 布局
优点: 比较简单,兼容性也比较好。只要清除浮动做的好,是没有什么问题的缺点:浮动元素是脱离文档流,要做清除浮动,这个处理不好的话,会带来很多问题,比如高度塌陷等。 - 绝对布局
优点:很快捷,设置很方便,而且也不容易出问题缺点:绝对定位是脱离文档流的,意味着下面的所有子元素也会脱离文档流,这就导致了这种方法的有效性和可使用性是比较差的。 - flex 布局
优点:简单快捷缺点:不支持 IE8 及以下 - table布局
优点:实现简单,代码少缺点:当其中一个单元格高度超出的时候,两侧的单元格也是会跟着一起变高的,而有时候这种效果不是我们想要的。 - grid布局
跟 flex 相似。
水平垂直居中
- absolute + 负margin
这种方式比较好理解,兼容性也很好,缺点是需要知道子元素的宽高
1 | <div class="out"> |
- absolute + auto margin
这种方法兼容性也很好,缺点是需要知道子元素的宽高
1 | <style type="text/css"> |
- absolute + calc
这种方法的兼容性依赖于 calc,且也需要知道宽高
1 | <style type="text/css"> |
- absolute + transform
兼容性依赖 translate,不需要知道子元素宽高
1 | <style type="text/css"> |
table
css新增的table属性,可以让我们把普通元素,变为table元素的显示效果,通过这个特性也可以实现水平垂直居中。
这种方法兼容性也不错。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<style type="text/css">
.out{
display: table-cell;
width: 300px;
height: 300px;
text-align: center;
vertical-align: middle;
background: red;
}
.inner{
display: inline-block;
background: yellow;
width: 100px;
height: 100px;
}
</style>flex
flex 实现起来比较简单,三行代码即可搞定。可通过父元素指定子元素的对齐方式,也可通过 子元素自己指定自己的对齐方式来实现。第二种方式见 grid 布局。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<style type="text/css">
.out{
display: flex;
justify-content: center;
align-items: center;
width: 300px;
height: 300px;
background: red;
}
.inner{
background: yellow;
width: 100px;
height: 100px;
}
</style>grid
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35//方法一:父元素指定子元素的对齐方式
<style type="text/css">
.out{
display: grid;
align-content: center;
justify-content: center;
width: 300px;
height: 300px;
background: red;
}
.inner{
background: yellow;
width: 100px;
height: 100px;
}
</style>
//方法二:子元素自己指定自己的对齐方式
<style type="text/css">
.out{
display: grid;
width: 300px;
height: 300px;
background: red;
}
.inner{
background: yellow;
width: 100px;
height: 100px;
align-self: center;
justify-self: center;
}
</style>
页面布局小结:
语义化掌握到位
页面布局理解深刻
CSS基础知识扎实
思维灵活且积极上进
代码书写规范
CSS盒模型
CSS盒模型是前端的基石,这个问题由浅入深,由易到难,可以依次问出下面几个问题
- 基本概念:标准模型 + IE模型
- 标准模型 和 IE模型的区别
- CSS如何设置这两种模型
- JS如何设置和获取盒模型对应的宽和高
- 实例题(根据盒模型解释边距重叠)
- BFC(边距重叠解决方案)
1. 基本概念
所有HTML元素可以看作盒子,在CSS中,”box model”这一术语是用来设计和布局时使用。CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括:边距,边框,填充,和实际内容。盒模型允许我们在其它元素和周围元素边框之间的空间放置元素。下面的图片说明了盒子模型(Box Model):
margin->border->padding->content
2. 标准模型与IE模型的区别
标准模型与 IE 模型的区别在于宽高的计算方式不同。
标准模型计算元素的宽高只算 content 的宽高,
IE模型是 content + padding + border 的总尺寸。
假如 content 宽高是 100100px,padding 为 10px,border为 10px,margin为10px,那么在标准模型下,这个元素的宽为 100px,高为 100px。
IE模型下,宽为 100px + 210px(左右padding) + 210px(左右border) = 140px;高为 100px + 210px(上下padding) + 2*10px(上下border) = 140px;
3. 如何设置这两种模型
1 | //设置标准模型 |
box-sizing 的默认值是 content-box,即默认标准模型。
4. JS如何设置盒模型的宽和高
1 | //只能获取内联样式设置的宽高 |
5. BFC
Block Formatting Context(块级格式化上下文)。
在解释什么是BFC之前,我们需要先知道Box、Formatting Context的概念。
Box:css布局的基本单位
Box 是 CSS 布局的对象和基本单位, 直观点来说,就是一个页面是由很多个 Box 组成的。元素的类型和 display 属性,决定了这个 Box 的类型。 不同类型的 Box, 会参与不同的 Formatting Context(一个决定如何渲染文档的容器),因此Box内的元素会以不同的方式渲染。让我们看看有哪些盒子:
- block-level box: display 属性为 block, list-item, table 的元素,会生成 block-level box。并且参与 block fomatting context;
- inline-level box: display 属性为 inline, inline-block, inline-table 的元素,会生成 inline-level box。并且参与 inline formatting context;
- run-in box: css3 中才有, 这儿先不讲了。
Formatting Context
Formatting context 是 W3C CSS2.1 规范中的一个概念。它是页面中的一块渲染区域,并且有一套渲染规则,它决定了其子元素将如何定位,以及和其他元素的关系和相互作用。最常见的 Formatting context 有 Block fomatting context (简称BFC)和 Inline formatting context (简称IFC)。
BFC的布局规则
- 内部的Box会在垂直方向,一个接一个地放置。
- Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠。
- 每个盒子(块盒与行盒)的margin box的左边,与包含块border box的左边相接触(对于从左往*右的格式化,否则相反)。即使存在浮动也是如此。
- BFC的区域不会与float box重叠。
- BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
- 计算BFC的高度时,浮动元素也参与计算。
如何创建BFC
- float的值不是none。
- position的值不是static或者relative。
- overflow的值不是visible。
- display的值是inline-block、table-cell、flex、table-caption或者inline-flex。
BFC的作用
利用BFC避免margin重叠
应两栏布局自适
清除浮动
DOM事件
1. 事件级别
三个事件级别,注意没有 DOM1,因为 DOM1 标准制定的时候没有涉及 DOM事件。DOM3 比 DOM2 只是增加了一些事件类型。
DOM0:element.onclick = function(){}
DOM2:element.addEventListener(‘click’, function(){}, false)
DOM3:element.addEventListener(‘keyup’, function(){}, false)
2. 事件模型和事件流
DOM事件模型包括捕获和冒泡。事件流即用户与界面交互的过程中,事件的流向过程。
3. DOM事件捕获的具体流程
捕获的流程为:window -> document -> html -> body -> … -> 目标元素。
冒泡的流程为:目标元素 -> … -> body -> html -> document -> window。
4. Event 对象常见应用
event. preventDefault()
取消事件的默认动作。
event.stopPropagation()
阻止事件冒泡。
event.stopImmediatePropagation()
阻止剩下的事件处理程序被执行。如果一个元素上绑定了三个事件,在其中一个事件上调用了这个方法,那其他 的两个事件将不会被执行。
HTTP协议
1. http协议的主要特点
简单快速、灵活、无连接、无状态HTTP三点注意事项:
- HTTP是无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
- HTTP是媒体独立的:这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。
- HTTP是无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
2. 请求报文
一个HTTP请求报文由请求行(request line)、请求头(header)、空行和请求数据4个部分组成,下图给出了请求报文的一般格式。
- 请求行:
包括请求方法字段、URL字段和HTTP协议版本,如:GET /index.html HTTP/1.1。
- 请求头
请求头部由关键字/值对组成,每行一对,关键字和值用英文冒号“:”分隔。请求头部通知服务器有关于客户端请求的信息,典型的请求头有:
- User-Agent:产生请求的浏览器类型。
- Accept:客户端可识别的内容类型列表。
- Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机。
- Content-Type:请求体的MIME类型 (用于POST和PUT请求中)。如:Content-Type: application/x-www-form-urlencoded
- 空行
最后一个请求头之后是一个空行,发送回车符和换行符,通知服务器以下不再有请求头。
- 请求数据
请求数据不在GET方法中使用,而是在POST方法中使用。POST方法适用于需要客户填写表单的场合。与请求数据相关的最常使用的请求头是Content-Type和Content-Length。
3. 响应报文
包括:状态行、响应头、空行、响应正文。
4. HTTP 状态码
HTTP状态码的英文为HTTP Status Code。状态代码由三位数字组成,第一个数字定义了响应的类别,且有五种可能取值。
1xx:指示信息–表示请求已接收,继续处理。
2xx:成功–表示请求已被成功接收、理解、接受。
3xx:重定向–要完成请求必须进行更进一步的操作。
4xx:客户端错误–请求有语法错误或请求无法实现。
5xx:服务器端错误–服务器未能实现合法的请求。
常见状态代码、状态描述的说明如下。
200 OK:客户端请求成功。
400 Bad Request:客户端请求有语法错误,不能被服务器所理解。
401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用。
403 Forbidden:服务器收到请求,但是拒绝提供服务。
404 Not Found:请求资源不存在,举个例子:输入了错误的URL。
500 Internal Server Error:服务器发生不可预期的错误。
503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常,举个例子:HTTP/1.1 200 OK(CRLF)。
5. 浏览器输入 url 之后发生了什么
https://www.cnblogs.com/yuanzhiguo/p/8119470.html
原型链
1 创建对象的几种方法
1 | <!DOCTYPE html> |
2 原型、构造函数、实例、原型链
构造函数.prototype.constructor === 构造函数
M.prototype.constructor === M 的结果为 true
构造函数.prototype === 实例对象.proto
M.prototype === obj3._ proto_ 的结果为 true
3 instanceof 的原理
instanceof 用于判断一个引用类型是否属于某构造函数;还可以在继承关系中用来判断一个实例是否属于它的父类型。
instanceof 的原理是判断实例对象的 __proto__
是否与构造函数的 prototype
指向同一个引用。
1 | //M-构造函数,obj-实例对象 |
4 instanceof 和 typeof 的区别
typeof 对于基本数据类型(null, undefined, string, number, boolean, symbol),除了 null 都会返回正确的类型。null 会返回 object。
typeof 对于对象类型,除了函数会返回 function,其他的都返回 object。如果我们想获得一个变量的正确类型,可以通过 Object.prototype.toString.call(xx)
。这样我们就可以获得类似 [object Type]
的字符串。
判断是否等于 undefined 的方法:
1 | let a |
5 new运算符
1 | <!DOCTYPE html> |
如何理解 [].shift.call(arguments)
因为 shift 内部实现是使用的this代表调用对象。那么当[].shift.call() 传入 arguments对象的时候,通过 call函数改变原来 shift 方法的 this 指向, 使其指向 arguments,并对 arguments 进行复制操作,而后返回一个新数组。至此便是完成了 arguments 类数组转为数组的目的!其实这可以理解为,让类数组调用数组的方法!
面向对象
1 类的声明和实例化
声明类有两种方法:
1 | function Animal(name){ |
类的实例化只有一种方式
1 | var a1 = new Animal('shape'); |
2 继承
继承有多种实现方式:
- 借助构造函数实现继承
Parent1 是父类,Child1 是子类。通过 Parent1.call(this, name) 改变了 this 指向,使子类继承了父类的属性,即 Child1 也有了 name 属性。1
2
3
4
5
6
7
8
9
10
11
12
13
14/*
构造函数实现继承
*/
function Parent1(name){
this.name = name;
}
function Child1(name, age){
Parent1.call(this, name)
this.age = age
}
var c1 = new Child1('bobo', 19)
console.log(c1)
但是,这种方式不能继承父类原型链上的属性,只能继承在父类显式声明的属性。
1 | function Parent1(name){ |
- 借助原型链实现继承
实现原理是将 Child.prototype 赋值为一个新的 Parent 对象,即 Child2.prototype = new Parent2(‘bob’)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function Parent2(name){
this.name = name;
this.arr = [1,2,3]
}
function Child2(age){
this.age = age
}
//重点在这句
Child2.prototype = new Parent2('bob')
c2 = new Child2(20)
c3 = new Child2(22)
console.log('c2', c2)
console.log('c3', c3)
这种方法也有缺点,看 arr 属性是一个数组,如果创建两个实例对象 c2、c3,因为这两个实例对象的 arr 指向同一个引用,所以改变其中一个的值,另一个也会跟着改变。我们来看下面的实验:c2.arr 和 c3.arr 值都是 [1,2,3],此时通过 c2.arr.push(4) 给 c2.arr 添加一个元素,c2.arr 变成了 [1,2,3,4],这没有问题。但再看 c3.arr ,也是 [1,2,3,4],这就有问题了,不是我们期望的。
- 组合方式实现继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14//组合方式优化2
function Parent5(name){
this.name = name
}
function Child5(name, age){
Parent5.call(this, name)
this.age = age
}
Child5.prototype = Object.create(Parent5.prototype)
Child5.prototype.constructor = Child5
var c8 = new Child5()
console.log(c8 instanceof Child5, c8 instanceof Parent5)
console.log(c8.constructor)
通信类
1 同源策略及限制
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
协议、域名、端口只要有一个不一样,就是不同的源。
不同源的限制:
- Cookie、LocalStorage、IndexDB 无法获取
- DOM 无法获取
- Ajax 请求不能发送(Ajax 只限于同源使用,不能跨域使用)
2 前后端如何通信
Ajax
WebSocket
CORS
3 跨域通信的几种方式
JSONP(利用 script 标签的异步加载实现的)
Hash(window.location.hash + iframe)
postMessage (H5中新增的)
WebSocket
CORS
https://segmentfault.com/a/1190000011145364
正向代理代理客户端,反向代理代理服务器。
安全类
- CSRF
CSRF(Cross-site request forgery)跨站请求伪造。
攻击原理
用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。
防御措施
Token验证
Referer 验证(简单易行,但 referer 可能被改变)
隐藏令牌(跟 Token验证差不多,把令牌存到 header 中)
2. XSS
XSS(cross-site scripting)跨域脚本攻击
攻击原理
往 Web 页面里插入恶意Script代码
防御措施
HTML:对以下这些字符进行转义:
1 | &:& |
Javascript:把所有非字母、数字的字符都转义成小于256的ASCII字符;
URL:使用Javascript的encodeURIComponent()方法对用户的输入进行编码,该方法会编码如下字符:, / ? : @ & = + $ #
渲染机制
1 DOCTYPE 及其作用
DTD (Document type definition,文档类型定义) 是一系列的语法规则,用来定义 XML 或 HTML 的文件类型。浏览器会使用它来判断文档类型,决定使用何种协议来解析,以及切换浏览器模式。
DOCTYPE 是用来声明文档类型和 DTD 规范的,一个主要的用途便是文件的合法性验证。如果文件代码不合法,那么浏览器解析时便会出一些差错。
注意:<!DOCTYPE> 声明不区分大小写。
2 浏览器渲染过程
这个问题照着这张图讲清楚就好。浏览器拿到 HTML 和 CSS 之后,通过 HTML Parser 把 HTML 解析成 DOM Tree , 通过 CSS Parser 把 CSS 解析成 Style Rules 即 CSS 规则,然后 DOM Tree 和 CSS规则 结合起来形成 Render Tree 。然后进行布局 Layout 和绘制 Painting,最终 Display 显示在页面上。
3 重排 Reflow
定义
DOM 结构中各个元素都有自己的盒子(模型),这些都需要浏览器根据各种样式来计算并根据计算结果将元素放到它该出现的位置,这个过程称为 reflow。
触发 Reflow
什么情况会触发 Reflow 呢?(记住两三个就可以)
- 增加、删除、修改 DOM 节点时,会导致 Reflow 或 Repaint
- 移动 DOM 位置,或搞个动画时
- 修改 CSS 样式时
- Resize 窗口(移动端没这个问题)或滚动的时候
- 修改网页默认字体时
4 重绘 Repaint
定义
当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定之后,浏览器把这些元素按照各自的特性绘制了一遍,于是页面内容出现了,这个过程称之为 repaint。触发 Repaint
DOM 改动
CSS 改动
如何最小程度的 Repaint
比如要添加多个 DOM 节点,一次性添加,而不要一个个添加。
5 dispaly:none 和 visibility:hidden
dispaly:none 设置该属性后,该元素下的元素都会隐藏,占据的空间消失。visibility:hidden 设置该元素后,元素虽然不可见了,但是依然占据空间的位置。
display:none和visibility:hidden的区别?
1.visibility具有继承性,其子元素也会继承此属性,若设置visibility:visible,则子元素会显示2.visibility不会影响计数器的计算,虽然隐藏掉了,但是计数器依然继续运行着。3.在css3的transition中支持visibility属性,但是不支持display,因为transition可以延迟执行,因此配合visibility使用纯css实现hover延时显示效果可以提高用户体验4. display:none会引起回流(重排)和重绘 visibility:hidden会引起重绘
JS运行机制
1 为什么 JavaScript 是单线程
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
2 任务队列
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。具体来说,异步执行的运行机制如下:(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)
1 | (1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。 |
只要主线程空了,就会去读取”任务队列”,这就是JavaScript的运行机制。这个过程会不断重复。
3 宏任务 & 微任务
这里需要注意的是new Promise是会进入到主线程中立刻执行,而promise.then则属于微任务
宏任务(macro-task):整体代码script、setTimeOut、setInterval
微任务(mincro-task):promise.then、promise.nextTick(node)
4 EventLoop 事件循环
宏任务一般是:包括整体代码script,setTimeout,setInterval、I/O、UI render。
微任务主要是:Promise、Object.observe、MutationObserver。
主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
整体的script(作为第一个宏任务)开始执行的时候,会把所有代码分为两部分:“同步任务”、“异步任务”;
同步任务会直接进入主线程依次执行;
异步任务会再分为宏任务和微任务;
宏任务进入到Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中;
微任务也会进入到另一个Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中;
当主线程内的任务执行完毕,主线程为空时,会检查微任务的Event Queue,如果有任务,就全部执行,如果没有就执行下一个宏任务;
上述过程会不断重复,这就是Event Loop事件循环;
首先执行script下的宏任务->微任务队列->宏任务队列
一旦遇到await 就立刻让出线程,阻塞后面的代码
等候之后,对于await来说分两种情况
- 不是promise 对象
- 是promise对象
如果不是promise,await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完毕后,在回到async内部,把promise的东西,作为await表达式的结果
如果它等到的是一个 promise 对象,await 也会暂停async后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。
如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。
5 定时器
除了放置异步任务的事件,”任务队列”还可以放置定时事件,即指定某些代码在多少时间之后执行。这叫做”定时器”(timer)功能,也就是定时执行的代码。定时器功能主要由setTimeout()和setInterval()这两个函数来完成,它们的内部运行机制完全一样,区别在于前者指定的代码是一次性执行,后者则为反复执行。以下主要讨论setTimeout()。setTimeout()接受两个参数,第一个是回调函数,第二个是推迟执行的毫秒数。
总之,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在”任务队列”的尾部添加一个事件,因此要等到同步任务和”任务队列”现有的事件都处理完,才会得到执行。HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()。需要注意的是,setTimeout()只是将事件插入了”任务队列”,必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。
6 哪些是异步任务
setTimeout 和 setInterval
DOM 事件
Promise
网络请求
I/O
7 常考题
1) 第1题
1 | console.log(1); |
答案:1,3,2解析: console.log() 是同步任务, setTimeout 是异步任务。异步任务会等同步任务执行完再执行。虽然 setTimeout 设置的延迟是 0,但浏览器规定延迟最小为 4ms,所以 console.log(2) 在 4ms 后被放入任务队列。当同步任务执行完,即打印完 1,3 之后,主线程再从任务队列中取任务,打印 2。
2) 第2题
1 | console.log('A') |
答案:A
解析:代码从上往下执行,先打印 A,然后 while 循环,因为条件一直是 true,所以会进入死循环。while 不执行完就不会执行到第三行
1 | console.log('A'); |
同样只会输出 A。因为异步任务需要等同步任务执行完之后才执行,while 进入了死循环,所以不会打印 B。
3) 第3题
1 | for(var i=0; i<4; i++){ |
结果:4个4。解析:这题主要考察异步任务放入任务队列的时机。当执行到 setTimeout 即定时器时,并不会马上把这个异步任务放入任务队列,而是等时间到了之后才放入。然后等执行栈中的同步任务执行完毕后,再从任务队列中依次取出任务执行。for 循环是同步任务,会先执行完循环,此时 i 的值是 4。4ms后 console.log(i) 被依次放入任务队列,此时如果执行栈中没有同步任务了,就从任务队列中依次取出任务,所以打印出 4 个 4。
那么如何才能按照期望打印出 0, 1,2,3 呢?有三个方法:
1 | //方法1:把 var 换成 let |
4) 第4题
1 | setTimeout(function(){ |
分析:1.setTimeout是异步,且是宏函数,放到宏函数队列中;2.new Promise是同步任务,直接执行,打印2,并执行for循环;3.promise.then是微任务,放到微任务队列中;4.console.log(4)同步任务,直接执行,打印4;5.此时主线程任务执行完毕,检查微任务队列中,有promise.then,执行微任务,打印3;6.微任务执行完毕,第一次循环结束;从宏任务队列中取出第一个宏任务到主线程执行,打印1;7.结果:2,4,3,1
5) 第5题
1 | console.log(1); |
分析:1.console.log(1)是同步任务,直接执行,打印1;2.setTimeout是异步,且是宏函数,放到宏函数队列中;3.Promise.resolve().then是微任务,放到微任务队列中;4.console.log(5)是同步任务,直接执行,打印5;5.此时主线程任务执行完毕,检查微任务队列中,有Promise.resolve().then,执行微任务,打印3;6.此时发现第二个.then任务,属于微任务,添加到微任务队列,并执行,打印4.我是新增的微任务;7.这里强调一下,微任务执行过程中,发现新的微任务,会把这个新的微任务添加到队列中,微任务队列依次执行完毕后,才会执行下一个循环;8.微任务执行完毕,第一次循环结束;取出宏任务队列中的第一个宏任务setTimeout到主线程执行,打印2;9.结果:1,5,3,4.我是新增的微任务,2
6) 第6题
1 | function add(x, y) { |
分析:1.add()是同步任务,直接执行,打印1;2.add()里面的setTimeout是异步任务且宏函数,记做timer1放到宏函数队列;3.add()下面的setTimeout是异步任务且宏函数,记做timer2放到宏函数队列;4.new Promise是同步任务,直接执行,打印4;5.Promise里面的setTimeout是异步任务且宏函数,记做timer3放到宏函数队列;6.Promise里面的for循环,同步任务,执行代码;7.Promise.then是微任务,放到微任务队列;8.console.log(8)是同步任务,直接执行,打印8;9.此时主线程任务执行完毕,检查微任务队列中,有Promise.then,执行微任务,发现有setTimeout是异步任务且宏函数,记做timer4放到宏函数队列;10.微任务队列中的console.log(7)是同步任务,直接执行,打印7;11.微任务执行完毕,第一次循环结束;12.检查宏任务Event Table,里面有timer1、timer2、timer3、timer4,四个定时器宏任务,按照定时器延迟时间得到可以执行的顺序,即Event Queue:timer2、timer4、timer3、timer1,取出排在第一个的timer2;13.取出timer2执行,console.log(3)同步任务,直接执行,打印3;14.没有微任务,第二次Event Loop结束;15.取出timer4执行,console.log(6)同步任务,直接执行,打印6;16.没有微任务,第三次Event Loop结束;17.取出timer3执行,console.log(5)同步任务,直接执行,打印5;18.没有微任务,第四次Event Loop结束;19.取出timer1执行,console.log(2)同步任务,直接执行,打印2;20.没有微任务,也没有宏任务,第五次Event Loop结束;21.结果:1,4,8,7,3,6,5,2
7) 第7题
1 | setTimeout(function() { // timer1 |
分析:1.第一个setTimeout是异步任务且宏函数,记做timer1放到宏函数队列;2.第三个setTimeout是异步任务且宏函数,记做timer2放到宏函数队列;3.没有微任务,第一次Event Loop结束;4.取出timer1,console.log(1)同步任务,直接执行,打印1;5.timer1里面的setTimeout是异步任务且宏函数,记做timer3放到宏函数队列;6.没有微任务,第二次Event Loop结束;7.取出timer2,console.log(3)同步任务,直接执行,打印3;8.没有微任务,第三次Event Loop结束;9.取出timer3,console.log(2)同步任务,直接执行,打印2;10.没有微任务,也没有宏任务,第四次Event Loop结束;11.结果:1,3,2
页面性能
提升页面性能的方法有哪些?
- 资源压缩合并,减少 HTTP 请求
- 非核心代码异步加载(异步加载的方式,异步加载的区别)
- 利用浏览器缓存(缓存的分类,缓存原理)
- 使用 CDN
- 预解析 DNS
1
2
3
4//强制打开 <a> 标签的 dns 解析
<meta http-equiv="x-dns-prefetch-controller" content="on">
//DNS预解析
<link rel="dns-prefetch" href="//host_name_to_prefetch.com">
1 异步加载
异步加载的方式
- 动态脚本加载
- defer
- async
异步加载的区别
defer 是在 HTML 解析完之后才会执行,如果是多个,按照加载的顺序依次执行。defer脚本会在DOMContentLoaded和load事件之前执行。
async 是在脚本加载完之后立即执行,如果是多个,执行顺序和加载顺序无关。async会在load事件之前执行,但并不能确保与DOMContentLoaded的执行先后顺序。2 浏览器缓存
https://www.jianshu.com/p/256d0873c398
缓存策略的分类:
强缓存
协商缓存
缓存策略都是通过设置 HTTP Header 来实现的。
浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识。
浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中。1 强缓存
强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。
1. Expires
缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age + 请求时间,需要和Last-modified结合使用。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。Expires: Wed, 22 Oct 2018 08:41:00 GMT表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过期,需要再次请求。
2. Cache-Control
在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存。比如当Cache-Control:max-age=300时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令:
3. Expires和Cache-Control两者对比
其实这两者差别不大,区别就在于 Expires 是http1.0的产物,Cache-Control是http1.1的产物,两者同时存在的话,Cache-Control优先级高于Expires;在某些不支持HTTP1.1的环境下,Expires就会发挥用处。所以Expires其实是过时的产物,现阶段它的存在只是一种兼容性的写法。强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。
2 协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
协商缓存生效,返回304和Not Modified
协商缓存失效,返回200和请求结果
协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。
1. Last-Modified 和 If-Modified-Since
浏览器在第一次访问资源时,服务器返回资源的同时,在response header中添加 Last-Modified 的header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和 header;
Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT
浏览器下一次请求这个资源,浏览器检测到有 Last-Modified这个header,于是添加If-Modified-Since这个header,值就是Last-Modified中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200。但是 Last-Modified 存在一些弊端:
- 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
- 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略?所以在 HTTP / 1.1 出现了 ETag 和If-None-Match
2. ETag 和 If-None-Match
Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。
3. 两者之间对比:
首先在精确度上,Etag要优于Last-Modified。
Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified也有可能不一致。
第二在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。
第三在优先级上,服务器校验优先考虑Etag
3 缓存机制
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。
4 强缓存与协商缓存的区别
强缓存与协商缓存的区别可以用下表来表示:
缓存类型 获取资源形式 状态码 发送请求到服务器
强缓存 从缓存取 200(from cache) 否,直接从缓存取
协商缓存 从缓存取 304(Not Modified) 是,通过服务器来告知缓存是否可用
用户行为对缓存的影响
用户操作 Expires/Cache-Control Last-Modied/Etag
地址栏回车 有效 有效
页面链接跳转 有效 有效
新开窗口 有效 有效
前进回退 有效 有效
F5刷新 无效 有效
Ctrl+F5强制刷新 无效 无效