2025前端社招最新面试题汇总- 浏览器篇

183 阅读38分钟

1. url过程

1、首先,在浏览器地址栏中输入url(浏览器首先会判断输入的内容是一个URL还是搜索关键字。

如果是URL,会把不完整的URL合成完整的URL。)

2、浏览器先查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有,会直接在屏幕中显示页面内容。若没有,则跳到第三步操作。

3、在发送http请求前,需要域名解析(DNS解析),解析获取相应的IP地址。

4、浏览器向服务器发起tcp连接,与服务器建立tcp三次握手。

5、握手成功后,浏览器向服务器发送http请求,请求数据包。

6、服务器处理收到的请求,将数据返回至浏览器

7、浏览器收到HTTP响应

8、读取页面内容,浏览器渲染,解析html源码

9、生成Dom树、解析css样式、js交互

10、客户端和服务器交互

11、ajax查询

2. TCP

2.1. TCP的三次握手

客户机——>服务器

我想要和你建立连接,你同意吗?(SYN=1),随机初始序号:(seq=x)

服务器——>客户机

我同意和你建立连接(ACK=1),随机初始序号:(seq=y);我也想和你建立连接,

你同意吗?(SYN=1) 确认号:希望客户端发过来的第一个数据字节的序号(ack=x+1)

客户机——>服务器

我同意和你建立连接。(ACK=1)确认号:希望服务端发过来的第一个数据字节的序号(ack=y+1)

(seq=x+1):报文段可以携带信息

.  其实,在进行第二次握手时(即服务器向客户机进行应答时),可以看作时发了两次包,先回答客户机的服 务请求(ACK=1,ack=x+1),然后再向客户机发出请求(SYN=1,seq=y)

2.2. 四次挥手

3. 浏览器安全

3.1. 常网站攻击方式

有XSS和CSRF、SQL注入、iframe滥用

1.XSS:(跨站脚本攻击)

简单描述:

xss攻击是跨站脚本攻击,例如在表单中提交含有可执行的javascript的内容文本,如果服务器端没有过滤或转义这些脚本,而这些脚本由通过内容的形式发布到了页面上,这个时候如果有其他用户访问这个网页,那么浏览器就会执行这些脚本,从而被攻击,从而获取用户的cookie等信息

防御手段:

  • 设置HttpOnly,浏览器禁止页面的JS访问带有HttpOnly属性的Cookie
  • 对输入内容做格式检查,类似“白名单”,可以让一些基于特殊字符的攻击失效。在客户端JS和服务器端代码中实现相同的输入检查(服务器端必须有)
  • 在变量输出到html页面时,可以使用编码或转义的方式来防御XSS攻击

2.CSRF(跨站请求伪造)

简单描述:

CSRF(Cross Site Request Forgery),中文是跨站点请求伪造。CSRF攻击者在用户已经登录目标网站之后,诱使用户访问一个攻击页面,利用目标网站对用户的信任,以用户身份在攻击页面对目标网站发起伪造用户操作的请求,达到攻击目的。

攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账…造成的问题包括:个人隐私泄露以及财产安全。
防御手段:

  • 验证HTTP Referer字段。Referer字段记录了HTTP请求的来源地址,从银行A网站发出来的请求会带有A网站的地址,从携带CSRF地址发出的请求会携带B网站的地址,我们只需在每个敏感请求验证Referer值,如果是来自A网站的通过,否则不通过。但是这种方法把安全寄托于浏览器,并不完全安全,在某些浏览器上,还是可以通过篡改 Referer 从而进行CSRF攻击。而且,在某些用户禁用Referer的情况下,服务器会一直拒绝客户的请求。
  • Anti CSRF Token:在请求地址中添加token 并验证。更多的是生成一个随机的token,在用户提交数据的同时提交这个token,服务器端比对后如果不正确,则拒绝执行操作。在用户登录之后,产生token 并放入session中,在每次请求时把token从session中拿出来,以参数的形式加入请求,在服务器端建立拦截器验证该token,token则通过,否则拒绝。但是这种方法也是有安全问题的,在某些网站支持用户发表链接的,那么黑客在该网站发布自己的个人网站地址,系统也会为这个地址后加上token,则黑客可以在自己的网站上得到这个token参数,从而发动CSRF攻击。
  • 请求时附带验证信息,比如验证码或者 token

3.SQL注入

SQL注入攻击,攻击者在提交表单的时候,在表单上面填写相关的sql语句,而系统把这些字段当成普通的变量发送给服务器端进行sql查询,则,由攻击者填写的sql会拼接在系统的sql语句上,从而进行数据库的某些操作。

【防御】

  • 表单过滤,验证表单提交的合法性,对一些特殊字符进行转义处理

  • 数据库权限最小化

  • 查询语句使用数据库提供的参数化查询接口,不要直接拼接SQL

  • 防止SQL注入最好的方法是使用预编译语句

  1. iframe的滥⽤:

iframe中的内容是由第三⽅来提供的,默认情况下他们不受控制,他们可以在iframe中运⾏JavaScirpt脚本、Flash插件、弹出对话框等等,这可能会破坏前端⽤户体验;

【防御】

  • iframe 中的内容进行严格审查和控制,确保其来源可信,不会对前端用户体验造成破坏。
  • 限制 iframe 中的脚本执行权限,避免其运行可能存在风险的 JavaScript 代码。
  • 仅在必要时使用 iframe ,避免过度依赖。

3.2. 网络劫持有哪几种,如何防范?

⽹络劫持分为两种:

(1)DNS劫持: (输⼊京东被强制跳转到淘宝这就属于dns劫持)

  • DNS强制解析: 通过修改运营商的本地DNS记录,来引导⽤户流量到缓存服务器
  • 302跳转的⽅式: 通过监控⽹络出⼝的流量,分析判断哪些内容是可以进⾏劫持处理的,再对劫持的内存发起302跳转的回复,引导⽤户获取内容

(2)HTTP劫持: (访问⾕歌但是⼀直有贪玩蓝⽉的⼴告),由于http明⽂传输,运营商会修改你的http响应内容(即加⼴告)

DNS劫持由于涉嫌违法,已经被监管起来,现在很少会有DNS劫持,⽽http劫持依然⾮常盛⾏,最有效的办法就是全站HTTPS,将HTTP加密,这使得运营商⽆法获取明⽂,就⽆法劫持你的响应内容。

4. 浏览器常见的状态码

各类别常见状态码:

  • 2xx (3种)
    • 200 OK:表示从客户端发送给服务器的请求被正常处理并返回;
    • 204 No Content:表示客户端发送给服务端的请求得到了成功处理,但在返回的响应报文中不含实体的主体部分(没有资源可以返回);
    • 206 Patial Content:表示客户端进行了范围请求,并且服务器成功执行了这部分的GET请求,响应报文中包含由Content-Range指定范围的实体内容。
  • 3xx (5种)

301与302的区别:前者是永久移动,后者是临时移动(之后可能还会更改URL)

