中高级前端面试题【2024.10月最新版】
当我们意识到自己的处境不如从前时:重生之做一个合格的牛马
目录
-
- 1、Apply call bind 三者的区别:
- 2、For in和for of有什么区别
- 3、你了解那个深拷贝和浅拷贝以及区别吗
- 4、一个数组进行去重有哪些方法
- 5、ES6中新引入的数组方法
- 6、你常用的选择器有哪些种类
- 7、全局状态管理vuex
- 8、判断一个数据的类型 有哪些方法
- 9、Webpack
- 10、权限菜单配置
- 11、什么是低代码开发
- 12、vue3
- 13、简述nextTick 的作用是什么 他的实现原理是什么
- 14、 Vue首屏白屏如何解决
- 15、简述完整的HTTP事务是怎样的一个过程
- 16、使用new关键字创建一个对象时,会经历几个步骤
- 17、简述Vue的MVVM 模式?
- 18、简述MVC与MVVM的区别
- 19、computed
- 20、Vue中deep选择器的原理
- 21、uniapp有什么缺点
- 22、vue 插槽
- 23、vue keep-alive的作用以及生命周期
- 24、请说出vue.cli项目中src目录每个文件夹和文件的用法?
- 25、promise finally all rance使用场景
- 26、 请简述构建 vue-cli 工程都用到了哪些技术,他们的作用分别是什么
- 27、 简述 Vue 2.0 响应式数据的原理( 重点 )
- 28、组件传递参数
- 29、路由守卫的种类
- 30、同步与异步的区别
- 31、axios和fetch有什么区别
- 32、Event Loop
- 33、class从ES5到ES6的定义转变,继承转变
- 34、前端全局缓存有几级缓存怎么去控制这些缓存
- 35、vue-router的hash和history模式的区别和原理
- 36、前端图表的底层实现方式有哪两种
- 37、前端自定义组件的v-model
- 38、uniapp性能优化
- 39、说一下typescript
- 40、TypeScript的泛型和接口
- 41、 this指向
- 42、 冒泡排序
- 43、选择排序
- 44、快速排序
- 45、性能优化
- 46、vue和rect框架区别
- 47、在uniapp中怎么保证页面是最新的
- 48、封装出高性能、低耦合、易于维护和扩展的公共组件
- 49、原型链的理解
- 50、虚拟DOM
- 51、 vue2 双向数据绑定
- 52、foreach和map
1、Apply call bind 三者的区别:
- 三者都可以改变函数的this对象指向
- 三者第一个参数都是this要指向的对象,如果没有这个参数或参数为undefined或null,则默认指向全局window
- 三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入
- bind是返回绑定this之后的函数,apply、call则是立即执行
2、For in和for of有什么区别
for…in用于遍历对象的可枚举属性,包括原型链上的属性,而for…of用于遍历可迭代对象(如数组、字符串等)的元素。for…in适合用于对象的属性迭代,而for…of则更适合用于数组等集合,能够更简洁地获取元素值。
- 循环数组
区别一:for in 和 for of 都可以循环数组,for in 输出的是数组的index下标,而for of 输出的是数组的每一项的值。 - 循环对象
区别二:for in 可以遍历对象,for of 不能遍历对象,只能遍历带有iterator接口的,例如Set,Map,String,Array
3、你了解那个深拷贝和浅拷贝以及区别吗
深拷贝和浅拷贝是编程中常见的两种对象复制方式,它们在实现方式、内存分配以及影响范围等方面存在区别。以下是具体分析:
-
实现方式
- 深拷贝:创建一个新的对象,并且递归地复制原始对象的所有属性和引用指向的对象。这通常通过手动编写递归方法或使用库函数(如JavaScript中的JSON.parse(JSON.stringify()))来实现。
- 浅拷贝:只复制对象的最顶层结构,如果对象中有引用类型的属性,则这些属性的引用会被直接复制到新对象中,新旧对象共享同一个引用。
-
内存分配
- 深拷贝:为每一个层级的属性都分配新的内存空间,因此会占用更多的内存。
- 浅拷贝:仅复制对象的顶层属性,对于引用类型的属性,新旧对象共享同一块内存空间,因此占用的内存较少。
-
影响范围
- 深拷贝:由于是完全独立的副本,修改新对象不会影响原对象。
- 浅拷贝:由于引用类型属性共享内存,修改新对象可能会影响原对象。
-
性能开销
- 深拷贝:由于需要递归复制所有层级的属性,深拷贝的性能开销较大,尤其是在对象结构复杂时。
- 浅拷贝:由于仅复制顶层属性,浅拷贝的性能较好,适用于大多数简单场景。
-
适用场景
- 深拷贝:适用于需要完全独立的数据副本的场景,例如在多线程环境中避免数据竞争。
- 浅拷贝:适用于对性能要求较高且不需要深层独立的场景,例如简单的对象赋值操作。
实现深拷贝的方法有多种,具体取决于编程语言和应用场景。以下是几种常见的方法:
- 使用递归函数手动实现深拷贝
这种方法适用于简单的对象结构,可以通过递归遍历对象的所有属性来实现深拷贝。
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
let clone = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}
- 使用JSON序列化和反序列化
这种方法简单快捷,但有一些限制,例如不能处理函数、undefined
、循环引用等。
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
- 使用第三方库
有许多成熟的第三方库可以方便地实现深拷贝,如Lodash的_.cloneDeep
方法。
const _ = require('lodash');
function deepClone(obj) {
return _.cloneDeep(obj);
}
- 使用结构化克隆算法(Structured Clone Algorithm)
在现代浏览器中,可以使用structuredClone
方法,这是一种标准化的深拷贝方式。
function deepClone(obj) {
return structuredClone(obj);
}
- 使用Map来处理循环引用
对于复杂的对象结构,特别是包含循环引用的情况,可以使用Map来记录已经复制的对象,从而避免无限递归。
function deepClone(obj, map = new Map()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (map.has(obj)) {
return map.get(obj);
}
let clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], map);
}
}
return clone;
}
4、一个数组进行去重有哪些方法
- 使用 Set 数据结构
Set 是 ES6 引入的一种新的数据结构,它可以自动去除其中的重复项。
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
- 使用 filter 方法
filter 方法可以遍历数组,根据回调函数的返回值决定是否保留该元素。我们可以利用它配合 indexOf 来去重。
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = array.filter((item, index) => {
return array.indexOf(item) === index;
});
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
- 使用 reduce 方法
reduce 方法可以累加器累加数组中的每个值,我们可以利用这个特性,结合 includes 来去重。
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = array.reduce((acc, current) => {
if (!acc.includes(current)) {
acc.push(current);
}
return acc;
}, []);
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
- 使用 Map 数据结构
如果数组中包含的元素是复杂数据类型(如对象),上述方法可能无法正确去重。这时可以使用 Map 来根据对象的某个属性去重。
const array = [
{id: 1, name: 'Tom'},
{id: 2, name: 'Jerry'},
{id: 2, name: 'Jerry'},
{id: 3, name: 'Bob'}
];
const map = new Map();
for (const item of array) {
if (!map.has(item.id)) {
map.set(item.id, item);
}
}
const uniqueArray = [...map.values()];
console.log(uniqueArray); // 输出: [{id: 1, name: 'Tom'}, {id: 2, name: 'Jerry'}, {id: 3, name: 'Bob'}]
注意事项
当数组中的元素是对象时,需要确保用来判断重复的属性(如上例中的 id)是唯一且正确的。
在处理大数据集时,去重操作可能会影响性能,需要根据实际情况选择最优解。
- lodash库的去重函数
库介绍:lodash 是一个流行的JavaScript实用程序库,提供了许多功能来处理数据操作,包括去重。
使用示例:如果你使用了 lodash,那么 _.uniq([1, 2, 2, 3, 4, 4]) 会返回一个去重后的数组 [1, 2, 3, 4]。
5、ES6中新引入的数组方法
-
Array.from():这个方法用于将类数组对象或可迭代对象转换为数组。例如,可以将一个具有length属性的对象或字符串转换成一个真正的数组。
-
Array.of():此方法接受一系列参数,并将它们转换为一个数组。这在处理少量元素时特别有用。
-
find():该方法用于查找数组中满足提供的测试函数的第一个元素的值,并返回该元素。如果没有找到任何元素,则返回undefined。
-
fill():此方法用于用一个固定值填充一个数组的所有元素,从开始索引到结束索引(不包括结束索引)。
-
entries()、keys()和values():这些方法用于遍历数组,分别返回键值对、键名或键值。
-
includes():这个方法用来检查数组中是否包含某个特定的元素,根据情况返回true或false。
-
flat() 和 flatMap():这两个方法用于处理嵌套数组,flat()用于将嵌套数组扁平化成一维数组,而flatMap()则是先对数组中的每个元素进行映射处理,再进行扁平化操作。
-
orEach():这是一个实例方法,用于遍历数组的每一个元素并执行给定的函数。
-
map()、filter()、reduce()、some() 和 every():这些是ES5中已经存在的方法,但在ES6中得到了保留和优化。它们分别用于映射数组、过滤数组、减少数组、检查数组中是否有元素满足条件以及检查所有元素是否都满足条件。
6、你常用的选择器有哪些种类
选择器有多种类型,它们在CSS中扮演着至关重要的角色。以下是对一些常见选择器种类的简要介绍:
- 通用选择器:通用选择器(*)匹配文档中的每个元素。它常用于设置全局样式或重置所有元素的默认样式,如清除所有元素的内外边距。
- 标签选择器:标签选择器通过HTML元素的标签名来选择元素,例如p选择器会选择所有的段落元素。这种选择器适用于为特定类型的元素设定统一的样式。
- ID选择器:ID选择器使用井号#和元素的ID属性值来选择具有特定ID的元素,例如#nav会选择ID为"nav"的元素。由于ID在一个文档中应该是唯一的,因此ID选择器的优先级很高。
- 类选择器:类选择器通过HTML元素的class属性来选择元素,使用英文句号.后跟类名,如.black选择所有class="black"的元素。类选择器可以应用于多个元素,非常适合于定义可重用的样式。
- 后代选择器:后代选择器通过空格分隔两个选择器,选择第一个选择器内的所有后代元素,例如div p选择所有位于div内的p元素。后代选择器允许样式规则覆盖到深层次的元素。
- 子选择器:子选择器使用大于号>来选择直接子元素,如ul > li选择所有直接位于ul内的li元素。子选择器确保样式只应用于直接的子代。
- 相邻兄弟选择器:相邻兄弟选择器使用加号+来选择紧接在另一元素后的元素,这两个元素共享相同的父元素。这种选择器适用于需要强调相邻元素关系的场景。
- 通用兄弟选择器:通用兄弟选择器使用波浪号~来选择某个元素之后的所有兄弟元素。与相邻兄弟选择器不同,通用兄弟选择器不要求元素之间紧邻。
- 伪类选择器:伪类选择器用于定义元素的特殊状态,如:hover用于鼠标悬停时的状态。这些选择器增强了用户与页面交互的能力。
- 伪元素选择器:伪元素选择器用于样式化元素的特定部分,如::before和::after分别用于在元素内容前后添加内容。
- 结构性伪类选择器:结构性伪类选择器允许根据元素在文档树中的位置来选择元素,例如:root选择文档的根元素,:nth-child(n)选择属于其父元素的第n个子元素。
7、全局状态管理vuex
Vuex是一个专为Vue.js应用程序开发的状态管理库,它允许开发者集中式地存贮、管理和共享应用的所有组件的状态,从而使得状态变更可以预测且易于管理。以下是对Vuex的具体介绍:
-
核心概念
- State:State 是应用的核心数据源,存储了应用的状态,可以是任何类型的数据对象。在 Vuex 中,所有组件都可以访问到相同的 state,确保了状态的一致性。
- Getters:Getters 允许组件以计算属性的形式访问 state,它们提供了一种机制来派生和组合 state 中的不同部分,使组件能够方便地获取所需的状态片段。
- Mutations:Mutations 是更改 state 的唯一方式,它们是同步的操作函数。为了保证状态管理的可预测性,所有的状态更新都应通过 mutations 进行。
- Actions:Actions 用于处理异步操作,如 API 请求等。它们可以包含任意异步操作的逻辑,并通过提交 (commit) mutations 来间接改变 state。
- Modules:当应用变得复杂时,可以将 store 拆分为多个模块,每个模块拥有自己的 state、getters、mutations 和 actions,有助于更好地组织代码和管理状态。
-
安装使用
- 安装:可以通过 npm 或 yarn 安装 Vuex。对于 Vue 2,使用
npm install vuex@3
;对于 Vue 3,则推荐使用 Pinia,但若仍想用 Vuex,则可以使用npm install vuex@next
。 - 初始化:创建一个 store.js 文件,导入 Vue 和 Vuex,然后使用 Vue.use(Vuex) 安装 Vuex。接着定义 state、mutations、actions 和 getters,最后导出 store 实例。
- 注册:在 main.js 文件中导入 store,并在创建 Vue 实例时将其作为选项注册,这样所有组件就都能访问到这个 store。
- 安装:可以通过 npm 或 yarn 安装 Vuex。对于 Vue 2,使用
-
组件交互
- 访问状态:在组件中,可以通过 this.$store.state 直接访问 state,或者使用 mapState 辅助函数将 state 映射到局部计算属性中,以便更方便地使用。
- 提交变更:要变更状态,可以直接调用 this.$store.commit(‘mutationName’, payload) 来触发一个 mutation,或者使用 mapMutations 辅助函数简化代码。
- 派发动作:对于需要处理异步逻辑的情况,可以在组件中使用 this.$store.dispatch(‘actionName’, payload) 来触发一个 action,或者使用 mapActions 辅助函数。
-
模块化
- 模块划分:当应用足够大时,可以将 store 分割成不同的模块,每个模块负责管理一部分状态。这样可以提高代码的可维护性和可读性。
- 动态加载:为了提升应用性能,可以根据需要懒加载 store 模块,避免一开始就加载所有状态管理代码。
-
工具集成
- 调试工具:Vue Devtools 提供了对 Vuex 状态的检查和时间旅行调试功能,这对于追踪状态变化历史和调试非常有用。
- 集成测试:可以与其他测试框架结合使用,对 Vuex 的状态管理和业务逻辑进行单元测试和集成测试,确保应用的稳定性和可靠性。
8、判断一个数据的类型 有哪些方法
在JavaScript中,判断数据类型的方法主要包括typeof、instanceof、constructor以及Object.prototype.toString.call()。这些方法各有特点,适用于不同的场景。以下是具体介绍:
- typeof操作符:typeof是一个简单且常用的操作符,用于判断基本数据类型,例如string、number、boolean、undefined等。对于对象和数组,typeof返回"object",对于null,它同样返回"object",这是它的一个缺点。
- instanceof操作符:instanceof用于检查一个对象是否为某个构造函数的实例。这个方法可以正确识别大多数内建对象类型,如Array、Object、Function等。然而,instanceof不能用于基本数据类型,也不能用于null值。
- constructor属性:每个对象都有一个constructor属性,指向创建该对象的原型对象的构造函数。通过比较这个属性与构造函数,可以判断对象的类型。但是,constructor属性可能会被修改,导致不准确的结果。
- Object.prototype.toString.call()方法:这是一个非常可靠的方法,可以精确地判断出几乎所有类型的数据,包括基本类型和对象类型。该方法返回一个字符串,格式为"[object Type]",其中Type是具体的类型名称。
9、Webpack
WebPack 是一个模块打包工具,可以使用WebPack管理模块,并分析模块间的依赖关系,最终编绎输出模块为HTML、JavaScript、CSS以及各种静态文件(图片、字体等),让开发过程更加高效。
对于不同类型的资源,webpack有对应的模块加载器loader
- Loader 和 Plugin 的区别
功能不同:
Loader 本质是一个函数,对接收到的文件进行转换,比如将ts转换成js,将scss转换成css等。
Plugin 是webpack的插件,可以扩展 Webpack 的功能。
运行时机不同
loader运行在打包文件之前,对文件进行预处理;
plugins 运行在loader结束后,webpack打包的整个过程中,它是基于事件机制,监听webpack打包过程中的某些节点,从而执行相应任务,进而改变输出。
- Loader的作用和常见Loader
Loader是webpack中提供的一种处理多种文件格式的机制。由于webpack只认识JS和JSON,Loader相当于翻译官,将其他类型资源进行预处理,最终变为js代码。Loader支持链式调用,调用的顺序是从右往左。常见的Loader包括:
file-loader:把文件输出到一个文件夹中,通过相对URL引用。
url-loader:与file-loader类似,但可以设置阈值,大于阈值会交给file-loader处理,小于阈值时返回文件base64形式编码。
css-loader:加载CSS文件,支持模块化、压缩、文件导入等特性。
style-loader:将CSS代码注入到JavaScript中,通过DOM操作加载CSS。
json-loader:加载JSON文件。
ts-loader:将TypeScript转换成JavaScript。
less-loader:将less代码转换成CSS。
- Plugin的作用和常见Plugin
Plugin是webpack中用于执行范围更广泛的任务的钩子。它们在webpack构建过程中,在特定时机注入自己的功能。常见的Plugin包括:
HtmlWebpackPlugin:简化HTML文件的创建。
MiniCssExtractPlugin:分离样式文件,支持按需加载。
CleanWebpackPlugin:清理目录。
TerserWebpackPlugin:压缩ES6代码。
CopyWebpackPlugin:复制文件到输出目录。
10、权限菜单配置
RBAC(基于角色的访问控制)是一种常用的权限管理方案,通过将系统功能和资源组织成角色,并将用户分配到不同的角色来实现权限控制。每个角色都定义了一组权限,包括可以执行的操作和访问的资源。通过给用户分配适当的角色,可以实现对用户的权限控制。
-
权限管理分类
- 菜单权限控制:菜单权限控制是页面级的权限控制,决定了用户能够看到哪些导航菜单选项。这通常涉及到根据用户的权限集合动态生成菜单结构。
- 按钮权限控制:按钮权限控制是按钮级的权限控制,决定了用户能否操作特定的按钮。这种控制通常需要与后端配合,确保用户只能执行他们有权执行的操作。
- 接口权限控制:接口权限控制是URL级别的权限控制,确保只有具备相应权限的用户才能访问特定的API接口。这通常通过JWT等令牌机制来实现。
-
权限控制的实现方式
- Vue Router与Vuex的结合使用:Vue Router用于处理路由,Vuex用于集中管理状态。通过将两者结合,可以实现基于用户权限的动态路由和菜单生成。
- 路由守卫的使用:通过Vue Router提供的全局前置守卫和路由独享守卫,可以在路由跳转前进行权限校验,防止未授权访问。
- 组件懒加载:为了提高性能,可以采用路由懒加载技术,仅在需要时加载对应的组件和路由信息。
-
数据存储与获取
- 本地存储:登录成功后,前端可以从后端获取用户的权限集合,并将其存储在sessionStorage或localStorage中。这些数据随后被用于生成菜单和控制按钮的显示。
- Vuex状态管理:Vuex用于管理全局状态,包括用户的权限信息。通过Vuex,可以在应用的任何地方轻松访问和更新这些状态。
-
界面与交互
- 菜单的动态渲染:根据用户的权限信息,动态生成菜单项。这通常涉及到遍历权限列表,匹配相应的菜单数据,并决定是否显示该菜单项。
- 按钮的显示控制:在具体的页面上,根据用户的权限动态显示或隐藏操作按钮,确保用户只能操作他们有权执行的功能。
-
安全性考虑
- 防止权限绕过:需要确保前端的权限控制不能被轻易绕过,同时后端的权限验证要严密,避免未授权访问。
- 敏感操作记录:对于关键操作,应记录操作日志,以便审计和追踪可能的安全事件。
-
用户体验优化
- 权限提示:当用户尝试访问未授权的资源时,应提供清晰的提示信息,指导用户如何获得所需权限或解释为何无法访问。
- 简化操作流程:合理设计权限申请和管理流程,使用户能够方便地申请新的权限或修改现有权限。
11、什么是低代码开发
低代码开发(Low-Code Development)是一种软件开发方法,旨在通过最小化手动编码的需求,使开发人员能够更快速地构建和部署应用程序。它使用可视化的用户界面和简化的工具来创建应用程序和业务流程,而不需要编写大量的代码。
低代码开发的特点
低代码开发平台通常提供丰富的预构建组件、模板和自动化工具,旨在简化应用开发过程,降低开发难度,缩短开发周期,提高开发效率。这些平台通常具有以下特点:
图形化拖拽:通过图形化界面进行拖拽操作,实现应用程序的构建。
参数化配置:通过参数化配置实现功能的定制化。
预构建组件:提供大量的预构建组件,减少重复工作。
自动化工具:提供自动化工具,减少手动编码的需求。
低代码开发的应用场景
低代码开发适用于多种场景,包括但不限于:
企业内部工具开发:快速构建内部使用的工具和流程。
业务应用开发:快速响应业务需求,构建业务应用。
原型开发:用于快速构建原型,测试市场反应。
多端应用构建:支持多端应用构建,适应不同设备的需求。
12、vue3
改进的响应性系统:Vue 3的响应性系统进行了优化,使得re-render性能更好。例如,Vue 3使用Proxy代替了Object.defineProperty来实现响应式数据,减少了订阅数据的开销。
更好的TypeScript支持:Vue 3对TypeScript的支持更加完善,包括更好的类型推断和编辑器支持。这使得开发者在使用TypeScript进行开发时,可以获得更好的开发体验。
-
性能提升:Vue 3重写了虚拟DOM的实现,并优化了模板编译过程,使得初始化和更新性能提高了约1.3到2倍。通过静态标记(PatchFlag)与上次虚拟节点对比时,只对比动态数据所在的节点,减少了不必要的计算。
-
体积优化:利用Webpack的tree-shaking功能,可以仅打包需要的模块,从而减小最终构建文件的体积。
-
易于维护:
Composition API: Vue 3引入了Composition API,它允许开发者将组件逻辑进行拆分和复用,使得代码更加清晰和灵活。例如,使用Composition API可以更容易地实现跨组件的状态管理和逻辑复用。Teleport组件:Vue 3引入了Teleport组件,它可以使内容在DOM结构中的任意一个地方渲染,从而可以更加灵活地控制组件的渲染位置。例如,可以将弹出框的内容渲染到body节点之外,以避免父级组件的样式影响。
Fragments:Vue 3支持Fragments,可以让组件返回多个根节点,而不需要使用额外的div包裹。这样可以更加简洁地编写组件,避免不必要的DOM层级。
-
响应式原理:Vue 3采用了Proxy作为响应式系统的基础,相比Vue 2中的Object.defineProperty,Proxy提供了更强大和灵活的拦截器机制。
-
组件通信:Vue 3支持多种组件通信方式,包括props、emit、provide/inject等,以及expose与ref实现父子组件方法调用。
-
项目结构:Vue 3项目的目录结构通常包括node_modules、public、src等目录,以及一些配置文件如package.json、vue.config.js等。
-
生命周期钩子:Vue 3的生命周期钩子名称上有所变化,大部分钩子名称后加上了“on”,如beforeCreate变为setup,created变为onMounted等。
-
多根节点支持:Vue 3支持多个根节点,也就是fragment,这使得模板编写更加灵活。
-
TypeScript支持:Vue 3对TypeScript的支持更好,有助于提高类型安全性和开发效率。
13、简述nextTick 的作用是什么 他的实现原理是什么
nextTick的作用是将回调函数延迟到下一个 DOM 更新周期之后执行。其实现原理是利用了JavaScript的事件循环机制,通过微任务或宏任务来安排异步操作的执行顺序。
nextTick的作用:
-
等待DOM更新:在Vue中,数据变化后不会立即触发DOM更新,而是等到当前事件循环的所有任务完成后,才进行DOM的更新。nextTick允许用户在DOM更新后立即执行某个操作。
-
获取最新DOM状态:由于DOM更新是异步的,直接在数据变化后操作DOM可能会得到旧的DOM状态。使用nextTick可以确保在获取或操作DOM元素时,得到的是最新的DOM状态。
-
执行后续逻辑:在需要基于更新后的DOM执行某些逻辑时,如获取元素尺寸、执行动画等,nextTick提供了一种可靠的机制来保证这些逻辑能够在正确的时机执行。
-
优化性能:通过合并多次DOM操作为一次,减少了不必要的计算和渲染,提高了应用的性能。
-
兼容不同平台:Vue根据运行环境的不同,自动选择最适合的异步策略(如Promise.then、MutationObserver、setImmediate或setTimeout),以确保跨平台的兼容性和效率。
nextTick的实现原理:
-
微任务与宏任务:nextTick利用JavaScript的事件循环机制,通过微任务(如Promise.then)或宏任务(如setTimeout)来推迟回调函数的执行。微任务优先于宏任务执行,因此通常能更快地响应数据变化。
-
队列管理:当调用nextTick时,会将要执行的回调函数加入一个队列中。这个队列会在当前的事件循环结束后,也就是所有的同步任务和微任务执行完毕后,被清空并执行其中的回调。
-
事件循环:nextTick的执行依赖于浏览器的事件循环机制。在每个事件循环中,首先执行所有的同步任务,然后是微任务队列中的任务,最后是宏任务。nextTick确保其回调在微任务阶段执行,从而保证了在DOM更新后立即执行。
-
兼容性处理:为了适应不同的运行环境,nextTick会根据环境特性选择合适的方法来实现异步调度。例如,在不支持Promise的环境中,可能会回退到使用setTimeout。
综上所述,nextTick是Vue框架中一个关键的方法,它通过巧妙地利用JavaScript的事件循环和异步任务队列,为开发者提供了一个强大而灵活的工具,以优化应用的性能和提升用户体验。
14、 Vue首屏白屏如何解决
查看文件大小加载时间
- 过network查看资源加载时间
- 通过dist包查看文件大小
- 通过使用webpack-bundle-analyzer查看文件打包细节
具体方法:
1)路由懒加载
2)vue-cli开启打包压缩 和后台配合 gzip访问
3)进行cdn加速
4)开启vue服务渲染模式
5)用webpack的externals属性把不需要打包的库文件分离出去,减少打包后文件的大小
6)在生产环境中删除掉不必要的console.log
7)开启nginx的gzip ,在nginx.conf配置文件中配置
8)添加loading效果,给用户一种进度感受
15、简述完整的HTTP事务是怎样的一个过程
基本流程:
- 域名解析
- 发起TCP的3次握手
- 建立TCP连接后发起http请求
- 服务器端响应http请求,浏览器得到html代码
- 浏览器解析html代码,并请求html代码中的资源
- 浏览器对页面进行渲染呈现给用户
16、使用new关键字创建一个对象时,会经历几个步骤
通过new操作符构建对象的步骤如下。
(1)创建一个新的对象,这个对象的类型是 object.
(2)将this变量指向该对象。
(3)将对象的原型指向该构造函数的原型。
(4)执行构造函数,通过this对象,为实例化对象添加自身属性方法。
(5)将this引用的新创建的对象返回。
代码如下。
function demo(Base) {
var obj ={};
//this =obj
obj. proto= Base. prototype;
School.call(obj)
return obj
}
new内部做了什么
- 创建了一个新对象 var obj = {}
- this关键字指向obj
- prototype原型指向obj原型,
- 执行构造函数
17、简述Vue的MVVM 模式?
MVVM 是 Model-View-ViewModel的缩写,即将数据模型与数据表现层通过数据驱动进行分离,从而只需要关系数据模型的开发,而不需要考虑页面的表现,具体说来如下:Model代表数据模型:主要用于定义数据和操作的业务逻辑。View代表页面展示组件(即dom展现形式):负责将数据模型转化成UI 展现出来。ViewModel为model和view之间的桥梁:监听模型数据的改变和控制视图行为、处理用户交互。通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
- Model(模型)
模型是指代表真实状态内容的领域模型(面向对象),或指代表内容的数据访问层(以数据为中心)。 - View(视图)
就像在MVC和MVP模式中一样,视图是用户在屏幕上看到的结构、布局和外观(UI)。 - ViewModel(视图模型)
视图模型是暴露公共属性和命令的视图的抽象。MVVM没有MVC模式的控制器,也没有MVP模式的
presenter,有的是一个绑定器。在视图模型中,绑定器在视图和数据绑定器之间进行通信。
MVVM 优点:
低耦合 :View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化
的时候Model可以不变,当Model变化的时候View也可以不变。
可重用性 : 可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。
独立开发 : 开发人员可以专注于业务逻辑和数据的开发,设计人员可以专注于页面的设计
18、简述MVC与MVVM的区别
MVC和MVVM的区别并不是VM完全取代了C,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用。
- MVC中Controller演变成MVVM中的ViewModel
- MVVM通过数据来显示视图层而不是节点操作
- MVVM主要解决了MVC中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验
19、computed
computed是Vue中一个计算属性,它可以根据依赖的数据动态计算出一个新的值,并将其缓存起来。computed属性是基于它们的依赖进行缓存的,只有当依赖发生变化时,才会重新计算。
computed属性具有以下特性:
缓存:computed属性会缓存计算结果,在依赖不变时直接返回缓存值,提高性能。
响应式:当依赖发生变化时,computed属性会自动重新计算并更新。
依赖追踪:Vue会自动追踪computed属性所依赖的数据,并在其发生变化时触发重新计算。
computed 中可以分成 getter(读取) 和 setter(设值),一般情况下是没有 setter 的,computed 预设只有 getter,也就是只能读取,不能改变设值。
setter的写法,可以设值
fullName: {
//getter 方法
get(){
console.log(‘computed getter…’)
return this.firstName + ’ ’ + this.lastName
},
//setter 方法
set(newValue){
console.log(‘computed setter…’)
var names = newValue.split(’ ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
return this.firstName + ’ ’ + this.lastName
}
}
20、Vue中deep选择器的原理
在Vue中,deep选择器主要用于解决scoped样式在多层嵌套组件中的穿透问题。当我们在一个组件的style标签中添加scoped属性时,Vue会在生成的DOM元素上添加一个唯一的动态属性(如data-v-xxx),以确保样式的局部性,防止样式污染全局。然而,当需要修改子组件的样式时,由于scoped样式的限制,直接修改可能无法生效。这时,可以使用deep选择器来穿透scoped样式的限制,直接修改子组件的样式。
21、uniapp有什么缺点
UniApp 的缺点
UniApp 作为跨平台开发框架,在带来诸多便利的同时,也存在一些不容忽视的缺点:
- 性能受限
UniApp 采用了混合开发模式,通过 WebView 展示界面,这就不可避免地带来了性能瓶颈。相比于原生开发,UniApp 应用在打开速度、页面渲染和交互响应方面都有一定的差距。 - 生态系统不完善
UniApp 目前的生态系统还不够完善,特别是对于一些特定领域的需求,例如高级动画、3D 图形处理等,可选组件和库相对较少。这可能会限制开发者发挥创意和实现复杂功能。 - 兼容性问题
在不同平台上构建 UniApp 应用时,可能会遇到兼容性问题。由于 WebView 在各个平台上的实现方式不同,可能会出现样式差异、API 支持不一致等情况,给开发者带来额外的调试和维护工作。 - 安全性限制
WebView 的安全机制与原生应用有所不同,可能会降低应用的安全性。例如,在 WebView 中加载外部内容时,需要格外注意权限控制和数据保护,以防范恶意代码和网络攻击。 - 开发成本高
虽然 UniApp 提供了统一的开发体验,但对于需要同时支持多个平台的应用,每次发布更新都需要进行多次编译打包,这可能会增加开发和维护成本。特别是对于大型应用,开发周期相对较长
5.更新问题:开发过程中,如果需要对Uniapp进行更新,可能会出现一些问题。例如,在更新后,之前的配置或代码可能会失效,导致应用无法正常运行。此外,如果更新需要重新提交审核,那么审核的时间可能会比较长,这也会对开发进度造成一定的影响
22、vue 插槽
Vue 插槽是一种让父组件能够向子组件传递标记的方法,这些插槽可以是具名的,可以是匿名的,也可以是作用域插槽。
匿名插槽:子组件使用标签定义一个插槽,父组件在子组件标签内添加的所有内容都会插入到这个位置。
.
// 子组件
<template>
<div>
<slot></slot>
</div>
</template>
// 父组件
<template>
<child-component>
<p>这是一些内容。</p>
</child-component>
</template>
.
具名插槽:子组件可以定义多个插槽,使用name属性来区分。父组件通过slot="slotName"来指定内容插入到哪个插槽。
.
// 子组件
<template>
<div>
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
</div>
</template>
// 父组件
<template>
<child-component>
<template v-slot:header>
<p>这是头部内容。</p>
</template>
<p>这是默认插槽的内容。</p>
<template v-slot:footer>
<p>这是底部内容。</p>
</template>
</child-component>
</template>
.
作用域插槽:子组件可以通过传递数据给父组件,父组件通过来接收。
.
// 子组件
<template>
<div>
<slot name="user" :user="userInfo"></slot>
</div>
</template>
<script>
export default {
data() {
return {
userInfo: { name: '张三', age: 30 }
};
}
};
</script>
// 父组件
<template>
<child-component>
<template v-slot:user="slotProps">
<p>用户名: {{ slotProps.user.name }}</p>
<p>年龄: {{ slotProps.user.age }}</p>
</template>
</child-component>
</template>
以上是Vue插槽的基本使用方法。在Vue 2.6及以上版本,可以使用v-slot指令简化写法,如v-slot:header可以简化为#header
23、vue keep-alive的作用以及生命周期
是Vue的内置组件,用于缓存组件状态或避免重新渲染。它提供了activated和deactivated这两个生命周期钩子,分别在组件被激活和停用时调用。
使用时,会维护一个缓存的组件实例池,在组件切换时不会被销毁,而是被deactivated,再次显示时会被activated。
keep-alive 组件是 vue 的内置组件 ,用于 缓存内部组件 实例。这样做的目的在于,keep
alive 内部的组件切回时, 不用重新创建 组件实例,而直接使用缓存中的实例,一方面能够
避免创建组件带来的开销,另一方面可以保留组件的状态 。
keep-alive 具有 include 和 exclude 属性,通过它们可以控制哪些组件进入缓存。另外它 还提供了 max 属性,通过它可以设置最大缓存数,当缓存的实例超过该数时,vue 会移除最久没有使用的组件缓存。
受keep-alive的影响,其内部所有嵌套的组件都具有两个生命周期钩子函数,分别是activated 和 deactivated,它们分别在组件激活和失活时触发。第一次 activated 触发是在 mounted 之后
在具体的实现上,keep-alive 在内部维护了一个 key 数组和一个缓存对象
// keep-alive 内部的声明周期函数
created () {
this.cache = Object.create(null)
this.keys = []
}
key 数组记录目前缓存的组件 key 值,如果组件没有指定 key 值,则会为其自动生成一个
唯一的 key 值 cache 对象以 key 值为键,vnode 为值,用于缓存组件对应的虚拟 DOM
在 keep-alive 的渲染函数中,其基本逻辑是判断当前渲染的 vnode 是否有对应的缓存,
如果有,从缓存中读取到对应的组件实例;如果没有则将其缓存。、
当缓存数量超过 max 数值时,keep-alive 会移除掉 key 数组的第一个元素
24、请说出vue.cli项目中src目录每个文件夹和文件的用法?
- assets 文件夹是放静态资源
- components 文件夹是放全局组件的
- router 文件夹是定义路由相关的配置
- store 文件夹是管理 vuex管理数据的位置 模块化开发 全局getters
- views 视图 所有页面 路由级别的组件
- App.vue 入口页面 根组件
- main.js 入口文件 加载组件 初始化等
25、promise finally all rance使用场景
Promise的finally、all、race方法在不同的场景中有着各自独特的应用。
- finally方法
主要用于指定无论Promise对象最后状态如何,都需要执行的操作。它不接受任何参数,因此其内部的操作应该是与状态无关的,不依赖于Promise的执行结果。finally方法的实现本质上是then方法的特例,能够确保无论Promise是fulfilled还是rejected,都能执行特定的操作。这种特性使得finally非常适合用于清理资源、关闭连接、释放内存等操作,确保这些操作无论Promise的执行结果如何都会被执行
- all方法
用于将多个Promise实例包装成一个新的Promise实例。当所有Promise都成功解决(fulfilled)时,all方法返回的Promise才会解决,并将所有Promise的解决值作为一个数组返回。如果任何一个Promise被拒绝(rejected),则all方法返回的Promise也会被拒绝,并且会返回第一个被拒绝的Promise的原因。all方法适用于需要等待多个异步操作都成功完成才能继续后续操作的情况,比如同时从多个数据源获取数据后进行汇总处理
- race方法
与all方法类似,也是用于处理多个Promise对象,但它不等待所有的Promise都解决或拒绝,而是返回最早解决的Promise的结果。如果某个Promise首先解决或拒绝,那么race方法返回的Promise就会以那个结果为准。race方法适用于需要快速得到结果,而不关心其他Promise是否完成的情况2。
综上所述,finally方法适合用于需要确保无论结果如何都必须执行的操作,all方法适合用于需要等待所有操作完成后再进行后续处理的情况,而race方法则适合用于需要快速得到结果,而不关心其他操作是否完成的情况。这些方法的使用场景根据具体的需求和业务逻辑而有所不同
26、 请简述构建 vue-cli 工程都用到了哪些技术,他们的作用分别是什么
vue.js:vue-cli 工程的核心,主要特点是双向数据绑定和组件系统。
vue-router:vue 官方推荐使用的路由框架。
vuex:专为 Vue.js 应用项目开发的状态管理器,主要用于维护 vue 组件间共用的一些 变量 和 方法。
axios(或者 fetch、ajax):用于发起 GET 、或 POST 等 http请求,基于 Promise 设计。
vux等:一个专为vue设计的移动端UI组件库。
webpack:模块加载和vue-cli工程打包器。
eslint:代码规范工具
vue-cli 工程常用的 npm 命令有哪些?
下载 node_modules 资源包的命令:npm install
启动 vue-cli 开发环境的 npm命令:npm run dev
vue-cli 生成 生产环境部署资源 的 npm命令:npm run build
用于查看 vue-cli 生产环境部署资源文件大小的 npm命令:npm run build --report
27、 简述 Vue 2.0 响应式数据的原理( 重点 )
Vue2.x 采用 数据劫持结合发布订阅模式 (PubSub 模式)的方式,通过 Object.defineProperty 来劫 持 各个属性 的 setter、getter ,在 数据变动时 发 布消息给订阅者 , 触发相应的监听回 调。
当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用
Object.defineProperty 它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让
Vue 追踪依赖,在属性被访问和修改时 通知变化 。
Vue 的数据 双向绑定 整合了 Observer,Compile 和 Watcher 三者,通过 Observer 来监听 自己的
model 的数据变化,通过 Compile 来解析编 译模板指令,最终 利用 Watcher 搭 起 Observer 和
Compile 之间的 通信桥梁 ,达到数据变化->视图更新,视图交互变化(例如 input 操作)->数据
model 变更的双向绑定效果。
Vue3.x 放弃了 Object.defineProperty ,使用 ES6 原生的 Proxy,来解决以前使用
Object.defineProperty 所存在的一些问题。
- vue2.x问题
在Vue 2.x中,对象的新增和删除属性无法被Vue的响应式系统检测到,除非你使用Vue提供的特定方法来处理。这是因为Vue在初始化实例时,会遍历data对象中所有的属性,并使其转换为响应式的,但这种转换仅限于属性已经存在的情况。
数组的下标也无法监听,因为数组的长度是动态的,无法在创建时预知。我们只能监听数组的原生方法,如push、pop等,而对于直接修改下标的操作则无法监听。
为了解决这个问题,你应该使用Vue提供的Vue.set方法或者vm. s e t 实例方法来向响应式对象添加一个新属性,并确保这个属性是响应式的,能够触发视图更新。对于删除属性的情况,你可以使用 V u e . d e l e t e 方法或者 v m . set实例方法来向响应式对象添加一个新属性,并确保这个属性是响应式的,能够触发视图更新。 对于删除属性的情况,你可以使用Vue.delete方法或者vm. set实例方法来向响应式对象添加一个新属性,并确保这个属性是响应式的,能够触发视图更新。对于删除属性的情况,你可以使用Vue.delete方法或者vm.delete实例方法来确保删除一个属性后,能够从响应式系统中正确地移除。
以下是一个简单的例子:
// 假设有一个Vue实例
var vm = new Vue({
data: {
myObject: {}
}
});
// 添加一个新的属性
Vue.set(vm.myObject, 'newProp', 'value');
// 或者使用vm实例方法
this.$set(this.myObject, 'anotherProp', 'another value');
// 删除一个属性
Vue.delete(vm.myObject, 'newProp');
// 或者
this.$delete(this.myObject, 'anotherProp');
使用Vue.set或vm. s e t 来保证新添加的属性同样是响应式的,而使用 V u e . d e l e t e 或 v m . set来保证新添加的属性同样是响应式的,而使用Vue.delete或vm. set来保证新添加的属性同样是响应式的,而使用Vue.delete或vm.delete则确保删除的属性不再是响应式的。
28、组件传递参数
Vue 组件之间的通信大概归类为:
-
父子组件通信: props;ref;$attrs / l i s t e n e r s ; listeners; listeners;parent / $children
-
兄弟组件通信: eventBus;vuex
-
跨级通信: eventBus;Vuex;$attrs / $listeners
29、路由守卫的种类
路由守卫主要分为三种类型:全局守卫、路由独享守卫和组件内部生命周期守卫。
1. 全局守卫:适用于所有路由,当浏览器地址发生变化时,路由会进行检测。全局守卫包括:
全局前置守卫(beforeEach):在路由跳转前触发,主要用于登录验证等场景。
全局后置守卫(afterEach):在路由跳转完成后触发,主要用于分析、更改页面标题等辅助功能。
全局前置解析守卫(beforeResolve):在所有组件内守卫以及异步路由组件解析完后触发。
2. 路由独享守卫:仅适用于特定的路由或路由组,通过在路由配置对象中使用beforeEnter和afterEach钩子定义。主要用于控制特定路由的访问权限。
3. 组件内部生命周期守卫:仅适用于特定Vue组件的路由,通过在Vue组件中使用beforeRouteEnter、beforeRouteUpdate和beforeRouteLeave钩子定义。主要用于在组件进入、更新和离开时进行守卫。
这些路由守卫提供了多种机会植入路由导航过程中,可以帮助开发者实现复杂的导航控制和权限验证,确保应用的路由安全和用户体验
30、同步与异步的区别
异步任务和同步任务的主要区别在于任务执行的方式和是否阻塞主线程。异步任务可以相互独立地执行,不会阻塞主线程,并且可以提高系统的响应速度;而同步任务需要按照一定顺序执行,会阻塞主线程,并且可能导致性能下降或系统崩溃等问题。因此,在开发过程中需要根据具体情况选择合适的任务处理方式,以提高软件系统的性能和稳定性。
异步和同步是描述两个或多个事件、操作或进程之间的关系。同步意味着事件、操作或进程是有序的,一个操作必须在另一个操作完成后开始执行。而异步则意味着事件、操作或进程是独立的,可以在不等待其他操作完成的情况下开始执行。
31、axios和fetch有什么区别
Axios和Fetch的主要区别在于:Axios是一个基于Promise的HTTP客户端,提供了更丰富的功能,如请求和响应拦截、请求取消等,而Fetch是浏览器内置的API,虽然也支持Promise,但功能相对简单,处理错误的方式也有所不同。
一、axios和fetch的区别
Axios 和 Fetch 都是 JavaScript 中用于发送 HTTP 请求的 API,它们的主要区别在以下方面:
- Axios 支持更广泛的浏览器和 Node.js 版本,而 Fetch 只能在较新的浏览器中使用,或需要使用 polyfill 兼容旧版浏览器。
- Axios 可以拦截请求和响应,可以全局配置默认的请求头、超时时间等,而 Fetch 目前不支持这些功能。
- Axios 默认返回 JSON 格式的数据,而 Fetch 返回的是 Response 对象,需要自己通过 Response 的方法(如 json()、text() 等)将结果转换成所需的格式。
- Axios 对于请求错误可以直接抛出异常,方便进行错误处理,而 Fetch 的错误处理比较繁琐,需要手动检查 Response.ok 属性。
- fetch是原生js自带的,axios是封装的原生的xhr
32、Event Loop
js的事件循环机制
js是单线程它有 ,同步任务和异步任务,一异步任务又分为微任务和宏任务
event loop:JS 主线程不断的循环往复的从任务队列中读取任务,执⾏任务,这种运⾏机制称为事件循环(event loop)
- 执行顺序:
js代码在执行的时候,会先执行宏任务script ==> 进入script后,所有的同步任务主线程执行 ==> 遇到异步宏任务则将异步宏任务放入宏任务队列中 ==> 遇到异步微任务则将异步微任务放入微任务队列中 ==> 当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行 ==> 所有的微任务执行完毕后,再将异步宏任务从队列中调入主线程执行 ==> 一直循环至所有的任务执行完毕(完成一次事件循环EventLoop)。
注:一次事件循环只能处理一个宏任务,一次事件循环可以将所有的微任务处理完毕。
- 简述:
先执行同步任务队列执行完了,再执行微任务队列完了之后再执行宏任务,每 执行一次宏任务时会时时检测微任务队列有没有任务,如果有微任务 会先清空微任务队列,执行完了 再去执行下一个宏任务。所以一次循环,这就是事件循环
33、class从ES5到ES6的定义转变,继承转变
一、class从ES5到ES6的定义转变
ES5的定义与调用
<script>
function phone(name,type) {
this.name=name;
this.type=type;
}
phone.prototype.call=function () {
console.log("打电话");
}
let p=new phone("华为","荣耀");
p.call();
</script>
ES6的定义与调用
class phone6
{
constructor(name,type)
{
this.name=name;
this.type=type;
}
call()
{
console.log("ES6打电话")
}
}
let p6=new phone6("小米","os");
p6.call();
二、class从ES5到ES6的继承转变
ES5的继承
<script>
//手机
function phone(name,price) {
this.name=name;
this.price=price;
}
phone.prototype.call=function () {
console.log("电话来了");
}
function smartPhone(name,price,size,color) {
phone.call(this,name,price);
this.size=size;
this.color=color;
}
//继承,这样子才能调用phone的函数
smartPhone.prototype=new phone;
smartPhone.prototype.smartcall=function () {
console.log("现在我是智能手机");
}
let smart=new smartPhone("华为","34444","23","白色");
console.log(smart);
smart.smartcall();
</script>
ES6的面向对象的继承
<script>
class phone{
constructor(name,price)
{
this.name=name;
this.price=price;
}
call()
{
console.log("打电话");
}
}
class smartPhone extends phone
{
constructor(name,price,size,color)
{
super(name,price);
this.size=size;
this.color=color;
}
smartcall()
{
console.log("我是智能手机");
}
}
const shouji=new smartPhone("华为",2344,"23inc","红色");
console.log(shouji);
shouji.call();
</script>
34、前端全局缓存有几级缓存怎么去控制这些缓存
Web缓存种类: 数据库缓存,CDN缓存,代理服务器缓存,浏览器缓存。
前端全局缓存通常可以分为以下几个级别:
-
浏览器缓存:这是客户端的本地存储,包括HTTP缓存(协商缓存和强缓存)以及Cookies、LocalStorage等。
强缓存:强缓存是指浏览器在访问URL时,不会向服务器发送请求,直接从缓存中读取资源,并返回状态码200。强缓存的设置可以通过HTTP响应头中的Cache-Control和Expires字段来实现。Cache-Control优先级更高,常见的取值有public、private、no-cache、no-store、max-age等。Expires则是一个具体的时间戳,表示资源的过期时间。
协商缓存:当强缓存失效后,浏览器会进行协商缓存。浏览器会向服务器发送请求,并通过If-Modified-Since、If-None-Match等头部与服务器确认资源是否修改。如果资源未修改,服务器返回状态码304,浏览器继续使用缓存中的资源;如果资源已修改,服务器返回全新资源。 -
CDN缓存:内容分发网络(CDN)会在边缘节点缓存静态资源,以减少延迟和带宽消耗。
-
服务器缓存:在后端服务器上的缓存,如Redis、Memcached等,用于加速动态内容的响应。
-
代理缓存:通过代理服务器缓存请求结果,以提高访问速度。
控制这些缓存的方法包括: -
HTTP缓存头:通过设置Cache-Control、Expires、ETag、Last-Modified等头部字段来控制浏览器缓存。
-
服务端配置:在CDN或反向代理服务器上进行缓存策略配置,比如设置TTL(生存时间)、缓存规则等。
-
数据库与缓存层:使用如Redis、Memcached等缓存工具时,可以通过设置过期时间、淘汰策略等来管理缓存。
-
代码逻辑控制:在前端代码中,可以使用条件判断来读取和写入LocalStorage或Cookies,确保数据一致性和及时更新。
35、vue-router的hash和history模式的区别和原理
单页面应用(SPA,Single-page Web applications)是一种特殊的Web应用
它加载单个HTML页面,并在用户与应用交互时动态更新该页面,而不是重新加载整个页面。
SPA通过前端路由技术实现不同视图或组件之间的切换,提高了用户体验和页面加载速度。
在实现SPA时,Vue-Router提供了两种路由模式用于构建单页面应用:hash模式和history模式。
- Hash模式(默认):
- URL中永远带有#,例如http://www.example.com/#/about。URL不美观可能影响用户体验且不利于SEO
- 兼容性好,可以在所有现代浏览器中工作,不需要服务器端配置特殊的路由规则
- 当#后面的部分发生变化时,不会向服务器发送请求。
- 利用了onhashchange事件监听#后面的部分变化。
- History模式:
- URL中不带#,看上去像普通的URL,例如http://www.example.com/about。URL更美观且利于SEO
- 兼容性较差,在一些旧版浏览器中不支持(IE >= 10),且需要服务器端进行相应的配置
- 当路由改变时,可能会向服务器发送请求。
- 依赖于HTML5 History API中的pushState和replaceState方法。
实现原理:
Hash模式:通过监听hashchange事件,然后通过window.location.hash获取当前的hash值,进行路由切换。
History模式:通过调用history.pushState和history.replaceState来改变浏览器的历史记录,不会向服务器发送请求。当路由改变时,需要服务器配置支持,否则页面会404。
示例代码:
// 使用Vue Router
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from './components/Home.vue';
import About from './components/About.vue';
Vue.use(VueRouter);
// 创建Router实例
const router = new VueRouter({
mode: 'history', // 使用History模式
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About },
]
// 创建Vue实例
new Vue({
router,
template:
'<div>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-view></router-view>
</div>'
}).$mount('#app');
在实际应用中,可以根据需要选择使用Hash模式或History模式。
如果不需要SEO优化或者不介意网址变化时对服务器造成的额外请求,可以使用Hash模式。
如果对SEO有要求或者需要应用能在所有浏览器中正常工作,推荐使用History模式。
36、前端图表的底层实现方式有哪两种
前端图表的底层实现方式主要有两种:Canvas和SVG。
- Canvas
是一种通过JavaScript和HTML的元素来绘制图形的方式,它是一种命令式的图形API。使用Canvas绘制图形时,需要先获取元素并获取上下文,然后使用一系列命令来描述图形的绘制过程。例如,设置绘制样式属性、绘制边框、填充颜色等。Canvas适合绘制复杂的图形和游戏等动态内容1。
- SVG
则是一种基于XML标记语言的声明式API,用于描述二维矢量图形。SVG能够优雅而简洁地渲染不同大小的图形,并与CSS、DOM、JavaScript和其他网络标准无缝衔接。使用SVG时,可以通过声明式的方法定义图形的各种属性,如形状、颜色、大小等。SVG适合用于需要高保真度的静态图形和图标
此外,还有一种技术是WebGL,它是一种更高级的3D图形API,可以用于绘制复杂的3D场景和动画。WebGL基于Canvas,通过GPU加速来实现高性能的3D图形渲染
37、前端自定义组件的v-model
在Vue.js中,v-model是一个非常方便的指令,用于在输入表单元素和应用状态之间创建双向数据绑定。当用在自定义组件上时,v-model默认会利用名为value的prop和名为input的事件。
如何使用v-model在自定义组件中
.
定义prop: 在你的组件中定义一个名为value的prop。
.
.
监听输入: 当组件内部的输入值变化时,使用this.$emit(‘input’, newValue)来通知父组件更新值。
.
.
使用v-model: 父组件中使用v-model绑定到这个自定义组件。
.
示例
假设我们有一个自定义的输入框组件,我们想要使用v-model来实现双向绑定。
CustomInput.vue
vueCopy Code
<template>
<input :value="value" @input="$emit('input', $event.target.value)">
</template>
<script>
export default {
props: ['value']
}
</script>
ParentComponent.vue
vueCopy Code
<template>
<div>
<custom-input v-model="inputValue"></custom-input>
</div>
</template>
<script>
import CustomInput from './CustomInput.vue';
export default {
components: {
CustomInput
},
data() {
return {
inputValue: ''
}
}
}
</script>
自定义model选项
Vue.js还允许你自定义用于v-model的prop和事件。你可以通过组件的model选项来实现。
vueCopy Code
<script>
export default {
model: {
prop: 'checked',
event: 'change'
},
props: ['checked'],
methods: {
toggleChecked() {
this.$emit('change', !this.checked);
}
}
}
</script>
在这个例子中,v-model将绑定到checkedprop,而不是默认的value,并且监听change事件而不是input事件。
通过这种方式,Vue的v-model指令为开发者提供了一种非常便利的方法来创建自定义的表单输入组件,同时保持数据的双向绑定。
38、uniapp性能优化
uni-app性能优化主要包括代码优化、组件优化、图片优化、网络请求优化、页面加载优化以及长列表优化。
代码优化涉及避免不必要的计算和重复计算,优化数据结构。
组件优化要求合理使用组件的生命周期,避免在不必要的生命周期钩子中执行耗时操作,对于频繁更新的组件,使用性能优化方法避免不必要的重新渲染。
图片优化包括压缩图片大小,提供合适尺寸的图片。
网络请求优化涉及合并和减少网络请求的次数,对网络请求进行缓存。
页面加载优化要求懒加载非关键内容,合理使用预加载和预取技术。
长列表优化建议将长列表中的每个item做成一个组件,以实现差量数据更新
UniApp基本概念:
UniApp是基于Vue.js的跨平台应用开发框架,可编译生成在iOS、Android、H5等平台运行的应用。
特点包括跨平台、高性能、开放生态和开发便捷。
.
生命周期钩子函数:
.
页面/组件生命周期:onLoad、onShow、onReady、onHide、onUnload。
应用生命周期:onLaunch、onShow、onHide等。
.
39、说一下typescript
TypeScript是一种由Microsoft开发的JavaScript的超集,它添加了静态类型系统和其他特性,如接口、枚举、泛型等,这些特性有助于在开发过程中捕获错误,并使代码更易于维护和理解。TypeScript编译成纯JavaScript,因此可以在任何支持JavaScript的环境中运行
- 主要特性
- 静态类型系统:TypeScript提供了静态类型系统,允许开发者在声明变量、函数参数和返回值时指定类型,从而在编译时捕获类型错误,减少运行时错误
- 接口:TypeScript支持接口,这是一种定义对象形状的方式,可以确保类或其他对象具有特定的属性或方法
- 枚举:TypeScript的枚举类型提供了一种定义数值集合的方式,相比在JavaScript中手动创建集合,枚举类型提供了更好的可读性和安全性
- 泛型:泛型允许在编写代码时使用类型参数,这些参数在代码实例化时被替换为具体的类型,有助于编写可重用的组件
- 类:TypeScript提供了类语法,用于定义对象的结构和行为,可以包含属性、方法和构造函数等
- 装饰器:装饰器是TypeScript的一种特殊声明,可以附加到类声明、方法、属性或参数上,用于增强代码的功能
- 模块:TypeScript支持ES6模块语法,允许将代码拆分成可重用的模块,实现代码的模块化
- 优势
- . 适用于大型项目:TypeScript的类型系统可以提高大型项目的可维护性,减少bug
- 提高开发效率:TypeScript的类型推论减少了大部分手动声明类型的需要,增强了编辑器的功能,如代码补全和接口提示,提高了开发效率
- 与JavaScript共存:可以在现有JavaScript项目中逐步引入TypeScript特性,无需完全迁移项目3。
编译选项:通过修改编译选项,可以调整类型检查的严格程度,适应不同项目的需求
40、TypeScript的泛型和接口
TypeScript的泛型和接口是两个重要的概念,它们可以帮助我们更好地进行类型检查和代码复用。
- 泛型:泛型是一种在编译时提供类型的机制,它允许我们在编写代码时使用占位符来表示类型,然后在使用时再指定具体的类型。这样可以避免重复编写相似的代码,提高代码的可读性和可维护性。
例如,我们可以定义一个泛型函数getArrayValues
,这个函数接收一个数组作为参数,并返回一个新的数组,其中每个元素都是原数组元素的两倍:
function getArrayValues<T>(arr: T[]): (T | null)[] {
return arr.map(item => item * 2);
}
const nums: number[] = [1, 2, 3];
const doubledNums = getArrayValues<number>(nums); // [2, 4, 6]
在这个例子中,我们使用了泛型T
来表示数组元素的类型。在调用getArrayValues
函数时,我们指定了T
为number
类型,这样就可以确保返回的新数组中的元素也是number
类型。
- 接口:接口是一种用于描述对象结构的类型,它可以包含多个属性和方法。接口的主要作用是提供一种契约,确保实现接口的类或对象遵循一定的规范。
例如,我们可以定义一个Person
接口,包含name
和age
两个属性:
interface Person {
name: string;
age: number;
}
class Student implements Person {
name: string;
age: number;
major: string;
constructor(name: string, age: number, major: string) {
this.name = name;
this.age = age;
this.major = major;
}
}
const student = new Student("Tom", 20, "Computer Science");
console.log(student.name); // "Tom"
console.log(student.age); // 20
console.log(student.major); // "Computer Science"
在这个例子中,我们定义了一个Person
接口,然后创建了一个Student
类来实现这个接口。通过实现接口,我们可以确保Student
类具有name
和age
两个属性,同时还可以添加其他属性和方法。这样,我们就可以使用接口来约束对象的结构,提高代码的可读性和可维护性。
41、 this指向
- 普通函数调用,此时 this 指向 window
- 构造函数调用, 此时 this 指向 实例对象
- 对象方法调用, 此时 this 指向 该方法所属的对象
- 通过事件绑定的方法, 此时 this 指向 绑定事件的对象
- 定时器函数, 此时 this 指向 window
42、 冒泡排序
冒泡排序的流程如下:
从第一个元素开始,逐一比较相邻元素的大小。
如果前一个元素比后一个元素大,则交换位置。
在第一轮比较结束后,最大的元素被移动到了最后一个位置。
在下一轮比较中,不再考虑最后一个位置的元素,重复上述操作。
每轮比较结束后,需要排序的元素数量减一,直到没有需要排序的元素。
排序结束。
这个流程会一直循环,直到所有元素都有序排列为止。
// 定义函数,用于实现冒泡排序算法
function bubbleSort(arr: number[]): number[] {
// 外层循环,控制需要比较的轮数
for (let i = 0; i < arr.length - 1; i++) {
// 内层循环,控制每轮需要比较的次数
for (let j = 0; j < arr.length - 1 - i; j++) {
// 如果前一个元素比后一个元素大,则交换它们的位置
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
// 返回排序后的数组
return arr;
}
// 测试代码
const arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48];
console.log(bubbleSort(arr));
// 输出:[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
43、选择排序
选择排序(Selection Sort)是一种简单的排序算法。
它的基本思想是:
首先在未排序的数列中找到最小(大)元素,然后将其存放到数列的起始位置;
接着,再从剩余未排序的元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
以此类推,直到所有元素均排序完毕。
function selectionSort(arr: number[]): number[] {
// 循环遍历整个数组
for (let i = 0; i < arr.length; i++) {
// 预设最小数的索引为当前循环的索引
let minIndex = i;
// 在后面的数中寻找更小的数
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
// 如果找到更小的数,记录它的索引
minIndex = j;
}
}
// 如果当前循环的索引不是最小数的索引,交换它们
if (i !== minIndex) {
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
}
// 返回排序后的数组
return arr;
}
// 测试数据
const testArr = [5, 2, 9, 1, 5, 6];
// 调用插入排序函数
const sortedArr = selectionSort(testArr);
// 打印结果
console.log(sortedArr);
44、快速排序
function quickSort(arr) {
if (arr.length <= 1) {
return arr;
}
let left = [];
let right = [];
let mid = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < mid) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat(mid, quickSort(right));
}
45、性能优化
- 路由、组件懒加载、按需加载Webpack会自动将应用划分为多个chunk,实现按需加载
引入项目框架时,先删除本次项目不用的资源,一定要及时删除没用的代码 - 静态文件一定要进行压缩,public文件中的js,css文件并没有通过webpack进行压缩,可以自行压缩或者修改构建流程
- 使用CDN进行加速,CDN的费用相对便宜,能加速不少。修改静态资源时,记得添加一个版本号?v=1.0.0。以免取得cdn缓存的值。记得设置CDN的缓存时间,好像CDN的默认缓存时间为1小时
- 使用Webpack的externals配置项:将一些不需要打包的模块通过CDN引入,减小打包体积
- 使用Webpack的Tree Shaking功能可以去除项目中未使用的代码,减小打包后的文件大小。可以通过配置optimization中的usedExports选项来实现。。webpack 默认为usedExports=true;
- 开启gzip压缩compression-webpack-plugin:在服务器端配置gzip压缩,可以减小传输过程中的数据量,提高加载速度,对打包文件进行压缩,减小文件大小,加快加载速度。webpack优化系列二:Vue配置compression-webpack-plugin实现Gzip压缩
- 预加载关键资源,使用或标签,在首次加载完成后,提前加载其他资源。
- 代码分割:使用Webpack的代码分割功能,将代码分割成多个小块,实现按需加载,提高页面加载速度,配置SplitChunksPlugin,提取公共库和重复模块为单独的chunk,避免重复加载。webpack优化系列四:vue打包后生成的chunk-vendors文件过大,利用SplitChunks插件,分离chunk
- 减少外部库的依赖:尽可能减少不必要的库和框架的使用,以减少总体积。
- 升级 webpack 版本: 首先,确保你正在使用最新版本的 webpack。新版本通常会改进性能,并修复一些已知的问题。可以通过升级 webpack 和其相关插件来获得更好的性能。
- 去掉代码中的console.logwebpack优化系列六:vue项目配置 terser-webpack-plugin 压缩 JS,去除console
- 将public的静态资源移入assets。静态资源应该放在assets下,public只会单纯的复制到dist,应该放置不经webpack处理的文件,比如不兼容webpack的库,需要指定文件名的文件等等
46、vue和rect框架区别
1、框架不同
Vue本质是MVVM框架,由MVC发展而来; React就是一个前端组件化框架,由后端组件化发展而来。
2、数据流方向
2.1 数据流向及数据变化
1.vue的思想是响应式的,实现了数据的双向绑定。通过对每一个属性建立Watcher来监听,当属性变化的时候,响应式的更新对应的虚拟dom。
2.react整体是函数式的思想,是单向数据流,倾向于数据不可变。react在setState之后会重新走渲染的流程。
2.2监听数据变化的实现原理不同
1.Vue通过 getter/setter以及一些函数的劫持,能精确知道数据变化。
2.React默认是通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的VDOM的重新渲染。3.为什么React不精确监听数据变化呢?这是因为Vue和React设计理念上的区别,Vue使用的是可变数据,而React更强调数据的不可变,Vue更加简单,而React构建大型应用的时候更棒。
3、模板语法
3.1 vue 采用了template, react采用了jsx
4、渲染
Vue可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
React在应用的状态被改变时,全部子组件都会重新渲染。通过shouldComponentUpdate这个生命周期方法可以进行控制,但Vue将此视为默认的优化。
5、diff算法
Vue Diff使用双向链表,边对比,边更新DOM 。
React主要使用 diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。
6、事件机制
1.Vue原生事件使用标准Web事件。Vue组件自定义事件机制,是父子组件通信基础。vue合理利用了snabbdom库的模块插件
2.React的原生事件被包装,所有事件都冒泡到顶层document监听,然后在这里合成事件下发。React是基于这套可以跨端使用事件机制,而不是和Web
DOM强绑定。 React组件上无事件,父子组件通信使用props。
7、性能
react的性能优化需要手动去做 而vue的性能优化是自动的 但是vue的响应式机制也有问题,就是当state特别多的时候,Watcher也会很多,会导致卡顿,所以大型应用(状态特别多的)一般用react,更加可控。
Vue和React在性能上是相似的。但是两者在优化性能时有一些差异:
1.在react中,当组件状态改变时,它会触发整个子组件数重新渲染,以根组件作为渲染基点。为了避免不必要的子组件重新渲染,你需要使用PureComponent或者实现
shouldComponentUpdate。
2.在Vue中,一个组件在渲染期间依赖于自动追踪,因此系统知道提前预判哪一个组件需要渲染当组件状态发生改变时。每个组件可以被认为具有自动为你实现shouldComponentUpdate,不需要注意嵌套的组件。
基于以上的梳理,总结出以下不同:
1.框架的本质是不同的:vue的MVVM的数据双绑的模式,react是构建用户界面的js库。
2.数据流及响应式的不同:vue是数据双绑,react数据流向是单向的。监听数据变化的实现原理不同。
3.模板语法不同:vue是指令+模板语法,react是jsx函数式编程。
4.渲染区别:vue是数据变化通知依赖项精确的驱动渲染,react是需要调用setState时重新渲染全部子组件,但是可以通过shouldComponentUpdate等一些方法进行优化控制
5.diff:Vue Diff使用双向链表边对比边更新,react的diff将需要更新的部分添加到任务队列进行批量更新
6.事件机制:vue直接是原生事件,react是合成事件:事件冒泡到根节点进行事件委托处理,且做了跨端兼容处理。
47、在uniapp中怎么保证页面是最新的
这在uni-app中,保证页面是最新的可以通过以下几种方法实现:
- 确保HTML文件不缓存:
- 在HTML文件中添加特定的meta标签来禁用浏览器缓存。这些标签包括
Cache-Control
、Pragma
和Expires
。具体代码如下:<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"> <meta http-equiv="Pragma" content="no-cache"> <meta http-equiv="Expires" content="0">
- 将这些标签添加到项目的
index.html
模板中,可以确保每次访问页面时都从服务器加载最新的内容。
- 在HTML文件中添加特定的meta标签来禁用浏览器缓存。这些标签包括
- 确保JavaScript文件不缓存:
- 修改
vue.config.js
文件,配置webpack的输出选项,设置filename
和chunkFilename
为带有hash值的文件名。这样可以确保每次打包生成的文件名都是唯一的,从而强制浏览器加载最新的文件。示例配置如下:module.exports = { configureWebpack: { output: { filename: 'static/js/[name].[hash:8].js', chunkFilename: 'static/js/[name].[hash:8].chunk.js' } } }
- 修改
- 使用HTTP头控制缓存:
- 通过服务器配置HTTP头,设置
Cache-Control
为no-cache, no-store, must-revalidate
,Pragma
为no-cache
,以及Expires
为0
。这可以在服务器端强制客户端每次都从服务器获取最新版本的资源。
- 通过服务器配置HTTP头,设置
- 利用版本号或时间戳更新资源:
- 在请求静态资源(如CSS、JS、图片等)时,可以在文件名后添加版本号或当前时间戳作为查询参数。这样,每次资源更新时,URL都会变化,从而绕过浏览器缓存。例如:
<link rel="stylesheet" href="styles.css?v=1.0"> <script src="script.js?timestamp=xxx"></script>
- 在请求静态资源(如CSS、JS、图片等)时,可以在文件名后添加版本号或当前时间戳作为查询参数。这样,每次资源更新时,URL都会变化,从而绕过浏览器缓存。例如:
- 监听数据变化并强制刷新:
- 如果页面中的数据是通过API请求获取的,可以在数据更新后手动刷新页面或相关组件。例如,使用Vue的
this.$forceUpdate()
方法强制重新渲染组件。 - 另外,可以使用计算属性或watcher来监听数据变化,并在数据变化时执行某些操作(如重新获取数据或更新视图)。
- 如果页面中的数据是通过API请求获取的,可以在数据更新后手动刷新页面或相关组件。例如,使用Vue的
- 版本号比较
- 配置Uniapp项目public目录下的manifest.json文件 配置对应的appid和version。
调用接口获取应用程序的最新版本号,如果取得的版本号和当前安装的应用程序版本号不一样,则可以提示用户进行更新,调用uni-app提供的getUpdateManager函数来创建一个更新对象。然后通过onCheckForUpdate监听应用程序是否有新版本更新。如果有新版本更新,则会弹出是否进行更新的提示框。如果用户点击确认,则会调用onUpdateReady函数,开始进行新版本的下载和更新uni.getUpdateManager().onCheckForUpdate(function (res) { if (res.hasUpdate) { uni.showModal({ title: '发现新版本', content: '是否进行更新?', success: function (res) { if (res.confirm) { uni.getUpdateManager().onUpdateReady(function () { uni.showModal({ title: '更新提示', content: '新版本已经下载完成,是否立即更新?', success: function (res) { if (res.confirm) { uni.getUpdateManager().applyUpdate(); } else if (res.cancel) { uni.showToast({ title: '放弃更新', icon: 'none' }); } } }); }); } } }); } else { uni.showToast({ title: '已是最新版本', icon: 'none' }); } });
- 配置Uniapp项目public目录下的manifest.json文件 配置对应的appid和version。
48、封装出高性能、低耦合、易于维护和扩展的公共组件
1. 独立性:组件应该不依赖于外部环境的特定条件,确保在不同的使用场景下都能正常工作
2. 可配置性:组件应该能够根据不同的需求进行自定义配置,提高其灵活性和复用性
3. 可扩展性:组件应该具有良好的扩展性,方便后续的功能扩展和维护
4. 高性能和低耦合:通用组件必须具备高性能和低耦合的特性,避免使用全局状态管理工具如Vuex,以减少内存占用和性能损耗
5. 良好的文档和示例:组件应该提供详细的文档和示例,方便其他开发者使用和理解
6. 输入和输出:组件的输入和输出应该清晰明确,确保组件在不同场景下的正确使用
7. 避免与业务耦合:组件应该尽量避免与业务逻辑的耦合,确保其通用性和复用性
封装公共组件的具体步骤和原则:
1. 定义组件:使用Vue.component(‘组件名’, 组件对象)来定义全局组件,提供丰富的配置项,允许使用者根据实际需求进行动态配置。例如,可以设置组件的颜色、大小、形状等属性,以满足不同项目的视觉和功能需求。
2. 遵循封装原则:封装时考虑组件的独立性、可配置性、可扩展性和低耦合性
3.父对子传参:使用props进行父对子传参,避免子组件生成数据,确保数据从父组件传入
4. 事件处理:将事件处理方法放在父组件中,通用组件只作为中转,减少组件间的直接依赖
5. 使用slot:利用 Vue 的插槽(slot)机制,允许使用者在组件中自定义内容,增加组件的灵活性和扩展性
6. 避免使用Vuex:尽量避免使用Vuex进行参数传递,以减少内存占用和性能损耗,
7. 编写CSS:合理使用scoped属性编写CSS,避免全局样式污染,同时减少重复代码
49、原型链的理解
原型链是JavaScript中一个重要且基础的概念,它用于实现对象的属性继承。以下是对原型链的详细理解:
-
概念定义:原型链是由多个对象通过其
__proto__
属性(在ES6之前为非标准的__proto__
,ES6之后推荐使用Object.getPrototypeOf()
方法)连接而成的链条。每个对象的__proto__
属性指向它的原型对象,而原型对象本身也可能有其自己的原型对象,如此递归下去,直到链条的终点,即null
。 -
作用:
- 属性查找:当访问一个对象的属性时,如果该对象自身没有这个属性,JavaScript引擎会沿着原型链向上查找,直到找到该属性或到达链条的终点(即
null
),此时返回undefined
。这种机制确保了即使对象本身没有定义某个属性或方法,也能通过原型链继承到它们。 - 代码重用和扩展性:由于原型链的存在,不同对象可以共享原型对象上的属性和方法,从而减少了代码冗余,提高了代码的可复用性和灵活性。
- 属性查找:当访问一个对象的属性时,如果该对象自身没有这个属性,JavaScript引擎会沿着原型链向上查找,直到找到该属性或到达链条的终点(即
-
特点:
- 就近原则:当要使用一个值时,程序会优先查找离自己最近的,也就是对象本身有没有,如果自己没有,才会沿着原型链向上查找。
- 引用类型共享:当我们使用或者修改原型链上的值时,其实使用的是同一个值,这体现了引用类型的共享特性。
-
注意事项:
- 在通过原型链实现继承时,需要注意避免对原型对象上的属性进行直接修改,因为这会影响到所有继承自该原型对象的实例。
- 如果需要修改实例的属性或方法,通常应该在实例自身上进行修改,而不是在原型对象上进行修改。
总的来说,原型链实现了属性和方法的共享以及继承关系,它通过对象之间的链接关系,使得对象可以访问到不属于自己的属性和方法。理解原型链对于深入理解JavaScript的继承机制以及编写高效的JavaScript代码都非常重要。
50、虚拟DOM
直接操作浏览器的DOM是非常耗时的,因为每次操作都可能导致页面的回流和重绘。虚拟DOM用JavaScript对象来描述,模拟真实DOM结构,比如说:一个元素对象,包含TagName、props 和 Children这些属性,在内存中进行计算和对比,通过diff算法找出需要更新的部分,最大限度地减少DOM操作,从而提高了性能。
为什么使用虚拟DOM(常问)
- 创建真实DOM的代价高:真实的 DOM 节点 node 实现的属性很多,而 vnode 仅仅实现一些必要的属性,相比起来,创建一个 vnode 的成本比较低。
- 触发多次浏览器重绘及回流:使用 vnode ,相当于加了一个缓冲,让一次数据变动所带来的所有 node 变化,先在 vnode 中进行修改,然后 diff 之后对所有产生差异的节点集中一次对 DOM tree 进行修改,以减少浏览器的重绘及回流。
- 虚拟dom由于本质是一个js对象,因此天生具备跨平台的能力,可以实现在不同平台的准确显示。
- Virtual DOM 在性能上的收益并不是最主要的,更重要的是它使得 Vue 具备了现代框架应有的高级特性。
简述diff算法的理解
-
diff算法的作用:用来修改dom的一小段,不会引起dom树的重绘
-
diff算法的实现原理:diff算法将virtual dom的某个节点数据改变后生成的新的vnode与旧节点进行比较,并替换为新的节点,具体过程就是调用patch方法,比较新旧节点,一边比较一边给真实的dom打补丁进行替换
-
具体过程详解:
a、在采用diff算法进行新旧节点进行比较的时候,比较是按照在同级进行比较的,不会进行跨级比较:
b、当数据发生改变的时候,set方法会调用dep.notify通知所有的订阅者watcher,订阅者会调用patch函数给响应的dom进行打补丁,从而更新真实的视图
c、patch函数接受两个参数,第一个是旧节点,第二个是新节点,首先判断两个节点是否值得比较,值得比较则执行patchVnode函数,不值得比较则直接将旧节点替换为新节点。如果两个节点一样就直接检查对应的子节点,如果子节点不一样就说明整个子节点全部改变不再往下对比直接进行新旧节点的整体替换
d、patchVnode函数:找到真实的dom元素;判断新旧节点是否指向同一个对象,如果是就直接返回;如果新旧节点都有文本节点,那么直接将新的文本节点赋值给dom元素并且更新旧的节点为新的节点;如果旧节点有子节点而新节点没有,则直接删除dom元素中的子节点;如果旧节点没有子节点,新节点有子节点,那么直接将新节点中的子节点更新到dom中;如果两者都有子节点,那么继续调用函数updateChildren
e、updateChildren函数:抽离出新旧节点的所有子节点,并且设置新旧节点的开始指针和结束指针,然后进行两辆比较,从而更新dom(调整顺序或者插入新的内容 结束后删掉多余的内容)
Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 检测差异
考点: Vue 的变化侦测原理
前置知识: 依赖收集、虚拟 DOM、响应式系统
现代前端框架有两种方式侦测变化,一种是pull,一种是push
pull: 其代表为React,我们可以回忆一下React是如何侦测到变化的,我们通常会用setStateAPI显式更新,然后React会进行一层层的Virtual Dom Diff操作找出差异,然后Patch到DOM上,React从一开始就不知道到底是哪发生了变化,只是知道「有变化了」,然后再进行比较暴力的Diff操作查找「哪发生变化了」,另外一个代表就是Angular的脏检查操作。
push: Vue的响应式系统则是push的代表,当Vue程序初始化的时候就会对数据data进行依赖的收集,一但数据发生变化,响应式系统就会立刻得知。因此Vue是一开始就知道是「在哪发生变化了」,但是这又会产生一个问题,如果你熟悉Vue的响应式系统就知道,通常一个绑定一个数据就需要一个Watcher,一但我们的绑定细粒度过高就会产生大量的Watcher,这会带来内存以及依赖追踪的开销,而细粒度过低会无法精准侦测变化,因此Vue的设计是选择中等细粒度的方案,在组件级别进行push侦测的方式,也就是那套响应式系统,通常我们会第一时间侦测到发生变化的组件,然后在组件内部进行VirtualDom Diff获取更加具体的差异,而Virtual Dom Diff则是pull操作,Vue是push+pull结合的方式进行变化侦测的。
51、 vue2 双向数据绑定
Vue2 的双向数据绑定主要通过 v-model 指令和相应的事件处理机制来实现的。v-model 是 Vue 提供的一个语法糖,本质上是 value + change 的组合,它使得数据和视图能够实时同步更新。
单个双向绑定
-
使用 model 属性:
- 在子组件中,通过
model
属性指定要绑定的 prop 和事件。 - 计算属性(computed)用于简化代码,getter 返回 prop 的值,setter 触发事件。
- 在子组件中,通过
-
使用 .sync 修饰符:
- 在父组件中使用
:value.sync="value"
绑定子组件的 prop。 - 在子组件中,通过 computed 属性监听 prop 的变化,并在 setter 中触发
update:xxx
事件。 - 但这种方式在Vue3中已经被废弃,因此在Vue2中使用时应谨慎。
- 在父组件中使用
多个双向绑定
- 可以通过多个
v-bind:xxx.sync
来实现多个双向绑定。
自定义组件的 v-model 实现
<!-- 父组件 -->
<template>
<Child v-model="value" />
</template>
<script>
export default {
data() {
return {
value: ''
}
}
}
</script>
<!-- 子组件 -->
<template>
<input v-model="input" />
</template>
<script>
export default {
props: {
value: String,
},
model: {
prop: 'value', // 指定 v-model 要绑定的参数叫什么名字,来自于 props 中定义的参数
event: 'change', // 指定要触发的事件名字,将被用于 $emit
},
computed: {
input: {
get() {
return this.value;
},
set(val) {
this.$emit('change', val); // 触发
}
}
}
}
</script>
双向绑定由三个重要部分构成
数据层(Model):应用的数据及业务逻辑
视图层(View):应用的展示效果,各类UI组件
业务逻辑层(ViewModel):框架封装的核心,它负责将数据与视图关联起来
它的主要职责就是:
数据变化后更新视图
视图变化后更新数据
当然,它还有两个主要部分组成
监听器(Observer):对所有数据的属性进行监听
解析器(Compiler):对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数
52、foreach和map
- foreach和map的区别
1. 返回值:forEach没有返回值,如果使用return会返回undefined;而map会返回一个新数组,包含调用函数处理后的值
2. 用途:forEach用于遍历数组并对每个元素执行操作,但不返回新数组;map用于创建新数组,返回处理后的结果
- foreach不会改变原数组
forEach方法用于遍历数组并对每个元素执行一次提供的函数,但它不会改变原数组。如果数组中的元素是对象,forEach可以修改对象的属性,但不能改变整个对象。例如:
let array = [{name: '小红', age: 12}, {name: '小明', age: 18}];
array.forEach(person => {
if (person.name === '小红') {
person.age = 15; // 修改属性
console.log(person === array); // true
}
});
console.log(array); // [{name: '小红', age: 15}, {name: '小明', age: 18}]
在这个例子中,forEach修改了对象的属性,但整个对象并没有被替换,因此原数组被更新了1。
- map不会改变原数组
map方法会创建一个新数组,其结果是原数组中的每个元素调用一个提供的函数后的返回值。原数组不会被改变。例如:
let arr = [1, 2, 3, 4, 5];
let arrMap = arr.map(item => item * 2);
console.log(arr); // [1, 2, 3, 4, 5]
console.log(arrMap); // [2, 4, 6, 8, 10]
在这个例子中,map创建了一个新数组,原数组没有被改变
更多推荐
所有评论(0)