1. http 和 https
http :超文本传输协议,是互联网上应用最为广泛的一种网络协议,用于从 WWW 服务器传输超文本到本地浏览器的传 输协议,请求和响应消息的头以ASCII形式给出;而消息内容则具有一个类似MIME的格式,它可以使浏览器更加高效,使网络传输减少。
https :简单说就是http的安全加密版本,是由 http 和 ssl 协议构建的可进行 加密传输和身份认证的网络协议。就是在 http 下加入 SSL 层对 http 协议传输的数据进行加密处理,SSL协议是 https 的安全基础。
二者的区别:http 是明文传输,https 是加密传输;
https 需要 ca 证书,费用较高;
使用不同的链接方式,http的连接很简单,是无状态的;
接口也不同,一般 http 端口是80 ,https 是443。
2. https 的工作原理
客户端使用 https url 访问服务器,则要求 Web 服务器建立 ssl 链接;
web 服务器收到客户端请求后,将网站的证书(证书中包含公钥)返回给客户端;
客户端和 web服务器端开始协商 ssl 链接的安全等级,也就是加密等级;
客户端通过双方协商一致的安全等级,建立会话密钥,然后通过网站公钥来加密会话密钥,并传送给网站;
服务器端通过自己的私钥解密出会话密钥;
服务器端通过会话密钥加密与客户端之间的通信。
3. https的优点
可认证用户和服务器,确保数据发送到正确的客户端和服务器端;
比HTTP 安全,可防止数据在传输过程中被窃取、改变,大幅增加了中间人攻击的成本。
4. https的缺点
握手阶段比较费时,会使页面加载时间延长50%;
缓存不如 http 高效,会增加数据开销;
SSL 证书需要花钱,功能越强大的证书费用越高;
SSL 证书需要绑定 IP ,不能在同一个 ip 上面绑定多个域名,ipv4(互联网通信协议第四版)资源支持不了这种消耗。
5. TCP/IP协议
是能够在多个不同网络间实现信息传输的协议簇。在一定程度上参考了OSI的体系结构,OSI模型有七层,从上到下:物理层、数据链路层、网络层、运输层、会话层、表示层和应用层。在TCP/IP协议中,它们被简化为了四个层次。
应用层(主要协议:TaInet、FTP、SMTP等):接收来自传输层的数据或者按不同应用要求与方式将数据传输至传输层。不同种类的应用程序会使用应用层的不同协议,就比如万维网www使用了HTTP协议;可以加密、解密、格式化数据。 数据 信息
运输层(主要协议:UDP、TCP):实现数据传输与数据共享。 报文
网络层(主要协议:ICMP、IP、IGMP):负责网络中数据包的传送,网络连接的建立和终止以及IP地址的寻找。 比特流 字节流
网络接口层(主要协议:ARP、RARP):兼并了物理层和数据链路层,提供链路管理错误检测、对不同通信媒介有关信息细节问题进行有效处理等。
字节流和报文:
1、一般TCP/IP的应用层或者OSI的会话、表示、应用层把数据称为数据或者信息,到了传输层把数据称为报文,到了最底层就是比特流了也就是字节流
2、字节就是散乱的数据 报文就是添加了标记,封装后的数据
6. TCP三次握手
TCP协议位于传输层,作用是提供可靠的字节流服务,为了准确无误地将数据送达目的地,TCP协议采纳三次握手策略。
三次握手原理:
第1次握手:客户端发送一个带有SYN(synchronize)标志的数据包给服务端;
第2次握手:服务端接收成功后,回传一个带有SYN/ACK标志的数据包传递确认信息,表示我收到了;
第3次握手:客户端再回传一个带有ACK标志的数据包,表示我知道了,握手结束。
其中:SYN标志位数置1,表示建立TCP连接;ACK标志表示验证字段。
7. TCP和UDP区别
TCP UDP
面向连接的 无连接的(发送数据前不需先建立连接)
传送数据无差错、不丢失、不重复、按序到达 不保证可靠交付
面向字节流的 面向报文,网络拥塞时不会降低发送速率 因此会出现丢包
只能1对1 支持1对1、1对多
首部较大,20字节 8字节
总结:TCP是面向连接的可靠性传输,不会丢失数据,更适合大数据量的交换。
8. WebSocket
WebSocket 是 H5 下一种新的持久化的协议,本质上是一个基于tcp的协议,但不属于 http协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和宽带并达到实时通讯的目的。
原理:约定了一个通信的规范,通过一个握手的机制,客户端和服务器端建立一个类似tcp的连接,从而方便双方通信。在websocket之前,web交互一般是基于http协议的短链接或长连接。
过程:客户端发起http请求,3次握手后建立tcp连接,请求里存放websocket支持的版本号等信 息,如:Upgrade、Connection、WebSocket-Version等;
服务器收到客户端握手请求后,同样采用http协议返回数据;
客户端收到连接成功的信息后,开始借助于tcp传输信道进行全双工通信。
9. websocket 和 http
相同点:都是应用层协议、都基于tcp、都是可靠性传输协议
不同点:1.websocket是双向通信协议,模拟socket协议,双向发送和接收信息,而http是单向;
2. websocket需要浏览器和服务器握手建立连接,http是浏览器向服务器发起连接,服务 器预先并不知道
二者联系:websocket在建立握手时数据是通过http传输的,但建立之后的传输不需要http。
10. websocket解决了哪些问题
1. http存在的问题
http是一种无状态协议,每次会话完成后服务端都不知道下一次客户端是谁,需要知道对方是谁才进行相应的响应,这对于实时通讯就是一种极大的障碍。
http的生命周期由request来决定,一次请求一次响应,这次http请求就结束了。在http1.1中有了改进:connection: keep-alive长连接,一个http连接中可以发送多个request,接收多个response,但响应仍然是被动的,无法实现主动,且每次请求和响应携带大量的请求头,对于实时通信来说效率低下。
2. ajax轮询
每隔一段时间客户端就发起一次请求,查询有没有新消息,如果有就返回并关闭连接,如果没有就等待相同的时间间隔再次询问,这需要服务器有很快的处理速度。
3. 长轮询
算是ajax轮询的加强版。客户端发起长轮询,即发出一个设置较长网络超时时间的http请求。如果服务器端数据没有发生变更,会阻塞请求,直到数据发生变化,或者等待一定时间超时再返回,返回后客户端又立即发起下一次长轮询。
长轮询解决了http不能实时更新的弊端,发起请求就处理请求返回响应,时间很短,实现了伪长连接。但是需要有很高的并发。
缺点:客户端需要不断向服务端发起请求,在消耗较多客户端资源的情况下服务端不一定有新数据返回;http请求和回复信息中需要包含较长的头部信息,其中真正有效的数据可能只有一小部分,还会带来较多的宽带资源消耗;若服务端同一时间存在连续频繁的数据变化,如聊天室中,此时客户端获取数据更新相对较慢,无法保证用户体验。
4. websocket优势
一旦连接建立后,后续数据都以帧序列的形式传输。在客户端或者服务端中断连接之前,不需要双方重新发起连接请求,极大节省了网络宽带资源的消耗,有很明显的性能优势。并且客户端发送和接收消息是在同一持久连接上发起,实现了真长连接,实时性优势也很明显。
websoket是真正的全双工方式,服务端不再仅仅是被动接收消息,双方可以互相主动请求。且在第一个request建立了tcp连接后,交换数据不再需要发送http header。
11. 常用的BOM属性对象方法
1. location对象
location.href:返回当前url
location.search:返回url中查询字符串部分,即?后面的内容
location.hash:返回url中#后面的内容,没有#就返回空
location.host:返回url中域名部分,例如www.baidu.com
location.hostname:主域名部分,如baidu.com
location.replace(url):设置当前文档的 URL,并且在 history 对象的地址列表中移除 这个 URL
2. history
history.go(num):前进或后退指定的步数
history.back():后退一步
history.forward():前进一步
3. Navigator
navigator.userAgent:返回用户代理头的字符串表示(就是包括浏览器版本信息等 的字符串) navigator.cookieEnabled:返回浏览器是否支持(启用)cookie
12. http2.0
只用于https://网址,而http://继续用http1。
提升访问速度,请求资源所需时间更少;
允许多路复用,即允许同时通过单一的http2连接发送多重请求-响应信息。http1中浏览器客户端子同一时间内针对同一域名下的请求有一定数量限制,超过限制会被阻塞;
二进制分帧,将所有传输信息分隔为更小的信息或帧,进行二进制编码;
首部压缩;
服务器端推送。
13. http状态码
204:请求成功,但没有返回值
400:请求无效。前端提交到后台的数据应该是 json 字符串类型,但是前端没有将对象 JSON.stringify 转化成字符串。
401:当前请求需要用户验证
403:服务器已经得到请求,但是拒绝执行,没权限等原因被禁止访问
301: 永久重定向
302:临时重定向
304:请求资源未修改
415:请求类型错误,比如本该是post请求,结果使用了get请求
413:请求参数过大,只能由服务器端去修改配置
14. fetch发送两次请求的原因
fetch是h5的w3c新标准发布后才有的新对象,它的诞生是为了取代原生ajax中XHR(XMLHttpRequest)的存在。但是fetch的兼容性非常不好,对于那些不支持此方法的浏览器就需要额外的添加一个polyfill:[链接]: https://github.com/fis-components/whatwg-fetch。
fetch 发送 post 请求的时候,总是发送 2 次,第一次状态码是 204(请求成功,但没有返回值),第二次才成功。因为用 fetch 的 post 请求的时候, fetch 第一次发送了一个 Options 请求,询问服务器是否支持修改的请求头,如果服务器支持,则在第二次中发 送真正的请求。
15. 闭包相关
函数调用:


