原文地址:https://juejin.cn/post/6948576107163549732

前端开发工程师面试

今天我请到两位朋友,面试官(小黄),面试者(小达)来进行如下前端模拟面试:(此处省略介绍,直接来面试题问答过程)

小黄:来说说你对http和https的理解

小达:嗯,好的。

http是超文本传输协议,是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准,用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。 而对于https,他是以安全为目标的http通道,是http的安全版,在http中加入了ssl层,https的安全基础是ssl。

http的连接很简单的,是无状态的,http传输的数据都是没有加密的,也就是明文的,网景公司设置了ssl协议来对http协议传输的数据进行加密处理,所以说https协议是由http和ssl协议构建的可进行加密传输和身份认证的网络协议,比http协议的安全性更高。

https协议是需要证书的,费用较高,http是超文本传输协议,信息是明文传输,https是具有安全性的ssl加密传输协议,使用不同的链接方式,端口不同,一般来说,http协议的端口是80,而https的端口为443。

使用https协议可以认证用户和服务器,确保数据发送到正确的客户端和服务器端。对于https协议的工作原理,客户端在使用https方式与web服务器通信时,首先客户使用https url访问服务器,则要求web服务器建立ssl链接,web服务器接收到客户端的请求之后,会将网站的证书,返回或者说传输给客户端,然后客户端和web服务器端开始协商ssl链接的安全等级,也就是加密等级,通过双方协商一致的安全等级,建立会话密钥,然后通过网站的公钥来加密会话密钥,并传送给网站。(web服务器通过自己的私钥解密出会话密钥,通过会话密钥加密与客户端之间的通信)

建议使用https,比起同等http网站,使用https加密的网站在搜索结果中的排名会更高哦!

https协议握手阶段比较费时,会使页面加载时间延长50%,增加10%~20%的耗电,https缓存也不如http高效,会增加数据开销,ssl证书也是需要钱💴的,功能越强大证书费用越高,ssl证书时需要绑定ip的,不能再同一个ip上绑定多个域名。

小黄:嗯,好,你刚刚提到握手阶段,那你说说tcp三次握手

image.png

小达:嗯,好的

如通话一样,喂,你听到了吗? 听到了哦!那你听到我的吗? 听到了,我们可以聊天了哦~

这是场景模式,首先,发送端发送一个带有SYN标志的数据包给对方,接收端收到后,回传一个带有SYN/ACK标志的数据包表示传达确认信息,发送端再回传一个带有ACK标志的数据包,代表“握手”结束。

小黄:嗯,好,说到TCP,那么你说说TCP和UDP的区别

小达:嗯,好的

TCP是面向连接的,UPD是无连接的,即发送数据前不需要先建立连接;TCP提供可靠的服务,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达,UDP尽最大努力交付,即不保证可靠交付。

并且因为 tcp 可靠,面向连接,不会丢失数据因此适合大数据量的交换;TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流,UDP是面向报文的,UDP没有拥塞控制,因此,网络出现拥塞不会使原主机的发送速率降低,因此会出现丢包,对实时的应用很有用,比如IP电话和视频会议等

每一条TCP连接,只能是1对1的,UDP支持1对1,1对多,多对1,多对多的交互通讯;TCP的首部开销为20字节,而UDP的只有8字节;TCP面向连接的可靠性传输,而UDP是不可靠的。

image.png

小黄:嗯,可以,那么说到通讯,说说WebSocket的实现和应用

小达:嗯,好的

WebSocket是HTML5中的协议,支持持久连接,http协议不支持持久性连接,http1.0和http1.1都不支持持久性的链接,http1.1中的keep-alive,将多个http请求合并为1个,也就是说,不用再去重复建立tcp连接了。

WebSocket是基于http协议的,或者说借用了http协议来完成一部分握手,在握手阶段与是相同的。http的生命周期通过request来界定的,也就是Request一个Response,那么在http1.0协议中,这次http请求就代表结束了。

在http1.1中做了改进,有一个connection:keep-alive字段,也就是说,在一个http连接中,可以发送多个Request,接收多个Response。

但是在http中一个request只能对应一个response,而且这个response是被动,不是主动发起的。

基于一个websocket握手协议的实现,基本是有2个属性,Upgrade和Connection,基本请求上有多这两个属性,告诉服务器发起的WebSocket协议:

1
2
3
4
5
6
7
8
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: fsdgfdsfew34rfdfdfds==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

websocket与Http协议一样度是基于tcp的,都属于应用层的协议,websocket在建立握手连接时,数据是通过http协议传输的,但是在建立连接之后,真正的数据传输阶段是不需要http协议参与的。

websocket的应用场景有:实时显示网站在线人数,多玩家网络游戏,多媒体聊天,如聊天室,账户余额等数据的实时更新。

小黄:嗯,可以,说到http请求,那么说说http请求的方式,head方式

小达:嗯,好的,http请求方式有:

  • get,请求指定的页面信息,并返回实体主体
  • post,请求服务器接受所指定的文档作为对所标识的URI的新的从属实体
  • head,类似于get请求,只不过返回的响应中没有具体的内容,用户获取报头
  • options,允许客户端查看服务器的性能,比如说服务器支持的请求方式等等
  • PUT,传输文件
  • DELETE,删除文件
  • OPTIONS,询问支持的方法
  • TRACE,追踪路径
  • CONNECT,要求用隧道协议连接代理

HEAD方法与GET相同,除了服务器在响应中不得返回消息正文。响应HEAD请求的HTTP标头中包含的元信息应该与响应GET请求发送的信息相同。此方法可用于获取有关请求所隐含的实体的元信息,而无需转移实体主体本身。

此方法通常用于测试超文本链接的有效性,可访问性和最新修改。

对HEAD请求的响应可以是可缓存的,因为响应中包含的信息可以用于从该资源更新先前缓存的实体。如果新的字段值表示缓存的实体与当前实体不同(如Content-Length,Content-MD5,ETag或Last-Modified的更改所指示),则缓存必须将缓存条目视为过期。

小黄:嗯恩,那你说说几个BOM属性对象方法

小达:嗯,好的。

BOM是浏览器对象,那么常用的属性有:

location对象

location.href,返回或设置当前文档的URL,location.search,返回URL中的查询字符串部分。

location.hash,返回URL#后面的内容,如果没有#,返回空。location.host,返回 URL 中的域名部分,location.hostname,返回 URL 中的主域名部分。

location.pathname,返回 URL 的域名后的部分,location.port,返回 URL 中的端口部分,location.protocol,返回 URL 中的协议部分,location.assign,设置当前文档的 URL。

  • location.replace(),设置当前文档的 URL,并且在 history 对象的地址列表中移除这个 URL location.replace(url);
  • location.reload() – 重载当前页面

history.go()

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

Navigator 对象

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

小黄:嗯恩,那你了解http2.0吗?可以说说吗?

小达:嗯恩,行,相比于 http,https 是基于 ssl 加密的 http 协议。

http2.0 是基于 1999 年发布的 http1.0 之后的首次更新。提升访问速度,请求资源所需时间更少,访问速度更快,相比 http1.0,允许多路复用。

多路复用允许同时通过单一的 HTTP/2 连接发送多重请求-响应信息,在 http1.1 中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制(连接数量),超过限制会被阻塞。

HTTP2.0 会将所有的传输信息分割为更小的信息或者帧(二进制分帧),并对他们进行二进制编码 首部压缩 服务器端推送

小黄,嗯恩,那说说你在项目中常见的状态码,除200外?

小达:嗯,好的,如400,401,403状态码。

400 状态码表示请求无效,产生原因是前端提交数据的字段名称和字段类型与后台的实体没有保持一致,前端提交到后台的数据应该是 json 字符串类型,但是前端没有将对象 JSON.stringify 转 化成字符串。

解决方法是对照字段的名称,保持一致性 将 obj 对象通过 JSON.stringify 实现序列化

  • 401 状态码:当前请求需要用户验证
  • 403 状态码:服务器已经得到请求,但是拒绝执行

小黄:嗯,说到状态码,你能说出多少来

小达,嗯,如:

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

304 Not Modified 未修改。

所请求的资源未修改,服务器返回此状态码时,不会返回 任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返 回在指定日期之后修改的资源

305 Use Proxy 使用代理。

所请求的资源必须通过代理访问

400 Bad Request

客户端请求的语法错误,服务器无法理解

401 Unauthorized

请求要求用户的身份认证

402 Payment Required

保留,将来使用

403 Forbidden

服务器理解请求客户端的请求,但是拒绝执行此请求

404 Not Found

服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站 设计人员可设置”您所请求的资源无法找到”的个性页面

405 Method Not Allowed

客户端请求中的方法被禁止

503 Service Unavailable

由于超载或系统维护,服务器暂时的无法处理客户端的请求。 延时的长度可包含在服务器的 Retry-After 头信息中