302与303的区别:后者明确表示客户端应当采用GET方式获取资源

    • 301 Moved Permanently:永久性重定向,表示请求的资源被分配了新的URL,之后应使用更改的URL;
    • 302 Found:临时性重定向,表示请求的资源被分配了新的URL,希望本次访问使用新的URL;
    • 303 See Other:表示请求的资源被分配了新的URL,应使用GET方法定向获取请求的资源;
    • 304 Not Modified:自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容 第一次请求:浏览器将资源缓存到本地,服务器在响应头中返回一个ETag为xxx的值,可以标记这个资源第二次请求:浏览器在请求头中If-None-Match属性告诉服务器我已经有了一个ETag为xxx的缓存,如果服务器上的资源没有变化,也就是服务器上的ETag还是xxx的话,则返回304.告诉浏览器缓存有效,资源未改变。
    • 307 Temporary Redirect:临时重定向,与303有着相同的含义,307会遵照浏览器标准不会从POST变成GET;(不同浏览器可能会出现不同的情况);
  • 4xx (4种)
    • 400 Bad Request:表示请求报文中存在语法错误;
    • 401 Unauthorized:未经许可,需要通过HTTP认证;
    • 403 Forbidden:服务器拒绝该次访问(访问权限出现问题)
    • 404 Not Found:表示服务器上找不到请求的资源
    • 找到请求的资源,除此之外,也可以在服务器拒绝请求但不想给拒绝原因时使用;
  • 5xx (2种)
    • 500 Inter Server Error:表示服务器在执行请求时发生了错误,也有可能是web应用存在的bug或某些临时的错误时;
    • 503 Server Unavailable:表示服务器暂时处于超负载或正在进行停机维护,无法处理请求;

5. TCP和UDP

(1)TCP是面向连接的,UDP是无连接的即发送数据前不需要先建立链接。
(2)TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。 并且因为tcp可靠,面向连接,不会丢失数据因此适合大数据量的交换。

(3)TCP是面向字节流,UDP面向报文,并且网络出现拥塞不会使得发送速率降低(因此会出现丢包,对实时的应用比如IP电话和视频会议等)。

(4)TCP只能是1对1的,UDP支持1对1,1对多。

(5)TCP的首部较大为20字节,而UDP只有8字节。

(6)TCP是面向连接的可靠性传输,而UDP是不可靠的。

6. http 和 https 区别

  1. http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
  2. HTTPS之所以比HTTP更安全,主要是通过以下几种机制来保证其安全性:
    1. 加密传输:HTTPS使用SSL/TLS协议对HTTP报文进行加密,使得敏感数据在网络传输过程中不容易被窃听和篡改。这种加密过程结合了对称加密和非对称加密,确保数据的保密性和完整性。
    2. 身份验证:HTTPS通过数字证书进行身份验证,确保通信双方的真实性。在建立HTTPS连接时,服务器会提供数字证书来证明自己的身份。如果验证通过,客户端就可以信任服务器,并继续与其进行安全的数据传输。这有效防止了被恶意伪装的服务器攻击。
    3. 数据完整性保护:在传输数据之前,HTTPS会对数据进行加密,并使用消息摘要(hash)算法生成一个摘要值。在数据到达接收端后,接收端会使用相同的算法对接收到的数据进行摘要计算,并与发送端的摘要值进行比较。如果两者一致,说明数据在传输过程中没有被篡改。如果不一致,通信双方应重新进行验证或中断连接。

这些机制共同作用,使得HTTPS在数据传输、身份验证和数据完整性保护等方面都优于HTTP,从而提供了更高的安全性。因此,对于涉及用户个人隐私和敏感信息的网站,如银行、电商等,使用HTTPS协议可以有效保护用户的账号、密码等信息,提高用户信息的安全性。

6.1. https协议的工作原理(SSL层原理)

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

6.2. http1.0、http1.1、http2 的区别

  1. 在http1.0h协议中,一个Request一个Response,这次http请求就结束了
  2. 在http1.1中进行了改进,WebSocket有一个connection:Keep-alive,也就是说,在一个http连接中,可以发送多个Request,接收多个Response。默认长链接, 关闭: connection:close
  3. http2.0中:

3.1.允许多路复用:多路复用允许同时通过单一的HTTP/2连接发送多重请求-响应信息。改善了:在http1.1中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制(连接数量),超过限制会被阻塞。
3.2.二进制分帧:HTTP2.0会将所有的传输信息分割为更小的信息或者帧,并对他们进行二进制编码
3.3.首部压缩
3.4.服务器端推送

7. 1.GET和POST有什么区别?

GET和POST是HTTP协议中的两种请求方法,它们之间存在一些重要的区别,主要体现在以下方面:

  1. 请求参数的位置:GET请求的请求参数会附加在URL之后,参数之间使用"&"连接,多个参数将会造成URL长度增加。而POST请求的请求参数则包含在请求体中,不会在URL中显示。
  2. 请求长度的限制:由于GET请求的参数附加在URL之后,因此其请求长度受限于浏览器对URL长度的限制(谷歌推荐不超过2000)。而POST请求则没有这个问题,请求参数包含在请求体中,因此可以传输大量数据。
  3. 安全性:GET请求的参数会暴露在URL中,因此不能用于传输敏感信息,如密码等。而POST请求的参数在请求体中,不会在URL中显示,相对更加安全。然而,这并不意味着POST请求就一定比GET请求更安全,因为安全性还需要依赖于其他因素,如SSL/TLS加密等。
  4. 传输数据类型:GET只能传输字符串,POST可以传输多种类型数据.因为GET请求是挂载在URL上的,所以传输的数据类型只能是字符串,但是POST请求可以传输除字符串以外的数据,比如:视频,文档等。
  5. 幂等性:GET请求是幂等的,即多次执行同一GET请求,服务器将返回相同的结果。而POST请求则不是幂等的,因为每次提交都会创建新的资源。
  6. 缓存:GET请求可以被缓存,而POST请求则不会,除非在响应头中包含适当的Cache-Control或Expires字段。
  7. 后退/刷新按钮的影响:GET请求可以被浏览器缓存,因此可以通过点击后退按钮或刷新按钮来重复执行。而POST请求则不会,因为这些操作对POST请求没有实际意义。

总的来说,GET和POST请求各有其特点和适用场景。GET请求通常用于请求数据,而POST请求则常用于提交数据。在选择使用哪种请求方法时,需要考虑到安全性、请求长度、幂等性等因素。

8. 进程与线程

8.1. 进程与线程的概念

进程(process)和线程(thread)是操作系统的基本概念。

进程是 CPU 资源分配的最小单位(是能拥有资源和独立运行的最小单位)。一个应用程序

线程是 CPU 调度的最小单位(是建立在进程基础上的一次程序运行单位)。

在一个进程内部,要同时做多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程
进程和线程之间的关系有以下四个特点:

(1)进程中的任意一线程执行出错,都会导致整个进程的崩溃。

(2)线程之间共享进程中的数据。

(3)当一个进程关闭之后,操作系统会回收进程所占用的内存。

(4)进程之间的内容相互隔离。

8.2. Chrome 浏览器