要使空间不被销毁,函数必须有返回值,返回值必须是复杂类型,且要赋值给外面的变量。
补充:常见的复杂数据类型:函数、对象、数组、正则表达式、Map、Set

闭包:函数内部返回一个函数,被外界所引用,这个内部函数就不会被销毁回收,这个内部函数所用到的外部函数的变量也不会被销毁

优点:让临时变量永驻内存
缺点:内存泄漏,手动回收 func=null
应用:
1. 函数柯里化
柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)。 柯里化不会调用函数。它只是对函数进行转换。

2. 防抖:在一段时间内只执行一次函数,如果在这段时间内再次触发,则重新计时。防抖常用于处理输入框输入事件、窗口大小变化事件等频繁触发的事件,以减少不必要的函数调用
Array.prototype.slice.call(arguments, 0)这行代码的作用是将类数组对象 arguments 转换为一个真正的数组。arguments 是一个类数组对象,它包含了函数调用时传入的所有参数,虽然有类似数组的结构,但它并不是一个真正的数组,因此无法直接使用数组的方法和属性。call 方法用于调用一个函数,并将一个指定的对象绑定为函数执行时的 this 上下文。将 arguments 对象作为 slice 方法的上下文对象,同时传入参数 0。这表示我们要从 arguments 中的索引 0 开始提取元素。

3. 节流:在一段时间内只执行一次函数,即使在这段时间内再次触发也不会立即执行,而是等待一定的时间间隔后再执行。节流常用于处理页面滚动事件、鼠标移动事件等频繁触发的事件,以减少函数执行的频率

4. 缓存


注:使用 cache[input] 时,实际上是在访问 cache 对象中键名为 input 的属性。如果 cache 对象中已经存在了键名为 input 的属性,那么 cache[input] 就会返回该属性对应的值;如果 cache 对象中没有键名为 input 的属性,那么 cache[input] 就会返回 undefined
Object[key]是一种属性访问方式。在 JavaScript 中可以使用 [] 来访问对象的属性,这种方式称为属性访问器,可以通过变量或表达式来动态地访问对象的属性,可以用于任何普通对象。
扩展:Object.key和Object[key]的区别
前者返回一个对象所有的可枚举属性的名称组成的数组

后者返回某个名为key的属性的值

16. 深浅拷贝
内存:简单划分为栈区域和堆区域,复杂数据类型储存在堆区,基本数据类型和引用变量储存在栈区域。
拷贝:深拷贝、浅拷贝
浅拷贝:复制的copy对象只能复制一层,对象里面只能是简单数据类型,如果是对象则不能复制

深拷贝:完全复制,即使对象属性值是对象也可以一起复制得到全新的对象
1. for循环递归 (递归:函数外部调用,在函数内部也调用自己本身,必须设置条件跳出递归,否则会栈溢出)

优化:当存在undefined时

2. json.stringfy() / json.parse()