504 Gateway Time-out

充当网关或代理的服务器,未及时从远端服务器获取请求

505 HTTP Version not supported

服务器不支持请求的 HTTP 协议的版本,无法完成处理

小黄:嗯,行,那你说说http 常用请求头

小达:嗯,好的。

Accept

可接受的响应内容类型(Content-Types)。

Accept-Charset

可接受的字符集

Accept-Encoding

可接受的响应内容的编码方式。

Accept-Language

可接受的响应内容语言列表。

Accept-Datetime

可接受的按照时间来表示的响应内容版本

Authorization

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

Cache-Control

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

Connection

客户端(浏览器)想要优先使用的连接类型

Cookie

由之前服务器通过Set-Cookie设置的一个HTTP协议Cookie

Content-Length

以 8 进制表示的请求体的长度

Content-MD5

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

Content-Type

请求体的 MIME 类型 (用于 POST 和 PUT 请求中)

Expect

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

From

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

If-Modified-Since

允许在对应的资源未被修改的情况下返回 304 未修改

If-None-Match

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

If-Range

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

If-Unmodified-Since

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

Max-Forwards

限制该消息可被代理及网关转发的次数。

Range

表示请求某个实体的一部分,字节偏移以 0 开始。

User-Agent

浏览器的身份标识字符串

Upgrade

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

小黄:嗯,行,那么说说强缓存,协商缓存

小达:嗯,好的。

缓存分为两种:强缓存和协商缓存,根据响应的 header 内容来决定

强缓存

  • 获取资源形式 从缓存取
  • 状态码 200(from cache)
  • 发送请求到服务器 否,直接从缓存取

协商缓存

  • 获取资源形式 从缓存取
  • 状态码 304(not modified)
  • 发送请求到服务器 是,通过服务器来告知缓存是否可用

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

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

小黄:嗯嗯,那么说到这两个缓存,什么时候用哪个呢

小达:嗯恩,推荐HTTP强缓存和协商缓存

因为服务器上的资源不是一直固定不变的,大多数情况下它会更新,这个时候如果我们 还访问本地缓存,那么对用户来说,那就相当于资源没有更新,用户看到的还是旧的资 源;所以我们希望服务器上的资源更新了浏览器就请求新的资源,没有更新就使用本地 的缓存,以最大程度的减少因网络请求而产生的资源浪费。

浏览器缓存主要分为强强缓存(也称本地缓存)和协商缓存(也称弱缓存)

小黄:嗯,行,那么说说fetch发送2次请求的原因

小达:嗯,好的。fetch 发送 post 请求的时候,总是发送 2 次,第一次状态码是 204,第二次才成功。原因很简单,因为你用 fetch 的 post 请求的时候,导致 fetch 第一次发送了一个 Options 请求,询问服务器是否支持修改的请求头,如果服务器支持,则在第二次中发送真正的 请求。

小黄,行,那你说说Cookie,sessionStorage,localStorage的区别

image.png

小达:嗯,好的。

cookie的内容主要包括:名字、值、过期时间、路径和域。路径与域一起构成cookie的作用范围。若不设置时间,则表示这个cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就会消失。这种生命期为浏览器会话期的cookie被称为会话cookie。

cookie 只在设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭。(key:本身就是一个回话过程,关闭浏览器后消失,session 为一个回话,当页面不同即使是同一页面打开两次,也被视为同一次回话)

会话cookie一般不存储在硬盘而是保存在内存里,当然这个行为并不是规范规定的。若设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再打开浏览器这些cookie仍然有效直到超过设定的过期时间。对于保存在内存里的cookie,不同的浏览器有不同的处理方式session机制。 说到cookie,我也说说session吧。

当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识(称为session id),如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(检索不到,会新建一个),如果客户端请求不包含session id,则为客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。

sessionStorage:仅在当前浏览器窗口关闭前有效,不可能持久保持

localStorage: 始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据

localStorage:localStorage 在所有同源窗口中都是共享的;cookie 也是在所有同源窗口中 都是共享的。(key:同源窗口都会共享,并且不会失效,不管窗口或者浏览器关闭与 否都会始终生效)

sessionStorage、localStorage和cookie共同点:都是保存在浏览器端、且同源的

区别在于:

1、cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下

2、存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大

3、数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭

4、作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的

保存用户登录状态。例如将用户 id 存储于一个 cookie 内,这样当用户下次访问该页面 时就不需要重新登录了,现在很多论坛和社区都提供这样的功能。

cookie 还可以设置 过期时间,当超过时间期限后,cookie 就会自动消失。因此,系统往往可以提示用户保 持登录状态的时间:常见选项有一个月、三个 月、一年等

跟踪用户行为。例如一个天气预报网站,能够根据用户选择的地区显示当地的天气情况。 如果每次都需要选择所在地是烦琐的,当利用了 cookie 后就会显得很人性化了,系统能 够记住上一次访问的地区,当下次再打开该页面时,它就会自动显示上次用户所在地区 的天气情况。

因为一切都是在后台完成,所以这样的页面就像为某个用户所定制的一样,使用起来非常方便定制页面。如果网站提供了换肤或更换布局的功能,那么可以使用 cookie 来记录用户的选项,例如:背景色、分辨率等。当用户下次访问时,仍然可以保存上一次访问的界面风格

小黄:嗯恩,刚刚你有说到session,那么说说cookie session的区别吧

小达:嗯,好的。

  1. cookie 数据存放在客户的浏览器上,session 数据放在服务器上

  2. cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗 考虑到安全应当使用 session。

  3. session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能 考虑到减轻服务器性能方面,应当使用 COOKIE。

  4. 单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个 cookie

小黄:嗯恩,说说你对HTML 语义化标签的理解

小达:嗯,好的。HTML5 语义化标签是指正确的标签包含了正确的内容,结构良好,便于阅读。根据内容的结构化(内容语义化),选择合适的标签(代码语义化)便于开发者阅读和写出更优雅的代码的同时让浏览器的爬虫和机器很好地解析。语义化更具可读性,遵循W3C标准的团队都遵循这个标准,可以减少差异化。

小黄:嗯恩,那你使用过ifram吗,说说其缺点

小达:嗯,使用过的,iframe 元素会创建包含另一个文档的内联框架,可以将提示文字放在<iframe###</iframe###之间,来提示某些不支持 iframe 的浏览器。

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

小黄:嗯恩,那你说说Doctype 作用?严格模式与混杂模式如何区分?它们有何意义?

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

严格模式,即标准模式,是指浏览器按照w3c标准解析代码(严格模式的排版和 JS 运作模式是 以该浏览器支持的最高标准运行。)

