前端最全面试题整理(持续更新)

写在前面

此文章的题是同事给我发的一个PDF文档里的题,原版我也不清楚是哪里的,好像是某个培训机构的题库。题比较全,但是原版很多题的答案不是很清晰,在此基础上我重新补充了一下

最近应该会持续的把这些题的答案更新完

2023.2.17

前端基础

一、 HTTP/HTML/浏览器

1、说一下 http 和 https

https 的 SSL 加密是在传输层实现的。

(1) http 和 https 的基本概念

  • http: 超文本传输协议,是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从 WWW 服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。
  • https: 是以安全为目标的 HTTP 通道,简单讲是 HTTP 的安全版,即HTTP 下加入SSL层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL。https 协议的主要作用是:建立一个信息安全通道,来确保数组的传输,确保网站的真实性。

(2) http 和 https 的区别?

http 传输的数据都是未加密的,也就是明文的,网景公司设置了SSL 协议来对http 协议传输的数据进行加密处理,简单来说 https 协议是由 http 和 ssl 协议构建的可进行加密传输和身份认证的网络协议,比 http 协议的安全性更高

主要的区别如下:

  • Https 协议需要 ca 证书,费用较高。
  • http 是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl 加密传输协议。使用不同的链接方式,端口也不同,一般而言,http 协议的端口为80,https 的端口为443
  • http 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 http 协议安全。

(3) https 协议的工作原理

客户端在使用 HTTPS 方式与 Web 服务器通信时有以下几个步骤:

  • 客户使用 https url 访问服务器,则要求 web 服务器建立 ssl 链接
  • web 服务器接收到客户端的请求之后,会将网站的证书(证书中包含了公钥),返回或者说传输给客户端。
  • 客户端和 web 服务器端开始协商 SSL 链接的安全等级,也就是加密等级。
  • 客户端浏览器通过双方协商一致的安全等级,建立会话密钥,然后通过网站的公钥来加密会话密钥,并传送给网站。
  • web 服务器通过自己的私钥解密出会话密钥。
  • web 服务器通过会话密钥加密与客户端之间的通信。

(4) https 协议的优点

  • 使用 HTTPS 协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;
  • HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,要比http 协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。
  • HTTPS 是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。
  • 谷歌曾在 2014 年 8 月份调整搜索引擎算法,并称“比起同等 HTTP 网站,采用HTTPS加密的网站在搜索结果中的排名将会更高”。

(5) https 协议的缺点

  • https 握手阶段比较费时,会使页面加载时间延长 50%,增加 10%~20%的耗电
  • https 缓存不如 http 高效,会增加数据开销。
  • SSL 证书也需要钱,功能越强大的证书费用越高。
  • SSL 证书需要绑定 IP,不能再同一个 ip 上绑定多个域名,ipv4 资源支持不了这种消耗。

2、tcp 三次握手,一句话概括

确认客户端和服务端的接收与发送能力是否正常,因此需要三次握手。

简化三次握手:
前端最全面试题整理(持续更新)_第1张图片
从图片可以得到三次握手可以简化为:C发起请求连接S确认,S也发起请求连接C确认

每次握手的作用:

  • 第一次握手: S只可以确认 自己可以接收C发送的报文段
    (客户端给服务器发送一个 SYN 报文)
    (服务器收到 SYN 报文之后,会应答一个 SYN+ACK 报文。)
  • 第二次握手:C可以确认 S收到了自己的报文段,并且可以确认自己可以接受S发送的报文段
    (客户端收到 SYN+ACK 报文之后,会回应一个 ACK 报文。)
  • 第三次握手:S可以确认C收到了自己发送的报文段
    (服务器收到 ACK 报文之后,三次握手建立完成。)

3、TCP 和 UDP 的区别

  1. TCP 是面向连接的,udp 是无连接的即发送数据前不需要先建立链接。
  2. TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP 尽最大努力交付,即不保证可靠交付。并且因为tcp 可靠,面向连接,不会丢失数据因此适合大数据量的交换。
  3. TCP 是面向字节流,UDP 面向报文,并且网络出现拥塞不会使得发送速率降低(因此会出现丢包,对实时的应用比如 IP 电话和视频会议等)。
  4. TCP 只能是 1 对 1 的,UDP 支持 1 对 1,1 对多。
  5. TCP 的首部较大为 20 字节,而 UDP 只有 8 字节。
  6. TCP 是面向连接的可靠性传输,而 UDP 是不可靠的。

4、WebSocket 的实现和应用

(1) 什么是 WebSocket?

WebSocket 是 HTML5 中的协议,支持持久连续,http 协议不支持持久性连接。Http1.0和 HTTP1.1 都不支持持久性的链接,HTTP1.1 中的 keep-alive,将多个http 请求合并为1 个

(2) WebSocket 是什么样的协议,具体有什么优点?

HTTP 的生命周期通过 Request 来界定,也就是 Request 一个 Response,那么在Http1.0协议中,这次 Http 请求就结束了。在 Http1.1 中进行了改进,是的有一个connection:Keep-alive,也就是说,在一个 Http 连接中,可以发送多个 Request,接收多个Response。但是必须记住,在 Http 中一个 Request 只能对应有一个 Response,而且这个Response是被动的,不能主动发起。

WebSocket 是基于 Http 协议的,或者说借用了 Http 协议来完成一部分握手,在握手阶段与 Http 是相同的。我们来看一个 websocket 握手协议的实现,基本是2 个属性,upgrade,connection。

基本请求如下:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

多了下面 2 个属性:

Upgrade:webSocket
Connection:Upgrade

告诉服务器发送的是websocket

Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

5、HTTP 请求的方式

HTTP 请求方式一共有 9 种,分别为 POST 、GET 、HEAD、PUT 、PATCH 、 OPTIONS 、DELETE 、CONNECT 、 TRACE 。
其中前三种 POST 、GET 、HEAD 是 HTTP 1.0 定义的,后六种 PUT 、PATCH 、 OPTIONS 、DELETE 、CONNECT 、 TRACE 是 HTTP 1.1 定义的。

  • POST :表示向指定资源提交数据,数据包含在请求头中。有可能导致新的资源建立或原有资源修改。 POST 请求是 HTTP 请求中使用最多的一种请求方式。
  • GET :表示请求指定的页面信息,并返回实体内容。
  • HEAD :类似于 GET,只不过返回的响应体中没有具体内容,只有报文头,用于获取报文头。
  • PUT :从客户端向服务器传送的数据取代指定的内容,即向指定的位置上传最新的内容。
  • PATCH :对 PUT 方法的补充,用来对已知资源进行局部更新。
  • OPTIONS :返回服务器针对特殊资源所支持的 HTML 请求方式 或 允许客户端查看服务器的性能。
  • DELETE :请求服务器删除 Request-URL 所标识的资源。
  • CONNECT :HTTP 1.1 中预留给能够将连接改为管道方式的代理服务器。
  • TRACE :回显服务器收到的请求,主要用于测试和诊断。

6、一个图片 url 访问后直接下载怎样实现?

请求的返回头里面,用于浏览器解析的重要参数就是 OSS 的 API 文档里面的返回http头,决定用户下载行为的参数。

下载的情况下:

  1. x-oss-object-type: Normal
  2. x-oss-request-id: 598D5ED34F29D01FE2925F41
  3. x-oss-storage-class: Standard

7、说一下 web Quality(无障碍)

能够被残障人士使用的网站才能称得上一个易用的(易访问的)网站。残障人士指的是那些带有残疾或者身体不健康的用户。
使用 alt 属性:

<img src="person.jpg" alt="this is a person"/>

有时候浏览器会无法显示图像。具体的原因有:

  • 用户关闭了图像显示
  • 浏览器是不支持图形显示的迷你浏览器
  • 浏览器是语音浏览器(供盲人和弱视人群使用)
  • 如果您使用了 alt 属性,那么浏览器至少可以显示或读出有关图像的描述。

8、几个很实用的 BOM 属性对象方法?

(1)什么是 Bom?

Bom 是浏览器对象。

(2) 有哪些常用的 Bom 属性呢?

1. location 对象

  • location.href-- 返回或设置当前文档的 URL
  • location.search – 返回 URL 中的查询字符串部分。例如 http://www.baidu.com/baidu.php?id=5&name=baidu 返回包括(?)后面的内容?id=5&name=baidu
  • location.hash – 返回 URL#后面的内容,如果没有#,返回空
  • location.host – 返回 URL 中的域名部分,例如 www.baidu.com
  • location.hostname – 返回 URL 中的主域名部分,例如 baidu.com
  • location.pathname – 返回 URL 的域名后的部分。例如 http://www.baidu.com/xhtml/ 返回/xhtml/
  • location.port – 返回 URL 中的端口部分。例如 http://www.baidu.com:8080/xhtml/ 返回8080
  • location.protocol – 返回 URL 中的协议部分。例如 http://www.baidu.com:8080/xhtml/ 返回(//)前面的内容 http:
  • location.assign – 设置当前文档的 URL
  • location.replace() – 设置当前文档的 URL,并且在 history 对象的地址列表中移除这个URL location.replace(url);
  • location.reload() – 重载当前页面

2. history 对象

  • history.go() – 前进或后退指定的页面数 history.go(num);
  • history.back() – 后退一页
  • history.forward() – 前进一页

3. Navigator 对象

  • navigator.userAgent – 返回用户代理头的字符串表示(就是包括浏览器版本信息等的字符串)
  • navigator.cookieEnabled – 返回浏览器是否支持(启用)cookie

4. window 对象

  • alert(str):用于向用户展示一些用户不可控的警告信息
  • confirm(str):用于向用户展示一段信息并确认结果
  • prompt(str,str): 用于向用户展示一段信息并收集用户输入结果
  • print(): 显示打印对话框(等同与点击浏览器菜单栏打印选项)
  • find(): 显示查找对话框(等同与点击浏览器菜单栏查找选项)

9、说一下 HTML5 drag api

让一个元素被拖拽需要添加 draggable 属性,再加上全局事件处理函数如下

<p id="p1" draggable="true">This element is draggable.p>
  • dragstart:事件主体是被拖放元素,在开始拖放被拖放元素时触发,。
  • darg:事件主体是被拖放元素,在正在拖放被拖放元素时触发。
  • dragenter:事件主体是目标元素,在被拖放元素进入某元素时触发。
  • dragover:事件主体是目标元素,在被拖放在某元素内移动时触发。
  • dragleave:事件主体是目标元素,在被拖放元素移出目标元素是触发。
  • drop:事件主体是目标元素,在目标元素完全接受被拖放元素时触发。
  • dragend:事件主体是被拖放元素,在整个拖放操作结束时触发

10、说一下 http2.0

首先补充一下,http 和 https 的区别,相比于 http,https 是基于 ssl 加密的http 协议

简要概括:http2.0 是基于 1999 年发布的 http1.0 之后的首次更新。

  • 提升访问速度:相比HTTP1.0,请求资源所需时间更少,访问速度更快。
  • 允许多路复用:多路复用允许同时通过单一的HTTP/2连接发送多重请求-响应信息。改善了在HTTP1.1中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制(连接数量),超过限制会被阻塞。
  • 二进制分帧:HTTP2.0会将所有的传输信息分割为更小的信息或者帧,并对他们进行二进制编码。
  • 首部压缩:使报头更紧凑,更快速传输,有利于移动网络环境。
  • 服务器端推送(server push):还没有收到浏览器的请求,服务器就把各种资源推送给浏览器。 比如,浏览器只请求了index.html,但是服务器把index.html、style.css、example.png全部发送给浏览器。

11、HTTP状态码

1. 下面是常见的 HTTP 状态码:

  • 200 - 请求成功
  • 301 - 资源(网页等)被永久转移到其它URL
  • 404 - 请求的资源(网页等)不存在
  • 500 - 内部服务器错误

2. HTTP 状态码分类

HTTP 状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型。响应分为五类:信息响应(100–199),成功响应(200–299),重定向(300–399),客户端错误(400–499)和服务器错误 (500–599):

分类 分类描述
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误

3. HTTP状态码列表:

状态码 状态码英文名称 中文描述
100 Continue 继续。客户端应继续其请求
101 Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议
200 OK 请求成功。一般用于GET与POST请求
201 Created 已创建。成功请求并创建了新的资源
202 Accepted 已接受。已经接受请求,但未处理完成
203 Non-Authoritative Information 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本
204 No Content 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
205 Reset Content 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域
206 Partial Content 部分内容。服务器成功处理了部分GET请求
300 Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303 See Other 查看其它地址。与301类似。使用GET和POST请求查看
304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
305 Use Proxy 使用代理。所请求的资源必须通过代理访问
306 Unused 已经被废弃的HTTP状态码
307 Temporary Redirect 临时重定向。与302类似。使用GET请求重定向
400 Bad Request 客户端请求的语法错误,服务器无法理解
401 Unauthorized 请求要求用户的身份认证
402 Payment Required 保留,将来使用
403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求
404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
405 Method Not Allowed 客户端请求中的方法被禁止
406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求
407 Proxy Authentication Required 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权
408 Request Time-out 服务器等待客户端发送的请求时间过长,超时
409 Conflict 服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突
410 Gone 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置
411 Length Required 服务器无法处理客户端发送的不带Content-Length的请求信息
412 Precondition Failed 客户端请求信息的先决条件错误
413 Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息
414 Request-URI Too Large 请求的URI过长(URI通常为网址),服务器无法处理
415 Unsupported Media Type 服务器无法处理请求附带的媒体格式
416 Requested range not satisfiable 客户端请求的范围无效
417 Expectation Failed 服务器无法满足Expect的请求头信息
500 Internal Server Error 服务器内部错误,无法完成请求
501 Not Implemented 服务器不支持请求的功能,无法完成请求
502 Bad Gateway 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求
505 HTTP Version not supported 服务器不支持请求的HTTP协议的版本,无法完成处理

12、fetch 发送 2 次请求的原因

之所以会发送2次请求,那是因为我们使用了带预检(Preflighted)的跨域请求。该请求会在发送真实的请求之前发送一个类型为OPTIONS的预检请求。预检请求会检测服务器是否支持我们的真实请求所需要的跨域资源,唯有资源满足条件才会发送真实的请求。

13、 Cookie、sessionStorage、localStorage 的区别

相同点:
存储在客户端

不同点

  1. 生命周期:
    Cookie:可设置失效时间,否则默认为关闭浏览器后失效
    Localstorage:除非被手动清除,否则永久保存
    Sessionstorage:仅在当前网页会话下有效,关闭页面或浏览器后就会被清除
  2. 存放数据:
    Cookie:4k 左右
    Localstorage 和 sessionstorage:可以保存 5M 的信息
  3. http 请求:
    Cookie:每次都会携带在 http 头中,如果使用 cookie 保存过多数据会带来性能问题其他两个:仅在客户端即浏览器中保存,不参与和服务器的通信
  4. 易用性:
    Cookie:需要程序员自己封装,原生的 cookie 接口不友好
    其他两个:即可采用原生接口,亦可再次封装
  5. 应用场景:
    从安全性来说,因为每次 http 请求都回携带 cookie 信息,这样子浪费了带宽,所以cookie应该尽可能的少用,此外 cookie 还需要指定作用域,不可以跨域调用,限制很多,但是用户识别用户登陆来说,cookie还是比storage好用,其他情况下可以用storage,localstorage可以用来在页面传递参数,sessionstorage 可以用来保存一些临时的数据,防止用户刷新页面后丢失了一些参数。

14、cookie 的作用

  • 保存用户登录状态
    例如将用户 id 存储于一个 cookie 内,这样当用户下次访问该页面时就不需要重新登录了,现在很多论坛和社区都提供这样的功能。cookie 还可以设置过期时间,当超过时间期限后,cookie 就会自动消失。因此,系统往往可以提示用户保持登录状态的时间:常见选项有一个月、三个 月、一年等。
  • 跟踪用户行为
    例如一个天气预报网站,能够根据用户选择的地区显示当地的天气情况。如果每次都需要选择所在地是烦琐的,当利用了 cookie 后就会显得很人性化了,系统能够记住上一次访问的地区,当下次再打开该页面时,它就会自动显示上次用户所在地区的天气情况。因为一切都是在后 台完成,所以这样的页面就像为某个用户所定制的一样,使用起来非常方便定制页面。如果网站提供了换肤或更换布局的功能,那么可以使用 cookie 来记录用户的选项,例如:背景色、分辨率等。当用户下次访问时,仍然可以保存上一次访问的界面风格。

15、说一下 web worker

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。

(1) 如何创建 web worker

  • 检测浏览器对于 web worker 的支持性
if (typeof(Worker) !== "undefined") {
  // Yes! Web worker support!
  // Some code.....
} else {
  // Sorry! No Web Worker support..
}
  • 创建 Web Worker 文件
    该脚本存储在 “demo_workers.js” 文件中,通常 web worker 不用于这种简单的脚本,而是用于 CPU 密集型任务。
let i = 0;

function timedCount() {
  i ++;
  postMessage(i); //用于将消息发送回 HTML 页面。
  setTimeout("timedCount()",500);
}

timedCount();
  • 创建 Web Worker 对象
    以下代码行检查 worker 是否已存在,如果不存在,它会创建一个新的 web worker 对象并运行 “demo_workers.js” 中的代码:
if (typeof(w) == "undefined") {
  w = new Worker("demo_workers.js");
}

完整

<!DOCTYPE html>
<html>
<body>

<p>Count numbers: <output id="result"></output></p>
<button onclick="startWorker()">Start Worker</button>
<button onclick="stopWorker()">Stop Worker</button>

<script>
let w;

function startWorker() {
  if (typeof(w) == "undefined") {
    w = new Worker("demo_workers.js");
  }
  //向 web worker 添加一个 "onmessage" 事件侦听器。
  //当 Web Worker 发布消息时,将执行事件侦听器中的代码。来自 Web Worker 的数据存储在 event.data 中。
  w.onmessage = function(event) {
    document.getElementById("result").innerHTML = event.data;
  };
}

function stopWorker() {
  w.terminate();
  w = undefined;
}
</script>

</body>
</html>

16、对 HTML 语义化标签的理解

HTML5 语义化标签是指正确的标签包含了正确的内容,结构良好,便于阅读,比如nav表示导航条,类似的还有 article、header、footer 等等标签。

语义化的优点:

  • 在没CSS样式的情况下,页面整体也会呈现很好的结构效果
  • 代码结构清晰,易于阅读,
  • 利于开发和维护 方便其他设备解析(如屏幕阅读器)根据语义渲染网页。
  • 有利于搜索引擎优化(SEO),搜索引擎爬虫会根据不同的标签来赋予不同的权重

17、iframe 是什么?有什么缺点?

定义: iframe 元素会创建包含另一个文档的内联框架

提示: 可以将提示文字放在 < iframe> < /iframe> 之间,来提示某些不支持iframe 的浏览器

缺点:

  • 会阻塞主页面的 onload 事件
  • 搜索引擎无法解读这种页面,不利于 SEO
  • iframe 和主页面共享连接池,而浏览器对相同区域有限制所以会影响性能。

18、Doctype 作用?严格模式与混杂模式如何区分?它们有何意义?

Doctype 声明于文档最前面,告诉浏览器以何种方式来渲染页面,这里有两种模式,严格模式和混杂模式。

  • 严格模式的排版和 JS 运作模式是 以该浏览器支持的最高标准运行。
  • 混杂模式,向后兼容,模拟老式浏览器,防止浏览器无法兼容页面。

19、Cookie 如何防范 XSS 攻击

XSS(跨站脚本攻击)是指攻击者在返回的 HTML 中嵌入 javascript 脚本,为了减轻这些攻击,需要在 HTTP 头部配上,set-cookie:

  • httpOnly-这个属性可以防止 XSS,它会禁止 javascript 脚本来访问cookie
  • secure - 这个cookie只能用https协议发送给服务器,用http协议是不发送的

20、Cookie 和 session 的区别

HTTP 是一个无状态协议,因此 Cookie 的最大的作用就是存储; sessionId 用来唯一标识用户。

  1. session 在服务器端,cookie 在客户端(浏览器)
  2. session 默认被存在在服务器的一个文件里(不是内存)
  3. session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id)
  4. session 可以放在 文件、数据库、或内存中都可以。
  5. 用户验证这种场合一般会用 session
  6. cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行COOKIE 欺骗考虑到安全应当使用 session。
  7. session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用 COOKIE

21、一句话概括 RESTFUL

就是用 URL 定位资源,用 HTTP对服务器端的资源进行操作,实现服务器端上资源的“表现层状态转移”

22、讲讲 viewport 和移动端布局

具体可以参考下面这篇文章
https://github.com/forthealllight/blog/issues/13

上面文章总结: 常用的自适应解决方案包括媒体查询、百分比、rem和vw/vh等。

1. px和视口

像素:我们在js或者css代码中使用的px单位就是指的是css像素,物理像素也称设备像素,只与设备或者说硬件有关,同样尺寸的屏幕,设备的密度越高,物理像素也就越多。

  • css像素:为web开发者提供,在css中使用的一个抽象单位
  • 物理像素:只与设备的硬件密度有关,任何设备的物理像素都是固定的

视口:广义的视口,是指浏览器显示内容的屏幕区域,狭义的视口包括了布局视口、视觉视口和理想视口

  1. 布局视口:pc网页在移动端的默认布局为980px
  2. 视觉视口:浏览器内看到的网站的显示区域,用户可以通过缩放来查看网页的显示内容,从而改变视觉视口
  3. 理想视口:在移动设备中就是指设备的分辨率,给定设备物理像素的情况下,最佳的“布局视口”
(1) 在移动端中,理想视口或者说分辨率跟物理像素之间有什么关系呢?

DPR(Device pixel ratio)设备像素比

1 DPR = 物理像素/分辨率
1 CSS像素 = 物理像素/分辨率  (不缩放的情况下)
(2) px与自适应

在pc端的布局视口通常情况下为980px,移动端以iphone6为例,分辨率为375 * 667,也就是说布局视口在理想的情况下为375px。比如现在我们有一个750px * 1134px的视觉稿,那么在pc端,一个css像素可以如下计算:

PC端: 1 CSS像素 = 物理像素/分辨率 = 750 / 980 =0.76 px
iphone6:1 CSS像素 = 物理像素 /分辨率 = 750 / 375 = 2 px

不同的移动设备分辨率不同,也就是1个CSS像素可以表示的物理像素是不同的,因此如果在css中仅仅通过px作为长度和宽度的单位,造成的结果就是无法通过一套样式,实现各端的自适应

2、媒体查询

使用@media媒体查询可以针对不同的媒体类型定义不同的样式,特别是响应式页面,可以针对不同屏幕的大小,编写多套样式,从而达到自适应的效果

但是媒体查询的缺点也很明显,如果在浏览器大小改变时,需要改变的样式太多,那么多套样式代码会很繁琐。

3、百分比

为了了解百分比布局,首先要了解的问题是:

(1)css中的子元素中的百分比(%)到底是谁的百分比?
  1. height或width,是相对于子元素的直接父元素的height或width
  2. top和bottom 、left和right
    子元素的top和bottom如果设置百分比,则相对于直接非static定位(默认定位)的父元素的高度
    子元素的left和right如果设置百分比,则相对于直接非static定位(默认定位的)父元素的宽度
  3. padding或margin:子元素的padding或者margin如果设置百分比,不论是垂直方向或者是水平方向,都相对于直接父亲元素的width,而与父元素的height无关
  4. border-radius:如果设置border-radius为百分比,则是相对于自身的宽度
(2)百分比单位布局应用

前端最全面试题整理(持续更新)_第2张图片

(3)百分比单位缺点
  1. 计算困难,如果我们要定义一个元素的宽度和高度,按照设计稿,必须换算成百分比单位。
  2. 各个属性中如果使用百分比,相对父元素的属性并不是唯一的。比如width和height相对于父元素的width和height,而margin、padding不管垂直还是水平方向都相对比父元素的宽度、border-radius则是相对于元素自身等等,造成我们使用百分比单位容易使布局问题变得复杂。

4、自适应场景下的rem解决方案

rem是一个灵活的、可扩展的单位,由浏览器转化像素并显示。与em单位不同,rem单位无论嵌套层级如何,都只相对于浏览器的根元素(HTML元素)的font-size。当页面的size发生变化时,只需要改变font-size的值,那么以rem为固定单位的元素的大小也会发生响应的变化。默认情况下,html元素的font-size为16px,所以:

 1 rem = 16px
 
 html{ font-size: 62.5% }    //为了计算方便,通常可以将html的font-size设置62.5%

 1 rem = 10px     //上述情况下

但是在使用时,如果我们使用rem单位,每次都要把设计稿的px计算之后转换为rem,这样就很麻烦,我们可以在使用的时候依旧用px单位,然后把px再转化为rem

转换的插件:px2rem-loader postcss-loader

5、通过vw/vh来实现自适应

  • vw 相对于视窗的宽度,视窗宽度是100vw
  • vh 相对于视窗的高度,视窗高度是100vh
  • vmin vw和vh中的较小值
  • vmax vw和vh中的较大值

vw单位换算

  1. 如果要将px换算成vw单位,对于iphone6/7 375*667的分辨率
1px = (1/375)*100 vw
  1. postcss-px-to-viewport插件

23、click 在 ios 上有 300ms 延迟,原因及如何解决?

原因:2007年苹果发布首款Iphone上ios搭载的safari,采用了双击缩放的方案。

手机端浏览器不能区分用户的单机操作还是双击操作,所以设置了300ms的延迟时间,用来判断用户是点击还是双击。浏览器会在捕获用户第一次单击时,开启300ms定时,若300ms捕获不到第二次单继,则判断用户就是单击操作;若在300ms内,用户有第二次单继操作,则对区域进行缩放操作

解决方法:

  1. 禁止缩放
<meta name="viewport" content="width=device-width,user-scalable=no">
  1. css属性解决

touch-action:none那么当触控事件发生在元素上时,不进行任何操作——即不会出现滑动和缩放的效果

touch-action:none; //
  1. 使用FastClick插件

原理: 在检测到touchend事件的时候,会通过DOM自定义事件立即发出模拟一个click事件,并把300ms之后发出的click事件阻止掉。

但是使用FastClick,就会发现一个缺点: 在某些ios上,点击输入框启动键盘,触点不是很灵敏,必须重压或者长按才能成功唤启,快速点击是不会唤起软键盘的

解决: 在引用fastClick模块后,重写focus方法

24、addEventListener 参数

addEventListener(event, function, useCapture)

event 指定事件名;function 指定要事件触发时执行的函数;useCapture 指定事件是否在捕获或冒泡阶段执行。

25、http 常用请求头

  1. Accept 可接受的响应内容类型(Content-Types)。
    Accept: application/json 浏览器可以接受服务器回发的类型为 application/json。
    Accept: / 代表浏览器可以处理所有类型,(一般浏览器发给服务器都是发这个)。

  2. Accept-Charset 可接受的字符集

  3. Accept-Encoding 可接受的响应内容的编码方式。
    Accept-Encoding: gzip, deflate 浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate),(注意:这不是只字符编码)。

  4. Accept-Language 可接受的响应内容语言列表。
    Accept-Language:zh-CN,zh;q=0.9 浏览器申明自己接收的语言。

  5. Accept-Datetime 可接受的按照时间来表示的响应内容版本

  6. Authorization 用于表示 HTTP 协议中需要认证资源的认证信息

  7. Cache-Control 用来指定当前的请求/回复中的,是否使用缓存机制。

  8. Connection 客户端(浏览器)想要优先使用的连接类型
    Connection: keep-alive 当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。
    Connection: close 代表一个Request完成后,客户端和服务器之间用于传输HTTP数据的TCP连接会关闭, 当客户端再次发送Request,需要重新建立TCP连接。

  9. Cookie 由服务器通过Set-Cookie设置,用来存储一些用户信息以便让服务器辨别用户身份的

  10. Content-Length 以 8 进制表示的请求体的长度

  11. Content-MD5 请求体的内容的二进制 MD5 散列值(数字签名),以Base64 编码的结果

  12. Content-Type 请求体的 MIME 类型 (用于 POST 和PUT 请求中)

  13. Date 发送该消息的日期和时间(以 RFC 7231 中定义的"HTTP 日期"格式来发送)

  14. Expect 表示客户端要求服务器做出特定的行为

  15. From 发起此请求的用户的邮件地址

  16. Host 表示服务器的域名以及服务器所监听的端口号。如果所请求的端口是对应的服务的标准端口(80),则端口号可以省略。

  17. If-Match 仅当客户端提供的实体与服务器上对应的实体相匹配时,才进行对应的操作。主要用于像 PUT 这样的方法中,仅当从用户上次更新某个资源后,该资源未被修改的情况下,才更新该资源。

  18. If-Modified-Since 允许在对应的资源未被修改的情况下返回304 未修改

  19. If-None-Match 允许在对应的内容未被修改的情况下返回304 未修改(304 Not
    Modified ),参考 超文本传输协议的实体标记

  20. If-Range 如果该实体未被修改过,则向返回所缺少的那一个或多个部分。否则,返回整个新的实体

  21. If-Unmodified-Since仅当该实体自某个特定时间以来未被修改的情况下,才发送回应。

  22. Max-Forwards 限制该消息可被代理及网关转发的次数。

  23. Origin 发起一个针对跨域资源共享的请求(该请求要求服务器在响应中加入一个 Access-Control-Allow-Origin 的消息头,表示访问控制所允许的来源)。

  24. Pragma 与具体的实现相关,这些字段可能在请求/回应链中的任何时候产生。

  25. Proxy-Authorization 用于向代理进行认证的认证信息。

  26. Range 用于断点续传 bytes=0-5 指定第一个字节的位置和最后一个字节的位置。用于告诉服务器自己想取对象的哪部分。始。

  27. Referer 当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器我是从哪个页面链接过来的,服务器籍此可以获得一些信息用于处理

  28. TE 浏览器预期接受的传输时的编码方式:可使用回应协议头Transfer-Encoding 中的值(还可以使用"trailers"表示数据传输时的分块方式)用来表示浏览器希望在最后一个大小为0 的块之后还接收到一些额外的字段。

  29. User-Agent User-Agent:Mozilla/…,告诉HTTP服务器, 客户端使用的操作系统和浏览器的名称和版本

  30. Upgrade 要求服务器升级到一个高版本协议。

  31. Via 告诉服务器,这个请求是由哪些代理发出的。

  32. Warning 一个一般性的警告,表示在实体内容体中可能存在错误。