缺点:无法拷贝数据类型为 Function 或 undefined 的数据
原因:json标准定义的数据交换格式,json字符串值类型只包括数字、字符串、布尔值、数组、对象、null,因此当想将包含undefined的对象进行转换时,undefined会被自动过滤
17. sass
css预处理器也叫css扩展语言,用来为css增加一些编程特性,无需考虑浏览器兼容问题,让css语言更简洁,适应性更强,代码更直观等。
分为Less / Sass / Stylus
Sass有两个版本:sass / scss,scss是Sass3 引入的新语法
全局安装sass: npm install sass -g

编译: $ sass index.scss index.css 。只编译一次,修改文件后重新执行一遍指令才可以。
实时编译:$ sass --watch index.scss:index.css 。只能监控一个文件
监控多个文件:创建一个文件夹放sass文件。$ sass --watch sass:css 。添加文件会实时响应,删除不会,css文件夹中需手动删除。
直接安装vscode插件:Easy Sass。自动转化编译css保存,直接引用css文件使用
scss语法:
1. 变量
全局变量 : $c:变量值
私有变量: 
2. 嵌套
嵌套中的&: 
群组嵌套(多个标签同时嵌套):
3. 混入(混合器):定义一个“函数”在scss中使用


混入传参:定义时形参,调用时实参。写了多少个形参,调用时就要传多少个实参。


默认值:有了默认值就可以不传实参


4. 继承


5. 注释

6. 导入文件
定义变量和混合器没有使用时不会被编译,可以写为单独的文件,导入后使用

7. 条件


8. for
@for 指令可以在限制的范围内重复输出格式,每次按要求(变量的值)对输出结果做出变动。这个指令包含两种格式:
@for $var from <start> through <end>:包含 <start> 与 <end> 的值
@for $var from <start> to <end>:只包含 <start> 的值不包含 <end> 的值
另外,$var 可以是任何变量,比如 $i;<start> 和 <end> 必须是整数值。
Sass和less相同:都可以用变量 嵌套 混合 运算
区别:less基于js在客户端处理,sass基于ruby(一种编程语言)在服务器端处理
less定义变量用前缀@,sass用$
less混合直接在classB里面引用classA,sass需要@mixin和@include命令
less没有if for 自定义函数
less变量分全局和局部,sass都为全局,但可以在后面加!default
18. es6新特性
1.let 、const
let、const不允许重复声明变量;
没有变量提升(预解析);
var只有函数能限制作用域,let和const会有块作用域(用{}包裹起来的,其声明的变量只在{}包裹的代码内生效),更好的防止内存泄漏;
let声明的变量值可以改变,也可以不赋值;
const声明值不能变,且必须赋值
补充:声明式函数和变量声明会被预解析,且函数声明优先于变量声明,若存在二者同名,函数声明会覆盖变量声明。此外的函数表达式和变量赋值都不会被预解析。
2.箭头函数:
语法: (函数的行参) => { 函数体内要执行的代码 }
只能简写函数表达式,不能简写声明式函数

箭头函数没有原型,不可以作为构造函数new出来会报错,内部没有 this,箭头函数的 this 是上文的 this;
函数的行参只有一个的时候可以不写 () ,没有或者有多个必须写
函数体只有一行代码的时候,可以不写 {} ,并且会自动 return

3.解构赋值
解构对象

解构数组

应用:1.函数参数解构
2.数组值交换位置
3.提取部分函数返回值
4.避免重复引用
4.模板字符串

5.展开运算符 ...
6. 字面量简写

7. 第6种基本数据类型Symbol
应用:给对象添加私有属性
const speak = Symbol();
class Person {
[speak]() {
console.log(123)
}
}
let person = new Person()
console.log(person[speak]())

8. Map Set
Map:是有序的键值对,其中的键允许是任何类型,普通对象的键只允许是字符串,其余的会强制转换为字符串

例如:
Set:无重复值的有序列表。根据 `Object.is()`方法来判断其中的值不相等,以保证无重复。当向Set对象添加一个已存在的键,不会有任何效果,也不会报错。


9. for of


补充:要成为可迭代对象(Iterable),必须实现Symbol.iterator()方法并返回一个迭代器对象。迭代器对象(Iterator)则需要实现next()方法来提供逐个访问元素的能力。
js如何让将对象转为可迭代对象:
需要实现一个迭代器协议。在对象上定义一个[Symbol.iterator]()方法,该方法返回一个迭代器对象;迭代器对象需要有一个next()方法,这个方法被调用时返回一个包含value和done属性的对象。
*10. 模块化语法 import / export
什么是模块? 将一个复杂的程序依据一定的规则封装成几个块, 块的内部数据是私有的, 只是向外部暴露一些接口,与外部其它模块通信。
前端模块化开发中:一个js文件就是一个模块,在js文件模块中定义的数据是私有的, 可以向外部暴露数据和方法,其它js文件模块可以引入暴露的方法数据进行使用。

模块指定默认输出:export default

外部引用文件时,需声明引用脚本的类型(即在<script>标签内加type="module"),index.js文件的内容也可以直接写在script标签内~


如果没有加:
11.promise对象
12.proxy代理
用来读取或设置对象的某些属性前记录日志;设置对象的某些属性值前进行验证;某些属性的访问控制等
使用方法:var p = new Proxy(target,handler)
当外界每次对p进行操作时,就会执行handler对象上的方法,如get读取、set修改、has判断是否有该属性、construct构造函数等