混杂模式,又叫怪异模式或者是兼容模式,是指浏览器用自己的方式解析代码(向后兼容,模拟老式浏览器,防止浏览器无法兼容页面。

意义:如果只有严格模式,那么许多旧网站必须受到影响;如果只有混杂模式,那么每个浏览器都有自己的解析模式

HTML5没有DTD,因此没有严格模式和混杂模式区别,它有相对宽松的语法,实现时已经尽可能大的实现了向后兼容

小黄:嗯恩,那你说说Cookie 如何防范 XSS 攻击

小达:嗯恩,好的。

XSS(跨站脚本攻击)是指攻击者在返回的 HTML 中嵌入 javascript 脚本,为了减轻这些 攻击,需要在 HTTP 头部配上。(XSS 攻击是页面被注入了恶意的代码)

set-cookie:

  • httponly - 这个属性可以防止 XSS,它会禁止 javascript 脚本来访问 cookie。
  • secure - 这个属性告诉浏览器仅在请求为 https 的时候发送 cookie。

XSS 防范是后端 RD(研发人员)的责任,后端 RD 应该在所有用户提交数据的接口,对敏感字符进行转义,才能进行下一步操作。

所有要插入到页面上的数据,都要通过一个敏感字符过滤函数的转义,过滤掉通用的敏感字符后,就可以插入到页面中。

XSS 有哪些注入的方法:

在 HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。

在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。

在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。

在标签的 href、src 等属性中,包含 javascript: 等可执行代码。

在 onload、onerror、onclick 等事件中,注入不受控制代码。

在 style 属性和标签中,包含类似 background-image:url("javascript:..."); 的代码(新版本浏览器已经可以防范)。

在 style 属性和标签中,包含类似 expression(...) 的 CSS 表达式代码(新版本浏览器已经可以防范)。

防止方法:

  • 输入过滤
  • 纯前端渲染,把代码和数据分隔开(预防存储型和反射型 XSS 攻击)
  • 对 HTML 做充分转义(预防存储型和反射型 XSS 攻击)
  • 避免js在字符串中拼接不可信数据(预防 DOM 型 XSS 攻击)
  • Content Security Policy
  • 输入内容长度控制
  • HTTP-only Cookie: 禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie。
  • 验证码:防止脚本冒充用户提交危险操作。

小黄:嗯恩,那你说说 viewport 和移动端布局

小达:嗯恩,好的。移动端布局方式(一般采用自适应布局),移动端适配:

a.流体布局+少量响应式

b.基于rem的布局

c.弹性布局。

响应式布局很少应用在移动端,因为工作量大难以维护。

1
<meta name="viewport" content="width=device-width,initial-scale=1"###

在静态网页中,经常用像素px作为单位,来描述一个元素的宽高以及定位信息(在pc端,通常认为css中,1px所表示的真实长度是固定的)

pc端下font-size为16px时的显示结果和移动端下font-size为16px时的显示结果是,不一样的。

像素是网页布局的基础,一个像素表示了计算机屏幕所能显示的最小区域,像素分为两种类型:css像素和物理像素。

物理像素称设备像素,只与设备或者说硬件有关,同样尺寸的屏幕,设备的密度越高,物理像素也就越多。

这里我要说一下视口,广义的视口,是指浏览器显示内容的屏幕区域,狭义的视口包括了布局视口、视觉视口和理想视口。

推荐:响应式布局的常用解决方案对比(媒体查询、百分比、rem和vw/vh)

1
<meta id="viewport" name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1; user-scalable=no;"###

小黄:嗯恩,那你说说 addEventListener 参数

小达:嗯恩,好的。addEventListener(event, function, useCapture)

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

小黄:嗯恩,那你说说前端优化

image.png

image.png

小达:嗯恩,好的。

降低请求量:合并资源,减少 HTTP 请求数,minify / gzip 压缩,webP,lazyLoad。 加快请求速度:预解析 DNS,减少域名数,并行加载,CDN 分发。

缓存:HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存 localStorage。

渲染:JS/CSS 优化,加载顺序,服务端渲染,pipeline。

小黄:嗯恩,那你说说 GET 和 POST 的区别

小达:嗯恩,好的。

一、功能不同

1、get是从服务器上获取数据。

2、post是向服务器传送数据。

二、过程不同

1、get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到。

2、post是通过HTTP post机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。

三、获取值不同

1、对于get方式,服务器端用Request.QueryString获取变量的值。

2、对于post方式,服务器端用Request.Form获取提交的数据。

四、传送数据量不同

1、get传送的数据量较小,不能大于2KB。

2、post传送的数据量较大,一般被默认为不受限制。

五、安全性不同

1、get安全性非常低。

2、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 数据包。

推荐:GET 和 POST 到底有什么区别?

小黄:嗯恩,那你说说 301 和 302 的区别

小达:嗯恩,好的。

301 Moved Permanently

被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一。如果可能,拥有链接编辑功能的客户端应当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定,否则这个响应也是可缓存的。

302 Found

请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的, 客户端应当继续向原有地址发送以后的请求。只有在 Cache-Control 或 Expires 中进行了指定的情况下,这个响应才是可缓存的。

字面上的区别就是 301 是永久重定向,而 302 是临时重定向。 301 比较常用的场景是使用域名跳转。302 用来做临时跳转比如未登陆的用户访问用户中心重定向到登录页面。

小黄:嗯恩,那你说说 HTTP 支持的方法

小达:嗯恩,好的。如:GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE, CONNECT

小黄:嗯恩,那你说说 如何画一个三角形

小达:嗯恩,好的。

1
2
3
4
5
6
7
8
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;
}

小黄:嗯恩,那你说说 HTML5 新增的元素

小达:嗯恩,好的。

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

小黄:嗯恩,那你说说 在地址栏里输入一个 URL,到这个页面呈现出来,中间会发生什么?

小达:嗯恩,好的。

一、DNS域名解析

根据输入的 URL 域名找到真实 IP 地址,在查找的过程中,浏览器首先会寻找缓存,查看缓存中是否有记录,缓存的查找记录为:

  • 浏览器缓存-》操作系统缓存-》路由器缓存;
  • 缓存中没有则查找系统的hosts文件中是否有记录;
  • 如果没有则查询DNS服务器,首先从顶级域名,再到二级域名,以此类推。

输入 url 后,首先需要找到这个 url 域名的服务器 ip,为了寻找这个 ip,浏览器首先会寻 找缓存,查看缓存中是否有记录,缓存的查找记录为:浏览器缓存-》系统缓存-》路由 器缓存,缓存中没有则查找系统的 hosts 文件中是否有记录,如果没有则查询 DNS 服务器

二、建立TCP连接

根据 IP 地址,客户端与服务端进行三次握手,建立连接。

为了准确无误地把数据送达目标处,TCP采用了三次握手策略:用TCP把数据包发送出去后,TCP不会对传送后的数据置之不理,它一定会向对方确认是否成功送达

  • 发送端首先给接收端发送一个带SYN标志的数据包。

  • 接收端收到后,回传一个带有SYN/ACK标志的数据包以表示正确传达,并确认信息。

  • 最后,发送端再回传一个带ACK标志的数据包,代表“握手”结束。

得到服务器的 ip 地址后,浏览器根据这个 ip 以及相应的端口号,构造一个 http 请 求,这个请求报文会包括这次请求的信息,主要是请求方法,请求说明和请求附带的数 据,并将这个 http 请求封装在一个 tcp 包中,这个 tcp 包会依次经过传输层,网络层, 数据链路层,物理层到达服务器,服务器解析这个请求来作出响应,返回相应的 html 给浏览器

三、传输数据

连接后,客户端向服务端发起 HTTP 请求,服务器接收到请求后,返回请求静态资源,并同时调用 apache 服务器请求接口数据。

四、关闭TCP连接

数据传输完成,客户端与服务端进行四次挥手,关闭连接。

