Fork me on GitHub

Any application that can be written in JavaScript, will eventually be written in JavaScript.

2020 年最新面试题

到了2020年了,在网宿呆了两年半,今年终于想出来换一个环境,虽然带有一些突然,2019年12月25号离职,刚好圣诞节那天,特定记录一下之前的总结和感想,顺便记录一下面试笔试等的一些题目,做个记录。

网宿的两年半完成了人生的两件大事,2017年10月10日结婚,2019年6月16日宝贝女儿的出生,非常开心,在网宿的日子在我往后的生命中一定是让人难忘的一笔,网宿是一家比较安逸的公司,有着优厚的福利和待遇,加上不加班等在我离职后才发现这一点,可是命运就是这样,公司在2019年的连续的巨幅亏损,导致不少人离开,互联网的环境真是真是凶险。离职之后面试了大概6家公司,拿了4个offer,命中率还算不错,通过在网宿还算稍微正规的前端工程化磨练,稍微对一些面试的技巧有一些把握,自己之前面过一些人,因此当自己作为被面试的那一方的时候,起码能从面试中大概摸个对方的水平来大概评判公司的水平。接下来是一些面试题做的准备,希望对你有一些帮助。

1.同域请求的并发数限制的原因

浏览器的并发请求数目限制是针对同一域名的,同一时间针对同一域名下的请求有一定数量限制,超过限制数目的请求会被阻塞(chorme和firefox的限制请求数都是6个)。

限制其数量的原因是:基于浏览器端口的限制和线程切换开销的考虑,浏览器为了保护自己不可能无限量的并发请求,如果一次性将所有请求发送到服务器,也会造成服务器的负载上升。

2.cdn加速原理

  • 1、当用户点击网站页面上的url时,经过本地dns系统解析,dns系统会将域名的解析权给交cname指向的cdn专用dns服务器。

  • 2、cdn的dns服务器将cdn的全局负载均衡设备ip地址返回给用户。

  • 3、用户向cdn的全局负载均衡设备发起内容url访问请求。

  • 4、cdn全局负载均衡设备根据用户ip,以及用户请求的内容url,选择一台用户所属区域的区域负载均衡设备。

  • 5、6、区域负载均衡设备会为用户选择一台合适的缓存服务器提供服务,选择的依据包括:根据用户IP地址,判断哪一台服务器距用户最近;根据用户所请求的URL中携带的内容名称,判断哪一台服务器上有用户所需内容;查询各个服务器当前的负载情况,判断哪一台服务器尚有服务能力。基于以上这些条件的综合分析之后,区域负载均衡设备会向全局负载均衡设备返回一台缓存服务器的IP地址全局负载均衡设备把服务器的IP地址返回给用户。

  • 7、用户向缓存服务器发起请求,缓存服务器响应用户请求,将用户所需内容传送到用户终端。如果这台缓存服务器上没有用户想要的内容,而区域均衡设备依然将它分配给了用户,那么这台服务器 就要向它的上一级缓存服务器发起请求内容,直至追溯到网站的源服务器将内容拉回给用户。

3.讲讲闭包

闭包是指有权访问另一个函数作用域的中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量。
闭包的缺点就是常驻内存,会增大内存的使用量,容易造成内存泄露。
闭包的应用场合主要为了:设计私有的方法和变量。一般函数执行完毕后,局部活动的变量就被销毁了,内存中仅仅保存全局作用域,但是闭包不会。

4.vuex 解决了什么问题

vuex解决了组件之间同一状态的共享问题,当我们的应用遇到多个组件之间的共享问题时会需要:

多个组件依赖同一状态。传承的方法对于多层嵌套的组件将会变得很繁琐,并且对于兄弟组件间的传递无能为力。他采用集中式存储管理应用的所有组件的状态,这意味着本来需要共享状态的更新是需要组件之间的通讯,而现在有了vuex,组件就都和store通讯了。

在没有vuex之前:

  • 1、父组件向子组件传递状态使用props属性实现。

  • 2、子组件向父组件传递参数使用子组件触发事件,父组件监听发出的事件的方式来实现( $emit)。

  • 3、兄弟组件之间使用事件总线的方式(eventBus),其中一个组件使用$emit触发,另一个组件使用$on监听。

5.变量提升

js中函数及变量的声明都将被提升到函数的最顶部,变量可以先使用后声明,但是初始化不会提升,就是会一开始 let/const 变量,但是赋值需要到代码执行的时候才会赋值。

