2020前端面试-面试题分享 (上) | 掘金技术征文

1,900 阅读31分钟

前言

   人在,代码在!大家好,我是2年前端小油条一枚(本来,我打算写萌新一枚的,结果被人喷了,说我都老油条了😭),主要的技术栈是Vue全家桶。本篇文章主要是近一个月来自己遇见的一些面试题和自己的回答。因为内容有点多,所以会分成上下两篇,大纲如下图:

希望可以对大家有所帮助!青春须早为,岂能长少年。冲。




一、浏览器

1.1 OSI七层模型和TCP/IP四层模型

 七层模型   四层模型   功能   协议 
 应用层  应用层  文件传输,电子邮件,虚拟终端等  HTTP, FTP, DNS域名系统等
 表示层  数据格式化,代码转换,数据加密
 会话层  建立或解除和别的端的连接
 传输层  传输层  提供端对端的接口,数据传输  TCP传输控制协议, UDP用户数据报协议,SSL安全套接字协议
 网络层  网络层  为数据包选择路由  IP,ICMP, RIP等
 数据链路层  数据链路层  传输有地址的帧以及错误检测功能  PPP,ARP, RARP等
 物理层  以二进制形式在物理传输媒体上传输数据  IEEE标准

1.2 在浏览器上输入url之后的流程

我会大致上分成 获取资源 和 页面渲染 两个阶段。

  • 阶段1 获取资源
  1. 查找url对应服务器的IP, 如果没有在缓存中找到,则通过DNS服务器进行解析
  2. 发起TCP的3次握手
  3. 建立TCP连接后发起http请求
  4. 服务器响应http请求,返回相应的html给浏览器
  5. 浏览器解析html
  • 阶段2 页面渲染
  1. 逐行解析html,加载外部文件
  2. 生成css树和dom树(互不影响),但script标签的加载阻塞dom树的构建
  3. attachment,将css树和dom树结合,进行layout构建出渲染树。css文件则会阻塞渲染树的构建
  4. paint渲染,呈现页面

1.3 HTTP, HTTPS, HTTP2.0的区别和联系

HTTP: 超文本传输协议,是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和响应的标准。或者说是计算机在两端之间传输文字、图片、音频、视频等超文本数据的约定和规范。

HTTS: 是以安全为目标的HTTP通道,即HTTP下加入SSL层,SSL加密是在传输层实现的。http是明文传输,https则是具有安全性的ssl加密传输协议(证书中的公钥和服务器的私钥)

优点:

  • 使用 HTTPS 协议可认证用户和服务器,确保数据发送到正确的客户机和服务器。
  • 比HTTP协议更安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性 。
  • HTTPS 是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。

缺点:

  • 相同网络环境下,HTTPS 协议会使页面的加载时间延长近 50%,增加 10%到 20%的耗电。此外,HTTPS 协议还会影响缓存,增加数据开销和功耗。
  • HTTPS 协议的安全是有范围的,在黑客攻击、拒绝服务攻击和服务器劫持等方面几乎起不到什么作用。
  • SSL证书需要钱,功能越大费用越高。

优缺点来自百度 baike.baidu.com/item/https


HTTP2.0: 超文本传输协议 2.0,是下一代HTTP协议。在开放互联网上HTTP2.0将只用于https网址,而 http网址将继续使用HTTP/1。他有以下优点:

  • 提升访问速度
  • 允许多路复用:多路复用允许同时通过单一的HTTP/2连接发送多重请求-响应信息。改善了:在http1.1中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制(连接数量),超过限制会被阻塞。(使用请求ID对应响应)
  • 二进制分帧:HTTP2.0会将所有的传输信息分割为更小的信息或者帧,并对他们进行二进制编码
  • 首部压缩: HPACK(HTTP2头部压缩算法)压缩格式对传输的header进行编码。并在两端建立索引表,进行缓存。
  • 服务器端推送

特别的 meta标签可以把http请求换为https

<meta http-equiv ="Content-Security-Policy" content="upgrade-insecure-requests">

1.3.1 HTTP状态码

  1. 信息
  • 100 Continue: 继续, 客户端应继续其请求
  • 101 Switching Protocols: 切换协议
  • 102 Processing: 处理将被继续执行
  1. 成功
  • 200 OK: 请求成功
  • 201 Created: 请求已经被实现
  • 202 Accepted: 服务器已接受请求,但尚未处理
  • 204 No Content: 服务器成功处理,但未返回内容
  1. 重定向
  • 300 Multiple Choices: 被请求的资源有一系列可供选择的回馈信息, 用户或浏览器能够自行选择一个首选的地址进行重定向。
  • 301 Moved Permanently: 被请求的资源已永久移动到新位置
  • 302 Move temporarily: 请求的资源临时从不同的 URI响应请求
  • 303 See Other: 对应当前请求的响应可以在另一个 URI 上被找到,而且客户端应当采用 GET 的方式访问那个资源
  • 304 Not Modified: 所请求的资源未修改,服务器返回此状态码时,不会返回任何资源
  • 305 Use Proxy: 被请求的资源必须通过指定的代理才能被访问
  1. 客户端错误
  • 400 Bad Request: 语义有误,当前请求无法被服务器理解 | 请求参数有误
  • 401 Unauthorized: 当前请求需要用户验证
  • 403 Forbidden: 服务器已经理解请求,但是拒绝执行它
  • 404 Not Found: 请求失败,请求所希望得到的资源未被在服务器上发现
  • 405 Method Not Allowed: 请求行中指定的请求方法不能被用于请求相应的资源。例如使用服务端为未允许的PUT,DELETE请求。
  • 408 Request Timeout: 请求超时
  • 409 Conflict: 由于和被请求的资源的当前状态之间存在冲突,请求无法完成
  • 410 Gone: 被请求的资源在服务器上已经不再可用,而且没有任何已知的转发地址
  • 413 Request Entity Too Large: 服务器拒绝处理当前请求,因为该请求提交的实体数据大小超过了服务器愿意或者能够处理的范围
  • 414 Request-URI Too Long: 请求的URI 长度超过了服务器能够解释的长度,因此服务器拒绝对该请求提供服务
  • 415 Unsupported Media Type: 对于当前请求的方法和所请求的资源,请求中提交的实体并不是服务器中所支持的格式,因此请求被拒绝
  1. 服务端错误
  • 500 Internal Server Error: 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理
  • 501 Not Implemented: 服务器不支持当前请求所需要的某个功能
  • 502 Bad Gateway: 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应
  • 503 Service Unavailable: 由于临时的服务器维护或者过载,服务器当前无法处理请求
  • 504 Gateway Timeout: 作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)或者辅助服务器(例如DNS)收到响应
  • 505 HTTP Version Not Supported: 服务器不支持,或者拒绝支持在请求中使用的 HTTP 版本