26、http 常用响应头

  1. Cache-Control(对应请求中的Cache-Control)

  2. Content-Type
    Content-Type: text/html;charset=UTF-8 告诉客户端,资源文件的类型,还有字符编码,客户端通过utf-8对资源进行解码,然后对资源进行html解析。通常我们会看到有些网站是乱码的,往往就是服务器端没有返回正确的编码。

  3. Content-Encoding
    Content-Encoding:gzip 告诉客户端,服务端发送的资源是采用gzip编码的,客户端看到这个信息后,应该采用gzip对资源进行解码。

  4. Date
    Date: Tue, 03 Apr 2020 03:52:28 GMT 这个是服务端发送资源时的服务器时间,GMT是格林尼治所在地的标准时间。http协议中发送的时间都是GMT的,这主要是解决在互联网上,不同时区在相互请求资源的时候,时间混乱问题。

  5. Server
    Server:Tengine/1.4.6 这个是服务器和相对应的版本,只是告诉客户端服务器信息。

  6. Transfer-Encoding
    Transfer-Encoding:chunked 这个响应头告诉客户端,服务器发送的资源的方式是分块发送的。一般分块发送的资源都是服务器动态生成的,在发送时还不知道发送资源的大小,所以采用分块发送,每一块都是独立的,独立的块都能标示自己的长度,最后一块是0长度的,当客户端读到这个0长度的块时,就可以确定资源已经传输完了。

  7. Expires
    Expires:Sun, 1 Jan 1994 01:00:00 GMT 这个响应头也是跟缓存有关的,告诉客户端在这个时间前,可以直接访问缓存副本,很显然这个值会存在问题,因为客户端和服务器的时间不一定会都是相同的,如果时间不同就会导致问题。所以这个响应头是没有Cache-Control:max-age=*这个响应头准确的,因为max-age=date中的date是个相对时间,不仅更好理解,也更准确。

  8. Last-Modified
    Last-Modified: Dec, 26 Dec 2019 17:30:00 GMT 所请求的对象的最后修改日期(按照 RFC 7231 中定义的“超文本传输协议日期”格式来表示)

  9. Connection
    Connection:keep-alive 这个字段作为回应客户端的Connection:keep-alive,告诉客户端服务器的tcp连接也是一个长连接,客户端可以继续使用这个tcp连接发送http请求。

  10. Etag
    ETag: “637060cd8c284d8af7ad3082f209582d” 就是一个对象(比如URL)的标志值,就一个对象而言,比如一个html文件,如果被修改了,其Etag也会别修改,所以,ETag的作用跟Last-Modified的作用差不多,主要供WEB服务器判断一个对象是否改变了。比如前一次请求某个html文件时,获得了其 ETag,当这次又请求这个文件时,浏览器就会把先前获得ETag值发送给WEB服务器,然后WEB服务器会把这个ETag跟该文件的当前ETag进行对比,然后就知道这个文件有没有改变了。

  11. Refresh
    Refresh: 5; url=http://baidu.com 用于重定向,或者当一个新的资源被创建时。默认会在5秒后刷新重定向。

  12. Access-Control-Allow-Origin
    Access-Control-Allow-Origin: * 号代表所有网站可以跨域资源共享,如果当前字段为那么Access-Control-Allow-Credentials就不能为true
    Access-Control-Allow-Origin: www.baidu.com 指定哪些网站可以跨域资源共享

  13. Access-Control-Allow-Methods
    Access-Control-Allow-Methods:GET,POST,PUT,DELETE 允许哪些方法来访问

  14. Access-Control-Allow-Credentials
    Access-Control-Allow-Credentials: true 是否允许发送cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。如果access-control-allow-origin为*,当前字段就不能为true

  15. Content-Range
    Content-Range: bytes 0-5/7877 指定整个实体中的一部分的插入位置,它也指示了整个实体的长度。在服务器向客户返回一个部分响应,它必须描述响应覆盖的范围和整个实体长度。

27、 强缓存和协商缓存

缓存类型 获取资源形式 状态码 发送请求到服务器
强缓存 从缓存取 200(from cache) 否,直接从缓存取
协商缓存 从缓存取 304(not modified) 是,通过服务器来告知缓存是否可用

强缓存相关字段有 expires,cache-control。如果 cache-control 与expires 同时存在的话,cache-control 的优先级高于 expires。

协商缓存相关字段有 Last-Modified/If-Modified-Since,Etag/If-None-Match

28、 强缓存、协商缓存什么时候用哪个

强缓存是利用http头中的Expires和Cache-Control两个字段来控制的,用来表示资源的缓存时间。强缓存中,普通刷新会忽略它,但不会清除它,需要强制刷新。浏览器强制刷新,请求会带上Cache-Control:no-cache和Pragma:no-cache

协商缓存就是由服务器来确定缓存资源是否可用,所以客户端与服务器端要通过某种标识来进行通信,从而让服务器判断请求资源是否可以缓存访问。

普通刷新会启用弱缓存,忽略强缓存。只有在地址栏或收藏夹输入网址、通过链接引用资源等情况下,浏览器才会启用强缓存,这也是为什么有时候我们更新一张图片、一个js文件,页面内容依然是旧的,但是直接浏览器访问那个图片或文件,看到的内容却是新的。

参考:https://segmentfault.com/a/1190000008956069
前端最全面试题整理(持续更新)_第3张图片

29、前端web性能优化

  • 降低请求量:合并资源,减少 HTTP 请求数,minify / gzip 压缩,webP,lazyLoad。
  • 加快请求速度:预解析 DNS,减少域名数,并行加载,CDN 分发。
  • 缓存:HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存localStorage。
  • 渲染:JS/CSS 优化,加载顺序,服务端渲染,pipeline。

30、 GET 和 POST 的区别

  • get 参数通过 url 传递,post 放在 request body 中。
  • get 请求在 url 中传递的参数是有长度限制的,而 post 没有。
  • get 比 post 更不安全,因为参数直接暴露在 url 中,所以不能用来传递敏感信息。
  • get 请求只能进行 url 编码,而 post 支持多种编码方式
  • get 请求会浏览器主动 cache,而POST不会,除非手动设置。
  • get 请求参数会被完整保留在浏览历史记录里,而 post 中的参数不会被保留。
  • GET 和 POST 本质上就是 TCP 链接,并无差别。但是由于 HTTP 的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
  • GET 产生一个 TCP 数据包;POST 产生两个 TCP 数据包。

31、301 和 302 的区别

301 代表永久性转移(Permanently Moved)
302 代表暂时性转移(Temporarily Moved )

参考:https://blog.csdn.net/grandPang/article/details/47448395

301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取(用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)——这是它们的共同点。

他们的不同在于。301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址;302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。

什么时候进行301或者302跳转呢?

当一个网站或者网页24—48小时内临时移动到一个新的位置,这时候就要进行302跳转

使用301跳转的场景:

1)域名到期不想续费(或者发现了更适合网站的域名),想换个域名。

2)在搜索引擎的搜索结果中出现了不带www的域名,而带www的域名却没有收录,这个时候可以用301重定向来告诉搜索引擎我们目标的域名是哪一个。

3)空间服务器不稳定,换空间的时候。

为什么尽量要使用301跳转?

从网站A(网站比较烂)上做了一个302跳转到网站B(搜索排名很靠前),这时候有时搜索引擎会使用网站B的内容,但却收录了网站A的地址,这样在不知不觉间,网站B在为网站A作贡献,网站A的排名就靠前了

301跳转对查找引擎是一种对照驯良的跳转编制,也是查找引擎能够遭遇的跳转编制,它告诉查找引擎,这个地址弃用了,永远转向一个新地址,可以转移新域名的权重。而302重定向很容易被搜索引擎误认为是利用多个域名指向同一网站,那么你的网站就会被封掉,罪名是“利用重复的内容来干扰Google搜索结果的网站排名”。

32、状态码 304 和 200当客户端第一次请求服务器资源,服务器成功返回资源,这时状态码为200。

当客户第一次请求服务器资源,服务器成功返回资源,这时状态码为200。所以,状态码为200的数据包往往包含用户从服务器获取的数据。

每个资源请求完成后,通常会被缓存在客户端,并会记录资源的有效时间和修改时间。当客户再次请求该资源,客户端首先从缓存中查找该资源。如果该资源存在,并且在有效期,则不请求服务器,就不会产生对应的请求数据包。

如果不在有效期,客户端会请求服务器,重新获取。服务器会判断修改时间,如果没有修改过,就会返回状态码304,告诉客户端该资源仍然有效,客户端会直接使用缓存的资源。针对304的响应,渗透人员可以分析对应的请求包,获取资源路径。如果该资源不限制访问,就可以直接请求获取。否则,就需要进行Cookie劫持,进行获取。

33、如何画一个三角形

div {
	width:0px;
	height:0px;
	border-top:10px solid red;
	border-right:10px solid transparent;
	border-bottom:10px solid transparent;
	border-left:10px solid transparent;
}

34、HTML5 新增的元素

  • 为了更好的实践 web 语义化,增加了 header,footer,nav,aside,section 等语义化标签
  • 为了增强表单,为 input 增加了 color,emial,data ,range 等类型
  • 在存储方面,提供了 sessionStorage,localStorage,和离线存储,通过这些存储方式方便数据在客户端的存储和获取
  • 在多媒体方面规定了音频和视频元素audio 和vedio
  • 另外还有地理定位,canvas 画布,拖放,多线程编程的 web worker 和websocket 协议。

35、在地址栏里输入一个 URL,到这个页面呈现出来,中间会发生什么?

  1. 首先在浏览器中输入URL

  2. 查找缓存:浏览器先查看浏览器缓存-系统缓存-路由缓存中是否有该地址页面,如果有则显示页面内容。如果没有则进行下一步。

    • 浏览器缓存:浏览器会记录DNS一段时间,因此,只是第一个地方解析DNS请求;
    • 操作系统缓存:如果在浏览器缓存中不包含这个记录,则会使系统调用操作系统, 获取操作系统的记录(保存最近的DNS查询缓存);
    • 路由器缓存:如果上述两个步骤均不能成功获取DNS记录,继续搜索路由器缓存;
    • ISP缓存:若上述均失败,继续向ISP搜索。
  3. DNS域名解析:浏览器向DNS服务器发起请求,解析该URL中的域名对应的IP地址。DNS服务器是基于UDP的,因此会用到UDP协议。

  4. 建立TCP连接:解析出IP地址后,根据IP地址和默认80端口,和服务器建立TCP连接

  5. 发起HTTP请求:浏览器发起读取文件的HTTP请求,,该请求报文作为TCP三次握手的第三次数据发送给服务器

  6. 服务器响应请求并返回结果:服务器对浏览器请求做出响应,并把对应的html文件发送给浏览器

  7. 关闭TCP连接:通过四次挥手释放TCP连接

  8. 浏览器渲染:客户端(浏览器)解析HTML内容并渲染出来,浏览器接收到数据包后的解析流程为:

    • 构建DOM树:词法分析然后解析成DOM树(dom tree),是由dom元素及属性节点组成,树的根是document对象
    • 构建CSS规则树:生成CSS规则树(CSS Rule Tree)
    • 构建render树:Web浏览器将DOM和CSSOM结合,并构建出渲染树(render tree)
    • 布局(Layout):计算出每个节点在屏幕中的位置
    • 绘制(Painting):即遍历render树,并使用UI后端层绘制每个节点
  9. JS引擎解析过程:调用JS引擎执行JS代码(JS的解释阶段,预处理阶段,执行阶段生成执行上下文,VO,作用域链、回收机制等等)

    • 创建window对象:window对象也叫全局执行环境,当页面产生时就被创建,所有的全局变量和函数都属于window的属性和方法,而DOM Tree也会映射在window的doucment对象上。当关闭网页或者关闭浏览器时,全局执行环境会被销毁。
    • 加载文件:完成js引擎分析它的语法与词法是否合法,如果合法进入预编译
    • 预编译:在预编译的过程中,浏览器会寻找全局变量声明,把它作为window的属性加入到window对象中,并给变量赋值为’undefined’;寻找全局函数声明,把它作为window的方法加入到window对象中,并将函数体赋值给他(匿名函数是不参与预编译的,因为它是变量)。而变量提升作为不合理的地方在ES6中已经解决了,函数提升还存在。
    • 解释执行:执行到变量就赋值,如果变量没有被定义,也就没有被预编译直接赋值,在ES5非严格模式下这个变量会成为window的一个属性,也就是成为全局变量。string、int这样的值就是直接把值放在变量的存储空间里,object对象就是把指针指向变量的存储空间。函数执行,就将函数的环境推入一个环境的栈中,执行完成后再弹出,控制权交还给之前的环境。JS作用域其实就是这样的执行流机制实现的。

36、HTTP2.0 的特性

参考:http://www.javashuo.com/article/p-asqnzngi-mg.html

  1. 二进制分帧

    • 对性能优化的贡献:二进制分帧主要是为下文中的各类特性提供了基础。它能把一个数据划分封装为更小更便捷的数据。首先是在单连接多资源方式中,减小了服务端的连接压力,内存占用更少,连接吞吐量更大。 另外一方面,因为TCP连接的减小而使网络拥塞状态得以改善,同时慢启动时间的减小。使拥塞和丢包恢复的速度更快
  2. 首部压缩
    对性能优化的贡献: 使报头更紧凑,更快速传输,有利于移动网络环境。减小每次通信的数据量,使网络拥塞状态得以改善

  3. 流量控制

    • 流量基于HTTP连接的每一跳进行,而非端到端的控制
    • 流量控制基于窗口更新帧进行,即接收方广播本身准备接收某个数据流的多少字节,以及对整个连接要接收多少个字节。
    • 流量控制有方向性,即接收方可能根据本身的状况为没个流乃至整个连接设置任意窗口大小
    • 流量控制能够由接收方禁用,包括针对个别的流和针对整个连接。
    • 帧的类型决定了流量控制是否适用于帧,目前只有DATA帧服从流量控制,全部其余类型的帧并不会消耗流量控制窗口的空间。这保证了重要的控制帧不会被流量控制阻塞
  4. 多路复用

    • 在HTTP1.1中,浏览器客户端在同一时间,针对同一域名下的请求有必定数量的限制。超过限制数目的请求会被阻塞。而HTTP2.0中的多路复用优化了这一性能。
    • 基于二进制分帧层,HTTP2.0能够在共享TCP连接的基础上同时发送请求和响应。HTTP消息被分解为独立的帧,而不破坏消息自己的语义,交错发出去,在另外一端根据流标识符和首部将他们从新组装起来。
    • 对性能优化的贡献
      • 能够并行交错的发送请求和响应,这些请求和响应之间互不影响
      • 只使用一个连接便可并行发送多个请求和响应
      • 消除没必要要的延迟,从而减小页面加载的时间
      • 没必要再为绕过HTTP1.x限制而多作不少工做
  5. 请求优先级

    • 把HTTP消息分为不少独立帧以后,就能够经过优化这些帧的交错和传输顺序进一步优化性能。
    • 客户端明确指定优先级,服务端能够根据这个优先级做为交互数据的依据,好比客户端优先设置为.css>.js>.jpg。服务端按此顺序返回结果更加有利于高效利用底层链接,提升用户体验。然而,在使用请求优先级时应注意服务端是否支持请求优先级,是否会引发队首阻塞问题,好比高优先级的慢响应请求会阻塞其余资源的交互。
    • 对性能优化的贡献:服务器能够根据流的优先级控制资源分配(CPU、内存、宽带),而在响应数据准备好以后,优先将最高优先级的帧发送给客户端。浏览器能够在发现资源时当即分派请求,指定每一个流的优先级,让服务器决定最优的响应次序。这样请求就不用排队了,既节省了时间,又最大限度的利用了每一个链接。
  6. 服务器推送

    • HTTP2.0新增的一个强大的新功能,就是服务器能够对一个客户端请求发送多个响应。服务器向客户端推送资源无需客户端明确的请求。
    • 对性能优化的贡献:服务端推送是一种在客户端请求以前发送数据的机制。在HTTP2.0中,服务器能够对一个客户端的请求发送多个响应。若是一个请求是由你的主页发送的,服务器可能会响应主页内容、logo以及样式表,由于他知道客户端会用到这些东西。这样不但减轻了数据传送冗余步骤,也加快了页面响应的速度,提升了用户体验

37、cache-control 的值有哪些

cache-control 是一个通用消息头字段被用于 HTTP 请求和响应中,通过指定指令来实现缓存机制,这个缓存指令是单向的,常见的取值有 private、no-cache、max-age、must-revalidate 等,默认为 private。

38、浏览器在生成页面的时候,会生成那两颗树?

构造两棵树,DOM 树和 CSSOM 规则树,
当浏览器接收到服务器相应来的 HTML 文档后,会遍历文档节点,生成DOM树,CSSOM 规则树由浏览器解析 CSS 文件生成。

39、csrf 和 xss 的网络攻击及防范

  • CSRF(Cross Site Request Forgery,跨站请求伪造),字面理解意思就是在别的站点伪造了一个请求。专业术语来说就是在受害者访问一个网站时,其Cookie 还没有过期的情况下,攻击者伪造一个链接地址发送受害者并欺骗让其点击,从而形成CSRF 攻击。
  • XSS, 即为(Cross Site Scripting), 中文名为跨站脚本, 是发生在目标用户的浏览器层面上的,当渲染 DOM 树的过程成发生了不在预期内执行的 JS 代码时,就发生了XSS 攻击。大多数 XSS 攻击的主要方式是嵌入一段远程或者第三方域上的JS 代码。实际上是在目标网站的作用域下执行了这段 JS 代码。

防范:

  • XSS 防御的总体思路是:对输入(和 URL 参数)进行过滤,对输出进行编码。也就是对提交的所有内容进行过滤,对 url 中的参数进行过滤,过滤掉会导致脚本执行的相关内容;然后对动态输出到页面的内容进行 html 编码,使脚本无法在浏览器中执行。虽然对输入过滤可以被绕过,但是也还是会拦截很大一部分的 XSS 攻击。
  • 防御 CSRF 攻击主要有三种策略:验证 HTTP Referer 字段;在请求地址中添加token 并验证;在 HTTP 头中自定义属性并验证。

40、知道 304 吗,什么时候用 304?

304:如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个304 状态码。

41、cookie 有哪些字段可以设置

前端最全面试题整理(持续更新)_第4张图片

  1. Name: Cookie名称,一旦创建不可修改
  2. Value: Cookie值
  3. Domain: Cookie在哪个域是有效的,也就是决定向该域发送请求时是否携带此Cookie,对子域名生效
  4. Path: 可以访问此 cookie 的页面路径。 比如 domain 是 abc.com,path 是/test,那么只有/test 路径下的页面可以读取此 cookie,对子路径生效。
  5. Expires/Max-age: 均为Cookie的有效期,Expires是该Cookie被删除的时间戳,不设置则默认关闭时删除,Max-age是Cookie的有效期,表示多少秒后失效,0立即失效,-1关闭页面时失效。
  6. Size: Cookie的大小
  7. HttpOnly: 值为true或false,设为true时,不允许document.cookie去更改这个值,这个值在document.cookie中也不可见,但请求仍会携带
  8. Secure: Cookie的安全属性,设置为true,则浏览器只会在https和SSL等安全协议中传输此cookie,不会在不安全的http协议中传输
  9. SameSite: 限制第三方cookie,表示Cookie不随着跨域请求发送,减少安全风险
    • Strict: 最为严格,完全禁止第三方cookie,跨站点时任何情况都不发送cookie
    • Lax: 稍稍放宽,大多数情况不发送第三方cookie,但导航到目标网址的get请求除外
    • None: 网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。
  10. Priority: 优先级,chrome的提案,定义了三种优先级,Low/Medium/High,cookie数量超出限制时低优先级会被优先清除

42、 cookie 有哪些编码方式?

encodeURI()

43、HTML5 和 CSS3 用的多吗?你了解它们的新属性吗?有在项目中用过吗?

html5:

  1. 标签增删
    • 8 个语义元素 header section footer aside nav main article figure
    • 内容元素 mark 高亮 progress 进度
    • 新的表单控件 calander date time email url search
    • 新的 input 类型 color date datetime datetime-local email
    • 移除过时标签 big font frame frameset
  2. canvas 绘图,支持内联 SVG。支持 MathML
  3. 多媒体 audio video source embed track
  4. 本地离线存储,把需要离线存储在本地的文件列在一个 manifest 配置文件5)web 存储。localStorage、SessionStorage

css3:

  1. CSS3边框如border-radius,box-shadow,border-image;
  2. CSS3背景如background-size,background-origin等;
  3. CSS3文字效果:text-shadow、word-wrap
  4. CSS3 2D,3D 转换如 transform 等;
  5. CSS3 动画如 animation 等
    参考:https://www.cnblogs.com/xkweb/p/5862612.html

二、CSS

1、说一下css盒子模型

标准盒模型中,盒子宽度等于内容区宽度,如下所示
前端最全面试题整理(持续更新)_第5张图片

IE盒模型中,盒子宽度等于内容区宽度、内边距和边框三部分的总和,如下所示
前端最全面试题整理(持续更新)_第6张图片

可以借助CSS3的box-sizing属性切换盒模型,如下所示
前端最全面试题整理(持续更新)_第7张图片

2、画一条 0.5px 的线

  1. 移动端,采用meta viewport的方式
<meta name="viewport" 
      content="width=device-width, 
      initial-scale=0.5, minimum-scale=0.5, maximum-scale=0.5"/>

这样子就能缩放到原来的0.5倍,如果是1px那么就会变成0.5px。,但这样也意味着UI需要按2倍图的出,整体面面的单位都会放大一倍。

  1. 采用 transform: scale()的方式
transform: scale(0.5,0.5);
  1. 使用boxshadow

    设置box-shadow的第二个参数为0.5px,表示阴影垂直方向的偏移为0.5px

<style>
.boxshadow {
    height: 1px;
    background: none;
    box-shadow: 0 0.5px 0 #000;
}
style>
<p>box-shadow: 0 0.5px 0 #000p>

<div class="boxshadow">div>
  1. 采用 border-image的方式
    首先需要自己制作一个0.5像素的线条作为线条背景图片;

  2. 使用background-image结合SVG的方式

    使用svg的line元素画线,stroke表示描边颜色,默认描边宽度stroke-width=“1”,由于svg的描边等属性的1px是物理像素的1px,相当于高清屏的0.5px
    这样在Chrome在能很好的显示,但在firefox挂了,究其原因是因为firefox的background-image如果是svg的话只支持命名的颜色,如"black"、"red"等,如果把上面代码的svg里面的#000改成black的话就可以显示出来,但是这样就很不灵活了。因此,只能把svg转成base64的形式,最终如下:

.hr.svg {
    background: url("data:image/svg+xml;utf-8,
                    <svg xmlns='http://www.w3.org/2000/svg' 
                    width='100%' height='1px'>
                      <line x1='0' y1='0' x2='100%' y2='0' stroke='#000'>
                      line>
                    svg>");
    //使用base64来使得支持firefox
    background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMDAlJyBoZWlnaHQ9JzFweCc+PGxpbmUgeDE9JzAnIHkxPScwJyB4Mj0nMTAwJScgeTI9JzAnIHN0cm9rZT0nIzAwMCc+PC9saW5lPjwvc3ZnPg==");
}

3、link 标签和 import 标签的区别

  • link 属于 html 标签,而@import 是 css 提供的
  • 页面被加载时,link 会同时被加载,而@import 引用的 css 会等到页面加载结束后加载。
  • link 是 html 标签,因此没有兼容性,而@import 只有 IE5 以上才能识别。
  • link 方式样式的权重高于@import 的。

4、css动画transition 和 animation 的区别

Animation 和 transition 大部分属性是相同的,他们都是随时间改变元素的属性值,他们的主要区别是

  • transition 需要触发一个事件才能改变属性,而 animation 不需要触发任何事件的情况下才会随时间改变属性值

  • transition 只有两个状态:开始状态 和 结束状态 。animation 可能是多个状态, 有帧的概念 。

  • animation 控制动效上要比 transition 强,因为它具备一些控制动效的属性,比如“播放次数”、“播放方向”、“播放状态”等。

5、Flex 布局

  • 任何一个容器都可以指定为 Flex 布局
  • 设为 Flex 布局以后,子元素的float、clear和vertical-align属性将失效

以下6个属性设置在容器上

  1. flex-direction :决定主轴的方向 ,取值 row | row-reverse | column | column-reverse
  2. flex-wrap : 如果一条轴线排不下,如何换行 ,取值 nowrap | wrap | wrap-reverse
  3. flex-flowflex-directionflex-wrap的简写形式,默认值为row nowrap
  4. justify-content:项目在主轴上的对齐方式,取值 flex-start | flex-end | center | space-between | space-around;
  5. align-items:项目在交叉轴上如何对齐,取值flex-start | flex-end | center | baseline | stretch
  6. align-content:多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用,取值flex-start | flex-end | center | space-between | space-around | stretch

以下6个属性设置在项目上

  1. order:项目的排列顺序。数值越小,排列越靠前,默认为0
  2. flex-grow :项目的放大比例,默认为0,即如果存在剩余空间,也不放大。如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。
  3. flex-shrink: 项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小
  4. flex-basis:定义了在分配多余空间之前,项目占据的主轴空间。览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。它可以设为跟width或height属性一样的值
  5. flex:flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选;该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。
  6. align-self:允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。取值auto | flex-start | flex-end | center | baseline | stretch

6、BFC(块级格式化上下文,用于清除浮动,防止margin 重叠等)

简单的理解:这是一个独立的渲染区域,规定了内部如何布局,并且这个区域的子元素不会影响到外面的元素,其中比较重要的布局规则有内部box 垂直放置

生成BFC的元素

  • 根元素(html)
  • 浮动元素(元素的float值不是none)
  • 绝对定位元素(元素的position为absolute或者fixed)
  • 行内块元素(元素的display为inline-block)
  • 表格单元格(元素的display为table-cell,HTML表格单元格默认为该值,表格标题(元素的display为table-caption,HTML表格标题默认为该值)row,tbody,thead,tfoot的默认属性)或inline-table)
  • overflow计算值(Computed)不为visible的块元素
  • 弹性元素(display为flex或inline-flex元素的直接子元素)
  • 网格元素(display为grid或inline-grid元素的直接子元素)
  • display值为flow-root的元素

7、垂直居中的方法

  1. margin:auto
position: absolute;
margin: auto;
top: 0;
left: 0;
right: 0;
bottom: 0;
  1. margin 负值法
position: absolute;
top: 50%;
left: 50%;
margin-top: -190px; /*height 的一半*/
margin-left: -240px; /*width 的一半*/

这里也可以将 marin-top 和 margin-left 负值替换成,transform:translateX(-50%)和 transform:translateY(-50%)

  1. table-cell(未脱离文档流的)
    设置父元素的 display:table-cell,并且 vertical-align:middle,这样子元素可以实现垂直居中
  2. flex
    将父元素设置为 display:flex,并且设置 align-items:center;justify-content:center;

8、关于 JS 动画和 css3 动画的差异性

渲染线程分为 main thread (主线程)和 compositor thread(复合线程),如果 css 动画只改变transform和opacity,在Chromium基础上的浏览器中,这时整个 CSS 动画得以在复合线程 完成(而 JS 动画则会在主线程执行,然后触发复合线程进行下一步操作),特别注意的是如果改变transform和opacity是不会 layout(布局) 或者 paint (绘制)的。 如果在CSS动画或JS动画都触发了paint或layout时,需要main thread进行Layer树的重计算,这时CSS动画或JS动画都会阻塞后续操作

区别:
1、js动画的控制能力比css3动画强;
2、js动画的效果比css3动画丰富;
3、js动画大多数情况下没有兼容性问题,而css3动画有兼容性问题;
4、js动画的复杂度高于css3动画。

js动画

优点:

  1. 灵活,js动画控制能力强,可以在动画过程中对动画进行精细控制,开始、暂停、终止、取消都是可以做到的
  2. 动画效果比css3动画丰富,比如曲线运动,冲击闪烁,视差滚动效果,只有js动画才能完成
  3. js动画大多数情况下没有兼容性问题,而css3动画有兼容性问题

缺点:

  1. js动画的复杂度高于css3
  2. js在动画浏览器的主线程中执行,而主线程还有其他javaScript脚本,样式计算、布局、绘制任务等,对其干扰可能出现阻塞从而出现丢帧的情况
  3. js动画往往需要频繁操作DOM的css属性来实现视觉上的动画效果,这个时候浏览器要不停地执行重绘和重排,这对于性能的消耗是很大的,尤其是在分配给浏览器的内存没那么宽裕的移动端

CSS3动画

优点:

  • 在Chromium基础上的浏览器中,CSS动画不触发layout或pain情况下,浏览器可以对动画进行优化。

缺点:

  1. 运行进程控制较弱,css3动画只能在某些场景下控制动画的暂停与继续,不能在特定的位置添加添加回调函数
  2. 代码冗长。想用 CSS 实现稍微复杂一点动画,最后CSS代码都会变得非常笨重

css动画比js动画流畅的前提

  • js在执行一些复杂的任务
  • css动画比较少或者不触发pain和layout,即重绘和重排,例如通过改变如下属性生成的css动画
    • backface-visibility
    • opacity
    • perspective (设置元素视图)
    • perspective-origin
    • transfrom
  • 部分属性能够启动3D加速和GPU硬件加速,例如使用transform的translateZ进行3D变换时
  • 在Chromium基础上的浏览器中,这个貌似是内核做了优化,当css动画知识改变transfrom和opacity时,整个CSS动画得以在compositor thread完成(而JS动画则会在main thread执行),这样css动画渲染不会影响主线程。

9、说一下块元素和行元素

  • 块元素:独占一行,并且有自动填满父元素,可以设置 margin 和pading 以及高度和宽度
  • 行元素:不会独占一行,width 和 height 会失效,并且在垂直方向的padding 和margin会失效。

10、单行、多行元素的文本省略号

单行

white-space: nowrap; /* 文本超过容器最大宽度不换行(若文本自动显示在一行则不需要这个属性) */
overflow: hidden;  /* 本文超出容器最大宽度的部分不显示 */
text-overflow: ellipsis;  /* 文本超出容器最大宽度时显示三个点 */

多行

overflow:hidden;
display:-webkit-box; /* 让容器变成一个弹性伸缩盒子 */
-webkit-line-clamp:2;  /* 最大显示的文本行数 */
-webkit-box-orient:vertical;  /* 设置或检索伸缩盒对象的子元素纵向排列  */

11、visibility:hidden, opacity:0,display:none

  • opacity:0,透明度为0,不会改变页面布局,并且,如果该元素已经绑定一些事件,如 click 事件,那么点击该区域,也能触发点击事件的
  • visibility:hidden,不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见。不能点击,子孙元素继承visibility:hidden样式,可设置子孙样式visibility:visibile覆盖祖先的visibility:hidden样式,可见的子孙元素和它本身都绑定click事件时,点击子孙元素会触发子孙的click事件,也会冒泡到visibility:hidden元素上
  • display:none,会让元素完全从渲染树中消失,渲染的时候不占据任何空间。不能点击,子孙元素不继承该样式,但是由于display:none元素不渲染,所以子孙不可显示

12、双边距重叠问题(外边距折叠)

多个相邻(兄弟或者父子关系)普通流的块元素垂直方向 marigin 会重叠折叠的结果为:

  • 两个相邻的外边距都是正数时,折叠结果是它们两者之间较大的值。
  • 两个相邻的外边距都是负数时,折叠结果是两者绝对值的较大值。
  • 两个外边距一正一负时,折叠结果是两者的相加的和。

13、 position 属性

  • 固定定位 fixed:
    元素的位置相对于浏览器窗口是固定位置,即使窗口是滚动的它也不会移动。Fixed 定位使元素的位置与文档流无关,因此不占据空间。
  • 相对定位 relative:
    如果对一个元素进行相对定位,它将出现在它所在的位置上。然后,可以通过设置垂直或水平位置,让这个元素“相对于”它的起点进行移动。 在使用相对定位时,无论是否进行移动,元素仍然占据原来的空间。因此,移动元素会导致它覆盖其它框。
  • 绝对定位 absolute:
    绝对定位的元素的位置相对于最近的已定位父元素,如果元素没有已定位的父元素,那么它的位置相对于。absolute 定位使元素的位置与文档流无关,因此不占据空间。
  • 粘性定位 sticky:
    元素先按照普通文档流定位,然后相对于该元素在流中的 flow root(BFC)和containingblock(最近的块级祖先元素)定位。而后,元素定位表现为在跨越特定阈值前为相对定位,之后为固定定位。
  • 默认定位 Static:
    默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者z-index 声明)。
  • inherit: 规定应该从父元素继承 position 属性的值。

