前端面试知识点

本专题按照以下几个方便进行整理:

  • HTTP && 浏览器
  • HTML && CSS
  • JS、TS、ES6
  • Vue
  • React
  • 构建工具 && 工程化
  • 性能优化

适合初次全面复习的同学,查缺补漏,知识面比较全,复习完成后,再按照本人整理的面试高频题配合复习,使得找工作事半功倍,一定要理解,不要死记硬背,对于一些概念性的和原理的内容要深入理解。

“你从头读,尽量往下读,直到你一窍不通时,再从头开始,这样坚持往下读,直到你完全读懂为止。”

HTTP && 浏览器

HTTP协议与数据请求

1. HTTP工作原理

HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。客户端向服务器发送一个请求报文,服务器以一个状态行作为响应。

2.HTTP请求/响应的步骤
  • 1.客户端连接到Web服务器
  • 2.发送HTTP请求
  • 3.服务器接受请求并返回HTTP响应
  • 4.释放连接TCP连接
  • 5.客户端(浏览器)解析HTML内容
3.HTTP报文的组成成分

请求报文{ 请求行、请求头、空行、请求体 } 请求行:{http方法、页面地址、http协议、http版本} 响应报文{ 状态行、响应头、空行、响应体 }

Request Header:

  1. GET /sample.Jsp HTTP/1.1 //请求行
  2. Host: www.uuid.online/ //请求的目标域名和端口号
  3. Origin: http://localhost:8081/ //请求的来源域名和端口号 (跨域请求时,浏览器会自动带上这个头信息)
  4. Referer: https://localhost:8081/link?query=xxxxx //请求资源的完整URI
  5. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36 //浏览器信息
  6. Cookie: BAIDUID=FA89F036:FG=1; BD_HOME=1; sugstore=0 //当前域名下的Cookie
  7. Accept: text/html,image/apng //代表客户端希望接受的数据类型是html或者是png图片类型
  8. Accept-Encoding: gzip, deflate //代表客户端能支持gzip和deflate格式的压缩
  9. Accept-Language: zh-CN,zh;q=0.9 //代表客户端可以支持语言zh-CN或者zh(值得一提的是q(0~1)是优先级权重的意思,不写默认为1,这里zh-CN是1,zh是0.9)
  10. Connection: keep-alive //告诉服务器,客户端需要的tcp连接是一个长连接

Response Header:

  1. HTTP/1.1 200 OK // 响应状态行
  2. Date: Mon, 30 Jul 2018 02:50:55 GMT //服务端发送资源时的服务器时间
  3. Expires: Wed, 31 Dec 1969 23:59:59 GMT //比较过时的一种验证缓存的方式,与浏览器(客户端)的时间比较,超过这个时间就不用缓存(不和服务器进行验证),适合版本比较稳定的网页
  4. Cache-Control: no-cache // 现在最多使用的控制缓存的方式,会和服务器进行缓存验证,具体见博文”Cache-Control“
  5. etag: "fb8ba2f80b1d324bb997cbe188f28187-ssl-df" // 一般是Nginx静态服务器发来的静态文件签名,浏览在没有“Disabled cache”情况下,接收到etag后,同一个url第二次请求就会自动带上“If-None-Match”
  6. Last-Modified: Fri, 27 Jul 2018 11:04:55 GMT //是服务器发来的当前资源最后一次修改的时间,下次请求时,如果服务器上当前资源的修改时间大于这个时间,就返回新的资源内容
  7. Content-Type: text/html; charset=utf-8 //如果返回是流式的数据,我们就必须告诉浏览器这个头,不然浏览器会下载这个页面,同时告诉浏览器是utf8编码,否则可能出现乱码
  8. Content-Encoding: gzip //告诉客户端,应该采用gzip对资源进行解码
  9. Connection: keep-alive //告诉客户端服务器的tcp连接也是一个长连接
4.HTTP 的 5 种方法
  • GET---获取资源
  • POST---传输资源
  • PUT---更新资源
  • DELETE---删除资源
  • HEAD---获取报文首部

HTTP 和 HTTPS

1.http 和 https 的基本概念

http: 是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从 WWW 服务器传输超文本到本地浏览器的超文本传输协议。
https:是以安全为目标的 HTTP 通道,即 HTTP 下加入 SSL 层进行加密。

https 协议的主要作用:建立一个信息安全通道,来确保数据的传输,确保网站的真实性。

2.http 和 https 的区别?
  • http 是超文本传输协议,信息是明文传输,https 则是具有安全性的 ssl 加密传输协议。
  • Https 协议需要 ca 证书,费用较高。
  • 使用不同的链接方式,端口也不同,一般,http 协议的端口为 80,https 的端口为 443。
  • http 的连接很简单,是无状态的。
3.https 协议的工作原理

客户端在使用 HTTPS 方式与 Web 服务器通信时有以下几个步骤,如图所示。

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

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

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

从输入URL到页面加载的全过程

从输入URL到页面加载的主干流程

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

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

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

  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作用域其实就是这样的执行流机制实现的。

浏览器渲染机制、重绘(Repaint)、重排/回流(Reflow):

  1. Reflow(回流):当DOM的变化影响了元素的几何信息(DOM对象的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。

触发原因:

1. 页面初始化的时候;
2. 操作DOM元素时;
3. 某些元素的尺寸改变了;
4. 如果 CSS 的属性发生变化了。
复制代码

2. Repaint(重绘):当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。

触发原因:改变元素的 color、background、box-shadow 等属性

Reflow要比Repaint更花费时间,也就更影响性能。所以要尽量避免过多的Reflow。

  1. 减少 reflow/repaint

    1. 样式集中修改,不要一条一条地修改 DOM 的样式。
    2. 不要把 DOM 结点的属性值放在循环里当成循环里的变量。
    3. 为动画的 HTML 元件使用 fixedabsoultposition,那么修改他们的 CSS 是不会 reflow 的。
    4. 不使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。
    5. 尽量只修改position:absolutefixed元素,对其他元素影响不大
    6. 动画开始GPU加速,translate使用3D变化

TCP三次握手

  1. 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

  2. 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

  3. 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。
复制代码

TCP 四次挥手

  1. 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。

2)服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。

3)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最 后的数据)。

4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。

6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

利用Socket建立网络连接的步骤

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

  1、服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

  2、客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。

  为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

  3、连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。

而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

TCP和UDP的区别

  1. TCP是面向链接的,而UDP是面向无连接的。

  2. TCP仅支持单播传输,UDP 提供了单播,多播,广播的功能。

  3. TCP的三次握手保证了连接的可靠性; UDP是无连接的、不可靠的一种数据传输协议,首先不可靠性体现在无连接上,通信都不需要建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收。

  4. UDP的头部开销比TCP的更小,数据传输速率更高实时性更好

GET与POST的区别

1.浏览器回退表现不同 GET在浏览器回退时时无害的,而POST会再次提交请求

2.浏览器对请求地址的处理不同 GET请求地址会被浏览器主动缓存,而POST不会,除非手动设置

3.浏览器对响应的处理不同GET请求参数会被完整的保留在浏览器历史记录里,而POST中的参数不会被保留

4.参数大小不同. GET请求在URL中传送的参数是有长度的限制,而POST没有限制

5.安全性不同. GET参数通过URL传递,会暴露,不安全;POST放在Requset Body中,相对更安全

6.针对数据操作的类型不同.GET对数据进行查询,POST主要对数据进行增删改!简单说,GET是只读,POST是写。

HTTP 请求跨域问题

  1. 跨域的原理

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

  2. 解决方案

    最初做项目的时候,使用的是jsonp,但存在一些问题,使用get请求不安全,携带数据较小,后来也用过iframe,但只有主域相同才行,也是存在些问题,后来通过了解和学习发现使用代理和proxy代理配合起来使用比较方便,就引导后台按这种方式做下服务器配置,在开发中使用proxy,在服务器上使用nginx代理,这样开发过程中彼此都方便,效率也高;现在h5新特性还有 windows.postMessage()

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

      步骤:

      1. 去创建一个script标签
      2. script的src属性设置接口地址
      3. 接口参数,必须要带一个自定义函数名,要不然后台无法返回数据
      4. 通过定义函数名去接受返回的数据
      //动态创建 script
      var script = document.createElement('script');
      

// 设置回调函数
function getData(data) {
console.log(data);
}

//设置 script 的 src 属性,并设置请求地址
script.src = ‘http://localhost:3000/?callback=getData’;