19. 删除数组用 delete 和 Vue.delete 有什么区别?
delete:只是被删除数组元素变为undefined,不会改变数组长度,length不会减少
Vue.delete:直接删了数组成员,并改变了数组的键值(对象是响应式的,确保删除能触发更新视图,这个方法主要用于避开 Vue 不能检测到属性被删除的限制)
vuex中想要在mutations中操作数据,如往state中一个对象新加一个属性,由于这是state中未事先定义好的数据,所以不具备响应式,解决方案:Vue.set
state:{
obj:{
name:"zhangsan",
age:"18"
}
}
mutations:{
//payload为参数
methodName(state,payload){
//事先定义好的name这是响应式的
state.obj.name = "lisi"
//如下设置就是响应式,如果是数组,后面就是索引 和 值
Vue.set(state.obj,"address","wuhan")
}
//同样删除属性也不是响应式的,此时就要用Vue.delete
methodName(state,payload){
//如下设置就不是响应式,哪怕是事先定义好的属性
delete state.obj.name
//如下设置是响应式
Vue.delete (state.obj,name)
}
20. axios
axios 是一种基于promise的异步请求,安装 npm install axios --save 即可 使用,请求中包括 get,post,put, patch ,delete 等五种请求方式,解决跨域可以在 请求头中添加 Access-Control-Allow-Origin,也可以在 index.js 文件中更改 proxyTable 配置等解决跨域问题。

实现请求和响应拦截:对请求和响应进行全局性处理,如统一添加请求头、处理错误信息等,可以减少重复代码

21. Vue3和Vue2的区别
1.性能提升
a. diff 方法优化
Vue2.x 中的虚拟 dom 是进行全量的对比。 Vue3.0 中新增了静态标记(PatchFlag):在与上次虚拟结点进行对比的时候,值对比 带有 patch flag 的节点,并且可以通过 flag 的信息得知当前节点要对比的具体内容 化。
b. hoistStatic 静态提升
Vue2.x : 无论元素是否参与更新,每次都会重新创建。 Vue3.0 : 对不参与更新的元素,只会被创建一次,之后会在每次渲染时候被不停的复 用。
c. cacheHandlers 事件侦听器缓存
默认情况下 onClick 会被视为动态绑定,所以每次都会去追踪它的变化但是因为是同一 个函数,所以没有追踪变化,直接缓存起来复用即可。
2.双向数据绑定原理
vue2中vue实例创建时会遍历data中的所有属性,通过Object.defineProperty()将这些属性转换为getter和setter,从而使其变成响应式,当这些属性值发生变化时,vue就能感知到并通知订阅者,触发相应回调来更新视图。这种方法存在性能问题,它只能监听在vue实例创建时就存在的属性,对于实例创建后动态添加的属性,vue无法自动为他们添加getter和setter,也就是说vue2无法在运行时动态地为新属性添加响应式逻辑。所以vue2只能检测到对象属性的读取和修改,无法检测增加和删除。
解决办法:this.$set(this.data, 'newProperty', 'new value') / Vue.set(this.data, 'newProperty', 'new value')
this.$delete / Vue.delete(this.data, 'deletedProperty')
vue3是通过es6的Proxy来监听整个对象,能准确捕获并响应数据的变化。
3.API设计
vue3新增了组合式api,与react的hooks概念相似
4.生命周期钩子函数
vue2:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed
vue3:setup、onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted
22. cookie、sessionStorage、localStorage的区别
1.cookie:即使不需要也始终在http请求中携带,在浏览器和服务器之间来回传递,存储容量小,4k左右,在设置的cookie过期时间之前一直有效,可以被用户或第三方查看或修改,不安全。
作用:1.保存用户登录状态,可设置保持登录的时间,一月三月一年等;
2.跟踪用户行为,例如一个天气预报网站,可根据用户选择地区显示当地天气情况,使用cookie之后系统会记住上次访问的地区。
3.个性化设置,例如网站提供的换肤或布局等功能,都可以用cookie记录用户选项。
存入:设置expires属性来指定cookie的过期时间,path属性来指定cookie适用的路径。如果不设置expires属性,cookie将在浏览器关闭时过期。

取出: 需要解析document.cookie字符串,它包含了所有cookie的列表,每个cookie之间用分号(;)和空格( )分隔

设置安全的cookie:在服务端添加HttpOnly和Secure标志

2.seeionStorage:仅在当前浏览器窗口关闭前有效
3.localStorage:始终有效,用作长久数据。和cookie都是在所有同源窗口中都是共享的,无论关闭与否。
存入数据:localStorage.setItem(key,value),存储对象或数组需要先转换为字符串

取出数据:localStorage.getItem(key)

删除:localStorage.removeItem()
清空:localStorage.clear()
以上 两者都是h5针对h4中cookie存储的一个改善,属于webStorage
23. cookie和session的区别
cookie保存在客户端浏览器,不安全,session保存在服务器,安全且没有大小限制;
Cookie的生命周期通常是从创建时开始计时,并在浏览器关闭时或达到设定的过期时间后结束。而Session的生命周期则是从创建时开始,如果在设定的时间间隔内没有访问,Session就会被销毁;
Cookie适用于存储非敏感信息,如用户偏好,而Session则更适用于存储敏感信息,如用户身份验证和会话管理。
24.promise
Promise是一个构造函数,new一个Promise相当于调用这个函数,这个构造函数的参数是一个函数,而这个函数的两个参数分别为resolve和reject(可自己命名),这也是两个函数,将promise的状态改为成功或失败。promise只有三种状态:pendding、fulffiled、rejected,一旦改变,状态不可逆。
Promise函数对象的方法:resolve、reject、all、race等,而promise原型对象的方法:then、catch、finally。then方法中有两个回调函数,成功和失败,但一般失败写在catch中。
then()返回的实际也是一个promise对象,无论函数内部返回什么类型的数据,函数都会加工返回promise对象。当内部返回为非promise类型的数据时,返回的promise状态为成功,会直接传递给下一个then,下一个then会通过形如then(data)中的data来接收该值,如 return 123,data就是123,没有返回值就是undefined。当内部返回为promise对象时,then的状态值为这个promise的状态值,成功值也是这个promise的成功值。当内部手动throw一个异常,那then的返回值也是rejected。
链式调用时候,当我们想要拿到第二个接口的请求结果,也就是说.then请求的结果,则需要return第一个promise实例再.then,否则第二个.then不起作用

promise.all([p1,p2,p3..])
promise.race([p1,p2,p3..])
中断promise链有且只有一个方法,就是返回一个pendding状态的promise,形如return new Promise(()=>{})
25.async和await
async函数语法就是在函数前面加上async关键字,表示函数是异步的,返回值是一个promise对象,promise对象的结果由async的返回值决定,等同于then()

await右侧表达式一般为promise对象,也可以是其他值,如果是promise对象,则await返回的是promise成功的值,如果是其他值,则await直接返回这个值

await必须写在async函数中,但async函数可以没有await
如果await的promise失败就会抛出异常,需要try..catch进行捕获处理
如果可能出错的代码比较少的时候可以使用try/catch结构来了处理,如果可能出错的代码比较多的时候,可以利用async函数返回一个promise对象的原理来处理,给async修饰的函数调用后返回的promise对象,调用catch方法来处理异常。

搭配axios使用:

26.vuex
vuex和vue一样是单向数据流,vue中的数据从父组件流向子组件,子组件不可直接修改,只能通知父组件修改原数据,vuex的完整流程:组件中触发action,action提交mutation,mutation修改state,组件又根据state或者getter来渲染页面。
安装:npm i vuex --save
配置文件夹目录store用来存放,创建index.js配置文件
import Vue from 'vue'
import Vuex from 'vuex'
// 应用vuex插件
Vue.use(Vuex)
// 创建并暴露store
export default new Vuex.Store({
// 数据,相当于data
state: {
},
//准备getters——用于将state中的数据进行加工
getters: {
},
//准备mutations——用于操作数据(state)
mutations: {
},
//准备actions——用于响应组件中的动作
actions: {
},
modules: {}
})
在main.js中引入
//引入store
import store from './store'
//创建vm
new Vue({
el:'#app',
render: h => h(App),
store
})
1. state:可以在任何组件中直接调用,使用方法:
1.标签中直接使用
2.在computed中调用:![]()
3.引入辅助函数:


2. getters:加工state中的数据,类似计算属性

使用方法:
1. ![]()
2.
3. 
3.mutations是唯一可以修改state值的方法

使用方法:
1.
2.
4.actions中写异步操作,但是并不能直接修改state,只能通过commit去调用mutations去修改

使用方法:
1.
2.
context是上下文,固定写法。
5.modules:模块拆分
当数据量很大时store会很臃肿,vuex允许将store分割成模块module,每个模块拥有自己的state、mutation等,甚至是嵌套子模块。默认情况下模块内部action和mutation仍然是注册在全局命名空间,这使得多个模块能同时响应一个action或mutation,当多人开发防止多个模块中5个核心中的名字发生冲突,可以开启命名空间,在每一个模块中加入namespaced:true。
修改store/index.js文件:
或者
//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
import countOptions from './count'
import personOptions from './person'
//应用Vuex插件
Vue.use(Vuex)
//创建并暴露store
export default new Vuex.Store({
modules:{
countAbout:countOptions,
personAbout:personOptions
}
})
创建count.js文件和person.js文件:
export default {
namespaced:true, // 开启命名空间
actions:{
},
mutations:{
},
state:{
},
getters:{
},
}
开启命名空间后调用方法:

面试问到开发中曾经遇到的问题时可以说:
使用pinia获取state中数据进行使用时,因为在{{}}中使用例如store.count比较麻烦,就用了解构赋值的方法来解构state中数据,在{{}}中就直接写count,结果发现解构后的数据只有一次作用,并不具有响应性,解决方法就是storeToRefs对解构的数据做ref响应式代理
import {storeToRefs} from "pinia"
const {count} = storeToRefs(store)
因为pinia可以直接修改state,所以不建议上面解构的方式,因为会破坏响应性
vuex和pinia的区别:pinia更适合中小级、低复杂度的应用,一般vue2用vuex,vue3用pinia
1.pinia没有mutation,同步异步都在action处理,同时
2.pinia体积很小,约1kb,更轻便,语法也更简单
3.pinia支持多个store,所以没有modules配置,每个仓库都是defineStore独立生成的
4.pinia不支持时间旅行(跟踪和记录变化历史,在不破坏当前状态的情况下回溯到之前的状态,vuex一般通过vuex-persistedstate、vuex-undo插件实现)和编辑等调试功能
27.解决vuex数据刷新丢失问题
因为js数据都是保存在浏览器的堆栈内存中,当刷新页面,浏览器运行机制会释放堆栈内存,vuex会重新更新state,获取的数据自然会丢失
当我们需要保存某些状态,实现持久化存储时,
1. 将状态储存到localstorage或者sessionstorage,要用时从中取出,这是手写的方法。
2. 第三方插件,常用:vuex-persistedstate
$ npm install vuex-persistedstate
import Vue from "vue";
import Vuex from "vuex";
import test from './modules/test'
import user from './modules/user'
// 引入插件
import createPersistedState from "vuex-persistedstate";
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
test,
user
},
/* vuex数据持久化配置 */
plugins: [
createPersistedState({
// 存储方式:localStorage、sessionStorage、cookies
storage: window.sessionStorage,
reducer(state) {
//指定需要储存的数据,这里是全部储存
return { ...state };
//第二种方式 用path
// 只持久化存储user模块的状态
paths: ['user']
}
})
]
})
28. v-model双向绑定原理
数据变化更新视图(model => view)
是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()或Proxy()来给各个属性添加setter,getter并劫持监听,在数据变动时发布消息给订阅者,触发相应的监听回调,也就是通知watcher重新渲染页面
视图变化更新数据(view => model)
利用事件监听(例如input、change事件等),拿到页面上输入的最新值赋值给data
35. 浏览器组成