包含以下进程:

  • 浏览器进程: 主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。负责浏览器的TAB的前进、后退、地址栏、书签栏的工作和处理浏览器的一些不可见的底层操作,比如网络请求和文件访问。
  • 渲染进程( 浏览器内核 ): 页面渲染,脚本执行,事件处理等。默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。 所以单个 tab 页面崩溃不会影响到整个浏览器。
    • GUI 渲染线程
      • 负责渲染浏览器界面,解析 HTML,CSS,构建 DOM 树和 RenderObject 树,布局和绘制等。
      • 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。
      • 注意,GUI 渲染线程与 JS 引擎线程是互斥的,当 JS 引擎执行时 GUI 线程会被挂起(相当于被冻结了),GUI 更新会被保存在一个队列中等到 JS 引擎空闲时立即被执行。
    • JS 引擎线程
      • Javascript 引擎,也称为 JS 内核,负责处理 Javascript 脚本程序。(例如 V8 引擎)
      • JS 引擎线程负责解析 Javascript 脚本,运行代码。
      • JS 引擎一直等待着任务队列中任务的到来,然后加以处理,一个 Tab 页(renderer 进程)中无论什么时候都只有一个 JS 线程在运行 JS 程序。
      • 注意,GUI 渲染线程与 JS 引擎线程是互斥的,所以如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
    • 事件触发线程
      • 归属于浏览器而不是 JS 引擎,用来控制事件循环(可以理解,JS 引擎自己都忙不过来,需要浏览器另开线程协助)
      • 当 JS 引擎执行代码块如 setTimeOut 时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX 异步请求等),会将对应任务添加到事件线程中
      • 当事件被触发时,该线程会把事件添加到待处理队列的队尾,等待 JS 引擎的处理
    • 定时触发器线程
      • 传说中的 setInterval 与 setTimeout 所在线程
      • 浏览器定时计数器并不是由 JavaScript 引擎计数的,(因为 JavaScript 引擎是单线程的, 如果处于阻塞状态就会影响记计时的准确)
      • 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待 JS 引擎空闲后执行)
      • 注意,W3C 在 HTML 标准中规定,规定要求 setTimeout 中低于 4ms 的时间间隔算为 4ms。
    • 异步请求线程
      • 在 XMLHttpRequest 在连接后是通过浏览器新开一个线程请求。
      • 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由 JavaScript 引擎执行。
  • GPU 进程:负责处理整个应用程序的GPU任务, 用于 3D 绘制等
  • 网络进程:主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
  • 插件进程:负责控制网页使用到的插件,所以第三方插件崩溃也不会影响到整个浏览器。。

所以,打开一个网页,最少需要四个进程:1 个网络进程、1 个浏览器进程、1 个 GPU 进程以及 1 个渲染进程。如果打开的页面有运行插件的话,还需要再加上 1 个插件进程。

8.3. tab页面通信

每一个tab都是独立的进程,互不影响,互相隔离。本质上都是通过中介者模式来实现的。

  • websocket: 因为 websocket 协议可以实现服务器推送,标签页通过向服务器发送数据,然后由服务器向其他标签页推送转发。
  • localStorage: 在一个标签页对 localStorage 的变化事件进行监听,然后当另一个标签页修改数据的时候,就可以通过这个监听事件来获取到数据。这个时候 localStorage 对象就是充当的中介者的角色。
  • ShareWorker : shareWorker 会在页面存在的生命周期内创建一个唯一的线程: 标签页间通过共享一个线程,然后通过这个共享的线程来实现数据的交换。
  • postMessage(data,origin): 如果我们能够获得对应标签页的引用,就可以使用postMessage 方法,进行通信。

8.4. 浏览器内核

五大主流浏览器,四大浏览器内核

1、IE浏览器内核:Trident内核,也是俗称的IE内核;
2、Chrome浏览器内核:统称为Chromium内核或Chrome内核 ,以前是Webkit内核,现在是Blink内核
3、Firefox浏览器内核:Gecko内核,俗称Firefox内核;
4、Safari浏览器内核:Webkit内核;
5、Opera浏览器内核:最初是自己的Presto内核,后来是Webkit,现在是Blink内核;
6、360浏览器、猎豹浏览器内核:IE+Chrome双内核;
7、搜狗、遨游、QQ浏览器内核:Trident(兼容模式)+Webkit(高速模式);
8、百度浏览器、世界之窗内核:IE内核;
9、2345浏览器内核:以前是IE内核,现在也是IE+Chrome双内核;

9. 浏览器阻塞、渲染、回流和重绘

9.1. 浏览器渲染过程:

1.处理 HTML 并构建 DOM 树
2.处理 CSS 构建 CSS 规则树(如图所示:DOM 和 CSSOM 通常是并行构建的,所以 CSS 加载不会阻塞 DOM 的解析。但是会影响整体dom的渲染)
3.将 DOM 树 与 CSS 规则树 合并成一个渲染树(render tree)
4.根据渲染树来布局,计算每个节点的位置
5.调用 GPU 绘制,合成图层,显示在屏幕上

9.2. js 与css 的阻塞

juejin.cn/post/713754…

  • CSS不会阻塞DOM解析,但是会阻塞DOM渲染,严谨一点则是CSS会阻塞render tree的生成,进而会阻塞DOM的渲染
  • JS会阻塞DOM解析
    • 使用defer或async避免
  • CSS会阻塞JS的执行
    • 如果js脚本中是获取dom元素的css样式属性,为了获取到最新的正确的样式,需要所有的css加载完成,否则获取的样式可能是错误的或者不是最新的
    • 要等到js脚本前面的css加载完成,js才能再执行
    • 所以一般cdn引入第三方库的script一般放在link之前
  • 浏览器遇到<script>标签且没有deferasync属性时会触发页面渲染
    • 浏览器解析DOM时,虽然是一行一行地向下执行,但是它会预先加载具有引用标记的外部资源(例如带有src标记的script标签)
    • 在解析到此标签的时候,则无需再去加载,直接运行,以此提高运行效率
    • 浏览器无法预先知道脚本的具体内容,因此碰到script标签时,只好先渲染一次页面,确保script脚本内能获取到dom最新的样式
    • 如果在决定渲染页面时,还有尚未加载完成的css文件,只能等待其加载完成再去渲染页面
  • Body内部的外链CSS较为特殊,会形成FOUC(样式闪烁)现象,请慎用

9.3. 回流/重排(Reflow)和重绘(Repaint)

(回流必定会发生重绘,重绘不一定会引发回流。)

  • 回流 —— 布局或者几何属性需要改变
    • 添加或删除可见的DOM元素
    • 元素的位置发生变化
    • 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
    • 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。
    • 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
    • 浏览器的优化机制
    • 读取属性引起回流: 由于每次回流都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化回流过程。浏览器会在过了一段时间或者操作达到了一个阈值,才清空队列。但是!当你获取布局信息的操作的时候,为了保证准确,会强制队列刷新,比如当你访问以下属性或者使用以下方法:
clientWidth、clientHeight、clientTop、clientLeft、offsetWidth、offsetHeight、offsetTop、
offsetLeft、scrollWidth、scrollHeight、scrollTop、scrollLeft
scrollIntoView()、scrollIntoViewIfNeeded()、getComputedStyle()、getBoundingClientRect()
scrollTo()
  • 重绘 —— 当节点需要更改外观而不会影响布局的,比如改变 color

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility 等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