14、清除浮动

  • 浮动的设置:css属性float:left/right/none 左浮动/右浮动/不浮动(默认)
  • 浮动的原理:使当前元素脱离普通流,相当于浮动起来一样,浮动的框可以左右移动,直至它的外边缘遇到包含框或者另一个浮动框的边缘
  • 浮动的影响:对附近的元素布局造成改变,使得布局混乱

因为浮动元素脱离了普通流,会出现一种高度坍塌的现象:原来的父容器高度是内部元素撑开的,但是当内部元素浮动后,脱离普通流浮动起来,那父容器的高度就坍塌,变为高度0px。

清除浮动的5种方法

  1. 在浮动元素后使用一个空元素设置 .clear{clear:both;}

  2. 父级div定义overflow:hidden;或 overflow:auto

    • 另外在IE6 中还需要触发 hasLayout ,例如为父元素设置容器宽高或设置 zoom:1
    • 在添加 overflow 属性后,浮动元素又回到了容器层,把容器高度撑起,达到了清理浮动的效果。
  3. 父级div定义height

    • 父级div手动定义height,就解决了父级div无法自动获取到高度的问题,只适合高度固定的布局
  4. 使用邻接元素处理,什么都不做,给浮动元素后面的元素添加clear属性。

  5. 父级div定义伪元素:after和zoom推荐使用这种方法,建议定义公共类,以减少CSS代码

    • 结合:after 伪元素(注意这不是伪类,而是伪元素,代表一个元素之后最近的元素)和IEhack ,可以完美兼容当前主流的各大浏览器,这里的 IEhack 指的是触发hasLayout。 为了IE6和IE7浏览器,要给这个父级div的class添加一条zoom:1;触发haslayout。
.clearfix:after{
  content: "020"; 
  display: block; 
  height: 0; 
  clear: both; 
  visibility: hidden;  
  }

.clearfix {
  /* 触发 hasLayout */ 
  zoom: 1; 
  }

15、CSS 选择器有哪些,优先级呢

内联选择器,ID选择器,class选择器,属性选择器,伪类选择器,元素选择器,通配符选择器,继承选择器等

  1. !important 优先级10000
  2. 内联选择器 优先级1000
  3. ID选择器 优先级100
  4. 类别选择器 优先级10
  5. 属性选择器 优先级10
  6. 伪类 优先级10
  7. 元素选择器 优先级1
  8. 通配符选择器 优先级0
  9. 继承选择器 没有优先级

16、CSS3 中对溢出的处理

使用CSS3 text-overflow 属性
前端最全面试题整理(持续更新)_第8张图片

17、float 的元素,display 是什么

block

18、隐藏页面中某个元素的方法

display:none; visibility:hidden; opacity: 0; position 移到外部,z-index 涂层遮盖等等

19、 三栏布局的实现方式

三栏布局:两边固定,中间自适应

  1. flex 实现: 两边定宽,中间flex:1;首先绘制左右两栏,再绘制中间一栏,主要内容无法最先加载
  2. 流体布局 float+margin: 两边定宽,中间设置margin为两边定宽的值;项目绘制按照左中右排列
  3. BFC:左右浮动,中间添加overflow:hidden或者display: flex,因为BFC不会和浮动元素重叠的规则,主要内容无法最先加载
  4. position定位:左右绝对定位,中间设置margin为两边定宽的值;主要内容可以优先加载
  5. table布局:父级设置display:table,三个子元素设置 display: table-cell,左右两栏设置宽度,中间无需设置;缺点就是无法设置栏间距
  6. 圣杯布局:中间栏的div要写在最前面, 三栏都使用float:left 进行浮动,左右定宽,中间宽度100%,左栏设置margin-left为-100%,右栏的margin-left为其宽度的负值,这时中间沾满100%,需要父元素设置margin为左右两边的宽度,左右两边再设置为相对相位,就可以把中间的盒子完整显示出来
  7. 双飞翼布局:双飞翼布局前两步和圣杯布局一样,只是处理中间栏部分内容被遮挡的问题解决方案不同,只用在中间盒子内部添加一个div,给这个div设置左右margin,即可来避开左右遮挡
  8. grid布局:父元素设置display:grid,子元素设置 grid-template-columns: 200px auto 200px; 有兼容性问题

20、calc 属性

用户动态计算长度值,任何长度值都可以使用 calc()函数计算,需要注意的是,运算符前后都需要保留一个空格,例如:width: calc(100% - 10px)

21、display:table 和本身的 table 有什么区别

display:table 和本身 table 是相对应的,区别在于,display:table 的css 声明能够让一个html 元素和它的子节点像 table 元素一样,使用基于表格的 css 布局,是我们能够轻松定义一个单元格的边界,背景等样式,而不会产生因为使用了 table 那样的制表标签导致的语义化问题。

目前,在大多数开发环境中,已经基本不用table元素来做网页布局了,取而代之的是div+css,那么为什么不用table系表格元素呢?

  1. 用DIV+CSS编写出来的文件k数比用table写出来的要小,不信你在页面中放1000个table和1000个div比比看哪个文件大
  2. table必须在页面完全加载后才显示,没有加载完毕前,table为一片空白,也就是说,需要页面完毕才显示,而div是逐行显示,不需要页面完全加载完毕,就可以一边加载一边显示
  3. 非表格内容用table来装,不符合标签语义化要求,不利于SEO
  4. table的嵌套性太多,用DIV代码会比较简洁

22、z-index

z-index属性只能在设置了position: relative | absolute | fixed的元素和父元素设置了 display: flex属性的子元素中起作用,在其他元素中是不作用的。

23、如果想要改变一个 DOM 元素的字体颜色,不在它本身上进行操作?

可以更改父元素的 color

24、line-height 和 height 的区别

line-height 一般是指布局里面一段文字上下行之间的高度,是针对字体来设置的,height 一般是指容器的整体高度。

25、设置一个元素的背景颜色,背景颜色会填充哪些区域?

background-color 设置的背景颜色会填充元素的 content、padding、border 区域

26、inline-block、inline 和 block 的区别;为什么 img 是inline 还可以设置宽高

  • Block :是块级元素,能设置宽度,高度,margin/padding 水平垂直方向都有效。
  • Inline:设置 width 和 height 无效,margin 在竖直方向上无效,padding 在水平方向垂直方向都有效
  • Inline-block:能设置宽度高度,margin/padding 水平垂直方向 都有效

img确实是行内元素 但它也是置换元素;置换元素就是浏览器根据元素的标签和属性,来决定元素的具体显示内容。置换元素拥有内在尺寸,内置宽高 他们可以设置width/height属性。他们的性质同设置了display:inline-block的元素一致。

27、了解重绘和重排吗,知道怎么去减少重绘和重排吗,让文档脱离文档流有哪些方法

回流:也叫做重排,当我们对 DOM 的修改引发了 DOM 尺寸的变化时,浏览器需要重新计算元素的几何属性,其他元素的几何属性和位置也会因此受到影响,然后再将计算的结果绘制出来。这个过程就是回流

重绘:改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。这个过程就叫重绘

重绘不一定导致回流,回流一定会导致重绘。

减少重绘和回流的方法

  1. 尽量减少table使用,table属性变化使用会直接导致布局重排或者重绘
  2. 当dom元素position属性为fixed或者absolute, 可以通过css形变触发动画效果,此时是不会触发重排的
  3. 不要把 DOM 节点的属性值放在一个循环里当成循环里的变量
  4. 如果需要创建多个DOM节点,可以使用DocumentFragment创建完后一次性的加入document
  5. 可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。

28、两个嵌套的 div,position 都是 absolute,子 div 设置top 属性,那么这个top是相对于父元素的哪个位置定位的

boder的内边缘

29、display

主要取值有 none,block,inline-block,inline,flex 等

/* precomposed values */
display: block;
display: inline;
display: inline-block;
display: flex;
display: inline-flex;
display: grid;
display: inline-grid;
display: flow-root;

/* box generation */
display: none;
display: contents;

/* two-value syntax */
display: block flow;
display: inline flow;
display: inline flow-root;
display: block flex;
display: inline flex;
display: block grid;
display: inline grid;
display: block flow-root;

/* other values */
display: table;
display: table-row; /* all table elements have an equivalent CSS display value */
display: list-item;

/* Global values */
display: inherit;
display: initial;
display: revert;
display: revert-layer;
display: unset;

30、css 布局

六种布局方式总结:圣杯布局、双飞翼布局、Flex 布局、绝对定位布局、表格布局、网格布局。

31、css 预处理器有什么

less,sass 等

三、JavaScript

1、get 请求传参长度的误区

误区:我们经常说 get 请求参数的大小存在限制,而 post 请求的参数大小是无限制的。实际上 HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对get 请求参数的限制是来源与浏览器或 web 服务器,浏览器或 web 服务器限制了 url 的长度。为了明确这个概念,我们必须再次强调下面几点:

  • HTTP 协议 未规定 GET 和 POST 的长度限制
  • GET 的最大长度显示是因为 浏览器和 web 服务器限制了 URI 的长度
  • 不同的浏览器和 WEB 服务器,限制的最大长度不一样
  • 要支持 IE,则最大长度为 2083byte,若只支持 Chrome,则最大长度8182byte

2、补充 get 和 post 请求在缓存方面的区别

  • get 请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。
  • post 不同,post 做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。

3、说一下闭包

闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建一个函数,通过内部函数访问外部函数的局部变量。

闭包中外部函数返回内部函数,也可以返回对象中的函数

闭包的优缺点

优点:

  1. 可以有私有变量的存在
  2. 避免全局污染
  3. 防止私有变量被垃圾回收机制所清除

缺点:

  • 会造成内存泄漏

解决方法:

  • 将内层函数返回到全局的变量设为null,这种方法只能清除基本数据类型

4、说一下类的创建和继承

4.1 ES5类的创建和继承

类的创建(es5):new 一个 function,在这个 function 的 prototype 里面增加属性和方法。
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};

这样就生成了一个 Animal 类,实力化生成对象后,有方法和属性

类的继承

1. 原型链继承: 直接让子类的原型对象指向父类实例。当子类实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承

function Cat(){ }
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name);//cat
console.log(cat.eat('fish')); //cat正在吃:fish
console.log(cat.sleep());//cat正在睡觉!
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true

优点:通过原型链继承的方式,原先存在父类型的实例中的所有属性和方法,现在也能存在于子类型的原型中了
缺点:

  • 由于所有子类实例原型都指向同一个父类实例, 因此对某个子类实例的父类引用类型变量修改会影响所有的子类实例;
  • 在创建子类实例时无法向父类构造传参, 即没有实现super()的功能

2. 构造函数继承: 在子类的构造函数中执行父类的构造函数,并为其绑定子类的this,通过call()函数,改变this指针的指向,让父类的构造函数把成员属性和方法都挂到子类的this上去

function Cat(name){
	Animal.call(this);
	this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name); //Tom
console.log(cat.sleep());//Tom正在睡觉!
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

优点:可以实现多继承,避免实例之间共享一个原型实例,能向父类构造方法传参
缺点:只能继承父类实例的属性和方法,不能继承原型上的属性和方法。

3. 组合继承 相当于构造函数继承和原型链继承的组合体。通过调用父类构造函数,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// Test Code
var cat = new Cat();
console.log(cat.name);//Tom
console.log(cat.sleep());//Tom正在睡觉!
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

优点:可以继承实例属性/方法,也可以继承原型属性/方法;组合继承拥有上面两种方法的优点。同时还能避免上面两种方法的缺点。
缺点:调用了两次父类构造函数,生成了两份实例

4. 寄生式继承 使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。

 let parent5 = {
    name: "parent5",
    friends: ["p1", "p2", "p3"],
    getName: function() {
      return this.name;
    }
  };function clone(original) {
    let clone = Object.create(original); //用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)
    clone.getFriends = function() {
      return this.friends;
    };
    return clone;
  }let person5 = clone(parent5);
​
  console.log(person5.getName());
  console.log(person5.getFriends());

优点:解决了组合继承中每次创建子类实例都执行了两次构造函数

缺点:

  • 原型继承存在的缺点他都存在。
  • 使用寄生式继承为对象添加方法,会由于不能做到方法的复用而降低效率,这一点和构造函数模式类似。

5. 寄生组合继承 使用寄生式继承来继承父类的原型,将结果指定给子类型的原型;砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性

function Parent6() {
    this.name = 'parent6';
    this.play = [1, 2, 3];
  }
   Parent6.prototype.getName = function () {
    return this.name;
  }
  function Child6() {
    Parent6.call(this);
    this.friends = 'child6';
  }function clone (parent, child) {
    // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
    child.prototype = Object.create(parent.prototype);
    child.prototype.constructor = child;
  }clone(Parent6, Child6);
  Child6.prototype.getFriends = function () {
    return this.friends;
  }let person6 = new Child6();
  console.log(person6); //{name: 'parent6', play: [1, 2, 3], friends: 'child6'}
  console.log(person6.getName()); //parent6
  console.log(person6.getFriends()); //child6

优点:

  • 集寄生式继承和组合式继承的优点与一身,实现基本类型继承的最有效方法。
  • 调用了一次父类
  • Child可以向Parent传参
  • 父类方法可以复用
  • 父类的引用属性不会被共享

4.2 ES6类的创建和继承

类的创建

1. 下面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法

   class Box{//类名首字母必须大写,驼峰式命名
   		a=1;//描述改对象的属性值,ES7支持,不需要let或者var
       constructor(a,b){// 构造函数
           console.log(a+b);//8
       }
       play(){
           console.log(this===b)//this 就是通过构造函数实例化对象b,谁调用该方法,this就是谁
       }
   }
   let b=new Box(5,3)  //实例化,当实例化时执行构造函数constructor
   b.play()
   console.log(b.constructor === Box)//true  
    //对象的构造函数就是当前的类名
    
   console.log(b.constructor === Box.prototype.constructor)//true  
   //b是Box类的实例,它的constructor方法就是Box类原型的constructor方法
  • 类名首字母必须大写,驼峰式命名
  • 如果不继承任何类别,意味着自动继承Object,Object就是js所有类别的基类
  • 对象的构造函数就是当前的类名 ,构造函数是通过该类创建对象的唯一入口函数
  • 如果不写constructor构造函数,类别会自动添加这个函数默认没有内容或者执行超类的构造函数
  • 在其他语言中类名和构造函数名称一样,因为这个类需要实例化对象 ,必须先执行构造函数,有些语言的构造函数可以有多个
  • 原生js构造函数只能有一个,并且所有类的构造函数写为construstor函数,这个构造函数实际就是当前的类名

2. 因为js中构造函数就是类名,因此我们可以根据对象的构造函数是否是某个类名来判断它是否属于该类


//通过实例化创建的对象就是一个类,可以根据构造函数判断是否属于该类
	var arr=new Array(1,2,3);
	console.log(arr.constructor.name);//Array
	//单独打印constrctor是一个函数
	//构造函数有一个name属性就是这个类的类名
	console.log(arr.constructor===Array);//true
	
	var date=new Date();
	console.log(date.constructor===Date);//true
	
	var reg=/a/g;
	console.log(reg.constructor===RegExp);//true
	
	var div=document.createElement("div");
	console.log(div.constructor===HTMLDivElement);//true

3. 类中除了函数中的局部变量就是类中的属性,类中的this就是实例化对象

//以下代码只为解释定义一个人类的类,并且去如何使用它,中文编程万万不可以
   class man{
       eyes=2;
       mouse=1;
       ear=2;
       hands=2;
       foots=2;
       name;//创建了一个man的类,有以上属性
       constructor(_name){
          this.name=_name;
       }
       walk(){//man的类有walk() run()  eat() fire()的方法
           console.log(this.name+"走")
       }
       run(){
           console.log(this.name+"跑")
       }
       eat(){
           console.log(this.name+"吃")
       }
       fire(){
           console.log(this.name+"生火")
       }
   }

  var xiaoming=new man("小明");
  //创建一个xioaming的实例化对象,具备这个man的类的所有方法和属性,可以直接使用
  xiaoming.eat();
  xiaoming.run();
  var xiaohua=new man("小花");
  //创建了一个xioahua的实例化对象
  xiaohua.run();
  xiaohua.fire();
  1. 通过构造函数实例化的对象,就是这个类的实例对象(比如上述代码xiaohua就是通过构造函数实例化的对象,它就是这个类的实例对象)
  2. 因此这个实例对象就具备了类所有属性和方法,比如上述代码xiaohua就具备man这个类的所有属性和方法
  3. 这个实例对象就可以调用他自身的方法和属性了
  4. 因为构造函数默认生成实例化对象,因此不能在构造函数中使用return 返回其他内容,构造函数不允许写return
  5. 谁调用方法,在类的函数中this就是那个实例对象

类的继承

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

比如,要继承Parent类,只需要使用extends就可以继承过来Parent的属性和方法了,继承父类后,必须在构造函数中使用super()调用超类的构造函数,超类的是什么,这个就执行的是什么;需要注意的是这里的super就可以理解为调用父类的constructor ,所以里面会传一些和父类相同的参数,而且必须写在最前面

ES6 的继承必须先调用super(),这样会生成一个继承父类的this对象,没有这一步就无法继承父类。这意味着新建子类实例时,父类的构造函数必定会先运行一次

class Child extends Parent{
       constructor(r){
           // 继承父类后,必须在构造函数中调用超类的构造函数
           super();//超类的构造函数,超类是什么,这个就是什么
           //超类的构造函数如果有参数的,在这里子类中必须传入这个需要参数
       }
   }

具体可查看该篇文章https://blog.csdn.net/weixin_44157964/article/details/103933204

5、如何解决异步回调地狱

promise、generator、async/await

6、 说说前端中的事件流

HTML 中与 javascript 交互是通过事件驱动来实现的,例如鼠标点击事件onclick、页面的滚动事件 onscroll 等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念

什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2 级事件流包括下面几个阶段

  • 事件捕获阶段:事件从Document节点自上而下向目标节点传播的阶段
  • 处于目标阶段:真正的目标节点正在处理事件的阶段
  • 事件冒泡阶段:事件从目标节点自下而上向Document节点传播的阶段

什么是冒泡?
冒泡是从下往上执行的,比如有个子事件和父事件,点击子事件后触发父事件,浏览器是默认事件冒泡的

事件委托
就是不在事件当前的dom上进行事件,而是在父级进行事件监听,通过事件冒泡来触发子事件
最常见的就是往ul 上绑定事件来触发 li

addEventListener:addEventListener 是 DOM2 级事件新增的指定事件处理程序的操作,这个方法接收 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。

IE 只支持事件冒泡

7、如何让事件先冒泡后捕获

根据w3c标准,应先捕获再冒泡。若要实现先冒泡后捕获,给一个元素绑定两个addEventListener,其中一个第三个参数设置为false(即冒泡),另一个第三个参数设置为true(即捕获),调整它们的代码顺序,将设置为false的监听事件放在设置为true的监听事件前面即可。

还有一种,在执行捕获时,设置setTimeOut(方法名,0),把它放到下一个宏任务

8、说一下事件委托