1.3.2 常见HTTP请求头

 通用头部

  • Cache-Control:指定请求和响应遵循的缓存机制。

    • public:表明响应可以被任何对象缓存,即使是通常不可缓存的内容。(例如:1.该响应没有max-age指令或Expires消息头;2. 该响应对应的请求方法是 POST 。)
    • private:表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)
    • no-cache:在发布缓存副本之前,强制要求缓存把请求提交给原始服务器进行验证(协商缓存验证)。。   - no-store:缓存不应存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。   - max-age:设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。还是仍由缓存提供。若同时还发送了max-stale指令,则使用期可能会超过其过期时间。   - s-maxage,max-stale,min-fresh等  
  • Connection:连接管理

    • Close:在完成本次请求的响应后,断开连接。
    • Keepalive:在完成本次请求的响应后,保持连接,等待本次连接的后续请求。
    • Keep-Alive:如果浏览器请求保持连接,则该头部表明希望 WEB 服务器保持连接多长时间(秒),如Keep-Alive:300。
  • Date 消息发送的日期和时间,世界标准时。

  • Transfor-Encoding 报文主体的传输编码格式

  • Upgrade 升级协议。如http1.0升级到http1.1,http升级到websocket

 请求头部

  • Host:请求资源所在的服务器 (唯一一个HTTP/1.1规范里要求必须出现的字段)
  • Accept:客户端或者代理能够处理的媒体类型。还有Accept-Charset,Accept-Encoding,Accept-Language
  • If-Match:对象的 ETag 没有改变才执行请求的动作,去获取文档。
  • If-None-Match:对象的 ETag 改变了,才执行请求的动作,去获取文档。
  • If-Modified-Since:如果请求的对象在该头部指定的时间之后修改了,才执行请求的动作(比如返回对象),否则返回代码304,告诉浏览器该对象没有修改。
  • If-Unmodified-Since:如果请求的对象在该头部指定的时间之后没修改过,才执行请求的动作(比如返回对象)。
  • Range:实体的字节范围请求。例如:Range: bytes=1173546
  • Referer:表明自己是从哪个网页URL获得点击当前请求中的网址
  • User-Agent:客户端信息

 响应头部

  • Location:重定向的 URI
  • ETag:表示资源唯一资源的字符串
  • Server:服务器的信息

 实体头部

  • Allow 资源可支持 HTTP 请求方法
  • Last-Modified 资源最后修改时间
  • Expires 实体主体过期时间
  • Content-Language:实体资源语言
  • Content-Encoding:实体编码格式
  • Content-Length:实体大小
  • Content-Range:实体传送的范围
  • Content-Type:实体媒体类型
  • Content-MD5:主体的MD5校验和

1.4 DNS

   域名系统(服务)协议(DNS)是一种分布式网络目录服务,主要用于域名与 IP 地址的相互转换,以及控制因特网的电子邮件的发送。为了保证高可用、高并发和分布式,它设计成了树状的层次结构。由根DNS服务器、顶级域 DNS 服务器和权威 DNS 服务器组成。

   解析顺序是首先从浏览器缓存、操作系统缓存以及本地 DNS 缓存 (/etc/hosts) 逐级查找,然后从本地 DNS 服务器、根 DNS、顶级 DNS 以及权威 DNS层层递归查询。

   还可以基于域名在内网、外网进行负载均衡。

   不过传统的 DNS 有很多问题(解析慢、更新不及时),HTTPDNS 通过客户端 SDK 和服务端配合,直接通过 HTTP 调用解析 DNS 的方式,可以绕过传统 DNS 这些缺点,实现智能调度。

答案来自 mp.weixin.qq.com/s/mgGKIxYbZ…


1.5 UDP 和 TCP

1.5.1 两者的区别

UDP协议全称是用户数据报协议

  • 面向无连接: 首先 UDP 是不需要和 TCP一样在发送数据前进行三次握手建立连接的,想发数据就可以开始发送了。并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作。
  • 有单播,多播,广播的功能
  • 不可靠性:这种不可靠性体现在无连接上
  • 头部开销小,传输数据报文时是很高效的

TCP协议全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议

  • 面向连接:发送数据之前必须在两端建立连接。建立连接的方法是“三次握手”
  • 仅支持单播传输:只能进行点对点的数据传输,不支持多播和广播传输方式
  • 可靠传输:判断丢包,误码靠的是TCP的段编号以及确认号
  • 提供拥塞控制:当网络出现拥塞的时候,TCP能够减小向网络注入数据的速率和数量,缓解拥塞

1.5.2 三次握手和四次挥手

TCP连接采用三次握手

  1. 客服端首先发送一个带有SYN标志地数据包给服务端。自身进入sent状态,等待服务端确认
  2. 服务端接收后,回传一个带有SYN/ACK标志的数据包传递确认信息,表示我收到了,服务端进入rcvd收到状态。
  3. 最后,客户端收到确认,再回传一个带有ACK标志的数据包,代表我知道了,表示"握手"结束,两端进入established确立状态。

通俗的说法:

Client:喂,小明,是我,听的到吗? Server:听到了,你听的见吗? Client:听见了,听见了,我们可以聊天了。

为什么需要三次握手?可能有以下两种情况

  • 客户端发出第一次挥手后就失效了,那么服务端接收后就产生TCP链接是无用的,需要第三次挥手确认
  • 服务端发送的数据表,因为网络情况丢失了,客户端超时后重新发起了链接,那么上次的TCP,链接服务端因为没有接受到回应而一直开着,造成了资源浪费

TCP断开采用四次挥手

  1. 客户端发送一带有个FIN标志地数据包给服务,用来关闭客户端到服务端的数据传送,客户端进入FIN_WAIT_1状态。
  2. 服务端收到后,发送一个ACK数据包给客户端,自身进入CLOSE_WAIT状态。
  3. 服务端发送一个FIN,用来关闭服务端到客户端的数据传送,客户端进入LAST_ACK状态。
  4. 客户端收到FIN后,进入TIME_WAIT状态,接着发送一个ACK给 服务端,服务端进入CLOSED状态,通信结束。

通俗的说法(云顶)

Client:我已经第8,抬走出门了。 Server:我看到了,等等我,我还有血。 Server:好了,我也结束了。 Client:好的,这把游戏结束。

为什么中间服务端发送了两次? 因为此时服务端可能还有数据没有发送完,所以并不会马上关闭socket,而是等待数据发送后再发送FIN标志。


1.6 跨域

1.6.1 同源策略

   同源策略是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

同源策略限制以下几种行为:

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM 和 Js对象无法获得
  • AJAX 请求不能发送

1.6.2 跨域处理方案

  • Jsonp:网页通过动态添加一个 script 元素,向服务器请求数据; 服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。但只支持get请求。
  • 设置document.domain: 此方案仅限主域相同,子域不同的跨域应用场景
  • 通过更改中间页面的hash值或是window.name属性。
  • PostMessage跨域API
  • 设置跨域资源共享(CORS),需要浏览器和服务器同时支持。
    • Access-Control-Allow-Origin
    • Access-Control-Allow-Methods
    • Access-Control-Allow-Headers
    • Access-Control-Allow-Credentials
    • Access-Control-Max-Age
  • Nginx反向代理
  • Node中间件代理
  • Websock:因为有了Origin(请求源)这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。

1.6.3 跨域未携带cookie可能的原因

  • 未设置withCredentials:axios, $.ajax, xhr未设置withCredentials的请求头
  • CORS的Access-Control-Allow-Credentials未设置成true

1.7 事件循环

1.7.1 宏任务和微任务

   首先来了解下宏任务和微任务,异步任务分为task(宏任务,也可称为macroTask)和microtask(微任务,也叫jobs)两类。

Task:

  • setTimeout
  • setInterval
  • setImmediate (Node独有)
  • requestAnimationFrame (浏览器独有)
  • I/O
  • UI rendering (浏览器独有)
  • script标签

MicroTask:

  • process.nextTick (Node独有)
  • Promise
  • MutationObserver

当满足执行条件时,task和microtask会被放入各自的队列中,等待放入执行线程执行,我们把这两个队列称为Task Queue(也叫Macrotask Queue)和Microtask Queue。


1.7.2 基本流程

  1. 从任务队列task queue选择中最先进入的任务执行,如果队列为空,则执行microtask queue。
  2. 检查该task是否注册了microtask queue,如果有,则按注册顺序依次执行microtask。如果没有则继续下一步。
  3. 一些其他操作,例如界面渲染。
  4. 重复以上步骤。

1.8 GC垃圾回收机制

   垃圾回收机制(GC:Garbage Collection),执行环境负责管理代码执行过程中使用的内存。浏览器会在浏览器渲染的空闲时间内清除内存。

   在V8中,主要将内存分为新生代和老生代,新生代的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存的对象。

1.8.1 栈内存的回收

栈内存在调用栈上下文切换后就会被回收。