6.https 和 http 的区别

  • 1、https 协议需要到 ca 申请证书,http 是基于 tcp/ip 协议的,传输的内容都是明文,https 在基于 tcp/ip 协议的基础上多了一层 ssl/tls 加密,传输内容都是通过加密,因此https 可以做到防劫持;

  • 2、http使用的是80端口,https 使用的是443端口;

7.tcp 的三次握手

第一次握手:建立连接,客户端 a 发送syn=1、随机产生seq=client_isn的数据包到服务器 b,等待服务器确认;

第二次握手:服务器 b 收到请求后确认联机(可以接受数据),发起第二次握手请求,ACK=(A的Seq+1)、SYN=1,随机产生Seq=client_isn的数据包到 a;

第三次握手:a 收到后检查ACK是否正确,若正确,a 会在发送确认包ACK=服务器b的Seq+1、ACK=1,服务器b收到后确认Seq值与ACK值,若正确,则建立连接。
( SYN(synchronous建立联机)、ACK(acknowledgement 确认)、Sequence number(顺序号码))。

进行三次握手的原因:为了保证服务端能接收到客户端的信息并能做出应答而进行前两次握手,为了保证客户端能够接收到服务端的信息并能做出正确的应答而进行第三次握手。

8.讲讲 js 的线程

js 是单线程的,诸如 setTimeout,ajax等这些事怎么实现的呢,因为浏览器/node宿主环境是多线程的,浏览器搞了几个其他线程去辅助 js 线程的运行。

浏览器有很多线程如:

  • 1、gui 渲染线程
  • 2、js 引擎线程
  • 3、浏览器事件线程(onclick)
  • 4、定时器触发线程(setTimeout)
  • 5、http 异步线程
  • 6、eventLoop 轮询处理线程

1、2、3是常驻线程。

线程和进程

浏览器打开一个页面就是一个进程,一个进程的运行需要多个线程的互相配合。浏览器中 1 线程和 2 线程是两个互斥的线程,因为gui在渲染的时候不允许js改变其dom。js 引擎线程我们称为主线程(不包括异步的那些代码),比如:

1
2
3
4
5
var a = 2;
setTimeout()
ajax()
console.log()
dom.onclick(func C)

第1和4行代码是同步代码,直接在主线程中运行,第2和3是一部代码,会先扔给其他进程,主线程运行js时,会生成一个执行栈,执行过程中遇到异步的操作会把其回调函数扔到消息队列(又称任务队列,存放异步回调函数成功后的回调函数,先入先出)当中去,等其中先成功的回调通知了就返回成功后的回调函数。代码中遇到第2行就将其交给定时器触发线程,第3行交给 http 异步线程,第5行交给浏览器事件进程。

这几个线程主要做两件事:

  • 1、执行主线程扔进来的异步代码并执行代码;
  • 2、保存着回调函数,异步代码执行成功后,通知 eventLoop轮询处理线程过来取相应的回调函数。

EventLoop 轮询处理线程

上面我们知道了这三个东西:

  • 1、主线程,处理同步代码
  • 2、各种异步线程处理异步代码
  • 3、消息队列,存储着异步成功后的回调函数,一个静态存储结构

这三个中间有个专门的中介去沟通它们,这就是 eventLoop 轮询处理线程,通过不断的循环沟通这三个过程。

只有主线程的同步代码都执行完了才会去队列里看看啥要执行的。

主线程把setTimeout、ajax、dom.onclick分别给三个线程,他们之间有些不同

  • 1、对于setTimeout代码,定时器触发线程在接收到代码时就开始计时,时间到了将回调函数扔进队列。
  • 2、对于ajax代码,http 异步线程立即发起http请求,请求成功后将回调函数扔进队列。
  • 3、对于dom.onclick,浏览器事件线程会先监听dom,直到dom被点击了,才将回调函数扔进队列。

9.讲讲缓存

根据 response header 里面的 cache-control 和expires 这两个属性,当两个都存在时,cache-control 的优先级较高。

cache-controll:用于控制浏览器在什么情况下直接使用本地缓存而不向服务器发送请求。有以下指:

  • Public:指示响应可被任何缓存区缓存。
  • Private:指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效。
  • no-cache:指示请求或响应消息不能缓存。
  • no-store:用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
  • max-age:指示浏览器可以接收生存期不大于指定时间(以秒为单位)的响应。
  • min-fresh:指示浏览器可以接收响应时间小于当前时间加上指定时间的响应。
  • max-stale:指示浏览器可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么浏览器可以接收超出超时期指定值之内的响应消息。