断开一个TCP连接则需要“四次挥手”:

  • 第一次挥手:主动关闭方发送一个FIN,用来关闭主动关闭方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方,主动关闭方已经不会再给被动关闭方发送数据了(当然,在FIN包之前发送出去的数据,如果没有收到对应的ACK确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可以接收数据。

  • 第二次挥手:被动关闭方收到FIN包后,给对方发送一个ACK,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。

  • 第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,被动关闭方的数据也发送完了,不会再给主动关闭方发送数据了。

  • 第四次挥手:主动关闭方收到FIN后,给被动关闭方发送一个ACK,确认序号为收到序号+1,至此,完成四次握手。

五、渲染页面

对于浏览器根据服务端返回的静态资源,浏览器使用 Native GUI 引擎渲染 HTML 和 CSS ;使用 JS 引擎加载 JS 。

因为 html 是一个树形结构,浏览器根据这个 html 来构建 DOM 树,在 dom 树的构建过程中如果遇到 JS 脚本和外部 JS 连接,则会停止构建 DOM 树来执行和下载 相应的代码,这会造成阻塞,这就是为什么推荐 JS 代码应该放在 html 代码的后面,之 后根据外部样式,内部样式,

内联样式构建一个 CSS 对象模型树 CSSOM 树,构建完成 后和 DOM 树合并为渲染树,这里主要做的是排除非视觉节点,比如 script,meta 标签和 排除 display 为 none 的节点,之后进行布局,布局主要是确定各个元素的位置和尺寸,之后是渲染页面,因为 html 文件中会含有图片,视频,音频等资源,在解析 DOM 的过 程中,遇到这些都会进行并行下载,浏览器对每个域的并行下载数量有一定的限制,一 般是 4-6 个,当然在这些所有的请求中我们还需要关注的就是缓存

  • 将 HTML 节点解析成 DOM 树结构

在DOM树的构建过程中如果遇到JS脚本和外部JS连接,则会停止构建DOM树来执行和下载相应的代码,这会造成阻塞,这就是为什么推荐JS代码应该放在html代码的后面。

从 HTML 字节码到 DOM 树结构的流程:字节(Bytes) =### 字符串(Characters) =### Tokens =### 节点(Nodes) =### DOM

计算机只能识别0和1的字节,根据字节的编码规则将字节转换成字符串,再将字符串转化为 W3C 定义的各种标签,生成 tokens(令牌),匹配字符串,将 tokens 按照规则转换为包含属性和规则的节点对象(nodes),根据每个节点的层次关系(父子节点关系)和规则转换为直观的树形结构。HTML 是增量构建的,在 HTML 文件还在传输时,这个转换过程就已经开始了。最终得到完整的 DOM(Document Object Module文档对象模型)

  • 将 CSS 解析成 CSSOM 规则树

这里主要做的是排除非视觉节点,比如script,meta标签和排除display为none的节点;有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系

CSS 构建的过程跟 DOM 差不多,只不过 CSS 会涉及到复杂的计算,如 CSS 的属性来源,匹配不同的类(id 或者 class),确认复写规则及权重,最后确定每个节点的样式值,形成 CSSOM(CSS Object Module CSS对象模型)。

  • 将 DOM 与 CSSOM 组合成 Render-tree(渲染树)

  • 布局:计算出每个节点在屏幕中的位置

  • 绘制:即遍历render树,并使用UI后端层绘制每个节点

六、加载 JavaScript 脚本

虽然 HTML\CSS 与 JS 是通过不同的引擎加载,但是却是互斥的,即加载 HTML\CSS 时,JS 会停止加载,相反亦然,这是因为 JS 引擎可以操作 DOM,改变样式、内容等。所以当执行了 JS 之后,渲染树要重新构建。

缓存一般通过 Cache-Control、Last-Modify、Expires 等首部字段控制。 Cache-Control 和 Expires 的区别 在于 Cache-Control 使用相对时间,Expires 使用的是基于服务器 端的绝对时间,因为存 在时差问题,一般采用 Cache-Control,在请求这些有设置了缓存的数据时,会先 查看 是否过期,如果没有过期则直接使用本地缓存,过期则请求并在服务器校验文件是否修 改,如果上一次 响应设置了 ETag 值会在这次请求的时候作为 If-None-Match 的值交给 服务器校验,如果一致,继续校验 Last-Modified,没有设置 ETag 则直接验证 Last-Modified,再决定是否返回 304。

简单来说:

DNS 解析

TCP 连接

发送 HTTP 请求

服务器处理请求并返回 HTTP 报文

浏览器解析渲染页面

连接结束

小黄:嗯恩,那你说说 cache-control 的值有哪些

小达:嗯恩,好的。

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

小黄:嗯恩,那你说说 浏览器在生成页面的时候,会生成哪两颗树?

小达:嗯恩,好的。

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

小黄:嗯恩,那你 CSS3 用的多吗?了解它们的新属性吗?

小达:嗯恩,还行。

CSS3边框:

border-radius:CSS3圆角边框。

在 CSS2 中添加圆角矩形需要技巧,我们必须为每个圆角使用不同的图片,在 CSS3 中,创建圆角是非常容易的,在 CSS3 中,border-radius 属性用于创建圆角。border:2px solid;

box-shadow:CSS3边框阴影。在 CSS3 中,box-shadow 用于向方框添加阴影。

1
box-shadow:10px 10px 5px #888888;

border-image:CSS3边框图片。

通过 CSS3 的 border-image 属性,您可以使用图片来创建边框。border-image:url(border.png) 30 30 round;

background-size: 属性规定背景图片的尺寸。在 CSS3 之前,背景图片的尺寸是由图片的实际尺寸决定的。在 CSS3 中,可以规定背景图片的尺寸,这就允许我们在不同的环境中重复使用背景图片。您能够以像素或百分比规定尺寸。如果以百分比规定尺寸,那么尺寸相对于父元素的宽度和高度。

background-origin :属性规定背景图片的定位区域。背景图片可以放置于 content-box、padding-box 或 border-box 区域。

text-shadow:在 CSS3 中,text-shadow 可向文本应用阴影。text-shadow:5px 5px 5px #FFFFFF;

word-wrap :单词太长的话就可能无法超出某个区域,允许对长单词进行拆分,并换行到下一行:p{word-wrap:break-word;}

transform:通过 CSS3 转换,我们能够对元素进行移动、缩放、转动、拉长或拉伸。

CSS3 3D转换:

rotateX():元素围绕其 X 轴以给定的度数进行旋转。transform:rotateX(120deg);

rotateY():元素围绕其 Y 轴以给定的度数进行旋转。transform:rotateY(120deg);

小黄:嗯恩,那你 说一下 css 盒模型

小达:嗯恩,好的。

CSS 中的盒子模型包括 IE 盒子模型和标准的 W3C 盒子模型

image.png
从上图可以看到标准 W3C 盒子模型的范围包括 margin、border、padding、content,并且 content 部分不包含其他部分。
image.png
从上图可以看到 IE 盒子模型的范围也包括 margin、border、padding、content,和标准 W3C 盒子模型不同的是:IE 盒子模型的 content 部分包含了 border 和 padding。

小达:嗯恩,好的。

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

小黄:嗯恩,那你说说 Flex 布局

小达:嗯恩,好的。

推荐:哪吒闹海,席卷图文学习前端Flex布局-七日打卡

布局的传统解决方案,基于盒状模型,依赖 display 属性 + position 属性 + float 属性

容器的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
flex-direction:决定主轴的方向(即子 item 的排列方法) 

.box { flex-direction: row | row-reverse | column | column-reverse; }

flex-wrap:决定换行规则

.box{ flex-wrap: nowrap | wrap | wrap-reverse; }

flex-flow:

.box { flex-flow: <flex-direction### || <flex-wrap###; }

justify-content:对其方式,水平主轴对齐方式

align-items:对齐方式,竖直轴线方向

项目的属性(元素的属性):

order 属性:定义项目的排列顺序,顺序越小,排列越靠前,默认为 0

flex-grow 属性:定义项目的放大比例,即使存在空间,也不会放大

flex-shrink 属性:定义了项目的缩小比例,当空间不足的情况下会等比例的缩小,如果 定义个 item 的 flow-shrink 为 0,则为不缩小

flex-basis 属性:定义了在分配多余的空间,项目占据的空间。

flex:是 flex-grow 和 flex-shrink、flex-basis 的简写,默认值为 0 1 auto。

align-self:允许单个项目与其他项目不一样的对齐方式,可以覆盖 align-items,默认属 性为 auto,表示继承父元素的 align-items 比如说,用 flex 实现圣杯布局

小黄:嗯恩,那你说说 BFC

小达:嗯恩,好的。BFC块级格式化上下文,用于清除浮动,防止 margin 重叠等

块级格式化上下文,是一个独立的渲染区域,并且有一定的布局规则。

  • BFC 区域不会与 float box 重叠
  • BFC 是页面上的一个独立容器,子元素不会影响到外面
  • 计算 BFC 的高度时,浮动元素也会参与计算
    那些元素会生成 BFC

如何创建BFC

1、float的值不是none

2、position的值不是static或者relative

3、display的值是inline-block、table-cell、flex、table-caption或者inline-flex

4、overflow的值不是visible

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html###
<html lang="en"###
<head###
<meta charset="UTF-8"###
<meta name="viewport" content="width=device-width, initial-scale=1.0"###
<meta http-equiv="X-UA-Compatible" content="ie=edge"###
<title###防止margin重叠</title###
</head###
<style###
*{
margin: 0;
padding: 0;
}
p {
color: #f55;
background: yellow;
width: 200px;
line-height: 100px;
text-align:center;
margin: 30px;
}
</style###
<body###
<p###看看我的 margin是多少</p###
<p###看看我的 margin是多少</p###
</body###
</html###

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html###
<html lang="en"###
<head###
<meta charset="UTF-8"###
<meta name="viewport" content="width=device-width, initial-scale=1.0"###
<meta http-equiv="X-UA-Compatible" content="ie=edge"###
<title###防止margin重叠</title###
</head###
<style###
*{
margin: 0;
padding: 0;
}
p {
color: #f55;
background: yellow;
width: 200px;
line-height: 100px;
text-align:center;
margin: 30px;
}
div{
overflow: hidden;
}
</style###
<body###
<p###看看我的 margin是多少</p###
<div###
<p###看看我的 margin是多少</p###
</div###
</body###
</html###

image.png

小黄:嗯恩,那你说说 垂直居中的方法

小达:嗯恩,好的。

一:父元素 display:flex,align-items:center;

二:元素绝对定位,top:50%,margin-top:-(高度/2)

三:高度不确定用 transform:translateY(-50%)

四:父元素 table 布局,子元素设置 vertical-align:center;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
div { 
width: 400px;
height: 400px;
position: relative;
border: 1px solid #465468;
}

img {
position: absolute;
margin: auto; top: 0;
left: 0; right: 0;
bottom: 0;
}

<div###
<img src="mm.jpg"###
</div###

利用 flex
display:flex;
align-items:center;
justify-content:center;

小黄:嗯恩,那你说一下块元素和行元素

小达:嗯恩,好的。

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

小黄:嗯恩,那你说一下 position 属性

小达:嗯恩,好的。

固定定位 fixed:

元素的位置相对于浏览器窗口是固定位置,即使窗口是滚动的它也不会移动。Fixed 定位使元素的位置与文档流无关,因此不占据空间。 Fixed 定位的元素和其他元素重叠。

相对定位 relative:

如果对一个元素进行相对定位它将出现在它所在的位置上。然后,可以通过设置垂直 或 水平位置,让这个元素“相对于”它的起点进行移动。 在使用相对定位时,无论是否进行移动,元素仍然占据原来的空间。因此,移动元素会导致它覆盖其它框

绝对定位 absolute:

绝对定位的元素的位置相对于最近的已定位父元素,如果元素没有已定位的父元素,那 么它的位置相对于<html###。absolute 定位使元素的位置与文档流无关,因此不占据空间。 absolute 定位的元素和其他元素重叠。

粘性定位 sticky:

元素先按照普通文档流定位,然后相对于该元素在流中的 flow root(BFC)containing block(最近的块级祖先元素)定位。而后,元素定位表现为在跨越特定阈值前为相对定 位,之后为固定定位。

默认定位 Static: 默认值。

没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声 明)。

inherit: 规定应该从父元素继承 position 属性的值

小黄:嗯恩,那你说一下 清除浮动

小达:嗯恩,好的。

方法一:使用带 clear 属性的空元素

在浮动元素后使用一个空元素如

<div class="clear"###</div###,并在 CSS 中赋予.clear{clear:both;}属性即可清理浮动。亦可使用<br class="clear" /###或<hr class="clear" /### 来进行清理。

方法二:使用 CSSoverflow 属性

给浮动元素的容器添加 overflow:hidden;overflow:auto; 可以清除浮动,另外在 IE6 中还需要触发 hasLayout ,例如为父元素设置容器宽高或设置 zoom:1。 在添加 overflow 属性后,浮动元素又回到了容器层,把容器高度撑起,达到了 清理浮动 的效果。

方法三:使用邻接元素处理 什么都不做,给浮动元素后面的元素添加 clear 属性。

方法四:使用 CSS:after 伪元素

结合:after 伪元素(注意这不是伪类,而是伪元素,代表一个元素之后最近的元素)和 IEhack ,可以完美兼容当前主流的各大浏览器,这里的 IEhack 指的是触发 hasLayout。 给浮动元素的容器添加一个 clearfixclass,然后给这个 class 添加一个 :after 伪元素实现元素末尾添加一个看不见的块元素(Block element)清理浮动。

推荐:CSS技巧(一):清除浮动

小黄:嗯恩,那你说一下 CSS 选择器有哪些,以及优先级

小达:嗯恩,好的。

  • id 选择器,class 选择器,标签选择器,伪元素选择器,伪类选择器
  • 优先级顺序为:id 选择器 ### class 选择器 ### 标签选择器
  • 带有!important 标记的样式属性的优先级最高
  • 优先级顺序为:内联样式### 内部样式 ### 外部样式 ### 浏览器用户 自定义样式 ### 浏览器默认样式

小黄:嗯恩,那你说一下 怎么样让一个元素消失

小达:嗯恩,好的。可以使用:

1
2
3
display:none; 
visibility:hidden;
opacity: 0;

小黄:嗯恩,那你说一下 CSS3 中对溢出的处理

小达:嗯恩,好的。

  • text-overflow 属性,值为 clip 是修剪文本;
  • ellipsis 为显示省略符号来表被修剪的文本;
  • string 为使用给定的字符串来代表被修剪的文本。

小黄:嗯恩,那你说一下 z-index 的定位方法

小达:嗯恩,好的。z-index 属性设置元素的堆叠顺序,拥有更好堆叠顺序的元素会处于较低顺序元素之前, z-index 可以为负,且 z-index 只能在定位元素上奏效,该属性设置一个定位元素沿 z 轴的位置,如果为正数,离用户越近,为负数,离用户越远,它的属性值有 auto,默认, 堆叠顺序与父元素相等,number,inherit,从父元素继承 z-index 属性的值

小黄:嗯恩,那你说一下 重绘和重排

小达:嗯恩,好的。

  • 重绘不一定需要重排,重排必然会导致重绘
  • 重排:当渲染树的一部分必须更新并且节点的尺寸发生了变化,浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树(当DOM的变化引发了元素几何属性的变化,比如改变元素的宽高,元素的位置,导致浏览器不得不重新计算元素的几何属性,并重新构建渲染树,这个过程称为“重排”。)
  • 重绘:是在一个元素的外观被改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观(完成重排后,要将重新构建的渲染树渲染到屏幕上,这个过程就是“重绘”)
  • DOM树:表示页面的结构
  • 渲染树:表示页面的节点如何显示
  • 一旦渲染树构建完成,就要开始绘制(paint)页面元素了

重排触发机制 重排发生的根本原理就是元素的几何属性发生了改变

image.png

  • 添加或删除可见的DOM元素
  • 元素位置改变
  • 元素本身的尺寸发生改变
  • 内容改变
  • 页面渲染器初始化
  • 浏览器窗口大小发生改变

小黄:嗯恩,那你说一下 box-sizing

小达:嗯恩,好的。box-sizing 规定两个并排的带边框的框,语法为 box-sizing:content-box/border-box/inherit

实例
规定两个并排的带边框的框:

image.png

image.png

1
2
3
4
5
6
7
8
div
{
box-sizing:border-box;
-moz-box-sizing:border-box; /* Firefox */
-webkit-box-sizing:border-box; /* Safari */
width:50%;
float:left;
}

image.png

image.png

image.png

  • content-box:宽度和高度分别应用到元素的内容框,在宽度和高度之外绘制元素的内边 距和边框
  • border-box:为元素设定的宽度和高度决定了元素的边框盒
  • inherit:继承父元素的 box-sizing

小黄:嗯恩,那你说一下 get 和 post 请求在缓存方面的区别

小达:嗯恩,好的。

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

小黄:嗯恩,那你说一下 一句话概括闭包

小达:嗯恩,好的。闭包就是能够读取其他函数内部变量的函数,或者子函数在外调用, 子函数所在的父函数的作用域不会被释放。

小黄:嗯恩,那你说一下类的创建和继承

小达:嗯恩,好的。

类的创建(es5):new 一个 function,在这个 function 的 prototype 里面增加属性和 方法。

下面来创建一个 Animal 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义一个动物类 
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
2
3
4
5
6
7
8
9
10
11
12
function Cat(){ } 
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());

