同程旅行前端
这是第一个上来就对我进行自我介绍的面试官,结果他自我介绍了,我还没自我介绍😂 2023/3/15
项目
看得出来面试官是提前对我的简历看了很多的,一上来就先对我的项目进行了分析,然后问我最近的一次实习(就这个最近的一次实习直接把我整不会了,一下就紧张了起来😭)和项目是什么时候
我最近还没有实习,零经验,我直接介绍我自己的项目可以吗?
这里感觉自己介绍的项目太拉胯了,没有很好的体现出自己的项目,接下来应该将自己的项目进行概括才行啊
你为什么要用jsx进行开发组件库呢?有什么好处呢?
JSX 是 React 中一种用于编写组件的语法,它可以将 HTML 和 JavaScript 结合起来,让开发者更加方便地编写动态组件。使用 JSX 进行组件库开发的好处如下:
- 增加可读性和可维护性:JSX 让代码看起来更像是 HTML 模板,这使得代码更容易阅读和理解,也更容易进行修改和维护。
- 提高开发效率:使用 JSX 可以减少开发者在编写组件时需要编写的模板代码,这可以减少代码量,提高开发效率。
- 更好的性能:JSX 可以通过使用虚拟 DOM 来优化组件渲染性能。React 在每次组件更新时会生成新的虚拟 DOM 树,并与旧的虚拟 DOM 树进行比较,然后只更新需要更新的部分,从而提高渲染效率。
- 易于与 React 集成:React 是一种流行的前端框架,使用 JSX 可以使组件库更容易与 React 集成,从而提高组件库的适用性。
综上所述,使用 JSX 进行组件库开发可以提高开发效率、可读性和可维护性,并且可以提高组件渲染性能,从而使组件库更加适用于 React 等前端框架
pinia在这个项目里解决了什么问题
在一个 Vue 3 项目中使用 Pinia 可以解决以下问题:
- 简化状态管理:Pinia 提供了一个简洁的 API,使得我们可以更容易地定义和管理状态,并在整个应用程序中共享它们。
- 更好的类型支持:Pinia 提供了一个类型安全的 API,可以让我们更容易地编写类型安全的代码,并减少错误。
- 更好的可测试性:Pinia 的状态管理使得我们可以更容易地对 Vue 3 组件进行单元测试,从而提高代码的可测试性。
- 更好的性能:Pinia 的状态管理实现了基于 Proxy 的响应式系统,从而提高了性能并减少了不必要的重渲染
综上所述,Pinia 能够帮助我们更好地管理 Vue 3 应用程序中的状态,并且提供了更好的类型支持、可测试性和性能,从而使得我们可以更容易地编写高质量的 Vue 3 应用程序。在这个项目中使用 Pinia,可以提高项目的开发效率和代码质量
pinia的store你是如何进行设计的
在设计 Pinia 的 store 时,我们通常需要考虑以下几个方面:
- 状态的划分:我们需要考虑应用程序中需要管理的状态,并根据不同的功能和需求进行划分。通常情况下,我们会将状态划分为多个 store,每个 store 管理一部分相关的状态。
- Store 的命名:我们需要为每个 store 提供一个唯一的名称,以便在整个应用程序中引用它们。
- Store 的定义:我们需要定义每个 store 的结构,包括 store 的状态、getter、mutation 和 action 等。
- Store 的注册:我们需要将定义好的 store 注册到应用程序中,以便在应用程序的其他地方使用。
- Store 的使用:我们需要在组件中使用 store,通过 getter 获取 store 的状态,并在需要时通过 mutation 和 action 来修改 store 的状态。
在实际的应用程序中,我们通常会根据具体的业务需求来设计 store 的结构和划分。一般来说,我们会将相关的状态放在一个 store 中,并通过模块化的方式来组织多个 store,从而实现更好的可维护性和可扩展性。
在使用 Pinia 的过程中,可以参考官方文档提供的示例和最佳实践,以便更好地设计和管理 store,从而提高应用程序的性能和可维护性
你的store有进行模块的拆分吗?还是说放在一起
在实际应用程序中,将 store 模块化并组织成多个文件通常是比较好的实践,这有助于提高应用程序的可维护性和可扩展性。
通常情况下,我们会将 store 模块化为多个文件,每个文件对应一个 store 模块,然后通过 Vuex 的模块化功能来组织它们。每个 store 模块负责管理自己的状态、getter、mutation 和 action,并且可以通过模块间的调用来实现跨 store 的状态共享。
模块化的方式可以使得 store 更加易于维护和扩展,因为每个模块只负责自己的一部分功能,而不会将所有的状态都放在同一个 store 中,从而导致代码的臃肿和难以维护。
在使用 Pinia 时,同样可以将 store 模块化为多个文件,并使用 Pinia 提供的模块化功能来组织它们。每个 store 模块也负责管理自己的状态、getter、mutation 和 action,并且可以通过其他 store 模块的调用来实现跨 store 的状态共享。
综上所述,将 store 模块化并组织成多个文件是一个比较好的实践,可以提高应用程序的可维护性和可扩展性
你做的组件库当中有遇见什么困难吗?可以举例说明
html5
h5当中新增了哪些
以下是 HTML5 中一些新增的功能和特性:
- 新的语义化标签:HTML5 新增了一些语义化标签,如
<header>
、<footer>
、<nav>
、<article>
、<section>
、<aside>
等,可以更好地描述页面的结构和内容,有助于提高页面的可读性和可访问性。 - 多媒体支持:HTML5 提供了更好的多媒体支持,包括
<audio>
和<video>
标签,可以直接在网页中嵌入音频和视频。 - 本地存储:HTML5 提供了本地存储功能,包括 localStorage 和 sessionStorage,可以在客户端浏览器中存储数据,从而提高应用程序的性能和用户体验。
- Web Workers:HTML5 中新增了 Web Workers,可以在后台线程中执行 JavaScript 代码,从而提高应用程序的性能和响应速度。
- Canvas:HTML5 中新增了
<canvas>
标签,可以在网页中绘制各种图形和动画,有助于实现更加复杂的交互效果。 - 地理位置 API:HTML5 提供了地理位置 API,可以获取用户的地理位置信息,有助于实现基于地理位置的应用。
- Web Socket:HTML5 中新增了 Web Socket,可以实现双向通信,从而实现更加实时和高效的应用程序。
- Web Storage:HTML5 中新增了 Web Storage,可以在客户端浏览器中存储数据,从而提高应用程序的性能和用户体验。
综上所述,HTML5 提供了许多新的功能和特性,可以帮助开发人员更加方便地实现一些复杂的应用场景,提高应用程序的性能和用户体验
html的行内元素和块级元素的区别,都有哪些
HTML 元素可以分为两类:行内元素和块级元素。它们的主要区别在于:
- 显示方式:块级元素在页面上以块的形式展现,它会占据一整行的空间,可以设置宽度、高度、内边距和外边距等属性。而行内元素则不会独占一行,它们在一行内按照从左到右的顺序排列,并且不能设置宽度、高度和内边距等属性。
- 内容模型:块级元素通常用于包含其他块级元素或行内元素,可以包含任何其他元素。而行内元素通常用于包含文本或其他行内元素,不能包含块级元素。
- 默认样式:块级元素通常具有明显的外观特征,例如:段落
<p>
元素会在前后添加空白,标题<h1>
~<h6>
元素会加粗并换行等等。而行内元素通常没有这些明显的外观特征,例如:超链接<a>
元素只是有下划线,并且字体颜色有所变化等等。
以下是一些常见的 HTML 块级元素和行内元素:
块级元素:
<div>
<p>
<h1>
~<h6>
<ul>
、<ol>
、<li>
<table>
<form>
<hr>
<header>
、<footer>
、<nav>
、<section>
等 HTML5 新增的语义化标签
行内元素:
<a>
<span>
<strong>
、<em>
<img>
<input>
<label>
<br>
<button>
<select>
<textarea>
img说行内还是块呢?span说行内还是块
<img>
元素默认是行内元素,但可以通过设置 display 属性为 block 或 inline-block 等值来改变其显示方式。
<span>
元素是一个纯粹的行内元素,它不能包含块级元素,但可以包含其他行内元素。
css
盒子模型
盒子模型是指 HTML 元素在渲染时呈现为一个矩形盒子的模型。这个矩形盒子包含了元素的内容、内边距(padding)、边框(border)和外边距(margin)等部分。
具体来说,盒子模型包含以下几个部分:
- 内容区域(content area):元素内部的实际内容,包括文本、图像、嵌套的元素等。
- 内边距(padding):内容区域和边框之间的空白区域,可以通过 CSS 属性
padding
或padding-*
来设置。 - 边框(border):包围内容和内边距的线条,可以通过 CSS 属性
border
或border-*
来设置。 - 外边距(margin):边框和周围元素之间的空白区域,可以通过 CSS 属性
margin
或margin-*
来设置。
盒子模型在 Web 页面布局中扮演着重要的角色,可以用来控制元素的尺寸、位置、边距和内边距等方面的表现。默认情况下,元素的尺寸是指内容区域的大小,但是通过设置 box-sizing
属性可以改变元素尺寸的计算方式,使其包括内边距和边框
will-change
CSS 属性 will-change
为 web 开发者提供了一种告知浏览器该元素会有哪些变化的方法,这样浏览器可以在元素属性真正发生变化之前提前做好对应的优化准备工作。这种优化可以将一部分复杂的计算工作提前准备好,使页面的反应更为快速灵敏
链接:https://developer.mozilla.org/zh-CN/docs/Web/CSS/will-change
垂直居中布局
translate是基于自身还是父元素
translate()
是一种 CSS transform
函数,用于在元素上进行平移变换。它是相对于元素自身的位置进行平移,而不是相对于父元素或其他元素。
具体来说,translate()
函数接受两个参数:translate(x, y)
,其中 x
和 y
分别代表在水平和垂直方向上的平移距离。如果只给出一个参数,则默认为水平方向上的平移。
translate()
函数的平移距离是相对于元素自身的初始位置进行计算的,而不受到父元素的影响。因此,如果我们想要实现相对于父元素进行平移,可以考虑使用 position
属性和 top
、bottom
、left
、right
属性来控制元素的位置
移动端兼容问题你是如何解决的
移动端兼容问题是前端开发中非常重要的一环,以下是我通常采用的一些解决方法:
- 使用移动端适配方案:移动端设备的屏幕尺寸和像素密度差异较大,因此需要使用适配方案来保证页面在不同屏幕上显示效果一致。常见的适配方案有:rem、vw/vh、flexible.js 等。
- 使用 CSS3 和 ES6 功能时需要进行前缀处理:移动端的浏览器兼容性不如 PC 端,因此在使用 CSS3 和 ES6 功能时需要进行前缀处理,例如
-webkit-
、-moz-
、-ms-
、-o-
等。 - 避免使用过多的图片和动画效果:移动设备的网络环境和硬件性能相对较弱,因此需要尽量减少页面中的图片数量和动画效果,以提高页面的加载速度和流畅性。
- 使用移动端专用的 UI 组件和交互方式:移动设备的操作方式和 PC 端有较大差异,因此需要使用移动端专用的 UI 组件和交互方式,例如滑动、轻扫、长按等。
- 针对不同设备的浏览器进行测试:移动设备的浏览器种类繁多,不同浏览器在兼容性上也有所不同,因此需要在开发完成后对不同设备的浏览器进行测试,以确保页面在各种浏览器上的兼容性。
除此之外,还可以使用一些工具来帮助解决移动端兼容问题,例如 Autoprefixer 可以自动添加 CSS3 前缀,FastClick 可以解决移动端点击事件的延迟等
css的相对单位有哪些
在 CSS 中,相对单位有以下几种:
em
:相对于父元素的字体大小。例如,如果父元素的字体大小为 16px,子元素的font-size
设为 1.5em,则子元素的字体大小为 24px。rem
:相对于根元素的字体大小。例如,如果根元素的字体大小为 16px,元素的font-size
设为 1.5rem,则元素的字体大小为 24px。与em
不同的是,rem
取决于根元素的字体大小,而不是父元素的字体大小。vw
和vh
:相对于视口宽度和高度的百分比。例如,如果视口宽度为 1000px,元素的width
设为 50vw,则元素的宽度为 500px。vmin
和vmax
:相对于视口宽度和高度中较小或较大的那个值的百分比。例如,如果视口宽度为 1000px,视口高度为 800px,元素的width
设为 50vmin,则元素的宽度为 400px(因为视口宽度为较大的值,所以按照视口宽度计算)。%
:相对于父元素的宽度或高度的百分比。例如,如果父元素的宽度为 1000px,元素的width
设为 50%,则元素的宽度为 500px。
相对单位与绝对单位(如像素、英寸等)相比,具有更好的响应式特性,可以根据不同的屏幕尺寸和设备类型自适应地调整大小,因此在响应式设计中得到广泛应用
计算机网络
输入URL
渲染进程
js
普通数据类型存储在哪里?堆还是栈
在 JavaScript 中,普通数据类型的值通常存储在栈内存中。栈是一种后进先出的数据结构,可以高效地管理函数调用和局部变量。
当我们声明一个变量并赋值时,JavaScript 引擎会为该变量分配一段栈内存,并将变量的值存储在其中。当该变量不再使用时,这段栈内存也会被释放,变成可用的空间。
常见的普通数据类型包括数字、字符串、布尔值、null 和 undefined 等。这些类型的值都比较简单,不需要过多的内存空间来存储,因此通常存储在栈内存中。
与普通数据类型不同,引用数据类型(如对象、数组、函数等)的值存储在堆内存中。堆内存是一种动态分配的内存空间,可以存储复杂的数据结构和对象。
当我们声明一个引用类型的变量时,JavaScript 引擎会为该变量分配一段栈内存,并将其指向堆内存中的实际值。因为引用类型的值通常比较复杂,包含大量的属性和方法,因此需要较大的内存空间来存储
深拷贝和浅拷贝的区别。 让你实现一个深拷贝的思路
深拷贝和浅拷贝都是对于复杂数据类型进行复制的操作,区别在于复制的方式不同。
浅拷贝是指创建一个新对象,这个新对象有着原始对象属性值的一份精确拷贝,如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝是指创建一个新对象,这个新对象的值和原始对象的值完全没有关联,即便原始对象中有引用类型的属性,新对象也会开辟新的内存地址,完全拷贝一份新的对象,修改一个对象不会影响到另一个对象。
一个实现深拷贝的思路是:
- 首先判断需要拷贝的对象是否是引用类型,如果是基本类型则直接返回该值。
- 如果是引用类型,则创建一个新的空对象或数组(取决于原始对象的类型)。
- 遍历原始对象的所有属性或元素,将它们的值递归地拷贝到新对象中,这个递归过程需要注意以下几点:
- 如果属性或元素的值是基本类型,则直接复制该值;
- 如果属性或元素的值是引用类型,则递归调用深拷贝函数,并将结果赋值给新对象的相应属性或元素。
- 返回新对象或数组
function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
const newObj = Array.isArray(obj) ? [] : {};
for (let key in obj) {
newObj[key] = deepCopy(obj[key]);
}
return newObj;
}
除了这个方法你还有其他的思路吗?json如果来做深拷贝存在哪些问题
除了递归拷贝之外,还有其他实现深拷贝的思路:
- 使用Object.assign()方法实现浅拷贝,然后对于每个属性值是引用类型的属性,再递归调用深拷贝函数。
- 使用ES6的展开运算符(…)实现浅拷贝,然后对于每个属性值是引用类型的属性,再递归调用深拷贝函数。
- 使用第三方库,如Lodash的_.cloneDeep()方法,该方法能够递归地深拷贝一个对象。
使用JSON.stringify()和JSON.parse()方法进行深拷贝是一种常见的错误做法。虽然这种方法能够将一个对象序列化为JSON字符串,再将JSON字符串解析为一个新对象,但是存在以下几个问题:
- 该方法只能序列化对象中的可枚举属性,不能序列化对象的原型链和方法。
- 如果对象中有循环引用(即一个对象引用了自身),则该方法会抛出错误。
- 该方法不能序列化RegExp、Date、Map、Set等特殊类型的对象,会将其序列化为字符串或空对象。
因此,使用JSON.stringify()和JSON.parse()方法进行深拷贝并不可靠,建议使用其他方法实现深拷贝
箭头函数的作用,箭头函数和普通函数的区别
箭头函数是ES6中新增的一种函数定义方式,主要有以下几个作用:
- 简化函数定义:箭头函数可以使用更短的语法定义函数,省略了function关键字和return语句。
- 更简洁的this指向:箭头函数没有自己的this,它的this指向最近的一层非箭头函数作用域的this,可以避免this指向混乱的问题。
- 更简洁的代码结构:箭头函数通常可以使代码更加简洁易懂,特别是当需要传递回调函数或者进行函数式编程时,箭头函数可以使代码更加简洁易读。
与普通函数相比,箭头函数的主要区别在于this的指向和函数定义的语法结构:
- this指向:箭头函数的this指向在定义时就已经确定了,指向最近的一层非箭头函数作用域的this。而普通函数的this指向在运行时才能确定,可能会受到调用方式、绑定方式等多种因素的影响。
- 语法结构:箭头函数省略了function关键字和return语句,更加简洁明了。同时,箭头函数的参数只有一个时可以省略括号,而普通函数的参数需要用括号括起来。
需要注意的是,由于箭头函数的this指向与普通函数不同,因此在某些场景下可能会出现错误的结果。此外,箭头函数也不能作为构造函数使用,因为它没有自己的this。因此,需要根据实际情况选择合适的函数定义方式
箭头函数的this指向哪里?它的this可以被改变吗
箭头函数的this指向在函数定义时就已经确定了,它的this指向的是定义时所在的作用域中的this,而不是在调用时所在的作用域。
具体来说,箭头函数的this指向最近的一层非箭头函数作用域的this。如果箭头函数本身没有定义作用域,则指向全局对象。这与普通函数不同,普通函数的this指向在调用时才能确定,可能会受到调用方式、绑定方式等多种因素的影响。
由于箭头函数的this指向在定义时就已经确定,因此它的this不能被改变。即使使用apply、call等方法来改变this指向,也无法改变箭头函数的this指向。
需要注意的是,箭头函数的this指向是静态的,不能动态改变,因此在某些场景下可能会出现错误的结果。在这种情况下,可以使用普通函数来替代箭头函数,或者使用bind方法来绑定this指向
typeof检测null
使用typeof检测null的结果是”object
“。
这是因为在JavaScript中,null被认为是一个空对象引用。虽然它不是对象,但typeof检测null的结果是”object”,这是一个历史遗留问题。在ES6中,通过Symbol.hasInstance方法可以正确地检测null,但它并不常用。如果需要判断一个值是否为null,可以直接使用严格等于(===)运算符,因为null只等于它本身,不等于任何其他值
了解微前端吗?微前端目前业内的解决方案 阿里的乾坤了解吗
微前端是一种将前端应用程序拆分为更小、更容易管理的部分的架构风格,每个部分可以独立地开发、测试、部署和扩展。它的主要目的是解决单体应用程序的复杂性和可维护性问题,以及不同应用程序之间的耦合问题。
在业内,目前有许多微前端的解决方案,包括Single-SPA、qiankun、Mosaic、Piral、Luigi等等。这些解决方案都有各自的优缺点和适用场景,可以根据实际需求进行选择。
阿里的qiankun是一种在React、Vue、Angular等前端框架上实现微前端的解决方案。它使用了主应用和子应用的概念,主应用负责整体框架的搭建和管理子应用,子应用则可以使用不同的前端框架进行开发。qiankun提供了统一的路由、状态管理、样式隔离等功能,可以有效地实现微前端架构
微前端的好处
- 技术栈无关性:不同的团队可以使用不同的技术栈来开发不同的微前端应用,而这些应用可以无缝地集成到一个统一的应用中,不会出现技术栈不一致的问题。
- 模块化开发:每个微前端应用都是独立开发的,可以根据需求进行拆分成多个小模块,每个模块可以独立开发、测试、部署和升级,从而提高了开发效率和代码质量。
- 独立部署:每个微前端应用都是独立部署的,可以快速部署新的功能和修复bug,而不需要整个应用重新部署,从而提高了部署效率和灵活性。
- 高可维护性:由于每个微前端应用都是独立开发、测试、部署和升级的,因此可以更容易地维护和更新每个应用,从而提高了整个应用的可维护性。
- 更好的扩展性:微前端应用可以在需要时独立开发和扩展,可以更好地满足业务需求,同时也可以更容易地扩展到新的平台和设备
数组如何进行扁平化的处理?给你几个多维数组,将其平展开来
数组扁平化可以将多维数组转化为一维数组,常用的方法有递归方法和非递归方法。以下是一些实现方法:
递归方法:
flatten(arr) {
var result = [];
arr.forEach(function(item) {
if (Array.isArray(item)) {
result = result.concat(flatten(item));
} else {
result.push(item);
}
});
return result;
}
// 示例
var arr1 = [1, 2, [3, 4], 5, [6, 7, [8, 9]]];
console.log(flatten(arr1)); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
非递归方法:
flatten(arr) {
var result = [];
var stack = [...arr];
while (stack.length) {
var item = stack.pop();
if (Array.isArray(item)) {
stack.push(...item);
} else {
result.unshift(item);
}
}
return result;
}
// 示例
var arr2 = [1, 2, [3, 4], 5, [6, 7, [8, 9]]];
console.log(flatten(arr2)); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
ES6方法:
flatten(arr) {
return arr.flat(Infinity);
}
// 示例
var arr3 = [1, 2, [3, 4], 5, [6, 7, [8, 9]]];
console.log(flatten(arr3)); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
需要注意的是,如果数组元素中包含了对象、函数等复杂类型,则需要根据具体情况进行处理
flat参考链接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
场景题
实现一个功能,我在页面A点击了一个按钮,进入页面B,这个时候我在页面B进行了一个操作,这时候如何让A进行一个刷新,也就是如何实现两个进程的通信
实现两个进程的通信可以通过以下几种方式:
- 使用浏览器的
localStorage
或者sessionStorage
来存储需要传递的数据,然后在另一个页面中读取存储的数据并进行处理。需要注意的是,存储的数据类型必须是字符串,因此需要使用JSON.stringify
和JSON.parse
进行数据的转换。
示例代码:
在页面A中:
localStorage.setItem('data', JSON.stringify({ name: 'John', age: 25 }));
// 跳转到页面B
window.location.href = 'pageB.html';
在页面B中:
// 从localStorage中读取数据
const data = JSON.parse(localStorage.getItem('data'));
// 处理数据
console.log(data.name, data.age); // John 25
// 删除localStorage中的数据
localStorage.removeItem('data');
// 触发页面A的刷新
window.location.reload();
- 使用浏览器的
window.postMessage
方法进行跨窗口通信。该方法可以在不同窗口之间传递数据,并且不同窗口可以处于不同的域名和协议下。
示例代码:
在页面A中:
// 在A页面中注册message事件的监听器
window.addEventListener('message', (event) => {
if (event.origin !== 'http://localhost:3000') {
// 如果不是指定的来源,不予处理
return;
}
// 处理接收到的数据
console.log(event.data);
// 触发页面A的刷新
window.location.reload();
});
// 在A页面中向B页面发送数据
window.open('pageB.html');
在页面B中:
// 在B页面中向A页面发送数据
window.opener.postMessage({ name: 'John', age: 25 }, 'http://localhost:3000');
需要注意的是,该方法存在跨站点脚本攻击(XSS)的风险,因此需要在处理消息时进行数据的合法性检验,确保消息的来源和内容都是可信的。
- 使用第三方的库来实现进程间通信,例如 SignalR、Socket.IO 等。这些库提供了更高级的通信功能,并且支持实时通信、广播消息等特性,但也需要相应的服务器支持。
需要根据具体的场景和需求来选择合适的通信方式
vue的响应式系统。我分了vue2和vue3来讲
在 Vue 2 中,Vue 通过 Object.defineProperty() 来实现响应式系统。当一个对象被传入 Vue 实例进行响应式处理时,Vue 会遍历这个对象的每一个属性,并使用 Object.defineProperty() 把这个属性转换成 getter 和 setter。当这个属性被读取时,getter 会被触发,这个属性就会被添加到依赖中;当这个属性被修改时,setter 会被触发,这个属性的依赖就会被通知,并执行相应的更新操作。这样,当数据被修改时,所有依赖这个数据的地方都会自动更新。
但是,Vue 2 的响应式系统存在一些问题。首先,它只能监听对象的属性,而不能监听新增的属性和删除的属性;其次,它无法监听数组的变化,只能监听数组的索引变化,即当使用数组的 push、pop、shift、unshift、splice 等方法时才能触发更新。
在 Vue 3 中,Vue 引入了 Proxy 对象来实现响应式系统。当一个对象被传入 Vue 实例进行响应式处理时,Vue 会使用 Proxy 对象对这个对象进行代理,这样就可以监听新增的属性和删除的属性,同时也可以监听数组的变化。当一个属性被读取或修改时,Proxy 对象的 get 和 set 方法会被触发,这样就可以实现响应式更新。
Vue 3 的响应式系统还有一个优点,就是它支持了多个根节点,也就是 Fragment。这样可以在不需要添加额外的 DOM 节点的情况下,返回多个元素。
总体来说,Vue 3 的响应式系统更加灵活和高效,能够更好地应对复杂的应用场景
vue2是如何解决数组检测的问题
在 Vue 2 中,对于数组的检测是通过对数组的原型进行改写来实现的。Vue 2 中通过 Object.defineProperty()
方法对数组原型上的7个变异方法进行重写,分别是 push()
、pop()
、shift()
、unshift()
、splice()
、sort()
、reverse()
。在这些方法被调用时,除了执行它们本身的操作外,还会通知依赖更新。
当数据是数组类型时,Vue 会先判断该数组是否具有 __ob__
属性(Observer 对象),如果有则说明已经被观测过,直接返回该 __ob__
对象;如果没有,则会创建一个 Observer
对象来观测该数组,然后返回该 __ob__
对象。
虽然这种方式可以监听数组的变化,但是存在以下问题:
- 监听不到索引值的变化,比如
arr[1] = newValue
。 - 对象的新增和删除也需要进行额外处理。
- 遍历数组时会将数组中的每一项都进行依赖收集,造成性能问题。
Vue 提供了 $set
方法,可以用来给数组添加新元素或者修改已有元素的值,使得这些修改也能够被 Vue 监听到并更新视图。例如:
this.$set(this.array, 1, 'new value')
这行代码会将 array
数组中索引为 1
的元素的值改为 'new value'
,并通知 Vue 去更新视图。
除了 $set
方法,Vue 还提供了 $delete
方法来删除数组中的元素
为了解决这些问题,Vue 3 采用了更加高效的响应式系统
vue2的缺陷,性能问题。如果data里的层次很深的话,进行多层次的监听开销是很大的
Vue 2 在进行响应式处理时,会递归遍历数据对象中的每一个属性,并将这些属性转化为 getter 和 setter。当数据层次比较深时,这种递归遍历的开销就会非常大,会导致页面渲染性能下降。
除此之外,Vue 2 还存在以下性能问题:
- 每个组件都会创建自己的观察者 Watcher 实例,当组件数量较多时,会导致大量的内存开销和性能问题;
- 每次数据变化都会导致重新渲染整个组件,即使数据变化的影响仅限于某个子组件;
- 在大型列表渲染时,使用 v-for 进行循环渲染会产生大量的 DOM 操作,影响渲染性能
vue的模版解析过程
在 Vue 中,模板是由 HTML 代码和 Vue 特定的模板语法组成的。Vue 的模板编译器会将模板编译成渲染函数,然后再生成 Virtual DOM,最终进行渲染。
下面是 Vue 的模板解析过程:
- 解析模板,生成 AST 抽象语法树:Vue 的编译器会将模板转换为 AST 抽象语法树,这是一个树形结构,代表了模板的结构,包括元素节点、文本节点、指令等。
- 优化 AST:在生成 AST 之后,Vue 的编译器会对其进行优化,例如静态节点提取、静态根节点提取等优化操作,以提高渲染性能。
- 生成代码:最后,Vue 的编译器会将 AST 转换为渲染函数的代码。这个渲染函数是一个 JavaScript 函数,接收一个参数 h,返回一个 Virtual DOM 节点。
- 生成 Virtual DOM:渲染函数生成后,Vue 会使用它来生成 Virtual DOM,然后对比新旧 Virtual DOM,计算出需要更新的部分,最终只更新需要更新的部分。
- 渲染:最后,Vue 将更新后的 Virtual DOM 渲染到真实的 DOM 中。
这个过程是 Vue 的模板编译过程的核心,也是 Vue 能够高效渲染页面的重要原因
反问
- 你对我的面试表现怎么样
挺好的 基础问题基本上都回答的出来,前端的广度还不够 挺好的
- 还会有二面吗?
这个我们得和后面的小组商量一下
- 同程旅行的技术栈是什么?
苏州这边是vue2为主,做一个低代码平台的组件化