浏览器针对页面的回流与重绘,进行了自身的优化——渲染队列

浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。

将多个读操作(或者写操作)放在一起,就会等所有的读操作进入队列之后执行,这样,原本应该是触发多次回流,变成了只触发一次回流。

减少重绘和回流

  • 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)

  • 把 DOM 离线后修改,比如:先把 DOM 给 display:none (有一次 Reflow),然后你修改 100 次,然后再把它显示出来

  • 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局

  • 对于复杂动画效果,使用绝对定位让其脱离文档流( position 属性为 absolute 或 fixed

  • 避免使用 CSS 表达式(例如:calc())。

  • 不要经常访问会引起浏览器缓存队列立刻刷新的属性(上述那些浏览器会立刻清空队列的属性)。如果确实要访问,利用缓存

10. 浏览器渲染优化:

10.1. 针对js文件

JavaScript既会阻塞HTML的解析,也会阻塞CSS的解析。因此我们可以对JavaScript的加载方式进行改变,来进行优化:

其中蓝色代表js脚本网络加载时间,红色代表js脚本执行时间,绿色代表html解析。

  • defer 属性: 使脚本异步加载,然后在文档解析完成后再执行这个脚本文件,使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
  • async 属性: 使脚本异步加载,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
  • 动态创建 DOM 方式: 对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。
window.onload = function() {
    console.log('整个页面(包括所有资源)已完全加载');
    // 在这里执行你的代码
};
  • 使用 setTimeout 延迟方法: 设置一个定时器来延迟加载js脚本文件

  • 让 JS 最后加载: 将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。

10.1.1. 预加载 preload、prefetch有什么区别

  1. preload:以高优先级为当前页面加载资源;
  2. prefetch:以低优先级为后面的页面加载未来需要的资源,只会在空闲时才去加载;

无论是preload还是prefetch,都只会加载,不会执行,如果预加载的资源被服务器设置了可以缓存cache-control那么会进入磁盘,反之只会被保存在内存中。

as表示资源类型

10.2. 针对css

针对CSS: 使用CSS有三种方式:使用 link、@import、内联样式

其中link和@import都是导入外部样式。它们之间的区别:

  • link:多个 <link> 标签可以同时并行加载,从而提高页面加载速度。支持预加载:可以使用 rel="preload" 属性来指示浏览器预加载样式表,进一步优化加载性能。
  • @import@import 会导致样式表的串行加载,一个样式表加载完毕后才会加载下一个样式表,可能会影响页面加载性能。也不支持预加载。
  • style:GUI直接渲染

外部样式如果长时间没有加载完毕,浏览器为了用户体验,会使用浏览器会默认样式,确保首次渲染的速度。所以CSS一般写在header中,让浏览器尽快发送请求去获取css样式。

所以,在开发过程中,导入外部样式使用link,而不用@import。如果css少,尽可能采用内嵌样式,直接写在style标签中。

link和@import的区别:

区别1:link是HTML标签,除了加载CSS外,还可以定义其他内容;@import属于CSS范畴,只能加载CSS。
区别2:link引用CSS时,可以并行加载;@import只能串行加载。
区别3:link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。
区别4:link支持使用Javascript控制DOM去改变样式;而@import不支持

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

@import url("styles.css");

10.3. 减少回流和重绘

参考 8.3

11. web项目性能优化

减少页面体积,提升网络加载:

1)静态资源压缩合并,如js代码合并,css代码合并,压缩图片等;

2)静态资源缓存;

3)使用 CDN 让资源加载更快;

优化页面渲染:

1)减少HTTP请求;

2)减少DOM操作,多个操作尽量合并在一起执行(DocumentFragment);

let wrapper = document.getElementsById('demo')
let fragment = document.createDocumentFragment()
for (let i = 0; i < 10; i++) {
  let p = document.createElement('p')
  p.innerText = `测试${i}`
  fragment.appendChild(p)
}
wrapper.appendChild(fragment)

3)懒加载(图片懒加载、下拉加载更多);

先只加载可视窗口区域的图片,当用户向下拖动滚动条时再继续加载后面的图片(也是只加载目前可视窗口区域内的图片)。

1.这样减少了加载时的线程数量,使可视区域内的图片也能够快速加载,优化了用户体验。

2.减少了同一时间发向服务器的请求数,服务器压力剧减。

 const imgs = document.querySelectorAll('img');
 let begin = 0;
 function lazyload(){
     for (let index = begin; index < imgs.length; index++) {
          const img = imgs[index];
          if(img.offsetTop < window.innerHeight + document.documentElement.scrollTop){
                console.log("scroll"+index+"到了")
                begin = index; //不去遍历已经加载了得图片
                img.src = img.getAttribute("imgPath")
          }
     }
}
    lazyload() //渲染首屏,先执行一次
//增加节流函数,防止每次滚动发都去加载
const throttle = (fn,delay) => {
  let start = Date.now();
  return function() {
    const ctx = this;
    const args = arguments;
    if (Date.now() - start > delay) {
      fn.apply(ctx,args);
      start = Date.now();
    }
  }
}
window.onscroll = throttle(50, lazyload) //事件回调是个闭包

4)使用 SSR 后端渲染,数据直接输出到 HTML 中,减少浏览器使用 JS 模板渲染页面 HTML 的时间(smarty、Vue SSR);

5)虚拟滚动

11.1. 性能指标

性能指标中文全称描述
FP首次绘制浏览器首次在屏幕上绘制像素的时间点,即页面开始显示内容的时间。
FCP首次有内容绘制页面首次绘制出任何文本、图像或其他可视元素的时间点,用户可以看到页面有一些可见的内容。
LCP最大内容绘制页面中最大的可见内容元素绘制完成并可见的时间点,通常是页面上最显眼的图像或文本块。
TTI可交互时间页面加载完成且用户可以与页面进行交互的时间点,主线程空闲且页面响应用户输入。
TBT总阻塞时间页面加载过程中,主线程被长时间任务(通常是 JavaScript 执行)阻塞的总时间。
CLS累计布局偏移页面加载过程中发生的意外布局变化的总量,可能导致用户在交互时误触或出现不良体验。
FID首次输入延迟用户首次与页面交互(如点击按钮)时,页面响应用户输入所需的时间。

11.2. 性能指标测量工具

lighthouse

Lighthouse生成的是一个报告,会给你的页面跑出一个分数来。 分别是页面性能(performance)、Progressive(渐进式 Web 应用)、Accessibility(可访问性)、Best Practices(最佳实践)、SEO 五项指标的跑分。甚至针对我们的性能问题给出了可行的建议、以及每一项优化操作预期会帮我们节省的时间。这份报告的可操作性是很强的——我们只需要对着 LightHouse 给出的建议,一条一条地去尝试,就可以看到自己的页面,在一秒一秒地变快。

Performance

lighthouse生成一个报告有些参数来源于performance,相对比lighthouse的分数和建议,performance用于记录和分析我们的应用在运行时的所有活动。它呈现的数据具有实时性、多维度的特点,可以帮助我们很好地定位性能问题。