console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
  • 特点:基于原型链,既是父类的实例,也是子类的实例
  • 缺点:无法实现多继承

构造继承:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给 子类(没用到原型)

1
2
3
4
5
6
7
8
9
10
11
12
function Cat(name){ 
Animal.call(this);
this.name = name || 'Jeskson';
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());

console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
  • 特点:可以实现多继承
  • 缺点:只能继承父类实例的属性和方法,不能继承原型上的属性和方法

组合继承:相当于构造继承和原型链继承的组合体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
  • 特点:可以继承实例属性/方法,也可以继承原型属性/方法
  • 缺点:调用了两次父类构造函数,生成了两份实例

寄生组合继承:

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Cat(name){ 
Animal.call(this);
this.name = name || 'Jeskson';
}

(function(){
// 创建一个没有实例方法的类
var Super = function(){};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
})();

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true

小黄:嗯恩,那你说一下如何让事件先冒泡后捕获

小达:嗯恩,好的。

在 DOM 标准事件模型中,是先捕获后冒泡。但是如果要实现先冒泡后捕获的效果,对 于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓 执行,直到冒泡事件被捕获后再执行捕获事件。

小黄:嗯恩,那你说一下事件委托

小达:嗯恩,好的。

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

举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用事件委 托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。 好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。

小黄:嗯恩,那你说一下图片的懒加载和预加载

小达:嗯恩,好的。

  • 预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染
  • 懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数

两种技术的本质:

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

小黄:嗯恩,那你说一下mouseover 和 mouseenter 的区别

小达:嗯恩,好的。

  • mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。对应的移除事件是 mouseout

  • mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,对应的移除事件是 mouseleave

小黄:嗯恩,那你说一下 JS 的 new 操作符做了哪些事情

小达:嗯恩,好的。

new 操作符新建了一个空对象,这个对象原型指向构造函数的 prototype,执行构造函数 后返回这个对象

小黄:嗯恩,那你说一下 JS 拖拽功能的实现

小达:嗯恩,好的。

首先是三个事件,分别是 mousedown,mousemove,mouseup

当鼠标点击按下的时候,需要一个 tag 标识此时已经按下,可以执行 mousemove 里面的具体方法。 clientX,clientY 标识的是鼠标的坐标,分别标识横坐标和纵坐标,并且我们用 offsetX 和 offsetY 来表示元素的元素的初始坐标

移动的举例应该是: 鼠标移动时候的坐标-鼠标按下去时候的坐标。

也就是说定位信息为: 鼠标移动时候的坐标-鼠标按下去时候的坐标+元素初始情况下的 offetLeft. 还有一点也是原理性的东西,也就是拖拽的同时是绝对定位,我们改变的是绝对定位条件下的 left 以及 top 等等值。

小黄:嗯恩,那你说一下 JS 的节流和防抖

小达:嗯恩,好的。

防抖

1
2
3
4
5
6
7
8
9
10
11
12
13
// timer用于储存定时器
delayed(){
// 检测定时器
if(this.timer){
// 如果有定时器,则清除
clearTimeout(this.timer)
this.timer=null
}
// 设置定时器和事件
this.timer=setTimeout(()=>{
// do something
},500)
}