1个浏览器主进程:
负责界面显示、用户交互、子进程管理
多个渲染进程:
一般浏览器会为每个Tab标签窗口创建一个渲染进程,负责将html、css、js转换成网页,里面包含多个线程
1个GPU进程:
负责复杂计算,比如绘图、3D动画
1个网络进程:
负责网络资源加载
多个插件进程:
浏览器每个插件都会分配一个插件进程
50.常见的http请求头、响应头
请求头:
1.Accept:指定客户端能够接收的内容类型,如 Accept: application/json
2.Content-Type:指定请求体的内容类型,如 Content-Type: application/json
3.Authorization:包含了用于验证请求的凭证,如 Authorization: Bearer <token>
4.User-Agent:包含了客户端的相关信息,如操作系统、浏览器等,用于服务器识别客户端
5.Origin:用于跨域请求,指示请求的来源
6.Accept-Language:指定客户端接受的语言类型,如 Accept-Language: en-US
响应头:
- Content-Type: 指定返回的数据类型,如text/html、application/json等
- Content-Length: 返回的数据长度
- Cache-Control: 控制缓存行为,如max-age、no-cache等
- Date: 响应生成的时间
- Server: 服务器类型信息
- Set-Cookie: 设置Cookie信息
- Location: 重定向的URL地址
- Expires: 指定响应的过期时间
- Last-Modified: 指定资源的最后修改时间
- ETag: 指定资源的标识符,用于缓存验证
36.浏览器原理(输入一个url之后浏览器做了什么)