1.8.2 堆内存的回收

  • 新生代内存回收机制:新生代内存容量小,默认下,64位系统下仅有32M。

   使用cheney算法将新生代内存分为From、To两部分,处于使用状态的称为From空间,处于闲置状态的称为To空间。进行垃圾回收时,将From中的存活对象复制到To,然后将非存活对象回收,之后调换From/To。

  • 晋升:如果新生代的变量经过复制依然依然存活时,那么就会被放入老生代内存中。晋升有两个条件:

    • 是否经历过新生代的回收
    • To空间内存占比超过限制
  • 老生代内存回收机制

   V8在老生代中主要采用Mark-Sweep和Mark-Compact相结合的方式进行垃圾回收。主要使用Mark-Sweep,在空间不足以对新生代中晋升过来的对象进行分配是才使用Mark-Compact。

  • Mark-Sweep: 在标记阶段遍历所有堆中的所有对象,并标记活着的对象,在清除阶段清除没有标记的对象。
  • Mark-Compact: 在Mark-Sweep的基础上演变而来,差别在于在标记死亡后,在整理过程中会将活着的对象往一端移动,移动后,直接清理掉边界外的内存,解决Mark-Sweep的内存碎片问题。

答案来自《深入浅出Node》


1.9 缓存机制

   浏览器会拷贝一份已经请求过的web资源在内存或硬盘中。当下一个请求到来的时候,如果是相同的URL,浏览器会根据缓存机制决定是直接使用副本响应访问请求还是向源服务器再次发起请求。使用缓存有以下的优点:

  • 减少网络带宽消耗
  • 降低服务器压力
  • 减少网络延迟

浏览器的缓存分成强缓存和协商缓存两种。对应的字段有

  • 强缓存:Expires 和 Cache-Control,Expires 和 Cache-Control。
  • 协商缓存:Last-Modified / If-Modified-Since 和 Etag / If-None-Match,其中 Etag / If-None-Match 的优先级比 Last-Modified / If-Modified-Since 高。

浏览器再向服务器请求资源时,首先会判断是否命中强缓存,再判断是否命中协商缓存。 简单的流程图如下:

推荐文章:zhuanlan.zhihu.com/p/25953524


1.10 REST架构

   REST即表述性状态传递,表述性状态转移是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful。需要注意的是,REST是设计风格而不是标准。 他有以下几个原则:

  • C-S架构: 数据的存储在Server端,Client端只需使用就行。两端彻底分离。
  • 无状态: 从客户端到服务器的每个请求都必须包含理解请求所需的所有信息,并且不能利用服务器上任何存储的上下文。这表示你应该尽可能的避免使用session,由客户端自己标识会话状态。(例如使用token标识用户而不是session)
  • 规范接口: restfulAPI - restfulAPI就是用URL定位资源,用HTTP描述操作
    • get: 请求数据
    • post: 提交数据
    • put: restful,更新实体的全部属性
    • patch: 更新实体的部分属性
    • delete: restful,删除实体
  • 可缓存: 响应中的数据隐式或显式标记为可缓存或不可缓存。它表示get请求响应头中应该表示有是否可缓存的头(Cache-Control)

二、Html

2.1 语义化

语义化:用合理、正确的标签来展示内容,比如h1~h6定义标题。

优点:

  • 易于用户阅读,样式丢失的时候能让页面呈现清晰的结构。
  • 有利于SEO,搜索引擎根据标签来确定上下文和各个关键字的权重。
  • 方便其他设备解析,如盲人阅读器根据语义渲染网页
  • 有利于开发和维护,语义化更具可读性,代码更好维护,与CSS3关系更和谐。

HTML5语义化标签:article nav(导航) aside section(节,段) header footer address等


2.2 事件捕获流,冒泡流和事件委托

  • 事件流描述的是从页面中接收事件的顺序。
  • 类型
    • 事件冒泡流:事件的传播是从最特定的事件目标到最不特定的事件目标。即从DOM树的叶子到根。(IE)
    • 事件捕获流:事件的传播是从最不特定的事件目标到最特定的事件目标。即从DOM树的根到叶子。(网景公司)
  • DOM标准规定事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。
    • 事件捕获阶段:实际目标()在捕获阶段不会接收事件。也就是在捕获阶段,事件从(window ->)document到再到就停止了。
    • 处于目标阶段:事件在上发生并处理。但是事件处理会被看成是冒泡阶段的一部分。
    • 冒泡阶段:事件又传播回文档。
  • 事件委托又叫事件代理,是根据事件冒泡流,让父元素代理响应函数减少DOM的访问

注意以下事件不支持冒泡

  • blur
  • focus
  • load
  • unload
  • 以及自定义的事件。

原因是在于:这些事件仅发生于自身上,而它的任何父节点上的事件都不会产生,所有不会冒泡

如何阻止事件捕获或冒泡

  • 阻止冒泡
e.stopPropagation() || return falsewindow.e.cancelBubble=true; // IE
  • 阻止捕获
e.stopImmediatePropagation() // 阻止捕获和其他事件
  • 阻止默认事件: 事件处理过程中,不阻击事件冒泡,但阻击默认行为
e.preventDefault()

window.e.returnValue=false; || return false;// IE

2.3 盒子模型

   一个盒子模型可分成margin(外边距), border(边框), padding(内边距), content(内容), 四部分组成。

盒子模型分为标准盒子模型和IE盒子模型。区别是:

  • 标准盒子模型中,width 和 height 指的是内容区域的宽度和高度。增加内边距、边框和外边距不会影响内容区域的尺寸,但是会增加元素框的总尺寸。
  • IE盒子模型中,width 和 height 指的是内容区域+padding+border的宽度和高度。
  • box-sizing: content-box|border-box|inherit;
    • content-box: 使用标准盒子模型
    • border-box: 使用IE盒子模型

2.4 BFC

块级格式化上下文 (Block Fromatting Context)

为了便于理解,我们换一种方式来重新定义BFC。一个HTML元素要创建BFC,则满足下列的任意一个或多个条件即可:

  • body元素
  • float的值不是none。
  • position的值不是static或者relative。
  • display的值是inline-block、table-cell、flex、table-caption或者inline-flex
  • overflow的值不是visible

BFC是一个独立的布局环境,其中的元素布局是不受外界的影响,并且在一个BFC中,块盒与行盒(行盒由一行中所有的内联元素所组成)都会垂直的沿着其父元素的边框排列。

特性:

  • 内部的Box会在垂直方向,一个接一个地放置。
  • Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠 (margin坍塌)
  • 每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
  • BFC的区域不会与float box重叠。
  • 计算BFC的高度时,浮动元素也参与计算
  • BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。

2.5 Script异步加载和JSONP

如何实现script异步加载:

  • 动态添加script标签
  • defer属性,如果script标签设置了该属性,则浏览器会异步的下载该文件并且不会影响到后续DOM的渲染; 如果有多个设置了defer的script标签存在,则会按照顺序执行所有的script; defer脚本会在文档渲染完毕后,DOMContentLoaded事件调用前执行。
  • async属性:相当于异步回调,加载完后立即执行。它们将在onload 事件之前完成。

介绍一下JSONP:

JSONP: 利用html的script标签,img标签,iframe标签,可以请求第三方的资源(不受同源策略影响)的作用做跨域请求。

  1. 定义一个回调函数cb
  2. 动态加载一个script标签,url一般是url?callback=cb
  3. 服务端会把返回的参数和函数包装起来,以js语法的形式返回
  4. 接受http响应,执行回调函数的代码

2.6 src和href的区别

  • href:Hypertext Reference的缩写,超文本引用,它指向一些网络资源,建立和当前元素或者说是本文档的链接关系。
<a href="https://www.aaa.com"></a>

<link type="text/css" rel="stylesheet" href="aaa.css">
  • src:source的缩写,表示的是对资源的引用,它指向的内容会嵌入到当前标签所在的位置。