节流

1
2
3
4
5
6
7
8
9
10
11
12
test(){
let that=this
if(that.stopClickLogin){
console.log(`请等待1s后再次点击 >>>>>>>>>`)
return false
}
that.stopClickLogin=true
setTimeout(()=>{
that.stopClickLogin=false
},1000)
console.log(`do something >>>>>>>>>`)
}
  • 搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索,具体使用哪种方案要看业务需求。
  • 页面resize事件,常见于需要做页面适配的时候。需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况)

函数防抖

image.png

函数节流

image.png

小黄:嗯恩,那你实现一个 once 函数,传入函数参数只执行一次

小达:嗯恩,好的。

1
2
3
4
5
6
7
8
9
10
function ones(func){ 
var tag=true;
return function(){
if(tag==true){
func.apply(null,arguments);
tag=false;
}
return undefined
}
}

小黄:嗯恩,那你说说 ==和===、以及 Object.is 的区别

小达:嗯恩,好的。

(1) == 主要存在:强制转换成 number,null==undefined

1
2
3
4
5
" "==0 //true 
"0"==0 //true
" " !="0" //true
123=="123" //true
null==undefined //true

(2)Object.js 主要的区别就是+0!=-0NaN==NaN (相对比===和==的改进)

小黄:嗯恩,那你实现一个 bind 函数

小达:嗯恩,好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Function.prototype.bind=function(obj,arg){
var arg=Array.prototype.slice.call(arguments,1);
var context=this;
return function(newArg){
arg=arg.concat(Array.prototype.slice.call(newArg));
return context.apply(obj,arg);
}
}

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;
}

小黄:嗯恩,如何数组去重

小达:嗯恩,好的。

一:indexOf 循环去重

二:ES6 Set 去重;Array.from(new Set(array))

三:Object 键值对去重;把数组的值存成 Object 的 key 值,比如 Object[value1] = true, 在判断另一个值的时候,如果 Object[value2]存在的话,就说明该值是重复的。

小黄:嗯恩,如何如何判断一个数组

小达:嗯恩,好的。

  • Object.prototype.call.toString()
  • instanceof

小黄:嗯恩,说说 JS 基本数据类型

小达:嗯恩,好的。基本数据类型:undefined、null、number、boolean、string、symbol

小黄:嗯恩,说说 跨域的原理

小达:嗯恩,好的。

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

小黄:嗯恩,说说 什么是按需加载

小达:嗯恩,好的。当用户触发了动作时才加载对应的功能。触发的动作,是要看具体的业务场景而言,包 括但不限于以下几个情况:鼠标点击、输入文字、拉动滚动条,鼠标移动、窗口大小更 改等。加载的文件,可以是 JS、图片、CSS、HTML 等。

小黄:嗯恩,说说 JS 中继承实现的几种方式

小达:嗯恩,好的。

1、原型链继承

2、构造继承

3、实例继承

4、拷贝继承

5、组合继承

6、寄生组合继承

小黄:嗯恩,说说 Vue 的生命周期

小达:嗯恩,好的。

Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模板、挂载 Dom、渲染→更新→渲染、销毁等一系列过程,我们称这是 Vue 的生命周期。通俗说就 是 Vue 实例从创建到销毁的过程,就是生命周期。

每一个组件或者实例都会经历一个完整的生命周期,总共分为三个阶段:初始化、运行 中、销毁。

实例、组件通过 new Vue() 创建出来之后会初始化事件和生命周期,然后就会执行 beforeCreate 钩子函数,这个时候,数据还没有挂载呢,只是一个空壳,无法访问到数据 和真实的 dom,一般不做操作

挂载数据,绑定事件等等,然后执行 created 函数,这个时候已经可以使用到数据,也 可以更改数据,在这里更改数据不会触发 updated 函数,在这里可以在渲染前倒数第二次 更改数据的机会,不会触发其他的钩子函数,一般可以在这里做初始数据的获取

接下来开始找实例或者组件对应的模板,编译模板为虚拟 dom 放入到 render 函数中准备 渲染,然后执行 beforeMount 钩子函数,在这个函数中虚拟 dom 已经创建完成,马上就 要渲染,在这里也可以更改数据,不会触发 updated,在这里可以在渲染前最后一次更改 数据的机会,不会触发其他的钩子函数,一般可以在这里做初始数据的获取

接下来开始 render,渲染出真实 dom,然后执行 mounted 钩子函数,此时,组件已经出 现在页面中,数据、真实 dom 都已经处理好了,事件都已经挂载好了,可以在这里操作 真实 dom 等事情

当组件或实例的数据更改之后,会立即执行 beforeUpdate,然后 Vue 的虚拟 dom 机制会 重新构建虚拟 dom 与上一次的虚拟 dom 树利用 diff 算法进行对比之后重新渲染,一般不做什么事儿

当更新完成后,执行 updated,数据已经更改完成,dom 也重新 render 完成,可以操作 更新后的虚拟 dom

当经过某种途径调用$destroy 方法后,立即执行 beforeDestroy,一般在这里做一些善后工 作,例如清除计时器、清除非指令绑定的事件等等

组件的数据绑定、监听…去掉后只剩下 dom 空壳,这个时候,执行 destroyed,在这里做 善后工作也可以

小黄:嗯恩,说说 浏览器阻塞会影响哪些操作

小达:嗯恩,好的。script脚本在执行的时候会阻塞DOM树解析,渲染,延迟DOMContentLoaded事件触发。

小黄:嗯恩,说说 文件上传,前后端你都做了些什么处理

小达:嗯恩,好的。

  • 安全方面校验文件类型和后缀
  • 对图片进行压缩
  • 支持大文件拆分上传请求
  • 支持断点续传
  • 支持显示上传进行和暂停上传

小黄:嗯恩,说说 Vue中v-model的实现

小达:嗯恩,好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<input type="text" :value="inputValue" @change="handleInputChange"/>
</template>
export default {
methods: {
handleInputChange(e) {
this.inputValue = e.target.value;
},
}
}
Vue.component('child',{
template: `<span>1024bibi.com</span>`,
model: {
prop: 'checked',
event: 'change',
},
props: {
checked: Boolean
}
})
<child v-model="status" @change="handleChange"></child>

小黄:嗯恩,说说 什么是事件监听

小达:嗯恩,好的。

addEventListener()方法,用于向指定元素添加事件句柄,它可以更简单的控制事件,语法为element.addEventListener(event, function, useCapture);

第一个参数是事件的类型(如 "click" 或 "mousedown")

第二个参数是事件触发后调用的函数

第三个参数是个布尔值用于描述事件是冒泡还是捕获。该参数是可选的。 事件传递有两种方式,冒泡和捕获

事件传递定义了元素事件触发的顺序,如果你将 P 元素插入到 div 元素中,用户点击 P 元素, 在冒泡中,内部元素先被触发,然后再触发外部元素, 捕获中,外部元素先被触发,在触发内部元素。

小黄:嗯恩,说说 promise

小达:嗯恩,好的。Promise 是一个对象,保存着未来将要结束的事件

对象的状态不受外部影响,Promise 对象代表一个异步操作,有三种状态,pending 进行中,fulfilled 已成功,rejected 已失败,只有异步操作的结果,才可以决定当前是哪 一种状态,任何其他操作都无法改变这个状态,这也就是 promise 名字的由来

一旦状态改变,就不会再变,promise 对象状态改变只有两种可能,从 pending 改到 fulfilled 或者从 pending 改到 rejected,只要这两种情况发生,状态就凝固了,不会再改 变,这个时候就称为定型 resolved

Promise 的基本用法:

1
2
3
4
5
6
7
8
let promise1 = new Promise(function(resolve,reject){
setTimeout(function(){
resolve('ok')
},1000)
})
promise1.then(function success(val){
console.log(val)
})

小黄:嗯恩,说说 JS 原型链,原型链的顶端是什么?Object 的原型是什么?Object 的原型的 原型是什么?在数组原型链上实现删除数组重复数据的方法

小达:嗯恩,好的。

JS 是通过原型来实现继承的,在 JS 中一个构造函数默认带有一个 prototype 属性,这个的属性值是一个对象, 同时这个 prototype 对象自带有一个 constructor 属性,这个属性指向这个构造函数,同时 每一个实例都会有一个_proto_属性指向这个 prototype 对象,我们可以把这个叫做隐式原 型,我们在使用一个实例的方法的时候,会先检查这个实例中是否有这个方法,没有的 话就会检查这个 prototype 对象是否有这个方法

每个函数都有一个 prototype 属性,这个属性指向了一个对象,这个对象正是调用该函数 而创建的实例的原型

那么什么是原型呢,可以这样理解,每一个 JavaScript 对象在创 建的时候就会预制管理另一个对象,这个对象就是我们所说的原型,每一个对象都会从 原型继承属性

image.png