事件委托指的是,不在事件的发生地(直接 dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。

举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。

好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。

事件代理在捕获阶段的实际应用

可以在父元素层面阻止事件向子元素传播,也可代替子元素执行某些操作。

9、说一下图片的懒加载和预加载

(1) 懒加载

懒加载:图片的懒加载指的是在长网页中延迟加载图像,是一种很好的优化网页性能的方式。用户滚动到他们之前,可视区域之外的图像是不会加载的。在某种情况下,他还可以帮助减少服务器负载,常适使图片很多,页面很长的电商网站场景中。

为什么要使用懒加载?

  • 可以提升用户体验:我们可以想一下,当用户打开手机淘宝的话,如果页面上全部的图片都需要加载,由于图片
    数量巨大,等待的时间就很长,用户体验就很不好。
  • 减少无用的资源加载:如果用户都没有去查看相应的内容,而我们却需要加载,此时就会增加浏览器的负担。
  • 防止并发资源过多导致阻塞js的加载。

懒加载实现原理

首先先将页面上的图片的src地址设置为空字符串,而图片上的真实路径设置在data-original属性中,当页面
发生滚动时需要去监听scroll事件,在scroll事件的回调中,判断我们的懒加载的图片是否进入可视区域,如
果图片在可视区域的话将src设置为data-original的值,这样就达到了延迟加载的效果。

(2) 预加载

预加载就是将所需的资源提前加载到本地,这样当用户使用到该资源时,就会自动从缓存中取出。

预加载实现方法

  1. 用html标签
<img src="http://pic26.nipic.com/20121213/6168183 0044449030002.jpg" style="display:none"/>

这样当我们进行加载页面时,也会加载src中的图片资源,此时该图片就会缓存到本地,当我们使用到该图片
时,就会自动读取缓存中的内容。

  1. 使用Image对象,在js解析的时候加载
<script src='./myPreload.js'></script>

//myPreload.js文件
var image = new Image()
image.src="http://pic26.nipic.com/20121213/6168183 004444903000 2.jpg"

  1. 使用XMLHTTPRequest对象
    这里我是这样理解的,因为XMLHTTPRequest创建的是异步ajax请求对象,当我们加载完其他内容时,此时我们
    可以使用ajax进行加载资源,此时并不会对页面产生不好的效果。此时当我们加载完毕后,在后面如果我们使用
    到该资源时,就会从缓存中读取出来,加载就会很快。

两种技术的本质:

  • 两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。
  • 懒加载对服务器有一定的缓解压力作用,预加载则会增加服务器压力

参考https://blog.csdn.net/weixin_47450807/article/details/123376938

10、常用的鼠标事件有哪些,mouseover 和 mouseenter 的区别

常用的鼠标事件分为三部分: 鼠标点击事件、鼠标经过事件、鼠标移动事件

鼠标点击事件
鼠标点击事件包括 click(单击)dblclick(双击)mousedown(按下) mouseup(松开)四个;其中 click 事件类型比较常用,而mousedownmouseup 事件类型多用在鼠标拖放、拉伸操作中

鼠标经过事件
mouseover:无论论鼠标指针穿过被选元素或其子元素,都会触发 mouseover 事件;有一个重复触发,冒泡的过程
mouseenter:只有在鼠标指针穿过被选元素时,才会触发 mouseenter 事件;不会冒泡

mouseout:不论鼠标指针离开被选元素还是任何子元素,都会触发 mouseout 事件;有一个重复触发,冒泡的过程
mouseleave:只有在鼠标指针离开被选元素时,才会触发 mouseleave 事件;不会冒泡

鼠标移动事件
mousemove 事件类型是一个实时响应的事件,当鼠标指针的位置发生变化时(至少移动一个像素),就会触发 mousemove 事件。该事件响应的灵敏度主要参考鼠标指针移动速度的快慢以及浏览器跟踪更新的速度

11、JS 的 new 操作符做了哪些事情

  1. 创建一个新对象
  2. 将这个新对象的原型指向构造函数的prototype
  3. 执行构造函数,将this指向新创建的对象
  4. 返回新创建的对象
    前端最全面试题整理(持续更新)_第9张图片

12、bind,apply,call 的区别

通过 apply 和 call 改变函数的 this 指向,他们两个函数的第一个参数都是一样的表示要改变指向的那个对象,第二个参数,apply 是数组,而 call 则是arg1,arg2…这种形式。 call和apply都会会立即执行该函数

通过 bind 改变 this 作用域会返回一个新的函数,这个函数不会马上执行。需要再调用一次

如果使用call、apply、bind时,第一个参数是null,就意味着将函数中this重定向到window

只有apply的参数是array

13、JS 的各种位置,比如 clientHeight,scrollHeight,offsetHeight ,以及scrollTop, offsetTop,clientTop 的区别?

  • clientHeight:表示的是可视区域的高度,不包含 border 和滚动条
  • offsetHeight:表示可视区域的高度,包含了 border 和滚动条
  • scrollHeight:表示了所有区域的高度,包含了因为滚动被隐藏的部分
  • clientTop:表示边框 border 的厚度,在未指定的情况下一般为 0
  • scrollTop:滚动后被隐藏的高度,获取对象相对于由 offsetParent 属性指定的父坐标(css
    定位的元素或 body 元素)距离顶端的高度

前端最全面试题整理(持续更新)_第10张图片

14、JS 拖拽功能的实现

如果要设置物体拖拽,那么必须使用三个事件,并且这三个事件的使用顺序不能颠倒。

  1. onmousedown:鼠标按下事件
  2. onmousemove:鼠标移动事件
  3. onmouseup:鼠标抬起事件

拖拽的基本原理就是根据鼠标的移动来移动被拖拽的元素。鼠标的移动也就是x、y坐标的变化;元素的移动就是style.position的top和left的改变。当然,并不是任何时候移动鼠标都要造成元素的移动,而应该判断鼠标左键的状态是否为按下状态,是否是在可拖拽的元素上按下的。

也可以通过 html5 的拖放(Drag 和 drop)来实现

15、JS异步加载

15.1 为什么要JS异步加载?

因为同步加载存在问题!
JS在默认情况下是以同步模式(又称阻塞模式)加载的,这里“加载”的意思是“解释、执行”。在最新版本的浏览器中,浏览器对于代码请求的资源都是瀑布式的加载,而不是阻塞式的,但是JS的执行总是阻塞的。这会引起什么问题呢?如果在页面中加载一些JS,但其中某个请求迟迟得不到响应,位于此JS后面的JS将无法执行,同时页面渲染也不能继续,用户看到的就是白屏

如果js在标签中,如果JS迟迟无法加载,于是阻塞了后面代码的执行,页面得不到渲染
如果把JS代码放到标签之前(这也是所提倡的页面结构),页面瞬间被渲染,问题似乎解决了,可是…如果我们在引用js代码的后面再写一段js代码,如果上个js请求阻塞了,后面的代码也不会加载,所以问题依然存在:改变JS的加载位置只能改变页面的渲染,JS还是会阻塞。

15.2 异步加载 JS 的方法

  1. defer
    • 等dom文档全部解析完(dom树生成)才会被执行。
  2. async
    • async是HTML5的新属性,该属性规定一旦脚本可用,则会异步执行(一旦下载完毕就会立刻执行)。
    • async属性仅适用于外部脚本(只有在使用src属性时)

15.3 async和defer看起来差不多呀?而且经常一起出现!来辨析一下:

  • 如果没有async和defer属性,那么浏览器会立即执行当前的JS脚本,阻塞后面的脚本;
  • 如果有async属性,加载和渲染后续文档的过程和当前JS的加载与执行并行进行(异步),它是乱序执行的,不管你声明的顺序如何,只要它加载完了就会执行
  • 如果有defer属性,加载后续文档元素的过程和JS的加载是并行进行(异步)的,但是JS的执行在所有元素解析完成之后进行,而且它是按照加载顺序执行脚本的

15、Ajax 解决浏览器缓存问题

  1. ajax 发送请求前加上 anyAjaxObj.setRequestHeader("If-Modified-Since","0")
  2. ajax 发送请求前加上 anyAjaxObj.setRequestHeader("Cache-Control","no-cache")
  3. URL 后面加上一个随机数: "fresh=" + Math.random()
  4. URL 后面加上时间搓:"nowtime=" + new Date().getTime()
  5. 如果是使用 jQuery,直接这样就可以了 $.ajaxSetup({cache:false})。

16、JS 的节流和防抖

16.1 防抖和节流

  • 防抖是将多次执行变为最后一次执行
  • 节流是将多次执行变为每隔一段时间执行

16.2 防抖和节流的场景

防抖的应用场景很多:

  • 输入框中频繁的输入内容,搜索或者提交信息;
  • 频繁的点击按钮,触发某个事件;
  • 监听浏览器滚动事件,完成某些特定操作;
  • 用户缩放浏览器的resize事件;
    总之,密集的事件触发,我们只希望触发比较靠后发生的事件,就可以使用防抖函数;

节流的应用场景:

  • 监听页面的滚动事件;
  • 鼠标移动事件;
  • 用户频繁点击按钮操作;
  • 游戏中的一些设计;

总之,依然是密集的事件触发,但是这次密集事件触发的过程,不会等待最后一次才进行函数调用,而是会按照一定的频率进行调用

16.3 防抖和节流的实现

  1. lodash、underscore第三方库来实现
  2. 防抖实现
 // 通过闭包实现防抖
      function debounce(fn, delay) {
        let timer = null;
        return function () {
          if (timer !== null) {
            clearTimeout(timer);
          }
          timer = setTimeout(() => {
             // 箭头函数没有自己的this,改变指向,使其指向input。同时执行fn函数
            fn.call(this);
          }, delay);
        };
      }
  • 第一次点击了输入框,输入内容后,开启定时器,timer会得到一个值。(这个值代表着等待队列);
  • 第二次输入内容时,会进入if判断(已经有值,不为null)。执行清除定时器。重新生成一个定时器,timer得到一个 新- 的值;
  • 如果在3秒内,不停的输入内容,则会不停地触发事件——清除上一个定时器,开启新的定时器;
  • 直到最后一次输入结束,最后一个定时器等待3秒后,输出结果。

可以看出,防抖的关键点在于:利用闭包,在返回的函数体里,通过不断开启和清除定时器,在限定时间内不断点击,仍然只执行最后一次

  1. 节流实现
 function throttle(fn, wait) {
        // 获取初始时间
        let start = 0;
        return function () {
          let now = new Date().getTime();
          // 判断前后时间戳的差值
          if (now - start < wait) return;
          fn();
          // 重新设置初始时间
          start = now;
        };
      }
  • 间隔时间 wait
  • 初始时间 start-time
  • 最新时间 new-time (事件被触发时计算)
  • 当2者时间差 < wait , 不能执行事件函数
  • 当2者时间差 > = wait , 执行操作

17、JS 中的垃圾回收机制

必要性:由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript 程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript 的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。

这段话解释了为什么需要系统需要垃圾回收,JS 不像 C/C++,他有自己的一套垃圾回收机制(Garbage Collection)。JavaScript 的解释器可以检测到何时程序不再使用一个对象了,当他确定了一个对象是无用的时候,他就知道不再需要这个对象,可以把它所占用的内存释放掉了

垃圾回收常用的方法:标记清除、计数引用

  • 标记清除

    • 标记阶段:给所有的活动对象打上标记。
    • 清除阶段:回收没有标记的对象。

    缺点:碎片化。经过多轮空间分配和垃圾回收后,堆从一整片完整的空间被划分为了很多不连续的碎片,这就导致另外一个问题:分配速度受限。因为每次重新分配空间时,都要遍历所有空闲链表,去寻找足够大的分块,最坏的情况下,每次进行分配都要遍历整个空闲链表。
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 计数引用
    记录每个值的被引用的次数,当它被初始化,并被赋值给一个变量,引用为 1;如果另外一个变量引用了它,引用加 1;如果一个变量不再引用它,引用减 1。当一个值的引用数变为 0,那就说明它再没法被访问了,因此它成为垃圾,可以放心回收

    缺点:无法回收循环引用的对象,造成内存泄漏

function problem() {
    let objectA = new Object();
    let objectB = new Object();
    objectA.someOtherObject = objectB;
    objectB.anotherObject = objectA;
}

在上面这个例子中,objectA 和 objectB 通过各自的属性相互引用,于是它们的引用数都是 2。函数运行结束,objectA 和 objectB 的引用次数各自减 1,但因为它们还引用着彼此,所以它们的引用次数永远不会置为 0,也就无法被回收

还有一些别的GC算法:复制算法、标记 - 整理算法、分代式垃圾回收、增量式垃圾回收 具体参考https://www.jianshu.com/p/20364ba1d7a6

18、eval

它的功能是将对应的字符串解析成 JS 并执行,如果传入的参数不是字符串,它会原封不动地将其返回

应该避免使用eval,因为非常消耗性能(2次,一次解析成 JS,一次执行)

eval缺点:

  • 降低性能:网上一些文章甚至说 eval() 会拖慢性能 10 倍,倒没有10倍这么夸张
  • 安全问题:因为它的动态执行特性,给被求值的字符串赋予了太大的权力,于是大家担心可能因此导致 XSS 等攻击。
  • 调试困难:eval 就像一个黑盒,其执行的代码很难进行断点调试。

19、如何理解前端模块化

前端模块化就是复杂的文件编程一个一个独立的模块,比如 JS 文件等等,分成独立的模块有利于重用(复用性)和维护(版本迭代),这样会引来模块之间相互依赖的问题,所以有了 commonJS 规范,AMD,CMD 规范等等,以及用于 JS 打包(编译等处理)的工具 webpack

20、说一下 CommonJS、ES6模块化、AMD、 CMD、UMD

JS模块化的演变经历了一个漫长的过程,从最初的CommonJS ,到后来的AMD和CMD,再到今天的ES6模块化方案

20.1 CommonJs

开始于服务器端的模块化,同步定义的模块化,每个模块都是一个单独的作用域,模块输出,modules.exports,模块加载 require()引入模块

为什么不在浏览器也是用CommonJS ?

CommonJS的 require 语法是同步的,当我们使用require 加载一个模块的时候,必须要等这个模块加载完后,才会执行后面的代码。如果知道这个事实,那我们的问题也就很容易回答了。NodeJS 是服务端,使用 require 语法加载模块,一般是一个文件,只需要从本地硬盘中读取文件,它的速度是比较快的。但是在浏览器端就不一样了,文件一般存放在服务器或者CDN上,如果使用同步的方式加载一个模块还需要由网络来决定快慢,可能时间会很长,这样浏览器很容易进入“假死状态”。所以才有了后面的AMD和CMD模块化方案,它们都是异步加载的,比较适合在浏览器端使用。

1. 导出
CommonJs中使用module.exports导出变量及函数,也可以导出任意类型的值

// 导出一个对象
module.exports = {
    name: "hh",
    age: 18
}
// 导出任意值
module.exports.name = "hh"

导出也可以省略module关键字,直接写exports导出也可以

exports.name = "hh"

混合导出exportsmodule.exports可以同时使用,不会存在问题。

exports.name = "hh"
module.exports.age = 18

2. 导入

// index.js
module.exports.name = "hh"
module.exports.age = 18

let data = require("./index.js")
console.log(data) // { name: "hh", age: 18 }

重复导入:不管是CommonJs还是Es Module都不会重复导入,就是只要该文件内加载过一次这个文件了,我再次导入一次是不会生效的。

let data = require("./index.js")
let data = require("./index.js") // 不会再执行了

动态导入: CommonJs支持动态导入,什么意思呢,就是可以在语句中,使用require语法

let lists = ["./index.js", "./config.js"]
lists.forEach((url) => require(url)) // 动态导入

if (lists.length) {
    require(lists[0]) // 动态导入
}

导入值的变化 CommonJs导入的值是拷贝的,所以可以修改拷贝值,但这会引起变量污染,一不小心就重名

3. 总结
CommonJs解决了变量污染,文件依赖等问题,上面我们也介绍了它的基本语法,它可以动态导入(代码发生在运行时),不可以重复导入。

20.2 Es Module

在Es Module中导出分为两种,单个导出(export)、默认导出(export default),单个导出在导入时不像CommonJs一样直接把值全部导入进来了,Es Module中可以导入我想要的值。那么默认导出就是全部直接导入进来,当然Es Module中也可以导出任意类型的值。

1. 导出

// 导出变量
export const name = "hh"

// 导出函数也可以
export function fn() {}
export const test = () => {}


// 如果有多个的话
const name = "hh"
const sex = "male"
export { name, sex }

混合导出: 可以使用exportexport default同时使用并且互不影响,只需要在导入时地方注意,如果文件里有混合导入,则必须先导入默认导出的,在导入单个导入的值

export const name = "hh"
export const age = 18

export default {
    fn() {}msg: "hello hh"
}

2. 导入
Es Module使用的是import语法进行导入。如果要单个导入则必须使用花括号{}注意:这里的花括号跟解构不一样

// index,js
export const name = "hh"
export const age = 24

import { name, age } from './index.js'
console.log(name, age) // "hh" 24

// 如果里面全是单个导出,我们就想全部直接导入则可以这样写
import * as all from './index.js'
console.log(all) // {name: "hh", age: 24}

==混合导入:==混合导入,则该文件内用到混合导入,import语句必须先是默认导出,后面再是单个导出,顺序一定要正确否则报错

// index,js
export const name = "hh"
export const age = 24
export default {
    msg: "hh"
}

import msg, { name, age } from './index.js'
console.log(msg) // { msg: "hh" }

导入值的变化: export导出的值是值的引用,并且内部有映射关系,这是export关键字的作用。而且导入的值,不能进行修改也就是只读状态。

Es Module是静态: 就是Es Module语句``import只能声明在该文件的最顶部,不能动态加载语句,Es Module`语句运行在代码编译时

if (true) {
	import xxx from 'XXX' // 报错
}

3. 总结
Es Module也是解决了变量污染问题,依赖顺序问题,Es Module语法也是更加灵活,导出值也都是导出的引用,导出变量是可读状态,这加强了代码可读性

20.3 AMD和require.js

AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。这里介绍用require.js实现AMD规范的模块化:用require.config()指定引用路径等,用define()定义模块,用require()加载模块。

20.4 CMD和sea.js

CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范其实是在sea.js推广过程中产生的。

20.5 UMD

UMD是AMD和CommonJS的糅合
AMD模块以浏览器第一的原则发展,异步加载模块。CommonJS模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。这迫使人们又想出另一个更通用的模式UMD (Universal Module Definition)。希望解决跨平台的解决方案。
UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

20.6 CommonJs和Es6 Module的区别

  • CommonJs导出值是拷贝,可以修改导出的值,这在代码出错时,不好排查引起变量污染;Es Module导出是引用值之前都存在映射关系,并且值都是可读的,不能修改
  • CommonJs可以动态加载语句,代码发生在运行时;Es Module是静态的,不可以动态加载语句,只能声明在该文件的最顶部,代码发生在编译时
  • CommonJs混合导出,当导出引用对象时之前的导出就被覆盖了;Es Module混合导出,单个导出,默认导出,完全互不影响
  • 循环加载时,CommonJs属于加载时执行。即脚本代码在require的时候就会全部执行,一旦出现某个模块被“循环加载”,就只输出已经执行的部分,还未执行的部分不会输出;es6模块时动态引用的,只要两个模块之间存在某个引用,代码就能构执行

参考https://juejin.cn/post/6938581764432461854#heading-18

21、对象克隆的简单实现

1. json暴力转化

通过JSON.stringify() JSON.parse() 将对象转为字符串之后在转为对象。

var obj = {name:'123'}
var obj2 = JSON.parse(JSON.stringify(obj)

这种简单粗暴的方式有局限性,当值为undefined、function、symbol会在转换过程中被忽略。

2. es6解构赋值

var obj = {name:'123',age:13};
var obj2 = {...obj}

只能深度拷贝对象的第一层,如果对象中的属性也是对象的话,没有办法进行深度拷贝的。

3. for in 循环遍历对象

  var obj = {
    name: "小明",
    age: 20
  }
  var obj1 = {}
  for (var key in obj) {
    obj1[key] = obj[key]
  }
  console.log(obj1);

同样的,只能深度拷贝对象的第一层,如果对象中的属性也是对象的话,没有办法进行深度拷贝的。

4. Object.assign() 对象的合并
利用Object.assign(), 第一个参数必须是空对象

var obj = {name:'123',age:13};
var obj2 = Object.assign({},obj1);

只能深度拷贝对象的第一层,如果对象中的属性也是对象的话,没有办法进行深度拷贝的。

5. 利用循环和递归的方式

function deepClone(obj, newObj) {
    var newObj = newObj || {};
    for (let key in obj) {
        if (typeof obj[key] == 'object') {
            newObj[key] = (obj[key].constructor === Array) ? [] : {}
            deepClone(obj[key], newObj[key]);
        } else {
            newObj[key] = obj[key]
        }
    }
    return newObj;
}

在循环递归中需要注意设置临界值(typeof obj[key] == ‘object’),否则会造成死循环
循环递归可以处理对象中嵌套数组或对象的问题。相当于第三种方法的优化

22、实现一个 once 函数,传入函数参数只执行一次

function ones(func){
	var tag=true;
	return function(){
		if(tag==true){
			func.apply(null,arguments);
			tag=false;
		}
		return undefined
	}
}

23、将原生的 ajax 封装成 promise

var myNewAjax=function(url){
	return new Promise(function(resolve,reject){
		var xhr = new XMLHttpRequest();
		xhr.open('get',url);
		xhr.send(data);
		xhr.onreadystatechange = function(){
			if(xhr.status == 200 && readyState == 4){
				var json=JSON.parse(xhr.responseText);
				resolve(json)
			}else if(xhr.readyState == 4 && xhr.status != 200){
				reject('error');
			}
		}
	})
}

24、JS 监听对象属性的改变 Object.defineProperty和Proxy

1. 在 ES5 中可以通过 Object.defineProperty 来实现已有属性的监听

defineProperty 只能劫持对象的某一个属性,不能对整个对象进行劫持,如果需要监听某一个对象的所有属性,需要遍历对象的所有属性并对其进行劫持来进行监听

let val = user.name
Object.defineProperty(user,'name',{
	set(newValue){
	val = newValue
	},
	get(){
	return val
	}
})

console.log(user.name) //调用get
user.name = 1  //调用set

缺点:如果 id 不在 user 对象中,则不能监听 id 的变化;,数组对象是个特例,监听不到改变

2. 在 ES6 中可以通过 Proxy 来实现

const proxyData = new Proxy(data, {
 get(target, prop) {
   console.log('%c 调用get', 'color: green')
   return Reflect.get(target, prop)
 },
 set(target, prop, value) {
   console.log('%c 调用set', 'color: blue')
   return Reflect.set(target, prop, value) // Reflect通过代理对象更改目标对象的属性值
 }
})

console.log('proxyData.name -> ', proxyData.name) // Jane
console.log('data.name -> ', data.name) // Jane
proxyData.name = 'Jian'
console.log('proxyData.name -> ', proxyData.name) // Jian
console.log('data.name -> ', data.name) // Jian 

前端最全面试题整理(持续更新)_第11张图片

设置代理对象的属性后,原始对象和代理对象都发生了变化,但是获取原始对象的属性不会触发 getter ,只有访问代理对象的属性才能触发 getter,但是触发了 getter 和 setter 都是去给原始对象获取属性值和设置属性值,因为这里的 target 就是这个原始对象

3. Object.defineProperty和Proxy的区别

  1. defineProperty 是对属性劫持,proxy 是对对象代理

  2. defineProperty 无法监听对象新增属性,proxy 可以

    • 当对象新增属性的时候,defineProperty 没有对新增的属性进行劫持,自然就不会监听到对象新增的属性变化,而proxy 是对对象进行代理,自然就能监听到对象属性的新增
  3. defineProperty 无法监听对象删除属性,proxy 可以

    • proxy 有专门针对属性删除的方法 deleteProperty(和set,get同级),可以在对象属性被删除时触发
  4. defineProperty 监听数组的操作需要重载原型方法,proxy 不需要对数组的方法进行重载

    • 数组的 push、pop、shift、unshift、splice、sort,reverse是无法触发 set 方法的;
    • Vue 中能对数组的这些方法监听到是因为 Vue 源码对数组的这些方法进行了重载
  5. proxy 是浏览器支持的原生 API 直接通过浏览器的引擎就可以执行,defineProperty 是循环遍历对象属性的方式来进行监听,自然会比 proxy 对整个对象进行监听的方式要耗性能

参考https://blog.csdn.net/weixin_43443341/article/details/124041094

25、如何实现一个私有变量,用 getName 方法可以访问,不能直接访问

  1. 使用闭包
        (function (window) {
            var name = '我是私有变量,其他人都找不到我'
            window.getName = function () {
                return name
            }
        })(window)
        console.log(getName()) //'我是私有变量,其他人都找不到我'
        console.log(name) // undefined
        console.log(window.name) //undefined
  1. 构造函数
 function Private(){
  let a='私有变量';
  this.getName=function(){
 	 return a;
 }
}
let obj=new Private();
console.log(obj.a)  //undefine
console.log(obj.getName()); //私有变量
  1. 类构造器

class private {
	  constructor(){
		  let a='class私有';
		  this.getName=function(){
		  	return a;
		  }
	 }
}
let p=new private();
console.log(p.a); //undefine
console.log(p.getName());//class私有

26、= =和===、以及 Object.is 的区别

  • = =:等同,比较运算符,两边值类型不同的时候,先进行类型转换,再比较
  • = = =:恒等,严格比较运算符,不做类型转换,类型不同就是不等;
  • Object.is()是ES6新增的用来比较两个值是否严格相等的方法,与= = =的行为基本一致,不过有两处不同
    • +0不等于-0
    • NaN等于自身
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

27、setTimeout、setInterval 和 requestAnimationFrame 之间的区别

  1. setTimeout
    setTimeout()方法用来指定某个函数或字符串在指定的毫秒数之后执行。它返回一个整数,表示定时器的编号,这个值可以传递给clearTimeout()用于取消这个函数的执行

  2. setInterval

setInterval的用法与setTimeout完全一致,区别仅仅在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行

[注意]:HTML5标准规定,setTimeout的最短时间间隔是4毫秒;setInterval的最短间隔时间是10毫秒,也就是说,小于10毫秒的时间间隔会被调整到10毫秒

  1. requestAnimationFrame
    大多数电脑显示器的刷新频率是60Hz,大概相当于每秒钟重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是1000ms/60,约等于16.6ms

    而setTimeout和setInterval的问题是,它们都不精确。它们的内在运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行

    requestAnimationFrame采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果

使用
requestAnimationFrame的用法与settimeout很相似,只是不需要设置时间间隔而已。requestAnimationFrame使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。它返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame用于取消这个函数的执行

//控制台输出1和0
var timer = requestAnimationFrame(function(){
    console.log(0);
}); 
console.log(timer);//1
cancelAnimationFrame(timer);

优点:

【1】requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率
【2】在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量
【3】requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销

参考https://www.cnblogs.com/xiaohuochai/p/5777186.html

28、自己实现一个 bind 函数

bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用

var obj = {name:"Smiley"};
var greeting = function(str, lang){
    this.value = 'greetingValue';
    console.log("Welcome "+this.name+" to "+str+" in "+lang);
};
var objGreeting = greeting.bind(obj, 'the world'); 

var newObj = new objGreeting('JS');
console.log(newObj.value);
  • 我们通过使用call这个函数,让arguments也可以使用slice这个方法,从而通过slice(1)获取第二个参数以及以后的参数;
  • 之后,把得到的args传入apply函数,作为第二个参数。我们只需要把这个函数接受到的arguments,和之前args拼在一起成为一个数组就可以了。如果不拼,第二次传的参数JS就会是undefined
    arg=arg.concat(Array.prototype.slice.call(newArg));
  • 由于bind函数并不是立即执行,而是要返回一个函数,所以需要把return context.apply(obj,arg)包装在一个函数当中进行返回。
  • 在 new 一个 bind 过生成的新函数的时候,必须的条件是要继承原函数的原型,如果不继承this指向还是obj,newObj.value就会是undefined
Function.prototype.bind=function(obj,arg){
	var arg=Array.prototype.slice.call(arguments,1);
	var context=this;
	var bound=function(newArg){
		arg=arg.concat(Array.prototype.slice.call(newArg));
		return context.apply(obj,arg);
	}
	var F=function(){}
	//这里需要一个寄生组合继承
	F.prototype=context.prototype;
	bound.prototype=new F();
	return bound;
}

参考https://zhuanlan.zhihu.com/p/85438296

29、用 setTimeout 来实现 setInterval

	var timeWorker = {}
	var mySetInterval= function(fn, time) {
	// 定义一个key,来标识此定时器
	var key = Symbol();
	// 定义一个递归函数,持续调用定时器
	  var execute = function(fn, time) {
	     timeWorker[key] = setTimeout(function(){
	        fn();
	        execute(fn, time);
	     }, time)
	   }
	  execute(fn, time);
	  // 返回key
	  return key;
	}
	var myClearInterval = function(key) {
	  if (key in timeWorker) {
	   clearTimeout(timeWorker[key]);
	    delete timeWorker[key];
	  }
	}

	//使用
	let time1 = mySetInterval(() => {console.log(111)}, 3000);
	let time2 = mySetInterval(() => {console.log(222)}, 3000);
	//清除定时器
	setTimeout(() => {
		myClearInterval(time2);
	}, 4000)

30、JS 怎么控制一次加载一张图片,加载完后再加载下一张

  1. 使用promise中的all方法,实现将异步过程转换为阻塞式同步加载
	function loadImage(src) {
		return new Promise(function (resolve, reject) {
			var img = new Image();
			img.src = src; 
			img.onload = function () {
				resolve(img);
			};
			img.onerror = function () {
				reject("错误的地址:" + src);
			};
		});
	}
	var arr = [];
	for (var i = 5; i < 14; i++) {
		arr.push(loadImage("./img/" + i + "-.jpg"));
	}
	Promise.all(arr).then(function (list) {
		list.forEach((item) => {
			console.log(item.src, item.width, item.height);
		});
	});

  1. 使用promise中的async和await方法
	function loadImage(src) {
		return new Promise(function (resolve, reject) {
			var img = new Image();
			img.src = src; 
			img.onload = function () {
				resolve(img);
			};
			img.onerror = function () {
				reject("错误的地址:" + src);
			};
		});
	}
	async function loadAll() {
		var arr = [];
		for (var i = 5; i < 14; i++) {
			var img = await loadImage("./img/" + i + "-.jpg");
			arr.push(img);
		}
		console.log(arr);
	}
	loadAll();

31、 promise、process.nextTick、setTimeout代码的执行顺序

	setTimeout(function () {
		console.log(1);
	}, 0);
	new Promise(function (resolve, reject) {
		console.log(2);
		resolve();
	})
		.then(function () {
			console.log(3);
		})
		.then(function () {
			console.log(4);
		});
	process.nextTick(function () {
		console.log(5);
	});
	console.log(6);

输出 2,6,5,3,4,1

32、如何实现sleep函数

sleep函数作用是让线程休眠,等到指定时间在重新唤起。

  1. while 循环方式
	function sleep(ms) {
		var start = Date.now(),
			expire = start + ms;
		while (Date.now() < expire);
		console.log("1111");
		return;
	}

执行 sleep(1000)之后,休眠了 1000ms 之后输出了 1111。上述循环的方式缺点很明显,容易造成死循环

  1. 通过 promise 来实现
	function sleep(ms) {
		var temple = new Promise((resolve) => {
			console.log(111);
			setTimeout(resolve, ms);
		});
		return temple;
	}
	sleep(500).then(function () {
		console.log(222)
	});

先输出了 111,延迟 500ms 后输出 222

  1. 通过 async 封装
	function sleep(ms) {
		return new Promise((resolve) => setTimeout(resolve, ms));
	}
	async function test() {
		var temple = await sleep(1000);
		console.log(1111);
		return temple;
	}
	test();
  1. 通过 generate 来实现
	function* sleep(ms) {
		yield new Promise(function (resolve, reject) {
			console.log(111);
			setTimeout(resolve, ms);
		});
	}
	sleep(500)
		.next()
		.value.then(function () {
			console.log(2222);
		});

33、 promise

单独开了个文章https://blog.csdn.net/weixin_44157964/article/details/129525061

34、Function._ proto _(getPrototypeOf)是什么?

有4个规则一定要记住,如下:

  • javascript中一切皆对象,函数也属于对象
  • 所有对象都含有__proto__
  • 只有函数才有prototype
  • 所有函数的默认原型都是Object的实例

如果把函数当做对象,那么Function就是它对应的构造函数,所以Function.__proto__==Function.prototype
Object并不是Function的构造函数,所以是false

Function.__proto__==Object.prototype //false
Function.__proto__==Function.prototype//true

35、js中的this指向

this主要分为以下几种

  1. 非严格模式下,全局中this指向window
  2. 对象中的this : 属性中的this指向外层this ;方法中的this指向该对象
    • 对象中属性的this,指的是外层的this,因为这时候对象还没有创建完成
    • 对象中的方法是在对象执行完成以后才调用的.所以this就是当前对象
  3. 回调函数中的this指向window
  4. 事件回调函数中的this被修改成e.currentTarget(被侦听的对象)
  5. 箭头函数中的this指向指当前函数外
  6. ES6类中的this指向实例化对象 ,静态方法或者属性指向该类名(对于面向对象语言来说,一般在静态属性和方法中不允许使用this这个概念)
  7. ES5中的类中的this 原型上的属性和方法指向实例化对象,静态指向类名
  8. call apply bind 的this指向第一个参数

具体看我写的另一篇https://blog.csdn.net/weixin_44157964/article/details/103931479

36、JS 判断类型的几种方式

  1. typeof

    • string、number、boolean、undefined、function 、symbol、bigInt、object 返回对应类型字符串
    • 用typeof检测构造函数创建的Number,String,Boolean都返回object
    • 基本数据类型中:null 。引用数据类型中的:Array,Object,Date,RegExp。不可以用typeof检测。都会返回小写的object
  2. instanceof

    • instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 需特别注意:instanceof 检测的是原型
    • 即instanceof 用来比较一个对象是否为某一个构造函数的实例。instanceof可以准确的判断复杂数据类型,但是不能正确判断基本数据类型。
    • 一句话理解其运算规则:instanceof 检测左侧的 __proto__ 原型链上,是否存在右侧的 prototype 原型。
  3. constructor

    • JavaScript中,每个对象都有一个constructor属性,可以得知某个实例对象,到底是哪一个构造函数产生的, constructor属性表示原型对象与构造函数之间的关联关系
    • 当一个函数F被定义时,JS引擎会为F添加prototype原型,然后在prototype上添加一个constructor属性,并让其指向F的引用,F利用原型对象的constructor属性引用了自身,当F作为构造函数创建对象时,原型上的constructor属性被遗传到了新创建的对象上,从原型链角度讲,构造函数F就是新对象的类型。这样做的意义是,让对象诞生以后,就具有可追溯的数据类型。
    • 通过typeof运算符来判断它是原始的值还是对象。如果是对象,就可以使用constructor属性来判断其类型
      注意:null 和 undefined 是没有 constructor 存在的,这两种类型的数据需要通过其他方式来判断。
  4. Object.prototype.toString.call()

    • Object.prototype.toString()是 Object 的原型方法,他会直接返回[object xxx]
    • 而像数组、function会重写toString方法
    • js中所有类都继承于Object,因此toString()方法应该也被继承了,所以可以使用Object.prototype.toString()
    • call指向当前的数据类型,就可以使用原型上的toString来判断了

37、数组常用的方法

序号 方法 作用 返回值 是否改变原数组
1 join 拼接为一个字符串,默认使用逗号作为分隔符 新字符串 不改变
2 push 从数组末尾向数组添加元素,可以添加一个或多个元素 数组长度 改变
3 pop 删除数组的最后一个元素 删除的元素 改变
4 shift 删除第一个 删除的元素 改变
5 unshift 向数组的开头添加一个或更多元素 新的长度 改变
6 splice 可以实现删除、插入和替换 被删除元素组成的数组 改变
7 slice 从a(下标)截取到b之前 截取的数组 不改变
8 concat 连接两个以及多个数组 连接的新数组 不改变
9 indexOf 查找元素,两个参数(元素,从哪位开始查) 下标 不改变
10 laseIndexOf 查找元素,从后往前,两个参数(元素,从哪位开始逆向查) 下标 不改变
11 reverse 翻转数组 原数组 改变
12 sort 排序,默认升序 原数组 改变
13 forEach 遍历 无返回值
14 map 遍历 返回原始数组处理后的数组 不改变
15 filter 过滤 返回满足条件的过滤后的数组 不改变
16 fill es6新增,填充 填充后的原数组 改变
17 every 每一项满足返回true true或者false 不改变
18 some 有一项满足返回true true或者false 不改变
19 includes es7新增,一个数组是否包含一个指定的值 true或者false 不改变
20 reduce 把上一次的结果参与计算 处理后的结果 不改变
21 reduceRight 同上,但是是从最后一项开始遍历 处理后的结果 不改变
22 toString 将数组每一项用,号连接转换为字符串 字符串 不改变
23 toLocalString 同上,但是可以传入拼接的参数 字符串 不改变
24 find 查找 返回查找的值 不改变
25 findIndex 查找符合条件的索引 返回查找的索引 不改变
26 copyWithin 浅复制数组的一部分到同一数组中的另一个位置,三个参数(复制到该位置,复制起始位置,复制结束位置之前) 返回复制后的原数组 改变
27 flat 多层数组展开,默认深度是1 展开的新数组 不改变
28 flatMap 对每个成员先执行map,再执行flat 展开的新数组 不改变
29 entries 遍历数组返回一个遍历器对象,可以用for…of遍历,对键值对进行遍历 遍历器对象 不改变
30 keys 同上,对键进行遍历 遍历器对象 不改变
31 values 同上,对值进行遍历 遍历器对象 不改变

38、数组去重

  1. 循环判断当前元素与其后面所有元素对比是否相等,如果相等删除;(执行速度慢)

    var arr = [1,23,1,1,1,3,23,5,6,7,9,9,8,5];
    function removeDuplicatedItem(arr) {
       for(var i = 0; i < arr.length-1; i++){
           for(var j = i+1; j < arr.length; j++){
               if(arr[i]==arr[j]){
                 arr.splice(j,1);//console.log(arr[j]);
                  j--;
               }
           }
       }
       return arr;
    }
    
    arr2 = removeDuplicatedItem(arr);
    console.log(arr);
    console.log(arr2);
    
  2. 借助新数组 判断新数组中是否存在该元素如果不存在则将此元素添加到新数组中

    function unique4(arr){
     var res = [];
     for(var i=0; i<arr.length; i++){
      if(res.indexOf(arr[i]) == -1){
       res.push(arr[i]);
      }
     }
     return res;
    }
    
    console.log(unique4([1,1,2,3,5,3,1,5,6,7,4]));
    
  3. 利用对象属性存在的特性,如果没有该属性则存入新数组

    function unique3(arr){
     var res = [];
     var obj = {};
     for(var i=0; i<arr.length; i++){
      if( !obj[arr[i]] ){
       obj[arr[i]] = 1;
       res.push(arr[i]);
      }
     } 
     return res;
    }
    console.log(unique3([1,1,2,3,5,3,1,5,6,7,4]));
    
    
  4. 先将原数组排序,再与相邻的进行比较(新数组最后一个元素),如果不同则存入新数组

    function unique2(arr){
     var arr2 = arr.sort();
     var res = [arr2[0]];
     for(var i=1; i<arr2.length; i++){
      if(arr2[i] !== res[res.length-1]){
       res.push(arr2[i]);
      }
     } 
     return res;
    }
     
    console.log(unique2([1,1,2,3,5,3,1,5,6,7,4]));
    
  5. 定义一个新数组,并存放原数组的第一个元素,然后将原数组一一和新数组的元素对比,若不同则存放在新数组中

    function unique(arr){
     var res = [arr[0]];
     for(var i=1; i<arr.length; i++){
      var repeat = false;
      for(var j=0; j<res.length; j++){
       if(arr[i] === res[j]){
        repeat = true;
        break;
       }
      }
      if(!repeat){
       res.push(arr[i]);
      }
     }
     return res;
    }
    
    console.log(unique([1,1,2,3,5,3,1,5,6,7,4]));
    
    
  6. 利用数组原型对象上的includes方法

    function unique5(arr){
     var res = [];
      
     for(var i=0; i<arr.length; i++){
      if( !res.includes(arr[i]) ){ // 如果res新数组包含当前循环item
       res.push(arr[i]);
      }
     }
     return res;
    }
     
    console.log(unique5([1,1,2,3,5,3,1,5,6,7,4]));
    
    
  7. 利用数组原型对象上的 lastIndexOf 方法

    function unique9(arr){
     var res = []; 
     for(var i=0; i<arr.length; i++){
      res.lastIndexOf(arr[i]) !== -1 ? '' : res.push(arr[i]);
     }
     return res;
    }
    
    console.log(unique9([1,1,2,3,5,3,1,5,6,7,4]));
    
    
  8. 利用 ES6的set 方法

    function unique10(arr){
     //Set数据结构,它类似于数组,其成员的值都是唯一的
     return Array.from(new Set(arr)); // 利用Array.from将Set结构转换成数组
    }
    console.log(unique10([1,1,2,3,5,3,1,5,6,7,4]));
    
    
  9. 借助indexOf()方法判断此元素在该数组中首次出现的位置下标与循环的下标是否相等

    var ar = [1,23,1,1,1,3,23,5,6,7,9,9,8,5];
    function rep2(arr) {
        for (var i = 0; i < arr.length; i++) {
            if (arr.indexOf(arr[i]) != i) {
                arr.splice(i,1);//删除数组元素后数组长度减1后面的元素前移
                i--;//数组下标回退
            }
        }
        return arr;
    }
    var a1 = rep2(ar);
    console.log(ar);
    console.log(a1);
    
  10. 利用数组中的filter方法

    var arr = ['apple','strawberry','banana','pear','apple','orange','orange','strawberry'];
     var r = arr.filter(function(element,index,self){
        return self.indexOf(element) === index;
     });
     console.log(r);
    
  11. 双重循环,判断当前元素是否与后边元素有重复,如果没有重复,push进新数组,有重复则将重复元素中最后一个元素push进新数组

    var arr = [12, 2, 44, 3, 2, 32, 33, -2, 45, 33, 32, 3, 12];
    var newArr = [];
    for (var i = 0; i < arr.length; i++) {
        var repArr = [];//接收重复数据后面的下标
        //内层循环找出有重复数据的下标
        for (var j = i + 1; j < arr.length; j++) {
            if (arr[i] == arr[j]) {
                repArr.push(j);//找出后面重复数据的下标
            }
        }
        //console.log(repArr);
        if (repArr.length == 0) {//若重复数组没有值说明其不是重复数据
            newArr.push(arr[i]);
        }
    }
    console.log(newArr);//[ 44, 2, -2, 45, 33, 32, 3, 12 ]
    

参考https://www.cnblogs.com/web-record/p/9141373.html

39、去除字符串首尾空格

  • trim方法
  • 正则str.replace(/(^\s*)|(\s*$)/g, "")
  • split转换为数组,使用indexOf和lastIndexOf找第一个和最后一个不为空的值,使用splice截取空值数组,再使用join转换为字符串

40、能来讲讲 JS 的语言特性吗

  • 运行在客户端浏览器上;
  • 不用预编译,直接解析执行代码;
  • 是弱类型语言,较为灵活;
  • 与操作系统无关,跨平台的语言;
  • 脚本语言、解释性语言

41、如何判断是一个数组

  • Object.prototype.toString.call()
  • instanceof
  • constructor
  • Array.isArray()

42、跨域的原理

是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对 JavaScript 实施的安全限制,那么只要协议、域名、端口有任何一个不同,都被当作是不同的域。跨域原理,即是通过各种方式,避开浏览器的安全限制。

43、js实现跨域

  1. jsonp

    • JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
    • 只能实现get一种请求、不安全 容易遭到xss攻击
     <script> 
           function show(params) {
                console.log(params);
            }
    
     </script>    
    <script src="https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=a&cb=show"></script>
    
  2. CORS
    CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制

    • 普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置
    • 若要带cookie请求:前后端都需要设置。由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页

      优缺点:
    1. 目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。
    2. 整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
    3. CORS与JSONP的使用目的相同,但是比JSONP更强大。JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据
  3. document.domain + iframe跨域
    此方案仅限主域相同,子域不同的跨域应用场景(网页一级域名相同,只是二级域名不同)。实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域

    父窗口:(www.a.com/a.html)

    <iframe id="iframe" src="http://child.a.com/b.html"></iframe>
    <script>
        document.domain = 'a.com';
        var user = 'admin';
    </script>
    

    子窗口:(child.a.com/b.html)

    <script>
        document.domain = 'a.com';
        // 获取父窗口中变量
        alert('get js data from parent ---> ' + window.parent.user);
    </script>
    
  4. location.hash
    实现原理: a与b跨域相互通信,通过中间页c来实现(且c与a是同域)。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

    具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象

    a.html:(www.a.com/a.html)

    <iframe id="iframe" src="http://www.b.com/b.html" style="display:none;"></iframe>
    <script>
        var iframe = document.getElementById('iframe');
    
        // 向b.html传hash值
        setTimeout(function() {
            iframe.src = iframe.src + '#user=admin';
        }, 1000);
        
        // 开放给同域c.html的回调方法
        function onCallback(res) {
            alert('data from c.html ---> ' + res);
        }
    </script>
    

    b.html:(www.b.com/b.html)

    <iframe id="iframe" src="http://www.a.com/c.html" style="display:none;"></iframe>
    <script>
        var iframe = document.getElementById('iframe');
    
        // 监听a.html传来的hash值,再传给c.html
        window.onhashchange = function () {
            iframe.src = iframe.src + location.hash;
        };
    </script>
    

    c.html:(www.a.com/c.html)

    <script>
        // 监听b.html传来的hash值
        window.onhashchange = function () {
            // 再通过操作同域a.html的js回调,将结果传回
            window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
        };
    </script>
    
  5. window.name + iframe跨域
    浏览器窗口有window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。并且可以支持非常长的 name 值(2MB)

    这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。

  6. postMessage跨域
    HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。

    这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。

    postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。

    它可用于解决以下方面的问题:

    • 页面和其打开的新窗口的数据传递
    • 多窗口之间消息传递
    • 页面与嵌套的iframe消息传递
    • 上面三个场景的跨域数据传递
  7. nginx代理跨域

    1、nginx配置解决iconfont跨域
    浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置

    location / {
      add_header Access-Control-Allow-Origin *;
    }
    

    2、 nginx反向代理接口跨域

    跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题

    实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录

    #proxy服务器
    server {
        listen       81;
        server_name  www.domain1.com;
    
        location / {
            proxy_pass   http://www.domain2.com:8080;  #反向代理
            proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
            index  index.html index.htm;
    
            # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
            add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
            add_header Access-Control-Allow-Credentials true;
        }
    }
    
  8. WebSocket

    WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现

    WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

    原生WebSocket API使用起来不太方便,可以使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容

  9. Nodejs中间件代理跨域

    node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。

  10. webpack-dev-server代理(vue中配置)

    module.exports = {
        entry: {},
        module: {},
        ...
        devServer: {
            historyApiFallback: true,
            proxy: [{
                context: '/login',
                target: 'http://www.domain2.com:8080',  // 代理跨域目标接口
                changeOrigin: true,
                secure: false,  // 当代理某些https服务报错时用
                cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改
            }],
            noInfo: true
        }
    }
    

参考https://juejin.cn/post/6844903809118896135#heading-10

44、JS 数据类型

基本:String Boolean Number Undefined Null Symbol BigInt
引用:Object、Array、RegExp、Date、Function、特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math)

45、 实现 JS 中所有对象的深度克隆(Date 对象,正则对象)

function cloneDeep(obj) {
    if (obj == null) {
        return obj;
    }
    if (typeof obj !== 'object') {
        return obj;
    }
    if (obj instanceof RegExp) return new RegExp(obj);
    if (obj instanceof Date) return new Date(obj);
    let newObj = new obj.constructor;
    for (let key in obj) {
        newObj[key] = cloneDeep(obj[key])
    }
    return newObj;
}

46、不同数据类型的值的比较,是怎么转换的,有什么规则

  • == 相等 如果两边类型不一样,首先会隐式转化为相同的类型,然后再做比较
    • 对象==字符串 会把对象转换为字符串
    • null == undefined true(三个等号不相等) 但是null/undefined和其他任何值都不会相等
    • NaN==NaN false
    • Symbol()== Symbol() false
    • 剩余的情况(例如:对象= =数字 字符串= =布尔…) 都是要转换为数字,再进行比较的
  • === 绝对相等
  • Object.js([val],[val])

47、 null == undefined 为什么

要比较相等性之前,不能将 null 和 undefined 转换成其他任何值,但null == undefined会返回 true 。ECMAScript 规范中是这样定义的

48、暂停死区(暂时性死区)

在代码块内,使用 let、const 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”

49、为什么var没有暂时性死区?

let/const作用域为块级作用域,变量不会提升;而var的作用域为全局作用域,可以进行变量提升,这也就是为什么var没有暂时性死区

50、有一个游戏叫做 Flappy Bird,就是一只小鸟在飞,前面是无尽的沙漠,上下不断有钢管生成,你要躲避钢管。然后小明在玩这个游戏时候老是卡顿甚至崩溃,说出原因(3-5 个)以及解决办法(3-5 个)

原因可能是:

  1. 内存溢出问题
  2. 资源过大问题
  3. 资源加载问题
  4. canvas 绘制频率问题

解决办法:

  1. 针对内存溢出问题,我们应该在钢管离开可视区域后,销毁钢管,让垃圾收集器回收钢管,因为不断生成的钢管不及时清理容易导致内存溢出游戏崩溃
  2. 针对资源过大问题,我们应该选择图片文件大小更小的图片格式,比如使用webp、png格式的图片,因为绘制图片需要较大计算量。
  3. 针对资源加载问题,我们应该在可视区域之前就预加载好资源,如果在可视区域生成钢管的话,用户的体验就认为钢管是卡顿后才生成的,不流畅
  4. 针对 canvas 绘制频率问题,我们应该需要知道大部分显示器刷新频率为60 次/s,因此游戏的每一帧绘制间隔时间需要小于 1000/60=16.7ms,才能让用户觉得不卡顿

51、什么是按需加载

按需加载是网站性能优化立竿见影的其中一项,按需加载可以了解为 当用户触发某个动作的时候,才主动去请求资源,这样带来的优化好处:减少了HTTP请求,节省宽带,让页面首屏的内容更快展现在用户的视线范围内,可见极大提高了用户体检。触发的动作有很多,如:鼠标点击、输入文字、拉动滚动条,鼠标移动、窗口大小更改等。加载的文件,可以是 JS、图片、CSS、HTML 等

52、说一下什么是 virtual dom

虚拟 dom 是相对于浏览器所渲染出来的真实 dom 的

以往,我们改变更新页面,只能通过首先查找dom对象,再进行修改dom的方式来达到目的。 但这种方式相当消耗计算资源, 因为每次查询 dom ,都需要遍历整颗 dom 树。

现在,我们用对象的方式来描述真实的 dom,并且通过对象与真实dom建立了一一对应的关系,那么每次 dom 的更改,我通过找到相应对象,也就找到了相应的dom节点,再对其进行更新。这样的话,就能节省性能,因为js 对象的查询,比对整个dom 树的查询,所消耗的性能要少。

Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存,就是用树型结构的JS对象来描述真实的DOM结构的信息,这个树结构的JS对象包含了整个DOM结构的信息

53、虚拟 DOM 的优缺点

优点:

  • 降低浏览器性能消耗
    因为Javascript的运算速度远大于DOM操作的执行速度,因此,运用patching算法来计算出真正需要更新的节点,最大限度地减少DOM操作,从而提高性能。
  • diff算法,减少回流和重绘
    通过diff算法,优化遍历,对真实dom进行打补丁式的新增、修改、删除,实现局部更新,减少回流和重绘
  • 跨平台
    虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM ,可以进行更方便地跨平台操作,例如:服务器渲染、weex 开发等等

缺点:

  • 首次加载要慢些
    首次渲染大量DOM时,由于多了一层虚拟DOM的计算, 会比innerHTML插入慢
  • 无法进行极致优化
    虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中 无法进行针对性的极致优化

54、webpack 用来干什么的

webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。

在webpack 看来, 前端的所有资源文件(js/json/css/img/less/…)都会作为模块处理。

它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。

55、ant-design 优点和缺点

优点:

  • 组件库丰富,提供了很多常用的 UI 组件,方便开发者使用。
  • 设计统一,提供了丰富的设计指南和设计资源,使得应用界面看起来统一且美观。
  • 文档齐全,提供了详细的使用文档和示例代码,方便开发者理解和使用。
  • 社区活跃,有许多开发者在使用和贡献 Ant Design,有较快的问题解决和更新速度

缺点:

  • 较大的体积,由于提供了丰富的组件,所以 Ant Design 的体积较大,可能会对应用的性能造成一定影响
  • 框架自定义程度低,默认 UI 风格修改困难

56、写一个函数,第一秒打印 1,第二秒打印 2

第一个是用 let 块级作用域

for(let i=0;i<5;i++){
	setTimeout(function(){
		console.log(i)
	},1000*i)
}

第二个方法闭包

for(var i=0;i<5;i++){
	(function(i){
		setTimeout(function(){
			console.log(i)
		},1000*i)
	})(i)
}

57、vue的生命周期

其实Vue实例的生命周期,主要分为三个阶段,每个阶段都会执行不同的钩子函数,分别为

挂载(初始化相关属性,例如watch属性,method属性)

  • beforeCreate
  • created
  • beforeMount
  • mounted

更新(元素或组件的变更操作)

  • beforeUpdate
  • updated

销毁(销毁相关属性)

  • beforeDestroy
  • destroyed

58、简单介绍一下 symbol

Symbol是ES6中引入的一种新的基本数据类型,用于表示一个独一无二的值。它是JavaScript中的第
七种数据类型,与undefined、null、Number(数值)、String(字符串)、Boolean(布尔值)、
Object(对象)并列

Symbol特点:

  • Symbol的值是唯一的,用来解决命名冲突问题
  • Symbol值不能与其他数据进行运算
  • Symbol定义的对象属性不能使用for…in循环遍历,但是可以使用Reflect.ownKeys来获取对象的所有键名(包括symbol),而且Object.getOwnPropertySymbols()返回一个给定对象自身的所有 Symbol 属性的数组

59、 什么是事件监听

事件分为DOM 0级事件和Dom 2级事件,DOM2级事件也叫做事件监听。DOM 0级事件的缺点是如果事件相同 后者的事件会覆盖前者的事件,DOM2级事件可以解决这个问题

document.onclick = function(){
    alert(1);
}
document.onclick = function(){
   alert(2);
}
//只会执行第二个函数    弹出2

DOM2级事件的方法(事件监听)

  • 主流浏览器 addEventListener() 【有三个参数】

    • 参数1:事件类型 不需要加on
    • 参数2:回调函数名称 (函数需要单独写在外面)
    • 参数3:布尔值 true代表捕获 false代表冒泡
    • 解绑事件方法:removeEventListener()
    function a(){
        alert(1)
    }
    function b(){
        alert(2)
    }
    document.addEventListener("click",a);
    document.addEventListener("click",b);     //点击后先执行第一个函数,再执行第二个函数
    document.removeEventListener("click",a)   //不在执行a()这个函数
    
  • IE浏览器:attachEvent()

    • 参数1:事件类型 需要加on
    • 参数2:回调函数名称(函数需要单独写在外面)
    • 解绑事件方法:detachEvent()
    function a(){
        alert(1)
    }
    function b(){
        alert(2)
    }
    document.attachEvent("onclick",a);
    document.attachEvent("onclick",b);//点击后先执行第二个函数,再执行第一个函数
    document.detachEvent("onclick",a)//不在执行a()这个函数
    

参考:https://www.cnblogs.com/Tian-yy/p/9060669.html

60、说说 C++,Java,JavaScript 这三种语言的区别

  • 从静态类型还是动态类型来看
    • 静态类型,编译的时候就能够知道每个变量的类型,编程的时候也需要给定类型,如Java中的整型 int,浮点型 float 等。C、C++、Java 都属于静态类型语言。
    • 动态类型,运行的时候才知道每个变量的类型,编程的时候无需显示指定类型,如JavaScript 中的 var、PHP 中的$。JavaScript、Ruby、Python 都属于动态类型语言
    • 静态类型还是动态类型对语言的性能有很大影响
  • 从编译型还是解释型来看
    - 编译型语言,像 C、C++,需要编译器编译成本地可执行程序后才能运行,由开发人员在编写完成后手动实施。用户只使用这些编译好的本地代码,这些本地代码由系统加载器执行,由操作系统的 CPU 直接执行,无需其他额外的虚拟机等。源代码=》抽象语法树=》中间表示=》本地代码
    - 解释性语言,像 JavaScript、Python,开发语言写好后直接将代码交给用户,用户使用脚本解释器将脚本文件解释执行。对于脚本语言,没有开发人员的编译过程,当然,也不绝对。
    源代码=》抽象语法树=》解释器解释执行

61、JS 原型链,原型链的顶端是什么?

null

62、Object 的原型是什么?Object 的原型的原型是什么?

Function.prototype(Object也是构造函数)

Object.prototype

63、在数组原型链上实现删除数组重复数据的方法

Array.prototype.unique = function(){
}

参考上面38数组去重

64、用闭包写个单例模式

定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
个人理解:通常做法就是一个变量初始值为空,每次创建之前判断这个变量,变量存在,复用之前的,不存在,则初始化

var Singleton = (function () {
    var instance;
    var CreateSingleton = function (name) {
        this.name = name;
        if (instance) {
            return instance;
        }
        // 打印实例名字
        this.getName();
        // instance = this;
        // return instance;
        return instance = this;
    }
    // 获取实例的名字
    CreateSingleton.prototype.getName = function () {
        console.log(this.name)
    }
    return CreateSingleton;
})();
// 创建实例对象 1
var a = new Singleton('a');
// 创建实例对象 2
var b = new Singleton('b');
console.log(a === b); //true

65、promise+Generator+Async 的使用

  • Promise 解决的问题:回调地狱

  • Generator 函数

    • 分段执行,可以暂停
    • 可以控制阶段和每个阶段的返回值
    • 可以知道是否执行到结尾
      function* g() {
          var o = 1;
          yield o++;
          yield o++;
      }
      var gen = g();
      console.log(gen.next()); // Object {value: 1, done: false}
      var xxx = g();
      console.log(gen.next()); // Object {value: 2, done: false}
      console.log(xxx.next()); // Object {value: 1, done: false}
      console.log(gen.next()); // Object {value: undefined, done: true}
      
      generator 和异步控制: 利用 Generator 函数的暂停执行的效果,可以把异步操作写在 yield 语句里面,等到调用next 方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在 yield 语句下面,反正要等到调用 next 方法时再执行。所以,Generator 函数的一个重要实际意义就是用来处理异步操作,改写回调函数
  • async
    async 表示这是一个 async 函数,await 只能用在这个函数里面。
    await 表示在这里等待异步操作返回结果,再继续执行。
    await 后一般是一个 promise 对象

    如果 async 函数返回的是一个同步的值,这个值将被包装成一个理解resolve 的Promise,等同于 return Promise.resolve(value)。
    await 用于一个异步操作之前,表示要“等待”这个异步操作的返回值。await 也可以用于一个同步的值。

66、写个函数,可以转化下划线命名到驼峰命名

function underlineToHump(arr){
    //判断下划线位置,下划线位置加一,调用js的大写方法toUpperCase();
    let a = arr.indexOf('_');
    let lit = arr.split('');
    lit[a+1] = lit[a+1].toUpperCase();
    arr = lit.join('');
    arr = arr.split('_').join('');
    console.log(arr);
}
 
let arr = "as_dad";
underlineToHump(arr);

67、JS 中 string 的 startsWith 和 indexof 两种方法的区别

startsWith() 方法用来判断当前字符串是否以另外一个给定的子字符串开头,并根据判断结果返回 true 或 false;有两个参数(要搜索的子字符,在 str 中搜索 searchString 的开始位置,默认值为 0)

Indexof 函数,indexof 函数可返回某个指定字符串在字符串中首次出现的位置

68、JS 字符串转数字的方法

通过函数 parseInt(),可解析一个字符串,并返回一个整数,语法为parseInt(string ,radix)

  • string:被解析的字符串

  • radix:表示要解析的数字的基数(以该进制读取,转换为十进制输出),默认是十进制,如果 radix<2 或>36,则返回NaN

  • 当忽略参数 radix , JavaScript 默认数字的基数如下:

    如果 string 以 “0x” 开头,parseInt() 会把 string 的其余部分解析为十六进制的整数。
    如果 string 以 0 开头,那么 ECMAScript v3 允许 parseInt() 的一个实现把其后的字符解析为八进制或十六进制的数字。
    如果 string 以 1 ~ 9 的数字开头,parseInt() 将把它解析为十进制的整数。

69、let const var 的区别

  1. 重复声明
    var允许重复声明,let、const不允许
  2. 变量提升
    var会提升变量的声明到作用域的顶部,但let和const不会
  3. 暂时性死区
    var没有暂时性死区,let const有
  4. 块级作用域
    var没有块级作用域,let和const有块级作用域
  5. window对象的属性和方法(全局作用域中)
    全局作用域中,var声明的变量,通过function声明的函数,会自动变为window对象的变量,属性或方法,但const和let不会

70、什么是块级作用域

块级作用域是指变量或函数在一个代码块内有效,在代码块外无效的作用域。常见的代码块如if语句、for循环等。在 JavaScript 中,使用大括号 {} 定义块级作用域

71、如何用ES5 的方法实现块级作用域

众所周知,ES6新增了let关键字用来定义局部变量,它使得JS的变量有了块级作用域。
块级作用域是指变量只在{}内有效
那么在ES6以前如何给变量实现块级作用域呢?答案就是使用立即执行匿名函数

先看看没有块级作用域的情况:

function a(){
	for(var i=0;i<3;i++){
			
	}	
	console.log(i);
}
a();

上述例子中,将输出 i=3,因为i使用var声明,作用域是整个函数。
如果需要让变量i只在for循环中有效呢,我们可以这样实现:
即在for循环外面包裹一层匿名函数,在匿名函数外加圆括号表示立即执行

function a(){
    (function(){
        for(var i=0;i<3;i++){
            console.log(i);
        }	
    })();
 
    console.log(i); //输出undefined
}
a();

72、ES6 箭头函数的特性

  • 箭头函数没有 this,所有箭头函数内的this都是指当前函数外的this指向
  • 箭头函数没有自己的 arguments 对象,但是可以访问外围函数的arguments 对象
  • 不能通过 new 关键字调用,同样也没有 new.target 值和原型

73、setTimeout 和 Promise 的执行顺序

setTimeout(function () {
    console.log(1)
}, 0);
new Promise(function (resolve, reject) {
    console.log(2)
    for (var i = 0; i < 10000; i++) {
        if (i === 10) { console.log(10) }
        i == 9999 && resolve();
    }
    console.log(3)
}).then(function () {
    console.log(4)
})
console.log(5);

74、平时是怎么调试 JS 的

分为移动端调试和pc端调试

pc就是chorome控制台

移动端比较复杂,因为我们的项目都是在我们app内部,

  • 配置host
  • 本地起项目 npm run serve
  • fiddler抓包软件的配置。
  • 打开手机wifi,让手机和电脑处在同一个局域网下,并配置代理。
  • 在对应的环境中打开项目链接
  • 用数据线将手机和电脑连接
  • 打开chrome,在地址栏输入 chrome://inspect/#devices

75、基本数据类型和引用数据类型的区别

  • 基本数据类型的值是不可变的,任何方法都无法改变一个基本类型的值,当这个变量重新赋值后看起来变量的值是改变了,但是这里变量名只是指向变量的一个指针,所以改变的是指针的指向改变,该变量是不变的,但是引用类型可以改变
  • 基本数据类型不可以添加属性和方法,但是引用类型可以
  • 基本数据类型的赋值是简单赋值,如果从一个变量向另一个变量赋值基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上,引用数据类型的赋值是对象引用
  • 基本数据类型的比较是值的比较,引用类型的比较是引用的比较,比较对象的内存地址是否相同
  • 基本数据类型是存放在栈区的,引用数据类型是保存在栈区和堆区

76、NaN是什么的缩写

Not a Number

77、JS 的作用域类型

  • 全局作用域:变量在函数外定义为全局变量,全局变量有全局作用域:网页中的所有脚本和函数均可使用
  • 函数作用域:变量在函数内部声明
  • 块级作用域:在 { } 中

78、undefined与 null 的区别

null是表示一个"无"的对象,指向空的对象,这个对象是不存在的,转为数值时为0。null的使用场景:经常用作函数的参数,或作为原型链的终点
undefined是表示一个"无"的原始值,变量被声明了但还没有赋值,就为undefined,转为数值时为NaN

相同点:都是表示值的空缺,所以两者等于(= =)是相等的

不同点:null的类型是Object对象,undefined的类型就是Undefiend,所以两者全等(= = =)不相等

79、setTimeout(fn,100);100 毫秒是如何权衡的

setTimeout()函数只是将事件插入了任务列表,必须等到当前代码执行完,主线程才会去执行它指定的回调函数,有可能要等很久,所以没有办法保证回调函数一定会在setTimeout指定的时间内执行,100毫秒是插入队列的时间+等待的时间

80、怎么获得对象上的属性

首先定义一个对象

//定义对象
var obj = {
    ['str']: 'String property', [Symbol()]: 'Symbol property',
}

//定义不可枚举的字符串属性
Object.defineProperty(obj, 'unenum', {
    value: ' Non-enumerable property', writable: true, enumerable: false, configurable: true,
});

//定义不可枚举的Symbol属性
Object.defineProperty(obj, Symbol('unenum'), {
    value: 'Non-enumerable Symbol value', writable: true, enumerable: false, configurable: true,
});

//在原型链上定义一个字符串属性和一个Symbol属性
Object.setPrototypeOf(obj, { foo: 'bar', [Symbol('foo')]: 'bar' });
  1. Object.keys() & Object.values() & Object.entries 这三个方法都是为了来获得对象的属性与值的,最终返回值是一个数组,不过只获取对象本身的可枚举字符串属性

    console.log(Object.keys(obj)); //["str"]
    console.log(Object.values(obj)); // ["String property"]
    console.log(Object.entries(obj)); //[["str", "String property"]]
    
  2. Object.getOwnPropertyNames()是获取对象自身上的字符串属性,包括可枚举的与不可枚举的属性,最后返回一个数组

    console.log(Object.getOwnPropertyNames(obj)); //["str", "unenum"]
    
  3. Object.getOwnPropertySymbols() 是获取对象自身上的Symbol属性,包括可枚举的与不可枚举的,最后返回一个数组

    console.log(Object.getOwnPropertySymbols(obj)); //[Symbol(), Symbol(unenum)]
    
  4. for...in.. 用来遍历对象上可枚举的字符串属性的,包括原型链上的可枚举的字符串属性。

    for(let key in obj){
    	console.log(key); //"str" "foo"
    }
    

81、简单讲一讲 ES6 的一些新特性

  1. let和const
  2. symbol
  3. 模板字符串
  4. 字符串新方法 includes()、startsWith() 、endsWith()、repeat()、padStart()、padEnd()
  5. 解构表达式
    • 数组解构
    • 对象解构
  6. 对象方面
    • Map和Set
    • 数组的新方法: Array.from()、includes()、map()、filter()、some()、every() 、reduce()
    • object的新方法:Object.is()、Object.assign()、Object.keys()、Object.values()、Object.entries()等方法
    • 对象声明简写 let person = {name,age}
    • …(对象扩展符)
  7. 函数方面
    • 参数默认值
    • 箭头函数
  8. class(类)
  9. promise和proxy
  10. 模块化 import export
  11. 运算符
    • … 扩展运算符
    • 可选链 ?.
    • 函数绑定运算符::

参考https://blog.csdn.net/ZLJ_999/article/details/124122540

82、给出以下代码,输出的结果是什么原因?

for (var i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}
console.log(i)

在一秒后输出 5 个 5

每次 for 循环的时候 setTimeout 都会执行,但是里面的 function 则不会执行被放入任务队列,因此放了 5 次;for 循环的 5 次执行完之后不到 1000 毫秒;1000 毫秒后全部执行任务队列中的函数,所以就是输出 5 个 5。

83、如果已经有三个 promise,A、B 和 C,想串行执行,该怎么写?

// promise
A.then(B).then(C).catch(...)

// async/await
(async () => {
    await a();
    await b();
    await c();
})()

84、知道 private 和 public 吗

public:public 表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用

private:private 表示私有,私有的意思就是除了 class 自己之外,任何人都不可以直接使用

85、原型题

Function.prototype.a = 1;
Object.prototype.b = 2;
function A() { }
var a = new A();
console.log(a.a, a.b); 
console.log(A.a, A.b); 

86、promise 和 await/async 的关系

async-await是promise和generator的语法糖
async 函数返回的是一个promise 对象

87、JS 加载过程阻塞,解决方法

指定 script 标签的 async 属性。

如果 async=“async”,脚本相对于页面的其余部分异步地执行(当页面继续进行解析时,脚本将被执行)

如果不使用 async 且 defer=“defer”:脚本将在页面完成解析时执行

88、说 promise,没有 promise 怎么办

没有 promise,可以用回调函数代替

89、arguments

arguments 是类数组对象,有 length 属性,不能调用数组方法

可用 Array.from()转换

90、JavaScript 中的轮播实现原理?假如一个页面上有两个轮播,你会怎么实现

图片轮播的原理就是图片排成一行,然后准备一个只有一张图片大小的容器,对这个容器设置超出部分隐藏,在控制定时器来让这些图片整体左移或右移,这样呈现出来的效果就是图片在轮播了

如果有两个轮播,可封装一个轮播组件,供两处调用

分割线 :下面是待更新的题

  1. 简单实现 Node 的 Events 模块
  2. JS 的全排列
  3. 编写代码,满足以下条件: (1)Hero(“37er”);执行结果为Hi! This is 37er
    ( 2) Hero(“37er”).kill(1).recover(30); 执 行 结 果 为 Hi! This is 37er Kill 1 bug
    Recover 30 bloods(3)Hero(“37er”).sleep(10).kill(2)执行结果为Hi! This is 37er //
    等待 10s 后 Kill 2 bugs //注意为 bugs (双斜线后的为提示信息,不需要打印)
  4. 给两个构造函数 A 和 B,如何实现 A 继承 B?
  5. assign 的深拷贝
  6. Eventloop
  7. 实现计算一年中有多少周?

前端核心

一、服务端编程

1、JSONP 的缺点

JSON 只支持 get,因为 script 标签只能使用 get 请求;
JSONP 需要后端配合返回指定格式的数据。

2、dom 是什么,你的理解?

文档对象模型(Document Object Model,简称 DOM),是 W3C 组织推荐的处理可扩展标志语言的标准编程接口。在网页上,组织页面(或文档)的对象被组织在一个树形结构中,用来表示文档中对象的标准模型就称为 DOM

3、关于 dom 的 api 有什么

  • 节点查找API
    • document.getElementById :根据ID查找元素,大小写敏感,如果有多个结果,只返回第一个;
    • document.getElementsByClassName:根据类名查找元素,多个类名用空格分隔,返回一个 HTMLCollection 。注意兼容性为IE9+(含)。另外,不仅仅是document,其它元素也支持 getElementsByClassName 方法;
    • document.getElementsByTagName:根据标签查找元素, * 表示查询所有标签,返回一个 HTMLCollection 。
    • document.getElementsByName:根据元素的name属性查找,返回一个 NodeList
    • document.querySelector:返回单个Node,IE8+(含),如果匹配到多个结果,只返回第一个。
    • document.querySelectorAll :返回一个 NodeList ,IE8+(含)
    • document.forms :获取当前页面所有form,返回一个 HTMLCollection ;
  • 节点创建API
    • createElement创建元素
    • createTextNode创建文本节点
    • cloneNode 克隆一个节点
    • createDocumentFragment
  • 节点修改API
    • appendChild
    • insertBefore
    • insertAdjacentHTML
    • Element.insertAdjacentElement()
    • removeChild
    • replaceChild
  • 节点关系API
    • 父关系API:parentNode
    • 子关系API:children childNodes firstChild lastChild
    • 兄弟关系型API : previousSibling nextSibling previousElementSibling nextElementSibling
  • 元素属性型API
    • setAttribute 给元素设置属性
    • getAttribute
    • hasAttribute
  • 样式操作API
    • 直接修改元素的样式

      elem.style.color = 'red';  
      elem.style.setProperty('font-size', '16px');  
      elem.style.removeProperty('color');
      
    • 动态添加样式规则

      var style = document.createElement('style');  
      style.innerHTML = 'body{color:red} #top:hover{background-color: red;color: white;}';  
      document.head.appendChild(style);
      
    • classList获取样式属性

      了解dom节点样式(classList)的remove, add, toggle, contains, replace等方法的使用。
      
    • window.getComputedStyle 通过 element.sytle.xxx 只能获取到内联样式,借助 window.getComputedStyle 可以获取应用到元素上的所有样式,IE8或更低版本不支持此方法

      var style = window.getComputedStyle(element[, pseudoElt]);

参考https://blog.csdn.net/weixin_43613849/article/details/121560210

二、Ajax

1、什么是ajax?ajax作用是什么?

异步的javascript和xml AJAX 是一种用于创建快速动态网页的技术。 ajax用来与后台交互

2、原生js ajax请求有几个步骤?分别是什么

//创建 XMLHttpRequest 对象
var ajax = new XMLHttpRequest();
//规定请求的类型、URL 以及是否异步处理请求。
ajax.open('GET',url,true);
//发送信息至服务器时内容编码类型
ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 
//发送请求
ajax.send(null);  
//接受服务器响应数据
ajax.onreadystatechange = function () {
    if (obj.readyState == 4 && (obj.status == 200 || obj.status == 304)) { 
    }
};

3、json字符串转换集json对象、json对象转换json字符串

//字符串转对象
JSON.parse(json)
eval('(' + jsonstr + ')')   
// 对象转字符串
JSON.stringify(json)

4、ajax返回的状态

分为readyState(状态值)和status(状态码)

readyState,是指运行AJAX所经历过的几种状态,无论访问是否成功都将响应的步骤,可以理解成为AJAX运行步骤,使用“ajax.readyState”获得

status,是指无论AJAX访问是否成功,由HTTP协议根据所提交的信息,服务器所返回的HTTP头信息代码,使用“ajax.status”获得

总体理解:可以简单的理解为state代表一个整体的状态。而status是这个大的state下面具体的小的状态

readyState

readyState总共有5个状态值,分别为0~4,每个值代表了不同的含义

  • 0:初始化,XMLHttpRequest对象还没有完成初始化
  • 1:载入,XMLHttpRequest对象开始发送请求
  • 2:载入完成,XMLHttpRequest对象的请求发送完成
  • 3:解析,XMLHttpRequest对象开始读取服务器的响应
  • 4:完成,XMLHttpRequest对象读取服务器响应结束

status

  • 1xx:信息响应类,表示接收到请求并且继续处理
  • 2xx:处理成功响应类,表示动作被成功接收、理解和接受
  • 3xx:重定向响应类,为了完成指定的动作,必须接受进一步处理
  • 4xx:客户端错误,客户请求包含语法错误或者是不能正确执行
  • 5xx:服务端错误,服务器不能正确执行一个正确的请求

5、如果我想发出两个有顺序的ajax 需要怎么做?

发出两个有顺序的 ajax,可以用回调函数,也可以使用 Promise.then 或者async 等

6、Ajax Fetch axios 比有什么优缺点?

  • ajax是最早出现发送后端请求的技术,属于原生 js
  • fetch 首先解决了回调地狱的问题,他返回的结果是一个 Promise 对象
  • axios 功能非常强大,包括 取消请求,超时处理,进度处理等等。但它的本质还是 ajax,基于 Promise 进行封装,既解决回调地狱问题,又能很好地支持各个浏览器。

ajax 的优缺点:

  • 属 js 原生,基于XHR进行开发,XHR 结构不清晰。
  • 针对 mvc 编程,由于近来vue和React的兴起,不符合mvvm前端开发流程。
  • 单纯使用 ajax 封装,核心是使用 XMLHttpRequest 对象,使用较多并有先后顺序的话,容易产生回调地狱
    fetch 的优缺点:
  • 属于原生 js,脱离了xhr ,号称可以替代 ajax技术。
  • 基于 Promise 对象设计的,可以解决回调地狱问题。
  • 提供了丰富的 API,使用结构简单。
  • 默认不带cookie,使用时需要设置。
  • 没有办法检测请求的进度,无法取消或超时处理。
  • 返回结果是 Promise 对象,获取结果有多种方法,数据类型有对应的获取方法,封装时需要分别处理,易出错。
  • 浏览器支持性比较差

axios的优缺点:

  • 在浏览器中创建XMLHttpRequest请求,在node.js中创建http请求
  • 解决回调地狱问题
  • 自动转化为json数据类型
  • 支持Promise技术,提供并发请求接口
  • 可以通过网络请求检测进度
  • 提供超时处理
  • 浏览器兼容性良好
  • 有拦截器,可以对请求和响应统一处理

三、移动 web 开发

1、知道 PWA 吗

概念

PWA 全称 Progressive Web App,即渐进式 WEB 应用。一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用. 随后添加上 App Manifest 和Service Worker
来实现 PWA 的安装和离线等功能

纵观现有 Web 应用与原生应用的对比差距,如离线缓存、沉浸式体验等等,可以通过已经实现的 Web 技术去弥补这些差距,最终达到与原生应用相近的用户体验效果

特性

  • 安全可靠
    使用 Service Work 技术实现即时下载,当用户打开应用后,页面资源的加载不再完全依赖于网络,而是使用 Service Work 缓存离线包存在本地,确保为用户提供即时可靠的体验。

  • 访问更快
    首屏可以部署在服务端,节省网页请求时间,加载速度更快,拥有更平滑的动态效果和快速的页面响应。

  • 响应式界面
    支持各种类型的终端和屏幕。

  • 沉浸式体验
    在支持 PWA 的浏览器和手机应用上可以直接将 Web 应用添加到用户的主屏幕上,无需从应用商店下载安装。从主屏幕上打开应用之后,提供沉浸式的全屏幕体验。

功能

  • 手机应用配置(Web App Manifest)
    可以通过 manifest.json 文件配置,使得可以直接添加到手机的桌面上。

  • 离线加载与缓存(Service Worker+Cache API )
    可以通过 Service Worker + HTTPS +Cache Api + indexedDB 等一系列 Web 技术实现离线加载和缓存。

  • 消息推动与通知(Push&Notification )
    实现实时的消息推送与通知

  • 数据及时更新(Background Sync )
    后台同步,数据及时更新

优势与劣势

  1. 优势
    • 超简单的安装和下载
    • 发布迭代不需要第三方平台审核
      我们都知道发布一个苹果应用是需要提交 App Store 商店进行审核,通过了方可发布成功的。安卓应用也是一样。并且更新迭代版本的时候也需要审核,还需要提交一些功能说明,图片等资料。但是网页版的应用就完全不需要这个审核过程,直接部署服务器就可以使用
    • 渐进式
      现有的 Web 项目可以通过 PWA 的几个核心技术点一步步转型成一个完整的 PWA 应用
  2. 劣势
    • 谷歌
      基于 Chromium 开发的浏览器 Chrome 和 Opera 已经完全支持 PWA 。
      这里说一下 Chromium 和 Chrome 的区别。
      Chromium 是谷歌的开源项目,由开源社区去维护。拥有众多的版本包括Windows、Mac、Linux。国内所有的 “双核浏览器”,都是基于 Chromium 开发的,而我们下载的 Chromium 浏览器是其源码未经修改的直接编译版本。
      Chrome 是基于 Chromium 开发的,是闭源的,跨平台多端支持,特性更加丰富。
      Google上线了两个新网站,web.dev 和 squoosh.app 都支持 PWA( web.dev 是宣传和推广 PWA 的,解释了 PWA 的几个关键技术。squoosh.app 是一个图片压缩工具) 。

    • 微软
      微软将 PWA 带到了 Windows 10。同时 Windows Edge(windows 10 之后微软推出的浏览器,比 IE更流畅、外观 UI 更舒适) 也支持 PWA。

    • IOS
      随着 iOS 11.3 的发布,iOS 正式开始支持 PWA,可以将它放在苹果手机主屏。

    • Android
      Twitter 和 Flipboard 都推出了 PWA,可以将它放在安卓手机主屏。

    • 国内
      国内支持 PWA 的应用有微博、淘宝、豆瓣和饿了么。

参考https://blog.csdn.net/weixin_44135121/article/details/105528430

2、flex布局及优缺点

优点在于其容易上手,根据 flex 规则很容易达到某个布局效果
缺点是:浏览器兼容性比较差,只能兼容到 ie9 及以上

3、Rem 布局及其优缺点

优点:可以快速适用移动端布局 字体图片 高度

缺点:

  • 目前 ie 不支持,对 pc 页面来讲使用次数不多;
  • 数据量大:所有的图片,盒子都需要我们去给一个准确的值;才能保证不同机型的适配;
  • 在响应式布局中,必须通过 js 来动态控制根元素 font-size 的大小。也就是说 css 样式和 js 代码有一定的耦合性。且必须将改变 font-size 的代码放在css 样式之前

4、 百分比布局缺点

  1. 计算困难,如果我们要定义一个元素的宽度和高度,按照设计稿,必须换算成百分比单位。
  2. 各个属性中如果使用百分比,相对父元素的属性并不是唯一的。比如width和height相对于父元素的width和height,而margin、padding不管垂直还是水平方向都相对比父元素的宽度、border-radius则是相对于元素自身等等,造成我们使用百分比单位容易使布局问题变得复杂。

5、移动端适配 1px 的问题

产生原因

主要是跟一个东西有关,DPR(devicePixelRatio) 设备像素比,它是默认缩放为100%的情况下,设备像素和CSS像素的比值 window.devicePixelRatio=物理像素 /CSS像素

目前主流的屏幕DPR=2 (iPhone 8),或者3 (iPhone 8 Plus)。拿2倍屏来说,设备的物理像素要实现1像素,而DPR=2,所以css 像素只能是 0.5。一般设计稿是按照750来设计的,它上面的1px是以750来参照的,而我们写css样式是以设备375为参照的,所以我们应该写的0.5px就好了啊! 试过了就知道,iOS 8+系统支持,安卓系统不支持

解决方案

  1. WWDC对iOS统给出的方案 推荐指数:**

    在 WWDC大会上,给出来了1px方案,当写 0.5px的时候,就会显示一个物理像素宽度的 border,而不是一个css像素的 border。 所以在iOS下,你可以这样写

    border:0.5px solid #E5E5E5
    

    可能你会问为什么在3倍屏下,不是0.3333px 这样的?经过我测试,在Chrome上模拟iPhone 8Plus,发现小于0.46px的时候是显示不出来

    优点: 简单,没有副作用
    缺点: 支持iOS 8+,不支持安卓。后期安卓follow就好了

  2. 使用边框图片推荐指数:**

      border: 1px solid transparent;
      border-image: url('./../../image/96.jpg') 2 repeat;
    

    优点: 没有副作用
    缺点: border颜色变了就得重新制作图片;圆角会比较模糊

  3. 使用box-shadow实现 推荐指数:***

    box-shadow: 0  -1px 1px -1px #e5e5e5,   //上边线
                1px  0  1px -1px #e5e5e5,   //右边线
                0  1px  1px -1px #e5e5e5,   //下边线
                -1px 0  1px -1px #e5e5e5;   //左边线
    

    前面两个值 x,y 主要控制显示哪条边,后面两值控制的是阴影半径、扩展半径
    优点:使用简单,圆角也可以实现
    缺点:模拟的实现方法,仔细看谁看不出来这是阴影不是边框

  4. 使用伪元素 推荐指数:****

    1 条border

    .setOnePx{
      position: relative;
      &::after{
        position: absolute;
        content: '';
        background-color: #e5e5e5;
        display: block;
        width: 100%;
        height: 1px; /*no*/
        transform: scale(1, 0.5);
        top: 0;
        left: 0;
      }
    }
    

    可以看到,将伪元素设置绝对定位,并且和父元素的左上角对齐,将width 设置100%,height设置为1px,然后进行在Y方向缩小0.5倍

    4 条border

    .setBorderAll{
         position: relative;
           &:after{
               content:" ";
               position:absolute;
               top: 0;
               left: 0;
               width: 200%;
               height: 200%;
               transform: scale(0.5);
               transform-origin: left top;
               box-sizing: border-box;
               border: 1px solid #E5E5E5;
               border-radius: 4px;
          }
        }
    

    同样为伪元素设置绝对定位,并且和父元素左上角对其。将伪元素的长和宽先放大2倍,然后再设置一个边框,以左上角为中心,缩放到原来的0.5倍

    优点:全机型兼容,实现了真正的1px,而且可以圆角
    缺点:暂用了after 伪元素,可能影响清除浮动

  5. 设置viewport的scale值 推荐指数:*****
    这个解决方案是利用viewport+rem+js 实现的

    <html>
      <head>
          <title>1px question</title>
          <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
          <meta name="viewport" id="WebViewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">        
          <style>
              html {
                  font-size: 1px;
              }            
              * {
                  padding: 0;
                  margin: 0;
              }
              .top_b {
                  border-bottom: 1px solid #E5E5E5;
              }
    
              .a,.b {
                          box-sizing: border-box;
                  margin-top: 1rem;
                  padding: 1rem;                
                  font-size: 1.4rem;
              }
    
              .a {
                  width: 100%;
              }
    
              .b {
                  background: #f5f5f5;
                  width: 100%;
              }
          </style>
          <script>
              var viewport = document.querySelector("meta[name=viewport]");
              //下面是根据设备像素设置viewport
              if (window.devicePixelRatio == 1) {
                  viewport.setAttribute('content', 'width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no');
              }
              if (window.devicePixelRatio == 2) {
                  viewport.setAttribute('content', 'width=device-width,initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no');
              }
              if (window.devicePixelRatio == 3) {
                  viewport.setAttribute('content', 'width=device-width,initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no');
              }
              var docEl = document.documentElement;
              var fontsize = 32* (docEl.clientWidth / 750) + 'px';
              docEl.style.fontSize = fontsize;
          </script>
      </head>
      <body>
          <div class="top_b a">下面的底边宽度是虚拟1像素的</div>
          <div class="b">上面的边框宽度是虚拟1像素的</div>
      </body>
    </html>
    

    优点:全机型兼容,直接写1px不能再方便
    缺点:适用于新的项目,老项目可能改动大

参考:https://juejin.cn/post/6844903877947424782#heading-16

6、toB 和 toC 项目的区别

to B(business)即面向企业,to C( customer)即面向普通用户

toC 产品更注重产品用户的共性而淡化角色关系,而 toB 产品则更强调面向用户、客户的角色关系,而淡化共性提取。实际上,这是由服务对象所引起的,C 端产品的服务对象,由终端所限,是一个面向个体的服务。而 B 端服务使用最终是面向一个系统体系组织,在干系人间配合使用中发挥产品价值。
一个好的产品 toB 可以让组织的系统变得更好,最终反哺于系统中的各个单位。需求动力之不同 toC 的产品方法论,用户体验是几乎最为重要的需求来源,腾讯此前,也以"以用户体验为归依"来驱动企业产品打造。

但 B 端产品则不同,B 端在一个商业的背景之下,B 端的决策思路是,“以企业获益为归依”,系统是否有利于企业的生产力,竞争力等,单纯的用户体验,仅能让员工得到片刻的享受, 但无法说服企业,企业并不会为一个不能"赚钱"的东西买单。需求动力的不同,引发的这是购买使用决策体系的变化。

toB 产品应更考虑 获益与系统性价值,部分情况还有可能会牺牲掉局部个体的利益,对于使用者而言应该是自律或他律的,toC 产品则更考虑的是个体用户的偏好,并长时间内,基于技术效率的提升,产品的服务中心更多地围绕着更高效地帮助用户的"欲望"释放进行设计,对于使用者而言是一个释放自我的存在。

7、移动端兼容性

1. 时间格式化后,在ios手机展示错误(展示NaN或null)

原因:ios无法识别 - 格式的日期,例如:2022-01-13
解决:使用replace将 - 替换成 /

let newTime = oldTime.replace(/-/g, "/")

2. 设置圆角(border-radius:50%;)部分手机显示为椭圆

原因:使用rem做屏幕适配会出现这个问题;,rem在换算为px时,会是一个带小数点的值,安卓对小于1px的做了处理(不同浏览器对小于1px的处理方式不同,有的采用四舍五入,有的大于某个值展示1px否则就舍去),从而导致圆角不圆;在ios下就没有这个问题

解决
1、 先把width,height的值放大一倍,然后用transform scale(.5)缩小一倍,接着用transform-origin调整下圆的位置就大功告成了

 i{
       display inline-block
       width .16rem
       height .16rem
       background-color #D0021B
       border-radius 50%
       transform scale(.5)
       transform-origin: 0% center
   }

2、设置圆角时,设置具体的数据,不用百分比的形式

3. 安卓手机line-height和height相等,文案垂直不居中

原因:推测可能是Android在排版计算的时候参考了primyfont字体的相关属性(即HHead Ascent、HHead Descent等),而primyfont的查找是看font-family里哪个字体在fonts.xml里第一个匹配上,而原生Android下中文字体是没有family name的,导致匹配上的始终不是中文字体,所以解决这个问题就要在font-family里显式申明中文,或者通过什么方法保证所有字符都fallback到中文字体

解决:设置字体为系统字体,在不是要求一定使用特殊字体的情况下可以参考以下字体的设置

// 通用设置
body {
  font-family: system-ui, —apple-system, Segoe UI, Roboto, Emoji, Helvetica, Arial, sans-serif;
}

// emoji字体
@font-face {
  font-family: Emoji;
  src: local("Apple Color Emojiji"), local("Segoe UI Emoji"), local("Segoe UI Symbol"), local("Noto Color Emoji");
  unicode-range: U+1F000-1F644, U+203C-3299;
}

// 衬线字体
.font-serif {
  font-family: Georgia, Cambria, "Times New Roman", Times, serif;
}

// 等宽字体
.font-mono {
  font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}

4. 安卓手机部分版本input的placeholder偏上

原因:未知
解决:设置input的line-height为normal

input {
    line-height: normal
}

5. 输入框在页面较底部时,安卓手机弹出的键盘会遮挡且点击原生键盘的关闭按钮收回键盘时,输入框没有失焦

原因:未知

解决
Element.scrollIntoView()和Element.scrollIntoViewIfNeeded()方法让当前的元素滚动到浏览器窗口的可视区域内。
使用Element.scrollIntoView()和Element.scrollIntoViewIfNeeded()解决遮挡问题,监听输入框聚焦事件,调用上面的方法将激活的元素(输入框)滚动到可视区域
通过监听页面resize事件来解决点击原生键盘的关闭按钮收回键盘时,输入框没有失焦的问题
可参考以下代码(使用vue实现)

data() {
    return {
        originHeight: 0,
        isAndroid:
            /Android/gi.test(navigator.userAgent) ||
            /adr/gi.test(navigator.userAgent),
        resizeTimer: null
    };
},
methods: {
    resizeFn() {
        // 防止部分手机触发两次resize事件导致无法拉起键盘
        if (this.resizeTimer) return;
        this.resizeTimer = setTimeout(() => {
            let resizeHeight =
                document.documentElement.clientHeight ||
                document.body.clientHeight;
            if (this.originHeight > resizeHeight) {
                // 拉起键盘会有动画,所以需要加延时,否则不管用
                setTimeout(() => {
                    if ("scrollIntoView" in document.activeElement) {
                        document.activeElement.scrollIntoView();
                    } else {
                        document.activeElement.scrollIntoViewIfNeeded();
                    }
                }, 0);
            } else {
                document.activeElement.blur();
                document.removeEventListener("resize", this.resizeFn);
            }
            clearTimeout(this.resizeTimer);
        }, 100);
    }
},
mounted() {
    if (this.isAndroid) {
        this.originHeight =
            document.documentElement.clientHeight ||
            document.body.clientHeight;
        window.addEventListener("resize", this.resizeFn);
    }
}

6. ios手机父元素设置了overflow:hidden和border-radius,子元素超出部分不隐藏

原因:未知
解决:在父元素加transform: rotate(0deg)属性

.father {
    transform: rotate(0deg)
}

7. ios手机页面滚动时动画停止

原因:iOS的事件处理机制有关,iOS最先响应屏幕反应。响应顺序依次为Touch——Media——Service——Core架构,当用户只要触摸接触了屏幕之后,系统就会最优先去处理屏幕显示也就是Touch这个层级,然后才是媒体(Media),服务(Service)以及Core架构。所以说,当系统接收到Touch事件之后会优先响应,此时会暂停屏幕上包括js、css的渲染。这个时候不光是css动画不动了,哪怕页面没有加载完如果你手指头还停留在屏幕上那么页面也不会继续加载,直到你的手松开

解决:给动画元素设置transform: translate3d(0, 0, 0);

.animation {
    transform: translate3d(0, 0, 0);
}

8. ios手机刘海屏和底部小黑条适配

原因:苹果公司提出的安全区域概念(safe area),简单的说就是我们的移动端页面可操作区域应该避开刘海区域和小黑条,因为在这两处地方的操作是不会响应我们的页面,即如果我们的按钮在这两块区域范围,那我们的点击就不会触发按钮上的事件

解决:官方给出的适配方案 iOS11同时新增了一个特性,constant(safe-area-inset-*),这是Webkit的一个CSS函数,用于获取安全区域与边界的距离,有四个预定义的变量(单位px):
safe-area-inset-left:安全区域距离左边界距离,横屏时适配
safe-area-inset-right:安全区域距离右边界距离,横屏时适配
safe-area-inset-top:安全区域距离顶部边界距离,竖屏下刘海屏为44px,iphone6系列20px,竖屏刘海适配关键
safe-area-inset-bottom:安全区域距离底部边界距离,竖屏下为34px,竖屏小黑条适配关键
一般使用 safe-area-inset-top,safe-area-inset-bottom

// 让页面占满全屏
<meta name="viewport" content="viewport-fil=cover">
// 使用@supports查询机型是否支持constant()或env()实现兼容代码隔离,个别安卓也会成功进入这个判断,因此加上-webkit-overflow-scrolling: touch的判断可以有效规避安卓机。
env() 是为了防止大于IOS11版本不支持constant()

@supports ((height: constant(safe-area-inset-top)) or (height: env(safe-area-inset-top))) and (-webkit-overflow-scrolling: touch) {
.fullscreen {
    /* 适配齐刘海 */
    padding-top: constant(safe-area-inset-top);
    padding-top: env(safe-area-inset-top);
    
    /* 适配底部小黑条 */
    padding-bottom: costant(safe-area-inset-bottom);
    padding-bottom: env(safe-area-inset-bottom);
  }
}

9. ios手机滚动卡顿

原因:未知
解决:给滚动的区域设置-webkit-overflow-scrolling: touch属性

.scroll {
    overflow: scroll;
    -webkit-overflow-scrolling: touch;
}

10. ios手机快速滚动页面卡死

原因:未知
解决:使用better-scroll插件做页面的滚动

11. ios手机最后一个子元素设置margin-bottom无效

原因:未知
解决:将margin-bottom改成padding-bottom

12. ios手机上输入框无法选中聚焦

原因:手贱设置了全部元素无法被选中(需求上不允许用户复制图片和文字)

* {
    webkit-user-select: none;
}

解决
输入框元素不设置这个属性,如果还存在问题,尝试在输入框和其父元素设置-webkit-user-select:text !important属性

13. ios系统14以上,设置特殊字体(UI提供的用在数字上的字体),钱币符号和数字设置相同的颜色,页面显示的颜色不一样

原因:字体原因
解决
1.使用系统字体
2.设置颜色时透明度设置为0.99

p {
    color: rgba(#B96E16, 0.99);
}

14. ios系统14以上,使用transform实现动画时,会出现闪屏的情况

原因:未知
解决:在做动画的元素的父元素设置transform: translate3d(0, 0, 1); backface-visibility: hidden;

.father {
    transform: translate3d(0, 0, 1);
    backface-visibility: hidden;
}

15. ios手机,图片使用transform rotateY()不显示的问题

原因:猜测是ios手机又视角概念,使用transform rotateY()旋转后,视角出现问题
解决:在父元素增加perspective: 1;

.father {
    perspective: 1;
}

16. transform导致z-index失效

原因:元素样式包含transform时形成新的堆叠上下文
解决
1.父级,任意父级,非body级别,设置overflow:hidden可恢复和其他浏览器一样的渲染。
2.元素设置transform: translateZ(120px)

17. 使用border-image后,border-radius无效

原因:未知
解决:设置父级元素背景渐变,设置padding,元素覆盖在上面营造成渐变边框的效果

18. 块级元素内嵌套图片,图片上下不居中

原因:未知
解决
1.在div内设置font-size和行高为0,使用flex布局居中
2.设置图片diaplay:block

19. 多个子元素设置为圆形时(测试结果为超过3个),或出现部分呈现椭圆形

原因:未知
解决:宽高等属性设置为两倍,然后缩放0.5

20. webview 前端上传图片被旋转

背景:用户上传图片,前端将这张图片绘制到canvas画布上
问题:绘制在canvas上的图片出现旋转(ios版本大于等于13.4.1的手机不需要前端对其调整图片方向,无论倒着拍,还是旋转拍,图片上传后的方向都是正确的,所以需要对ios的版本进行判断)
原因:在手机中默认横排才是正确的拍照姿势,如果我们手机竖着拿然后逆时针旋转90°这才是正确的拍照姿势,这时候拍出来的照片展示在canvas中是不会被旋转的。如果以其他角度拍搜时,就会发生旋转
解决

function imgToCanvasWithOrientation(img, width, height, orientation) {
    let canvas = document.createElement("canvas");
    let ctx = canvas.getContext("2d");
    canvas.width=width
    canvas.height=height
    if (判断机型系统不高于ios13.4.1) {
        switch (orientation) {
            case 3:
                ctx.rotate(Math.PI)
                ctx.drawImage(img, -width, -height, width, height);
                break;
            case 6:
                ctx.rotate(0.5 * Math.PI)
                ctx.drawImage(img, 0, -height, width, height);
                break;
            case 8:
                ctx.rotate(3 * Math.PI / 2)
                ctx.drawImage(img, -width, 0, width, height);
                break;
            default:
                ctx.drawImage(img, 0, 0, width, height);
                break
        }
    }
    
    return canvas;
}

21. 浏览器置于后台(移动端表现为去聊微信了),倒计时不准问题

原因:浏览器的“休眠”模式,页面未激活状态时,浏览器为节省性能,会停止或减少定时任务
解决:使用visibilitychange监听页面是否可见(激活)去重新拉取后台时间,使用setTimeout去进行倒计时,setTimeout会有误差,每次执行需要计算出减去误差后的时间作为下次执行的间隔

22. ios手机上input输入框设置opacity会导致聚焦拉起键盘时,不会把输入框挤到可视区域

原因:设置opacity小于等于0.01时,就会出现这个问题
解决:设置opacity大于0.01

.box {
    opacity:0.011
}

23. 移动端300ms延迟问题

原因:历史包袱问题:以前网站都是为大屏幕电脑设计的,手机上预览就会导致内容被缩小了,为解决这个问题,就约定双击屏幕就将网页等比例放大缩小,如何判断用户是否是双击了呢?那就在首次点击后等待300毫秒,判断用户是否再次点击了屏幕,点击了就判断是双击。这也是会有上述 300 毫秒延迟的主要原因
解决
1.在HTML文档头部添加如下meta标签,添加了user-scalable=no会禁止缩放

<meta name="viewport" content="width=device-width,user-scalable=no">

2.CSS touch-action属性

html {
    touch-action: none
}

24. 设置元素溢出隐藏时(overflow:hidden),部分安卓手机会把文字的头部切掉

原因:因为使用了rem的原因
解决:给元素设置line-height的值大于设置的font-size的值

.test {
  font-size: 20px;
  line-height: 24px
}

25. vite3创建的vue项目本地开发服务在ios12.1及以下版本的手机上白屏

原因:globalThis为undefined的原因
解决:给在入口的html文件将globalThis指向window

<script>
  if (globalThis === undefined) { var globalThis = window; }
</script>

26. ios手机上将图片转成base64失败

原因:转换需要给图片设置允许跨域,但是在ios手机上允许跨域和给src赋值有顺序的区别,在chrome模拟没顺序问题
解决:先给Image对象设置允许跨域,再给Image对象的src赋值
前端最全面试题整理(持续更新)_第12张图片

27. vite 创建的项目使用可选链操作符(?.)本地启动服务在 ios13.4 以下版本的手机白屏报错

原因:vite 在启动本地服务时,只处理语法转译不包含 polyfill,可选链操作符语法在 ios13.4 以下版本不支持,所以会报错白屏
解决:使用rollup-plugin-esbuild将可选链操作符语法降级到兼容低版本浏览器。如果是生产环境使用*@vitejs/plugin-legacy*

npm install rollup-plugin-esbuild -D
// vite.config.ts
import vue from "@vitejs/plugin-vue";
import legacy from "@vitejs/plugin-legacy";
import esbuild from "rollup-plugin-esbuild";

export default defineConfig(({ command }) => {
    const plugins = [vue()];
    if (command === "build") {
        plugins.push(
            legacy({
                targets: ["Android >= 8", "iOS >= 10"],
            })
        );
    }
    if (command === "serve") {
        plugins.push(
            esbuild({
                target: "ios12",
                loaders: { ".vue": "js", ".ts": "js" },
            })
        );
    }
    return {
        plugins,
    };
});

28. h5输入框被遮挡的解决方案

1、安卓手机里面由于在调出键盘的时候整个body高就是可视区的高,只需要fixed或者position为bottom:0就可以,
2、iphone手机在调出键盘的时候body的高度始终不变,在设备信息os 后面的版本号低于11的手机里面可以在聚焦以后把 document.body.scrollTop = document.body.scrollHeight; 放在定时器里面刷,失焦时解除定时器,版本号高于11的版本不需要做这一步document.body.scrollTop = document.body.scrollHeight

参考https://juejin.cn/post/7103835385280593957#heading-0

8、2X 图 3X 图适配

  1. 第一种方法 通过 css 的 DevicePixelRatio 媒体查询属性:

    /*默认大小*/
    .photo {background-image: url(image100.png);}
    /* 如果设备像素大于等于2,则用2倍图 */
    @media screen and (-webkit-min-device-pixel-ratio: 2),
    screen and (min--moz-device-pixel-ratio: 2) {
      .photo {
        background-image: url(image200.png);
        background-size: 100px 100px;
      }
    }
    /* 如果设备像素大于等于3,则用3倍图 */
    @media screen and (-webkit-min-device-pixel-ratio: 3),
    screen and (min--moz-device-pixel-ratio: 3) {
      .photo {
        background-image: url(image300.png);
        background-size: 100px 100px;
      }
    }
    .photo {width:100px;height:100px;}
    
  2. 第二种方法通过 scss 的mixin动态修改背景图片,判断设备dpr

    @mixin bg-image($url) {
        background-image: url($url + "@2x.png");
        @media (-webkit-min-device-pixel-ratio: 3),(min-device-pixel-ratio: 3) {
            background-image: url($url + "@3x.png");
        }
    }
    .div{
      width:30px;
      height:20px;
      background-size:30px  20px;
      background-repeat:no-repeat;
      //在这里相当于调用了上面媒体查询的方法 ,传入图片url
      @include bg-image('special_1');     
    }	```
    
  3. 第三种方法也是推荐的方法,通过img标签属性动态切换url:

    < img width="100" height="100" src="image100.png" srcset="image200.png 2x,image300.png 3x"/>
    

    浏览器会通过 srcset 属性来自动选择2X,3X图,比如用 iPhone 6s Plus,就会自动选择3x 的图像。

参考https://blog.csdn.net/azhou820567753/article/details/100232334

9、如何解决Android浏览器查看背景图片模糊的问题

用二倍图

这个问题是 devicePixelRatio 的不同导致的,因为手机分辨率太小,如果按照分辨率来显示网页,字会非常小,所以苹果系统当初就把 iPhone 4的960x640 像素的分辨率在网页里更改为480x320像素,这样 devicePixelRatio=2。而Android 的 devicePixelRatio比较乱,值有 1.5、2和3。为了在手机里更为清晰地显示图片,必须使用 2 倍宽高的背景图来代替 img标签(一般情况下都使用 2倍 )。

例如一个 div 的宽高是100px 100px,背景图必须是200px200px,然后设置 background-size:contain 样式,显示出来的图片就比较清晰了

10、click 的 300ms 延迟问题和点击穿透问题

方案一:禁用缩放

<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=1,maximum-scale=1">

表明这个页面是不可缩放的,那双击缩放的功能就没有意义了,此时浏览器可以禁用默认的双击缩放行为并且去掉300ms的点击延迟。
这个方案有一个缺点,就是必须通过完全禁用缩放来达到去掉点击延迟的目的,然而完全禁用缩放并不是我们的初衷,我们只是想禁掉默认的双击缩放行为,这样就不用等待300ms来判断当前操作是否是双击。但是通常情况下,我们还是希望页面能通过双指缩放来进行缩放操作,比如放大一张图片,放大一段很小的文字

方案二:更改默认的视口宽度

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

因为双击缩放主要是用来改善桌面站点在移动端浏览体验的,而随着响应式设计的普及,很多站点都已经对移动端坐过适配和优化了,这个时候就不需要双击缩放了,如果能够识别出一个网站是响应式的网站,那么移动端浏览器就可以自动禁掉默认的双击缩放行为并且去掉300ms的点击延迟。如果设置了上述meta标签,那浏览器就可以认为该网站已经对移动端做过了适配和优化,就无需双击缩放操作了。
这个方案相比方案一的好处在于,它没有完全禁用缩放,而只是禁用了浏览器默认的双击缩放行为,但用户仍然可以通过双指缩放操作来缩放页面。

方案三:CSS touch-action
跟300ms点击延迟相关的,是touch-action这个CSS属性。这个属性指定了相应元素上能够触发的用户代理(也就是浏览器)的默认行为。如果将该属性值设置为touch-action: none,那么表示在该元素上的操作不会触发用户代理的任何默认行为,就无需进行300ms的延迟判断。

方案四:FastClick
FastClick 是 FT Labs 专门为解决移动端浏览器 300 毫秒点击延迟问题所开发的一个轻量级的库。FastClick的实现原理是在检测到touchend事件的时候,会通过DOM自定义事件立即出发模拟一个click事件,并把浏览器在300ms之后的click事件阻止掉

点击穿透问题

  1. 使用touchstart
    第一:touchstart是手指触摸屏幕就触发,有时候用户只是想滑动屏幕,却触发了touchstart事件,这不是我们想要的结果;
    第二:使用touchstart事件在某些场景下可能会出现点击穿透的现象。
  2. 在vue中可以直接使用click.stop,js的解决办法是,直接在事件的方法中添加event.stopPropagation()

11、 phone 及 ipad 下输入框默认内阴影

element{
	-webkit-appearance:none
}

12、px、em、rem、%、vw、vh这些单位的区别

  1. px:像素
  2. em:参考物是父元素的font-size,默认字体大小是16px,所以1em不是固定值,因为它会继承父元素的字体大小
  3. rem:rem参考物是相对于根元素,我们在使用时可以在根元素设置一个参考值即可,相对于em使用,减少很大运算工作量,例:html大小为10px,12rem就是120px
  4. % :是相对于父元素的大小设定的比率
  5. vw:vw相对于视口的宽度。视口被均分为100单位,浏览器宽度1200px, 1 vw = 1200px/100 = 12 px
  6. vh:相对于视口的高度。视口被均分为100单位,浏览器高度900px, 1 vh = 900px/100 = 9 px

13、移动端适配- dpr 浅析

dpr = 物理像素 / css 像素

14、移动端扩展点击区域

  • 父级代理事件
  • 将 a 标签设置成块级元素

15、长时间按住页面出现闪退

element{
	-webkit-touch-callout:none
}

16、ios 和 android 下触摸元素时出现半透明灰色遮罩

element{
	-webkit-tap-highlight-color:rgba(255,255,255,0) /*设置透明*/
}

17、 active 兼容处理 即 伪类:active 失效

在iOS系统的移动设备中,需要在按钮元素或body/html上绑定一个touchstart事件才能激活:active状态。

第一种方案

<body ontouchstart="">

第二种方案

document.body.addEventListener('touchstart', function () { //...空函数即可});  

18、 webkit mask 兼容处理

CSS 属性 mask 允许使用者通过遮罩或者裁切特定区域的图片的方式来隐藏一个元素的部分或者全部可见区域。

mask-image这两个单词翻译过来就是,面具 图片,的确很形象,真的就像是给元素带上一个面具一样

使用方法跟ackground类似

兼容处理:

  1. css属性:-webkit-mask-image;这是最简单的方式。
    例如:-webkit-mask-image: url(mouse.png);或者-webkit-mask-image: -webkit-linear-gradient(top, rgba(0,0,0,1), rgba(0,0,0,0));等

  2. SVG标签:标签调用标签;

    <svg>
    	<defs> <mask id="mask" maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse">   遮罩图片 </mask> </defs>  
    	<foreignObject width="400px" height="300px" style="mask: url(#mask);">被遮对象</foreignObject>
    </svg>
    

参考
https://segmentfault.com/a/1190000011838367
https://www.likecs.com/show-307452954.html

19、 transiton 闪屏

.wrap {   
	-webkit-transform-style: preserve-3d;   /*设置内联的元素在 3D 空间如何呈现:保留
3D*/
	-webkit-backface-visibility: hidden;   /*设置进行转换的元素的背面在面对用户时是否可见:隐藏*/
	-webkit-perspective: 1000;  
}

20、如何解决android手机圆角失效问题

background-clip: padding-box;

前端工程化

一、Babel

1、 Babel 简介

Babel是什么?

Babel是一个工具集,主要用于将ES6版本的JavaScript代码转为ES5等向后兼容的JS代码,从而可以运行在低版本浏览器或其它环境中。

因此,你完全可以在工作中使用ES6编写程序,最后使用Babel将代码转为ES5的代码,这样就不用担心所在环境是否支持了。下面是一个示例:

转换前,代码里使用ES6箭头函数

var fn = (num) => num + 2;

转换后,箭头函数变成ES5的普通函数。这样就可以在不支持箭头函数的浏览器里运行了

  var fn = function fn(num) {
    return num + 2;
  }

使用Babel进行ES6转ES5时,转化之后默认是严格模式

2、 Babel 快速入门

2.1 Babel的安装,配置与转码

Babel依赖Node.js,没有安装的话,去官网下载安装最新稳定版本的Node.js。

在本地新建一个文件夹babel01,在该文件夹下新建一个js文件,文件命名为babel.config.js。该文件是 Babel配置文件 ,我们在该文件里输入如下内容:

  module.exports = {
    presets: ["@babel/env"],
    plugins: []
  }

然后在该文件夹下新建一个js文件main.js,该js里的代码是我们需要转译的,我们写入代码

var fn = (num) => num + 2;

然后执行下面的命令安装三个npm包,这些npm包是Babel官方包

 // npm一次性安装多个包,包名之间用空格隔开
  npm install --save-dev @babel/cli @babel/core @babel/preset-env

安装完成后,执行下面的命令进行转码,该命令含义是把main.js转码生成compiled.js文件

npx babel main.js -o compiled.js

此时文件夹下会生成compiled.js,该文件是转换后的代码:

  "use strict";
  var fn = function fn(num) {
    return num + 2;
  };

这就是一个最简单的Babel使用过程,我们把用ES6编写main.js转换成了ES5的compiled.js。

2.2 Babel转码说明

babel.config.js是Babel执行时会默认在当前目录寻找的Babel配置文件。

除了babel.config.js,我们也可以选择用.babelrc.babelrc.js这两种配置文件,还可以直接将配置参数写在package.json。它们的作用都是相同的,只需要选择其中一种,接下来默认使用babel.config.js

@babel/cli@babel/core与@babel/preset-env是Babel官方的三个包,它们的作用如下:

  • @babel/cli是Babel命令行转码工具,如果我们使用命令行进行Babel转码就需要安装它。
  • @babel/cli依赖@babel/core,因此也需要安装@babel/core这个Babel核心npm包。
  • @babel/preset-env这个npm包提供了ES6转换ES5的语法转换规则,我们在Babel配置文件里指定使用它。如果不使用的话,也可以完成转码,但转码后的代码仍然是ES6的,相当于没有转码。

2.3 小结

  1. 一个完整的Babel转码工程通常包括如下

    • Babel配置文件
    • Babel相关的npm包
    • 需要转码的JS文件
  2. 我们通过以下命令对单个JS文件进行转码:

      npx babel main.js -o compiled.js
    

2.4 注:

  1. 如果安装npm包慢的话,通过以下命令设置npm镜像源为淘宝npm后再安装

    npm config set registry https://registry.npm.taobao.org
    
  2. npx babel main.js -o compiled.js命令里npx是新版Node里附带的命令。它运行的时候默认会找到node_modules/.bin/下的路径执行

3、引入polyfill

总体来说,Babel的主要工作有两部分:

  • 语法转换
  • 补齐API

上一节Babel快速入门我们讲的是其实是用Babel进行语法转换,把ES6的箭头函数语法转换成了ES5的函数定义语法。 箭头函数语法、async函数语法、class定义类语法和解构赋值等等都是ES6新增的语法

那什么是补齐API? 简单解释就是,通过 Polyfill 的方式在目标环境中添加缺失的特性

我们按照上一节的操作对var promise = Promise.resolve(‘ok’)进行转换,会发现转换后的代码并没有改变,过程如下。

我们新建babel02文件夹,新建babel配置文件 babel.config.js ,内容如下

 module.exports = {
    presets: ["@babel/env"],
    plugins: []
  }

新建main.js文件,内容如下

  var fn = (num) => num + 2;
  var promise = Promise.resolve('ok')

然后执行下面的命令安装三个npm包

 // npm一次性安装多个包,包名之间用空格隔开
  npm install --save-dev @babel/cli @babel/core @babel/preset-env

然后执行命令

 npx babel main.js -o compiled.js

整个过程与上一节基本一样,只是main.js里的代码多了一行

var promise = Promise.resolve('ok')

此时文件夹下会生成新的compiled.js,代码如下

"use strict";
var fn = function fn(num) {
  return num + 2;
};
var promise = Promise.resolve('ok');

我们观察转换后生成的compiled.js代码,发现Babel并没有对ES6的Promise进行转换

我们通过一个index.html文件引用转码后的 compiled.js ,在比较老的浏览器( 例如火狐27 )里打开HTML文件后后控制台报错:Promise is not defined。

为何 Babel没有对ES6的Promise进行转换 ?

因为Babel默认只转换新的JavaScript语法(syntax),而不转换新的 API。

新的API分类两类,一类是Promise、Map、Symbol、Proxy、Iterator等全局对象及其对象自身的方法,例如Object.assign,Promise.resolve;另一类是新的实例方法,例如数组实例方法[1, 4, -5, 10].find((item) => item < 0)

如果想让ES6新的API在低版本浏览器正常运行,我们就不能只做语法转换。

在前端web工程里,最常规的做法是使用polyfill,为当前环境提供一个垫片。所谓垫片,是指垫平不同浏览器之间差异的东西。polyfill提供了全局的ES6对象以及通过修改原型链Array.prototype等实现对实例的实现

polyfill广义上讲是为环境提供不支持的特性的一类文件或库,狭义上讲是polyfill.js文件以及@babel/polyfill这个npm包

我们可以直接在html文件引入polyfill.js文件来作为全局环境垫片, polyfill.js 有Babel官方的 polyfill.js,也有第三方的。我们引入一个Babel官方已经构建好的polyfill脚本。

简单起见,我们通过在html里引入polyfill.js的方式。

<script src="https://cdn.bootcss.com/babel-polyfill/7.6.0/polyfill.js"></script>

我们在IE9打开验证,也可以用Firefox27等低版本的浏览器验证。这个时候发现可以正常运行了

补齐API的方式除了通过引入 polyfill.js 文件 ,还有通过在构建工具入口文件(例如webapck),babel配置文件等方式进行。本节讲的通过在HTML里直接引入 polyfill.js 文件 这种方式进行在现代前端工程里逐渐淘汰,很少使用了。但这种方式对初学者理解 polyfill 是做什么的是简单直接的。后续章节我们会学习到其它补齐API的方式

小结
本节讲了通过 polyfill.js 文件来补齐代码运行时环境所缺失的API。

通过上一节讲的语法转换和本节讲的补齐API,就可以使一个使用ES6编写项目完整运行了不支持ES6语言的环境上了。

4、 Babel 深入

4.1 关于Babel版本

目前,前端开发领域使用的Babel版本主要的Babel6和Babel7这两个版本。

你可能想问,怎么查看使用的Babel是哪个版本?

在入门章节,我们讲过Babel是一个工具集,而这个工具集是围绕@babel/core这个核心npm包构成的。每次@babel/core发布新版本的时候,整个工具集的其它npm包也都会跟着升级到与@babel/core相同的版本号,即使它们的代码可能一行都没有改变

因此,我们提到Babel版本的时候,通常是指@babel/core这个Babel核心包的版本

在一次次版本变更的过程中,很多Babel工具以及npm包等都发生了变化,导致其配置文件有各种各样的写法。同时,很多Babel相关的文章没有注意到版本的问题,这给学习者也造成了很大的困惑。

web前端开发有必要了解这两个版本的变化。

Babel7的npm包都是放在babel域下的,即在安装npm包的时候,我们是安装@babel/这种方式,例如@babel/cli、@babel/core等。而在Babel6,我们安装的包名是babel-cli,babel-core等。其实它们本质是一样的,都是Babel官方的cli命令行工具和core核心包,而且功能是一样的,只是名称版本变化了一下而已。在平时开发和学习的过程中,碰到’@babel/'和’babel-'应该下意识认识到他俩原本是一个包,只是版本不一样而已。

4.2 Babel 配置文件

  1. 配置文件

    无论是通过命令行工具babel-cli来进行编译,还是webpack这类的构建工具,通常情况下,我们都需要建立一个Babel配置文件来指定编译的规则。

    Babel的配置文件是Babel执行时默认会在当前目录寻找的文件,主要有.babelrc,.babelrc.js,babel.config.js和package.json。它们的配置项都是相同,作用也是一样的,只需要选择其中一种。

    对于.babelrc,它的配置是这样子

     {
        "presets": ["es2015", "react"],
        "plugins": ["transform-decorators-legacy", "transform-class-properties"]
      }
    

    对于babel.config.js和.babelrc.js,它的配置是一样的,通过module.exports输出配置项

    module.exports = {
        "presets": ["es2015", "react"],
        "plugins": ["transform-decorators-legacy", "transform-class-properties"]
      }
    

    对于package.json,就是在package.json中增加一个babel属性和值,它的配置是这样子

     {
        "name": "demo",
        "version": "1.0.0",
        "description": "",
        "main": "index.js",
        "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1"
        },
        "author": "",
        "babel": {
          "presets": ["es2015", "react"],
          "plugins": ["transform-decorators-legacy", "transform-class-properties"]
        }
      }
    

    仔细观察上述几种配置文件,会发现它们的配置项其实都是plugins和presets。

    另外,除了把配置写在上述这几种配置文件里,我们也可以写在构建工具的配置里。对于不同的构建工具,Babel也提供了相应的配置项,例如webpack的babel-loader的配置项,其本质和配置文件是一样的

    配置文件总结起来就是配置pluginspresets这两个数组,我们分别称之为插件数组和预设数组

    除了plugins和presets这两个配置项,还有minified、ignore等,但我们平时都用不到,大家还是把精力放在plugins和presets上

    推荐使用后缀名是js配置文件,因为可以使用js做一些逻辑处理,适用性更强。举一个例子

    //  这里只是举个例子,实际项目中,我们可以传入环境变量等来做处理
      var year = 2020;
      var presets = [];
      if (year > 2018) {
        presets = ["@babel/env"];
      } else {
        presets = "presets": ["es2015", "es2016", "es2017"],
      }
      module.exports = {
        "presets": presets,
        "plugins": []
      }
    
  2. 插件与预设

    plugin代表插件,preset代表预设,它们分别放在plugins和presets,每个插件或预设都是一个npm包。

    本节开头提到了通过Babel配置文件来指定编译的规则,所谓编译的规则,就是在配置文件里列出的编译过程中会用到的Babel插件或预设。这些插件和预设会在编译过程中把我们的ES6代码转换成ES5。

    Babel插件的数量非常多,处理ES2015的有

    • @babel/plugin-transform-arrow-functions
    • @babel/plugin-transform-block-scoped-functions
    • @babel/plugin-transform-block-scoping
    • ……

    处理ES2018的有

    • @babel/plugin-proposal-async-generator-functions
    • @babel/plugin-transform-dotall-regex
    • ……

    所有的插件都需要先安装npm包到node_modules后才可以使用。

    Babel插件实在太多,假如只配置插件数组,那我们前端工程要把ES2015,ES2016,ES2017…下的所有插件都写到配置项里,我们的Babel配置文件会非常臃肿。

    preset预设就是帮我们解决这个问题的。预设是一组Babel插件的集合,用大白话说就是插件包,例如babel-preset-es2015就是所有处理es2015的二十多个Babel插件的集合。这样我们就不用写一大堆插件配置项了,只需要用一个预设代替就可以了。另外,预设也可以是插件和其它预设的集合。Babel官方已经对常用的环境做了一些preset包

    • @babel/preset-env
    • @babel/preset-react
    • @babel/preset-typescript
    • @babel/preset-stage-0
    • @babel/preset-stage-1

    所有的预设也都需要先安装npm包到node_modules。

  3. plugin与preset的短名称

    插件可以在配置文件里写短名称,如果插件的npm包名称的前缀为 babel-plugin-,可以省略前缀。例如

    module.exports = {
        "presets": [],
        "plugins": ["babel-plugin-transform-decorators-legacy"]
      }
    

    可以写成短名称

    module.exports = {
        "presets": [],
        "plugins": ["transform-decorators-legacy"]
      }
    

    如果npm包名称的前缀带有npm作用域@,例如@org/babel-plugin-xxx,短名称可以写成@org/xxx。

    目前Babel7的官方npm包里绝大部分插件已经升级为@babel/plugin-前缀的,这种情况的短名称比较特殊了,绝大部分可以像babel-plugin-那样省略@babel/plugin-。但babel官方并没有给出明确的说明,所以还是推荐用全称。

    预设的短名称规则与插件的类似,预设npm包名称的前缀为babel-preset-或作用域@xxx/babel-preset-xxx的可以省略掉babel-preset-。

    对于Babel7的官方npm包里绝大部分预设已经升级为@babel/preset-前缀的,这种情况的短名称比较特殊了,绝大部分可以像babel-preset-那样省略@babel/preset-。但babel官方并没有给出明确的说明,例如,@babel/preset-env的短名称就是@babel/env,所以还是推荐用全称。

    plugins插件数组和presets预设数组是有顺序要求的。如果两个插件或预设都要处理同一个代码片段,那么会根据插件和预设的顺序来执行。规则如下:

    • 插件比预设先执行
    • 插件执行顺序是插件数组从前向后执行
    • 预设执行顺序是预设数组从后向前执行
  4. Babel插件和预设的参数

    每个插件是插件数组的一成员项,每个预设是预设数组的一成员项,默认情况下,成员项都是用字符串来表示的,例如"@babel/preset-env"。

    如果要给插件或预设设置参数,那么成员项就不能写成字符串了,而要改写成一个数组。数组的第一项是插件或预设的名称字符串,第二项是个对象,该对象用来设置第一项代表的插件或预设的参数。例如给@babel/preset-env设置参数:

    {
        "presets": [
          [
            "@babel/preset-env",
            {
              "useBuiltIns": "entry"
            }
          ]
        ]
      }
    

4.3 Babel 预设与插件的选择

Babel7.8官方的插件和预设目前有100多个,数量这么多,我们一个个都学习其作用是要花费大量时间的。

不过,我们没有必要全部学习。在我们现在的web前端工程里,常用的插件和预设其实只有几个。抓住重点,有的放矢地学习这几个,然后举一反三,这是最快掌握Babel的途径。

  1. preset预设的选择
    在Babel6的时代,常见的preset有babel-preset-es2015、babel-preset-es2016、babel-preset-es2017、babel-preset-latest、babel-preset-stage-0、babel-preset-stage-1、babel-preset-stage-2等。

    babel-preset-es2015、babel-preset-es2016、babel-preset-es2017分别是TC39每年发布的进入标准的ES语法的转换器预设,我们在这里称之为年代preset。

    目前,Babel官方不再推出babel-preset-es2017以后的年代preset了。

    babel-preset-stage-0、babel-preset-stage-1、babel-preset-stage-2、babel-preset-stage-3是TC39每年草案阶段的ES语法转换器预设

    从Babel7版本开始,上述的预设都已经不推荐使用了,babel-preset-stage-X因为对开发造成了一些困扰,也不再更新。

    babel-preset-latest,在Babel6的时候是你在使用它的时候所有年代preset的集合,在Babel6最后一个版本,它是babel-preset-es2015、babel-preset-es2016、babel-preset-es2017这三个的集合。因为Babel官方不再推出babel-preset-es2017以后的年代preset了,所以babel-preset-latest定义变成了TC39每年发布的进入标准的ES语法的转换器预设集合。其实,和Babel6时的内涵是一样的。

    @babel/preset-env包含了babel-preset-latest的功能,并对其进行增强,现在@babel/preset-env完全可以替代babel-preset-latest。

    经过一番梳理,可以总结为以前要用到的那么多preset预设,现在只需一个@babel/preset-env就可以了

    在实际开发过程中,除了使用@babel/preset-env对标准的ES6语法转换,我们可能还需要类型检查和react等预设对特定语法转换。这里有三个官方预设可以使用:

    • @babel/preset-flow
    • @babel/preset-react
    • @babel/preset-typescript

    总结起来,Babel官方的preset,我们实际可能会用到的其实就只有4个:

    • @babel/preset-env
    • @babel/preset-flow
    • @babel/preset-react
    • @babel/preset-typescript

    一个普通的vue工程,Babel官方的preset只需要配一个"@babel/preset-env"就可以了。

  2. plugin插件的选择

    虽然Babel7官方有90多个插件,不过大半已经整合在@babel/preset-env和@babel/preset-react等预设里了,我们在开发的时候直接使用预设就可以了。

    目前比较常用的插件只有@babel/plugin-transform-runtime。目前我做过的几个项目,前端工程已经很少见到里使用其它的插件了。

  3. 小结

    这节课我们主要学习了插件和预设的选择,经过一番筛选后,我们找出了会在我们开发的过程中可能用到4个预设和1个插件。分别是@babel/preset-env@babel/preset-flow@babel/preset-react@babel/preset-typescript这4个预设,以及 @babel/plugin-transform-runtime 这1个插件。

4.4 babel-polyfill

babel-polyfill在Babel7以后名字是@babel/polyfill

polyfill广义上讲是为环境提供不支持的特性的一类文件或库,既有Babel官方的库,也有第三方的。babel-polyfill指的是Babel官方的polyfill,本教程默认使用babel-polyfill。polyfill传统上分两类,一类是已构建成JS文件的polyfill.js,另一类是未构建的需要安装npm包@babel/polyfill。因为@babel/polyfill本质是由两个npm包core-js与regenerator-runtime组合而成的,所以在使用层面上还可以再细分为是引入@babel/polyfill本身还是其组合子包。

总体来说,Babel官方的polyfill使用方法主要有如下几种:

  1. 直接在html文件引入Babel官方的polyfill.js脚本文件;
  2. 在前端工程的入口文件里引入polyfill.js;
  3. 在前端工程的入口文件里引入@babel/polyfill;
  4. 在前端工程的入口文件里引入core-js/stable与regenerator-runtime/runtime;
  5. 在前端工程构建工具的配置文件入口项引入polyfill.js;
  6. 在前端工程构建工具的配置文件入口项引入@babel/polyfill;
  7. 在前端工程构建工具的配置文件入口项引入core-js/stable与regenerator-runtime/runtime;

所有的例子,我们仍以火狐27.0不支持的Promise为例,进行演示。该版本的火狐,在遇到如下代码的时会报错

var promise = Promise.resolve('ok');
console.log(promise);

报错信息为:ReferenceError: Promise is not defined

我们需要做的就是让火狐27.0可以正常运行我们的代码,下面对上文提到的7种方法进行讲解。

  1. 直接在html文件引入Babel官方的polyfill.js脚本文件
    该方法在分类上属于使用已构建成JS文件polyfill.js的一类,该方法在引入polyfill一节已经讲过,本节不再重复讲解。

  2. 在前端工程的入口文件里引入polyfill.js
    该方法在分类上属于使用已构建成JS文件polyfill.js的一类,该我们以目前业界最为流行的webpack打包工具为例,讲述该方法。

    我们的工程里有a.js与index.html文件,a.js文件的内容是

    var promise = Promise.resolve('ok');
      console.log(promise);
    

    index.html文件在head标签里直接引入了a.js文件,这个时候在火狐27.0下打开该html会报错。

    在之前的例子里,我们是在index.html里单独引入了polyfill.js文件对API进行补齐。现在,我们换一种方式,通过在工程入口文件a.js引入polyfill.js进行补齐。

    我们使用webpack来讲述这个过程,首先进行webpack和其命令行工具的安装

    npm install webpack webpack-cli --save-dev
    

    在webpack4.0和node8.2以上版本,我们可以使用npx webpack a.js -o b.js命令来进行打包。该命令的意思是,指定工程入口文件是a.js,最后打包成b.js。

    为了方便,我们在package.json里配置scripts项,现在,只需要执行npm run dev,就会自动执行npx webpack a.js -o b.js命令,即可完成打包。

     "scripts": {
        "dev": "npx webpack a.js -o b.js"
      },
    

    在我们这个例子里,前端工程入口文件是a.js,我们只需要在a.js最上方加入一句

    import './polyfill.js';
    

    在这里插入图片描述
    然后执行npm run dev,就可以把polyfill打包到我们的最终生成的文件里(我们需要提前在相应的文件目录里存放polyfill.js)。

    现在,我们把index.html使用的a.js改成b.js,然后在火狐27.0打开,可以看到控制台已经正常。

  3. 在前端工程的入口文件里引入@babel/polyfill
    该方法在分类上属于使用未构建的需要安装npm包@babel/polyfill的一类,其实整个过程和上面的例子非常像,不一样的地方如下。

    1. a.js里的import './polyfill.js';改成 import '@babel/polyfill';
      2)删除工程目录下的polyfill.js文件,同时安装@babel/polyfill这个npm包 npm install --save @babel/polyfill

    除了这两点,其余的地方和上面的例子完全。

    执行npm run dev,然后和之前一样在火狐打开进行验证正常。

  4. 在前端工程的入口文件里引入core-js/stable与regenerator-runtime/runtime
    该方法在分类上属于使用未构建的需要安装npm包@babel/polyfill的组合子包的一类,我们仍以目前业界最为流行的webpack构建工具为例,讲述该方法。后续默认是使用webpack。

    该方法需要我们单独安装单独安装core-js与regenerator-runtime这两个npm包,这种方式core-js是默认是3.x.x版本。
    需要注意的是,我们使用该方法的时候,不能再安装@babel/polyfill了。因为@babel/polyfill在安装的时候,会自动把core-js与regenerator-runtime这两个依赖安装上了,而@babel/polyfill使用的core-js已经锁死为2.x.x版本了。core-js的2.x.x版本里并没有stable文件目录,所以安装@babel/polyfill后再引入core-js/stable会报错。

    其实这个方法和上面的例子也是非常像,就是把一个npm包换成了两个而已。不一样的地方具体如下

    1. a.js里的import '@babel/polyfill';改成如下
      import "core-js/stable";
      import "regenerator-runtime/runtime";
      

    2)安装两个npm包core-js和regenerator-runtime

     ```
      npm install --save core-js regenerator-runtime
     ```
    

    替换之前安装的@babel/polyfill

    执行npm run dev,然后和之前一样在火狐打开进行验证正常。

  5. 在前端工程构建工具的配置文件入口项引入polyfill.js

    webpack的配置文件有多种类型,我们采用webpack.config.js,其它类型的webpack配置文件与其一致。

    因为要在webpack配置文件里指定入口文件,我们就不手动使用webpack a.js -o b.js来进行打包了,而是在webpack.config.js进行设置。

      const path = require('path');
      module.exports = {
        entry: ['./a.js'],
        output: {
          filename: 'b.js',
          path: path.resolve(__dirname, '')
        },
        mode: 'development'
      };
    