12. 渐进增强(progressive enhancement)、优雅降级(graceful degradation)

//渐进增强
.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;
}

渐进增强(progressive enhancement):针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。

优雅降级(graceful degradation):一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。

区别:优雅降级是从复杂的现状开始,并试图减少用户体验的供给,而渐进增强则是从一个非常基础的、能够起作用的版本开始,并不断扩充,以适应未来环境的需要。

13. 浏览器缓存

13.1. 强缓存和协商缓存

所谓的浏览器缓存指的是浏览器将用户请求过的静态资源,存储到电脑本地磁盘中,当浏览器再次访问时,就可以直接从本地加载,不需要再去服务端请求了。良好的缓存策略可以降低资源的重复加载提高网页的整体加载速度

通常浏览器缓存策略分为两种:强缓存和协商缓存,一起配合使用

1)基本原理

  • (1)浏览器在加载资源时,根据请求头的expirescache-control判断是否命中强缓存,是则直接从缓存读取资源,不会发请求到服务器。
  • (2)如果没有命中强缓存,浏览器一定会发送一个请求到服务器,通过last-modifiedetag验证资源是否命中协商缓存,如果命中,服务器会将这个请求返回304,但是不会返回这个资源的数据,依然是从缓存中读取资源
  • (3)如果前面两者都没有命中,直接从服务器加载资源

2)相同点

如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源数据;

3)不同点

强缓存不发请求到服务器,协商缓存会发请求到服务器。

强缓存总结

  1. cache-control: max-age=xxxx,public
    客户端和代理服务器都可以缓存该资源;
    客户端在xxx秒的有效期内,如果有请求该资源的需求的话就直接读取缓存,statu code:200 ,如果用户做了刷新操作,就向服务器发起http请求
  2. cache-control: max-age=xxxx,private
    只让客户端可以缓存该资源;代理服务器不缓存
    客户端在xxx秒内直接读取缓存,statu code:200
  3. cache-control: max-age=xxxx,immutable
    客户端在xxx秒的有效期内,如果有请求该资源的需求的话就直接读取缓存,statu code:200 ,即使用户做了刷新操作,也不向服务器发起http请求
  4. cache-control: no-cache
    跳过设置强缓存,但是不妨碍设置协商缓存;一般如果你做了强缓存,只有在强缓存失效了才走协商缓存的,设置了no-cache就不会走强缓存了,每次请求都会询问服务端。
  5. cache-control: no-store
    不缓存,这个会让客户端、服务器都不缓存,也就没有所谓的强缓存、协商缓存了。

协商缓存

第一次请求:浏览器将资源缓存到本地,服务器在响应头中返回一个ETag为xxx的值,可以标记这个资源

第二次请求:浏览器在请求头中If-None-Match属性告诉服务器我已经有了一个ETag为xxx的缓存,如果服务器上的资源没有变化,也就是服务器上的ETag还是xxx的话,则返回304.告诉浏览器缓存有效,资源未改变。

13.2. 点击刷新按钮或者按 F5、按 Ctrl+F5 (强制刷新)、地址栏回车有什么区别

  • 点击刷新按钮或者按 F5: 浏览器直接对本地的缓存文件过期,直接协商缓存。但是会带上If-Modifed-Since,If-None-Match,这就意味着服务器会对文件检查新鲜度,返回结果可能是 304,也有可能是 200。
  • 用户按 Ctrl+F5(强制刷新): 浏览器不仅会对本地文件过期,而且不会带上 If-Modifed-Since,If-None-Match,相当于之前从来没有请求过,返回结果是 200。
  • 地址栏回车: 浏览器发起请求,按照正常流程,本地检查是否过期,然后服务器检查新鲜度,最后返回内容。

13.3. 如何强制用户刷新

  • 文件名哈希(推荐)或文件后面使用时间戳作为版本号

为静态文件(如 JS、CSS)添加唯一哈希值,确保每次更新后文件名不同,绕过缓存机制。

构建工具自动生成(如 Webpack、Vite、Rollup):

// Webpack 配置示例
output: {
  filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].js',
  }
}

// 输出效果
<script src="app.3a7b2c1.js"></script>
<!-- 更新后哈希值改变 -->
<script src="app.8d4e5f6.js"></script>
  • 禁用强缓存,使用协商缓存
Cache-Control: no-cache
  • 后端接口返回版本号

请求后端接口时,携带版本号信息,与当前浏览器保存的版本号信息比对,如果不同,则前端强制刷新。

13.4. 浏览器缓存和浏览器本地存储的区别

浏览器缓存:

  • 浏览器缓存是一种临时性的数据存储,用于提高网页加载速度。
  • 缓存的数据(包括HTML、CSS、js文件以及图片等资源。)存储在内存或磁盘中,当用户再次访问相同的资源时,浏览器会优先从缓存中读取数据,而不是从服务器重新请求。
  • 缓存的数据根据 HTTP 头部信息设置的过期时间来清除。涉及到强缓存和协商缓存。

本地存储:

  • 本地存储包括 localStorage 和 sessionStorage、session、cookie、indexdb 等
  • 数据存储在浏览器端,不会随页面刷新或浏览器关闭而消失。
  • localStorage 中的数据会一直保留,直到手动清除或浏览器清除缓存。
  • sessionStorage 中的数据会在浏览器关闭时自动清除。

14. 浏览器本地存储

14.1. cookies

Cookie是最早被提出来的本地存储方式,在此之前,服务端是无法判断网络中的两个请求是否是同一用户发起的,为解决这个问题,Cookie就出现了。Cookie的大小只有4kb,它是一种纯文本文件,每次发起HTTP请求都会携带Cookie。

属性:

  • expires/max-age 指定了 cookie 失效的时间,不设置则只在当前会话中生效。
  • domain 是域名、path是路径,domain 和 path 一起限制了 cookie 能够被哪些 url 访问。
  • secure 用于限制 Web 页面仅在 HTTPS 安全连接时,才可以发送 Cookie
  • HttpOnly 规定了这个 cookie 只能被服务器访问,不能使用 js 脚本访问。设置 HTTPOnly 属性可以防止客户端脚本通过 document.cookie 等方式访问 Cookie,有助于避免 XSS 攻击
  • SameSite: SameSite 属性可以让 Cookie 在跨站请求时不会被发送,从而可以阻止跨站请求伪造攻击(CSRF)。可以设置为strict/lax/none几个值

Cookie的特性:

  • Cookie一旦创建成功,名称就无法修改
  • Cookie是无法直接跨域名的,但是可以通过设置domain来在不同子域名下共享cookie,可用于 单点登录 或 肤色保留。
  • 每个域名下Cookie的数量不能超过20个,每个Cookie的大小不能超过4kb
  • Cookie在发送一个新的请求时候都会被发送过去

Cookie的使用场景:

  1. 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)

  2. 个性化设置(如用户自定义设置、主题等)

  3. 浏览器行为跟踪(如跟踪分析用户行为等)

14.2. Session

Session 是存储在服务器端的用户会话信息。每个用户会话都有一个唯一的 Session ID,服务器通过这个 ID 来识别不同的用户。客户端通过 Cookie 将 Session ID 发送给服务器,从而实现用户识别。