<img src="aaa.jpg">
  
<iframe src="aaa.html">
  
<script src="aaa.js">

区别

  • src用于替代这个元素,而href用于建立这个标签与外部资源之间的关系。
  • 浏览器需要加载完毕src的内容才会继续往下走。而遇到href,页面会并行加载后续内容。

2.7 你在移动端H5开发中遇到了哪些问题?

2.7.1 IPhoneX的适配

  1. 适配方案viewport-fit:
  • auto默认:viewprot-fit:contain;页面内容显示在safe area内
  • cover:viewport-fit:cover,页面内容充满屏幕
<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">

IOS还提供了函数constant、env和内置变量:

  • safe-area-inset-top
  • safe-area-inset-right
  • safe-area-inset-left
  • safe-area-inset-bottom

例如

buttom: 0
buttom: constant(safe-area-inset-bottom);
buttom:env(safe-area-inset-bottom);
  1. 媒体查询
 @media only screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) {
    <!-- 样式 -->
 }
  1. 小程序

可以通过wx.getSystemInfo获取设备信息,来动态绑定样式


2.7.2 ios键盘问题

  • iOS12系统在输入框失去焦点,软键盘关闭后,被撑起的页面无法回退到原来的位置
$("input, select").blur(function(){ 
 setTimeout(() => { 
 const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop || 0;
 window.scrollTo(0, Math.max(scrollHeight - 1, 0)); 
 }, 100); 
}
  • 键盘挡住输入框
element.scrollIntoView() // 让当前的元素滚动到浏览器窗口的可视区域内。
  • ios无法自动聚焦唤起键盘

ios中唤起键盘必须有用户触发,所以无法直接用focus,可以由用户触发一个其他事件来使得输入框focus,从而唤起键盘。


2.7.3 视频非全屏播放和自动播放

使用playsinline webkit-playsinline="true" 支持非全屏播放视频

<video id='video' x5-video-player-type="h5" controls preload="auto" 
playsinline webkit-playsinline="true" muted="muted" autoplay='autoplay'></video>

不是所有的版本都支持自动播放,有些需要静音才能自动播放。一般我会用下面的函数进行辅助。

initVideo() {
  if (isWX) {
    let video = this.$refs.video1
    if (nextMounted) { // 非首次渲染
      setTimeout(() => {
        this.palyAudio();
      }, 300)
    } else {
      document.addEventListener("WeixinJSBridgeReady", () => {
        video.muted = false
        video.play()
        nextMounted = true
      }, false)
    }
  } else {
    setTimeout(() => {
      this.palyAudio();
    }, 300)
  }
},
palyAudio() {
  let video = this.$refs.video1
  // 判断是否支持自动播放
  let promise = video.play()
  if (promise !== undefined) {
    promise.then(() => {
      video.play()
    }).catch((error) => {
      // 自动播放的新规则 muted=true 可以自动播放
      video.muted = false
    });
  }
  // touch时触发视频播放
  document.querySelector('.page').addEventListener('touchstart', () => {
    if (firstTouch && isIOS) {
      video.muted = false
      firstTouch = false
      if (nextMounted) {
        video.play()
      }
    }
  })
}

三、CSS

3.1 优先级

权重越高,优先级越高;同一权重内,!import优先级最高; :not 不参与优先级计算

  • !import: 10000
  • 内联样式: 1000
  • id选择器: 100
  • class选择器,属性选择器,伪元素等:10
  • 标签选择器: 1

3.2 单位

  • %:
    • margin,padding是相对父元素的宽度计算的
    • top,left,bottom,right是相对于父元素的高度计算的
    • translate是相对自身元素来算的
  • px:像素点
  • em:1em等于当前元素的font-size(浏览器默认16px,可继承,em可以省略)
  • rem:1rem等于html元素的font-size
  • vw,vh:视口宽度被均分成100vw,视口高度被均分成100vh。视口宽高是页面的可视区域,如键盘弹起安卓的视口宽高发生变化,但iPhone不会。(安卓4.4,iOS6以上支持)
  • vmax, vmin:
    • vmax = max(vw,vh)(vmax安卓4.4,iOS8以上支持)
    • vmin = min(vw,vh)(vmin安卓4.4,iOS6以上支持)
  • ex和ch:
    • ex以字符"x"的高度为基准;例如1ex表示和字符"x"一样长。
    • ch以数字"0"的宽度为基准;例如2ch表示和2个数字"0"一样长。

兼容性查看网站推荐:caniuse.com/#


3.3 居中方案

3.3.1 水平居中

  • 行内元素: text-align: center;
  • 设置宽度 + margin: xxx auto;
  • flex的justify-content: center;
  • grid的justify-items: center;
  • 父元素相对定位,子元素绝对定位,left:50%。 如果子元素宽度已知,使用margin-left: -子元素宽度/2; 宽度未知,则使用transformX(-50%);

3.3.2 垂直居中

  • 行内元素: padding-top = padding-bottom
  • 行内元素: line-height = height
  • 父元素display:table; 子元素display:table-cell; vertical-align: middle;
  • flex的align-items: center;
  • grid的align-items: center;
  • 父元素相对定位,子元素绝对定位,top:50%。 如果子元素高度已知,使用margin-top: -子元素高度/2; 宽度未知,则使用transformY(-50%);
  • 父元素相对定位,子元素绝对定位。 子元素height: xxx; top: 0; bottom: 0; margin: auto xxx;

3.3.3 水平垂直居中

  • 已知高宽,使用负边距法
  • 未知,使用grid, flex或transfrom

3.4 link 和 @import的区别

引用CSS的两种方式:

<link rel="stylesheet" href="common.css" type="text/css" />

<style type="text/css">@import url(common.css) </style>
  • link是XHTML标签,除了加载CSS外,还可以定义RSS等其他事务;@import属于CSS范畴,只能加载CSS。
  • link引用CSS时,在页面载入时同时加载;@import需要页面网页完全载入以后加载。
  • link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。
  • link支持使用Javascript控制DOM去改变样式;而@import不支持。

3.5 float

  • 脱离文档流的控制
  • 只有左浮动或右浮动
  • 行内元素浮动后,元素的display会赋值为block,且拥有浮动特性,原留白也会消失。但是不会继承父元素的宽高
  • 页面的表现形式为: 浮动的元素会尽量向左或向右移动,直到它的外边缘碰到包含框或另一个浮动框的边框为止。浮动元素之后的元素将围绕它。浮动元素之前的元素将不会受到影响。

如何清除浮动:

/* 清除浮动 */
.clear-f::after {
  display: block;
  height: 0;
  content: '';
  clear: both;
  visibility:hidden;
}

3.6 position

  • absolute
    • 脱离文档流,通过 top,bottom,left,right 定位。选取其最近一个最有定位设置的父级对象进行绝对定位,如果对象的父级没有设置定位属性,absolute元素将以body坐标原点进行定位,可以通过z-index进行层次分级。
  • fixed
    • 生成固定定位的元素,相对于浏览器窗口进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行定位。
  • relative
    • 对象不可层叠、不脱离文档流。生成相对定位的元素,相对于其正常位置进行定位。因此,"left:20" 会向元素的 LEFT 位置添加 20 像素。
  • static
    • 默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。
  • sticky
    • 粘性定位,该定位基于用户滚动的位置。它的行为就像 position:relative;而当页面滚动超出目标区域时,它的表现就像position:fixed;,它会固定在目标位置。注意: Internet Explorer, Edge 15 及更早 IE 版本不支持 sticky 定位。 Safari需要使用 -webkit-
  • inherit
    • 从父元素继承 position 属性的值
  • initial
    • 设置该属性为默认值

3.7 transition 和 animation

transition

transition:all 0 ease 0; 
  • transition-property: 规定设置过渡效果的 CSS 属性的名称
  • transition-duration: 规定完成过渡效果需要多少秒或毫秒
  • transition-timing-function: 规定速度效果的速度曲线
  • transition-delay: 定义过渡效果何时开始

js函数: ontransitionstart, ontransitionend

animation 和 keyframes

animation:none 0 ease 0 1 normal;
  • animation-name: 规定需要绑定到选择器的 keyframe 名称。。
  • animation-duration: 规定完成动画所花费的时间,以秒或毫秒计。
  • animation-timing-function: 规定动画的速度曲线。
  • animation-delay: 规定在动画开始之前的延迟。
  • animation-iteration-count: 规定动画应该播放的次数。
  • animation-direction: 规定是否应该轮流反向播放动画。

@keyframes animation-name {keyframes-selector {css-styles;}}

  • animationname: 必需的,定义animation的名称。
  • keyframes-selector: 必需的,动画持续时间的百分比。 合法值:0-100% ; from (和0%相同) ; to (和100%相同) 注意: 您可以用一个动画keyframes-selectors。
  • css-styles 必需的。一个或多个合法的CSS样式属性

3.8 一像素边框

  • 媒体查询,写具体数值
.border { border: 1px solid #999 }
@media screen and (-webkit-min-device-pixel-ratio: 2) {
    .border { border: 0.5px solid #999 }
}
  • 设置 border-image 方案
.border-image-1px {
    border-width: 1px 0px;
    -webkit-border-image: url("border.png") 2 0 stretch;
    border-image: url("border.png") 2 0 stretch;
}
  • box-shadow 方案, box-shadow: h-shadow v-shadow [blur] [spread] [color] [inset]; 分别是水平阴影位置,垂直阴影位置,模糊距离, 阴影尺寸,阴影颜色,将外部阴影改为内部阴影
box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.5);
  • 伪元素 + transform + 媒体查询
<!-- ratio 2 -->
border-1px {
  position: relative;
}
.border-1px::after {
  display: block;
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
  transform-origin: center top;
  -webkit-transform-origin: center top;
  border-top: 1px solid #E6E6E6;
  width: 100%;
  height: 200%;
  transform: scaleY(0.5);
  -webkit-transform: scaleY(0.5);
}

3.9 移动端点击300ms延迟

300 毫秒延迟的主要原因是解决双击缩放(double tap to zoom)。

双击事件的顺序:touchstart -> mouseover -> mousemove -> mousedown -> mouseup -> click -> touchend

解决方案

  • meta禁用缩放或固定视口宽度
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
  • fastclick
    • FastClick的实现原理是在检测到touchend事件的时候,会通过DOM自定义事件立即出发模拟一个click事件,并把浏览器在300ms之后的click事件阻止掉。
    • 脚本相对较大
    • fastclick 在ios 上可能会影响元素自动触发,比如 直接click();会拦截第一次,需要执行两次click();才会触发;安卓上不需要;
  • tap事件
    • 在touchstart 时会记录一个值x1,y1,在touchend时会记录x2,y2,通过对比着几个值,判断用户是否是点击事件,而不是滑动事件,然后直接触发事件
    • tap事件存在"点透"的情况

3.10 css应用

3.10.1 绘制三角形

  1. border法:原理是基于盒模型,将一边的border宽度设为0,其他两边border的颜色设为透明.

<!-- 向上 -->
.triangle-1 {
  width: 0;
  height: 0;
  border-left: 50px solid transparent;
  border-right: 50px solid transparent;
  border-bottom: 100px solid red;
}
  1. svg:三点绘制一个三角形
<svg height="250" width="250">
    <polygon points="100,10 150,180 50,180" style="fill:lime;stroke:purple;stroke-width:1" />
</svg>

3.10.2 绘制扇形

  1. 圆切割法:圆任意不重合的半径和圆弧组成的就是一个扇形。绘制一个圆形的父元素,通过overflow切割矩形或三角形的子元素。如图所示:

<!-- html -->
<div class="circle">
    <div class="rect"></div>
</div>
<!-- css -->
.circle {
    position: relative;
    width: 80px;
    height: 80px;
    background: red;
    border-radius: 80px;
    overflow: hidden;
}
.rect {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 60px;
    height: 60px;
    background-color: yellow;
    display: block;
    transform: skew(30deg);
    transform-origin: left top;
}
  1. 两半圆法: 一个圆的两个半圆经中心旋转缓缓打开的部分也是一个扇形。如图所示:

<!-- html 绘制一个60度扇形 -->
<div class="circle">
    <div class="cir cir1"></div>
    <div class="cir cir2"></div>
</div>
.circle{
    position: relative;
    width: 200px;
    height: 200px;
    border-radius: 100px;
    background-color: yellow;
}
.cir{
    position: absolute;
    width: 200px;
    height: 200px;
    transform: rotate(30deg);
    clip: rect(0px, 100px, 200px, 0px);
    border-radius: 100px;
    background-color: red;
}
.cir2 {
  transform: rotate(-90deg);  
}
  1. svg:使用path绘制扇形,绘制两边和圆弧

<svg height="160" width="160">
    <path fill="red" d="M 20 20 H 120 A 100 100 0 0 1 70 106.602 Z" />
</svg> 

四、JS

4.1 基础类型和类型检测

  • 简单类型:Undefined, Null, boolean, number, string,Symbol。 存储结构-栈
  • 复杂类型:Object, Array, Date, Function, RegExp,Set, Map 存储结构-堆
  • 基本包装类型:Boolean, Number, String )存储结构-堆

类型检测

  • typeof: 区分不了引用类型(typeof null === Object)
  • instanceof: 区分不来普通类型
  • constructor: 容易篡改
  • Object.prototype.toString.call(): 完美(ES6的也能区分)
Object.prototype.toString.call('a') === [Object String]
Object.prototype.toString.call(Null) === [Object Null]

4.2 类型转换

4.2.1 undefined, null

  • undefined: undefined 的字面意思就是未定义的值,这个值的语义是,希望表示一个变量最原始的状态,而非人为操作的结果
  • null: null 的字面意思是空值,这个值的语义是,希望表示 一个对象被人为的重置为空对象,而非一个变量最原始的状态 规定 undefined == null 表示其行为相似性
  • void 0 === undefined

4.2.2 == 与 ===

  • === 严格相等
    • 类型相等
    • 普通类型值相等, 引用类型的地址相等
  • == 相同 (带类型转换)
    • undefined == null
    • 使用toPrimitive转换成原始值后比较

4.2.3 原始值转换函数toPrimitive(input,PreferredType?)

toPrimitive(input,PreferredType?) 函数将input转换成原始值,PreferredType是转化偏向

  • 如果input是原始值,则返回input
  • 如果input是对象(广义)
    • PreferredType是Number:
    1. 调用obj.valueOf(), 如果是原始值则返回,否则进入2
    2. 调用obj.toString(), 如果是原始值则返回,否则抛出 TypeError 异常
    • PreferredType是String:
    1. 调用obj.toString(), 如果是原始值则返回,否则进入2
    2. 调用obj.valueOf(), 如果是原始值则返回,否则抛出TypeError 异常
    • PreferredType未填: Date 类型的对象PreferredType默认 String,其它类型的值会被设置为 Number
  • (+, -, ==)等运算符,会使得 PreferredType 为 Number,但如有有一元为String,则 PreferredType 为 String
  • null和undefined是特殊的原始类型,他们不会转换成其他任何值,那么也不会调用该方法。且规范定义undefined == null

注意:

  • []转换成字符串为“”
  • []转换成boolean为true;
  • []转换成数字为0;

4.2.4 valueOf 和 toString

  1. toString: 将值转换为字符串形式并返回,不同类型的toString方法各有不同
类型 返回值
Number 返回数字的字符串形式。注意:10.toString()报错,10..toString()会把第一个.当作小数点
String 直接返回原字符串的值
Boolean 返回文本表示'true'或'false'
Object 返回[object 类型名]
Array 将数组元素转换为字符串,用逗号拼接并返回
Function 直接返回函数的文本声明
Date 返回日期的文本表示
RegExp 返回正则的字符串表示'/pattern/flag'
  1. valueOf: 返回类型的值
类型 返回值
Number 返回原始类型的数字值
String 返回原始类型的字符串值
Boolean 返回原始类型的Boolean
Object 返回对象本身
Array 方法继承于Object.prototype,返回原数组
Function 方法继承于Object.prototype,返回函数本身
Date 方法等同于getTime,返回时间戳
RegExp 方法继承于Object.prototype,返回值本身

4.3 原型和原型链

4.3.1 原型

每一个JavaScript对象(null除外)在创建的时候就会关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性和方法。

  • __proto__ 是对象实例才有的属性,指向对象的原型。
  • prototype 是构造函数才有的属性,该属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型
  • 实例的__proto__属性 和 构造函数的 prototype 都指向该对象原型
  • Function的prototype和__proto__属性都指向f () 匿名函数
  • Object作为构造函数时,他的prototype指向Object.prototype对象原型,作为实例时,他的__proto__指向匿名函数。我们可以认为Function实例和Object实例都是继承于该匿名函数。
  • 匿名函数作为“顶级构造函数”,他不需要prototype属性,即prototype=undefined,当作为对象时,他的对象原型是Object.prototype。
  • Object.prototype作为“顶级构造对象”,他的__proto__等于null,表示继承于一个空的对象。没有prototype属性。

送大家一张值得刻脑子里的图:

4.3.2 原型链

用 proto 链接的这条就是我们的原型链。原型链用于查找对象上的属性,当属性未从当前的对象上获取到的时候会从该原型链上查找,直到查到相应的属性。

原型链的顶层指向window,严格模式下不会指向window而是undefined


4.4 闭包

闭包是函数和声明该函数的词法环境的组合,这个环境包含了这个闭包创建时所能访问的所有局部变量

可能产生闭包的二种情况:

  • 函数作为返回值,
  • 函数作为参数传递

优点:

  • 可以读取函数内部的变量
  • 另一个就是让这些变量的值始终保持在内存中,不会在函数调用后被自动清除,同时这也算是个缺点。(在函数中return一个函数出来)
  • 可用于模拟私有变量和方法

缺点:

  • 消耗内存,影响网页性能
  • 可能会引起内存泄漏(不再用到的内存,但是没有及时释放,就叫做内存泄漏)

4.5 this

  • this指的是当前的执行环境
  • 一般时指向window, 严格模式下this绑定到undefined
  • 对象调用函数的情况下,指向调用者
  • 构造函数下,指向实例
  • bind call apply with函数可以绑定this的指向
    • call: call(this, arg1, arg2, ...)。会执行该函数
    • apply: apply(this, firstArg | argArray[]) 多个参数可使用参数数组,会执行该函数
    • bind: bind(this, firstArg | argArray[]) 返回一个函数,函数内的this指向传入的this
    • with: with (expression) { statement } // with'语句將某个对象添加到作用域链的顶部(window之下,没有切断作用域链,在expression中找不到定义的,仍会往window上寻找),在严格模式该标签禁止使用

4.6 new的原理

  1. 创建一个空对象,构造函数中的this指向这个空对象
  2. 这个新对象的__proto__设置为即构造函数的prototype
  3. 执行构造函数方法,属性和方法被添加到this引用的对象中
  4. 如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。

4.6.1 实现一个new函数

let _new = function(factory, ...rest) {
    let o = {
      "__proto__": factory.prototype
    }
    let res = factory.apply(o, rest)
    return typeof res === 'object' ? res : o;
}

4.7 class和继承

4.7.1 class

ES6之前实例化对象是通过构造函数实现的,ES6后可以通过关键字class创建类(可以认为是一种语法糖)

  • class中的constructor就是在实例化对象调用的构造函数,该构造函数可不写。
  • 实例对象必须使用new 关键字生成
  • class不可以当做函数执行
  • class不存在变量提升
  • class中定义的属性和方法都挂在原型上,所有的实例对象都有这些属性和方法。构造函数中定义的是实例的属性和方法。
  • class中可以通过static定义静态方法,静态变量需在类外声明(calss.staticName==staticValue)。静态属性和方法只可以通过class来调用,实例不可调用

4.7.2 继承

继承属性,方法,静态方法

  • ES6继承: 通过extends关键字
  • ES5继承: 通过修改原型链实现继承

本质:

  • ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
  • ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。

4.7.3 ES5-实现继承的几种方式

  1. 原型链继承:替换子类型的原型
function superClass(name) { this.name = name }
function subClass(sex) { this.sex = sex }

// 继承了superClass
subClass.prototype = new superClass()

缺点:

  • 包含引用类型的原型属性会被所有实例共享
  • 在创建子类型的实例时,不能向超类型的构造函数中传递参数

  1. 经典继承(借用构造函数):为了避免实例共享原型属性而带来的技术
function subClass(name, sex) {
    // 继承了superClass的属性
    superClass.call(this, name)
    this.sex = sex 
}

缺点:

  • 无法做到函数复用
  • 不能继承超类型在原型上定义的方法

  1. 组合继承:融合了原型链继承和经典继承,避免了他们的缺陷
function subClass(name, sex) {
    // 继承了superClass的属性
    superClass.call(this, name) // 第一次调用
    this.sex = sex 
}
// 继承方法
subClass.prototype = new superClass()  // 第二次调用
subClass.prototype.constructor = subClass

缺点:

  • 需要调用两次超类型的构造函数

  1. 原型继承:基于已有的对象创建新的对象
// 以一个对象作为实例的原型对象
function object(o) {
    function F(){}
    F.prototype = o
    return new F()
}

// 在ES5 规范化了该继承
Object.create()

缺点:

  • 包含引用类型的原型属性会被所有实例共享

  1. 寄生式继承:思路和工厂模式类似,即创建一个仅用于继承过程的函数
function createAnother(o) {
    // 创建新对象
    var clone = Object.create(o)
    // 增强这个对象
    o.say = function() {}
    return o
}

缺点:

  • 无法做到函数复用

  1. 寄生式组合继承:通过构造函数来继承属性,通过原型链的混成形式来继承方法
function inheritsPrototype(subClass, superClass) {
    var prototype = Object.create(superClass.prototype)
    prototype.constructor = subClass
    subClass.prototype = prototype
}

集寄生式继承和组合继承的优点于一身,是实现基于类型继承的最有效方式

答案来自《JavaScript高级程序设计》第六章


4.7.4 实现一个继承inherits函数

使用上面所述的寄生组合式继承

function inherits(subClass, superClass) { 
  // ... 省略参数校验
  subClass.prototype = Object.create(superClass.prototype, {
    constructor: { value: subClass, writable: true, configurable: true } 
  }); 
}


<!-- 使用 -->
function superClass(name) {
  this.name = name
}

superClass.prototype.say = function() {
  console.log(`hello my name is ${this.name}`)
} 

function subClass(name, sex) {
  superClass.call(this, name)
  this.sex = sex
}

_inherits(subClass, superClass)

4.8 ES6/7

4.8.1 let, const和var的区别

  • let和var都用于声明变量,而const必须初始化,且用于声明常量,这个常量指的是普通类型的值不变和复杂类型的内存地址不变。
  • var存在变量提升,而let,const存在“暂时性死区”,即在变量声明之前就访问变量的话,会直接提示ReferenceError,而不像var那样使用默认值undefined
  • let,const只有块级作用域,而var只有全局作用域和函数作用域概念

4.8.2 箭头函数

  • 更简化的代码语法
  • 不绑定this, 这也意味着使用call和apply是无法传递this,第一个参数就是需要传递的参数
  • arguments,即没有函数的参数arguments,但可以使用剩余参数...args替代
function foo(arg1,arg2) { 
    var f = (...args) => args[1]; 
    return f(arg1,arg2); 
} 
foo(1,2);  // 2
  • 不能使用new 关键字,因为箭头函数不是一个构造函数
  • 没有prototype属性
  • yield 关键字通常不能在箭头函数中使用(除非是嵌套在允许使用的函数内)。因此,箭头函数不能用作生成器。
  • 如果需要放回对象字面量,可以
var func = () => ({foo: 1})
// 或
var func = () => {return {foo:1}}

4.8.3 Promise

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。清晰的代码结构。避免出现回调地狱

Promise具有三种状态,分别是初始化的pending状态,resolve后的fulfilled状态,rejecte后的rejected状态。

常用方法:

then和catch实际上是注册异步操作成功或失败的回调函数

  • Promise.prototype.then() : 可以有两个参数,第一个是resolve的回调,第二个是reject的回调
  • Promise.prototype.catch(): 相当于.then(null, rejectFun)
  • Promise.prototype.finally() 【ES8】
  • Promise.all(): 只有所有都个变成fulfilled状态,包装实例才会变成fulfilled状态;如果有一个实例变成rejected状态,包装实例就会变成rejected状态。
  • Promise.allSettled(): 等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由【ES2020】引入。
  • Promise.any(): 只要实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。该方法目前是一个第三阶段的提案 。
  • Promise.race(): 异步操作竞赛,只返回最快的一个
  • Promise.resolve()
  • Promise.reject()
  • Promise.try(): 同步操作也可以像异步一样执行

4.9 Number

4.9.1 最大安全数

js使用8位浮动数存储数字,范围是-2^63 ~ 2^63 - 1。但是在超过一定的值后执行加减法就不正确了,这个值叫做最大安全值。

最大安全值为2^53-1 最大安全值为-2^53+1

js中Number的存储结构是:

  • 1 位符号位
  • 11 位指数位
  • 52 位尾数位

浮点数在保存数字的时候做了规格化处理,对于二进制来说, 小数点前保留一位, 规格化后始终是 1.***, 节省了 1 bit,这个 1 并不需要保存。所以是52+1=53。

可以使用第三方库如bignum, bigInt等处理


4.9.2 浮点数计算精确度问题

因为浮点数在计算机内是以二进制存储和计算的,所以在浮点数上计算会存在精度问题如:

0.1 + 0.2 = 0.30000000000000004
1.0 - 0.9 = 0.09999999999999998
  • 使用toFixed进行“四舍五入”
  • 将数扩大至整数,在进行计算
  • 使用例如number-precision等第三库进行计算

值得注意的是

toFixed并不是正整的四舍五入。而是使用更公平的银行家舍入法。具体规则是:

四舍六入五考虑,五后非零就进一
五后为零看奇偶,五前为偶应舍去,五前为奇要进一


4.10 Array

4.10.1 数组的常用方法

改变原数组:

  • pop(), push(),shift(),unshift()
  • reverse(),sort()
  • splice(index, howMany, newAddItem1, newAddItem2, ...) .. 删除项
  • forEach

其他:

  • map, filter, every, some
  • find, findIndex
  • reduce
  • join

4.10.2 map和forEach的区别

  • 都支持三个参数,参数分别为item(当前每一项),index(索引值),arr(原数组)
  • forEach允许callback更改原始数组的元素,无返回值。map则返回新的数组,表现上可认为是浅拷贝后进行操作。 forEach,filter,every,some会跳过空位,map会跳过空位,但是会保留这个值。

4.11 四种for循环的区别

  1. 普通for循环

  2. forEach:普通for循环简写,但不能中断循环

  3. for in

    • 索引为字符串
    • 无顺序(通常用于对象或json中)
    • 可扩展属性也会遍历
    • 为循环”enumerable“对象而设计
  4. for of

    • 不支持对象的遍历
    • 具有 obj[Symbol.iterator] 的数据使用,如类数组,字符串,set和map

4.12 js模块化方案

4.12.1 AMD和CMD

  • AMD 是 RequireJS 在推广过程中对模块定义的规范化而产出的。
  • CMD 是 SeaJS 在推广过程中对模块定义的规范化而产出的。
  • 对于依赖的模块,AMD可以提前执行,也可以延迟执行,CMD则是延迟执行。
  • AMD推崇依赖前置,CMD则推崇就近依赖。(可以说,CMD就是个"懒人")
  • AMD支持全局require、局部require,但是CMD则不支持全局require,所以CMD没有全局API而AMD有。
// AMD  依赖前置
define(['./zty', './ty'], function(zty, ty) {
  zty.sayName()
  ty.sayName()
})

// CMD
define(function(require, exprots, module) {
  var zty = require('./zty')
  zty.sayName()
  // 需要的时候才去require 
  var ty = require('./ty')
  ty.sayName()
})

4.12.1 CommonJs

CommonJS是为服务器提供的一种模块形式的优化,CommonJS模块建议指定一个简单的用于声明模块服务器端的API,并且不像AMD那样尝试去广泛的操心诸如io,文件系统,约定以及更多的一揽子问题。它有以下特点:

  • 主要运用在服务端js,如node
  • 全局对象:global
  • 一个文件就是一个模块,拥有单独的作用域,所有代码都运行在模块作用域,不会污染全局作用域;模块可以多次加载,但只会在第一次加载的时候运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果;(你可以暴露一个时间戳来测试)
  • 模块的加载顺序,按照代码的出现顺序,
  • 同步加载
  • 通过 require 来加载模块:require基本功能:读取并执行一个JS文件,然后返回该模块的module.exports对象,如果没有发现指定模块会报错
  • 通过 exports 和 module.exports 来暴露模块中的内容

那么 exports 和 module.exports有什么区别呢?

  • 模块内的exports:为了方便,node为每个模块提供一个exports变量,其指向module.exports,相当于在模块头部加了这句话:var exports = module.exports,在对外输出时,可以给module.exports对象添加方法

  • module.exports 方法还可以单独返回一个数据类型(String、Number、Object...),而 exports 只能返回一个 Object 对象。所有的 exports 对象最终都是通过 module.exports 传递执行,因此可以更确切地说,exports 是给 module.exports 添加属性和方法。


4.12.2 ES6 Module

ES6模块化规范是ES6在语言标准的层面上,实现了模块功能,而且实现得相当简单,其设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。并且ES6的模块自动采用严格模式,无论我们是否添加了在模块头部加上 "use strict" 。

ES6 Module常见的有四个命令,分别是

  • 导出 export
  • 默认导出 export default
  • 导入 import
  • 重命名 as

export:

  • export导出应该是一种接口或是理解为一种定义,而不应该是值
  • export导出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
  • export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下面的import命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷。

export default:

本质上,export default就是在Module上输出一个叫做default的变量或方法,和export完全不同,所以它后面不能跟变量声明语句,但表达式,function,class除外。

import:

  • import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。
  • 对于export导出的接口应该使用 import { interface1 } 的方式
  • 对于export default导出的变量应该使用import interface1 的方式
  • import命令具有提升效果,会提升到整个模块的头部,首先执行。
  • 如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次。

4.12.3 CommonJS和ES6模块的区别

  • CommonJS模块是运行时加载,ES6 Modules是编译时输出接口
  • CommonJS输出是值的拷贝;ES6 Modules输出的是值的引用,被输出模块的内部的改变会影响引用的改变
  • CommonJs导入的模块路径可以是一个表达式,因为它使用的是require()方法;而ES6 - Modules只能是字符串
  • CommonJS this指向当前模块,ES6 Modules this指向undefined
  • 且ES6 Modules中没有这些顶层变量:arguments、require、module、exports、__filename、__dirname

4.12.4 UMD

UMD是为了让同一个代码模块在使用 CommonJs、CMD 甚至是 AMD 的项目中运行。为了实现兼容,所以有点“丑陋”。

(function (global, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        // Node, CommonJS-like
        module.exports = factory(require('jquery'));
    } else {
        global.returnExports = factory(global.jQuery);
    }
}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
   // methods
      ...
   return {...}
}))