// 让 script 生效
document.body.appendChild(script);
复制代码

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

  • document.domain 基础域名相同 子域名不同

  • window.name 利用在一个浏览器窗口内,载入所有的域名都是共享一个window.name

  • CORS CORS(Cross-origin resource sharing)跨域资源共享 服务器设置对CORS的支持原理:服务器设置Access-Control-Allow-Origin HTTP响应头之后,浏览器将会允许跨域请求

  • proxy代理

  • window.postMessage() 利用h5新特性window.postMessage()

  • Websocket

  • 客户端与服务端长连接的几种方式

    1. ajax 轮询 实现原理:ajax 轮询指客户端每间隔一段时间向服务端发起请求,保持数据的同步。

      优点:可实现基础(指间隔时间较短)的数据更新。

      缺点:这种方法也只是尽量的模拟即时传输,但并非真正意义上的即时通讯,很有可能出现客户端请求时,服务端数据并未更新。或者服务端数据已更新,但客户端未发起请求。导致多次请求资源浪费,效率低下。【数据更新不及时,效率低下

    2. long poll 长轮询

      实现原理: long poll 指的是客户端发送请求之后,如果没有数据返回,服务端会将请求挂起放入队列(不断开连接)处理其他请求,直到有数据返回给客户端。然后客户端再次发起请求,以此轮询。在 HTTP1.0 中客户端可以设置请求头 Connection:keep-alive,服务端收到该请求头之后知道这是一个长连接,在响应报文头中也添加 Connection:keep-alive。客户端收到之后表示长连接建立完成,可以继续发送其他的请求。在 HTTP1.1 中默认使用了 Connection:keep-alive 长连接。

      优点:减少客户端的请求,降低无效的网络传输,保证每次请求都有数据返回,不会一直占用线程。

      缺点:无法处理高并发,当客户端请求量大,请求频繁时对服务器的处理能力要求较高。服务器一直保持连接会消耗资源,需要同时维护多个线程,服务器所能承载的 TCP 连接数是有上限的,这种轮询很容易把连接数顶满。每次通讯都需要客户端发起,服务端不能主动推送。【无法处理高并发,消耗服务器资源严重,服务端不能主动推送

    3. iframe 长连接

      实现原理:
      在网页上嵌入一个 iframe 标签,该标签的 src 属性指向一个长连接请求。这样服务端就可以源源不断地给客户端传输信息。保障信息实时更新。

      优点:消息及时传输。

      缺点消耗服务器资源

    4. WebSocket

      实现原理: Websocket 实现了客户端与服务端的双向通信,只需要连接一次,就可以相互传输数据,很适合实时通讯、数据实时更新等场景。

      Websocket 协议与 HTTP 协议没有关系,它是一个建立在 TCP 协议上的全新协议,为了兼容 HTTP 握手规范,在握手阶段依然使用 HTTP 协议,握手完成之后,数据通过 TCP 通道进行传输。

      Websoket 数据传输是通过 frame 形式,一个消息可以分成几个片段传输。这样大数据可以分成一些小片段进行传输,不用考虑由于数据量大导致标志位不够的情况。也可以边生成数据边传递消息,提高传输效率。

      优点: 双向通信。客户端和服务端双方都可以主动发起通讯。 没有同源限制。客户端可以与任意服务端通信,不存在跨域问题。 数据量轻。第一次连接时需要携带请求头,后面数据通信都不需要带请求头,减少了请求头的负荷。 传输效率高。因为只需要一次连接,所以数据传输效率高。

      缺点: 长连接需要后端处理业务的代码更稳定,推送消息相对复杂;
      长连接受网络限制比较大,需要处理好重连。
      兼容性,WebSocket 只支持 IE10 及其以上版本。
      服务器长期维护长连接需要一定的成本,各个浏览器支持程度不一;
      成熟的 HTTP 生态下有大量的组件可以复用,WebSocket 则没有,遇到异常问题难以快速定位快速解决。【需要后端代码稳定,受网络限制大,兼容性差,维护成本高,生态圈小】

    HTTP状态码及常见状态码

    HTTP状态码
    • 1xx:指示信息类,表示请求已接受,继续处理
    • 2xx:指示成功类,表示请求已成功接受
    • 3xx:指示重定向,表示要完成请求必须进行更近一步的操作
    • 4xx:指示客户端错误,请求有语法错误或请求无法实现
    • 5xx:指示服务器错误,服务器未能实现合法的请求
    常见状态码
    • 200 OK:客户端请求成功
    • 301 Moved Permanently:所请求的页面已经永久重定向至新的URL
    • 302 Found:所请求的页面已经临时重定向至新的URL
    • 304 Not Modified 未修改。
    • 403 Forbidden:对请求页面的访问被禁止
    • 404 Not Found:请求资源不存在
    • 500 Internal Server Error:服务器发生不可预期的错误原来缓冲的文档还可以继续使用
    • 503 Server Unavailable:请求未完成,服务器临时过载或宕机,一段时间后可恢复正常

    Cookie、sessionStorage、localStorage 的区别

    相同点

    • 存储在客户端

    不同点

    • cookie数据大小不能超过4k;sessionStorage和localStorage的存储比cookie大得多,可以达到5M+
    • cookie设置的过期时间之前一直有效;localStorage永久存储,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage数据在当前浏览器窗口关闭后自动删除
    • cookie的数据会自动的传递到服务器;sessionStorage和localStorage数据保存在本地

    HTML && CSS

    DOCTYPE及作用

    概念

    DTD(document type definition,文档类型定义)声明于文档最前面,用来定义XML或(X)HTML的文件类型。浏览器会使用它来判断文档类型,并根据这个判断决定用什么引擎来解析和渲染他们。

    解析引擎的两种模式

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

    DOCTYPE的作用

    DOCTYPE是用来声明文档类型和DTD规范的,其作用一是文件的合法性验证。如果文件代码不合法,那么浏览器解析时会出一些错误。二是浏览器会使用它来判断文档类型,并根据这个判断决定用什么引擎来解析和渲染他们。

    HTML5 新特性、语义化

    1. 概念

      HTML5的语义化指的是合理正确的使用语义化的标签来创建页面结构。【正确的标签做正确的事】

    2. 语义化标签

      header nav main article section aside footer

    3. 语义化的优点:

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

    HTML5新特性有哪些

    • 语义化标签
    • 音视频处理API(audio,video)
    • canvas / webGL
    • 拖拽释放(Drag and drop) API
    • history API
    • requestAnimationFrame
    • 地理位置(Geolocation)API
    • webSocket
    • web存储 localStorage、SessionStorage
    • 表单控件,calendar、date、time、email、url、search

    渐进增强与优雅降级的理解及区别

    渐进增强(Progressive Enhancement): 一开始就针对低版本浏览器进行构建页面,完成基本的功能,然后再针对高级浏览器进行效果、交互、追加功能达到更好的体验。

    优雅降级(Graceful Degradation): 一开始就构建站点的完整功能,然后针对浏览器测试和修复。比如一开始使用 CSS3 的特性构建了一个应用,然后逐步针对各大浏览器进行 hack 使其可以在低版本浏览器上正常浏览。 两者区别 1、广义: 其实要定义一个基准线,在此之上的增强叫做渐进增强,在此之下的兼容叫优雅降级 2、狭义: 渐进增强一般说的是使用CSS3技术,在不影响老浏览器的正常显示与使用情形下来增强体验,而优雅降级则是体现html标签的语义,以便在js/css的加载失败/被禁用时,也不影响用户的相应功能。

    /* 例子 */
    .transition { /*渐进增强写法*/
      -webkit-transition: all .5s;
         -moz-transition: all .5s;
           -o-transition: all .5s;
              transition: all .5s;
    }
    .transition { /*优雅降级写法*/
              transition: all .5s;
           -o-transition: all .5s;
         -moz-transition: all .5s;
      -webkit-transition: all .5s;
    }
    

    复制代码

    常见的兼容性问题

    1. 不同浏览器的标签默认的margin和padding不一样。*{margin:0;padding:0;}

    2. IE6双边距bug:块属性标签float后,又有横行的margin情况下,在IE6显示margin比设置的大。hack:display:inline;将其转化为行内属性。

    3. 设置较小高度标签(一般小于10px),在IE6,IE7中高度超出自己设置高度。hack:给超出高度的标签设置overflow:hidden;或者设置行高line-height 小于你设置的高度。

    4. Chrome 中文界面下默认会将小于 12px 的文本强制按照 12px 显示,可通过加入 CSS 属性 -webkit-text-size-adjust: none; 解决。

    5. 超链接访问过后hover样式就不出现了,被点击访问过的超链接样式不再具有hover和active了。解决方法是改变CSS属性的排列顺序:L-V-H-A ( love hate ): a:link {} a:visited {} a:hover {} a:active {}

    CSS 选择器及优先级

    选择器

    • id选择器(#myid)
    • 类选择器(.myclass)
    • 标签选择器(div, h1,p)
    • 相邻选择器(h1 + p)
    • 子选择器(ul > li)
    • 后代选择器(li a)
    • 通配符选择器(*)
    • 属性选择器(a[rel="external"])
    • 伪类选择器(a:hover, li:nth-child)

    优先级

    带!important 标记的样式属性优先级最高; 样式表的来源不同时,优先级顺序为:内联样式> 内部样式 > 外部样式 > 浏览器用户 自定义样式 > 浏览器默认样式
    样式表的来源相同时:!important > 行内样式>ID选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性

    CSS 盒子模型

    CSS 盒模型本质上是一个盒子,它包括:边距,边框,填充和实际内容。CSS 中的盒子模型包括 IE 盒子模型和标准的 W3C 盒子模型。在标准的盒子模型中,width 指 content 部分的宽度,在 IE 盒子模型中,width 表示 content+padding+border 这三个部分的宽度,故在计算盒子的宽度时存在差异:

    标准盒模型: 一个块的总宽度=width+margin(左右)+padding(左右)+border(左右)

    怪异盒模型: 一个块的总宽度=width+margin(左右)(既 width 已经包含了 padding 和 border 值)

    BFC(块级格式上下文)

    BFC的概念

    BFCBlock Formatting Context 的缩写,即块级格式化上下文。BFC是CSS布局的一个概念,是一个独立的渲染区域,规定了内部box如何布局, 并且这个区域的子元素不会影响到外面的元素,其中比较重要的布局规则有内部 box 垂直放置,计算 BFC 的高度的时候,浮动元素也参与计算。

    BFC的原理布局规则

    • 内部的Box会在垂直方向,一个接一个地放置
    • Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠
    • 每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反
    • BFC的区域不会与float box重叠
    • BFC是一个独立容器,容器里面的子元素不会影响到外面的元素
    • 计算BFC的高度时,浮动元素也参与计算高度
    • 元素的类型和display属性,决定了这个Box的类型。不同类型的Box会参与不同的Formatting Context

    如何创建BFC?

    • 根元素,即HTML元素
    • float的值不为none
    • position为absolute或fixed
    • display的值为inline-block、table-cell、table-caption
    • overflow的值不为visible

    BFC的使用场景

    • 去除边距重叠现象
    • 清除浮动(让父元素的高度包含子浮动元素)
    • 避免某元素被浮动元素覆盖
    • 避免多列布局由于宽度计算四舍五入而自动换行

    CSS3新特性

    • 过渡
    /*所有属性从原始值到制定值的一个过渡,运动曲线ease,运动时间0.5秒*/ 
    transition:all,.5s
    复制代码
    • 动画
    //animation:动画名称,一个周期花费时间,运动曲线(默认ease),动画延迟(默认0),播放次数(默认1),是否反向播放动画(默认normal),是否暂停动画(默认running)
    /*执行一次logo2-line动画,运动时间2秒,运动曲线为 linear*/
    animation: logo2-line 2s linear;
    复制代码
    • 形状转换
    //transform:适用于2D或3D转换的元素
    //transform-origin:转换元素的位置(围绕那个点进行转换)。默认(x,y,z):(50%,50%,0)
    transform:translate(30px,30px);
    transform:rotate(30deg);
    transform:scale(.8);
    复制代码
    • 选择器:nth-of-type()

    • 阴影 文字阴影: text-shadow: 2px 2px 2px #000;(水平阴影,垂直阴影,模糊距离,阴影颜色) 盒子阴影: box-shadow: 10px 10px 5px #999

    • 边框 border-image: url(border.png);

    • 背景

    • 文字

    • 渐变

    • Filter(滤镜)

    • 弹性布局、栅格布局、多列布局

    • 媒体查询

    position 属性的值有哪些及其区别

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

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

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

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

    默认定位 Static: 默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声 明)。 inherit: 规定应该从父元素继承 position 属性的值。

    box-sizing属性

    box-sizing 规定两个并排的带边框的框,语法为 box-sizing:content-box/border-box/inherit

    content-box:宽度和高度分别应用到元素的内容框,在宽度和高度之外绘制元素的内边距和边框。【标准盒子模型】

    border-box:为元素设定的宽度和高度决定了元素的边框盒。【IE 盒子模型】

    inherit:继承父元素的 box-sizing 值。

    让一个元素水平垂直居中,到底有多少种方案?

    • 水平居中

      • 对于 行内元素 : text-align: center;

      • 对于确定宽度的块级元素:

        (1)width和margin实现。margin: 0 auto;

        (2)绝对定位和margin-left: -width/2, 前提是父元素position: relative

      • 对于宽度未知的块级元素

        (1)table标签配合margin左右auto实现水平居中。使用table标签(或直接将块级元素设值为 display:table),再通过给该标签添加左右margin为auto。

        (2)inline-block实现水平居中方法。display:inline-block和text-align:center实现水平居中。

        (3)绝对定位+transform,translateX可以移动本身元素的50%。

        (4)flex布局使用justify-content:center

    • 垂直居中

      1. 利用 line-height 实现居中,这种方法适合纯文字类
      2. 通过设置父容器 相对定位 ,子级设置 绝对定位,标签通过margin实现自适应居中
      3. 弹性布局 flex :父级设置display: flex; 子级设置margin为auto实现自适应居中
      4. 父级设置相对定位,子级设置绝对定位,并且通过位移 transform 实现
      5. table 布局,父级通过转换成表格形式,然后子级设置 vertical-align 实现。(需要注意的是:vertical-align: middle使用的前提条件是内联元素以及display值为table-cell的元素)。

    页面布局

    1.Flex 布局

    布局的传统解决方案,基于盒状模型,依赖 display 属性 + position 属性 + float 属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。

    Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。指定容器 display: flex 即可。 简单的分为容器属性和元素属性。

    容器的属性:

    • flex-direction:决定主轴的方向(即子 item 的排列方法)flex-direction: row | row-reverse | column | column-reverse;
    • flex-wrap:决定换行规则 flex-wrap: nowrap | wrap | wrap-reverse;
    • flex-flow: .box { flex-flow: || ; }
    • justify-content:对其方式,水平主轴对齐方式
    • align-items:对齐方式,竖直轴线方向
    • align-content

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

    • 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 实现圣杯布局
    2.Rem 布局

    首先 Rem 相对于根(html)的 font-size 大小来计算。简单的说它就是一个相对单例 如:font-size:10px;,那么(1rem = 10px)了解计算原理后首先解决怎么在不同设备上设置 html 的 font-size 大小。其实 rem 布局的本质是等比缩放,一般是基于宽度。

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

    缺点

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

    3.百分比布局

    通过百分比单位 " % " 来实现响应式的效果。通过百分比单位可以使得浏览器中的组件的宽和高随着浏览器的变化而变化,从而实现响应式的效果。 直观的理解,我们可能会认为子元素的百分比完全相对于直接父元素,height 百分比相 对于 height,width 百分比相对于 width。 padding、border、margin 等等不论是垂直方向还是水平方向,都相对于直接父元素的 width。 除了 border-radius 外,还有比如 translate、background-size 等都是相对于自身的。

    缺点

    (1)计算困难
    (2)各个属性中如果使用百分比,相对父元素的属性并不是唯一的。造成我们使用百分比单位容易使布局问题变得复杂。

    4.浮动布局

    浮动布局:当元素浮动以后可以向左或向右移动,直到它的外边缘碰到包含它的框或者另外一个浮动元素的边框为止。元素浮动以后会脱离正常的文档流,所以文档的普通流中的框就变的好像浮动元素不存在一样。

    优点

    这样做的优点就是在图文混排的时候可以很好的使文字环绕在图片周围。另外当元素浮动了起来之后,它有着块级元素的一些性质例如可以设置宽高等,但它与inline-block还是有一些区别的,第一个就是关于横向排序的时候,float可以设置方向而inline-block方向是固定的;还有一个就是inline-block在使用时有时会有空白间隙的问题

    缺点

    最明显的缺点就是浮动元素一旦脱离了文档流,就无法撑起父元素,会造成父级元素高度塌陷

    清除浮动的方式

    • 添加额外标签
    <div class="parent">
        //添加额外标签并且添加clear属性
        <div style="clear:both"></div>
        //也可以加一个br标签
    复制代码
    • 父级添加overflow属性,或者设置高度
    • 建立伪类选择器清除浮动
    //在css中添加:after伪元素
    .parent:after{
        /* 设置添加子元素的内容是空 */
        content: '';
        /* 设置添加子元素为块级元素 */
        display: block;
        /* 设置添加的子元素的高度0 */
        height: 0;
        /* 设置添加子元素看不见 */
        visibility: hidden;
        /* 设置clear:both */
        clear: both;
    }
    复制代码

    CSS预处理器Sass、Less、Stylus的区别

    什么事CSS预处理器?

    CSS预处理器是一种语言用来为CSS增加一些变成的特性,无需考虑浏览器兼容问题,例如你可以在CSS中使用变量,简单的程序逻辑、函数等在编程语言中的一些基本技巧,可以让CSS更加简洁,适应性更强,代码更直观等诸多好处 基本语法区别

    Sass是以.sass为扩展名,Less是以.less为扩展名,Stylus是以.styl为扩展名 变量的区别

    Sass 变量必须是以$开头的,然后变量和值之间使用冒号(:)隔开,和css属性是一样的。 Less 变量是以@开头的,其余sass都是一样的。 Stylus 对变量是没有任何设定的,可以是以$开头或者任意字符,而且变量之间可以冒号,空格隔开,但是在stylus中不能用@开头 三种预处理器都有:嵌套、运算符、颜色函数、导入、继承、混入。Stylus还有一些高级特性。例如循环、判断等

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

    1.opacity:0,该元素隐藏起来了,但不会改变页面布局,并且,如果该元素已经绑定 一些事件,如click 事件,那么点击该区域,也能触发点击事件的

    2.visibility:hidden,该元素隐藏起来了,但不会改变页面布局,但是不会触发该元素已 经绑定的事件 ,隐藏对应元素,在文档布局中仍保留原来的空间(重绘)

    3.display:none,把元素隐藏起来,并且会改变页面布局,可以理解成在页面中把该元素。 不显示对应的元素,在文档布局中不再分配空间(回流+重绘)

    JS、TS、ES6

    JS中的8种数据类型及区别

    包括值类型(基本对象类型)和引用类型(复杂对象类型)

    基本类型(值类型): Number(数字),String(字符串),Boolean(布尔),Symbol(符号),null(空),undefined(未定义)在内存中占据固定大小,保存在栈内存中

    引用类型(复杂数据类型): Object(对象)、Function(函数)。其他还有Array(数组)、Date(日期)、RegExp(正则表达式)、特殊的基本包装类型(String、Number、Boolean) 以及单体内置对象(Global、Math)等 引用类型的值是对象 保存在堆内存中,栈内存存储的是对象的变量标识符以及对象在堆内存中的存储地址。

    简单介绍一下 symbol?

    Symbol 是 ES6 的新增属性,代表用给定名称作为唯一标识,这种类型的值可以这样创建,let id=symbol(“id”);Symbl 确保唯一,即使采用相同的名称,也会产生不同的值,有内置方法 Object.getOwnPropertySymbols(obj)可以获得所有的 symbol。 也有一个方法 Reflect.ownKeys(obj)返回对象所有的键,包括 symbol。

    null 和 undefined的区别

    undefined是访问一个未初始化的变量时返回的值,而null是访问一个尚未存在的对象时所返回的值。undefined看作是空的变量,而null看作是空的对象

    JS中的数据类型检测方案

    1.typeof
    console.log(typeof 1);               // number
    console.log(typeof true);            // boolean
    console.log(typeof 'mc');            // string
    console.log(typeof function(){});    // function
    console.log(typeof console.log());   // function
    console.log(typeof []);              // object 
    console.log(typeof {});              // object
    console.log(typeof null);            // object
    console.log(typeof undefined);       // undefined
    复制代码

    优点:能够快速区分基本数据类型

    缺点:不能将Object、Array和Null区分,都返回object

    2.instanceof
    console.log(1 instanceof Number);                    // false
    console.log(true instanceof Boolean);                // false 
    console.log('str' instanceof String);                // false  
    console.log([] instanceof Array);                    // true
    console.log(function(){} instanceof Function);       // true
    console.log({} instanceof Object);                   // true
    复制代码

    优点:能够区分Array、Object和Function,适合用于判断自定义的类实例对象

    缺点:Number,Boolean,String基本数据类型不能判断

    3.Object.prototype.toString.call()
    var toString = Object.prototype.toString;
    console.log(toString.call(1));                      //[object Number]
    console.log(toString.call(true));                   //[object Boolean]
    console.log(toString.call('mc'));                   //[object String]
    console.log(toString.call([]));                     //[object Array]
    console.log(toString.call({}));                     //[object Object]
    console.log(toString.call(function(){}));           //[object Function]
    console.log(toString.call(undefined));              //[object Undefined]
    console.log(toString.call(null));                   //[object Null]
    复制代码

    优点:精准判断数据类型

    缺点:写法繁琐不容易记,推荐进行封装后使用

    变量计算

    强制类型转换

    // 1.字符串拼接
    var a = 100 + 10 // 110
    var b = 100 + '10' // '10010'
    // 2.==运算符
    100 = '100' // true
    0 == '' // true
    null = undefined // true [nullundefined都会转换成false]
    // 3.if语句
    let a = true
    if(a){
    // 
    }
    

    let b = 100
    if(b){
    // 会将b强制转换成boolean类型
    }

    let c = ‘’
    if©{
    // 空字符串会被强制转换成boolean
    }
    //4.逻辑运算符
    console. Log (10 && 0) // 0
    console. log(‘’ || ‘abc’) // ‘abc’
    console. log(!window.abc) // true
    //判断一个变量会被当做true还是false,使用双非判断
    var a = 100
    console.log(!!a)
    复制代码

    var && let && const

    ES6之前创建变量用的是var,之后创建变量用的是let/const

    三者区别

    1. var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
      let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
      const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,且不能修改。

    2. var可以先使用,后声明,因为存在变量提升;let必须先声明后使用。

    3. var是允许在相同作用域内重复声明同一个变量的,而let与const不允许这一现象。

    4. 在全局上下文中,基于let声明的全局变量和全局对象GO(window)没有任何关系 ;
      var声明的变量会和GO有映射关系;

    5. 解决暂时性死区

    暂时性死区是浏览器的bug:检测一个未被声明的变量类型时,不会报错,会返回undefined
    如:console.log(typeof a) //undefined
    而:console.log(typeof a)//未声明之前不能使用
    let a

    1. let /const/function会把当前所在的大括号(除函数之外)作为一个全新的块级上下文,应用这个机制,在开发项目的时候,遇到循环事件绑定等类似的需求,无需再自己构建闭包来存储,只要基于let的块作用特征即可解决

    变量提升

    当浏览器开辟出供代码执行的栈内存后,代码并没有自上而下立即执行,而是继续做了一些事情:把当前作用域中所有带var、function关键字的进行提前的声明和定义 =>变量提升机制 【预解析】

    • 带var的只是提前声明,没有赋值,默认值是undefined。
    • 带function的声明加赋值。
    • 不带var的a=3表示给全局设置window.a=3和在全局作用域下var a=3是一样的;

    在变量提升阶段,遇到大括号判断体等,不论条件是否成立,都要进行变量提升,而在高版本浏览器中,函数只声明、不赋值。

    暂时性死区

    以下是关于ES6标准中关于暂时性死区的解释:

    当程序的控制流程在新的作用域(module function 或 block作用域)进行实例化时,在此作用域中用let/const声明的变量会先在作用域中被创建出来,但因此时还未进行词法绑定,所以是不能被访问的,如果访问就会抛出错误。因此,在这运行流程进入作用域创建变量,到变量可以被访问之间的这一段时间,就称之为暂时死区。

    造成该错误的主要原因是:ES6新增的let、const关键字声明的变量会产生块级作用域,如果变量在当前作用域中被创建之前被创建出来,由于此时还未完成语法绑定,如果我们访问或使用该变量,就会产生暂时性死区的问题,由此我们可以得知,从变量的创建到语法绑定之间这一段空间,我们就可以理解为‘暂时性死区’

    JS的单线程&同步

    1. 什么是单线程
      单线程即同一时间只有一个线程,只能做一件事
      原因:避免DOM渲染的冲突
      解决方案:异步
      实现方式:event-loop

    2. JS的 同步任务/异步任务

      • 同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务

      • 异步:不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行

    3. JavaScript为什么需要异步

      如果在JS代码执行过程中,某段代码执行过久,后面的代码迟迟不能执行,产生阻塞(即卡死),会影响用户体验。

    4. JavaScript怎么实现异步

      JS 实现异步时通过 事件循环(Event Loop),是JS异步的解决方案。 JS实现异步的具体解决方案

      1、同步代码,直接执行
      2、异步代码先放在 异步队列
      3、待同步函数执行完毕,轮询执行异步队列 中的函数

    5. 目前JS解决异步的方案有哪些

      • 回调函数
      • 事件监听
      • 发布-订阅
      • Promise
      • Generator
      • Async/Await

    JS堆栈内存的运行机制

    Snipaste_2021-07-28_19-05-38.png

    JS内存空间分为栈(stack)堆(heap)池(一般也会归类为栈中) 。 其中存放变量,存放复杂对象,存放常量,所以也叫常量池。

    • 基本类型:--> 内存(不包含闭包中的变量)
    • 引用类型:--> 内存

    栈内存(Stack):浏览器在计算机内存中分配出一块内存供代码执行的环境栈(ECStack),也称栈内存 ;

    基本数据类型都是存到栈里面的。 引用数据类型指针存到栈内存。

    堆内存(Heap):浏览器会把内置的属性和方法放到一个单独的内存中,

    引用数据类型是先开辟一个堆内存,一个16进制的地址,按照键、值分别存放,最后把地址放到栈中供代码关联使用;

    js 中存在多种作用域(全局,函数私有的,块级私有的),引擎在编译执行代码的过程中,首先会创建一个执行栈,也就是栈内存(ECStack => 执行环境栈),然后执行代码。

    代码执行前首先会形成自己的EC(执行上下文),执行上下文分为全局执行上下文(EC(G))和函数执行上下文(EC(...)),其中函数的执行上下文是私有的。

    创建执行上下文的过程中,可能会创建:

    • GO(Global Object):全局对象 浏览器端,会把GO赋值给window
    • VO(Varible Object):变量对象,存储当前上下文中的变量。
    • AO(Active Object):活动对象

    然后把上下文压缩进栈,进栈后,在当前上下文再依次执行代码; 全局执行器上下文(EC(G))进栈(ECStack)执行,执行完代码就会把形成的上下文释放(出栈),执行后一些没用的上下文也将出栈,有用的上下文会压缩到栈底(闭包)。栈底永远是全局执行上下文,栈顶则永远是当前执行上下文。当页面关闭全局上下文出栈。

    JS编译机制:VO/AO/GO

    VO 变量对象:每一个执行上下文都会有自己的一个VO变量对象,用来存放在当前上下文中创建的变量和函数。(函数私有上下文叫 AO 活跃对象,但也是变量对象)。

    GO 全局对象:他是一个堆内存(存储的都是浏览器内置的 api 属性方法),在浏览器端,让 window 指向它

    VO(G)全局变量对象:全局上下文中用来存储全局变量的空间,他不是 GO=》只不过某些情况下 VO(G)中的东西会和 GO 中的东西有所关联而已;

    函数执行:

    • 创建执行栈ECStack:引擎在编译执行代码的过程中,首先会创建一个执行栈
    • 形成全局执行上下文EC(FN):函数执行的时候,形成一个全新的私有上下文EC(FN),供字符串代码执行
    • 创建全局变量或对象 GO
    • 进栈执行:进栈执行,从上面进去,把全局往下压
    • 代码执行之前还需要:
      • 1.初始化作用域链(scopeChain):<EC(FN),EC(G)>
      • 2.初始化 this 指向:window
      • 3.初始化实参集合:arguments
      • 4.形参赋值
      • 5.变量提升
      • 6.代码执行

    JS垃圾回收机制

    1. 项目中,如果存在大量不被释放的内存(堆/栈/上下文),页面性能会变得很慢。当某些代码操作不能被合理释放,就会造成内存泄漏。我们尽可能减少使用闭包,因为它会消耗内存。

    2. 浏览器垃圾回收机制/内存回收机制:

      浏览器的Javascript具有自动垃圾回收机制(GC:Garbage Collecation),垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。

      标记清除:在js中,最常用的垃圾回收机制是标记清除:当变量进入执行环境时,被标记为“进入环境”,当变量离开执行环境时,会被标记为“离开环境”。垃圾回收器会销毁那些带标记的值并回收它们所占用的内存空间。
      谷歌浏览器:“查找引用”,浏览器不定时去查找当前内存的引用,如果没有被占用了,浏览器会回收它;如果被占用,就不能回收。
      IE浏览器:“引用计数法”,当前内存被占用一次,计数累加1次,移除占用就减1,减到0时,浏览器就回收它。

    3. 优化手段:内存优化 ; 手动释放:取消内存的占用即可。

      (1)堆内存:fn = null 【null:空指针对象】

      (2)栈内存:把上下文中,被外部占用的堆的占用取消即可。

    4. 内存泄漏

      在 JS 中,常见的内存泄露主要有 4 种,全局变量、闭包、DOM 元素的引用、定时器

    作用域和作用域链

    创建函数的时候,已经声明了当前函数的作用域==>当前创建函数所处的上下文。如果是在全局下创建的函数就是[[scope]]:EC(G),函数执行的时候,形成一个全新的私有上下文EC(FN),供字符串代码执行(进栈执行)

    定义:简单来说作用域就是变量与函数的可访问范围,由当前环境与上层环境的一系列变量对象组成
    1.全局作用域:代码在程序的任何地方都能被访问,window 对象的内置属性都拥有全局作用域。
    2.函数作用域:在固定的代码片段才能被访问

    作用:作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

    作用域链参考链接一般情况下,变量到 创建该变量 的函数的作用域中取值。但是如果在当前作用域中没有查到,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

    闭包的两大作用:保存/保护

    • 闭包的概念

      函数执行时形成的私有上下文EC(FN),正常情况下,代码执行完会出栈后释放;但是特殊情况下,如果当前私有上下文中的某个东西被上下文以外的事物占用了,则上下文不会出栈释放,从而形成不销毁的上下文。 函数执行函数执行过程中,会形成一个全新的私有上下文,可能会被释放,可能不会被释放,不论释放与否,他的作用是:

    (1)保护:划分一个独立的代码执行区域,在这个区域中有自己私有变量存储的空间,保护自己的私有变量不受外界干扰(操作自己的私有变量和外界没有关系);

    (2)保存:如果当前上下文不被释放【只要上下文中的某个东西被外部占用即可】,则存储的这些私有变量也不会被释放,可以供其下级上下文中调取使用,相当于把一些值保存起来了;

    我们把函数执行形成私有上下文,来保护和保存私有变量机制称为闭包

    闭包是指有权访问另一个函数作用域中的变量的函数--《JavaScript高级程序设计》

    稍全面的回答: 在js中变量的作用域属于函数作用域, 在函数执行完后,作用域就会被清理,内存也会随之被回收,但是由于闭包函数是建立在函数内部的子函数, 由于其可访问上级作用域,即使上级函数执行完, 作用域也不会随之销毁, 这时的子函数(也就是闭包),便拥有了访问上级作用域中变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。

    • 闭包的特性

      • 1、内部函数可以访问定义他们外部函数的参数和变量。(作用域链的向上查找,把外围的作用域中的变量值存储在内存中而不是在函数调用完毕后销毁)设计私有的方法和变量,避免全局变量的污染。

        1.1.闭包是密闭的容器,,类似于set、map容器,存储数据的

        1.2.闭包是一个对象,存放数据的格式为 key-value 形式

      • 2、函数嵌套函数

      • 3、本质是将函数内部和外部连接起来。优点是可以读取函数内部的变量,让这些变量的值始终保存在内存中,不会在函数被调用之后自动清除

    • 闭包形成的条件

      1. 函数的嵌套
      2. 内部函数引用外部函数的局部变量,延长外部函数的变量生命周期
    • 闭包的用途

      1. 模仿块级作用域
      2. 保护外部函数的变量 能够访问函数定义时所在的词法作用域(阻止其被回收)
      3. 封装私有化变量
      4. 创建模块
    • 闭包应用场景

      闭包的两个场景,闭包的两大作用:保存/保护。 在开发中, 其实我们随处可见闭包的身影, 大部分前端JavaScript 代码都是“事件驱动”的,即一个事件绑定的回调方法; 发送ajax请求成功|失败的回调;setTimeout的延时回调;或者一个函数内部返回另一个匿名函数,这些都是闭包的应用。

    • 闭包的优点:延长局部变量的生命周期

    • 闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏

    JS 中 this 的五种情况

    1. 作为普通函数执行时,this指向window
    2. 当函数作为对象的方法被调用时,this就会指向该对象
    3. 构造器调用,this指向返回的这个对象
    4. 箭头函数 箭头函数的this绑定看的是this所在函数定义在哪个对象下,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上。
    5. 基于Function.prototype上的 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply接收参数的是数组,call接受参数列表,`` bind方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this指向除了使用new `时会被改变,其他情况下都不会改变。若为空默认是指向全局对象window。

    创建对象有几种方法

    // 第一种:字面量
    var o1 = {name: "o1"}
    var o2 = new Object({name: "o2"})
    // 第二种:通过构造函数
    var M = function(name){this.name = name}
    var o3 = new M("o3")
    // 第三种:Object.create()
    var p = {name: "p"}
    var o4 = Object.create(p)
    

    复制代码

    类和实例

    ES6提供了更接近面向对象(注意:javascript本质上是基于对象的语言)语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。 类的创建(es5):new 一个 function,在这个 function 的 prototype 里面增加属性和 方法。

    原型规则和示例

    5条原型规则,是学习理解原型链的基础。
    1.所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了“nul”意外)
    2.所有的引用类型(数组、对象、函数),都有一个_proto_(隐式原型)属性,属性值是一个普通的对象
    3.所有的函数,都有一个prototype(显式原型)属性,属性值也是一个普通的对象
    4.所有的引用类型(数组、对象、函数),__ proto __ 属性值指向它的构造函数的" __ prototype __ "属性值
    5.当试图得到一个对象的某个属性时,如果这个对象本身没 有这个属性,那么会去它的__proto__(即它的构造函数的 prototype)中寻找

    var obj = {}; obj.a = 100;//符合第一条可以自由扩展属性
    var arr = []; arr.a = 100;
    function fn(){}
    fn.a=100;
    

    console. log(obj.proto)
    console. log(arr.proto)
    console. log(fn.proto)

    console. log(fn.prototype)

    console.log(obj.proto===Object. prototype)
    复制代码

    示例:

    // 构造函数
    function Foo(name, age) {
        this.name = name;
    }
    Foo.prototype. alertName = function(){
        alert(this.name);
    }
    //创建示例
    var f = new Foo('zhangsan');
    f.printName = function (){
        console.log(this. name);
    }
    // 测试
    f.printName();
    f.alertName();
    f.toString() //要去f._proto_._proto_中查找 原型链
    复制代码

    原型链就是我从我的实例对象网上找构造这个实例相关联的对象,然后这个关联的对象再往上找它又有创造它的原型对象以此类推,一直到Object.prototype原型对象终止。Object.prototype原型对象是整个原型链的顶端,到这就截止了。

    原型关系:

    • 每个 class都有显示原型 prototype
    • 每个实例都有隐式原型 _ proto_
    • 实例的_ proto_指向对应 class 的 prototype ‌

    基于原型的执行规则:即原型链

    • 获取属性 xialuo.name或执行方法 xialuo. sahi()时
    • 先在自身属性和方法寻找
    • 如果找不到则自动去隐式原型_ proto_中查找

    原型: 在 JS 中,每当定义一个对象(函数也是对象)时,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象

    原型链:函数的原型链对象constructor默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针__proto__,该指针是指向上一层的原型对象,而上一层的原型对象的结构依然类似。因此可以利用__proto__一直指向Object的原型对象上,而Object原型对象用Object.prototype.proto=null表示原型链顶端。如此形成了js的原型链继承。同时所有的js对象都有Object的基本防范

    特点: JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

    new运算符的实现机制

    1. 首先创建了一个新的空对象
    2. 设置原型,将对象的原型设置为函数的prototype对象。
    3. 让函数的this指向这个对象,执行构造函数的代码(为这个新对象添加属性)
    4. 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

    JS中的多种继承方案、继承(含es6)

    (1)第一种是以原型链的方式来实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。

    (2)第二种方式是使用借用构造函数的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。

    (3)第三种方式是组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。

    (4)第四种方式是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。

    (5)第五种方式是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是我们的自定义类型时。缺点是没有办法实现函数的复用。

    (6)第六种方式是寄生式组合继承,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。

    JS中的深浅拷贝

    1. 深拷贝 最简单的方法就是JSON.parse(JSON.stringify()),但是这种拷贝方法不可以拷贝一些特殊的属性(例如正则表达式,undefine,function)
    //对象深度克隆的简单实现
    var shallowCopy = function(obj) { // 只拷贝对象 
        if (typeof obj !== 'object') return;
        // 根据 obj 的类型判断是新建一个数组还是对象 
        var newObj = obj instanceof Array ? [] : {}; 
        // 遍历 obj,并且判断是 obj 的属性才拷贝 
        for (var key in obj) { 
            if (obj.hasOwnProperty(key)) { 
                newObj[key] = obj[key]; 
            }
        }
        return newObj;
    }
    复制代码
    1. 浅拷贝
    //方法 1
    Object.assign(target, ...sources)
    //方法 2
    function simpleClone(obj) {
        var result = {};
        for (var i in obj) {
            result[i] = obj[i];
        }
        return result;
    }
    复制代码
    复制代码

    EventLoop 事件循环

    JS是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。微任务队列的代表就是,Promise.thenMutationObserver,宏任务的话就是setImmediate setTimeout setInterval

    JS运行的环境。一般为浏览器或者Node。 在浏览器环境中,有JS 引擎线程和渲染线程,且两个线程互斥。 Node环境中,只有JS 线程。 不同环境执行机制有差异,不同任务进入不同Event Queue队列。 当主程结束,先执行准备好微任务,然后再执行准备好的宏任务,一个轮询结束。

    浏览器中的事件环(Event Loop)

    事件环的运行机制是,先会执行栈中的内容,栈中的内容执行后执行微任务,微任务清空后再执行宏任务,先取出一个宏任务,再去执行微任务,然后在取宏任务清微任务这样不停的循环。

    • eventLoop 是由JS的宿主环境(浏览器)来实现的;

    • 事件循环可以简单的描述为以下四个步骤:

      1. 函数入栈,当Stack中执行到异步任务的时候,就将他丢给WebAPIs,接着执行同步任务,直到Stack为空;
      2. 此期间WebAPIs完成这个事件,把回调函数放入队列中等待执行(微任务放到微任务队列,宏任务放到宏任务队列)
      3. 执行栈为空时,Event Loop把微任务队列执行清空;
      4. 微任务队列清空后,进入宏任务队列,取队列的第一项任务放入Stack(栈)中执行,执行完成后,查看微任务队列是否有任务,有的话,清空微任务队列。重复4,继续从宏任务中取任务执行,执行完成之后,继续清空微任务,如此反复循环,直至清空所有的任务。

      事件循环流程

    • 浏览器中的任务源(task):

      • 宏任务(macrotask)
        宿主环境提供的,比如浏览器
        ajax、setTimeout、setInterval、setTmmediate(只兼容ie)、script、requestAnimationFrame、messageChannel、UI渲染、一些浏览器api

      • 微任务(microtask)
        语言本身提供的,比如promise.then
        then、queueMicrotask(基于then)、mutationObserver(浏览器提供)、messageChannel 、mutationObersve

    Node 环境中的事件环(Event Loop)

    Node是基于V8引擎的运行在服务端的JavaScript运行环境,在处理高并发、I/O密集(文件操作、网络操作、数据库操作等)场景有明显的优势。虽然用到也是V8引擎,但由于服务目的和环境不同,导致了它的API与原生JS有些区别,其Event Loop还要处理一些I/O,比如新的网络连接等,所以Node的Event Loop(事件环机制)与浏览器的是不太一样。

    2020120317343116.png 执行顺序如下:

    • timers: 计时器,执行setTimeout和setInterval的回调
    • pending callbacks: 执行延迟到下一个循环迭代的 I/O 回调
    • idle, prepare: 队列的移动,仅系统内部使用
    • poll轮询: 检索新的 I/O 事件;执行与 I/O 相关的回调。事实上除了其他几个阶段处理的事情,其他几乎所有的异步都在这个阶段处理。
    • check: 执行setImmediate回调,setImmediate在这里执行
    • close callbacks: 执行close事件的callback,一些关闭的回调函数,如:socket.on('close', ...)

    Promise

    Promise的概念
    • 主要用于异步计算。promise是用来解决异步函数的顺序问题.
    • 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果。
    • 可以在对象之间传递和操作Promise,帮助我们处理队列。

    回调有四个问题

    • 嵌套层次很深,难以维护
    • 无法正常使用return和throw
    • 无法正常检索堆栈信息
    • 多个回调之间难以建立联系
    new Promise(
        /*执行器executor */
        function (resolve, reject){
            //段耗时很长的异步操作
            resolve();//数据处理完成
            reject(); //数据处理出错
    }
    .then(function A(){
        //成功,下一步
    },function B(){
        //失败,做相应处理
    }
    复制代码
    Promise详解如何实现异步执行
    • Promise是一个代理对象,它和原先要进行的操作并无关系。
    • 它通过引入一个回调,避免更多的回调。

    Promise的内部是如何实现异步执行的呢?

    通过查看Promise的源码实现,发现其异步执行是通过asap这个库来实现的。

    asap是as soon as possible的简称,在Node和浏览器环境下,能将回调函数以高优先级任务来执行(下一个事件循环之前),即把任务放在微任务队列中执行。

    宏任务(macro-task)和微任务(micro-task)表示异步任务的两种分类。在挂起任务时, js 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。

    // asap 用法
    asap(function () {
        // ...
    });
    复制代码
    Promise有3个状态:
    • pending|【待定】初始状态
    • fulfilled|【实现】操作成功
    • rejected【被否决】操作失败

    Promise状态发生改变,就会触发.then()里的响应函数处理后 续步骤。 Promise状态一经改变,不会再变。 Promise实例一经创建,执行器立即执行。

    .then()

    .then()接受两个函数作为参数,分别代表fulilledrejected .then()返回一个新的Promise实例,所以它可以链式调用 .当前面的Promise状态改变时,.then()根据其最终状态,选择特定 的状态响应函数执行 .状态响应函数可以返回新的Promise,或其它值 .如果返回新的Promise,那么下一级.then()会在新Promise状态改变之后执行 .如果返回其它任何值,则会立刻执行下一级.then()

    .then()里有.then()的情况

    因为.then()返回的还是Promise实例。 会等里面的.then()执行完,在执行外面的。 对于我们来说,此时最好将其展开,会更好读。

    错误处理

    Promise会自动捕获内部异常,并交给rejected响应函数处理。 错误处理的两种做法: reject('错误信息).then(null,message =>{}) throw new Error('错误信息')catch(message=>{}) 推荐使用第二种,更加清晰好读,并且可以捕获前面的错误。

    Promise.all() 批量执行
    • Promise.all(【p1,p2,p….】)用于将多个Promise实例,包装成一个新的Promise实例。
    • 返回的实例就是普通Promise
    • 它接受一个数组作为参数。
    • 数组里可以是Promise对象,也可以是别的值,只有Promise会等待状态改变。
    • 当所有子Promise都完成,该Promise完成,返回值是全部值的数组
    • 有任何一个失败,该Promise失败,返回值是第一个失败的子Promise的结果。 Promise.all() 最常见就是和.map() 连用。 实现队列1.使用.forEach()2.使用.reduce()
    Promise.resolve()
    • 返回一个fulfilled的Promise实例,或原始Promise实例。
    • 参数为空,返回一个状态为fulfilled 的Promise实例
    • 参数是一个跟Promise无关的值,同上,不过fulfuilled响应函数会得到这个参数
    • 参数为Promise实例,则返回该实例,不做任何修改
    • 参数为thenable,立刻执行它的.then()
    Promise.reject()

    返回一个rejected的Promise实例。 Promise.reject()其他特性同Promise.resolve(),但不认thenable

    Promise.race()

    类似Promise.all(),区别在于它有任意一个完成就算完成。 场景用法: 把异步操作和定时器放在一起 如果定时器先触发,就认为超时,告知用户

    把回调包装成Promise

    把回调包装成Promise最为常见。它有两个显而易见的好处: 可读性更好 返回的结果可以加入任何Promise队列

    c。

    原生AJAX核心四步操作

    ajax是一种异步通信的方法,从服务端获取数据,达到局部刷新页面的效果。 过程:

    1. 创建XMLHttpRequest对象;
    2. 调用open方法传入三个参数 请求方式(GET/POST)、url、同步异步(true/false);
    3. 监听onreadystatechange事件,当readystate等于4时返回responseText;
    4. 调用send方法传递参数。

    JS高阶编程技巧:惰性函数/柯理化函数/高阶函数 constructor构造函数模式 类和实例 call/apply/bind DOM/BOM的核心操作 DOM2级事件的核心运行机制 事件对象 发布订阅设计模式 浏览器底层渲染机制和DOM的回流重绘 事件传播机制和事件代理

    ES6/ES7的核心知识 解头函数ArrowFunction 解构聚值和拓展运算符 JS底层运行机制:单线程和同步 Set/Map数据结构 异步编程 Gonerator生成器函数 Intorator选代器和for of循环

    AJAX/HTTP前后端数据交互

    前端性能优化汇总(包含强缓存和弱缓存)

    Vue

    简述MVVM

    什么是MVVM?

    视图模型双向绑定,是Model-View-ViewModel的缩写,也就是把MVC中的Controller演变成ViewModel。Model层代表数据模型,View代表UI组件,ViewModelViewModel层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。以前是操作DOM结构更新视图,现在是数据驱动视图

    MVVM的优点:

    1.低耦合。视图(View)可以独立于Model变化和修改,一个Model可以绑定到不同的View上,当View变化的时候Model可以不变化,当Model变化的时候View也可以不变;
    2.可重用性。你可以把一些视图逻辑放在一个Model里面,让很多View重用这段视图逻辑。
    3.独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
    4.可测试

    说说Vue的MVVM实现原理

    1. Vue作为MVVM模式的实现库的2种技术

      a. 模板解析
      b. 数据绑定

    2. 模板解析:实现初始化显示

      a. 解析大括号表达式
      b. 解析指令

    3. 数据绑定:实现更新显示

      a. 通过数据劫持实现

    创建了两种对象Observer和complie,先创建的Observer,后创建的complie,observer是为了监视/劫持data中所有层次的属性,同时还为每一种属性创建了另外一种对象dep,dep与data中的属性一一对应,complie作用是用来编译模版,初始化界面,调用update对象,complie还为每个表达式创建了对应的watcher同时指定了更新节点的回调函数,将watcher添加到所有对应的dep中,

    说说你对 Vue 的理解

    Vue 是一个构建数据驱动的渐进性框架,它的目标是通过 API 实现响应数据绑定和视图更新。

    优点:
    1、数据驱动视图,对真实 dom 进行抽象出 virtual dom, 并配合 diff 算法、响应式和观察者、异步队列等手段以最小代价更新 dom,渲染页面
    2、组件化,组件用单文件的形式进行代码的组织编写,使得我们可以在一个文 件里编写 html\css\js 并且配合 Vue-loader 之后,支持更强大的预处理器等功能
    3、强大且丰富的 API 提供一系列的 api 能满足业务开发中各类需求
    4、由于采用虚拟 dom,但让 Vue ssr 先天不足
    5、生命周期钩子函数,选项式的代码组织方式,写熟了还是蛮顺畅的,但仍然 有优化空间(Vue3 composition-api)
    6、生态好,社区活跃

    缺点:
    1、由于底层基于 Object.defineProperty 实现响应式,而这个 api 本身不支持 IE8 及以下浏览器
    2、csr 的先天不足,首屏性能问题(白屏)
    3、seo 不友好

    说说你对vue虚拟DOM的理解

    什么是虚拟dom
    说白了就是以js对象的形式去添加dom元素
    本质上是优化了diff算法
    虚拟dom本身也有自己的缺陷他更适合批量修改dom
    尽量不要跨层级修改dom
    设置key可以最大的利用节点,避免重复渲染

    一、什么是vdom?

    Virtual DOM 就是用JS对象来模拟真实DOM结构,然后用这个树构建一个真正的 DOM 树, 插到文档当中。当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树 进行比较,记录两棵树差异 把所记录的差异应用到所构建的真正的 DOM 树上,视图就更新了。Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。【版本1.1】

    建立一个与 dom 树对应的虚拟 dom 对象( js 对象),以对象嵌套的方式来表示 dom 树,那么每次 dom 的更改就变成了 js 对象的属性的更改,这样一来就能查找 js 对象 的属性变化要比查询 dom 树的性能开销小。【版本1.2】

    总结起来就两点,一、用JS对象来模拟真实的DOM结构,用这个对象构建真正的DOM树,当状态变更时,重新构建一个新的对象树,然后新旧树进行对比,记录二者差异并应用到所构建的真正的树上,视图也就更新了。二、每次变更时由原来的操作真实的DOM变成了查找js对象的属性变化,直接在内存中操作js对象,性能开销更小,效率更高。【版本2】

    总结起来就是,Virtual DOM 就是用JS对象来模拟真实DOM结构,然后用JS对象树构建真正的DOM树。当状态变更时,重新构建一棵新的对象树,然后新旧树通过diff算法进行比较,若存在差异则将差异应用到所构建的真正的树上,视图就更新了。这个比较过程,由原来的查询真实DOM树变成查找js对象属性,性能开销小了,效率也就高了。Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。【版本3】

    二、为何要用vdom?

    1. 虚拟dom就是为了解决操作真是dom带来的性能问题而出现的,将DOM对比操作放在JS层,提高效率

    2. DOM结构的对比,放在JS层来做(图灵完备语言:能实现逻辑代码的语言)操作内存中的js显然效率更高

    三、vdom核心函数有哪些

    核心函数:
    h('标签名', {...属性名...}, [...子元素...])
    h('标签名', {...属性名...}, '.........')
    patch(container, vnode)
    patch(vnode, newVnode)

    Vue底层实现原理

    vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调
    Vue是一个典型的MVVM框架,模型(Model)只是普通的javascript对象,修改它则试图(View)会自动更新。这种设计让状态管理变得非常简单而直观

    Observer(数据监听器): Observer的核心是通过Object.defineProprtty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher

    Watcher(订阅者): Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:

    1. 在自身实例化时往属性订阅器(dep)里面添加自己
    2. 自身必须有一个update()方法
    3. 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调

    Compile(指令解析器): Compile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新试图

    常用指令

    • v-if:判断是否隐藏;
    • v-for:数据循环出来;
    • v-bind:class:绑定一个属性;
    • v-model:实现双向绑定

    v-model 是什么?有什么用呢?

    参考回答: 一则语法糖,相当于 v-bind:value="xxx" 和 @input,意思是绑定了一个 value 属性的值, 子组件可对 value 属性监听,通过$emit('input', xxx)的方式给父组件通讯。自己实现 v-model 方式的组件也是这样的思路。

    vue-loader解释下

    vue-loader就是一个加载器,能把vue组件转化成javascript模块 为什么要转译vue组件? 可以动态的渲染一些数据,对三个标签template(结构)、style(表现)、script(行为)都做了优化,script中可以直接使用es6 style 也默认可以使用sass并且还给你提供作用域的选择,另外开发阶段还给你提供热加载 还可以如下使用:

    <template src="../hello.vue"></template>
    复制代码

    导航钩子有哪些?它们有哪些参数

    导航钩子翻译过来就是路由的生命周期函数(vue-router) 他其实主要分为两种全局和局部

    全局的钩子函数
    beforeEach:在路由切换开始时调用
    afterEach:在路由切换离开是调用

    局部到单个路由 beforeEnter

    组件的钩子函数
    beforeRouterEnter,
    beforeRouterUpdate,
    beforeRouterLeave

    to:即将进入的目标对象
    from:当前导航要高开的导航对象
    next:是一个函数调用resolve执行下一步

    谈谈对vue生命周期的理解?

    每个Vue实例在创建时都会经过一系列的初始化过程,vue的生命周期钩子,就是说在达到某一阶段或条件时去触发的函数,目的就是为了完成一些动作或者事件

    • create阶段:vue实例被创建
      beforeCreate: 创建前,此时data和methods中的数据都还没有初始化
      created: 创建完毕,data中有值,未挂载
    • mount阶段: vue实例被挂载到真实DOM节点
      beforeMount:可以发起服务端请求,去数据
      mounted: 此时可以操作DOM
    • update阶段:当vue实例里面的data数据变化时,触发组件的重新渲染
      beforeUpdate :更新前
      updated:更新后
    • destroy阶段:vue实例被销毁
      beforeDestroy:实例被销毁前,此时可以手动销毁一些方法
      destroyed:销毁后
    组件生命周期

    生命周期(父子组件) 父组件beforeCreate --> 父组件created --> 父组件beforeMount --> 子组件beforeCreate --> 子组件created --> 子组件beforeMount --> 子组件 mounted --> 父组件mounted -->父组件beforeUpdate -->子组件beforeDestroy--> 子组件destroyed --> 父组件updated

    加载渲染过程 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

    挂载阶段 父created->子created->子mounted->父mounted

    父组件更新阶段 父beforeUpdate->父updated

    子组件更新阶段 父beforeUpdate->子beforeUpdate->子updated->父updated

    销毁阶段 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

    computed与watch

    通俗来讲,既能用 computed 实现又可以用 watch 监听来实现的功能,推荐用 computed, 重点在于 computed 的缓存功能 computed 计算属性是用来声明式的描述一个值依赖了其它的值,当所依赖的值或者变量 改变时,计算属性也会跟着改变; watch 监听的是已经在 data 中定义的变量,当该变量变化时,会触发 watch 中的方法。

    watch 属性监听 是一个对象,键是需要观察的属性,值是对应回调函数,主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作,监听属性的变化,需要在数据变化时执行异步或开销较大的操作时使用

    computed 计算属性 属性的结果会被缓存,当computed中的函数所依赖的属性没有发生改变的时候,那么调用当前函数的时候结果会从缓存中读取。除非依赖的响应式属性变化时才会重新计算,主要当做属性来使用 computed中的函数必须用return返回最终的结果 computed更高效,优先使用。data 不改变,computed 不更新。

    使用场景 computed:当一个属性受多个属性影响的时候使用,例:购物车商品结算功能 watch:当一条数据影响多条数据的时候使用,例:搜索数据

    组件中的data为什么是一个函数?

    1.一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。 2.如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。

    为什么v-for和v-if不建议用在一起

    1.当 v-for 和 v-if 处于同一个节点时,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。如果要遍历的数组很大,而真正要展示的数据很少时,这将造成很大的性能浪费 2.这种场景建议使用 computed,先对数据进行过滤

    为什么 Vuex的mutation中不能做异步操作?

    Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过 Action 来提交 mutation实现,这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。 每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。

    v-for中key的作用

    • 当 Vue.js 用 v-for 更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。重复的key会造成渲染错误。

    • key的作用主要是为了让vue可以区分元素,更高效的对比更新虚拟DOM;

    • Vue在patch过程中判断两个节点是否是相同节点,key是一个必要条件,是唯一标识,如不定义key,Vue只能认为比较的两个节点是同一个,这导致了频繁更新元素,使得整个patch过程比较低效,影响性能;

    • 从源码中可以知道,Vue判断两个节点是否相同时主要判断两者的key和元素类型等,因此如果不设置key,它的值就是undefined,则可能永远认为这是两个相同的节点,只能去做更新操作,这造成了大量的dom更新操作,明显是不可取的。

    vue组件的通信方式

    • 父子组件通信

      父->子props,子->父 $on、$emit 获取父子组件实例 parent、children Ref 获取实例的方式调用组件的属性或者方法 父->子孙 Provide、inject 官方不推荐使用,但是写组件库时很常用

    • 兄弟组件通信

      Event Bus 实现跨组件通信 Vue.prototype.$bus = new Vue() 自定义事件

    • 跨级组件通信

      Vuex、$attrs、$listeners Provide、inject

    双向绑定实现原理

    当一个Vue实例创建时,Vue会遍历data选项的属性,用 Object.defineProperty 将它们转为 getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher重新计算,从而致使它关联的组件得以更新。

    v-model的实现以及它的实现原理吗?

    1. vue中双向绑定是一个指令v-model,可以绑定一个动态值到视图,同时视图中变化能改变该值。v-model是语法糖,默认情况下相于:value和@input
    2. 使用v-model可以减少大量繁琐的事件处理代码,提高开发效率,代码可读性也更好
    3. 通常在表单项上使用v-model
    4. 原生的表单项可以直接使用v-model,自定义组件上如果要使用它需要在组件内绑定value并处理输入事件
    5. 我做过测试,输出包含v-model模板的组件渲染函数,发现它会被转换为value属性的绑定以及一个事件监听,事件回调函数中会做相应变量更新操作,这说明神奇魔法实际上是vue的编译器完成的。

    nextTick的实现

    1. nextTickVue提供的一个全局API,是在下次DOM更新循环结束之后执行延迟回调,在修改数据之后使用$nextTick,则可以在回调中获取更新后的DOM
    2. Vue在更新DOM时是异步执行的。只要侦听到数据变化,Vue将开启1个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中-次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用;
    3. 比如,我在干什么的时候就会使用nextTick,传一个回调函数进去,在里面执行dom操作即可;
    4. 我也有简单了解nextTick实现,它会在callbacks里面加入我们传入的函数,然后用timerFunc异步方式调用它们,首选的异步方式会是Promise。这让我明白了为什么可以在nextTick中看到dom操作结果。

    nextTick的实现原理是什么?

    在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后立即使用 nextTick 来获取更新后的 DOM。 nextTick主要使用了宏任务和微任务。 根据执行环境分别尝试采用Promise、MutationObserver、setImmediate,如果以上都不行则采用setTimeout定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。

    使用过插槽么?用的是具名插槽还是匿名插槽或作用域插槽

    vue中的插槽是一个非常好用的东西slot说白了就是一个占位的 在vue当中插槽包含三种一种是默认插槽(匿名)一种是具名插槽还有一种就是作用域插槽 匿名插槽就是没有名字的只要默认的都填到这里具名插槽指的是具有名字的

    keep-alive的实现

    作用:实现组件缓存,保持这些组件的状态,以避免反复渲染导致的性能问题。 需要缓存组件 频繁切换,不需要重复渲染

    场景:tabs标签页 后台导航,vue性能优化

    原理:Vue.js内部将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构。它将满足条件(pruneCache与pruneCache)的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。

    mixin

        mixin 项目变得复杂的时候,多个组件间有重复的逻辑就会用到mixin
        多个组件有相同的逻辑,抽离出来
        mixin并不是完美的解决方案,会有一些问题
        vue3提出的Composition API旨在解决这些问题【追求完美是要消耗一定的成本的,如开发成本】
        场景:PC端新闻列表和详情页一样的右侧栏目,可以使用mixin进行混合
        劣势:1.变量来源不明确,不利于阅读 2.mixin可能会造成命名冲突 3.mixin和组件可能出现多对多的关系,使得项目复杂度变高
    复制代码

    vuex是什么?原理是什么?怎么使用?哪种功能场景使用它?

    状态管理库,类似 React 中的 Rudux

    关于vuex vuex是一个专门为vue构建的状态集管理,主要是为了解决组件间状态共享的问题,强调的是数据的集中式管理,说白了主要是便于维护便于解耦所以不是所有的项目都适合使用vuex,如果你不是构建大型项目使用vuex反而使你的项目代码繁琐多余

    vuex的核心: state mutations getters actions modules

    Vuex的理解及使用场景

    Vuex 是一个专为 Vue 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。

    1. Vuex 的状态存储是响应式的;当 Vue 组件从 store 中读取状态的时候, 若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新
    2. 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation, 这样使得我们可以方便地跟踪每一个状态的变化 Vuex主要包括以下几个核心模块:
    3. State:定义了应用的状态数据
    4. Getter:在 store 中定义“getter”(可以认为是 store 的计算属性), 就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来, 且只有当它的依赖值发生了改变才会被重新计算
    5. Mutation:是唯一更改 store 中状态的方法,且必须是同步函数
    6. Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作
    7. Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中

    Vuex管理状态的机制

    1)对Vuex基本理解1)是什么:Vuex是一个专为Vue.js应用程序开发的状态管理的vue插件2)作用:集中式管理vue多个组件共享的状态和从后台获取的数据states帮助组件管理状态的,基于state的还有一个计算属性数据getters,getters是从state中读取数据并计算的,他们两个的数据都是给组件去读,组件中读取state状态数据使用store.state或mapState(),读取计算属性数据也有两个方法是store.state或mapState(),读取计算属性数据也有两个方法是store.statemapState(),读取计算属性数据也有两个方法是store.getters和mapGetters();更新状态数据涉及到actions和mutations,通过$store.dispatch或mapAction()触发action的调用,然后actions会通过commit()触发mutations调用,mutations则直接更新状态;actions还可以同后台API进行双向通信。

    单向数据流

    “单向数据流”理念的极简示意:

    • state:驱动应用的数据源。
    • view:以声明方式将 state 映射到视图 。
    • actions:响应在 view 上的用户输入导致的状态变化
    单向数据流过程:

    简单的单向数据流(unidirectional data flow)是指用户访问View,View发出用户交互的Action,在Action里对state进行相应更新。state更新后会触发View更新页面的过程。这样数据总是清晰的单向进行流动,便于维护并且可以预测

    vue的diff算法

    问题:渲染真实的DOM开销是很大的,修改了某个数据,如果直接渲染到真实dom上会引起整个DOM树的重绘和重排。 Virtual Dom 根据真实DOM生成的一个Virtual DOM,当Virtual DOM某个节点的数据改变后生成一个新的Vnode,然后Vnode和oldVnode作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使oldVnode的值为Vnode. 注意:在采取diff算法比较的时候,只会在同层级进行,不会跨层级比较。 当数据发生改变时,set方法会让调用Dep.notify()方法通知所有订阅者Watcher,订阅者就会调用patch函数给真实的DOM打补丁,更新响应的试图。

    Vue项目中实现路由按需加载(路由懒加载)的3中方式:

    1. vue异步组件
    2. es6提案的import()
    3. webpack的require.ensure()

    你知道Vue3有哪些新特性吗?它们会带来什么影响?

    • 性能提升

    更小巧、更快速 支持自定义渲染器 支持摇树优化:一种在打包时去除无用代码的优化手段 支持Fragments和跨组件渲染

    • API变动

    模板语法99%保持不变 原生支持基于class的组件,并且无需借助任何编译及各种stage阶段的特性 在设计时也考虑TypeScript的类型推断特性 重写虚拟DOM可以期待更多的编译时提示来减少运行时的开销 优化插槽生成可以单独渲染父组件和子组件 静态树提升降低渲染成本 基于Proxy的观察者机制节省内存开销

    • 不兼容IE11

    检测机制更加全面、精准、高效,更具可调试式的响应跟踪

    Vue3.0 编译做了哪些优化?

    a. 生成 Block tree Vue.js 2.x 的数据更新并触发重新渲染的粒度是组件级的,单个组件内部 需要遍历该组 件的整个 vnode 树。在 2.0 里,渲染效率的快慢与组件大小成正相关:组件越大,渲染 效率越慢。并且,对于一些静态节点,又无数据更新,这些遍历都是性能浪费。 Vue.js 3.0 做到了通过编译阶段对静态模板的分析,编译生成了 Block tree。 Block tree 是一个将模版基于动态节点指令切割的嵌套区块,每个 区块内部的节点结构是固定的, 每个区块只需要追踪自身包含的动态节点。所以,在 3.0 里,渲染效率不再与模板大小 成正相关,而是与模板中动态节点的数量成正相关。

    b. slot 编译优化 Vue.js 2.x 中,如果有一个组件传入了 slot,那么每次父组件更新的时候,会强制使子组 件 update,造成性能的浪费。 Vue.js 3.0 优化了 slot 的生成,使得非动态 slot 中属性的更新只会触发子组件的更新。 动态 slot 指的是在 slot 上面使用 v-if,v-for,动态 slot 名字等会导致 slot 产生运行时动 态变化但是又无法被子组件 track 的操作。 c. diff 算法优化

    Vue3.0 是如何变得更快的?(底层,源码)

    a. diff 方法优化 Vue2.x 中的虚拟 dom 是进行全量的对比。 Vue3.0 中新增了静态标记(PatchFlag):在与上次虚拟结点进行对比的时候,值对比 带有 patch flag 的节点,并且可以通过 flag 的信息得知当前节点要对比的具体内容化。 b. hoistStatic 静态提升 Vue2.x : 无论元素是否参与更新,每次都会重新创建。 Vue3.0 : 对不参与更新的元素,只会被创建一次,之后会在每次渲染时候被不停的复用。 c. cacheHandlers 事件侦听器缓存 默认情况下 onClick 会被视为动态绑定,所以每次都会去追踪它的变化但是因为是同一 个函数,所以没有追踪变化,直接缓存起来复用即可。 原作者姓名: 欧阳呀

    2.0存在的问题 1.对原始数据进行克隆一份 2.需要分别给对象中的每个属性设置监听 3.0里面使用的是proxy监听对象中的所有的属性

    Vue3.0 新特性

    Composition API 与 React.js 中 Hooks 的异同点

    a. React.js 中的 Hooks 基本使用 React Hooks 允许你 "勾入" 诸如组件状态和副作用处理等 React 功能中。Hooks 只能 用在函数组件中,并允许我们在不需要创建类的情况下将状态、副作用处理和更多东西 带入组件中。 React 核心团队奉上的采纳策略是不反对类组件,所以你可以升级 React 版本、在新组 件中开始尝试 Hooks,并保持既有组件不做任何更改。 案例: useState 和 useEffect 是 React Hooks 中的一些例子,使得函数组件中也能增加状态和 运行副作用。 我们也可以自定义一个 Hooks,它打开了代码复用性和扩展性的新大门。

    b. Vue Composition API 基本使用 Vue Composition API 围绕一个新的组件选项 setup 而创建。setup() 为 Vue 组件提供了 状态、计算值、watcher 和生命周期钩子。 并没有让原来的 API(Options-based API)消失。允许开发者 结合使用新旧两种 API (向下兼容)。

    c. 原理 React hook 底层是基于链表实现,调用的条件是每次组件被 render 的时候都会顺序执行 所有的 hooks。 Vue hook 只会被注册调用一次,Vue 能避开这些麻烦的问题,原因在于它对数据的响 应是基于 proxy 的,对数据直接代理观察。(这种场景下,只要任何一个更改 data 的地 方,相关的 function 或者 template 都会被重新计算,因此避开了 React 可能遇到的性能 上的问题)。 React 中,数据更改的时候,会导致重新 render,重新 render 又会重新把 hooks 重新注 册一次,所以 React 复杂程度会高一些。 m

    你都做过哪些Vue的性能优化?

    编码阶段
    尽量减少data中的数据及层次结构,data中的数据都会增加getter和setter,会收集对应的watcher v-if和v-for不能连用 如果需要使用v-for给每项元素绑定事件时使用事件代理 SPA 页面采用keep-alive缓存组件 在更多的情况下,使用v-if替代v-show key保证唯一 使用路由懒加载、异步组件 防抖、节流 第三方模块按需导入 长列表滚动到可视区域动态加载 图片懒加载
    SEO优化
    预渲染 服务端渲染SSR 打包优化 压缩代码 Tree Shaking/Scope Hoisting 使用cdn加载第三方模块 多线程打包happypack splitChunks抽离公共文件 sourceMap优化
    用户体验
    骨架屏 PWA 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。

    vue与React 的比较

    相同点: 1.都是组件化开发和虚拟DOM(Virtual Dom) 2.都支持通过props进行父子组件间数据通信 3.都支持数据驱动视图,不直接操作DOM,更新状态数据界面就自动更新 4.都支持服务端渲染 5.都支持native的方案,React的 React Native, Vue 的Weex

    不同点: 1.数据绑定:vue实现了数据的双向绑定,react的数据流动是单向的 2.组件的写法不一样,React推荐的是JSX语法,也就是把HTML和CSS都写进JavaScript,即"all in js";vue推荐的做法是webpack+vue+loader的单文件组件格式,即html,css,js写在同一个文件中; 3.数据状态管理不同,state对象在react应用中是不可变的,需要使用setState方法更新状态;在vue中state对象不是必须的,数据由data属性在vue对象中管理 4.Virtual Dom不一样,vue会跟踪每个组件的依赖关系,不需要重新渲染整个组件树; 而对于react而言,每当应用的状态改变时,全部的组件都会被渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制 5.React严格上只针对MVC的View层,Vue则是MVVM模式

    React

    react中父子组件传值

    使用公共组件进行状态提升

    react中父子组件中参数互传,子传父是先在父组件上绑定属性设置为一个函数,当子组件需要给父组件传值的时候,则通过props调用该函数将参数传入到该函数当中,此时就可以在父组件中的函数中接收到该参数了,这个参数则为子组件传过来的值

    父传子是在父组件中直接绑定一个正常的属性,这个属性就是指具体的值,在子组件中,用props就可以获取到这个值

    任意组件间通信

    1.可以new一个 Vue 的 EventBus,进行事件监听,一边执行监听,一边执行新增 VUE的eventBus 就是发布订阅模式,是可以在React中使用的;

    2.使用pubsub-js

    3.redux

    setState 既存在异步情况也存在同步情况

    1.异步情况 在React事件当中是异步操作

    import React,{ Component } from "react";
    class Count extends Component{
        constructor(props){
            super(props);
            this.state = {
                count:0
            }
        }
    
    <span class="hljs-title function_">render</span>(<span class="hljs-params"></span>){
        <span class="hljs-keyword">return</span> (
            <span class="xml"><span class="hljs-tag">&#x3C;></span>
                <span class="hljs-tag">&#x3C;<span class="hljs-name">p</span>></span>count:{this.state.count}<span class="hljs-tag">&#x3C;/<span class="hljs-name">p</span>></span>
                <span class="hljs-tag">&#x3C;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{this.btnAction}</span>></span>增加<span class="hljs-tag">&#x3C;/<span class="hljs-name">button</span>></span>
            <span class="hljs-tag">&#x3C;/></span></span>
        )
    }
    
    btnAction = <span class="hljs-function">()=></span>{
        <span class="hljs-comment">//不能直接修改state,需要通过setState进行修改</span>
        <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">setState</span>({
            <span class="hljs-attr">count</span>: <span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">count</span> + <span class="hljs-number">1</span>
        });
        <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">count</span>);
    }
    

    }

    export default Count;

    复制代码

    2.同步情况 如果是在setTimeout事件或者自定义的dom事件中,都是同步的

    import React,{ Component } from "react";
    class Count extends Component{
        constructor(props){
            super(props);
            this.state = {
                count:0
            }
        }
    
    <span class="hljs-title function_">render</span>(<span class="hljs-params"></span>){
        <span class="hljs-keyword">return</span> (
            <span class="xml"><span class="hljs-tag">&#x3C;></span>
                <span class="hljs-tag">&#x3C;<span class="hljs-name">p</span>></span>count:{this.state.count}<span class="hljs-tag">&#x3C;/<span class="hljs-name">p</span>></span>
                <span class="hljs-tag">&#x3C;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{this.btnAction}</span>></span>增加<span class="hljs-tag">&#x3C;/<span class="hljs-name">button</span>></span>
            <span class="hljs-tag">&#x3C;/></span></span>
        )
    }
    
    btnAction = <span class="hljs-function">()=></span>{
        <span class="hljs-comment">//不能直接修改state,需要通过setState进行修改</span>
        <span class="hljs-comment">//同步</span>
        <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">()=></span>{
            <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">setState</span>({
                <span class="hljs-attr">count</span>: <span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">count</span> + <span class="hljs-number">1</span>
            });
            <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">count</span>);
        })
    }
    

    }

    export default Count;

    复制代码

    3.同步情况 自定义dom事件

    import React,{ Component } from "react";
    class Count extends Component{
        constructor(props){
            super(props);
            this.state = {
                count:0
            }
        }
    
    <span class="hljs-title function_">render</span>(<span class="hljs-params"></span>){
        <span class="hljs-keyword">return</span> (
            <span class="xml"><span class="hljs-tag">&#x3C;></span>
                <span class="hljs-tag">&#x3C;<span class="hljs-name">p</span>></span>count:{this.state.count}<span class="hljs-tag">&#x3C;/<span class="hljs-name">p</span>></span>
                <span class="hljs-tag">&#x3C;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"btn"</span>></span>绑定点击事件<span class="hljs-tag">&#x3C;/<span class="hljs-name">button</span>></span>
            <span class="hljs-tag">&#x3C;/></span></span>
        )
    }
    
    <span class="hljs-title function_">componentDidMount</span>(<span class="hljs-params"></span>){
        <span class="hljs-comment">//自定义dom事件,也是同步修改</span>
        <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">'#btn'</span>).<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'click'</span>,<span class="hljs-function">()=></span>{
            <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">setState</span>({
                <span class="hljs-attr">count</span>: <span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">count</span> + <span class="hljs-number">1</span>
            });
            <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">count</span>);
        });
    }
    

    }

    export default Count;

    复制代码

    多次的异步setState,更新前会进行合并

    import React,{ Component } from "react";
    class Count extends Component{
        constructor(props){
            super(props);
            this.state = {
                count:0
            }
        }
    
    <span class="hljs-title function_">render</span>(<span class="hljs-params"></span>){
        <span class="hljs-keyword">return</span> (
            <span class="xml"><span class="hljs-tag">&#x3C;></span>
                <span class="hljs-tag">&#x3C;<span class="hljs-name">p</span>></span>count:{this.state.count}<span class="hljs-tag">&#x3C;/<span class="hljs-name">p</span>></span>
                <span class="hljs-tag">&#x3C;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{this.btnAction}</span>></span>增加<span class="hljs-tag">&#x3C;/<span class="hljs-name">button</span>></span>
            <span class="hljs-tag">&#x3C;/></span></span>
        )
    }
    
    btnAction = <span class="hljs-function">()=></span>{
        <span class="hljs-comment">//不能直接修改state,需要通过setState进行修改</span>
        <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">setState</span>({
            <span class="hljs-attr">message</span>:<span class="hljs-string">'hi'</span>,
            <span class="hljs-attr">count</span>: <span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">count</span> + <span class="hljs-number">1</span>
        },<span class="hljs-function">()=></span>{
            <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'a:'</span>,<span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">count</span>);
        });
        <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">setState</span>({
            <span class="hljs-attr">count</span>: <span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">count</span> + <span class="hljs-number">1</span>
        },<span class="hljs-function">()=></span>{
            <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'b:'</span>,<span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">count</span>);
        });
        <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">setState</span>({
            <span class="hljs-attr">count</span>: <span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">count</span> + <span class="hljs-number">1</span>
        },<span class="hljs-function">()=></span>{
            <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'c:'</span>,<span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">count</span>);
        });
        <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">setState</span>({
            <span class="hljs-attr">count</span>: <span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">count</span> + <span class="hljs-number">1</span>
        },<span class="hljs-function">()=></span>{
            <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'d:'</span>,<span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">count</span>);
        });
        <span class="hljs-comment">//会输出:a:2 b:2 c:2 d:2</span>
    }
    

    }

    export default Count;
    复制代码

    多次的异步setState,更新前不进行合并

    btnAction = ()=>{
            //不能直接修改state,需要通过setState进行修改
            this.setState((preState,props)=>{
                return {
                    count: preState.count + 1
                }
            },()=>{
                console.log('a:',this.state.count);
            });
            this.setState((preState,props)=>{
                return {
                    count: preState.count + 1
                }
            },()=>{
                console.log('b:',this.state.count);
            });
            this.setState((preState,props)=>{
                return {
                    count: preState.count + 1
                }
            },()=>{
                console.log('c:',this.state.count);
            });
            this.setState((preState,props)=>{
                return {
                    count: preState.count + 1
                }
            },()=>{
                console.log('d:',this.state.count);
            });
    
        <span class="hljs-comment">//会输出:a:1 b:2 c:3 d:4</span>
    }
    

    复制代码

    生命周期

    安装
    当组件的实例被创建并插入到 DOM 中时,这些方法按以下顺序调用:
    

    constructor()
    static getDerivedStateFromProps()
    render()
    componentDidMount()

    更新中
    更新可能由道具或状态的更改引起。当重新渲染组件时,这些方法按以下顺序调用:

    static getDerivedStateFromProps()
    shouldComponentUpdate()
    render()
    getSnapshotBeforeUpdate()
    componentDidUpdate()

    卸载
    当组件从 DOM 中移除时调用此方法:

    componentWillUnmount()

    复制代码

    Portals

    Portals 提供了一种一流的方式来将子组件渲染到存在于父组件的 DOM 层次结构之外的 DOM 节点中。结构不受外界的控制的情况下就可以使用portals进行创建

    异步组件

    // 异步懒加载
    const Box = lazy(()=>import('./components/Box'));
    // 使用组件的时候要用suspense进行包裹
    <Suspense fallback={<div>loading...</div>}>
        {show && <Box/>}
    </Suspense>
    复制代码

    immutable.js

    immutable内部提供的所有数据类型,对其数据进行任意操作,操作得到的结果是修改后的值 并且修改后的值是一个新的对象,原来的对象没有发生任何变化。 immutable.js文档

    github.com/immutable-j…

    学习文档

    rhadow.github.io/2015/05/10/…

    const map1 = Map({a:1,b:2,c:3});
    const map2 = map1.set('b',50);
    console.log(map1);
    console.log(map2);
    复制代码

    构建工具 && 工程化

    性能优化

    节流 && 防抖

    1,解释 节流:事件触发后,规定时间内,事件处理函数不能再次被调用。也就是说在规定的时间内,函数只能被调用一次,且是最先被触发调用的那次。

    防抖:多次触发事件,事件处理函数只能执行一次,并且是在触发操作结束时执行。也就是说,当一个事件被触发准备执行事件函数前,会等待一定的时间(这时间是码农自己去定义的,比如 1 秒),如果没有再次被触发,那么就执行,如果被触发了,那就本次作废,重新从新触发的时间开始计算,并再次等待 1 秒,直到能最终执行!

    2,使用场景: 节流:滚动加载更多、搜索框搜的索联想功能、高频点击、表单重复提交…… 防抖:搜索框搜索输入,并在输入完以后自动搜索、手机号,邮箱验证输入检测、窗口大小 resize 变化后,再重新渲染。

    几条关于优化渲染效率的建议

    • 合法地去书写HTML和CSS ,且不要忘了文档编码类型。
    • 样式文件应当在head标签中,而脚本文件在body结束前,这样可以防止阻塞的方式。
    • 简化并优化CSS选择器,尽量将嵌套层减少到最小。
    • DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作。
    • 如果某个样式是通过重排得到的,那么最好缓存结果。避免下一次用到的时候,浏览器又要重排。
    • 不要一条条地改变样式,而要通过改变class,或者csstext属性,一次性地改变样式。
    • 尽量用transform来做形变和位移
    • 尽量使用离线DOM,而不是真实的网页DOM,来改变元素样式。比如,操作Document Fragment对象,完成后再把这个对象加入DOM。再比如,使用cloneNode()方法,在克隆的节点上进行操作,然后再用克隆的节点替换原始节点。
    • 先将元素设为display: none(需要1次重排和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要1次重排和重绘)。这样一来,你就用两次重新渲染,取代了可能高达100次的重新渲染。
    • position属性为absolutefixed的元素,重排的开销会比较小,因为不用考虑它对其他元素的影响。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值