webpack的配置文件的入口项是entry,这里entry的值我们设置成数组,a.js就是入口文件。然后,package.json里的dev命令改为

  "scripts": {
    "dev": "npx webpack"
  },

现在我们执行npm run dev,webpack就完成了打包。现在我们index.html直接引用b.js,火狐27会报错。原因我们都知道,我们没有使用polyfill。

那么,在前端工程构建工具的配置文件入口项引入polyfill.js,该怎么操作呢?

其实很简单,那就是把数组的第一项改成’./polyfill.js’,原先的入口文件作为数组的第二项,polyfill就会打包到我们生成后的文件里了。

  const path = require('path');
  module.exports = {
    entry: ['./polyfill.js', './a.js'],
    output: {
      filename: 'b.js',
      path: path.resolve(__dirname, '')
    },
    mode: 'development'
  };

现在再执行npm run dev进行打包,然后index.html就不会在火狐27里报错了。

  1. 在前端工程构建工具的配置文件入口里引入@babel/polyfill;

    如果你对之前讲的方法都理解的话,那么相信你也会很容易理解该方法。该方法就是把上个方法的entry的第一项换成@babel/polyfill,并且安装了@babel/polyfill这个包就可以了。

    npm install --save @babel/polyfill
    

    webpack.config.js配置如下

      const path = require('path');
      module.exports = {
        entry: ['@babel/polyfill', './a.js'],
        output: {
          filename: 'b.js',
          path: path.resolve(__dirname, '')
        },
        mode: 'development'
      };
    