4.13 浅拷贝和深拷贝

简单来说,就是在拷贝复杂类型时,浅拷贝复制的是引用地址,深拷贝是拷贝一份新的属性。

如何实现深拷贝:

  1. 通过JSON拷贝
JSON.parse(JSON.stringify())

虽然简单,但是有一些缺陷:

  • 不能拷贝原型链上的属性
  • 对象的属性值是函数时,无法拷贝
  • 不能正确的处理 Date, 得到了Date.toString()
  • 不能正确的处理 RegExp 类型的数据, 得到了new Object()
  • 会忽略 symbol, undefined, Symbol

  1. Object.assign({}, target) 或 {...target}

优点:

  • 可以解决JSON不能处理或是无法拷贝的问题 缺点是:
  • 只能深拷贝最顶上的一层
  • 不能拷贝原型链上的属性

  1. 实现一个递归的深拷贝函数

优点:

  • 可以解决JSON不能处理或是无法拷贝的问题
  • 可以获取原型链上的属性 缺点是:
  • 需要递归和逻辑较为复杂

下面是一个深拷贝的实现函数

function deepClone(obj, hash = new WeakMap()) {
  // 递归拷贝
  if (obj instanceof RegExp) return new RegExp(obj);
  if (obj instanceof Date) return new Date(obj);
  if (obj === null || typeof obj !== "object") return obj; // 简单类型
  
  if (hash.has(obj)) return hash.get(obj); // 循环引用
  
  let instance = new obj.constructor();
  hash.set(obj, instance);
  
  for (let key in obj) { // no-prototype-builtins
    if (obj.hasOwnProperty(key)) {
      instance[key] = dc(obj[key], hash)
    }
  }
  return instance
}

