前端八股文
文章目录
- 前端八股文
-
- HTML
-
- 1.语义化标签
- [2. 媒体元素](#2. 媒体元素)
- [3. canvas](#3. canvas)
- [4. 本地存储](#4. 本地存储)
- [5. H5 新增表单控件](#5. H5 新增表单控件)
- [6. 浏览器缓存有哪些](#6. 浏览器缓存有哪些)
- [7. head 标签的作用](#7. head 标签的作用)
- [8. 什么是 BFC?如何触发?有什么特点?如何解决 margin "塌陷"?](#8. 什么是 BFC?如何触发?有什么特点?如何解决 margin “塌陷”?)
- [9. 什么是重绘?什么是回流?如何减少?如何让文档脱离文档流?](#9. 什么是重绘?什么是回流?如何减少?如何让文档脱离文档流?)
- CSS
-
- [1. 盒模型](#1. 盒模型)
- [2. CSS 权重如何计算](#2. CSS 权重如何计算)
- [3. less 和 sass 的区别](#3. less 和 sass 的区别)
- [4. rem 和 em 和 px 的区别](#4. rem 和 em 和 px 的区别)
- Javascript
-
- [1. script 标签中的 async 和 defer 的作用](#1. script 标签中的 async 和 defer 的作用)
- [2. setTimeout 最小执行时间是多少?](#2. setTimeout 最小执行时间是多少?)
- [3. call, apply, bind 三者有什么区别?](#3. call, apply, bind 三者有什么区别?)
- [4. 如何实现一个深拷贝?](#4. 如何实现一个深拷贝?)
- [5. ajax 是什么呢?怎么实现的?](#5. ajax 是什么呢?怎么实现的?)
- [6. get 和 post 的区别?](#6. get 和 post 的区别?)
- [7. Promise 的内部原理?它的缺点是什么?](#7. Promise 的内部原理?它的缺点是什么?)
- [8. Promise 和 async await 的区别?](#8. Promise 和 async await 的区别?)
- [9. 类型判断有几种方式?](#9. 类型判断有几种方式?)
- [10. 什么是闭包?](#10. 什么是闭包?)
- [11. 什么是内存泄漏?](#11. 什么是内存泄漏?)
- [12. 什么是事件委托?](#12. 什么是事件委托?)
- [13. 基本类型和引用数据类型的区别?](#13. 基本类型和引用数据类型的区别?)
- [14. 什么是原型链?](#14. 什么是原型链?)
- [15. New 具体做了什么?](#15. New 具体做了什么?)
- [16. JS 继承有哪几种?](#16. JS 继承有哪几种?)
- [17. this 的理解](#17. this 的理解)
- [18. 创建函数有多少种方式?](#18. 创建函数有多少种方式?)
- [19. var、let、const 的区别?](#19. var、let、const 的区别?)
- [20. 对 ES6 的理解?](#20. 对 ES6 的理解?)
- [21. map与forEach的区别?](#21. map与forEach的区别?)
- [22. 去重有几种方法?](#22. 去重有几种方法?)
- [23. 说说对事件循环的理解?](#23. 说说对事件循环的理解?)
- [24. 什么是防抖和节流?有什么区别?如何实现?](#24. 什么是防抖和节流?有什么区别?如何实现?)
- [25. 大文件上传如何做断点续传?](#25. 大文件上传如何做断点续传?)
- Vue
-
- [1. 谈谈对 Vue 的理解?](#1. 谈谈对 Vue 的理解?)
- [2. keep-alive 了解吗?](#2. keep-alive 了解吗?)
- [3. Vue项目中你是如何解决跨域的呢?](#3. Vue项目中你是如何解决跨域的呢?)
- [4. vue3有了解过吗?能说说跟vue2的区别吗?](#4. vue3有了解过吗?能说说跟vue2的区别吗?)
- React
-
- [1. 请解释 React 中虚拟 DOM 的工作原理,以及它相比直接操作真实 DOM 有哪些优势?](#1. 请解释 React 中虚拟 DOM 的工作原理,以及它相比直接操作真实 DOM 有哪些优势?)
- [2. React 有哪些内置 Hooks ?](#2. React 有哪些内置 Hooks ?)
- [3. React 组件通讯方式有哪些?](#3. React 组件通讯方式有哪些?)
- [4. 讲诉 React 组件生命周期?](#4. 讲诉 React 组件生命周期?)
- 微信小程序
-
- [1. WXML 和 HTML 的区别?](#1. WXML 和 HTML 的区别?)
- [2. WXSS 和 CSS 的区别?](#2. WXSS 和 CSS 的区别?)
- 3.小程序有哪些常用的生命周期?
- [4. 小程序有哪几个文件,作用是什么?](#4. 小程序有哪几个文件,作用是什么?)
- [5. 小程序的优劣势?](#5. 小程序的优劣势?)
- [6. 哪些方法可以提高小程序的应用速度?](#6. 哪些方法可以提高小程序的应用速度?)
- [7. 小程序有哪几种跳转方式?](#7. 小程序有哪几种跳转方式?)
- 工程类
-
- [react 和 vue 最大的区别](#react 和 vue 最大的区别)
- 前端网页性能优化?
HTML
1.语义化标签
语义化的作用是提高网页的阅读性和 HTML 结构的清晰性,即使在 CSS 未加载时也能确保页面不会错乱,从而提高用户体验。此外,语义化的代码还有利于爬虫和 SEO 收录,以及优化浏览器对代码的解析。
2. 媒体元素
audio、video
3. canvas
Canvas API 提供了一个通过JavaScript 和 HTML 的 canvas 元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。
4. 本地存储
sessionStorage、localStorage 都是以键值对的方式存储在浏览器,区别是 localStorage 是永久存储,在同源窗口下都能访问;sessionStorage 是会话级别的存储,关闭了浏览器或者标签后就会删除,在同源窗口不能共享。
5. H5 新增表单控件
file、calendar、date、time、email、url、tel
6. 浏览器缓存有哪些
cookie优点:兼容性好,请求头自带 cookie;缺点:存储量少、如果一个请求不需要的 cookie, 会造成资源浪费。localStorage、sessionStoragewebSQL内嵌在浏览器的关系型数据库,前端可以像在使用 mysql、Oracle 一样的写 sql 语句,并存储信息。兼容性良好。存储后可在浏览器 resource 中查看。indexedDBh5 加入的以键值对标准的方式,可以快速读取,适合 WEB 场景。
7. head 标签的作用
head标签用于为文档提供元信息、配置和引用外部资源,以及定义文档的标题和结构,对于网页的优化和展示有着重要的作用。
8. 什么是 BFC?如何触发?有什么特点?如何解决 margin "塌陷"?
- BFC 本质就是单独开启一个独立的布局空间,让内部子元素不受外部元素布局影响,也不让外部元素影响内部子元素;
- 如何触发:
- 设置元素
overflow为visible以外的值; - 设置
flex、table-cell、inline-block等布局; - 设置
static和relative以外的值; - 设置浮动
float
- 设置元素
- 解决了元素与元素之前的外边距重叠问题
9. 什么是重绘?什么是回流?如何减少?如何让文档脱离文档流?
-
重绘(Repaint):当元素的外观样式发生改变,但不影响其布局时,浏览器会执行重绘操作。重绘只涉及对元素的可见样式进行重新绘制,不涉及元素的位置和尺寸计算。例如,改变元素的颜色、背景等属性会触发重绘操作。
-
回流(Reflow):当页面布局或几何属性发生改变时,浏览器需要重新计算元素的布局信息,包括位置、尺寸等,并重新构建渲染树。这个过程就是回流。例如,改变元素的宽度、高度、位置等属性,或者改变文档的结构,都会导致回流操作。
-
繁触发回流和重绘操作可能会导致性能问题,因为浏览器需要花费较多的时间来重新计算布局和重新渲染页面。为了减少回流和重绘,可以采取以下方法:
- 批量处理DOM操作:将多个DOM操作尽量合并到一起,减少回流和重绘的次数。
- 使用样式类进行修改:通过添加或移除CSS类,实现对元素样式的修改,这样浏览器只需要进行一次回流操作。
-
可以使用 CSS中 的
position属性和float属性脱离文档流
CSS
1. 盒模型
- 盒模型都是由:margin+border+padding+content
- 标准盒模型:width主要是content的宽度 (content-box) 会撑大盒子
- IE盒模型:width主要是指(content+padding+border)(border-box)
2. CSS 权重如何计算
!important > 内联 > ID > 类 > 元素 > 通配符
3. less 和 sass 的区别
- 语法差异,less 是用
@获取变量和混合方法,sass 使用$; - 拓展名
- less 语法设计上更接近 css
4. rem 和 em 和 px 的区别
- px 最常用最基础的单位,px 像素是屏幕上显示的最小单位,不受父元素字体大小影响;
- em 是相对单位,相对于父元素的字体大小,例如,如果一个元素的字体大小设置为16px,那么对于这个元素,1em等于16px。如果你在这个元素的一个子元素上设置font-size: 2em,那么这个子元素的字体大小就会是32px;
- rem 也是相对单位,不同的是它相对的是根元素
html标签,1rem 总是等于根元素的字体大小;
Javascript
1. script 标签中的 async 和 defer 的作用
- 属性只对外部连接有效,对
script标签里面的代码无效; - 没有添加属性的时候,遇到
script则立刻加载执行,并且会阻塞后面内容加载; - 有 async 则会异步加载 script 的内容,不会阻塞后面的内容加载;
- 有 defer 也是同步加载,但是里面的内容则会在所有元素解析完成后执行;
2. setTimeout 最小执行时间是多少?
HTML 5 规定的事件,setTimeout 是 4ms,setInterval 是 10ms;
3. call, apply, bind 三者有什么区别?
都是改变了 this 指向和函数的调用, call 和 apply 功能类似,只是传参的方法不同;
- call 方法传递是一个参数列表
- apply 方法传的是一个数组
- bind 传参后不会立即执行,会返回一个改变了 this 指向的函数,这个函数还是可以传参的
- call 方法要比 apply 性能好一些,所以用 call 的更多一些
4. 如何实现一个深拷贝?
- 深拷贝就是完全拷贝一份新的对象,同时在堆内存中重新开辟空间。
- 拷贝的对象被修改后,原对象不对影响
- 主要针对引用类型
javascript
// 1. 拓展运算符
// 这个方法只能实现第一层的拷贝,当有多层的时候还是浅拷贝
let obj = {
name: 'lee',
age: 18,
func: function () {
console.log(this);
},
info: {
a1: 123,
a2: {
b2: 123
}
},
arr: [[1,2], 2, 4, 3]
}
let obj1 = { ...obj }
obj1.name = 'tom'
obj1.func()
console.log(obj1);
// 2. JSON.parse(JSON.stringify())
// 该方法不能拷贝内部函数
let obj2 = JSON.parse(JSON.stringify(obj))
obj2.name = 'avery'
console.log(obj2);
// 3. 递归函数
function extent(origin, deep) {
let obj = {}
if (origin instanceof Array) {
obj = []
}
for (let key in origin) {
let value = origin[key]
obj[key] = (!!deep && typeof value === 'object' && value !== null) ? extent(value, deep) : value
}
return obj
}
const obj3 = extent(obj, true)
obj3.arr[0].push(888)
console.log(obj3);
5. ajax 是什么呢?怎么实现的?
它是一个可以在页面不刷新的情况下与服务器交互的一个技术;通过 XMLHttpRequest 对象向服务器发送异步请求,从而拿到数据通过 JS 更新页面;
- 创建 XMLHttpRequest 对象;
- 通过 xmh 对象里的 open() 方法和服务器连接;
- 构建请求所需的数据,并通过 xmh 对象的 send() 发送给服务器;
- 通过 xmh 对象 onReadyState changes 事件监听服务器和你的通信状态;
- 接受并处理服务器的响应结果;
- 把处理的数据更新到 HTML 页面上;
6. get 和 post 的区别?
- get 一般获取数据,post 一般提交数据;
- get 数据放在 url 参数上,安全性比较差,post 是放在 body 上数据长度没有限制;
- get 请求刷新对服务器数据不影响,post 会重新提交;
- get 请求时会被缓存,post 不会;
- get 请求会保存在浏览器历史记录中,post 不会;
- get 只能通过 URL 进行请求,post 支持很多种;
7. Promise 的内部原理?它的缺点是什么?
原理: 需要构造一个 Promise 实例,实例需要传递函数的参数,两个形参分别是:resolve,reject;
缺点: 无法取消 Promise,一旦创建它就会立即执行,不能中途取消,不使用 .then 无法取到结果;
- Promise 对象是封装了一个异步操作并且可以获取成功和失败的结果;
- Promise 是为了解决回调地狱问题产生的,其结果可以依次用
.then处理; - 回调地狱指的是有多个异步操作,并且每一个操作都需要依赖前一个操作结果,导致代码中嵌套了大量的回调函数,可维护性差;
- Promise 有三种状态:pending(进行中)、fulfilled(已成功)、reject(已失败);
javascript
setTimeout(() => {
// 根据设定的时间优先于下面的 promise
console.log('快于 promise ')
}, 500)
const promise = new Promise((resolve, reject) => {
const random = Math.random();
setTimeout(() => {
if (random < 0.5) {
const data = {message: 'hello Promise!'}
resolve(data) // 返回成功结果
} else {
reject(new Error('Failed to fetch data.'))
}
}, 1000);
});
promise.then(data => {
console.log(data)
}).catch(error => {
console.log(error)
})
console.log('快于上面的异步操作');
8. Promise 和 async await 的区别?
- 都是异步请求的方式;
- Promise 是 ES6 的语法,async await 是 ES7 的语法;
- async await 是基于 Promise 实现的,都是非阻塞性的;
- Promise 是返回对象,要用
.then和.catch链式捕获结果,容易造成代码重叠,不好维护; - async await 通过
try和catch进行捕获结果; - async await 最大的优点是让代码看起来像同步一样,只要遇到 await 就会立刻返回结果,然后在执行下面代码;
promise.then()的返回方式,会出现请求还没返回就执行了后面的操作;
9. 类型判断有几种方式?
javascript
// typeof() 对基本类型没问题,遇到引用类型就不管用了
console.log(typeof 666)
console.log(typeof [1, 2, 3]);
// instanceof 只能判断引用类型,不能判断基本类型
console.log([] instanceof Array)
console.log('abc' instanceof String);
// constructor 几乎可以判断基本数据类型和引用类型
console.log(('123').constructor == String);
// Object.prototype.toString.call()
var opt = Object.prototype.toString
console.log(opt.call('123'));
console.log(opt.call(456));
console.log(opt.call([1, 2, 3]));
console.log(opt.call({}));
console.log(opt.call(true));
console.log(opt.call(() => {}));
10. 什么是闭包?
函数嵌套函数,内部函数被外部函数返回并保存下来时,就会产生闭包。
特点: 可以重复利用变量,并且这个变量不会污染全局的一种机制,这个变量会一直保存在内存中,不会被垃圾回收机制回收;
缺点: 闭包比较多的时候会消耗内存,导致页面性能下降,在 IE 浏览器中才会导致内存泄漏;
javascript
function fn(a) {
var b = '内部变量'
return function () {
console.log(a, b)
}
}
var fo = fn('hello')
fo()
var fo = fn('world')
fo()
11. 什么是内存泄漏?
JS 里已经分配内存地址的对象,由于长时间没有释放或者没办法清除,造成长期占用内存现象,会让内存资源大幅度下降,最终导致页面运行速度慢,甚至崩溃;
JS 垃圾回收机制,出现问题收不回来,会导致内存泄漏;
导致内存泄漏的因素:
- 未及时清理的引用:当一个对象不在需要时,如果其仍然被其他对象引用着,那么该对象将无法被垃圾回收机制回收。这种情况下,内存就会被一直占用,导致内存泄漏。通常发生在循环引用、事件监听未正确移除等场景;
- 定时器未清理:当我们使用定时器(如 setTimeout 或 setInterval)时,如果在定时器执行前,我们忘记清理或取消定时器,那么定时器引用的函数可能永远不会被释放,导致内存泄漏;
- 大量缓存数据:在使用缓存时,如果我们不合理地管理缓存数据的大小,就可能导致内存占用过多。特别是在长时间运行的应用程序中,如果不定期清理过期或不再使用的缓存数据,就会造成内存泄漏;
- DOM 引用未释放:在操作 DOM 元素时,如果我们保留了对这些元素的引用,即使它们已经从页面中移除,仍然会占用内存。因此,确保在不再需要时及时释放对 DOM 的引用是避免内存泄漏的重要一步;
- 闭包的使用不当:闭包是 JavaScript 中的一个强大特性,但在不正确使用时可能导致内存泄漏。当一个函数引用了其外部作用域中的变量或函数时,这些变量和函数将一直存在于内存中,即使函数已经执行完毕。如果这些变量和函数占用大量内存,就会导致内存泄漏;
为了避免这些内存泄漏问题,我们应该及时清理不再使用的引用、正确处理定时器、合理管理缓存数据、及时释放对 DOM 的引用,并注意闭包的使用。此外,使用开发者工具进行内存分析和性能优化也是一个好的方法,可以帮助我们及早发现和解决潜在的内存泄漏问题。
12. 什么是事件委托?
原理: 利用冒泡的机制来实现,把子元素的绑定在父元素身上,如果子元素阻止了冒泡,事件委托就不存在 event.stopPropagation(),事件委托可以理解为是一种优化方式;
javascript
var button = document.getElementById('myButton');
button.addEventListener('click', function (e) {
// 处理点击事件
console.log(e);
});
var parent = document.querySelector('.list');
parent.addEventListener('click', function (e) {
var target = e.target;
if (target.matches('.btn4')) {
// 查看事件内容
console.dir(target)
}
});
13. 基本类型和引用数据类型的区别?
基本数据类型: 保存到栈内存,在同作用域下只会保存一个值
- String
- Number
- Boolean
- undefined
- null
引用数据类型: 保存在堆内存
- Object
- function
- Array
javascript
var a = 20
var b = a
b = 30
// b 变量是新声明存储在栈中的基本类型值,不会影响到原来的 a
console.log(a, b) // 输出 20 30
var oldObj = {
name: 'lee'
age: 18
}
var newObj = oldObj
newObj.name = 'tom'
console.log(oldObj, newObj) // 两个对象里面的 name 已经都变成了 tom,堆内存的赋值修改都会指向同一个地址
14. 什么是原型链?
所有的引用类型都有一个原型,它就是一个普通的对象 prototype,实现继承和属性共享的重要机制,使用 __proto__ 访问对象的原型,这个称为链,如果从自身找到了方法和属性则会停查找,反之会一直向上查找知道为空对象,这个过程称为原型链。
javascript
function Person() {
this.say = function () {
console.log('唱歌')
}
this.name = 'lee'
}
// 下面多次声明实例,每个方法都是独立的,会造成内存消耗。
var p1 = new Person()
var p2 = new Person()
p1.say()
p2.say()
// 为了解决多次声明,JS 发明了 prototype 方法可以把方法挂在原型上,内存只保存一份,下面这段是解决方式
Person.prototype.age = 29 // 定义一个属性
Person.prototype.look = function () {
console.log('西游记');
}
p1.look()
p2.look()
p1.name = 'tom'
p1.age = 27
// p1 实例利用 __proto__ 指向了 Person 构造函数。可以理解 __proto__ 是指针,实例对象中的数下
console.log(p1.name, p1.age, '\n', p2.name, p2.age);
console.log(p1.__proto__ === Person.prototype);
15. New 具体做了什么?
- 先创建一个空对象;
- 把空对象和构造函数通过原型链进行连接;
- 把构造函数的 this 绑定到新的空对象;
- 根据构造函数返回的类型判断,如果是值类型,则返回对象。如果是引用类型,就要返回这个引用类型;
javascript
function newFun(Fun, ...args) {
// 先创建一个空对象
let newObj = {}
// 把空对象和构造函数通过原型链进行链接
newObj.__proto__ = Fun.prototype
// 把构造函数的 this 绑定到新的空对象
const result = Fun.apply(newObj, args)
// 返回值
return result instanceof Object ? result : newObj
}
function Person(name) {
this.name = name
}
Person.prototype.say = function() {
console.log(this.name);
}
const p1 = newFun(Person, 'lee')
p1.say()
console.log(p1);
16. JS 继承有哪几种?
我了解的 js 中实现继承的几种方式有:
-
第一种是以原型链的方式来实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。
-
第二种方式是使用借用构造函数的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。
-
第三种方式是组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。
-
第四种方式是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。
-
第五种方式是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是我们的自定义类型时。缺点是没有办法实现函数的复用。
-
第六种方式是寄生式组合继承,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。
17. this 的理解
- 全局下的 this 是指向了 window;
- 全局作用域和普通函数的 this 指向了 window;
- this 在不是箭头函数下永远指向最后一个调用;
- new 关键字改变 this 的指向
- 箭头函数没有 this ,看外层是否有函数,有则指向它,没有执行 window
- 匿名函数的 this 永远指向 window
javascript
console.log('全局下的 this', this);
{
console.log('作用域下的 this', this);
function func() {
console.log('普通函数下的 this', this);
}
(function () {
console.log('匿名函数1下的 this', this);
var fn = function () {
console.log('匿名函数2下的 this', this);
}
fn()
})()
}
() => console.log('普通箭头函数下面的 this', this);
function Parent () {
this.info = {
name: 'lee',
age: 28
}
}
Parent.prototype.getInfo = function () {
console.log('new 下面的 this ', this);
}
Parent.prototype.print = function() {
const print = () => console.log('new 里面使用箭头函数的 this', this);
print()
}
Parent.prototype.an = function() {
(function() {
console.log('new 里面的匿名函数1 this', this);
})()
var fn = function() {
console.log('new 里面的匿名函数2 this', this);
}
fn()
}
const child1 = new Parent()
child1.getInfo()
child1.print()
child1.an()
18. 创建函数有多少种方式?
javascript
// 函数声明:使用 function 关键字声明一个函数,并为其命名。函数声明可以在任何地方使用,因为 JavaScript 会将其提升到当前作用域的顶部。
function sum(a, b) {
return a + b;
}
// 函数表达式:将一个函数赋值给变量、对象的属性或数组元素。函数表达式需要使用 var、let 或 const 关键字来声明变量。
var sum = function(a, b) {
return a + b;
};
// Function 构造函数:使用 Function 构造函数创建一个新的函数。参数是函数的形参和函数体字符串。不推荐使用这种方式创建函数,因为会增加代码的安全性风险。
var sum = new Function('a', 'b', 'return a + b;');
// 箭头函数:ES6 引入了箭头函数,是一种更简洁的函数定义方式。箭头函数内部没有自己的 this,arguments 和 super,并且不能用作构造函数。
const sum = (a, b) => {
return a + b;
};
19. var、let、const 的区别?
能使用 const 的情况下尽量使用,大多数情况下大多数情况使用let,避免使用var。let一般应用于基本数据类型,const 一般应用于引用数据类型,也就是函数对象等。
- 变量提升:var 声明的存在变量提升,即变量可以在声明之前调用,值为
undefined。let 和 const 不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错; - 块级作用域:var 不存在块级作用域,let和const存在块级作用域;
- 重复声明:var 允许重复声明变量,let和const在同一作用域不允许重复声明变量;
- 修改声明的变量:var 和 let 可以,const声明一个只读的常量。一旦声明,常量的值就不能改变,但对于对象和数据这种引用类型,内存地址不能修改,可以修改里面的值。
javascript
// **** var 存在变量提升,var 的关键字被提升到其作用域的顶部,但是定义的值不会被提升
console.log(a); // undefined
var a = 10;
// 编译过程
var a;
console.log(a); // undefined
a = 10;
// **** 一个变量可多次声明,后面的声明会覆盖前面的声明
var a = 10;
var a = 20;
console.log(a); // 20
// **** 在函数中使用var声明变量的时候,该变量是局部的
var a = 10;
function change(){
var a = 20;
}
change();
console.log(a); // 10
// **** 而如果在函数内不使用var,该变量是全局的
var a = 10;
function change(){
a = 20
};
change();
console.log(a); // 20
// **** 不存在变量提升,let声明变量前,该变量不能使用(暂时性死区)
console.log(a); // ReferenceError: a is not defined
let a = 10;
// **** let命令所在的代码块内有效,在块级作用域内有效
{
let a = 10;
}
console.log(a); // ReferenceError: a is not defined
// **** let不允许在相同作用域中重复声明,注意是相同作用域,不同作用域有重复声明不会报错
let a = 10;
let a = 20;
// Uncaught SyntaxError: Identifier 'a' has already been declared
let a = 10;
{
let a = 20;
}
// ok
// **** const声明一个只读的变量,声明后,值就不能改变
const a = 10;
a = 20; // TypeError: Assignment to constant variable.
// **** const必须初始化
const a; // SyntaxError: Missing initializer in const declaration
const a = 10; // ok
// **** const并不是变量的值不能改动,而是变量指向的内存地址所保存的数据不得改动
const obj = {
age: 17
}
obj.age = 18; // ok
obj = {
age: 18
}
// SyntaxError: Identifier 'obj' has already been declared
20. 对 ES6 的理解?
ES6 是 2015 年推出 JavaScript 标准,主要增加一些新的特性:
- 块级作用域:引入了
let和const关键字,可以在块级作用域中声明变量,解决了变量提升和作用域污染的问题; - 箭头函数:使用肩头函数语法
=>可以更简洁的定义函数,并且自动绑定了上下文的this值; - 模版字符串:使用 (``) 可以创建多行字符串和插入变了,比字符串拼接更加方便和直观;
- 解构赋值:可以从数组或对象中快速提取值并赋给变量,简化了变量赋值的操作;
- 类和模块:引入类和模块的概念,面向对象编程更加直观;
- Promise:提供一种更好的异步操作方式,解决回调地狱的问题;
- 模块化:引入了 import 和 export 关键字,使得模块化开发更加简单和可维护;
21. map与forEach的区别?
map()基本用法与 forEach 一直致,但是不同的,它会返回一个新的数组,所以在 callback 需要有 return 值,如果没有会返回 undefined;forEach()是最基本的方法,就是遍历与循环,默认有3个传参:分别是遍历的数组内容item、数组索引index、和当前遍历数组 Array
22. 去重有几种方法?
- 使用 Set 数据结构:Set 是 ES6 引入的一种新的数据结构,它可以存储唯一的值。你可以将数组转换为 Set,然后再将其转换回数组,以实现去重;
javascript
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = Array.from(new Set(array));
console.log(uniqueArray); // [1, 2, 3, 4, 5]
- 使用 filter 方法:遍历原始数组,使用 indexOf 方法检查元素是否已经存在于新数组中,如果不存在则添加到新数组中;
javascript
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = array.filter((value, index, self) => {
return self.indexOf(value) === index;
});
console.log(uniqueArray); // [1, 2, 3, 4, 5]
- 使用 reduce 方法:遍历原始数组,使用 reduce 方法来构建一个新数组,同时检查新数组中是否已经存在当前元素,如果不存在则添加到新数组中;
javascript
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = array.reduce((acc, curr) => {
if (acc.indexOf(curr) === -1) {
acc.push(curr);
}
return acc;
}, []);
console.log(uniqueArray); // [1, 2, 3, 4, 5]
23. 说说对事件循环的理解?
首先,JavaScrip 是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环
在 JavaScript 中,所有的任务都可以分为
-
同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
-
异步任务:异步执行的任务,比如
ajax网络请求,setTimeout定时函数等
同步任务与异步任务的运行流程图如下:

从上面我们可以看到,同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就事件循环
宏任务与微任务:
javascript
console.log(1)
setTimeout(()=>{
console.log(2)
}, 0)
new Promise((resolve, reject)=>{
console.log('new Promise')
resolve()
}).then(()=>{
console.log('then')
})
console.log(3)
如果按照上面流程图来分析代码,我们会得到下面的执行步骤:
console.log(1),同步任务,主线程中执行setTimeout(),异步任务,放到Event Table,0 毫秒后console.log(2)回调推入Event Queue中new Promise,同步任务,主线程直接执行.then,异步任务,放到Event Tableconsole.log(3),同步任务,主线程执行
所以按照分析,它的结果应该是 1 => 'new Promise' => 3 => 2 => 'then'
但是实际结果是:1 => 'new Promise' => 3 => 'then' => 2
出现分歧的原因在于异步任务执行顺序,事件队列其实是一个"先进先出"的数据结构,排在前面的事件会优先被主线程读取
例子中 setTimeout 回调事件是先进入队列中的,按理说应该先于 .then 中的执行,但是结果却偏偏相反
原因在于异步任务还可以细分为微任务与宏任务
24. 什么是防抖和节流?有什么区别?如何实现?
本质上是优化高频率执行代码的一种手段
如:浏览器的 resize、scroll、keypress、mousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能
为了优化体验,需要对这类事件进行调用次数的限制,对此我们就可以采用 防抖(debounce) 和 节流(throttle) 的方式来减少调用频率
定义:
- 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
- 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
一个经典的比喻: 想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应
- 电梯第一个人进来后,15秒后准时运送一次,这是节流
- 电梯第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖
写法:
javascript
// 防抖
function debounce(func, delay) {
let timeoutId; // 定义定时器 ID,用于后续清除定时器
return function (...args) { // 返回一个新的函数,接受任意个参数
clearTimeout(timeoutId); // 清除之前设置的定时器
timeoutId = setTimeout(() => { // 设置一个新的定时器
func.apply(this, args); // 在定时器到期后执行回调函数,并通过 apply 方法将上下文和参数传递给回调函数
}, delay);
};
}
debounce(handleClick, 1000);
// 节流
function throttled(fn, delay = 500) {
let timer = null; // 定义一个计时器变量初始值为null
return function (...args) { // 返回一个新的函数,接受任意个参数
if (!timer) { // 如果计时器变量为空,则表示可以执行回调函数
timer = setTimeout(() => { // 设置一个定时器,在延迟时间后执行回调函数
fn.apply(this, args); // 执行回调函数,并通过 apply 方法将上下文和参数传递给回调函数
timer = null; // 将计时器变量置空,表示回调函数执行完成
}, delay);
}
};
}
throttled(handleClick, 1000);
应用场景
-
防抖在连续的事件,只需触发一次回调的场景有:
- 搜索框搜索输入。只需用户最后一次输入完,再发送请
- 手机号、邮箱验证输入检测
- 窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
-
节流在间隔一段时间执行一次回调的场景有:
- 滚动加载,加载更多或滚到底部监听
- 搜索框,搜索联想功能
25. 大文件上传如何做断点续传?
- 分片上传:将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(Part)来进行分片上传,上传完成之后服务端需要把分片整个成原始文件,大致流程如下:
- 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;
- 初始化一个分片上传任务,返回本次分片上传唯一标识;
- 按照一定的策略(串行或并行)发送各个分片数据块;
- 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件
- 断点续传:指的是在下载或上传时,将下载或上传任务人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度
- 服务器端返回,告知从哪开始
- 浏览器端自行处理
Vue
1. 谈谈对 Vue 的理解?
- 按照官网的 slogan 是一个渐进式的JavaScript框架,帮助开发者构建用户界面(UI)以及单页面应用程序(SPA)
- 使用组件化思想构建应用程序,组件的状态数据可以互相通讯
- 提供一种简单的模板语法,以声明式地将数据绑定到DOM元素上,使得开发者可以更加轻松地管理和更新UI状态
2. keep-alive 了解吗?
keep-alive作为一种vue的内置组件,主要作用是缓存组件状态。当需要组件的切换时,不用重新渲染组件,避免多次渲染,就可以使用keep-alive包裹组件。。主要有以下几个属性:
include:指定需要缓存的组件名称,可以是字符串或正则表达式。exclude:指定不需要缓存的组件名称,可以是字符串或正则表达式。max:指定最大缓存组件数量,超过这个数量后,最早创建的组件实例将会被销毁。
3. Vue项目中你是如何解决跨域的呢?
跨域是指在浏览器中,当一个网页的资源(如脚本、样式表、字体、图片等)请求另一个域名下的资源时,由于浏览器的同源策略限制,请求会被阻止。跨域问题需要通过使用 CORS(跨域资源共享)、JSONP(JSON with Padding)或代理服务器等方式进行解决。
在 vue 项目中,我们主要针对 CORS 或 Proxy 这两种方案进行展开
CORS:
- CORS (Cross-Origin Resource Sharing,跨域资源共享)是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应;
- CORS 实现起来非常方便,只需要增加一些 HTTP 头,让服务器能声明允许的访问来源;
- 只要后端实现了 CORS,就实现了跨域;
Proxy:
代理(Proxy)也称网络代理,是一种特殊的网络服务,允许一个(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有利于保障网络终端的隐私或安全,防止攻击
-
方案一:
- 如果是通过
vue-cli脚手架工具搭建项目,我们可以通过webpack为我们起一个本地服务器作为请求的代理对象 - 通过该服务器转发请求至目标服务器,得到结果再转发给前端,但是最终发布上线时如果web应用和接口服务器不在一起仍会跨域
- 在
vue.config.js文件,新增以下代码:
javascriptmodule.exports = { devServer: { host: '127.0.0.1', port: 8084, open: true,// vue项目启动时自动打开浏览器 proxy: { '/api': { // '/api'是代理标识,用于告诉node,url前面是/api的就是使用代理的 target: "http://xxx.xxx.xx.xx:8080", //目标地址,一般是指后台服务器地址 changeOrigin: true, //是否跨域 pathRewrite: { // pathRewrite 的作用是把实际Request Url中的'/api'用""代替 '^/api': "" } } } } }通过axios发送请求中,配置请求的根路径
javascriptaxios.defaults.baseURL = '/api' - 如果是通过
-
方案二:
还可通过
node服务端实现代理请求转发,以express框架为例javascriptvar express = require('express'); const proxy = require('http-proxy-middleware') const app = express() app.use(express.static(__dirname + '/')) app.use( '/api', proxy({ target: 'http://localhost:4000', changeOrigin: false }) ); module.exports = app -
方案三:通过
nginx实现代理
4. vue3有了解过吗?能说说跟vue2的区别吗?
vue3 简要的理解:利用新的语言特性(es6)、解决架构问题,主要以下新特性:
- 速度更快
- 重写了虚拟
Dom实现 - 编译模板的优化
- 更高效的组件初始化
update性能提高1.3~2倍SSR速度提高了2~3倍
- 重写了虚拟
- 体积减少:通过
webpack的tree-shaking功能,可以将无用模块"剪辑",仅打包需要的 - 更易维护
- 使用
composition Api - 可与现有的
Options API一起使用 Vue3模块可以和其他框架搭配使用
- 使用
- 更接近原生:可以自定义渲染 API
- 更易使用
- 新特性:
fragments一个组件下支持多个跟节点 - 生命周期改变
React
1. 请解释 React 中虚拟 DOM 的工作原理,以及它相比直接操作真实 DOM 有哪些优势?
-
虚拟 DOM 的工作原理可以概括为三个步骤
- 创建虚拟 DOM 树:当 React 组件运行时,会创建一棵轻量的 JavaScript 对象树来模拟真实 DOM 结构
- Diff 算法比较差异:当组件状态发生变化时,React 会生成一棵新的虚拟 DOM 树,然后通过高效的 Diff 算法与旧的虚拟 DOM 树进行比较,找出需要更新的最小变化
- 批量更新真实 DOM:React 将计算出的差异一次性批量更新到真实 DOM 上,而不是每次变化都立即操作 DOM
-
相比直接操作真实 DOM 的优势
-
性能优化
- 直接操作真实 DOM 代价高昂(DOM 操作是 JavaScript 引擎和渲染引擎的跨界操作)
- 虚拟 DOM 在内存中进行比较和计算,避免频繁操作真实 DOM
- 批量更新策略减少浏览器重绘和重排次数
-
开发体验提升
- 声明式编程:开发者只需关注状态变化,无需手动操作 DOM
- 代码更易维护和理解,减少调试时间
-
跨平台能力
- 虚拟 DOM 是抽象层,可以渲染到不同平台(React Native 渲染到原生组件)
- 同一套逻辑可同时用于 Web、移动端等
-
更好的可测试性
- 虚拟 DOM 是纯 JavaScript 对象,可以在 Node.js 环境中进行测试
- 无需依赖真实浏览器环境
-
2. React 有哪些内置 Hooks ?
-
状态管理 Hooks
- useState: 用于在函数组件中添加局部状态。
- useReducer: 用于管理复杂的状态逻辑,类似于 Redux 的 reducer。
-
副作用 Hooks
- useEffect: 用于在函数组件中执行副作用操作(如数据获取、订阅、手动 DOM 操作等)。
- useLayoutEffect: 与 useEffect 类似,但在 DOM 更新后同步执行,适用于需要直接操作 DOM 的场景。
-
上下文 Hooks
- useContext: 用于访问 React 的上下文(Context)。
-
引用 Hooks
- useRef: 用于创建一个可变的引用对象,通常用于访问 DOM 元素或存储可变值。
-
性能优化 Hooks
- useMemo: 用于缓存计算结果,避免在每次渲染时都重新计算。
- useCallback: 用于缓存回调函数,避免在每次渲染时都创建新的回调。
-
其他 Hooks
- useDeferredValue: 延迟更新 UI 的某些部分。
- useActionState: 根据某个表单动作的结果更新 state。
- useImperativeHandle: 用于自定义暴露给父组件的实例值,通常与 forwardRef 一起使用。
- useDebugValue: 用于在 React 开发者工具中显示自定义 Hook 的标签。
- useOptimistic 帮助你更乐观地更新用户界面
- useTransition: 用于标记某些状态更新为"过渡"状态,允许你在更新期间显示加载指示器。
- useId: 用于生成唯一的 ID,可以生成传递给无障碍属性的唯一 ID。
- useSyncExternalStore: 用于订阅外部存储(如 Redux 或 Zustand)的状态。
- useInsertionEffect: 为 CSS-in-JS 库的作者特意打造的,在布局副作用触发之前将元素插入到 DOM 中
3. React 组件通讯方式有哪些?
-
通过props向子组件传递数据
javascript//父组件 const Parent = () => { const message = 'Hello from Parent' return <Child message={message} /> } // 子组件 const Child = ({ message }) => { return <div>{message}</div> } -
通过回调函数向父组件传递数据
javascript//父组件 const Parent = () => { const handleData = (data) => { console.log('Data from Child:', data) } return <Child onSendData={handleData} /> } // 子组件 const Child = ({ message }) => { return <button onClick={() => onSendData('Hello from Child')}>Send Data</button> } -
使用refs调用子组件暴露的方法
javascriptimport React, { useRef, forwardRef, useImperativeHandle } from 'react' // 子组件 const Child = forwardRef((props, ref) => { // 暴露方法给父组件 useImperativeHandle(ref, () => ({ sayHello() { alert('Hello from Child Component!') }, })) return <div>Child Component</div> }) // 父组件 function Parent() { const childRef = useRef(null) const handleClick = () => { if (childRef.current) { childRef.current.sayHello() } } return ( <div> <Child ref={childRef} /> <button onClick={handleClick}>Call Child Method</button> </div> ) } export default Parent -
通过Context进行跨组件通信
-
使用状态管理库进行通信
- React Context + useReducer
- Redux:使用
Redux Toolkit简化 Redux 开发。
4. 讲诉 React 组件生命周期?
React 组件生命周期分为以下三个阶段。
挂载阶段:这是组件首次被创建并插入到 DOM 中的阶段。
更新阶段:当组件的 props 或 state 发生变化时,就会触发更新阶段。
卸载阶段:组件从 DOM 中移除时进入卸载阶段。
函数组件是没有明确的生命周期方法,但可以通过 useEffect 来模拟生命周期行为。
模拟挂载阶段的生命周期方法:
-
只需要在
useEffect的依赖数组中传入一个空数组[]。这样,该副作用只会在组件挂载后运行一次。javascriptuseEffect(() => { console.log('代码只会在组件挂载后执行一次') }, [])
模拟更新阶段的生命周期方法:
-
通过将依赖项放入依赖数组中,
useEffect可以在依赖项更改时执行。如果你省略了依赖数组,副作用将在每次渲染后执行。javascript// 注意这里没有提供依赖数组 useEffect(() => { console.log('代码会在组件挂载后以及每次更新后执行') }) // 特定依赖更新时执行 useEffect(() => { console.log('代码会在 count 更新后执行') }, [count])
模拟卸载阶段的生命周期方法:
-
在
useEffect的函数中返回一个函数,该函数会在组件卸载前执行。javascriptuseEffect(() => { return () => { console.log('代码会在组件卸载前执行') } }, [])
微信小程序
1. WXML 和 HTML 的区别?
- WXML 是小程序设计的一套标签语言,用来构建小程序页面,作用类似网页中的 html;
- 都是用来描述页面的结构;
- 都由标签和属性等构成;
- 标签名字不一样,小程序标签更少,单一标签多;
- 小程序多了一些
wx:if这样的属性以及{{}}这样的表达式; - WXML仅能在微信小程序开发者工具中预览,而HTML可以在浏览器内预览;
- 组件封装不同,WXML对组件进行了重新封装;
- 小程序运行在 JS Core 内,没有 DOM 树和 window 对象,小程序中无法使用window 对象和 document 对象
2. WXSS 和 CSS 的区别?
- 新增了rpx尺寸单位,css中需要手动进行像素单位换算,例如rem;
- wxss支持新的尺寸rpx,在不同大小的屏幕上小程序会自动进行换算;
- 提供了全局样式和局部样式,项目根目录中的app.wxss会作用于所有小程序页面,局部页面的.wxss样式仅对当前页面生效;
- wxss仅支持部分css选择器:
- 类选择器,id选择器;
- 元素选择器;
- 并集选择器,后代选择器;
- ::after 和 ::before等伪类选择器;
3.小程序有哪些常用的生命周期?
- onLoad:页面触发时候加载,只会被调用一次,可以在此方法中获取页面的参数并进行初始化操作;
- onShow:每次页面打开的时候触发,可以在此方法中更新页面数据;
- onReady:页面初始化完成时触发,只会被调用一次,可以在此方法中处理页面渲染后的操作;
- onHide:页面隐藏时触发,当页面被其他页面遮挡或者关闭时被调用,在此方法中清除定时器等资源;
- onUnload:页面卸载时触发,当页面被销毁时会被调用,可以在此方法中清除事件监听、定时器等资源;
- onPullDownRefresh:用户下拉刷新时触发,可以在此方法中执行数据刷新操作;
- onReachBottom:页面滚动到底部时触发,可以在此方法中加载更多数据;
- onShareAppMessage:用户点击分享按钮时触发,可以在此方法中自定义分享内容;
- onPageScroll:页面滚动时触发,可以在此方法中实现滚动效果等操作;
4. 小程序有哪几个文件,作用是什么?
- app.js:全局配置文件,可以在此文件中定义小程序的生命周期和方法,全局变量等;
- app.json:全局配置文件,可以在此文件中定义小程序的页面路径、窗口背景色、导航栏样式;
- app.wxss:全局样式文件,可以在此文件中定义小程序的公共样式;
- project.config.json:项目配置文件,用的最多的就是配置是否开启 https 校验;
- page.js:页面逻辑文件,定义页面的数据、逻辑、事件函数;
- page.json:页面配置文件,定义页面窗口背景颜色、导航颜色、是否开启下拉刷新;
- page.wxml:页面结构文件,编写页面布局和结构;
- page.wxss:页面样式文件,编写页面样式;
5. 小程序的优劣势?
优势:
- 便捷性:用户可以直接在微信打开小程序访问,不需要下载或手动更新;
- 跨平台支持:微信小程序可以在 iOS 和 Android、桌面端应用打开;
- 快速发布更新:开发者可以微信小程序开发平台快速发布和更新,无需应用商店审核;
- 社交分享能力:可以通过通过内置的分享功能,分享给好友、群组、朋友圈,扩大转播范围;
- 微信生态系统:提供庞大的用户基础和社交系统,小程序可以和微信功能进行使用,例如:支付、定位、消息,给用户更好的体验;
劣势:
- 功能限制:相比于原生应用程序,微信小程序在功能和性能方面存在一定的限制。由于需要在微信平台上运行,小程序无法直接访问设备的底层功能和硬件资源,对于一些高度定制化或需要复杂计算的应用场景可能有所局限;
- 依赖微信平台:微信小程序完全依赖于微信平台,对于没有安装微信或使用其他社交媒体平台的用户来说,无法直接访问和使用小程序;
6. 哪些方法可以提高小程序的应用速度?
- 提高页面加载速度
- 用户行为预测
- 减少默认data的大小
- 组件化方案
7. 小程序有哪几种跳转方式?
- wx.switchTab:跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面;
- wx.reLaunch:关闭所有页面,打开到应用内的某个页面
- wx.redirectTo:关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面;
- wx.navigateTo:保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面。使用 wx.navigateBack 可以返回到原页面。小程序中页面栈最多十层;
- wx.navigateBack 关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages 获取当前的页面栈,决定需要返回几层;
工程类
react 和 vue 最大的区别
相同点:
- 都有组件化思想
- 都支持服务器端渲染
- 都有Virtual DOM(虚拟dom)
- 数据驱动视图
- 都有支持跨平台的方案:Vue 的 weex、React 的React native
- 都有自己的构建工具:Vue 的 vue-cli、React 的 Create React App
不同点:
- 数据流向的不同。
react从诞生开始就推崇单向数据流,而Vue是双向数据流; - 数据变化的实现原理不同。
react使用的是不可变数据,而Vue使用的是可变的数据; - 组件化通信的不同。
react中我们通过使用回调函数来进行通信的,而Vue中子组件向父组件传递消息有两种方式:事件和回调函数; diff算法不同。react主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。Vue 使用双向指针,边对比,边更新DOM;
前端网页性能优化?
-
- 压缩和合并资源:压缩 CSS、JavaScript 和 HTML 文件,以减少文件大小,同时合并多个文件为一个,减少网络请求的数量;
-
- 图片优化:使用适当的图片格式(如 JPEG、PNG、WebP)和压缩级别,并设置合适的图片尺寸,以减小图片文件大小;
-
- 懒加载:延迟加载非关键内容,如图片、视频等,在用户需要时再加载,减少首次加载的资源量;
-
- 缓存机制:使用浏览器缓存机制,通过设置合适的缓存策略(如 Cache-Control、ETag、Last-Modified)来缓存静态资源,减少重复请求;
-
- DNS 预解析:通过使用
<link rel="dns-prefetch">标签来预解析域名,加速 DNS 解析过程;
- DNS 预解析:通过使用
-
- 减少重定向:避免不必要的重定向,确保网页直接加载到最终目标地址;
-
- 使用 CDN 加速:将静态资源部署至全球分布的 CDN(内容分发网络),使资源更接近用户,加快访问速度;
-
- 异步加载脚本:将 JavaScript 脚本标记为
async或defer,在页面加载时异步加载脚本,避免阻塞渲染过程;
- 异步加载脚本:将 JavaScript 脚本标记为
-
- 优化 CSS 和 JavaScript:减少 CSS 和 JavaScript 文件的大小,移除无用的代码,并使用压缩工具进行压缩;
-
- 最小化 DOM 操作:DOM 操作是昂贵的,减少频繁的 DOM 操作,尽量使用批量操作和文档片段(DocumentFragment);
-
- 使用 Web Workers:将一些计算密集型或长时间运行的任务放到 Web Workers 中,以避免主线程阻塞;
-
- 延迟执行加载项:将不影响页面显示的 JavaScript、CSS 等内容延迟加载,提高首次渲染速度;
-
- 使用字体图标或 SVG 替代图片:减少网络请求,提高页面加载速度;
-
- 前后端分离:采用前后端分离架构,通过 API 请求获取数据,实现前端与后端的解耦和并行开发;