现在再执行npm run dev进行打包,然后index.html就不会在火狐27里报错了。

  1. 在前端工程构建工具的配置文件入口里引入core-js/stable与regenerator-runtime/runtime
    其实这个方法和上面的例子也是非常像,就是把一个npm包换成了两个而已。我们需要做的就是安装两个npm包
    npm install --save core-js regenerator-runtime
    
    然后webpack.config.js的entry项数组的前两项改为core-js/stable和regenerator-runtime/runtime
      const path = require('path');
      module.exports = {
        entry: ['core-js/stable', 'regenerator-runtime/runtime', './a.js'],
        output: {
          filename: 'b.js',
          path: path.resolve(__dirname, '')
        },
        mode: 'development'
      };
    
    现在再执行npm run dev进行打包,然后index.html就可以正常在火狐27.0运行了。

从babel7.4开始,官方不推荐再使用@babel/polyfill了,因为@babel/polyfill本身其实就是两个npm包的集合:core-js与regenerator-runtime。

官方推荐直接使用这两个npm包。虽然@babel/polyfill还在进行版本升级,但其使用的core-js包为2.x.x版本,而core-js这个包本身已经发布到了3.x.x版本了,@babel/polyfill以后也不会使用3.x.x版本的包了。新版本的core-js实现了许多新的功能,例如数组的includes方法。