那么怎么表示实例与实例原型的关系呢

_proto_ 这是每一个 JS 对象都会有的一个属性,指向这个对象的原型

image.png

既然实例对象和构造函数都可以指向原型,那么原型是否有属性指向构造函数或者实例 呢,指向实例是没有的,因为一个构造函数可以生成多个实例,但是原型有属性可以直 接指向构造函数,通过 constructor 即可

实例和原型的关系

当读取实例的属性时,如果找不到,就会查找与对象相关的原型中的属性,如果还查不 到,就去找原型的原型,一直找到最顶层,那么原型的原型是什么呢

首先,原型也是 一个对象,既然是对象,我们就可以通过构造函数的方式创建它,所以原型对象就是通过 Object 构造函数生成的

如图:

image.png

那么 Object.prototype 的原型呢,我们可以打印 console.log(Object.prototype.__proto__ === null),返回 true

null 表示没有对象,即该处不应有值,所以 Object.prototype 没有原型

image.png

示例:

1
2
3
4
5
constructor:
function Person(){
}
var person = new Person();
console.log(Person === person.constructor);

原本 person 中没有 constructor 属性,当不能读取到 constructor 属性时,会从 person 的原 型中读取,所以指向构造函数 Person

__proto__:

绝大部分浏览器支持这个非标准的方法访问原型,然而它并不存在与 Person.prototype 中, 实际上它来自 Object.prototype,当使用 obj.__proto__时,可以理解为返回来 Object.getPrototype(obj)

继承:

每个对象都会从原型继承属性

继承意味着复制操作,然而 JS 默认不会复制对象的属性,相反,JS 只是在两个对象之间创建一个关联,这样子一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫 继承,叫委托更合适。

小黄:嗯恩,说说 什么是 js 的闭包?

小达:嗯恩,好的。

闭包是指那些能够访问自由变量的函数,自由变量是指在函数中使用的,但既不是函数参数又不是函数的局部变量的变量,由此可以看出,闭包=函数+函数能够访问的自由变量

所以从技术的角度讲,所有 JS 函数都是闭包,但是这是理论上的闭包,还有一个实践角度上的闭包,从实践角度上来说,只有满足:

1、即使创建它的上下文已经销毁,它仍然存在

2、在代码中引入了自由变量,才称为闭包

**闭包的应用: **

  • 模仿块级作用域
  • 保存外部函数的变量
  • 封装私有变量

小黄:嗯恩,说说 promise+Generator+Async 的使用

小达:嗯恩,好的。

Promise 解决的问题:回调地狱

Promise 规范:

promise 有三种状态,等待(pending)、已完成(fulfilled/resolved)、已拒绝(rejected).

Promise 的状态只能从“等待”转到“完成”或者“拒绝”,不能逆向转换,同时“完成”和 “拒绝”也不能相互转换.

promise 必须提供一个 then 方法

promise.then(resolve, reject),resolvereject 都是可选参数。如果 resolve 或 reject 不是函数,其必须被忽略. then 方法必须返回一个 promise 对象.

使用: 实例化 promise 对象需要传入函数(包含两个参数),resolve 和 reject,内部确定状态.resolve 和 reject 函数可以传入参数在回调函数中使用. resolve 和 reject 都是函数,传入的参数在 then 的回调函数中接收.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var promise = new Promise(function(resolve, reject) {
setTimeout(function(){
resolve('1024bibi.com');
});
});
promise.then(function(val){
console.log(val)
})

promise.then(val=>{
//resolved
},reason=>{
//rejected
})

Promise 回调链:

promise 能够在回调函数里面使用 return 和 throw, 所以在 then 中可以 return 出一个 promise 对象或其他值,也可以 throw 出一个错误对象,但如果没有 return,将默认返回 undefined,那么后面的 then 中的回调参数接收到的将是 undefined.

Generator 函数

generator 函数使用:

1、分段执行,可以暂停

2、可以控制阶段和每个阶段的返回值

3、可以知道是否执行到结尾

generator 和异步控制:

利用 Generator 函数的暂停执行的效果,可以把异步操作写在 yield 语句里面,等到调用 next 方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在 yield 语句下面,反正要等到调用 next 方法时再执行。所以,Generator 函数 的一个重要实际意义就是用来处理异步操作,改写回调函数

async 和异步:

用法:

async 表示这是一个 async 函数,await 只能用在这个函数里面。 await 表示在这里等待异步操作返回结果,再继续执行。

await 后一般是一个 promise 对象

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

小黄:嗯恩,说说 事件委托以及冒泡原理

小达:嗯恩,好的。

事件委托是利用冒泡阶段的运行机制来实现的,就是把一个元素响应事件的函数委托到另一个元素,一般是把一组元素的事件委托到他的父元素上

委托的优点是

减少内存消耗,节约效率 动态绑定事件 事件冒泡,就是元素自身的事件被触发后,如果父元素有相同的事件,如 onclick 事件, 那么元素本身的触发状态就会传递,也就是冒到父元素,父元素的相同事件也会一级一级根据嵌套关系向外触发,直到 document/window,冒泡过程结束。

小黄:嗯恩,说说 代码规范

小达:嗯恩,好的。

  • 源文件

JavaScript源文件必须以无BOM的UTF-8编码。

  • 缩进

必须采用4个空格缩进,不允许以Tab制表符或2个空格代

  • switch 中的 case 和 default 必须保持缩进。
1
2
3
4
5
6
7
8
9
10
11
// 正例
switch (variable) {
case '1':
// do...
break;
case '2':
// do...
break;
default:
// do...
}
  • 空格

二元运算符两个操作数之间必须使用空格,但一元运算符与其操作数间不能有空格。