14.3. LocalStorage

LocalStorage是HTML5新引入的特性,由于有的时候我们存储的信息较大,Cookie就不能满足我们的需求,这时候LocalStorage就派上用场了。

LocalStorage的优点:

  • 在大小方面,LocalStorage的大小一般为5MB,可以储存更多的信息
  • LocalStorage是持久储存,并不会随着页面的关闭而消失,除非主动清理,不然会永久存在
  • 仅储存在本地,不像Cookie那样每次HTTP请求都会被携带

LocalStorage的缺点:

  • 存在浏览器兼容问题,IE8以下版本的浏览器不支持
  • 如果浏览器设置为隐私模式,那我们将无法读取到LocalStorage
  • LocalStorage受到同源策略的限制,即端口、协议、主机地址有任何一个不相同,都不会访问

LocalStorage的常用API:

// 保存数据到 localStorage
localStorage.setItem('key', 'value');

// 从 localStorage 获取数据
let data = localStorage.getItem('key');

// 从 localStorage 删除保存的数据
localStorage.removeItem('key');

// 从 localStorage 删除所有保存的数据
localStorage.clear();

// 获取某个索引的Key
localStorage.key(index)

LocalStorage的使用场景:

  • 有些网站有换肤的功能,这时候就可以将换肤的信息存储在本地的LocalStorage中,当需要换肤的时候,直接操作LocalStorage即可
  • 在网站中的用户浏览信息也会存储在LocalStorage中,还有网站的一些不常变动的个人信息等也可以存储在本地的LocalStorage中

14.4. SessionStorage

SessionStorage和LocalStorage都是在HTML5才提出来的存储方案,SessionStorage 主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页之后将会删除这些数据。

SessionStorage与LocalStorage对比:

  • SessionStorage和LocalStorage都在本地进行数据存储
  • SessionStorage也有同源策略的限制,但是SessionStorage有一条更加严格的限制,SessionStorage只有在同一浏览器的同一窗口下才能够共享
  • LocalStorage和SessionStorage都不能被爬虫爬取

SessionStorage的常用API:

// 保存数据到 sessionStorage
sessionStorage.setItem('key', 'value');

// 从 sessionStorage 获取数据
let data = sessionStorage.getItem('key');

// 从 sessionStorage 删除保存的数据
sessionStorage.removeItem('key');

// 从 sessionStorage 删除所有保存的数据
sessionStorage.clear();

// 获取某个索引的Key
sessionStorage.key(index)

SessionStorage的使用场景

  • 由于SessionStorage具有时效性,所以可以用来存储一些网站的游客登录的信息,还有临时的浏览记录的信息。当关闭网站之后,这些信息也就随之消除了。

14.5. IndexedDB

是被正式纳⼊HTML5标准的数据库储存⽅案,它是非关系型数据库。(不支持SQL查询),⽤键值对进⾏储存,可以进⾏快速读取操作,⾮常适合web场景,同时⽤JavaScript进⾏操作会⾮常便。

特点:

  • 键值对储存:IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
  • 异步:IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
  • 支持事务:IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
  • 同源限制: IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
  • 储存空间大:IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
  • 支持二进制储存:IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。
##### 存储位置大小##### 有效期##### 传输数据##### 安全性
cookie客户端字符串每个大小约为 4KB,单个域名下的总数有限制。20可以设置为持久性(expires/max-age)或会话级别。每次请求都会携带 Cookie 数据,影响性能。容易被窃取和篡改,需注意 XSS 攻击。可设置httponly
session服务器端字符串务器端存储,大小取决于服务器配置。会话级别,浏览器关闭或会话超时后失效。仅在初始会话时传输 Session ID,后续请求不再携带全部会话数据。相对安全,通过加密的 Session ID 进行识别和验证。
LocalStorage客户端字符串一般为 5MB,各浏览器可能不同。持久性存储,除非手动删除,否则永久有效。不随请求发送,仅在客户端存储和访问。易受 XSS 攻击,数据存储在客户端。
SessionStorage客户端字符串一般为 5MB,各浏览器可能不同。会话级别,浏览器关闭或标签页关闭后失效。不随请求发送,仅在客户端存储和访问。易受 XSS 攻击,数据存储在客户端。
IndexedDB客户端除非被清理,否则一直存在不随请求发送,仅在客户端存储和访问。

15. 服务器主动向浏览器发消息

最初是ajax轮询或者长ajax连接的方式,但是在服务器端没有数据变动的时候,无疑加大了服务器的负担。

新的解决办法:

  • SSE(Server-SendEvent )但是这种方法的缺点是,半双工的连接,即和传统的方式相反,只能由服务器主动发送,客户端被动接收,且目前Edge和IE均没有对其提供支持。
  • WebSocket,这是Html5引进的新协议,一旦建立Websocket连接,客户端和服务器之间是全双工的通讯,即两者地位等同,目前各大浏览器均提供了对Websocket的支持,但是缺点是相比SSE开发方式复杂。
var ws = new WebSocket('ws://localhost:8080/echo');
ws.onopen = function() {
    console.log('WebSocket 连接已经建立。');
    ws.send('Hello, server!');
};
ws.onmessage = function(event) {
    console.log('收到服务器消息:', event.data);
};
ws.onerror = function(event) {
    console.error('WebSocket 连接出现错误:', event);
};
ws.onclose = function() {
    console.log('WebSocket 连接已经关闭。');
};

WebSocket 在浏览器中的连接机制

WebSocket 在浏览器中的连接机制如下:

  1. 握手过程:客户端通过 HTTP 请求发起 WebSocket 连接请求,服务器响应并建立 WebSocket 连接。
  2. 数据传输:一旦连接建立,客户端和服务器可以双向发送和接收消息。
  3. 心跳机制:为了保持连接的活跃状态,通常需要实现心跳机制。客户端定期向服务器发送心跳消息(如 PING),服务器收到后回复 PONG 消息。如果在一定时间内没有收到心跳回复,客户端可以认为连接已断开,并进行重连操作。

16. 长链接和短链接

短连接

连接->传输数据->关闭连接
比如HTTP是无状态的的短链接,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。

像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源

长连接

连接->传输数据->保持连接 -> 传输数据-> …………->直到一方关闭连接,多是客户端关闭连接。 数据传输完成了保持TCP连接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据,但安全性较差。

长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况转存失败,建议直接上传图片文件

17. 跨域

一、定义

同源策略:浏览器为了保护用户信息安全,禁止不同域之间的js交流,只要域名、端口号、协议之一不同,就会引起同源策略,限制交互

同源政策主要限制了三个方面:

  • 当前域下的 js 脚本不能够访问其他域下的 cookie、localStorage 和 indexDB。
  • 当前域下的 js 脚本不能够操作访问操作其他域下的 DOM。
  • 当前域下 ajax 无法发送跨域请求。

同源政策的目的主要是为了保证用户的信息安全,它只是对 js 脚本的一种限制,并不是对浏览器的限制,对于一般的 img、或者script 脚本请求都不会有跨域的限制,这是因为这些操作都不会通过响应结果来进行可能出现安全问题的操作。