虽然从babel7.4开始,不推荐再使用@babel/polyfill了,但我们仍然把传统@babel/polyfill的使用方式在本节进行讲解,这对于理解其使用方式是非常有帮助的。

ES6补齐API的方式,除了上述几种在前端工程入口文件或构建工具的配置文件里使用polyfill(或是其子包)的方式,还有使用Babel预设或插件进行补齐API的方式。
上述使用polyfill的方式,是把整个npm包或polyfill.js放到了我们最终的项目里了。完整的polyfill文件非常大,会影响我们的页面加载时间。
如果我们的运行环境已经实现了部分ES6的功能,那实在没有必要把整个polyfill都给引入。我们可以部分引入,这个时候需要使用Babel预设或插件了。

Babel预设或插件不光可以进行补齐API,还可以对API进行转换

小结

  1. 本节对使用polyfill进行了详细的梳理与讲解,把每一种使用方法都进行了讲述,并配有代码以便大家理解。
  2. 这么多的方法,在实际开发中该选择哪一种呢?从babel7.4版本开始,Babel官方已经不推荐再使用@babel/polyfill,这也包括官方的polyfill.js库文件。因此从2019年年中开始,我们的新项目都应该使用core-js和regenerator-runtime这两个包。即,我们应选择方法4与方法7。这两种方法,都是把两个npm包全部都引入到了我们的前端打包后的文件里了,对于部分引入的方法,我们将在后面两节进行讲解。

4.5 @babel/preset-env

在Babel6时代,这个预设名字是 babel-preset-env,在Babel7之后,改成@babel/preset-env

@babel/preset-env是整个Babel大家族最重要的一个preset。不夸张地说,所有配置项仅需要它自己就可以完成现代JS工程所需要的所有转码要求。

在使用它之前,需要先安装

 npm install --save-dev @babel/preset-env

@babel/preset-env是Babel6时代babel-preset-latest的增强版。该预设除了包含所有稳定的转码插件,还可以根据我们设定的目标环境进行针对性转码。

在Babel快速入门一节,我们简单使用过@babel/preset-env的语法转换功能。除了进行语法转换,该预设还可以通过设置参数项进行针对性语法转换以及polyfill的部分引入。

@babel/preset-env的参数项,数量有10多个,但大部分我们要么用不到,要么已经或将要弃用。这里建议大家掌握重点的几个参数项,有的放矢。重点要学习的参数项有targets、useBuiltIns、modules和corejs这四个,能掌握这几个参数项的真正含义,就已经超过绝大部分开发者了。

对于preset,当我们不需要对其设置参数的时候,其写法是只需要把该preset的名字放入presets对于的数组里即可,例如

  module.exports = {
    presets: ["@babel/env"],
    plugins: []
  }

注意,@babel/env是@babel/preset-env的简写