4.14 JS应用

4.14.1 实现Promise.all 和 Promise.race 的主要逻辑

Promise.prototype.all = function(arr) {
  let results = []; // 结果数组
  return new Promise((resolve, reject) => {
    let count = arr.length   
    for (let i = 0; i < arr.length; i++) {
      //arr[i]的promise返回值可能是普通值
      Promise.resolve(arr[i]).then((data) => {
        results[i] = data
        --count
        if (count <= 0) {
          resolve(results)
          return
        }
      }).catch(err => {
        reject(err)
        return
      })
    }
  })
}
Promise.prototype.race = function(promises) {
  return new Promise(function(resolve, reject) {
    for (var i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(data => {
        resolve(data)
      }).catch(err => {
        return reject(err)
      })
    }
  })
}

4.14.2 防抖和节流

防抖和节流是针对响应跟不上触发频率这类问题的两种解决方案。

  • 函数防抖: debounce
    • 定义:多次触发事件后,事件处理函数只执行一次,并且是在触发操作结束时执行。
  • 函数节流: throttle
    • 定义:触发函数事件后,短时间间隔内无法连续调用,只有上一次函数执行后,过了规定的时间间隔,才能进行下一次的函数调用。。
  • 简单实现:
function debounce(fn, delay) {
  let timer;
  return function(...rest) {
    timer && clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, rest), delay)
  }
}
function throttle(fn, delay) {
  let start
  return function(...rest) {
    let now =  Date.now()
    !start && (start = now)

    if (now - start >= delay) {
      fn.apply(this, rest)
      start = now
    }
  }
}