用户输入地址或关键字,触发当前页面beforeunload,浏览器tab栏变成加载状态(转动的圈圈),等后面”提交文档“后才会新内容替换
浏览器主进程合成完整url,比如输入的是地址baidu.com,就自动合成https://www.baidu.com,比如输入:‘hello’,默认搜索引擎为百度,则合成为:https://www.baidu.com/s?ie=UTF-8&wd=hello
然后把完整url发送给网络进程
网络进程收到url请求后,先判断本地是否缓存了资源,如果有直接返回给主进程,不发起网络请求,如果没有就进入网络请求
网络请求前先进行DNS解析,把域名转换成ip,也是先查DNS缓存,如果有直接取对应ip。如果没有,就从DNS服务器请求ip,然后构建请求体、请求头(包括cookie)等信息,然后向服务端发送网络请求(建立Tcp链接)
服务端收到请求后进行对应操作,生成响应数据,发给网络进程
网络进程收到后先解析响应头,判断状态码是否为重定向(3xx),如果是就取响应头中Location字段,重新发起请求,如果200就继续处理请求
如果是200,浏览器主进程会根据响应头的content-Type做出响应,如果为application/octet-stream(文件,告知浏览器这是一个字节流,浏览器处理字节流的默认方式就是下载,这个类型一般会配合另一个响应头Content-Disposition,该响应头指示回复的内容该以何种形式展示,是以内联的形式即网页或者网页的一部分,还是以附件的形式下载并保存到本地),则启动下载流程,如果是text/html,则启动渲染流程
默认情况下浏览器会为每一个tab页创建一个渲染进程,如果是同一站点(根域名+协议相同,端口+子域名不同),则公用一个渲染进程
进入渲染流程前,浏览器主进程会发送一个“接收文档(网络进程里的响应头信息)”给渲染进程,渲染进程接收后和网络进程建立一个通道用来接收数据
渲染进程接收到数据后,向主进程发送“确认提交”信息
主进程收到后开始更新浏览器页面,包括地址栏url、前进后退按钮
渲染进程开始生成页面,一边接收一边生成,渲染完(触发onload),发送”渲染完毕“
主进程接收后显示页面,停止标签栏加载动画
简而言之就是:输入url,访问本地,如果没有访问dns解析,进行tcp握手,发送http请求,http响应返回数据,浏览器解析并渲染页面
37. 事件轮询(EventLoop)
一个用来等待和发送消息和事件的程序结构,js的单线程异步执行机制
所有任务都在主线程上执行,形成一个执行栈
主线程发现有异步任务,如果是微任务(宏任务)就放到微任务(宏任务)消息队列里
先执行调用栈中所有的同步任务
再执行微任务队列(dom渲染前)完后,再执行宏任务队列(dom渲染后)
反复询问回调队列中是否有要执行的语句,如果有就放入调用栈继续执行
微任务和宏任务的根本区别:
微任务:es6语法规定的
promise、async、await
注意:promise本身是同步的,所以promise中的回调会立即执行,then中的回调才会推 入微任务;async函数中的内容也是放入调用栈,await的下一行才放入微任务队列
宏任务:浏览器规定的
setTimeout、setInterval、ajax、dom事件(click)
调用栈执行完毕后会不断轮询微任务队列,即使先将宏任务推入队列,也会先执行微任务
setTimeout() :在指定的毫秒数后调用函数或计算表达式,只执行一次。
setInterval() :按照指定的周期(以毫秒计)来调用函数或计算表达式。方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。
29. nextTick
nextTick是等待下一次dom更新刷新的工具方法
vue的异步更新策略,当数据变化,Vue不会立刻更新dom,而是开启一个队列,把组件更新函数保存在这个队列中,在同一事件循环中发生的所有数据变更会异步的批量更新,这个策略导致了数据修改不会立刻提现在dom上,此时想要获取最新的dom状态就需要使用nextTick
开发时一般有两个场景会用到nextTick:
1. 在created中就想获取dom,因为一般只有挂载后mounted中才能获取dom
2. 响应式数据变化后想要获取dom更新后状态,例如获取列表更新后高度
nextTick可以实现的原因:
将传入的callback函数添加到队列刷新函数的后面,等队列中更新函数全都执行完毕,所有dom操作也都结束了,此时的dom就是最新值,callback获取到的就是最新dom
30. 组件传参
常用的通信方式:

其中划掉的都是vue3中已经废弃的api,包括eventbus在vue3中也不是很好用了

31. v-if和v-for的优先级
实践中不应该把二者放在一起,文档中也明确指出不要把二者同时用在同一个元素上
vue2中v-for的优先级更高,输出的渲染函数中可以看出会先执行循环再执行判断,哪怕只渲染一小部分元素,也必须在每次渲染时候遍历整个列表,这样会造成资源浪费
vue3中v-if优先级更高,当v-if执行时,它调用的变量还不存在,会造成异常
32.数组常用方法
1.直接改变原数组
pop: 删除数组末尾最后元素并返回被删除的元素
push: 向数组末尾添加元素并返回新的数组长度
reverse: 反转元素顺序
shift: 删除第一个元素并返回第一个元素的值
unshift: 向数组开头添加元素并返回新的数组长度
sort: 排序
splice: 增删改,左闭右开
2.不改变原数组
concat: 连接数组并返回
slice: 截取数组元素,参数是开始索引和结束索引,左闭右开
join: 以指定的分隔符将数组每一项拼接为字符串
indexOf: 检测当前值在数组中第一次出现位置的索引
find: 返回匹配项
findIndex: 返回匹配项的索引
map: es6按原始数组元素顺序依次处理后返回一个新数组
filter: 过滤
every: 判断是否每一项都满足条件
some: 判断是否有满足条件的项
forEach: es5遍历数组每一项,三个参数:遍历的数组内容、索引、数组本身
includes: es7新增,判断是否包含一个指定值,===比较,返回true/false
reduce: 遍历返回一个最终值
forEach和map:
都是循环遍历数组中的每一项。forEach() 和 map() 里面每一次执行匿名函数都支持3个参数:数组中的当前项item,当前项的索引index,原始数组input。匿名函数中的this都是指Window。只能遍历数组。
区别是:forEach没有返回值,但map中要有返回值,返回处理后的所有新元素组成的数组。
33.移动端适配
1. rem+媒体查询
缺点:针对不同屏幕写大量媒体查询,不会实时更新
@media screen and (min-width:xxxpx){
html{
font-size:xxpx;}}
2. rem+js动态计算
实时监听尺寸改变函数resize
3. lib-flexible库
上述三个结合px to rem去做
4. vw/vh
视口宽度=100vw
视口高度=100vh
34. 浏览器兼容性
1. 获取标签节点
document.getElementsByClassName(‘类名’)在低版本
ie中不兼容。解决方法是使用其他方式获取:document.getElementById('id名')
document.getElementsByTagName('标签名')
document.getElementsByName('name属性值')
document.querySelector('css选择器')
document.querySelectorAll('css选择器')
2. * 获取卷去的高度
// 获取
var t = document.documentElement.scrollTop || document.body.scrollTop
var l = document.documentElement.srollLeft || document.body.scrollLeft
// 设置
document.documentElement.scrollTop = document.body.scrollTop = 数值
document.documentElement.srollLeft = document.body.scrollLeft = 数值
3. 获取样式:函数封装的方式兼容
function getStyle(ele,attr){
if(window.getComputedStyle){//w3c标准浏览器
return getComputedStyle(ele)[attr]
}else{//低版本ie
return ele.currentStyle[attr]
}
}
4. 事件侦听器
function bindEvent(ele,type,handler){
if(ele.addEventListener){//w3c标准浏览器
ele.addEventListener(type,handler)
}else if(ele.attachEvent){
ele.attachEvent('on'+type,handler)
}else{
ele['on'+type] = handler
}
}
5. 事件解绑
function unBind(ele,type,handler){
if(ele.removeEventListener){
ele.removeEventListener(type,handler)
}else if(ele.detachEvent){
ele.detachEvent('on'+type,handler)
}else{
ele['on'+type] = null
}
}
6. 事件对象的获取
元素.on事件类型 = function(e){
var e = e || window.event
}
元素.addEventListener(事件类型,fn)
function fn(e){
var e = e || window.event
}
7. 阻止默认行为
元素.on事件类型 = function(e){
var e = e || window.event
e.preventDefault?e.preventDefault():e.returnValue=false
}
8. 阻止事件冒泡
元素.on事件类型 = function(e){
var e = e || window.event
e.stopPropagation?e.stopPropagation():e.cancelBubble=true
}
9. 获取精准的目标元素
元素.on事件类型 = function(e){
var e = e || window.event
var target = e.target || e.srcElement;
}
10. 获取键盘码
元素.on事件类型 = function(e){
var e = e || window.event
var keycode = e.keyCode || e.which;
}
38. 跨域
同源策略限制两个页面的资源交互,要求两个url的协议、域名、端口号完全一致,否则就会出现跨域。
解决跨域:
1.CORS:最广泛使用的解决方案,服务器端设置响应头,添加Access-Control-Allow-Origin字段,允许指定域名访问