1
2
3
let a = !arr.length;
a++;
a = b + c;
  • { 前必须有1个空格。
1
2
3
4
5
6
7
8
9
10
11
12
// 正例

if (condition) {
}

set('attr', {
some: 'xxx',
any: 'yyy'
});

function funcName() {
}
  • if / else / for / while / function / switch / do / try / catch / finally 等关键字与括号之间都必须加一个空格。
1
2
3
4
5
6
7
8
9
10
// 正例

if (condition) {
}

while (condition) {
}

(function () {
})();
  • 在创建对象的语句中,: 后必须加一个空格,: 前不能有空格。
1
2
3
4
5
6
// 正例
const obj = {
a: 1,
b: 2,
c: 3
};
  • 在函数声明、命名函数表达式和函数调用时,函数名称和 ( 间不能有空格。
1
2
3
4
5
6
7
8
9
// 正例

function funcName() {
}

const funcName = function funcName() {
};

funcName();
  • , 和 ; 前不能有空格。
1
2
// 正例
callFunc(a, b);
  • ( 和 [ 之后、 ) 和 ] 之前不能有空格。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 正例

callFunc(param1, param2, param3);

save(this.list[this.indexes[i]]);

needIncream && (variable += increament);

if (num > list.length) {
}

while (len--) {
}

// 正例
const arr1 = [];
const arr2 = [1, 2, 3];
const obj1 = {};
const obj2 = {name: 'obj'};
const obj3 = {
name: 'obj',
age: 20,
sex: 1
};
  • 每行代码后不能有尾随空格。

  • 单行字符数不能超过120个,超出则需要换行。

  • 如果括号中的内容有多行,必须为 )、 ]、 } 另开一个新行,并使新行与 (、 [、 { 所在行的缩进相对应。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 正例
if (product) {
product.load();
if (user.isAuthenticated()
&& user.isInRole('admin')
&& user.hasAuthority('add-admin')
) {
sendProduct(user, product);
}
}
const arr = [
'candy', 'sugar'
];
  • , 或 ; 前不能有换行
1
2
3
4
5
6
7
8
9
10
11
12
// 正例
const obj = {
a: 1,
b: 2,
c: 3
};

foo(
aVeryVeryLongArgument,
anotherVeryLongArgument,
callback
);
  • 如果使用多行代码块,else 和 catch 必须另开一个新行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 正例

if (condition) {
// some statements;
}
else {
// some statements;
}

try {
// some statements;
}
catch (ex) {
// some statements;
}
  • 语句必须以 ; 结尾。
  • 如果只有一行,{} 不能被省略。
1
2
3
4
// 正例
if (condition) {
callFunc();
}
  • 函数定义的末尾不能有分号 ;
1
2
3
// 正例
function funcName() {
}
  • 对象和数组的声明中不能有尾随逗号。
1
2
3
4
5
6
7
8
9
10
11
// 正例

const obj = {
attr1: 'xxx',
attr2: 'yyy'
};

const arr = [
'xxx',
'yyy'
];

命名规约

  • 变量名、属性名及函数名的命名必须遵循 lowerCamelCase(小骆驼拼写法)。
1
2
3
const loadingModules = {};
function loadProduct() {
}
  • class类的命名必须遵循 UpperCamelCase (Pascal),即大骆驼拼写法(帕斯卡拼写法)。
1
2
function Element(options) {
}
  • 缩略词的所有字符应当一并大写或一并小写。
1
2
3
function parseSVG() {
}
const svgParser;
  • 优先使用const声明变量,且一行不能同时声明多个变量。
1
2
3
4
5
// 正例
const name = 'MyName';
const hangModules = [];
const missModules = [];
const visited = {};
  • 对于相等运算符,== 只能用于检查是否为 null 或者 undefined,其余情况必须使用 ===
1
2
3
4
5
6
7
// 正例
if (age === 30) {
// ...
}
if (type == null) {
// ...
}
  • 建议使用 xxx == null 来判断 null 或 undefined

  • 函数表达式或者函数声明不应该放在循环体中。

1
2
3
4
5
6
7
8
9
// 正例
function clicker() {
// ......
}

for (let i = 0, len = elements.length; i < len; i++) {
const element = elements[i];
addListener(element, 'click', clicker);
}
  • 建议使用 + '' 将值转为字符串。
1
2
// 正例
num + '';
  • 建议使用 + 将值转为数值。
1
2
// 正例
+str;
  • 在使用 parseInt 时,必须传入第二个参数。
1
2
// 正例
parseInt(str, 10);
  • 必须使用 ' 而不是 " 定义字符串。
  • 必须使用对象字面量 {} 来创建简单对象。
1
2
// 正例
const obj = {};
  • 如果一个对象字面量的所有属性都不需要引号,引号必须省略。如果需要加引号,须使用 ' 而不是 "
1
2
3
4
5
// 正例
const info = {
name: 'someone',
age: 28
};
  • 禁止修改内置对象的原型。
1
2
3
// 禁止
String.prototype.trim = function () {
};
  • 尽可能使用 . 而不是 [] 访问对象的属性。
  • 使用 for ... in ... 时,应当注意使用 hasOwnProperty 以防 Object 的原型在某些运行时环境中被添加一些额外属性的情况。
1
2
3
4
5
6
const newInfo = {};
for (const key in info) {
if (info.hasOwnProperty(key)) {
newInfo[key] = info[key];
}
}
  • 除非需要创建指定长度的数组,否则必须使用数组字面量 [] 创建数组。
1
2
3
// 正例
const arr = [];
const arr2 = new Array(1e4);
  • 不要使用 for in 语句对数组进行遍历。
  • 要使用 eval 和 with。允许使用new Function

小黄:嗯恩,说说 深浅拷贝

小达:嗯恩,好的。

数组的浅拷贝

如果是数组,我们可以利用数组的一些方法,比如 slice,concat 方法返回一个新数组的特性来实现拷贝,但假如数组嵌套了对象或者数组的话,使用 concat 方法克隆并不完整, 如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化,我们把这种复制引用的拷贝方法称为浅拷贝

深拷贝

指完全的拷贝一个对象,即使嵌套了对象,两者也互相分离,修改一个对象的属性,不会影响另一个

小黄:嗯恩,说说 let const var 的区别

小达:嗯恩,好的。

var 声明的变量是全局或者整个函数块的,而 let,const 声明 的变量是块级的变量var 声明的变量存在变量提升,let,const 不存在,let 声明的变量允许重新赋值,const 不允许

小黄:嗯恩,说说 ES6 箭头函数的特性

小达:嗯恩,好的。

箭头函数与普通函数的区别在于

1、箭头函数没有 this,所以需要通过查找作用域链来确定 this 的值,这就意味着如果箭头函数被非箭头函数包含,this 绑定的就是最近一层非箭头函数的 this

2、箭头函数没有自己的 arguments 对象,但是可以访问外围函数的 arguments 对象

3、不能通过 new 关键字调用,同样也没有 new.target 值和原型

小黄:嗯恩,说说 setTimeout 和 Promise 的执行顺序

小达:嗯恩,好的。

setTimeout 这种异步操作的回调,只有主线程中没有执行任何同步代码的前提下,才会 执行异步回调

promise 何时执行,promise 构造函数里代码 同步执行的

then 方法指向的回调将在当前脚本所有同步任务执行完成后执行,执行顺序为:同步执行的代码-》promise.then->settimeout

小黄:嗯恩,说说 setTimeout(fn,100);100 毫秒是如何权衡的

小达:嗯恩,好的。

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

小黄:嗯恩,说说 怎么获得对象上的属性

小达:嗯恩,好的。

  • for(let I in obj) 该方法依次访问一个对象及其原型链中所有可枚举的类型
  • object.keys:返回一个数组,包括所有可枚举的属性名称
  • object.getOwnPropertyNames:返回一个数组包含不可枚举的属性

小黄:嗯恩,讲一讲 ES6 的一些新特性

小达:嗯恩,好的。

ES6 在变量的声明和定义方面增加了 let、const 声明变量,有局部变量的概念,赋值中 有比较吸引人的结构赋值,同时 ES6 对字符串、 数组、正则、对象、函数等拓展了一 些方法,如字符串方面的模板字符串、函数方面的默认参数、对象方面属性的简洁表达 方式

ES6 也引入了新的数据类型 symbol,新的数据结构 set 和 map,symbol 可以通过 typeof 检测出来,为解决异步回调问题,引入了 promise 和 generator,还有最为吸引人 Class 和 模块,通过 Class 可以更好的面向对象编程,使用模块加载方便模块化编程

当然考虑到 浏览器兼容性,我们在实际开发中需要使用 babel 进行编译

重要的特性

块级作用域:ES5 只有全局作用域和函数作用域,块级作用域的好处是不再需要立即执 行的函数表达式,循环体中的闭包不再有问题

rest 参数:用于获取函数的多余参数,这样就不需要使用 arguments 对象了,

promise: 一种异步编程的解决方案,比传统的解决方案回调函数和事件更合理强大

模块化:其模块功能主要有两个命令构成,export 和 importexport 命令用于规定模块的 对外接口import 命令用于输入其他模块提供的功能

小黄:嗯恩,讲一讲 call 和 apply 是用来做什么

小达:嗯恩,好的。

Call 和 apply 的作用是一模一样的,只是传参的形式有区别而已

1、改变 this 的指向

2、借用别的对象的方法,

3、调用函数,因为 apply,call 方法会使函数立即执行

小黄:嗯恩,讲一讲 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。

小黄:嗯恩,那你知道哪些 ES6,ES7 的语法

小达:嗯恩。promise,await/async,let、const、块级作用域、箭头函数

小黄:嗯恩,那你知道基本对象类型以及引用对象类型的区别吗

小达:嗯恩。

分为**基本对象类型和引用对象类型 **

基本数据类型:

按值访问,可操作保存在变量中的实际的值。基本类型值指的是简单的 数据段。基本数据类型有这六种:undefined、null、string、number、boolean、symbol。

引用类型:

当复制保存着对象的某个变量时,操作的是对象的引用,但在为对象添加属 性时,操作的是实际的对象。引用类型值指那些可能为多个值构成的对象。

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

小黄:嗯恩,那你知道arguments吗

小达:嗯恩。arguments 是类数组对象,有 length 属性,不能调用数组方法 可用 Array.from()转换

小黄:嗯恩,那你知道JSONP 的缺点吗

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

说到jsonp,那说说跨域:跨域(jsonp,ajax)

JSONP

ajax 请求受同源策略影响,不允许进行跨域请求,而 script 标签 src 属性中的链接却可以访问跨域的 js 脚本,利用这个特性,服务端不再返回 JSON 格式的数据,而是返回一段调用某个函数的 js 代码,在 src 中进行了调用,这样实现了跨域。

说到ajax,说说它的返回状态有哪些吧

0 - (未初始化)还没有调用 send()方法

1 - (载入)已调用 send()方法,正在发送请求

2 - (载入完成)send()方法执行完成,已经接收到全部响应内容

3 - (交互)正在解析响应内容

4 - (完成)响应内容解析完成,可以在客户端调用了

小黄:嗯恩,那你说说移动布局方案

推荐:CSS 常见布局方式

推荐:rem与em的使用和区别详解

推荐:移动端适配1px的问题终极方案

推荐:移动端性能优化的方法

推荐:常见移动端兼容性问题

回看笔者往期高赞文章,也许能收获更多喔!

❤️关注+点赞+收藏+评论+转发❤️

点赞、收藏和评论

我是Jeskson(达达前端),感谢各位人才的:点赞、收藏和评论,我们下期见!(如本文内容有地方讲解有误,欢迎指出☞谢谢,一起学习了)

我们下期见!

文章持续更新,可以微信搜一搜「 程序员哆啦A梦 」第一时间阅读,回复【资料】有我准备的一线大厂资料,本文 http://www.dadaqianduan.cn/#/ 已经收录

github收录,欢迎Starhttps://github.com/webVueBlog/WebFamily