如果需要对某个preset设置参数,该preset就不能以字符串形式直接放在presets的数组项了。而是应该再包裹一层数组,数组第一项是该preset字符串,数组第二项是该preset的参数对象。如果该preset没有参数需要设置,则数组第二项可以是空对象或者不写第二项。以下几种写法是等价的:

module.exports = {
    presets: ["@babel/env"],
    plugins: []
  }
 module.exports = {
    presets: [["@babel/env", {}]],
    plugins: []
  }
 module.exports = {
    presets: [["@babel/env"]],
    plugins: []
  }

如果你使用过vue或react的官方脚手架cli工具,你一定会在其package.json里看到browserslist项,下面该项配置的一个例子:

  "browserslist": [
    "> 1%",
    "not ie <= 8"
  ]

上面的配置含义是,目标环境是市场份额大于1%的浏览器并且不考虑IE8及以下的IE浏览器。Browserslist叫做目标环境配置表,除了写在package.json里,也可以单独写在工程目录下.browserslistrc文件里。我们用browserslist来指定代码最终要运行在哪些浏览器或node.js环境。Autoprefixer、postcss等就可以根据我们的browserslist,来自动判断是否要增加CSS前缀(例如’-webkit-')。我们的Babel也可以使用browserslist,如果你使用了@babel/preset-env这个预设,此时Babel就会读取browserslist的配置。

如果我们的@babel/preset-env不设置任何参数,Babel就会完全根据browserslist的配置来做语法转换。如果没有browserslist,那么Babel就会把所有ES6的语法转换成ES5版本。

在本教程最初的例子里,我们没有browserslist,并且@babel/preset-env的参数项是空的,ES6箭头函数语法被转换成了ES5的函数定义语法。

转换前

var fn = (num) => num + 2;

转换后

  "use strict";
  var fn = function fn(num) {
    return num + 2;
  };

如果我们在browserslist里指定目标环境是Chrome60,我们再来看一下转换结果

  "browserslist": [
    "chrome 60"
  ]

在这里插入图片描述
转换后

  "use strict";
  var fn = num => num + 2;

我们发现转换后的代码仍然是箭头函数,因为Chrome60已经实现了箭头函数语法,所以不会转换成ES5的函数定义语法。

现在我们把Chrome60改成Chrome38,再看看转换后的结果

  "browserslist": [
    "chrome 38"
  ]

转换后

  "use strict";
  var fn = function fn(num) {
    return num + 2;
  };

我们发现转换后的代码是ES5的函数定义语法,因为Chrome38不支持箭头函数语法。

注意,Babel使用browserslist的配置功能依赖于@babel/preset-env,如果Babel没有配置任何预设或插件,那么Babel对转换的代码会不做任何处理,原封不动生成和转换前一样代码。

既然@babel/preset-env可以通过browserslist针对目标环境不支持的语法进行语法转换,那么是否也可以对目标环境不支持的特性API进行部分引用呢?这样我们就不用把完整的polyfill全部引入到最终的文件里,可以大大减少体积。

答案是可以的,但需要对@babel/preset-env的参数项进行设置才可以,这个我们接下来讲。

参数项
targets

该参数项可以取值为字符串、字符串数组或对象,不设置的时候取默认值空对象{}。

该参数项的写法与browserslist是一样的,下面是一个例子

  module.exports = {
    presets: [["@babel/env", {
      targets: {
        "chrome": "58",
        "ie": "11"
      }
    }]],
    plugins: []
  }

如果我们对@babel/preset-env的targets参数项进行了设置,那么就不使用browserslist的配置,而是使用targets的配置。如不设置targets,那么就使用browserslist的配置。如果targets不配置,browserslist也没有配置,那么@babel/preset-env就对所有ES6语法转换成ES5的。

正常情况下,我们推荐使用browserslist的配置而很少单独配置@babel/preset-env的targets。

useBuiltIns

useBuiltIns项取值可以是"usage" 、 “entry” 或 false。如果该项不进行设置,则取默认值false。

useBuiltIns这个参数项主要和polyfill的行为有关。在我们没有配置该参数项或是取值为false的时候,polyfill就是我们上节课讲的那样,会全部引入到最终的代码里。

useBuiltIns取值为"entry"或"usage"的时候,会根据配置的目标环境找出需要的polyfill进行部分引入。让我们看看这两个参数值使用上的不同。

useBuiltIns:“entry”
我们在入口文件用import语法引入polyfill(也可以在webpack的entry入口项)。此时的Babel配置文件如下:

  module.exports = {
    presets: [["@babel/env", {
      useBuiltIns: "entry"
    }]],
    plugins: []
  }

需要安装的npm包如下:

npm install --save-dev @babel/cli @babel/core  @babel/preset-env
npm install --save @babel/polyfill

我们指定目标环境是火狐58,package.json里的browserslist设置如下:

    "browserslist": [
      "firefox 58"
    ]

转换前端的代码如下:

  import '@babel/polyfill';
  var promise = Promise.resolve('ok');
  console.log(promise);

使用npx babel a.js -o b.js命令进行转码
转码后:

  "use strict";
  require("core-js/modules/es7.array.flat-map");
  require("core-js/modules/es7.string.trim-left");
  require("core-js/modules/es7.string.trim-right");
  require("core-js/modules/web.timers");
  require("core-js/modules/web.immediate");
  require("core-js/modules/web.dom.iterable");
  var promise = Promise.resolve('ok');
  console.log(promise);

我们可以看到Babel针对火狐58不支持的API特性进行引用,一共引入了6个core-js的API补齐模块。同时也可以看到,因为火狐58已经支持Promise特性,所以没有引入promise相关的API补齐模块。你可以试着修改browserslist里火狐的版本,修改成版本26后,会引入API模块大大增多,有几十个。

useBuiltIns:“usage”

"usage"在Babel7.4之前一直是试验性的,7.4之后的版本稳定。

这种方式不需要我们在入口文件(以及webpack的entry入口项)引入polyfill,Babel发现useBuiltIns的值是"usage"后,会自动进行polyfill的引入。

我们的Babel配置文件如下:

  module.exports = {
    presets: [["@babel/env", {
      useBuiltIns: "usage"
    }]],
    plugins: []
  }

需要安装的npm包如下:

  npm install --save-dev @babel/cli @babel/core  @babel/preset-env
  npm install --save @babel/polyfill

我们指定目标环境是火狐27,package.json里的browserslist设置如下:

    "browserslist": [
      "firefox 27"
    ]

转换前端的代码如下:

  var promise = Promise.resolve('ok');
  console.log(promise);

使用npx babel a.js -o b.js命令进行转码。
下面是转换后的代码:

  "use strict";
  require("core-js/modules/es6.promise");
  require("core-js/modules/es6.object.to-string");
  var promise = Promise.resolve('ok');
  console.log(promise);

观察转换的代码,我们发现引入的core-js的API补齐模块非常少,只有2个。为什么呢?

因为我们的代码里只使用了Promise这一火狐27不支持特性API,使用useBuiltIns:"usage"后,Babel除了会考虑目标环境缺失的API模块,同时考虑我们项目代码里使用到的ES6特性。只有我们使用到的ES6特性API在目标环境缺失的时候,Babel才会引入core-js的API补齐模块。

这个时候我们就看出了’entry’与’usage’这两个参数值的区别:'entry’这种方式不会根据我们实际用到的API进行针对性引入polyfill,而’usage’可以做到。另外,在使用的时候,'entry’需要我们在项目入口处手动引入polyfill,而’usage’不需要。

需要注意的是,使用’entry’这种方式的时候,只能import polyfill一次,一般都是在入口文件。如果进行多次import,会发生错误。

corejs

该参数项的取值可以是2或3,没有设置的时候取默认值为2(还有一种对象proposals取值方法,我们实际用不到,忽略掉即可)

这个参数项只有useBuiltIns设置为’usage’或’entry’时,才会生效。

取默认值或2的时候,Babel转码的时候使用的是core-js@2版本(即core-js2.x.x)。因为某些新API只有core-js@3里才有,例如数组的flat方法,我们需要使用core-js@3的API模块进行补齐,这个时候我们就把该项设置为3。

需要注意的是,corejs取值为2的时候,需要安装并引入core-js@2版本,或者直接安装并引入polyfill也可以。如果corejs取值为3,必须安装并引入core-js@3版本才可以,否则Babel会转换失败并提示:
‘@babel/polyfill’ s deprecated. Please, use required parts of ’core-js’ and regenerator-runtime/runtime ’separately’

modules

这个参数项的取值可以是"amd"、“umd” 、 “systemjs” 、 “commonjs” 、“cjs” 、“auto” 、false。在不设置的时候,取默认值"auto"。

该项用来设置是否把ES6的模块化语法改成其它模块化语法。

我们常见的模块化语法有两种:(1)ES6的模块法语法用的是import与export;(2)commonjs模块化语法是require与module.exports。

在该参数项值是’auto’或不设置的时候,会发现我们转码前的代码里import都被转码成require了。

如果我们将参数项改成false,那么就不会对ES6模块化进行更改,还是使用import引入模块。

使用ES6模块化语法有什么好处呢。在使用Webpack一类的打包工具,可以进行静态分析,从而可以做tree shaking 等优化措施。

4.6 @babel/plugin-transform-runtime

本节主要讲@babel/plugin-transform-runtime以及@babel/runtime。

在我们用Babel做语法转换的时候(注意,这里是单纯的做语法转换,暂时不使用polyfill补齐API),需要Babel在转换后的代码里注入一些函数才能正常工作,先看一个例子

Babel配置文件如下,用@babel/preset-env做语法转换:

  {
    "presets": [
      "@babel/env"
    ],
    "plugins": [
    ]
  }

转换前的代码使用了ES6的class类语法:

  class Person {
    sayname() {
      return 'name'
    }
  }
  var john = new Person()
  console.log(john)

Babel转码后生成的代码如下:

  "use strict";
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
  function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
  var Person = /*#__PURE__*/function () {
    function Person() {
      _classCallCheck(this, Person);
    }
    _createClass(Person, [{
      key: "sayname",
      value: function sayname() {
        return 'name';
      }
    }]);
    return Person;
  }();
  var john = new Person();
  console.log(john);

可以看到转换后的代码上面增加了好几个函数声明,这就是注入的函数,我们称之为辅助函数。@babel/preset-env在做语法转换的时候,注入了这些函数声明,以便语法转换后使用。

但样这做存在一个问题。在我们正常的前端工程开发的时候,少则几十个js文件,多则上千个。如果每个文件里都使用了class类语法,那会导致每个转换后的文件上部都会注入这些相同的函数声明。这会导致我们用构建工具打包出来的包非常大。

那么怎么办?一个思路就是,我们把这些函数声明都放在一个npm包里,需要使用的时候直接从这个包里引入到我们的文件里。这样即使上千个文件,也会从相同的包里引用这些函数。通过webpack这一类的构建工具打包的时候,我们只会把使用到的npm包里的函数引入一次,这样就做到了复用,减少了体积。

@babel/runtime就是上面说的这个npm包,@babel/runtime把所有语法转换会用到的辅助函数都集成在了一起。

我们先安装这个包:

  npm install --save @babel/runtime
  npm install --save-dev @babel/cli @babel/core  @babel/preset-env

然后到node_modules目录下看一下这个包结构
在这里插入图片描述
_classCallCheck, _defineProperties与 _createClass这个三个辅助函数就在图片所示的位置,我们直接引入即可。

我们手动把辅助函数替换掉函数声明,之前文件的代码就变成如下所示:

  "use strict";
  var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
  var _defineProperties = require("@babel/runtime/helpers/defineProperties");
  var _createClass = require("@babel/runtime/helpers/createClass");
  var Person = /*#__PURE__*/function () {
    function Person() {
      _classCallCheck(this, Person);
    }
    _createClass(Person, [{
      key: "sayname",
      value: function sayname() {
        return 'name';
      }
    }]);
    return Person;
  }();
  var john = new Person();
  console.log(john);

这样就解决了代码复用和最终文件体积大的问题。不过,这么多辅助函数要一个个记住并手动引入,平常人是做不到的,我也做不到。这个时候,Babel插件@babel/plugin-transform-runtime就来帮我们解决这个问题。

@babel/plugin-transform-runtime有三大作用,其中之一就是自动移除语法转换后内联的辅助函数(inline Babel helpers),使用@babel/runtime/helpers里的辅助函数来替代。这样就减少了我们手动引入的麻烦。

现在我们除了安装@babel/runtime包提供辅助函数模块,还要安装Babel插件@babel/plugin-transform-runtime来自动替换辅助函数:

  npm install --save @babel/runtime
  npm install --save-dev @babel/cli @babel/core  @babel/preset-env @babel/plugin-transform-runtime

现在,我们的Babel配置文件如下:

  {
    "presets": [
      "@babel/env"
    ],
    "plugins": [
      "@babel/plugin-transform-runtime"
    ]
  }

转换前a.js代码:

  class Person {
    sayname() {
      return 'name'
    }
  }
  var john = new Person()
  console.log(john)

执行"npx babel a.js -o b.js"命令后,转换生成的b.js里代码如下:

  "use strict";
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
  var Person = /*#__PURE__*/function () {
    function Person() {
      (0, _classCallCheck2["default"])(this, Person);
    }
    (0, _createClass2["default"])(Person, [{
      key: "sayname",
      value: function sayname() {
        return 'name';
      }
    }]);
    return Person;
  }();
  var john = new Person();
  console.log(john);

可以看到,它生成的代码比我们完全手动引入@babel/runtime里的辅助函数更加优雅。实际前端开发的时候,我们除了安装@babel/runtime这个包外,一定会安装@babel/plugin-transform-runtime这个Babel插件包的。

注:

  1. 每个转换后的文件上部都会注入这些相同的函数声明,那为何不用webpack一类的打包工具去掉重复的函数声明,而是要单独再引一个辅助函数包?

    webpack在构建的时候,是基于模块来做去重工作的。每一个函数声明都是引用类型,在堆内存不同的空间存放,缺少唯一的地址来找到他们。所以webpack本身是做不到把每个文件的相同函数声明去重的。因此我们需要单独的辅助函数包,这样webpack打包的时候会基于模块来做去重工作。

@babel/plugin-transform-runtime有三大作用:
  1. 自动移除语法转换后内联的辅助函数(inline Babel helpers),使用@babel/runtime/helpers里的辅助函数来替代;

  2. 当代码里使用了core-js的API,自动引入@babel/runtime-corejs3/core-js-stable/,以此来替代全局引入的core-js/stable;

  3. 当代码里使用了Generator/async函数,自动引入@babel/runtime/regenerator,以此来替代全局引入的regenerator-runtime/runtime;

作用2和3其实是在做API转换,对内置对象进行重命名,以防止污染全局环境。

在babel-polyfill一节,我们学习了引入’babel-polyfill’或’core-js/stable与regenerator-runtime/runtime’来做全局的API补齐。但这样可能有一个问题,那就是对运行环境产生了污染。例如Promise,我们的polyfill是对浏览器的全局对象进行了重新赋值,我们重写了Promise及其原型链。

有时候我们不想改变或补齐浏览器的window.Promise,那么我们就不能使用’babel-polyfill’或’core-js/stable与regenerator-runtime/runtime’,因为其会对浏览器环境产生污染(即修改了浏览器的window.Promise)。

这个时候我们就可以使用@babel/plugin-transform-runtime,它可以对我们代码里ES6的API进行转换。还是以Promise举例子。

Babel转换前的代码

var obj = Promise.resolve();

若使用了’babel-polyfill’或’core-js/stable与regenerator-runtime/runtime’来做全局的API补齐,那么Babel转换后的代码仍然是

  var obj = Promise.resolve();

polyfill只是补齐了浏览器的window.Promise对象。

若我们不使用polyfill,而开启@babel/plugin-transform-runtime的API转换功能。那么Babel转换后的代码将是

  var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
  var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
  var obj = _promise["default"].resolve();

看到效果了没?@babel/plugin-transform-runtime把我们代码里的Promise变成了_promise[“default”],而_promise[“default”]拥有ES标准里Promise所有的功能。现在,即使浏览器没有Promise,我们的代码也能正常运行。

开启core-js相关API转换功能的Babel配置与安装的npm包如下

配套代码是github仓库 https://github.com/jruit/babel-tutorial 的babel14例子
在这里插入图片描述

  {
    "presets": [
      "@babel/env"
    ],
    "plugins": [
      ["@babel/plugin-transform-runtime", {
        "corejs": 3
      }]
    ]
  }
npm install --save @babel/runtime-corejs3
npm install --save-dev @babel/cli @babel/core  @babel/preset-env @babel/plugin-transform-runtime

那么,上面讲的API转换有什么用,明明通过polyfill补齐API的方式也可以使代码在浏览器正常运行?

其实,API转换主要是给开发JS库或npm包等的人用的,我们的前端工程一般仍然使用polyfill补齐API。

可以想象,如果开发JS库的人使用polyfill补齐API,我们前端工程也使用polyfill补齐API,但JS库的polyfill版本或内容与我们前端工程的不一致,那么我们引入该JS库后很可能会导致我们的前端工程出问题。所以,开发JS库或npm包等的人会用到API转换功能。

当然,我们前端工程开发的时候也是可以使用@babel/plugin-transform-runtime的API转换功能,毕竟没有污染全局环境,不会有任何冲突。@babel/plugin-transform-runtime的默认设置下,就是对generators/async开启了API转换功能。

细心的你可能已经发现了,我们安装npm包的时候,安装的是@babel/runtime-corejs3,而上一节我们安装的是@babel/runtime。

看名字挺像的,那么这两者有什么不同呢?

在我们不需要开启core-js相关API转换功能的时候,我们只需要安装@babel/runtime就可以了。上一节我们已经知道,@babel/runtime里存放的是Babel做语法转换的辅助函数。

在我们需要开启core-js相关API转换功能的时候,就需要安装@babel/runtime的进化版@babel/runtime-corejs3。这个npm包里除了包含Babel做语法转换的辅助函数,也包含了core-js的API转换函数。

除了这两个包,还有一个@babel/runtime-corejs2的包。它和@babel/runtime-corejs3的功能是一样的,只是里面的函数是针对core-js2版本的。

上面的例子主要是拿Promise来讲的,它属于作用2,即对core-js的API进行转换。其实理解了作用2,也就理解了作用3。

下面简单说一下作用3。

在之前章节,若我们转码前代码里有Generator函数或async函数,转码后需要引入’regenerator-runtime/runtime’做全局API补齐。

全局API补齐必然会对浏览器的window对象进行修改,如果我们不想要污染window,那么我们就不能引入’regenerator-runtime/runtime’了。

这个时候,我们可以开启@babel/plugin-transform-runtime的作用3,对Generator/async进行API转换。

需要注意的是,@babel/plugin-transform-runtime对Generator/async进行API转换功能,默认是开启的,不需要我们设置。

如何开启或关闭@babel/plugin-transform-runtime的某个功能,除了与安装的npm包有关,也与Babel配置文件的配置有关

注:

  1. 如果我们使用@babel/plugin-transform-runtime来做polyfill的事情,那么就不要再使用之前讲过的polyfill方式了,无论是单独引入还是@babel/preset-env的方式。因为我们用transform-runtime来做api转换的目的是不污染全局作用域。
@babel/plugin-transform-runtime的配置项

@babel/plugin-transform-runtime是否要开启某功能,都是在配置项里设置的,某些配置项的设置是需要安装npm包的。

@babel/plugin-transform-runtime在没有设置配置项的时候,其配置项参数取默认值。下面的两个配置作用是等效的。

 {
    "plugins": [
      "@babel/plugin-transform-runtime"
    ]
  }
  // 是上方的默认值
  { 
    "plugins": [
      [
        "@babel/plugin-transform-runtime",
        {
          "helpers": true,
          "corejs": false,
          "regenerator": true,
          "useESModules": false,
          "absoluteRuntime": false,
          "version": "7.0.0-beta.0"
        }
      ]
    ]
  }

配置项讲解

  1. helpers
    该项是用来设置是否要自动引入辅助函数包,这个我们当然要引入了,这是@babel/plugin-transform-runtime的核心用途。该项取值是布尔值,我们设置为true,其默认值也是true,所以也可以省略不填

  2. corejs和regenerator
    这两项是用来设置是否做API转换以避免污染全局环境,regenerator取值是布尔值,corejs取值是false、2和3。这个上一节已经讲过了,在前端业务项目里,我们一般对corejs取false,即不对Promise这一类的API进行转换。而在开发JS库的时候设置为2或3。regenerator取默认的true就可以

  3. useESModules
    该项用来设置是否使用ES6的模块化用法,取值是布尔值。默认是fasle,在用webpack一类的打包工具的时候,我们可以设置为true,以便做静态分析。

  4. absoluteRuntime
    该项用来自定义@babel/plugin-transform-runtime引入@babel/runtime/模块的路径规则,取值是布尔值或字符串。没有特殊需求,我们不需要修改,保持默认false即可。

  5. version
    该项主要是和@babel/runtime及其进化版@babel/runtime-corejs2、@babel/runtime-corejs3的版本号有关系,这三个包我们只需要根据需要安装一个。我们把安装的npm包的版本号设置给version即可。例如,在上节的babel14例子里,安装的@babel/runtime-corejs3版本是7.10.4,那么配置项version也取’7.10.4’。
    其实该项不填取默认值就行,目前填写版本号主要是可以减少打包体积。

    另外,在Babel6版本,该插件还有两个配置选项polyfill和useBuiltIns,在v7版本已经移除了,大家不需要再使用。

小结:

要使用@babel/plugin-transform-runtime插件,其实只有一个npm包是必须要装的,那就是它自己@babel/plugin-transform-runtime。

对于@babel/runtime及其进化版@babel/runtime-corejs2、@babel/runtime-corejs3,我们只需要根据自己的需要安装一个。

如果你不需要对core-js做API转换,那就安装@babel/runtime并把corejs配置项设置为false即可。

如果你需要用core-js2做API转换,那就安装@babel/runtime-corejs2并把corejs配置项设置为2即可。

如果你需要用core-js3做API转换,那就安装@babel/runtime-corejs3并把corejs配置项设置为3即可。

注:

  1. 那regenerator为何默认值是true?我的理解是,实现Generator与async转换API代码较少,而且也需要一些语法转换,所以默认值取了true。我们也可以设为false,不过没必要。
  2. 在安装@babel/preset-env的时候,其实已经自动安装了@babel/runtime,不过在项目开发的时候,我们一般都会再单独npm install一遍@babel/runtime。

5、Babel工具

5.1 @babel/core

@babel/core是我们使用Bable进行转码的核心npm包,我们使用的babel-cli、babel-node都依赖这个包,因此我们在前端开发的时候,都需要安装这个包。

在我们的工程目录里,执行下面的命令,安装@babel/core。

  npm install --save-dev @babel/core

对于大部分开发者来说,这一小节的知识到这来就可以结束了,只需要知道Babel转码必须要安装这个包即可。下面的内容会讲解@babel/core自身对外提供的API。

无论我们是通过命令行转码,还是通过webpack进行转码,底层都是通过Node来调用@babel/core相关功能API来进行的。

我们来看一个例子,来看一下Node是如何调用@babel/core的API来进行转译的。

我们先新建一个index.js文件,这个文件在写完后直接用Node来执行。

  var babelCore = require("@babel/core");
  var es6Code = 'var fn = (num) => num + 2';
  var options = {
    presets: ["@babel/env"]
  };
  var result = babelCore.transform(es6Code, options);
  console.log(result);
  console.log('--------------');
  console.log('--------------');
  console.log(result.code);

我们来看一下这段代码的意思。

第1行我们引入了@babel/core模块,并将模块输出赋值给了变量babelCore。第2行变量es6Code是一个字符串,字符串内容是一个箭头函数,该字符串内容是我们需要转译的代码,这个变量传递给了接下来transform方法的第1个参数。第3行options是一个对象,这个对象传递给了接下来transform方法的第2个参数。最后,我们调用babelCore的transform方法,我们把结果打印在Node的控制台上。为了方便看输出结果,中间用’------'隔开。

在这里插入图片描述

可以看到,transform后的结果是个对象,该对象的code就是我们转码后的结果。

这就是@babel/core底层的一个调用过程。

transform也可以有第3个参数,第3个参数是一个回调函数,用来对转码后的对象进行进一步处理。@babel/core除了transform这个API,还有transformSync、transformAsync和transformFile等同步异步以及对文件进行转码的API,这里就不展开讲了,用法和上面的transform大同小异。

5.2 @babel/cli

@babel/cli是一个npm包,安装了它之后,我们就可以在命令行里使用命令进行转码了。

它的安装方法有全局安装和项目本地安装。

执行下面的命令可以进行全局安装

  npm install --global @babel/cli

执行下面的命令可以进行项目本地安装

  npm install --save-dev @babel/cli

@babel/cli如果是全局安装的,我们在命令行使用babel XXX进行转码,如果是项目本地安装的,那么命令行里使用npx babel XXX进行转码。下面是一个基本例子,把a.js文件转码为b.js。

提醒:转码前不要忘记写Babel配置文件,以及安装@babel/core。

  # @babel/cli如果是全局安装的
  babel a.js -o b.js
  # @babel/cli如果是本地安装的
  npx babel a.js -o b.js

两种方法是等效的,正常情况下,我们都是推荐项目本地安装。

对于大部分开发者来说,这一小节的知识到这来就完全够用了。下面介绍一下@babel/cli的其它命令。

将转译后的代码输出到Node.js的标准输出流

npx babel a.js

将转译后的代码写入到一个文件(上方刚使用过)

npx babel a.js -o b.js

 npx babel a.js --out-file b.js

-o是–out-file的简写

转译整个文件夹目录

  npx babel input -d output

  npx babel input --out-dir output

-d是–out-dir的简写

5.3 @babel/node

@babel/node在真正做前端项目开发的时候,是用不到的。该工具执行的时候需要占用大量内存空间,Babel官方不建议在生产环境使用该工具。因此若不是想深入研究该工具,下面的内容可以跳过。

@babel/node其实和node的功能非常接近,@babel/node的优点是在执行命令的时候可以配置Babel的编译配置项。如果遇到node.js不支持的ES6语法,我们通过@babel/node就可以完成。

在Babel6版本的时候,@babel/node这个工具是 @babel/cli附带的,所以只要安装了@babel/cli ,就可以直接使用 babel/node。但Babel7里,我们需要单独安装。

  npm install --save-dev @babel/node

然后我们就可以用@babel/node的babel-node命令来运行js文件。

提醒:不要忘记写Babel配置文件,以及安装core。

  var promise = Promise.resolve('ok')
  console.log(promise)

然后执行

  npx babel-node a.js

可以看到命令行输出了promise实例。

@babel/node也可以像node那样进入REPL环境。在命令行下执行下面的命令进入REPL环境

  npx babel-node

然后在REPL交互环境输入下面的内容

>  (x => x + 10)(5)

注意,>是交互环境提示符,不需要我们输入。

可以看到输出结果15。

小结

@babel/node提供了比Node.js更强大的功能,但我们开发的时候用不到它的。

5.4 @babel/register

@babel/register这个工具在我们平时的前端工程开发过程中也是用不到的。但若是想开发某些特殊的包,你可能会需要它。
@babel/register只有一个功能,就是重写node的require方法。

@babel/register在底层改写了node的require方法,在代码里引入@babel/register模块后,所有通过require引入并且以.es6, .es, .jsx 和 .js为后缀名的模块都会经过babel的转译。

5.5 babel-loader

babel-loader是用于webpack的一个loader,以便webpack在构建的时候用Babel对JS代码进行转译,这样我们就不用再通过命令行手动转译了。我们在配置该loader的时候需要先安装它

  npm install babel-loader

在webpack配置文件中,我们把babel-loader添加到module的loaders列表中

  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }

在这里,我们通过options属性给babel-loader传递预设和插件等Babel配置项。我们也可以省略这个options,这个时候babel-loader会去读取默认的Babel配置文件,也就是.babelrc,.babelrc.js,babel.config.js等。在现在的前端开发中,建议通过配置文件来传递这些配置项。

参考https://www.jiangruitao.com/babel/

6、Babel 的原理是什么?

babel 的转译过程也分为三个阶段,这三步具体是:

  • 解析 Parse: 将代码解析生成抽象语法树( 即 AST ),即词法分析与语法分析的过程
  • 转换 Transform: 对于 AST 进行变换一系列的操作,babel 接受得到AST 并通过 babel-traverse 对其进行遍历,在此过程中进行添加、更新及移除等操作
  • 生成 Generate: 将变换后的 AST 再转换为 JS 代码, 使用到的模块是babel-generator

7、如何写一个 babel 插件?

。。。待更新

二、webpack

132 小程序

前端进阶… 1383.1 |
前端工程化…
138 Babel 的原理是什么?..
138 如何写一个 babel 插件?..
139 你的 git 工作流是怎样的?..
143 rebase 与 merge 的区别?..
148 git reset、git revert 和 git checkout 有什么区别…
149 webpack 和 gulp 区别(模块化与流的区别)…

21
1503.2 | Vue 框架…
151 有使用过 Vue 吗?说说你对 Vue 的理解…
151 说说 Vue 的优缺点…
151Vue 和 React 有什么不同?使用场景分别是什么?…
151 什么是虚拟 DOM?…
152 请描述下 vue 的生命周期是什么?…
152 vue 如何监听键盘事件?…
155 watch 怎么深度监听对象变化…
156 删除数组用 delete 和 Vue.delete 有什么区别?…
156 watch 和计算属性有什么区别?… 1
56 Vue 双向绑定原理…
157 v-model 是什么?有什么用呢?…
157 axios 是什么?怎样使用它?怎么解决跨域的问题?…
157 在 vue 项目中如何引入第三方库(比如 jQuery)?有哪些方法可以做到?
157
 说说 Vue React angularjs jquery 的区别…
157 Vue3.0 里为什么要用 Proxy API 替代 defineProperty API?…
158 Vue3.0 编译做了哪些优化?…
158 Vue3.0 新特性 —— Composition API 与 React.js 中Hooks 的异同点…
159 Vue3.0 是如何变得更快的?(底层,源码)…
161 vue 要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?…
162 vue 在 created 和 mounted 这两个生命周期中请求数据有什么区别呢?
162 说说你对 proxy 的理解…

30
1623.3 | React 框架…
162 angularJs 和 React 区别…
162 redux 中间件…
163 redux 有什么缺点…
163 React 组件的划分业务组件技术组件?…
163 React 生命周期函数…
163 React 性能优化是哪个周期函数?…
164 为什么虚拟 dom 会提高性能?..
164 diff 算法?..
165 React 性能优化方案…
165 简述 flux 思想…
165 React 项目用过什么脚手架?Mern? Yeoman?..
165 你了解 React 吗?…
166 React 解决了什么问题?…
166 React 的协议?…
166 了解 shouldComponentUpdate 吗?…
166 React 的工作原理?…
166 使用 React 有何优点?…
166 展示组件(Presentational component)和容器组件(Container component)之间有何不同?…
167 类组件(Class component)和函数式组件(Functional component)之间有何不同?…
167 (组件的)状态(state)和属性(props)之间有何不同?…
167 应该在 React 组件的何处发起 Ajax 请求?…
168 在 React 中,refs 的作用是什么?…
168 何为高阶组件(higher order component)?…
168 使用箭头函数(arrow functions)的优点是什么?…
168 为什么建议传递给 setState 的参数是一个 callback 而不是一个对象?
168 除了在构造函数中绑定 this,还有其它方式吗?…
169 怎么阻止组件的渲染?…
169 当渲染一个列表时,何为 key?设置 key 的目的是什么?…
169 何为 JSX ?…

你可能感兴趣的:(interview,前端,服务器,ssl)