17.1. CORS

跨域资源共享:

服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。

Ajax前端需要设置withCredentials ( cookies 中 SameSite属性也需要设置,权值更高)

// 修改跨域请求的代码
crossButton.onclick = function () {
    ({
      withCredentials: true, // ++ 新增
      method: "get",
      url: "http://localhost:8003/anotherService",
    }).then((res) => {
      console.log(res);
    });
};

后端设置Access-Control-Allow-Origin

如下所示:

//指定允许其他域名访问
'Access-Control-Allow-Origin:*'//或指定域
//响应类型
'Access-Control-Allow-Methods:GET,POST'
//响应头设置
'Access-Control-Allow-Headers:x-requested-with,content-type'

17.2. JSONP实现跨域

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

JSONP的原理:通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。所以jsonp是需要服务器端的页面进行相应的配合的。(即用JavaScript动态加载一个script文件,同时定义一个callback函数给script执行而已。)

在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。 例如:有个a.html页面,它里面的代码需要利用ajax获取一个不同域上的json数据,假设这个json数据地址是example.com/data.php,那么…

js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。所以jsonp是需要服务器端的页面进行相应的配合的。

<?php
$callback = $_GET['callback'];//得到回调函数名
$data = array('a','b','c');//要返回的数据
echo $callback.'('.json_encode($data).')';//输出
?>

最终,输出结果为:dosomething(['a','b','c']);

CORS和JSONP对比:

CORS与JSONP相比,无疑更为先进、方便和可靠。

(1)JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求;

(2)使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得说句,比起JSONP有更好的错误处理;

(3)JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS;

17.3. Nginx代理

这种方式比较简单,将A应用和B应用都通过一个统一的地址进行转发,这样就可以避免跨域问题出现。

server {
        listen       80;
        server_name  www.gameloft9.top;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }
location /manager {
            proxy_pass http://127.0.0.1:6108/manager;
            proxy_set_header Host $http_host;
        }
location /service {
            proxy_pass http://127.0.0.1:9200/service;
            proxy_set_header Host $http_host;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        }

在上面的配置中,manager和service属于不同的域(IP虽然一样,但是端口不一样),如果manger里面的js调用service里面的接口,会报跨域错误。通过nginx转发后,所有的域都映射到www.gameloft9.top了,然后通过/manager,/se…

17.4. 为何 cdn 请求资源的时候不跨域

一些 CDN 资源不受同源策略限制,是因为同源策略主要是浏览器的一种安全机制,用于限制不同源的文档或脚本之间的交互操作,但对于某些特定类型的资源访问,浏览器会有一些例外情况。 在 HTML 中, <script>、<img>、<iframe>、<link> 等标签的 src 属性所指向的资源(如 JavaScript 文件、图片、CSS 文件等)通常是可以跨域访问的。 例如,可以在自己的网站中通过<script src="https://cdn.example.com/vue.min.js"></script>加载来自 CDN 的 Vue.js 库。

这样做的主要原因是为了保证 Web 的开放性和可扩展性。如果这些资源也严格受到同源策略的限制,那么将所有相关资源都部署在同一个服务器下会违背 Web 开放的初衷,并且不利于资源的分发和缓存。但是浏览器也会对这些内容限制读取和更改的权限,资源所在的服务器也会对可访问的来源进行限制。

18. 事件相关

事件是用户操作网页时发生的交互动作,比如 click/move。事件被封装成一个 event 对象,包含了该事件发生时的所有相关信息(event 的属性)以及可以对事件进行的操作( event 的方法)。

事件是用户操作网页时发生的交互动作或者网页本身的一些操作,现代浏览器一共有三种事件模型:

  • DOM0 级事件模型,支持事件冒泡的方式实现。直接在dom对象上注册事件名称,就是DOM0写法。
  • DOM0 级添加事件时,后面的事件会覆盖前面的事件
// DOM0级事件,就是直接通过 onclick 等方式实现相应的事件
1、
<input id="myButton" type="button" value="Click Me" onclick="alert('Hello1');" >
2、
document.getElementById("myButton").onclick = function () {
    alert('Hello2');
}
  • IE 事件模型,在该事件模型中,一次事件共有两个过程,事件处理阶段和事件冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过attachEvent 来添加监听函数,可以添加多个监听函数,会按顺序依次执行。
  • DOM2 级事件模型,在该事件模型中,一次事件共有三个过程,第一个过程是事件捕获阶段。捕获指的是事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段和 IE 事件模型的两个阶段相同。这种事件模型,事件绑定的函数是addEventListener,

addEventListener(eventType, handler, useCapture) 其中第三个参数可以指定事件是否在捕获阶段执行。(默认false 执行事件冒泡,否则执行事件捕获)

btn1.addEventListener('click',function (){
    alert('btn1')
  },false)   
//
btn1.removeEventListener(eventType, handler, useCapture)

18.1. 事件委托和事件冒泡

事件冒泡:JS中当触发某些具有冒泡性质的事件是,首先在触发元素寻找是否有相应的注册事件,如果没有再继续向上级父元素寻找是否有相应的注册事件作出相应,这就是事件冒泡。

阻止事件冒泡:

  • 普通浏览器使用:event.stopPropagation()
  • IE浏览器使用:event.cancelBubble = true;

事件委托:利用事件冒泡的特性,将本应该注册在子元素上的处理事件注册在父元素上,这样点击子元素时发现其本身没有相应事件就到父元素上寻找作出相应。只指定一个事件处理程序,就可以管理某一类型的所有事件

这样做的优势有:

  • 减少DOM操作,提高性能。
  • 随时可以添加子元素,添加的子元素会自动有相应的处理事件。
<ul id="ul1">
    <li>111</li>
    <li>222</li>
    <li>333</li>
</ul>

window.onload = function(){
  var oUl = document.getElementById("ul1");
  oUl.onclick = function(ev){
    // 兼容写法
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    if(target.nodeName.toLowerCase() == 'li'){
         alert(123);
         alert(target.innerHTML);
    }
  }
}
  • 适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。

  • focus、blur 之类的事件没有事件冒泡机制,所以无法实现事件委托;mousemove、mouseout 这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的。

19. web worker的使用

WebWorker 实际上是运行在浏览器后台的一个单独的线程,因此可以执行一些耗时的操作而不会阻塞主线程。WebWorker 通过与主线程之间传递消息实现通信,这种通信是双向的。WebWorker不能直接访问 DOM,也不能使用像 window 对象这样的浏览器接口对象,但可以使用一些WebWorker 标准接口和 Navigator 对象的部分属性和方法。

使用场景

  1. 大量数据的计算/处理: 比如图像处理、数据分析等
  2. 长时间运行的操作: 如一些复杂的数学计算
  3. 非阻塞式操作: 希望执行一些耗时操作时不阻塞主线程
// 主线程
const myWorker = new Worker('./worker.js')
 
myWorker.onmessage = function (e) {
  console.log('Fibonacci result:', e.data)
}

myWorker.postMessage(40) // 请求计算斐波那契数列的第40项


// worker.js