2. jsonp
兼容性好,只支持get请求,不安全,利用外链脚本没有跨域限制的特点,动态创建script标签
具体实现:在客户端创建一个script标签,把请求后端的接口拼接为一个回调函数名称作为参数传给后端,并且赋值给script的src属性,然后把script加到body中。后端接收后进行解析,把数据和回调函数名称拼接成函数调用的形式返回。客户端接收后解析调用该回调函数,获取返回的数据。
3. 正向代理proxy(代理客户端)
主动设置代理服务器ip或域名进行访问,由设置的服务器去访问真正的服务器,使真实客户端对服务器不可见。例如vpn
一般用来解决访问限制
4. 反向代理nginx(代理服务器)
不需要任何设置,为服务器收发请求,使真实服务器对客户端不可见
一般用来解决负载均衡(根据所有真实服务器的负载情况将客户端请求分发到不同真实服务器上)、安全防护等
5.websocket本身就不存在跨域
6.postMessage方法
window.postMessage(),发送时需始终指定精确目标origin,接收时始终使用origin和source属性验证其身份
39. new一个对象的过程
1. 开辟一个堆内存,创建一个空对象
2. 执行构造函数,对这个空对象进行构造
3. 给这个空对象添加隐式原型
箭头函数没有prototype也没有this指向,并且不可以使用arguments(函数独有的伪数组),所以不能new
40. git常用命令
git branch 分支查看
git branch branch_1 增加分支
git checkout branch 分支切换
git merge branch_1 合并分支(合并前要切换当前分支至master)
git branch -d branch_1 删除分支
git remote 查看当前仓库管理的远程仓库信息
git remote show origin 查看指定的远程仓库的详细信息
git push --set-upstream origin branch_1 第一次将本地分支推到远程仓库
git push <远程主机名> <本地分支名>:<远程分支名> 将本地分支推到远程分支
git pull <远程主机名> <远程分支>:<本地分支> 将远程分支拉到本地分支
git branch -d branch_0 删除本地合并后分支
git branch -D branch_0 删除本地未合并分支
git push origin --delete branch_0 删除远程分支
git restore [filename] 进行清除工作区的改变
git tag 查看标签
git tag v1.0.0 打标签
git push origin v1.0.0 将tag同步到远程服务器
41. 浏览器缓存
当我们访问同一个页面时,请求资源、数据都是需要一定的耗时,如果可以将一些资源缓存下来,那么从第二次访问开始,就可以减少加载时间,提高用户体验,也能减轻服务器的压力。
浏览器缓存分为强缓存和协商缓存,当存在缓存时,客户端第一次向服务器请求数据时,客户端会缓存到内存或者硬盘当中,当第二次获取相同的资源,二者应对方式有所不同。
强缓存:当客户端第二次向服务器请求相同的资源时,不会向服务器发送请求,而是直接从内存/硬盘中间读取,强缓存由服务器的响应头里 cache-control 和 expires 两个字段决定
协商缓存:当客户端第二次向服务器请求相同的资源时,先向服务器发送请求"询问"该请求的文件缓存在本地与服务器相比是否更改,如果更改,则更新文件,如果没有就从内存/硬盘中读取。协商缓存由 last-modified 和 etag两个字段决定
43. 作用域和作用域链
作用域就是在代码执行过程中,形成一个独立的空间,让空间内的变量不会邪泄露在空间外,也让独立空间内的变量函数在独立空间内运行,而不会影响到外部的环境。
作用域分为全局作用域和局部作用域,也就是本来有一个巨大的空间,空间内定义的函数内部,就形成了一个独立的小空间,全局作用域是最大的作用域。
但是当独立空间内的数据不能满足需求时,是可以从外部获取数据的,也就是说这样的独立空间之间是可以有层级关系的,外部的空间不可以从内部的空间获取数据,但内部的空间可以。当子级空间在父级空间中获取数据的时,父级空间没有的话,父级空间也会到他的父级空间中查找数据,这样形成的链式结构叫作用域链。
当将一个变量当做值使用时,会先在当前作用域中查找这个变量的定义和数据,如果没有定义的话,就会去父级作用域中查找,如果父级作用域中有的话就使用这个值,如果父级作用域中也没有的话,就通过父级作用域查找他的父级作用域,直到找到最大的作用域-全局,如果全局也没有就报错。
当将一个变量当做数据容器存储,也就是给变量赋值的时候,也要先在自己作用域中查找变量的定义,如果没有就在上一级作用域中查找,直到全局,如果全局作用域中也没有这个变量的定义,就在全局定义这个变量并赋值。
44. Js如何实现多线程
我们都知道JS是一种单线程语言,即使是一些异步的事件也是在JS的主线程上运行的。像setTimeout、ajax的异步请求,或者是dom元素的一些事件,都是在JS主线程执行的,这些操作并没有在浏览器中开辟新的线程去执行,而是当这些异步操作被操作时或者是被触发时才进入事件队列,然后在JS主线程中开始运行。
JS作为脚本语言,它的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。(这里这些问题我们不做研究)
但是单线程的语言,有一个很致命的确定。如果说一个脚本语言在执行时,其中某一块的功能在执行时耗费了大量的时间,那么就会造成阻塞。这样的项目,用户体验是非常差的,所以这种现象在项目的开发过程中是不允许存在的。
其实JS为我们提供了一个Worker的类,它的作用就是为了解决这种阻塞的现象。当我们使用这个类的时候,它就会向浏览器申请一个新的线程 : var worker = new Worker(js文件路径); 这个线程就用来单独执行一个js文件。
45. 不卡顿处理多条数据
如何在不卡住页面的情况下渲染数据,也就是说不能一次性将几万条 都渲染出来,而应该一次渲染部分 DOM,那么就可以通过 requestAnimationFrame 来 每 16 ms 刷新一次
<ul>控件</ul>
<script>
setTimeout(() => {
// 插入十万条数据
const total = 100000
// 一次插入 20 条,如果觉得性能不好就减少
const once = 20
// 渲染数据总共需要几次
const loopCount = total / once
let countOfRender = 0
let ul = document.querySelector("ul");
function add() {
// 优化性能,插入不会造成回流
const fragment = document.createDocumentFragment();
for (let i = 0; i < once; i++) {
const li = document.createElement("li");
li.innerText = Math.floor(Math.random() * total);
fragment.appendChild(li);
}
ul.appendChild(fragment);
countOfRender += 1;
loop();
}
function loop() {
if (countOfRender < loopCount) {
window.requestAnimationFrame(add);
}
}
loop();
}, 0);
46. src和href
src和href都是用在外部资源的引入上,比如图像,CSS文件,HTML文件,以及其他的web页面等等,那么src和href的区别都有哪些呢?
1、请求资源类型不同
(1) href是Hypertext Reference的缩写,表示超文本引用。用来建立当前元素和文档之间的链接。常用的有:link、a。
(2)在请求 src 资源时会将其指向的资源下载并应用到文档中,常用的有script,img 、iframe;2、作用结果不同
(1)href 用于在当前文档和引用资源之间确立联系;(2)src 用于替换当前内容;
3、 浏览器解析方式不同
(1)若在文档中添加href ,浏览器会识别该文档为 CSS 文件,就会并行下载资源并且不会停止对当前文档的处理。这也是为什么建议使用 link 方式加载 CSS,而不是使用 @import 方式。(2)当浏览器解析到src ,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等也如此,类似于将所指向资源应用到当前内容。这也是为什么建议把 js 脚本放在底部而不是头部的原因。
47.Map Set
1. Map和对象的区别
map是一种有序的键值对集合,键可以是任意类型,map可以直接获取键值对数量(size),不需要遍历整个集合,有内置的迭代方法forEach,常用方法:set、get、has、delete、clear(清空)等。普通对象是无序的,也不能直接且键只能是字符串或者Symbol类型,也没有直接获取大小的方法,迭代需要for...in或者Object.keys、Object.values等方法。