expires:提供一个日期和时间,在该日期前的所有对该资源的请求都会直接使用浏览器缓存。

服务端通过 last-modified 和 etag 这两个属性来判断缓存是否过期。

last-modified:响应资源的最后修改时间。需要配合 cache-controll 使用。当缓存过期时,会在请求头上加上last-modified 标识,服务器收到请求后会把该值和该资源的最后修改时间相对比,若最后修改时间较新,说明资源改动过,则响应 http 200,若最后修改时间较旧,则告诉浏览器使用缓存。

ETag:资源在服务器的唯一标识(生成规则由服务器决定)。Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。etag 和 last-modified 的功能类似。但是 last-modified 只能精确到秒;或者如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存;或者存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形。这样使用 etag 就可以解决这样的问题。

10.vue 和 react 的区别

react 和 vue 有很多相似之处,他们都有:

  • 1、使用 virtual dom
  • 2、提供响应式(reactive)和组件化(composable)的视图组件
  • 3、将注意力集中保持在核心库,将其他功能(路由,全局状态管理等)交给了相关的库

区别有:

  • 1、在 react 中,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树,如要避免不必要的子组件的重渲染,需要用到 PureComponent。react 在 setState 之后会重新走渲染流程,如果shouldComponentUpdate 返回的是 true 就继续渲染,如果返回 false 就不会重新渲染,PureComponent 就是重写了shouldComponentUpdate,然后在里面作了props和state的浅层对比。
    而 vue 是响应式,基于数据可变的,通过每一个属性建立 watcher 来监听,当属性变化时响应式的更新对应的虚拟 dom。
  • 2、react 中所有都是 js,没有模版,html 和 css 都是使用 js 来表达,vue 则有对应的 template 来区分。因此 react 在编码上会显得更加灵活。
  • 3、react 的内联样式是采用 css in js 的方式来实现的,而 vue 在样式的引入或者内联上都和传统的一致,且有 scope 来限制其专门的作用域。
  • 4、react 可以通过高阶组件(hoc)来扩展,而 vue 通过mixins(混入) 来扩展组件。

11.谈谈 vuex

  • State: 唯一数据源,存放状态仓库。mapState 批量取数据方法。
  • Getter:认为是 state 的计算属性,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖指变化之后才会被重新计算。mapGetter 批量取getter。
  • Mutation:修改 state 的同步方法。
  • Action:修改 state 的异步方法。

12.vue 响应式原理

当你把对象传入 vue 实例的 data 选项,vue 将便利此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter ,在属性被访问时组册了依赖且在修改时通知 watcher 变更,每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖,之后当依赖项的 setter 触发时,会通知 watcher ,从而使它关联的组件重新渲染。

由于现代 js 的限制,vue 无法检测到对象属性的添加和删除,由于 vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 vue 将它转化为响应式的。vue 提供了 $set 方法向嵌套对象添加单个响应式属性。若需要为已有对象赋值多个新属性我们可以对原对象与要混合进去的对象的属性一起创建一个新的对象:

1
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })。

13.nextTick()异步更新队列

vue 更新 dom 是异步操作,为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。

14.写一个冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var arr = [3, 1, 4, 6, 5, 7, 2];