self.onmessage = function (e) {
  const n = e.data
  let a = 0,
    b = 1,
    temp
  for (let i = 2; i <= n; i++) {
    temp = a
    a = b
    b = temp + b
  }
  self.postMessage(b)
}

webpack中需要使用wroker-loader

20. 一些浏览器的事件

20.1. 监听当前页面是否活跃(tab是否被关闭)

document.addEventListener("visibilitychange", function () {
  if (document.visibilityState === "visible") {  // hidden
    console.log("页面现在是活跃状态。");
  } else {
    console.log("页面现在不是活跃状态。");
  }
});

20.2. 检测当前网络状态

// 方法1 
window.addEventListener("online", () => console.log("网络已连接"));
window.addEventListener("offline", () => console.log("网络已断开"));
// 方法2   navigator.connection
if ("connection" in navigator) {
  const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;

  console.log(`网络类型: ${connection.effectiveType}`);
  console.log(`估计的下行速度: ${connection.downlink}Mbps`);
  console.log(`RTT: ${connection.rtt}ms`);

  // 监听网络类型变化
  connection.addEventListener("change", (e) => {
    console.log(`网络类型变化为: ${connection.effectiveType}`);
  });
}

20.3. 监听页面是否即将关闭

window.addEventListener("beforeunload", (event) => {
  // 在这里执行你的清理逻辑或者其他操作
  // 例如,发送一个统计日志
  navigator.sendBeacon("/log", "用户即将离开页面");

  // 显示离开提示(大多数现代浏览器不支持自定义文本)
  event.returnValue = "您确定要离开此页面吗?";
});

20.4. 监听浏览器窗口大小变化

可以结合节流或防抖

// 定义一个函数来处理窗口大小变化
function handleResize() {
  const width = window.innerWidth;
  const height = window.innerHeight;
  console.log(`当前窗口大小:宽度 = ${width}, 高度 = ${height}`);
}

// 在窗口 resize 事件上添加监听器
window.addEventListener("resize", handleResize);

20.5. PerformanceObserver

Performance Observer是一种JavaScript API,用于监测页面性能指标,如资源加载时间、页面渲染时间等。它可以触发回调函数,以便你收集和分析页面性能数据。

// 创建一个 PerformanceObserver 对象
const observer = new PerformanceObserver((list) => {
  const entries = list.getEntries();
  for (const entry of entries) {
     // 检查 entry 类型是否为我们关注的 'resource'  
    if (entry.entryType === 'resource') {  
      console.log(`Resource loaded: ${entry.name}`);  
      console.log(`Duration: ${entry.duration} ms`);  
      console.log(`Initiator Type: ${entry.initiatorType}`); // 哪个类型的事件触发了这个资源的加载  
      // 可以根据需要添加更多日志或处理逻辑  
    }
  }
});

-frame: 指的是整个页面,包括页面的导航性能和整体加载时间。它可以监测与整个页面的性能相关的数据。 
-navigation: 与页面导航和加载时间相关,提供有关导航事件(如页面加载、重定向等)的性能数据。
-resource: 与页面中加载的各种资源相关,如图像、脚本、样式表等。它可以监测单个资源的加载性能,包括资源的开始和结束时间,以及其他相关信息。
-mark: 与性能标记(mark)相关,性能标记是在代码中设置的时间戳,通常用于记录特定事件的时间,以便后续性能分析。这提供了在页面加载期间创建性能标记的方式。
-measure: 与性能测量(measure)相关,性能测量用于测量两个性能标记之间的时间间隔,以获取更详细的性能数据。这提供了测量和分析特定事件之间的时间差的方式。
-paint: 与页面绘制性能相关,可以是 "first-paint"(首次绘制)或 "first-contentful-paint"(首次内容绘制)之一。这些指标表示页面呈现的关键时间点,可以帮助我们评估用户视觉上的加载体验。

// 配置性能观察器以监测导航性能
observer.observe({ entryTypes: ['navigation'] });

observer.disconnect(); // 调用 disconnect 方法停止性能观察

20.6. MutationObserver

mutationObserver是一个在浏览器环境中一种用于监听DOM变化的JavaScript API,它可以用于监听 DOM 树中指定节点及其子节点的变化,当节点内容、属性、子节点结构等发生改变时,它可以及时检测到并触发回调函数。这对于前端开发来说非常有用,可以被用来监控 DOM 变化、异步数据加载和自定义组件等场景。

// 创建一个 MutationObserver 实例,监听目标节点的变化
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    console.log(mutation.type); // 打印出变化类型,例如 childList、attributes 等
  });
});

// 配置 MutationObserver,指定监听的节点和监听类型
const config = {
  attributes: true, // 监听属性变化
  childList: true, // 监听子节点变化
  characterData: true, // 监听文本内容变化
  subtree: true // 监听后代节点变化
};

// 将 MutationObserver 实例绑定到一个目标节点
const targetNode = document.getElementById("target");

observer.observe(targetNode, config);
observer.disconnect() // 断开监听

20.7. navigator.sendBeacon()

方法使得网页可以异步地将数据发送到服务器,与页面的卸载过程同时进行,这一点非常重要,因为它允许在不影响用户体验的情况下,安全地结束会话或者发送统计数据。

window.addEventListener("unload", function () {
  var data = { action: "leave", timestamp: Date.now() };
  navigator.sendBeacon("https://example.com/analytics", JSON.stringify(data));
});
  • 异步sendBeacon() 发送的请求是异步的,不会阻塞页面卸载过程或者延迟用户浏览器的关闭操作。
  • 小数据量:适用于发送少量数据,如统计信息和会话结束信号。
  • 不影响关闭:它允许在页面卸载或关闭时发送数据,而不会阻止或延迟页面的卸载过程。
  • 可靠:它确保数据能够在页面退出时被送出,相较于 beforeunloadunload 事件中使用同步的 XMLHttpRequest 更为可靠。

通过使用 navigator.sendBeacon(),开发者可以确保在页面卸载过程中,重要的数据能够被可靠地发送到服务器,从而改善数据收集的准确性和用户体验。

20.8. requestAnimationFrame

requestAnimationFrame 是一个用于更新动画的 API,requestAnimationFrame 就是约定在下一次浏览器刷新前执行的一个定时器。类似setTimeout

它相对于传统的 setTimeout setInterval 的优势

  • 性能提升:requestAnimationFrame 自动根据屏幕的刷新率来调整帧率,避免了硬编码的时间间隔。

  • 浏览器优化:浏览器会在屏幕即将重绘之前自动调用回调,确保动画的最佳时机。

  • 流畅动画:提高动画流畅度,减少卡顿,尤其在高性能需求场景下如游戏、视频渲染。

// 定时任务,调用一次执行一次,所以需要递归调用

var animationId;//用来赋值requestAnimationFrame的id,为之后取消它做准备
function step(){
    console.log('我就是下次浏览器刷新前需要执行的下一帧动画');
    animationId = requestAnimationFrame(step);//为了在之后的每次浏览器刷新前都执行回调,递归调用回调
}
animationId = requestAnimationFrame(step);//最开始的调用
···
cancelAnimationFrame(animationId)//在满足某个条件时,取消上边requestAnimationFrame的调用,终止无休止的执行回调。