2.Set和数组的区别
Set是值的集合,每个值都是唯一不可重复的,因此可以用new Set([])方法进行去重,其底层原理是根据Object.is()方法去判断两个值是否相等。Set是无序的,每个值没有下标值,因此无法直接通过索引获取,要获取的话需要先转换为数组。如果需要有序的Set可以使用另一个新的数据结构:OrderedSet。



48.内存泄漏原因及解决
1.设置了setTimeout 和 setInterval 定时器和addEventListener等事件监听未及时清理
在 Vue 组件的 beforeDestroy 生命周期钩子中进行清理
2.闭包
手动清除内部变量或闭包引用 =null
3.视频播放、WebSockets、Web Workers等需要手动释放的资源
需要在组件销毁时被清理,若使用了remove仍存在需要手动赋值null
4.定义的全局变量会一直存在于整个页面的生命周期中,不会被垃圾回收
尽量减少使用全局变量,限制在局部作用域中,或使用模块化开发
补充:console本身是一个全局对象,不会被垃圾机制所回收,但console对象的方法如.log()、.error()、.warn()等输出的日志信息是临时数据,会被回收。
49. js错误处理机制
手动生成错误对象




51.前端性能优化
1.代码压缩合并:将css和js压缩合并,常用的是webpack等打包时自动压缩,减少文件大小,避免使用过多库和框架,从而减少加载时间
2.图片压缩:使用在线工具JPEGmini等进行压缩,或者直接编辑图片尺寸质量等;
图片懒加载:在图片标签的src属性中使用一个占位符,真实图片地址放在data-src属性中,监听滚动事件,当图片进入可视区域再加载,示例:

3.将静态资源部署到cdn:
CDN将内容缓存到全球各地的服务器上,使用户可以从就近的服务器获取内容,从而加速网站访问速度,可以缓存和分发静态资源,减轻源服务器的负载,同时保护了源服务器的安全性。
但cdn通常需要付费,成本较高,使用过程中的部署和配置较为复杂,且由于CDN会将静态资源缓存在服务器上,并且在特定时间间隔内不会主动去源服务器请求最新资源,可能导致更新内容不及时等。
解决:通常建议采取手动清除缓存、设置缓存过期时间、设置自动刷新缓存等方法
4.预加载:通过在首页添加prefetch让浏览器在空闲时预加载其他页面的资源,这样在打开其他页面时就节省了加载时间,而使用preload是优先加载一些重要的资源,让用户能够优先看到重要的内容,提高用户体验
<link rel="prefetch" as="style" href="./css/other.css">
<link rel="preload" as="style" href="./css/index.css">

2591

被折叠的 条评论
为什么被折叠?



