HTML/CSS 部分
盒模型
介绍标准盒模型和 IE 盒模型的区别,以及如何通过 CSS 设置盒模型的属性来实现不同的布局效果。
区别:
- 标准盒模型中,
width
和height
仅指内容部分,总宽(高)由内容、padding
、border
、margin
组成。 - IE 盒模型里,
width
和height
包含内容、padding
与border
,总宽(高)再加上margin
。
CSS 设置盒模型属性实现布局效果
设置盒模型类型:用box-sizing
,content-box
为标准盒模型,border-box
为 IE 盒模型。如box-sizing: border-box;
可使width
包含padding
与border
,方便布局。
设置宽高、内外边距与边框:
- `width`和`height`可用绝对或相对单位设置。
- `padding`控制内容与边框距离,可分向设置或缩写。
- `border`设置边框样式、宽度与颜色,有缩写形式。
- `margin`控制元素间距,也可分向设置或缩写,`margin: 0 auto;`可水平居中元素。
结合display
属性布局:
display: block
块级元素独占一行,可通过盒模型属性控制垂直布局。display: inline
内联元素不独占行,默认width
等部分盒模型属性不影响布局。display: inline-block
兼具两者特点,可设置盒模型属性并在一行内排列。
重排/重绘
- 样式的调整会引起重绘,比如字体颜色、背景色调整等
- Dom的变动会引起重排,比如定位改动、元素宽高调整
优化:避免循环插入dom,比如table的行。可以js循环生成多个dom后,一次性插入。
CSS 选择器及其优先级
- 标签选择器:如
p
,选择所有<p>
标签。 - 类选择器:以
.
开头,像.classname
,选择带有指定类名的元素。 - ID 选择器:以
#
开头,如#idname
,选择具有特定 ID 的元素。 - 后代选择器:用空格分隔,如
parent child
,选择父元素下的子元素。 - 子选择器:用
>
,如parent>child
,选择父元素的直接子元素。 - 相邻兄弟选择器:用
+
,如sibling1 + sibling2
,选择紧挨着的兄弟元素。 - 通用兄弟选择器:用
~
,如sibling1~sibling2
,选择后面的兄弟元素。
优先级:内联样式 > id选择器 > 类选择器、属性选择器和伪类选择器 > 标签内选择器和伪元素选择器 > 通配符选择器 > 计算方式
CSS 布局方式
浮动布局(Float)
特点:
- 元素浮动后会脱离文档流,向左或向右移动,直到碰到父元素的边缘或另一个浮动元素。它会使后面的非浮动元素围绕在它的周围,可能会导致布局混乱,需要清除浮动来恢复正常的文档流布局。
- 只能实现简单的左右布局,如两列或多列布局,其中列的高度会根据内容自动撑开。
适用场景
- 用于创建简单的文字环绕图片效果,或者实现早期网页中基本的多列布局,如新闻列表页面的布局,左边是新闻标题列表,右边是广告等内容。
定位布局(Position)
特点
- 有
static
(默认,按照正常文档流布局)、relative
(相对自身原来位置定位)、absolute
(相对于最近的非static
祖先元素定位)、fixed
(相对于浏览器窗口定位)和sticky
(在滚动时,先在正常位置,达到某个阈值后固定)几种定位方式。 absolute
和fixed
定位会使元素脱离文档流,可能会影响其他元素的布局。relative
定位不会脱离文档流,sticky
结合了相对和固定定位的特点。
适用场景
- 用于制作弹出式菜单、模态框等需要精确控制元素位置的场景。例如,一个模态框可以使用
fixed
定位在浏览器窗口中心显示,或者制作导航栏固定在页面顶部(使用fixed
定位)。
弹性布局(Flexbox)
特点
- 基于弹性容器(
flex - container
)和弹性子元素(flex - items
)的概念。弹性容器可以控制子元素的排列方向(flex - direction
)、对齐方式(align - items
、justify - content
等)。 - 能够方便地实现水平和垂直方向的对齐,并且子元素可以根据容器的大小自动调整大小和间距,具有很好的响应性。
适用场景
- 适用于复杂的页面布局,如导航栏、产品列表、表单布局等。例如,在一个响应式的导航栏中,可以使用弹性布局让菜单项在不同屏幕尺寸下均匀分布或者自适应排列。
网格布局(Grid Layout)
响应式设计
响应式设计概念和重要性
- 概念:是一种网页设计理念,能让网站在不同设备(桌面、平板、手机)的屏幕尺寸、分辨率等条件下都呈现良好的布局和用户体验。
- 重要性:适应多种设备访问,提高用户体验,增加网站流量和搜索引擎排名。
CSS 媒体查询使用方法
- 语法结构:
@media
(媒体类型)和(媒体特性){CSS 规则}。例如,@media screen and (max - width: 768px)
用于屏幕设备且最大宽度为 768px 时的样式调整。 - 调整布局和样式:在媒体查询内修改元素的尺寸、显示隐藏、排列方式等来适配不同设备。
常见响应式设计框架和工具
- Bootstrap:通过类名定义布局,如
col-sm-
、col-md-
用于不同屏幕断点的列布局。 - Tailwind CSS:基于原子化 CSS,通过组合实用类来构建响应式样式,例如
sm:flex
用于小屏幕下的弹性布局。
CSS 动画和过渡效果
CSS 动画和过渡属性方法
- 过渡(transition) :用于在元素状态变化时产生平滑过渡效果。包括
transition-property
(指定过渡的 CSS 属性)、transition-duration
(过渡持续时间)、transition-timing-function
(过渡速度曲线)和transition-delay
(过渡延迟时间)。 - 动画(@keyframes) :通过定义关键帧来创建复杂动画。可以指定起始、结束以及中间关键帧的样式,配合
animation
属性使用,animation
包含animation-name
(关联 @keyframes 规则)、animation-duration
、animation-timing-function
、animation-delay
、animation-iteration-count
(动画循环次数)和animation-direction
(动画方向)。
提升交互和视觉效果
- 用于制作按钮悬停效果、元素的显示 / 隐藏动画、滑动菜单、加载动画等,吸引用户注意,增强交互体验。
浏览器兼容性处理
- 对于旧版本浏览器,使用浏览器前缀(如
-webkit-
、-moz-
)来确保属性被识别。可以使用工具(如 Autoprefixer)自动添加前缀。同时,进行充分的测试,确保在主流浏览器中的效果一致。
如果要做优化,CSS提高性能的方法有哪些?
- 内联首屏关键CSS
- 异步加载CSS
- 资源压缩
- 合理使用选择器
- 减少使用昂贵的属性
- 不要使用@import
JavaScript 部分
数据类型
数据类型列举
- 基本数据类型:Number(数字)、String(字符串)、Boolean(布尔值)、Null(空值)、Undefined(未定义)、Symbol(唯一值)。
- 引用数据类型:Object(对象)、Array(数组)、Function(函数)。
区别与存储方式
- 基本数据类型:值直接存储在栈内存中,占用空间固定,访问速度快。
- 引用数据类型:在栈内存中存储引用地址,实际数据存储在堆内存,占用空间不固定,访问时先找栈中的地址再找堆中的数据。
判断数据类型方法
- 使用
typeof
(基本数据类型有效,对引用数据类型中的函数也有效)、instanceof
(判断对象是否属于某个构造函数的实例)、Object.prototype.toString.call()
(准确判断各种数据类型)。
es6新特性
1、let 和 const
- let 表示申明变量。const 表示申明常量
- 常量定义了就不能改了。对象除外,因为对象指向的地址没变。
- const在申明是必须被赋值。
- 两者都为块级作用域。
2、模板字符串
3、解构
4、函数的默认值
5、Spread / Rest 操作符,三个点…
6、箭头函数
7、for of
- for of遍历的是键值对中的值
- for in遍历的是键值对中的键
8、class类,原型链的语法糖表现形式
9、导入导出
- 导入improt
- 导出export default
10、promise
- Promise 用于更优雅地处理异步请求。
11、async/await
- 比promise更好的解决了回调地狱
12、Symbol,新的基本类型
13、Set集合
-
存储任何类型的唯一值,即集合中所保存的元素是不重复的。类数组结构。
-
let arrNew = new Set(待去重的数组)
[...new Set(arr)]
-
new Set(arr)
:Set
是 JavaScript 中的一种数据结构,它类似于数组,但成员的值都是唯一的,即不会有重复的值。这里通过new
关键字创建了一个Set
实例,构造函数接收一个可迭代对象(例如数组arr
)作为参数,它会自动去除参数中的重复元素,比如数组[1, 2, 2, 3]
传入后,在Set
中就会变成{1, 2, 3}
(这里用类似对象的形式展示方便理解其唯一性,实际不是对象)。
-
[...new Set(arr)]
:...
是展开运算符。在这里它的作用是将new Set(arr)
生成的Set
实例中的元素展开,然后再用方括号[]
将展开后的元素包裹起来,最终的结果就是将原本的数组arr
去除重复元素后,转换为一个新的数组。
防抖和节流
-
防抖(Debounce)
-
概念:在事件被触发后,延迟一定时间后再执行回调函数,如果在延迟时间内事件又被触发,则重新计算延迟时间。
-
应用场景:
- 搜索框输入查询:当用户在搜索框输入内容时,避免频繁发送请求。只有在用户停止输入一段时间后,才执行搜索请求,减少不必要的请求次数。
- 窗口大小调整:当窗口大小改变时,延迟执行重新布局或重绘操作,直到用户停止调整窗口大小,防止短时间内大量的重复计算。
-
-
节流(Throttle)
-
概念:在规定时间内,只允许事件触发一次回调函数。即事件触发后,在一定时间间隔内,不管事件触发多少次,都只执行一次回调函数。
-
应用场景:
- 滚动加载:当页面滚动时,按照一定的时间间隔加载更多内容,避免滚动过程中频繁加载导致性能问题。
- 按钮点击:限制按钮在短时间内被多次点击,例如提交表单按钮,防止重复提交。
-
作用域和闭包
作用域概念
- 全局作用域:在函数外部定义的变量拥有全局作用域,在代码任何地方都能访问。
- 函数作用域:函数内部定义的变量,只能在函数内部访问。
- 块级作用域:由
{}
包裹的代码块形成,如let
和const
声明的变量作用域限制在块内。
变量提升原理和表现形式
- 变量和函数声明会在代码执行前被提升到所在作用域的顶部。函数声明会完全提升,变量声明只是提升声明部分,赋值留在原地。表现为可以在声明前访问变量(值为
undefined
)或函数。
闭包概念、形成条件和应用场景
闭包是有权访问另一个函数作用域中变量的函数,内部函数可访问外部函数变量,外部函数执行完,变量也不会被回收。
- 概念:函数和其词法环境(包含函数外部变量)的组合。
- 形成条件:函数嵌套,内部函数引用外部函数的变量。
- 应用场景:实现数据私有、函数柯里化、防抖和节流等。
闭包在内存管理方面的注意问题
- 闭包会使外部函数的变量不会被垃圾回收,可能导致内存泄漏。要注意合理释放引用,避免不必要的闭包。
深浅拷贝
-
概念
- 浅拷贝:只复制对象的第一层属性,如果属性是引用类型,那么复制的是引用,原对象和拷贝对象的引用类型属性会指向同一个内存地址。
- 深拷贝:完全复制一个对象,包括对象中的所有嵌套层次的属性,原对象和拷贝对象相互独立,修改其中一个不会影响另一个。
-
浅拷贝实现方式
- Object.assign() :用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,例如
let newObj = Object.assign({}, oldObj);
。 - 展开运算符(...) :如
let newObj = {...oldObj};
,它可以快速复制一个对象的第一层属性。
- Object.assign() :用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,例如
-
深拷贝实现方式
- JSON.parse(JSON.stringify()) :先将对象转换为 JSON 字符串,再将字符串转换回对象,例如
let newObj = JSON.parse(JSON.stringify(oldObj));
。但它有局限性,不能处理函数、循环引用等情况。 - 递归实现:通过自定义函数递归地复制对象的每个属性,适用于处理复杂对象,包括含有函数、循环引用等情况。
- JSON.parse(JSON.stringify()) :先将对象转换为 JSON 字符串,再将字符串转换回对象,例如
原型和原型链
原型和原型链概念
- 原型:每个对象都有一个原型对象,它是对象的属性和方法的共享来源。
- 原型链:对象通过
__proto__
属性连接到其原型对象,多个对象的这种连接形成的链式结构就是原型链,用于查找属性和方法。
通过原型链实现继承
- 子对象的原型设置为父对象,子对象就可以继承父对象原型上的属性和方法,查找属性时会沿着原型链向上查找。
原型方法和实例方法区别与使用方式
- 原型方法:定义在原型对象上,所有实例共享,节省内存,通过
prototype
属性添加。 - 实例方法:在构造函数内部通过
this
定义,每个实例都有独立的一份,适用于需要每个实例有不同行为的情况。
__proto__和 prototype 属性关系
prototype
是构造函数的属性,用于构建实例的原型;__proto__
是对象的属性,指向其原型对象,实例的__proto__
属性指向构造函数的prototype
。
异步编程
异步编程概念
- JavaScript 异步编程用于处理不阻塞主线程的任务,如网络请求、文件读取等。
方式及用法
- 回调函数:作为参数传递给异步函数,在异步操作完成后被调用。优点是简单,缺点是容易产生回调地狱。
- Promise:代表一个异步操作的最终完成或失败及其结果值。通过
then
处理成功,catch
处理失败。链式调用可避免回调地狱。 - async/await:
async
函数返回一个 Promise,await
暂停函数执行等待 Promise 解决。让异步代码看起来像同步代码,提高可读性。
Promise.all和Promise.race的区别,应用场景
-
区别
- Promise.all:接受一组 Promise,全部成功时返回包含所有成功结果的新 Promise,有一个失败就返回失败 Promise(结果是第一个失败原因)。
- Promise.race:接受一组 Promise,返回第一个完成(成功 / 失败)的 Promise 的结果。
-
应用场景
- Promise.all:用于并发加载多个资源,如同时获取多个接口数据,全部成功后统一处理。
- Promise.race:用于竞速场景,像请求多个镜像服务器资源,返回最快响应的数据。
错误和异常处理
- 回调函数通过在回调中检查错误参数处理;Promise 用
catch
;async/await
在try/catch
块中处理。
合理选择和运用
- 简单异步场景可用回调函数;多个异步操作有依赖关系用 Promise 或
async/await
。选择async/await
可让代码更易读,性能上避免不必要的嵌套,高效利用资源。
事件循环机制
事件循环机制
- JavaScript 是单线程语言,通过事件循环来处理异步任务。
- 宏任务:包括
script
(整体代码)、setTimeout
、setInterval
、I/O
操作等。 - 微任务:如
Promise.then
、MutationObserver
等,优先级高于宏任务。
执行顺序
- 先执行一个宏任务,执行过程中如果产生微任务则加入微任务队列,宏任务执行完后,清空微任务队列,然后再执行下一个宏任务。
单线程下的异步操作和非阻塞 I/O
- 异步操作被放在任务队列中,主线程继续执行其他任务,等异步操作完成后通过事件循环机制处理,实现非阻塞 I/O。
阻塞原因和解决方法
- 阻塞原因:长时间运行的同步代码、复杂的计算任务等。
- 解决方法:将长时间任务拆分成异步任务,使用
Web Workers
(用于复杂计算),优化算法以减少计算量。
继承
- 原型继承
- 原型继承是 JavaScript 中最基本的继承方式,通过将一个对象的原型设置为另一个对象来实现继承。
function Parent() {
this.name = 'Parent';
}
Parent.prototype.sayHello = function() {
console.log('Hello from Parent');
};
function Child() {
this.name = 'Child';
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
const child = new Child();
child.sayHello(); // 输出:Hello from Parent
- 构造函数继承
- 构造函数继承通过在子类构造函数中调用父类构造函数来实现继承。这种方式可以实现实例属性的继承,但无法继承父类原型上的方法。
function Parent(name) {
this.name = name;
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
const child = new Child('Child', 10);
console.log(child.name); // 输出:Child
- ES6 Class 继承
- ES6 引入了 Class 语法,提供了更加简洁和易读的继承语法。
class Parent {
constructor(name) {
this.name = name;
}
sayHello() {
console.log('Hello from Parent');
}
}
class Child extends Parent {
constructor(name, age) {
super(name);
this.age = age;
}
}
const child = new Child('Child', 10);
child.sayHello(); // 输出:Hello from Parent
MVVM模式
-
定义
- MVVM(Model - View - ViewModel)是一种软件架构设计模式。它主要用于构建用户界面,分离数据(Model)、视图(View)和连接两者的视图模型(ViewModel)。
-
组件作用
- Model:代表数据和业务逻辑,如数据的获取、存储和处理规则。
- View:是用户看到和交互的界面,如 HTML 页面和 CSS 样式。
- ViewModel:连接 Model 和 View,负责数据的转换、传递和处理用户交互。它监听 Model 的变化并更新 View,也接收 View 的用户输入并修改 Model。
-
优点
- 分离关注点:让开发人员可以专注于不同部分,提高代码的可维护性和可读性。
- 数据绑定:自动更新视图,减少了手动操作 DOM 的复杂性,提高开发效率。
-
在前端框架中的应用:如 Vue.js 和 Angular 就是基于 MVVM 模式构建的框架,在这些框架中,开发者可以方便地利用数据绑定和指令来构建响应式的用户界面。
webpack
-
定义
- Webpack 是一个模块打包工具,它能把项目中的各种模块(如 JavaScript、CSS、图片等)按照依赖关系打包成静态资源。
-
核心概念
- 入口(entry) :指定打包的起点文件,Webpack 从这里开始分析模块依赖关系。
- 输出(output) :定义打包后的文件存放位置和文件名。
- 加载器(loader) :用于处理非 JavaScript 文件,像用 CSS - loader 处理 CSS 文件,使 Webpack 能够理解并打包它们。
- 插件(plugin) :可以执行更复杂的任务,如压缩代码、分离 CSS 文件等,例如 Mini - CSS - Extract - Plugin 用于提取 CSS 到单独文件。
-
作用和优势
- 优化加载性能,通过代码分割将代码分成小块,按需加载。同时能转换 ES6 等新语法,让浏览器更好地兼容。并且方便管理模块,避免全局变量冲突。
-
使用场景
- 构建大型前端项目,无论是单页应用(SPA)还是多页应用(MPA),Webpack 都能有效组织资源,提升开发效率和项目性能。
浏览器存储
- localStorage:永久保存,以键值对保存,存储空间5M
- sessionStorage:关闭页签/浏览器时清空
- cookie:随着请求发送,通过设置过期时间删除
- session:保存在服务端
Vite
-
定义
- Vite 是一个新型的前端构建工具,主要用于快速开发和构建现代 Web 应用。
-
核心特点
- 快速冷启动:利用浏览器原生的 ES 模块支持,在开发时不需要打包全部文件,仅在浏览器请求模块时进行转换和提供,大大加快了启动速度。
- 即时热更新(HMR) :能在代码修改后迅速更新模块,保持应用状态,提供高效的开发体验。
-
与 Webpack 的区别
- 相较于 Webpack 的打包式开发,Vite 在开发阶段按需加载,减少等待打包的时间。但 Webpack 生态更成熟,在复杂的大型项目构建和优化上有诸多插件和工具。
-
应用场景
- 适合开发 Vue、React 等现代前端框架的应用,尤其是在开发阶段对启动速度和热更新要求较高的项目,能显著提高开发效率。
Vue 和 react的区别
-
编程范式
- Vue:采用渐进式框架,易于上手。可以在一个 HTML 文件中逐步引入 Vue,使用模板语法(如
{{}}
)来渲染数据,对于初学者比较友好。 - React:是一个用于构建用户界面的 JavaScript 库,基于函数式编程,更强调组件化,一切都是 JavaScript,使用 JSX 语法来描述 UI,灵活性较高。
- Vue:采用渐进式框架,易于上手。可以在一个 HTML 文件中逐步引入 Vue,使用模板语法(如
-
数据绑定方式
- Vue:使用双向数据绑定,通过
v - model
指令可以方便地在表单元素和数据之间建立双向的关联,数据的变化会自动更新视图,视图的变化也会自动反馈到数据。 - React:是单向数据流,数据从父组件流向子组件,通过状态(
state
)和属性(props
)来传递数据,子组件不能直接修改父组件传递过来的数据。
- Vue:使用双向数据绑定,通过
-
组件化概念
- Vue:组件是一个包含模板、脚本和样式的完整单元。可以通过
Vue.component
或单文件组件(.vue
)来定义组件,组件间通信有多种方式,如props
、emit
、vuex
等。 - React:组件主要通过函数或者类来定义,函数组件更加轻量简洁。组件通信主要通过
props
传递数据和useContext
等来实现,对于状态管理可以使用Redux
或Context API
。
- Vue:组件是一个包含模板、脚本和样式的完整单元。可以通过
-
渲染性能优化
- Vue:内部通过虚拟 DOM 和依赖追踪来自动优化渲染,当数据变化时,能够精准地更新需要改变的部分。
- React:需要手动使用
shouldComponentUpdate
或者React.memo
等方法来避免不必要的渲染,以提升性能。
-
生态系统和社区
- Vue:生态系统相对较小,但在中国市场有广泛的应用,文档清晰易懂,插件和组件库(如 Element - UI)也很丰富。
- React:生态系统庞大,有更多的第三方库和工具可供选择,在国际上更受欢迎,Facebook 等大公司的支持使得其在大型项目中的应用更为广泛。
Vue 部分
Vue.js 是构建用户界面的渐进式 JavaScript 框架,专注视图层。通过数据绑定,能自动更新 DOM。组件化是其核心,便于大型应用开发,也易与其他库集成。
Vue 的响应式原理
Vue 2 主要通过Object.defineProperty
来实现响应式。将数据对象的属性转为getter/setter
,getter
收集依赖,setter
在属性修改时通知更新。Vue 3 使用Proxy
,可代理整个对象,拦截操作来更新依赖。
vue首屏优化
- 使用较轻量的组件,比如echart对应有vue-chart
- vue-cli开启打包压缩 和后台配合 gzip访问;
- 路由懒加载,分包;
- 打包时配置删掉log日志
- 资源过大可以使用cdn模式引入,不再打包到本地
Vue 组件通信
Vue 组件之间有哪些通信方式?
- 有
props
和$emit
(父子组件通信)、EventBus
事件总线(非父子组件通信)、Vuex(大型应用复杂状态管理和通信)、provide/inject
(跨层级组件通信)。
如何在兄弟组件之间进行通信?
- 可以使用事件总线(创建事件中心实例,一个组件
$emit
发布事件,另一个$on
监听)或者 Vuex(通过store
的dispatch
和state
来修改和获取状态)。
指令相关
v - model
是语法糖。如在input
元素上,相当于:value
和@input
组合,双向绑定数据和表单元素的值。
v - if/v - else - if/v - else
用于条件渲染;v - show
控制显示隐藏(通过display
属性);v - for
循环渲染列表;v - bind
(缩写:
)动态绑定属性;v - on
(缩写@
)监听 DOM 事件。
为什么使用key
做一个唯一标识, Diff 算法就可以正确的识别此节点。作用主要是为了高效的更新虚拟 DOM。
keep-alive
-
定义和作用
keep - alive
是 Vue 内置的一个抽象组件,用于缓存组件实例。当组件在keep - alive
包裹下切换时,组件的状态不会被销毁,再次显示时可以保持之前的状态。
-
缓存机制
- 它会缓存被包裹组件的
DOM
结构和内部状态。例如,一个表单组件被keep - alive
包裹,用户在表单中输入的数据,在切换离开再返回时依然保留。
- 它会缓存被包裹组件的
-
生命周期钩子影响
- 被缓存的组件,在激活和失活时会触发特定的生命周期钩子,如
activated
和deactivated
,可以利用这些钩子来执行一些操作,像重新获取数据等。
- 被缓存的组件,在激活和失活时会触发特定的生命周期钩子,如
-
应用场景
- 适用于频繁切换但创建销毁成本高的组件,如多标签页系统中的标签页组件,或者有复杂表单输入的组件,能有效提升性能和用户体验。
vue.nextTick()
-
定义和作用
Vue.nextTick()
是 Vue.js 提供的一个全局方法,用于在下次 DOM 更新循环结束之后执行延迟回调。因为 Vue 在更新数据后,DOM 更新是异步的,不是立即执行。
-
使用场景示例
- 操作更新后的 DOM 元素:例如,当你修改了某个数据,并且这个数据的改变会影响 DOM 结构或样式,而你需要在 DOM 更新完成后获取它的高度、宽度等属性,或者进行其他基于 DOM 更新后的操作,就需要使用
nextTick
。 - 确保数据更新后的操作顺序:如果在一个方法中多次更新数据,想要在所有数据更新导致的 DOM 变化都完成后执行一个操作,也可以使用
nextTick
来确保顺序。
- 操作更新后的 DOM 元素:例如,当你修改了某个数据,并且这个数据的改变会影响 DOM 结构或样式,而你需要在 DOM 更新完成后获取它的高度、宽度等属性,或者进行其他基于 DOM 更新后的操作,就需要使用
computed和watch的使用场景
-
computed(计算属性)
-
概念:基于依赖缓存,依赖变才重新计算。
-
使用场景
- 数据格式化:如转换日期格式。
- 复杂计算:根据其他属性算新值,像购物车总价计算。
-
-
watch(侦听器)
-
概念:观察和响应数据变化,数据变就执行回调。
-
使用场景
- 异步操作:如表单数据变就发 Ajax 验证。
- 深度监听复杂对象:监测复杂对象内部变化。
-
params和query的区别
-
定义方面
- params(路由参数) :用于在路由路径中定义动态片段,是路径的一部分,通常用来标识特定资源。
- query(查询参数) :是在 URL 中 “?” 后面的键值对部分,主要用于传递一些额外的、非路径必要的信息。
-
使用场景方面
- params:像在文章详情页的路由中,用
/article/:id
(id
为路由参数)来获取特定文章的详情,id
是路径关键部分。 - query:比如在商品列表页,通过
/products?category=electronics&page=2
来表示获取电子品类的第二页商品,这些参数是筛选或分页的辅助信息。
- params:像在文章详情页的路由中,用
-
用法
-
query语法
- this.$router.push({path: 地址, query: {id: 1}}) 传递参数
- this.$route.query.id 接收参数
-
params语法
- this.$router.push({name: 地址, params: {id: 1}}) 传递参数
- this.$route.params.id 接收参数
-
call、apply与bind区别
-
相同点
- 都用于改变函数内部
this
指向。
- 都用于改变函数内部
-
不同点
-
call 和 apply
- 参数形式:call 逐个传参,第一个是
this
指向,后面是函数参数;apply 前一个是this
指向,后一个是参数数组。 - 执行时机:二者都会立即执行函数。
- 参数形式:call 逐个传参,第一个是
-
bind:不会立即执行函数,而是返回一个新的函数,这个新函数的
this
指向已被绑定。
-
vue自定义指令
-
定义
- 自定义指令是 Vue 中用于对普通 DOM 元素进行底层操作的方式。它可以让开发者对 DOM 元素进行更灵活的控制,像防抖、权限校验等功能。
-
全局注册
- 使用
Vue.directive()
方法,第一个参数是指令名称,第二个参数是包含指令钩子函数(如bind
、inserted
、update
等)的对象。这些钩子函数会在指令生命周期的不同阶段被触发,从而允许开发者在合适的时机进行操作。
- 使用
-
局部注册
- 在组件的
directives
选项中定义,格式和全局注册类似。这样指令仅在该组件内可用,方便组件特定的功能定制,如组件内的特定元素聚焦指令。
- 在组件的
-
钩子函数示例(以
bind
为例)- 在
bind
钩子函数中,可以获取到指令所绑定的元素和指令的表达式等信息。例如通过el.style.color = 'red'
这样的操作,来改变元素的样式,实现简单的自定义样式指令。
- 在
案例
创建指令文件(draggable.js)
// 创建一个对象来挂载指令相关的方法和属性
const draggable = {};
// 定义指令的逻辑函数
const draggableDirective = {
bind(el, binding) {
const targetClass = binding.arg;
let startX, startY, translateX = 0, translateY = 0;
// 鼠标按下事件处理函数
const mousedownHandler = (e) => {
if (e.target.classList.contains(targetClass)) {
startX = e.clientX;
startY = e.clientY;
document.addEventListener('mousemove', mousemoveHandler);
document.addEventListener('mouseup', mouseupHandler);
}
};
// 鼠标移动事件处理函数
const mousemoveHandler = (e) => {
translateX += e.clientX - startX;
translateY += e.clientY - startY;
el.style.transform = `translate(${translateX}px, ${translateY}px)`;
startX = e.clientX; startY = e.clientY;
};
// 鼠标松开事件处理函数
const mouseupHandler = () => {
document.removeEventListener('mousemove', mousemoveHandler);
document.removeEventListener('mouseup', mouseupHandler);
};
// 给元素添加鼠标按下事件监听器
el.addEventListener('mousedown', mousedownHandler);
}
};
// 在draggable对象上添加install方法,用于注册指令
draggable.install = (Vue) => {
Vue.directive('draggable', draggableDirective);
};
// 导出draggable对象,方便在其他地方引入并使用
export default draggable;
main.js中全局引入
// main.js文件
import Vue from 'vue';
import App from './App.vue';
import draggable from './draggable.js';
// 全局注册自定义指令
Vue.use(draggable);
页面中使用
<template>
<div id="app">
<div v-draggable>可拖拽区域</div>
</div>
</template>
性能优化
如何优化 Vue 应用的性能?
- 组件层面,合理用
v - if
和v - show
,用keep - alive
缓存组件。数据层面,避免模板复杂表达式,用计算属性,大数据列表考虑虚拟列表。网络层面,代码分割和懒加载组件,合理使用缓存。
在 Vue 中,如何避免不必要的组件重新渲染?
- 可以使用
shouldComponentUpdate
(Vue 2)或自定义类似功能来控制组件是否重新渲染,也可以合理使用Vuex
的mapState
辅助函数,或者在某些场景下用Object.freeze
冻结对象。
Vue 路由原理
工作原理
- Vue Router 通过管理 URL 和组件的映射关系来实现单页应用的路由功能。它监听浏览器地址栏的变化,根据路由规则匹配对应的组件,然后将组件渲染到
router - view
标签所在的位置。
路由模式区别和实现方式
-
hash 模式:
- 区别:URL 带有
#
,如http://example.com/#/home
。#
后面的内容是哈希值,不会发送给服务器。 - 实现方式:利用浏览器的
hashchange
事件来监听哈希值的变化,从而更新页面显示的组件。
- 区别:URL 带有
-
history 模式:
- 区别:URL 更像传统的路径格式,如
http://example.com/home
。但需要服务器配置支持,否则刷新页面可能出现 404。 - 实现方式:使用
HTML5
的history.pushState
和history.replaceState
方法来改变 URL,通过监听popstate
事件来处理浏览器的前进和后退操作。
- 区别:URL 更像传统的路径格式,如
路由懒加载机制
- 通过动态导入组件来实现懒加载。例如,
const Home = () => import('./views/Home.vue');
,这样在访问该路由时才会加载对应的组件,减小初始包体积,提高应用加载速度。
路由守卫实现功能
- 页面权限控制和导航拦截:
- 全局前置守卫(
beforeEach
) :在路由跳转前触发。可以检查用户是否登录、权限是否足够等。如果不符合条件,可以通过next
方法进行拦截或者重定向。 - 路由独享守卫(
beforeEnter
) :在特定路由进入前触发,用法和beforeEach
类似,但只作用于当前路由。 - 组件内守卫(
beforeRouteEnter
、beforeRouteUpdate
、beforeRouteLeave
) :在组件渲染前、路由更新时、组件离开时触发。用于在组件级别控制导航和权限相关操作。
- 全局前置守卫(
Vuex 状态管理
核心概念
- state:存储应用的全局状态,是单一数据源,类似一个数据仓库。
- mutations:是同步函数,用于修改 Vuex 中的状态(
state
)。它是唯一被允许修改state
的地方,能保证状态变更的可追踪性。 - actions:可以包含异步操作,比如发送 Ajax 请求。它通过提交(
commit
)mutation
来间接改变状态,不直接修改state
。 - getters:类似于计算属性,用于从 state 中获取派生状态,方便对 state 进行过滤、计算等操作。
协作关系和使用规范
- 组件通过
this.$store.dispatch('action名称', 参数)
触发 actions,actions 里通过this.$store.commit('mutation名称', 参数)
触发 mutations,mutations 修改 state,组件可以通过mapState
获取 state,通过mapGetters
获取 getters。在 mutations 里不能进行异步操作,且必须是同步函数。
在大型项目中运用
- 对于跨多个组件共享的数据(如用户信息、购物车数据),使用 Vuex 管理。先确定 state 结构,再根据业务逻辑编写 actions 和 mutations。把数据获取和更新逻辑放在 Vuex 里,让组件只负责展示和触发操作。
避免常见问题的方法
- 数据冗余:合理划分 state 结构,只存放真正需要共享的全局数据。对于可计算得出的数据,尽量用 getters 获取。
- 命名冲突:在命名 mutations、actions 和 getters 时,采用模块命名空间的方式,比如加上模块前缀,确保名字的唯一性。同时,在大型项目中可以划分模块,每个模块有独立的 state、mutations、actions 和 getters。
Vue 的生命周期钩子函数
生命周期钩子函数列举及执行时机和作用
- beforeCreate:实例刚创建,数据观测和事件机制还没配置。作用是可以加载初始指示器。
- created:实例创建完成,可访问数据和方法,但 DOM 未挂载。用于初始化数据、调用异步数据获取函数。
- beforeMount:在挂载开始之前,虚拟 DOM 已创建。可用于最后的数据检查和调整。
- mounted:DOM 挂载完成。适合进行 DOM 操作、初始化第三方插件、启动定时器。
- beforeUpdate:数据更新,DOM 更新前。可用于获取更新前 DOM 状态。
- updated:DOM 更新后。用于操作更新后的 DOM,但要避免无限循环更新。
- beforeDestroy:实例销毁之前。用于清除定时器、取消订阅、解绑事件监听器。
- destroyed:实例销毁后,用于释放资源后的收尾工作。
实际开发应用
- 数据初始化:在
created
或mounted
中获取并设置数据。 - DOM 操作:在
mounted
中使用$refs
操作 DOM。 - 定时器:在
mounted
设置定时器,beforeDestroy
清除定时器。
vue3 部分
新特性
有响应式系统的升级(使用Proxy
代替Object.defineProperty
,能更好地处理嵌套对象和数组)、组合式 API(setup
函数等,让代码逻辑更灵活地组合)、Teleport
(可以将组件内容移动到 DOM 的其他位置)、Fragments
(组件可以返回多个根元素)。
响应式原理
Vue3 使用Proxy
实现响应式。Proxy
可以代理目标对象,拦截对象的get
、set
等操作。当读取属性时会收集依赖,修改属性时会触发更新,依赖是通过effect
函数来管理的,这样可以高效地更新 DOM。
Vue3 的组件生命周期钩子函数有哪些变化
大部分生命周期钩子函数名称和作用和 Vue2 类似,但beforeCreate
和created
被setup
函数所涵盖。同时新增了onBeforeMount
、onMounted
等以on
开头的钩子函数,通过import { onMounted } from 'vue';
导入使用,在setup
函数中调用这些钩子来处理生命周期相关的操作。
组合式 API 方面
请介绍 Vue3 组合式 API 中的setup
函数。
-
setup
是 Vue3 组件的入口点,在组件创建之前执行。它接收props
和context
作为参数。在setup
中可以返回一个对象,其属性和方法可以在模板中使用。并且,它是组合式 API 的主要使用场景,用于定义响应式数据、计算属性、方法和生命周期钩子等。 -
如何在 Vue3 中使用组合式 API 实现组件逻辑复用?
-
可以通过自定义函数(如
useXXX
函数)来封装逻辑。例如,定义一个useCounter
函数,在里面通过ref
或者reactive
定义响应式数据和操作数据的方法,然后在多个组件的setup
函数中调用useCounter
,实现计数器逻辑的复用。
性能优化方面
-
Vue3 在性能上有哪些提升?如何利用这些提升优化应用?
-
响应式系统性能提升,
Proxy
的使用让数据更新更高效。在优化应用时,利用ref
和reactive
合理定义响应式数据,减少不必要的响应式转换。并且 Vue3 的编译优化会跳过静态节点,减少 DOM 更新的开销,开发者可以通过保持模板的简洁性来配合优化。 -
请谈谈 Vue3 中如何避免不必要的组件重新渲染?
-
可以使用
shallowRef
和shallowReactive
来创建浅层响应式数据,这样只有被代理对象的第一层属性变化时才会触发更新。另外,在setup
函数中返回的对象如果包含函数,要注意这些函数的引用稳定性,避免每次重新渲染都创建新的函数引用导致组件重新渲染。
与其他框架或库的对比和集成方面
-
比较一下 Vue3 和 React 在组件开发和数据管理方面的异同。
-
组件开发:Vue3 组件可以有单个或多个根元素,有模板语法,更加直观;React 组件通常只有一个根元素,使用 JSX 语法,更接近 JavaScript。
-
数据管理:Vue3 有响应式数据系统,通过
ref
、reactive
等方式处理数据;React 使用状态(state
)和钩子函数(如useState
、useEffect
)来管理数据和副作用。 -
如何在 Vue3 项目中集成第三方库?
-
对于 UI 库,按照其文档安装和引入组件即可。对于功能库,如工具函数库,可以直接在
setup
函数或者其他模块中引入使用。如果是操作 DOM 的库,需要注意在合适的生命周期阶段(如mounted
)进行操作,避免与 Vue 的 DOM 更新冲突。
网络相关
HTTP 和 HTTPS
-
基本概念
- HTTP(超文本传输协议) :是用于传输超文本(如 HTML 文件)的应用层协议,是 Web 通信的基础,采用明文传输。
- HTTPS(超文本传输安全协议) :是 HTTP 的安全版,在 HTTP 和 TCP 之间添加了 SSL/TLS 加密层,用于安全的数据传输。
-
工作原理
- HTTP:客户端(如浏览器)发起请求,通过 TCP 连接服务器,服务器接收请求后返回响应内容,采用请求 - 响应模式。
- HTTPS:在 HTTP 的基础上,先进行 SSL/TLS 握手,协商加密算法和密钥,然后对传输的数据进行加密和解密。
-
区别
- 安全性:HTTP 是明文传输,不安全;HTTPS 是加密传输,安全。
- 端口号:HTTP 一般使用 80 端口,HTTPS 使用 443 端口。
- 性能:HTTPS 由于加密解密等操作,性能稍差于 HTTP。
跨域问题
-
同源策略和跨域概念
- 同源策略:浏览器的安全机制,要求协议、域名、端口相同才允许相互访问资源。
- 跨域:当请求的资源与当前页面的协议、域名、端口不同时,就产生跨域问题。
-
跨域解决方案原理和实现方式
-
JSONP(JSON with Padding) :
- 原理:利用
<script>
标签不受同源策略限制,通过动态创建<script>
标签,请求一个返回函数调用(包含数据)的 URL。 - 实现方式:客户端定义一个回调函数,服务端返回的数据包装在这个回调函数中,在浏览器中执行获取数据。
- 原理:利用
-
CORS(跨域资源共享) :
- 原理:是一种基于 HTTP 头的机制,服务器通过设置响应头来允许跨域访问。
- 实现方式:在后端设置
Access - Control - Allow - Origin
等相关头信息,指定允许访问的源,还可以配置允许的方法、头信息等。
-
代理服务器:
- 原理:让跨域请求先经过同域的代理服务器,代理服务器再向目标服务器请求,将结果返回给客户端。
- 实现方式:可以在前端开发环境(如 Webpack 的
dev - server
)中配置代理,也可以在后端搭建独立的代理服务器,将请求转发出去。
-
-
后端技术栈中配置和实现跨域资源共享
- Node.js(Express) :使用
cors
中间件,通过app.use(cors())
允许所有源跨域,也可以配置参数来限制。 - Python(Flask) :可以使用
flask - cors
扩展,通过app.config['CORS_ALLOWED_ORIGINS']
等设置允许跨域的源。 - Java(Spring Boot) :通过
@CrossOrigin
注解在控制器或方法级别设置允许跨域,也可以全局配置跨域策略。
- Node.js(Express) :使用
缓存机制
-
浏览器缓存类型和原理
- 强缓存:浏览器直接从本地缓存读取资源,不向服务器发送请求。它根据
Expires
(过期时间)和Cache - Control
(优先级更高)来判断。 - 协商缓存:浏览器先向服务器发送请求,服务器根据资源的修改时间(
Last - Modified
和If - Modified - Since
)或实体标签(ETag
和If - Natch
)来判断资源是否更新,若未更新则返回 304 状态码,让浏览器使用本地缓存。
- 强缓存:浏览器直接从本地缓存读取资源,不向服务器发送请求。它根据
-
通过 HTTP 头控制缓存策略
- 可以设置
Cache - Control
的值(如public
、private
、no - cache
、max - age
)来控制强缓存。利用Last - Modified
和ETag
来配合协商缓存。
- 可以设置
-
前端利用缓存提高性能的方法
- 对于不常更新的静态资源(如样式表、脚本、图片)设置较长时间的强缓存。使用版本号或哈希值更新文件名,来更新缓存。
-
避免缓存问题的方法
- 对于频繁更新的数据,设置短时间的缓存或不缓存。需要更新时,通过修改 URL 或添加版本号等方式强制刷新缓存。
网络请求优化
-
优化前端网络请求的手段
- 减少请求次数:将多个小资源合并成一个(如 CSS、JavaScript 文件),使用雪碧图整合小图标。
- 合并请求:可以利用 HTTP/2 的多路复用,或者在业务逻辑允许的情况下,把相关请求放在一起发送。
- 使用缓存:合理设置强缓存和协商缓存,对不常更新的资源延长缓存时间。
- 懒加载:对于图片、组件等非关键资源,延迟加载,如图片的
loading="lazy"
属性,或者在滚动到可视区域时加载。
-
网络请求库的选择
- Axios:功能强大,支持请求和响应拦截、自动转换 JSON 数据,有良好的浏览器兼容性,适合复杂的业务场景和需要对请求进行全局控制的情况。
- Fetch:原生 API,简单轻量,语法简洁,和现代 JavaScript 更融合,但在兼容性和功能细节(如没有自动 JSON 转换)上稍弱,适合简单的请求场景。
-
处理错误和超时情况
- 错误处理:Axios 通过
catch
块捕获错误,Fetch 可以通过try - catch
,检查响应状态码来判断是否出错。根据错误类型(如 404、500)显示相应的错误提示给用户。 - 超时处理:Axios 可以设置
timeout
属性,Fetch 可以使用Promise.race
结合setTimeout
自定义超时机制。超时后可以重试或者给用户提示请求超时。这些措施能避免长时间等待,提高用户体验。
- 错误处理:Axios 通过
性能优化部分
页面加载性能优化
-
优化策略
- 压缩和合并文件:使用工具压缩 CSS 和 JavaScript 文件,减少文件大小。合并相关文件,减少请求次数。
- 优化图片资源:选择合适的图片格式,如 WebP。压缩图片,使用响应式图片(
srcset
属性)根据设备分辨率加载。 - 使用 CDN 加速:将静态资源分发到 CDN 服务器,用户从距离近的节点获取资源,加快访问速度。
- 懒加载和预加载:懒加载非关键资源(如图片、部分模块),页面滚动到相应位置再加载。预加载重要资源(如关键脚本、首屏图片)提前获取。
-
性能测试工具及优化
- 工具:使用 Google PageSpeed Insights、Lighthouse 等工具评估页面性能。它们能提供加载时间、性能指标(如首次内容绘制 FCP、最大内容绘制 LCP 等)的评分和建议。
- 优化改进:根据工具反馈,重点优化加载慢的资源、减少阻塞渲染的因素,提升性能指标评分。
渲染性能优化
-
减少 DOM 操作
- 尽量批量操作 DOM,如使用
document.createDocumentFragment()
先将多个节点添加到文档片段,再一次性插入 DOM,减少回流和重绘次数。
- 尽量批量操作 DOM,如使用
-
虚拟 DOM 和 Diff 算法
- 像 React 和 Vue 等框架使用虚拟 DOM。虚拟 DOM 是真实 DOM 的 JavaScript 对象表示,当数据变化时,通过 Diff 算法比较新旧虚拟 DOM,只更新有变化的部分到真实 DOM,减少不必要的 DOM 操作。
-
避免重排和重绘
- 避免频繁修改元素的几何属性(如宽、高、位置)来减少重排。读取元素布局属性(如
offsetTop
)也可能引发重排,应尽量减少。使用transform
和opacity
属性进行动画制作可减少重排重绘。
- 避免频繁修改元素的几何属性(如宽、高、位置)来减少重排。读取元素布局属性(如
-
CSS 样式和布局优化渲染效率
- 减少复杂的 CSS 选择器,避免使用通配符选择器。使用
flex
和grid
布局,它们的性能在大多数情况下优于传统布局方式。合理使用will - change
属性提前告知浏览器元素的变化,优化渲染。
- 减少复杂的 CSS 选择器,避免使用通配符选择器。使用
-
React 性能优化技巧
- 利用
shouldComponentUpdate
或React.memo
来避免不必要的组件重新渲染。使用useCallback
和useMemo
缓存函数和计算结果,减少组件重新渲染导致的性能开销。
- 利用
-
Vue 性能优化技巧
- 合理使用
v - show
和v - for
,对于长列表使用v - for
时,可以考虑使用虚拟列表技术。在组件中使用watch
时,设置深度监听的选项要谨慎,避免过度监听导致性能下降。
- 合理使用
内存管理和性能优化
-
垃圾回收原理和算法
- 原理:JavaScript 引擎自动检测不再使用的内存并回收。
- 算法:主要有标记清除(标记不可达对象清除)和引用计数(记录对象引用次数,为 0 时回收),现代浏览器多用标记清除。
-
避免内存泄漏和溢出
- 内存泄漏:注意清除定时器、取消事件监听器、及时清除闭包引用等。
- 内存溢出:避免创建大量数据占用过多内存,如无限循环添加数据。
-
优化内存使用
- 变量声明和使用:合理使用
let
、const
,避免全局变量过多。 - 释放资源:在对象或资源不再使用时,将其设为
null
,便于垃圾回收。及时清理缓存等占用内存的资源。
- 变量声明和使用:合理使用
性能优化工具和实践
-
常见前端性能优化工具
- Lighthouse:开源且集成于 Chrome 开发者工具,能全面评估网页性能、可访问性等多方面,在 Chrome 开发者工具中切换到其选项卡,选测试类别后点击生成报告,报告含各项指标得分与优化建议。
- PageSpeed Insights:谷歌在线工具,测试网页在移动与桌面设备性能,在其官网输入网址即可生成含核心网页指标得分及优化建议的报告,按优先级排序方便优化。
- Chrome 开发者工具:Chrome 自带,可调试 JavaScript、看网络请求与分析性能等。右键页面元素选 “检查” 打开,“Performance” 面板可录页面加载过程查看各阶段耗时,“Network” 面板能查网络请求详情。
-
性能分析与评估案例
- 电商网站用 Lighthouse 评估,发现图片未优化、JavaScript 文件加载久。大尺寸图片转 WebP 并压缩,用 Webpack 合并压缩 JavaScript 文件后性能提升。
- 新闻资讯网站在 Chrome 开发者工具 “Network” 面板中,将广告脚本异步加载改善阻塞,“Performance” 面板发现图片未懒加载,实现懒加载减少初始加载压力。
-
性能优化实践技巧
- 资源优化:压缩静态资源(Terser 压 JavaScript、CSSNano 压 CSS),依更新频率设缓存策略。
- 渲染优化:HTML 与 CSS 结构简洁,避免复杂选择器与嵌套,DOM 操作多用文档片段减少重排重绘。
- 代码执行优化:循环中避免复杂计算与函数调用,事件处理用事件委托减少绑定数量。