function throttle2(fn, delay){
  let timer
  return function(...rest){
    if(!timer){
      timer = setTimeout(() => {
        fn.apply(this, rest);
        timer = null;
      }, delay)
    }
  }
}

4.14.3 原生ajax

  1. 创建xhr实例
  2. open链接(请求方法,url, 同步异步)
  3. 设置请求参数
  4. 监听onreadystatechange事件
  5. 发送
var xhr=new XMLHttpRequest();
xhr.open('POST',url,false);
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xhr.onreadystatechange=function(){
    // readyState == 4说明请求已完成
    if(xhr.readyState==4){
        if(xhr.status==200 || xhr.status==304){
            console.log(xhr.responseText);
            fn.call(xhr.responseText);
        }
    }
}
xhr.send();

4.14.4 柯里化

   柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

function curry(fn, ...args) {
  return args.length < fn.length ? (...arguments) => curry(fn, ...args, ...arguments) : fn(...args)
}

4.14.5 扁平化处理

  1. 数组的扁平化处理, 将多层的数组转成一维数组,例如将
[1, [2], [[3]]] => [1,2,3]
  • 使用Array.prototype.flat(depth)。 depth不能小于数组的深度
arr.flat(3)
  • 遍历
function flat1(arr) {
  while(arr.some(item=>Array.isArray(item))) {
    arr = [].concat(...arr);
  }
  return arr;
}
  • 递归实现