function bubbleSort(arr) {
for (var i = 0; i < arr.length - 1; i++) {
for(var j = 0; j < arr.length - 1; j++) {
if(arr[j + 1] < arr[j]) {
var temp;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}

console.log(bubbleSort(arr));

15.写一个原生 ajax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1. 创建连接
var xhr = new XMLHttpRequest()
// 2. 连接服务器
xhr.open('get', url, true)
// 3. 发送请求
xhr.send(null);
// 4. 接受请求
xhr.onreadystatechange = function(){
// readyState 为 XMLHttpRequest 的状态:0:请求未初始化 1:服务器连接已建立 2: 请求已接收 3: 请求处理中 4:请求已完成
if(xhr.readyState == 4){
// status 200 为 ok,404 未找到页面
if(xhr.status == 200){
success(xhr.responseText);
} else { // fail
fail && fail(xhr.status);
}
}
}

16. 的 title 和 alt 有什么区别

title通常当鼠标滑动到元素上的时候显示;
alt是的特有属性,是图片内容的等价描述,用于图片无法加载时显示、读屏器阅读图片。可提图片高可访问性,除了纯装饰图片外都必须设置有意义的值,搜索引擎会重点分析。

17.浏览器地址栏输入 url 后发生了什么

  • 1、dns 解析查找 url 的 ip:第一步浏览器会把输入的域名解析成对应的 ip,过程如下:(1)查找浏览器缓存,浏览器会缓存dns 记录一段时间,如果有缓存直接返回ip;(2)查找系统缓存,若找不到ip,浏览器会进行系统调用,查找本机的host文件,如找到就直接返回 ip;(3)若第1,2步都查询无果,则借助网络查找。

  • 2、浏览器与目标服务器建立 tcp 连接:浏览器通过 dns 解析得到目标服务器的 ip 后,与服务器建立 tcp 连接,与服务器进行3次握手,握手成功则开始通信。

  • 3、浏览器通过 http 协议发送请求:客户端向服务端发起 http get 报文请求,会带上user-agent浏览器系统信息,编码格式,cookie 等。

  • 4、服务器收到请求后通过服务器处理,返回对应的响应,若成功返回200,返回报文。

  • 5、释放tcp连接:(1)客户端向服务器发出连接释放报文,然后停止发送数据。(2)服务器接收到释放报文后发出确认报文,然后将服务器上未传送完的数据发送完。(3)服务器数据传输完毕后,向客户端发送连接释放报文。(4)客户机接收到报文后,发出确认,释放tcp连接。

  • 6、浏览器显示页面,走gui渲染流程。

  • 7、浏览器发送嵌入在 html 中的其他资源连接:比如 css/js/img 等会通过url重新发送请求,请求过程依然和 html 的方式类似重复以上步骤,但是静态文件是可以缓存到浏览器的,第一次缓存后之后在规定时间内都会取缓存。

18.事件冒泡与捕获

事件冒泡是从发生事件的元素开始一直向外层传播,事件捕获是从最外层开始直到具体的元素。w3c采用了折中的方式,先捕获再冒泡。addEventListener 的第三个参数默认值是fasle 表示在事件冒泡阶段调用事件处理函数,如果是 true 则是捕获阶段调用函数。

1
2
3
document.getElementById('s3').addEventListener("click",function(e){
console.log("冒泡事件");
}, false);
  • e.stopPropagation():阻止事件冒泡传播。
  • e.preventDefault():阻止默认事件发生,既然说默认行为,元素必须有默认行为才能取消,如果没有默认行为则无效。

19.节流函数和防抖函数

针对一些会频繁触发的事件如 scroll、resize,如果正常绑定事件处理的话会在很短的时间内连续触发,很影响性能。可以使用两种方法:节流法和防抖法,两者都可以使用定时器来实现。

节流:使得一定时间内只触发一次函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function throttle (fn, delay) {
let lastTime = 0

return function() {
// 通过 this 和 arguments 获取函数的作用域和变量
let context = this
let args = arguments
let nowTime = Date.now()
if (nowTime - lastTime > delay) {
fn.apply(context, args)
lastTime = nowTime
}
}
}

防抖:只在最后一次操作的时候才触发。

1
2
3
4
5
6
7
8
9
10
11
12
function debounce (fn, delay) {
let timer = null

return function () {
let context = this
let args = arguments
clearTimeout(timer)
timer = setTimeout(function(){
fn.apply(context, args)
}, delay)
}
}

节流和防抖的区别在于:节流不管事件触发多频繁一定会在规定时间内执行一次方法,而防抖只在最后一次事件后才触发一次方法。

函数防抖和节流的目的都是减少某个操作的执行频率,防抖偏向于不在意过程更注重结束的时候能执行就可以,节流更偏向过程必须执行但是可以减少频率在一个合理的范围内。

20.apply 和 call

fn.apply(thisFn, [arg1, arg2, arg3 …]) 等同于 fn.call(thisFn, arg1, arg2, arg3 …) 区别只是 apply 传入的参数是数组,call 传入的参数连续参数。

功能:thisFn 会继承 fn 的方法,且后面的参数带入执行。call/apply 方法可将一个函数的对象上下文从初始的上下文改变为thisFn,如果没有提供 thisFn 参数,那么 Global 对象被用作 thisFn。

1
2
3
4
5
6
7
8
9
10
11
12
function add (j, k) {
return j+k;
}

function sub (j, k) {
return j-k;
}

add(5,3); //8
add.call(sub, 5, 3); //8
sub(5, 3); //2
sub.apply(add, [5, 3]); //2

21.前端常见的安全问题

  • 1、XSS(cross site scripting)跨站点脚本攻击:攻击者通过注入恶意 js/html 代码到受信任的网站上进行操作。
    解决方法:对用户输入的表达内容进行对应的正则校验,对 html 的开闭合标签进行编码,对 js 的一些语法进行转义和编码。

  • 2、CSRF(cross site request forgery)跨站请求伪造:攻击者盗用你的身份,以你的名义发送恶意请求,而服务器认为这些请求是合法的。例如:user 访问了受信任网站 a,输入了账号和密码登录了页面,通过验证后 a 网站产生了 cookie 保存到浏览器,这时候用户可以发送正常的请求,user 未退出网站 a,这时候登录了非法网站 b,user 在 b 网站的操作发送了一个伪造 a 网站的一些请求(因为session未过期,且 b 通过方法取得了 cookie),这时候 user 的请求被认为不非法而导致了损失。
    解决方法:验证 HTTP Referer 字段,判断请求的发送来源 url 地址;在请求添加 token;使用验证码。

  • 3、SQL 注入:攻击者将SQL命令插入到Web表单提交。
    解决方法:用户输入的数据进行严格的检查、对数据库配置使用最小权限原则

22.跨域资源共享 CORS

cors 需要浏览器和服务器通知支持,现在大多数浏览器已支持,实现cors 的关键是服务器,只要服务器实现cors接口,就可以跨域通信。

cors 请求分为两类:简单请求(get、post。content-type:只限于 application/x-www-form-urlencoded、multipart/for-data、text/plain 之一)和非简单请求(put/delete。content-type: application/json)。

对于简单请求浏览器直接发出cors 请求的头部会增加一个 origin 字断:

1
2
3
4
5
6
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0…

这个字断是让服务器根据这个值来决定是否同意这个请求,若origin 不在服务器的指定源中,则服务器的回应头不会包含 access-control-allow-origin,客户端则会被 XMLHttpRequest 的 onerror 回调函数捕获,若origin在指定的许可范围内,则响应头会多出以下信息:

1
2
3
4
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

23.常见数组方法

  • 1、Array.length : 获取数组长度。例:[1, 2, 3.14, ‘Hello’, null, true].length // 6。

  • 2、Array.indexOf(e) :返回元素e的索引,索引从 0 开始。例:[10, 20, ‘30’, ‘xyz’].indexOf(10) // 0

  • 3、Array.slice(index1,index2) :截取数组部分元素,返回新数组,包含开始索引,不包含结束索引。
    例: [‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’].slice(0, 3) // [‘A’, ‘B’, ‘C’]

  • 4、Array.push():向 Array 的末尾添加若干元素;
    Array.pop():把Array的最后一个元素删除掉;
    Array.unshift():往Array的头部添加若干元素;
    Array.shift():把Array的第一个元素删掉。

  • 5、Array.sort() :对当前Array进行排序,它会直接修改当前Array的元素位置。

  • 6、Array.reverse() :把整个 Array 元素反转。

  • 7、Array.concat() :把当前的Array和另一个Array连接起来,并返回一个新的Array。例如:

1
2
3
4
5
var arr = ['A', 'B', 'C'];
var added = arr.concat([1, 2, 3]);

added; // ['A', 'B', 'C', 1, 2, 3]
arr; // ['A', 'B', 'C']
  • 8、Array.join() : 把当前Array的每个元素都用指定的字符串连接成字符串。例:[‘A’, ‘B’, ‘C’, 1, 2, 3].join(‘-‘); // ‘A-B-C-1-2-3’

  • 9、Array.splice() :从指定的索引开始删除若干元素,然后再从该位置添加若干元素。例:

1
2
3
4
var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];

arr.splice(2, 3, 'Google', 'Facebook'); //从索引2开始删除3个元素,然后再添加两个元素
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']

24.常见字符串方法

  • 1、String.length:获取字符串长度,类似Array的下标操作,索引号从0开始。
  • 2、String.indexOf():字符串索引,索引从 0 开始。
  • 3、String.toUpperCase():字符串全部变成大写。
  • 4、String.toLowerCase():字符串全部变成小写。
  • 5、String.substring():字符串切取,返回新数组,包含开始索引,不包含结束索引。例:’hello, world’.substring(0, 5) // ‘hello’
  • 6、String.trim():去除字符串首末尾空格
  • 7、String.split():用于把一个字符串分割成字符串数组(与数组的join()方法互相照应)。例: ‘how are you doing today’.split(‘ ‘)) // [“how”, “are”, “you”, “doing”, “today”]

25.代码实现深拷贝

通过递归来实现深拷贝:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function deepClone (obj) {
let cloneObj = {}
if (obj && typeof obj === ‘Object’ ) {
for (let key in obj) {
if (obj[key] && typeof obj[key] === ‘Object’) {
// 如果属性值是对象则要递归
cloneObj[key] = deepClone(obj[key])
} else {
// 一层结构直接赋值
cloneObj[key] = obj[key]
}
}
}
return cloneObj
}

26.常见的 http 状态码

100 Continue 继续,一般在发送post请求时,已发送了http header之后服务端将返回此信息,表示确认。

200 OK 成功,正常返回。

302 Found 临时性重定向。

304 Not Modified 请求一个资源,如果资源在上次访问后没有更新过,就返回304直接使用浏览器 cache。

400 Bad Request 服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。

401 Unauthorized 请求未授权。

403 Forbidden 禁止访问。

404 Not Found 找不到如何与 URI 相匹配的资源。

500 Internal Server Error 最常见的服务器端错误。

503 Service Unavailable 服务器端暂时无法处理请求(可能是过载或维护)。

27.如何解决移动端点击 300ms 延时的问题

因为 iPhone 一开始的小屏幕浏览页面的时候具有双击缩放的功能,用户在点击页面的时候浏览器并不知道是要双击还是点击,因此添加了一个 300ms 间隔来判断下一个点击是否触发,因此移动端出现了 300ms 的延时。

解决方法:

  • 1、引入 faskclick.js;禁用浏览器缩放
  • 2、设置 meta 标签如下:

    1
    2
    <meta name="viewport" content="user-scalable=no”>
    <meta name="viewport" content="initial-scale=1, maximum-scale=1">
  • 3、设置页面宽度为终端宽度:

    1
    <meta name="viewport" content="width=device-width">

设置了该 meta 标签,那浏览器就可以认为该网站已经对移动端做过了适配和优化,就无需双击缩放操作了。

28.宏任务和微任务

宏任务队列可以有多个,微任务队列只有一个;
主线程上的任务是第一个宏任务;
会建立宏任务的有:setTimeOut、 setInterval、 requestAnimationFrame。
会建立微任务的有:Promise的回调、 process.nextTick。
当有一个宏任务队列执行完毕后,会执行微任务队列中的全部内容,然后执行另一个宏任务队列,如此反复。
js代码执行机制基本是这样的:当遇到3中的内容时建立新的宏任务,遇到4中的内容时将其加入微任务,然后按5中的顺序执行。结合下面的例子来分析一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
console.log("1")

// setTimeOut函数不设置时间
setTimeout ( () => {
console.log('2')
new Promise( (resolve => {
console.log("3")
resolve()
})) .then ( () => console.log("4"))
})

new Promise ( (resolve, reject) => {
console.log("5")
resolve()
}) .then( () => console.log('6'))

setTimeout ( () => {
console.log('7')
})

console.log("8")

测试发现setTimeOut不加时间与设置时间为0效果相同,都是等主线程上的代码执行完后立即执行。

分析:
js代码从上到下执行,先遇到console.log(1),将其加入主线程的宏任务队列,然后发现setTimeOut,为其创建第二个宏任务队列并将其加入,其中代码先不执行,然后遇到Promise,将其中的console.log(5)加入主线程的宏任务队列,将then回调函数中的内容加入微任务队列,继续往下发现第二个setTimeOut,将其放入第三个宏任务队列,最后将console.log(8)放入主线程宏任务队列,到此,代码已经完成了在不同队列中的分布,详细情况为:

接下来开始执行第一个宏任务队列,分别打印1,5,8,然后执行微任务队列,打印6,微任务队列变为空。接着执行第二个宏任务队列,开始执行第一个setTimeOut中的代码:先后打印 2,3,并将回调函数中的console.log(4)加入微任务。此时第二个宏任务队列执行完毕,开始执行微任务队列,打印 4。接着执行第三个宏任务队列,打印 7。执行完毕。所以最终输出顺序为:15862347。