function flat2(arr1) {
  return arr1.reduce((acc, val) => Array.isArray(val) ? acc.concat(flat2(val)) : acc.concat(val), []);
}
  • 非递归
function stackFlatten(input) {
  const stack = [...input];
  const res = [];
  while (stack.length) {
    const next = stack.pop();
    if (Array.isArray(next)) {
      stack.push(...next);
    } else {
      res.push(next);
    }
  }
  return res.reverse();
}
  1. 对象的扁平化,只包含普通类型,数组和对象。例如
{
    num: 1,
    arr: [1, 2]
    obj: {
        name: 'name'
    }
}

// 偏平化后=>
{
    num: 1,
    arr.[0]: 1,
    arr.[1]: 2,
    obj.name: 'name'
}
function _typeof(object) {
  let _toString = Object.prototype.toString
  return _toString.call(object)
}

function flatten(obj) {
  if (!obj) return
  let res = {}

  function flat(key, value) {
    if (_typeof(value) === "[object Array]") {
      if (value.length === 0) {
        res[key] = []
        return
      }
      for(let k in value) {
        flat(key ? `${key}.[${k}]` : k, value[k])
      }
    } else if (_typeof(value) === "[object Object]") {
      if (Object.keys(value).length === 0) {
        res[key] = {}
        return
      }
      for(let k in value) {
        flat(key ? `${key}.${k}` : k, value[k])
      }
    } else {
      res[key] = value
    }
  }

  flat('', obj)
  return res
}

4.14.6 判断两个对象相同

判断两个对象相同,两个对象从表面上看没有任何不同。

function looseEqual(a, b) {
  if (a === b) return true;
  const isObjectA = isObject(a);
  const isObjectB = isObject(b);
  if (isObjectA && isObjectB) {
    try {
      const isArrayA = Array.isArray(a);
      const isArrayB = Array.isArray(b);
      if (isArrayA && isArrayB) {
        return (
          a.length === b.length &&
          a.every((e, i) => {
            return looseEqual(e, b[i]);
          })
        );
      } else if (a instanceof Date && b instanceof Date) {
        return a.getTime() === b.getTime();
      } else if (!isArrayA && !isArrayB) {
        const keysA = Object.keys(a);
        const keysB = Object.keys(b);
        return (
          keysA.length === keysB.length &&
          keysA.every(key => {
            return looseEqual(a[key], b[key]);
          })
        );
      } else {
        /* istanbul ignore next */
        return false;
      }
    } catch (e) {
      /* istanbul ignore next */
      return false;
    }
  } else if (!isObjectA && !isObjectB) {
    return String(a) === String(b);
  } else {
    return false;
  }
}

答案来自vue源码 src/shared/util.js



结束语

   我是千千万万个普通的前端之一,我是第一次写掘金文,如果有错误,欢迎大家指正,一起成长。在下章中,会包含剩余的部分,同时也会分享整篇的有道云笔记链接给大家,感谢阅读!

   做一个爱分享,爱学习的前端,我是安迪five,oh yeah!