1.说说你对盒子模型的理解
盒子模型指的是CSS中用来描述和布局网页元素的一种概念。它将每个网页元素看作一个盒子,这个盒子由内容、内边距、边框和外边距组成。
具体来说,盒子模型包括以下几个部分:
-
内容(Content):指的是盒子内的实际内容,比如文本、图像等。
-
内边距(Padding):指的是内容与边框之间的空间,它可以用来增加盒子内部的空隙。
-
边框(Border):指的是盒子的边界线,它可以设定边框的样式、宽度和颜色。
-
外边距(Margin):指的是盒子与其他盒子之间的空隙,它可以用来控制盒子之间的距离。
通过调整盒子模型的这些属性,可以实现对网页元素的布局和样式的精确控制。在CSS中,可以使用box-sizing属性来控制盒子模型的计算方式,有两个选项:content-box和border-box。其中,content-box是默认值,表示计算盒子宽度和高度时只包括内容,而border-box表示计算盒子宽度和高度时包括内容、内边距和边框。
总体而言,盒子模型是CSS布局中非常重要的概念,它为开发者提供了灵活和可控的网页布局机制。
2.css选择器有哪些?优先级?哪些属性可以继承?
CSS选择器有多种类型,以下是一些常见的选择器:
-
元素选择器(Element Selector):通过元素名称选择元素,例如
p
选择所有<p>
元素。 -
类选择器(Class Selector):通过类名选择元素,例如
.my-class
选择所有具有my-class
类的元素。 -
ID选择器(ID Selector):通过元素的唯一ID选择元素,例如
#my-id
选择具有my-id
ID的元素。 -
伪类选择器(Pseudo-class Selector):根据元素的特定状态,如
:hover
、:focus
等选择元素。 -
属性选择器(Attribute Selector):根据元素的属性值选择元素,例如
[type="text"]
选择所有type
属性值为text
的元素。 -
后代选择器(Descendant Selector):选择某个元素的后代元素,用空格表示,例如
div p
选择所有<p>
元素,而这些元素是<div>
的后代元素。 -
直接子元素选择器(Child Selector):选择某个元素的直接子元素,使用
>
表示,例如ul > li
选择所有<ul>
元素的直接子元素<li>
。
CSS选择器优先级的顺序是:
-
内联样式(Inline style):使用
style
属性直接写在元素标签上,优先级最高。 -
ID选择器(ID selector):根据ID选择元素。
-
类选择器、属性选择器和伪类选择器(Class selector, Attribute selector, Pseudo-class selector):根据类、属性或伪类选择元素。
-
元素选择器和伪元素选择器(Element selector, Pseudo-element selector):根据元素类型或伪元素选择元素。
注意,如果具有相同优先级的选择器同时应用于同一个元素,那么后面出现的样式规则会覆盖之前的样式规则。
关于属性继承性,不是所有的属性都具有继承性。一般来说,文字相关的属性(如 font-family
、font-size
、color
等)、部分盒子模型的属性(如 padding
、margin
等)以及部分布局属性(如 text-align
)具有继承性。但是,背景相关的属性(如 background
、border
)、定位属性(如 position
、top
、left
等)以及大部分的显示和动画属性等,是不具有继承性的。
3.元素水平垂直居中的方法有哪些?如果元素不定宽高呢?
有多种方法可以将元素水平垂直居中,以下是一些常用的方法:
- 使用 flexbox 布局:将父容器的 display 属性设置为 flex,然后使用 align-items 和 justify-content 属性来实现垂直和水平居中。
.parent {
display: flex;
align-items: center; /* 垂直居中 */
justify-content: center; /* 水平居中 */
}
- 使用绝对定位和 transform 属性:将元素的 position 属性设置为 absolute,然后使用 top、left、bottom 和 right 属性为元素定位,并使用 transform 属性来将元素平移至水平垂直中心。
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* 平移至水平垂直中心 */
}
- 使用表格布局:将父容器的 display 属性设置为 table,将子元素的 display 属性设置为 table-cell,并将 vertical-align 属性设为 middle。
.parent {
display: table;
}
.child {
display: table-cell;
vertical-align: middle; /* 垂直居中 */
text-align: center; /* 水平居中 */
}
如果元素没有固定的宽度和高度,可以尝试以下方法:
- 使用 flexbox 布局的 align-items 和 justify-content 的值都设置为 center,这样元素会在父容器中水平垂直居中。
.parent {
display: flex;
align-items: center;
justify-content: center;
}
- 使用绝对定位和 transform,将 left 和 top 的值都设置为 50%,然后使用 translateX 和 translateY 来平移元素。
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
这些方法可以在不同的场景下使用,选择适合你的情况的方法进行元素的水平垂直居中。
4.怎么理解回流跟重绘?什么场景下会触发?
回流(reflow)和重绘(repaint)是浏览器渲染页面时的两个重要过程。
-
回流指的是当页面布局和几何属性发生变化时,浏览器需要重新计算元素的位置和大小,然后将这些元素重新渲染到页面上。回流会触发一系列计算,通常会导致页面重新布局,是一项较为昂贵的操作。
重绘指的是当元素的外观属性发生变化时,浏览器会更新元素的样式并重新绘制到页面上,而不会影响到布局。重绘比回流开销较小,因为它只涉及更新某些元素的外观而不需要重新计算其布局。
以下是一些可能触发回流和重绘的场景:
- 修改元素的位置、尺寸、边距等布局属性。
- 添加或删除 DOM 元素。
- 修改元素的字符内容、文本样式等。
- 改变浏览器窗口大小。
- 激活 CSS 动画或过渡效果。
- 改变浏览器的默认样式(如修改字体大小)。
注意,上述场景只是一些常见情况,实际情况可能因浏览器和具体操作而有所不同。
为了提高页面性能,应尽量减少回流和重绘的次数。可以采取一些优化策略,例如:
- 使用 CSS3 的 transform 属性替代偏移属性(如 top、left),因为 transform 不会触发回流。
- 使用 CSS3 的动画(animation)代替用 JavaScript 实现的动画。
- 避免频繁访问会引发回流或重绘的属性(如 offsetTop、offsetLeft、clientWidth 等),最好将它们缓存在变量中。
- 将需要多次修改的 DOM 操作合并为一次操作,或使用 DocumentFragment 进行离线操作,然后再一次性插入到文档中。
- 使用 CSS3 的 will-change 属性来预告元素可能发生的变化,帮助浏览器进行优化。
- 尽量使用相对定位和绝对定位来触发 GPU 加速。
通过合理的优化和减少回流和重绘的次数,可以提升页面的性能和用户体验。
5.什么是响应式设计?响应式设计的基本原理是什么?如何做?
响应式设计(Responsive Design)是一种网页设计方法,旨在提供适应不同设备和屏幕尺寸的最佳用户体验。它基于以下原则和技术来实现自动调整布局和内容。
-
弹性布局:使用相对单位(如百分比)和弹性容器(如 Flexbox 或 Grid)来创建灵活、自适应的布局。这使得网页能够根据屏幕尺寸进行伸缩和调整。
-
媒体查询(Media Queries):使用 CSS3 的媒体查询技术,根据不同的设备尺寸和特性,应用不同的样式规则。媒体查询可以根据屏幕宽度、高度、设备方向、分辨率等条件来选择不同的样式。
-
图片和媒体处理:使用响应式图片技术,根据不同的设备尺寸和像素密度加载适应的图片。这可以在保持图像清晰度的同时,减少页面加载时间和带宽消耗。
-
可扩展的模块化设计:采用模块化设计原则,将页面分解为多个独立的模块,使其能够在不同设备上进行重新排列和重组。每个模块都应适应不同的屏幕尺寸,并能够以可视觉上的一致性方式进行展示。
要实现响应式设计,可以按照以下步骤进行:
-
设计阶段:在设计过程中,考虑不同设备的布局和交互需求。确定关键内容和功能,并确定哪些元素需要在不同屏幕尺寸下显示或隐藏。
-
弹性布局:使用相对单位和弹性容器来创建适应不同屏幕尺寸的布局。确保页面元素能够自动伸缩和重新排列。
-
媒体查询:在 CSS 中使用媒体查询,根据不同的屏幕尺寸和设备特性,应用不同的样式规则。通过各种媒体查询条件,调整布局、字体大小、图片大小等样式。
-
图片和媒体处理:使用响应式图片技术,根据设备尺寸和像素密度加载适应的图片。使用图片压缩和延迟加载等技术来提高加载性能和用户体验。
-
测试与优化:确保在各种设备和屏幕尺寸下,网页能够正确响应并提供良好的用户体验。进行跨设备和跨浏览器的测试,根据反馈和分析结果,对布局和样式进行优化和调整。
响应式设计可以提供一个统一的网页版本,能够适应不同的设备,并提供一致的内容和体验。
6.如果要做优化,CSS提高性能的方法有哪些?
CSS 提高性能的方法主要涉及以下几个方面:
-
精简和优化 CSS 代码:减少不必要的代码和样式规则,删除冗余的选择器和属性。使用压缩工具来减少文件大小,以提高加载速度。
-
减少嵌套和层级:尽量避免过多的嵌套和选择器层级,因为浏览器匹配选择器的时候需要进行更多的计算。优先使用简洁的选择器,并尽量避免使用通配符和属性选择器,以提高匹配速度。
-
避免使用昂贵的属性和选择器:一些 CSS 属性和功能比较消耗资源,如渐变、阴影、大量的动画效果等。在使用它们时要注意性能开销,并合理选择使用或避免使用。同时,尽量避免使用复杂的选择器,如后代选择器和通用选择器等。
-
使用合适的选择器:根据具体的选择器规则和页面结构,选择最为高效的选择器。使用类名选择器(class)或 ID 选择器(id)进行样式定义,而避免使用标签选择器(element)或后代选择器。
-
避免过多的重绘和回流:减少样式修改的次数和范围,尽量一次性进行批量修改或使用 CSS3 动画代替 JavaScript 动画。避免频繁读取会引发回流的属性(如 offsetTop、offsetLeft 等),优化访问方式并进行缓存。
-
使用 CSS 预处理器:使用像 Sass、Less 或 Stylus 这样的 CSS 预处理器,可以帮助更好地组织和管理 CSS 代码。预处理器提供了变量、混合(mixin)、继承等功能,可以减少重复代码,提高开发效率和代码性能。
-
延迟加载 CSS:将不必要的 CSS 代码延迟加载,只在需要时加载,可以减少页面的起始加载时间。在需要使用的地方,通过动态添加样式表或媒体查询来加载所需的 CSS。
-
使用浏览器缓存:通过设置适当的缓存控制响应头,让 CSS 文件能够在客户端缓存起来,减少后续请求。
这些方法可以帮助优化 CSS 性能,提高页面加载速度和渲染性能,同时减少浏览器的计算和渲染开销。根据具体的项目需求,结合实际情况选择和应用适合的优化方法。
*7.8*
1:对前端工程师这个职位是怎么样理解的?它的前景会怎么样
作为一个前端工程师,主要负责开发网站和Web应用程序的用户界面。这包括设计和实现网页的外观、布局和交互功能,以确保用户获得良好的用户体验。前端工程师需要掌握HTML、CSS和JavaScript等技术,了解各种前端框架和库,以及与设计团队和后端开发人员合作。
关于前端工程师的前景,可以说非常乐观。随着互联网的不断发展,越来越多的企业和组织需要开发吸引人的网站和应用程序来吸引用户并提供良好的用户体验。前端工程师在这个过程中起着至关重要的作用。随着移动互联网的普及和新技术的不断涌现,如移动应用、响应式设计、人工智能等,前端工程师的需求也不断增加。
此外,前端工程师的角色也在不断扩展。现在的前端工程师不仅仅需要关注网站的外观和交互,还需要考虑性能优化、跨平台兼容性、安全性等方面。他们还可能涉及到用户研究和用户测试,以及与其他团队成员如设计师和后端工程师进行沟通和协作。
总的来说,前端工程师这个职位的前景非常广阔。随着技术的不断进步和互联网的持续发展,前端工程师将继续扮演着关键的角色,并且有机会参与到各种创新和有趣的项目中。
2:说说JavaScript中的数据类型?存储上的差别?
JavaScript中有以下几种数据类型:
- 基本数据类型(原始数据类型):
- String(字符串): 存储文本数据,使用单引号或双引号包围。
- Number(数字): 存储数值数据,包括整数和浮点数。
- Boolean(布尔): 存储逻辑值,即true或false。
- null(空): 表示一个空值对象。
- undefined(未定义): 表示一个未定义的值。
- Symbol(符号)(ES6+): 表示唯一的、不可变的值。
- 引用数据类型:
- Object(对象): 可以用来存储复杂的数据结构,包括键值对形式的属性和方法。
在存储上,JavaScript的基本数据类型是通过将值直接存储在变量中的方式进行存储的,而引用数据类型则是存储在内存中,并通过引用(指针)来访问其真正的值。具体来说:
-
基本数据类型的值是直接存储在栈内存中的,当我们创建一个变量并将一个基本数据类型的值赋给它时,变量会直接存储这个值。
-
引用数据类型的值则是存储在堆内存中的,当我们创建一个变量并将一个对象或数组赋给它时,变量中存储的是对堆内存中实际数据的引用。这意味着多个变量可以引用同一个对象,修改其中一个变量的值将影响其他变量。
需要注意的是,虽然字符串、数字和布尔类型本身是基本数据类型,但它们在被使用时会被自动封装成对应的对象类型(String、Number和Boolean),这样就可以使用这些对象类型的一些方法和属性。
总结一下,JavaScript中的数据类型包括基本数据类型和引用数据类型。基本数据类型的值直接存储在变量中,而引用数据类型的值存储在堆内存中,并通过引用访问。了解这些数据类型以及它们在存储上的差异,有助于更好地理解和使用JavaScript语言。
3:typeof 与 instanceof 区别
typeof和instanceof是JavaScript中用于判断数据类型的两个操作符,它们的区别如下:
- typeof操作符:
typeof是一个一元操作符,用于判断一个值的数据类型。它返回一个表示数据类型的字符串。
- 对于基本数据类型,typeof可以正确地判断出字符串、数字、布尔、null和undefined的类型。
- 对于对象类型,typeof无法区分具体的对象类型(除null之外),它会将对象、数组、函数等都识别为"object"。
- 对于函数,typeof会返回"function"。
示例:
typeof “Hello” // “string”
typeof 42 // “number”
typeof true // “boolean”
typeof null // “object”
typeof undefined // “undefined”
typeof {} // “object”
typeof [] // “object”
typeof function(){} // “function”
- instanceof操作符:
instanceof用于判断一个对象是否属于某个特定的类或构造函数的实例。
- instanceof运算符要求目标对象必须是引用类型,即对象、数组或函数等。
- 它会检查目标对象的原型链(prototype chain),判断是否存在于其原型链上的特定类或构造函数。
- 如果目标对象是特定类的实例,那么instanceof返回true,否则返回false。
示例:
var arr = [];
arr instanceof Array // true
function Person(){}
var person = new Person();
person instanceof Person // true
需要注意的是,instanceof运算符判断的是对象的原型链,而不是实际的数据类型。另外,使用instanceof对于基本数据类型(如字符串、数字和布尔)是无效的。
总结一下,typeof用于判断一个值的数据类型,返回对应的字符串;而instanceof用于判断一个对象是否属于某个特定类或构造函数的实例,返回布尔值。它们在判断数据类型的角度和用法上有所区别。
4:说说你对闭包的理解?闭包使用场景
闭包是指在函数内部创建并返回另一个函数的结构。这个返回的函数可以访问其定义时所在的父函数的作用域中的变量、参数和内部函数,即使父函数已经执行完毕,返回的函数仍然可以访问这些变量和函数。
闭包的概念可以理解为函数和其相关的引用环境的组合。当一个函数内部定义了另一个函数,并将内部函数作为返回值,同时内部函数引用了外部函数的局部变量,就形成了一个闭包。
闭包的特点和作用包括:
- 记忆状态:由于闭包可以访问其定义时的作用域,它可以记住状态,即使父函数已经执行完毕。这样可以在外部函数执行完毕后,仍然能够访问和修改父函数的局部变量。
- 封装数据和行为:通过闭包可以实现数据的私有化和封装,只暴露特定的接口函数,隐藏其他的变量和函数。
- 实现函数工厂:闭包可以用来动态生成函数,返回不同的函数实例,每个实例都保留了共享的父函数作用域。
- 回调函数:闭包常用于回调函数,当函数内部有异步操作需要处理时,可以通过闭包来保存异步操作所需的上下文信息。
- 模块化开发:通过闭包可以实现模块化开发,将相关的变量和函数封装在一个闭包中,外部无法直接访问,只通过暴露的接口函数来调用。
闭包的使用场景包括:
- 私有变量和方法的实现,隐藏内部实现细节,仅暴露有限的接口。
- 保存函数的上下文信息,实现异步操作的回调函数。
- 创建函数工厂,动态生成函数。
- 模块化开发,将相关的代码组织在一个独立的作用域中。
需要注意的是,闭包会占用内存资源,如果没有正确使用或及时释放,可能会导致内存泄漏。因此,在使用闭包时,应当注意合理管理内存。
5:bind、call、apply 区别?如何实现一个bind?使用场景分别是?
三者都可以改变函数的this对象指向
三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window
三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入
bind是返回绑定this之后的函数,apply、call 则是立即执行
const bindFn = fn.bind(obj);
bindFn(1,2)
bind、call和apply都是JavaScript中用于改变函数执行上下文的方法,它们的区别如下:
- bind方法:
- bind方法会创建一个新的函数,并将原始函数的执行上下文绑定到指定的对象。
- bind方法返回一个新的函数,不会立即执行,需要手动调用。
- 可以传递参数给原始函数,同时也可以在调用时传递额外的参数。
示例:
const obj = {
name: 'Alice',
sayHello: function() {
console.log(`Hello, ${this.name}`);
}
};
const boundFunc = obj.sayHello.bind(obj);
boundFunc(); // 输出:Hello, Alice
- call方法:
- call方法用于调用一个函数,并指定函数执行时的上下文对象。
- 除了传递上下文对象,call方法还可以传递额外的参数给原始函数。
- 调用call方法会立即执行原始函数。
示例:
const obj = {
name: 'Alice',
sayHello: function() {
console.log(`Hello, ${this.name}`);
}
};
obj.sayHello.call(obj); // 输出:Hello, Alice
- apply方法:
- apply方法和call方法类似,也是用于调用一个函数,并指定函数执行时的上下文对象。
- 不同之处在于,apply方法接受一个数组作为参数,其中数组的每个元素会作为原始函数的参数。
- 调用apply方法会立即执行原始函数。
示例:
const obj = {
name: 'Alice',
sayHello: function() {
console.log(`Hello, ${this.name}`);
}
};
obj.sayHello.apply(obj); // 输出:Hello, Alice
下面是一个简单的实现bind方法的例子:
Function.prototype.myBind = function(context, ...args) {
const fn = this;
return function(...innerArgs) {
return fn.apply(context, [...args, ...innerArgs]);
};
};
// 使用自定义的myBind方法
const obj = {
name: 'Alice',
sayHello: function() {
console.log(`Hello, ${this.name}`);
}
};
const boundFunc = obj.sayHello.myBind(obj);
boundFunc(); // 输出:Hello, Alice
以上代码创建了Function对象的原型方法myBind,并利用闭包返回了一个新的函数,该新函数在调用时会应用传入的上下文对象,同时传递原始函数所需的参数,并调用原始函数。这样就实现了一个简单的bind方法。不过需要注意的是,该实现并没有考虑到诸如原始函数是否可以作为构造函数等特殊情况的处理,真实的bind方法会更加复杂和全面。
6:说说你对事件循环的理解
事件循环是JavaScript中的一种执行模型,用于处理异步任务和事件(例如用户交互、网络请求、定时器等)。它是为了解决JavaScript单线程的限制而设计的。
在JavaScript中,所有的任务都被添加到一个任务队列(也称为事件队列)中,事件循环负责监视并执行这些任务。事件循环由一个主线程负责,它不断地从任务队列中取出任务,并按照特定的顺序执行。
事件循环的主要机制如下:
-
执行同步任务:
- JavaScript代码在执行过程中,遇到的同步任务(阻塞任务)会依次执行,并阻塞后续代码的执行。
-
处理异步任务:
- 当遇到异步任务(非阻塞任务)时,JavaScript会将其添加到任务队列中,而不会等待其完成。
- 异步任务完成后,会被推入任务队列的相应位置,等待被执行。
-
事件循环的工作过程:
- 当主线程完成同步任务后,它会检查任务队列是否有任务。
- 若任务队列为空,则等待新的任务加入。
- 若任务队列不为空,则主线程取出任务并执行。
- 执行任务期间,可能会触发新的异步任务,它们会被添加到任务队列中。
- 重复以上步骤,不断地从任务队列中取出任务并执行。
通过事件循环,JavaScript可以处理异步操作,使得在等待异步任务完成时,仍然能够执行其他任务,避免阻塞主线程。
需要注意的是,事件循环中的任务分为宏任务(macro task)和微任务(micro task)两类。宏任务包括整体代码块、setTimeout、setInterval等,而微任务包括Promise、MutationObserver等。在每次事件循环中,首先会先执行所有的微任务,然后再执行一个宏任务。
总结一下,事件循环是JavaScript中一种处理异步任务和事件的机制,通过任务队列的方式,按照特定的顺序执行任务。它通过不断地从任务队列中取出任务并执行,实现了异步任务的处理,使得JavaScript能够高效地处理各种异步操作。
事件循环(Event Loop)是浏览器或Node.js中管理执行任务(event task)和回调函数(callback)的一种机制。在JavaScript中,所有的命令都被添加到一个执行的队列(执行栈)中,而事件循环会不停地从队列中取出命令并执行,直到队列为空,事件循环的主要流程:首先执行就绪队列中的任务,根据任务的优先级将任务执行完毕后,会执行执行宏任务队列中的任务,等执行完后,再执行为任务队列中的任务,如果在此过程中产生了宏任务,就会先执行该宏任务载执行剩余的微任务,最后就是渲染和更新事件循环的重要性在于,它直接影响我们开发程序时的异步实现方式。比如setTimeout、setInterval、DOM事件、Promise等,都涉及到事件循环的概念
(扩展)浏览器的事件循环
浏览器的事件循环是一种机制,用于管理和处理浏览器中发生的事件和任务。它确保 JavaScript 代码按照一定的顺序和优先级执行,并且可以在需要时响应用户输入和其他异步操作。
下面是浏览器事件循环的基本工作原理:
-
执行同步任务: 首先,浏览器会执行当前执行环境(全局环境或函数环境)中的同步任务,这些任务按照它们在代码中的出现顺序执行。
-
处理微任务队列: 在执行完同步任务后,浏览器会检查正在运行的任务上下文中的微任务队列。微任务在每次任务完成后执行,并且在下一个任务执行之前执行。
- Promise回调函数会被添加到微任务队列中。
- MutationObserver 回调函数也会被添加到微任务队列中。
微任务会连续执行直到微任务队列为空。
-
更新渲染: 在执行完微任务后,浏览器会检查是否需要进行重新渲染。如果有需要更新的 DOM 或样式变化,浏览器将进行重绘和重排以反映这些变化。
-
处理宏任务队列: 在更新渲染后,浏览器会检查它的宏任务队列,宏任务是一些异步任务,如定时器回调、用户交互事件回调等。
- 定时器回调函数会被添加到宏任务队列中。
- 用户交互事件回调函数也会被添加到宏任务队列中。
- 网络请求、文件读写等异步操作的回调函数也会被添加到宏任务队列中。
宏任务按照它们的优先级依次执行,每次取出一个任务执行。当执行宏任务时,如果有新的微任务产生,会保存到微任务队列,并在下一个任务执行前执行这些微任务。
-
重复步骤: 重复执行上述步骤,直到没有更多的任务需要执行。
这个事件循环的过程会不断重复,直到浏览器关闭或页面跳转。事件循环的机制确保了 JavaScript 的单线程执行和异步操作的顺序性,以及能够及时响应用户的交互操作。
(扩展)node.js的事件循环
Node.js 的事件循环与浏览器的事件循环有一些不同之处,因为 Node.js 是基于单线程的非阻塞 I/O 模型。
以下是 Node.js 的事件循环的基本工作原理:
-
执行同步任务: 首先,Node.js 会执行当前的同步任务,这些任务按照它们在代码中的出现顺序执行。
-
处理微任务队列: 在执行完同步任务后,Node.js 会检查正在运行的任务上下文中的微任务队列。与浏览器的事件循环不同,Node.js 中的微任务队列仅包含
process.nextTick
的回调函数。微任务会连续执行直到微任务队列为空。
-
触发 I/O 操作或计时器: 如果没有其他任务需要执行,Node.js 将等待触发 I/O 操作或计时器。
- 当有 I/O 操作(如文件读写、网络请求等)完成时,将触发相应的回调函数。这些回调函数会被放入宏任务队列中准备执行。
- 如果有计时器超时,将触发相应的定时器回调函数。这些回调函数也会被放入宏任务队列中准备执行。
-
处理宏任务队列: 在触发了 I/O 操作或计时器后,Node.js 会检查它的宏任务队列中是否有任务需要执行。
宏任务按照它们的优先级依次执行,每次取出一个任务执行。当执行宏任务时,如果有新的微任务产生(即
process.nextTick
的回调),会保存到微任务队列,并在下一个任务执行前执行这些微任务。 -
重复步骤: 重复执行上述步骤,直到没有更多的任务需要执行。
Node.js 的事件循环采用了事件驱动和回调机制,使得 I/O 操作可以异步处理,而不会阻塞其他任务的执行。这样可以高效地处理大量的并发请求,提高系统的性能和响应能力。
*7.10*
1:DOM常见的操作有哪些
DOM(文档对象模型)是用于访问和操作HTML文档中元素的编程接口。以下是DOM常见的操作:
-
获取元素:
- getElementById(id):根据元素的id属性获取元素对象。
- getElementsByClassName(className):根据元素的类名获取一组元素对象。
- getElementsByTagName(tagName):根据元素的标签名获取一组元素对象。
- querySelector(selector):根据CSS选择器获取满足条件的第一个元素对象。
- querySelectorAll(selector):根据CSS选择器获取所有满足条件的元素对象。
-
修改元素属性:
- element.innerHTML:获取或设置元素的HTML内容。
- element.textContent:获取或设置元素的文本内容。
- element.setAttribute(name, value):设置元素的指定属性名和属性值。
- element.getAttribute(name):获取元素的指定属性值。
-
修改样式:
- element.style.property = value:修改元素的样式属性。
- element.className:获取或设置元素的类名。
-
创建和插入元素:
- document.createElement(tagName):创建一个指定标签名的元素对象。
- parentElement.appendChild(element):将一个元素插入到指定父元素中。
-
删除元素:
- parentElement.removeChild(element):从指定父元素中移除指定子元素。
-
事件操作:
- element.addEventListener(event, function):为元素添加事件监听器。
- element.removeEventListener(event, function):移除元素的事件监听器。
这只是DOM中常见的一些操作,实际上DOM提供了更多功能和方法来操作HTML文档中的元素。
2:说说你对BOM的理解,常见的BOM对象你了解哪些
BOM(浏览器对象模型)是用于访问和操作浏览器窗口的编程接口。它提供了许多对象和方法来操作浏览器窗口、历史记录、定时器、浏览器位置等信息。以下是我对BOM的理解以及常见的BOM对象:
-
Window对象:
- 表示浏览器窗口,是BOM的核心对象。
- 提供了浏览器窗口的各种属性和方法,如location、document、console等。
- 通过window对象可以访问和操作浏览器窗口的各个部分。
-
Navigator对象:
- 提供了有关浏览器的信息,如浏览器的名称、版本、语言等。
- 可以使用navigator对象判断浏览器的类型、支持的功能等。
-
History对象:
- 提供了对浏览器历史记录的访问和操作。
- 可以使用history对象向前或向后导航、获取当前URL等。
-
Location对象:
- 提供了对浏览器当前URL的访问和操作。
- 可以使用location对象获取或设置URL的各个部分,如协议、主机、路径等。
-
Screen对象:
- 提供了有关用户屏幕的信息,如屏幕的宽度、高度、像素密度等。
- 可以使用screen对象调整页面布局或根据屏幕大小进行适配。
-
Timer对象:
- 提供了定时器功能,用于执行定时任务。
- 可以使用timer对象设置定时器、取消定时器,并在指定的时间间隔内执行相应的操作。
以上是BOM中常见的一些对象,它们可以帮助开发者对浏览器窗口进行控制和操作,提供了丰富的功能和方法,以实现更多的用户交互和浏览器操作。
3:Javascript本地存储的方式有哪些?区别及应用场景
JavaScript提供了几种本地存储方式,用于在浏览器端将数据保存在用户的本地设备上。以下是常见的本地存储方式及其区别和应用场景:
-
Cookies(HTTP Cookies):
- Cookies是一小段由服务器发送到浏览器并保存在用户设备上的数据。
- Cookies存储在浏览器的Cookie文件中,每次发送请求时都会被自动添加到请求头中。
- Cookies大小有限制(通常为4KB),浏览器对Cookies的数量和大小也有限制。
- Cookies可设置过期时间,可以是会话级别的(浏览器关闭后失效)或具有持久性(按照设置的过期时间失效)。
- 应用场景:存储少量的用户信息、例如用户登录状态、个性化设置等。
-
Web Storage(本地存储):
- Web Storage提供了两个对象:sessionStorage和localStorage。
- sessionStorage:保存在会话级别的临时存储区域,只在当前会话期间(浏览器标签页或窗口)有效。关闭标签页或窗口时会自动清除。
- localStorage:具有持久性的本地存储区域,数据在用户设备上长期保存,除非被显式删除。
- Web Storage的总容量通常为5MB左右。
- 应用场景:适用于需要在不同页面间临时共享数据(sessionStorage)和持久保存数据(localStorage)的场景,如表单数据、页面状态等。
-
IndexedDB(索引数据库):
- IndexedDB是一个完整的客户端数据库系统,允许在浏览器中存储和操作大量结构化数据。
- IndexedDB是一个事务型数据库,支持复杂的数据查询和索引功能。
- IndexedDB的存储容量较大,通常为几十MB到几百MB。
- 应用场景:适用于需要大容量存储和高级查询功能的应用,如离线应用、大型应用的本地缓存等。
-
WebSQL(已废弃):
- WebSQL是一种基于SQL的数据库系统,允许使用SQL语句来操作数据。
- WebSQL的存储容量较小,通常为5MB。
- 目前,WebSQL已被废弃,不再建议在新项目中使用。推荐使用IndexedDB作为替代方案。
选择使用哪种本地存储方式取决于应用的具体需求。Cookies适用于保存少量的简单数据,Web Storage适用于临时共享和持久保存数据,IndexedDB适用于大容量和高级数据操作,而WebSQL已经不推荐使用。开发者需要根据应用场景的需求,综合考虑存储容量、数据类型、数据的生命周期以及浏览器兼容性等因素来选择适当的本地存储方式。
4:什么是防抖和节流?有什么区别?如何实现
防抖和节流是两种常用的前端性能优化技术,用于限制事件触发的频率,提升页面的响应速度和性能。
-
防抖(Debounce):
- 防抖是指在事件被触发后,等待一定的时间间隔,如果在这个时间间隔内再次触发同一事件,则重新计时。
- 如果在设定的时间间隔内再次触发事件,那么重新计时,不执行事件处理函数。
- 常用于解决高频触发事件(如浏览器窗口的resize、滚动等)而引起的性能问题。
- 实现方式:使用定时器和 clearTimeout() 方法,通过设置定时器延迟执行函数,并通过 clearTimeout() 方法清除之前的定时器。
-
节流(Throttle):
- 节流是指在一定的时间间隔内,只执行一次事件处理函数。
- 如果在设定的时间间隔内多次触发同一事件,只有第一次触发会执行事件处理函数,后续触发将被忽略。
- 常用于限制事件触发的频率和短时间内大量触发事件的场景(如滚动加载、按钮点击等)。
- 实现方式:使用时间戳和定时器,通过设置时间戳记录最后一次执行的时间,并在定时器中检查是否允许执行函数。
区别:
- 防抖和节流都可以限制事件的触发频率,但处理方式不同。
- 防抖在事件触发后只会执行一次,可以降低事件处理函数执行的次数。
- 节流在一定时间内只会执行一次,比如每隔100ms执行一次,无论触发频率如何,都会保证每隔设定的时间执行一次。
实现防抖函数的例子:
function debounce(fn, delay) {
let timeoutId;
return function() {
clearTimeout(timeoutId);
timeoutId = setTimeout(fn, delay);
};
}
实现节流函数的例子:
function throttle(fn, delay) {
let lastTime = 0;
return function() {
const now = Date.now();
if (now - lastTime >= delay) {
fn();
lastTime = now;
}
};
}
以上是简单的防抖和节流函数示例,实际应用中可以根据具体的需求进行调整和优化。
5:如何通过JS判断一个数组
通过 JavaScript 可以使用多种方式来判断一个值是否为数组。以下是一些常用的方法:
-
使用 Array.isArray() 方法:
- Array.isArray(arr) 方法返回一个布尔值,判断给定的参数是否为一个数组。
- 例如:Array.isArray([1, 2, 3]) 返回 true。
-
使用 instanceof 运算符:
- 使用 instanceof 运算符可以判断一个对象是否属于某个构造函数的实例。
- 例如:[1, 2, 3] instanceof Array 返回 true。
-
使用 Object.prototype.toString() 方法:
- Object.prototype.toString.call(arr) 返回一个表示对象类型的字符串。
- 对于数组而言,返回的字符串形式为 “[object Array]”。
- 例如:Object.prototype.toString.call([1, 2, 3]) 返回 “[object Array]”
下面是示例代码,演示如何使用这些方法来判断一个值是否为数组:
const arr = [1, 2, 3];
// 使用 Array.isArray()
console.log(Array.isArray(arr)); // true
// 使用 instanceof
console.log(arr instanceof Array); // true
// 使用 Object.prototype.toString()
console.log(Object.prototype.toString.call(arr) === '[object Array]'); // true
以上三种方法都可以用来判断一个值是否为数组,根据具体的需求和编程习惯选用即可。
6:说说你对作用域链的理解
作用域链(Scope Chain)是 JavaScript 中用于查找变量和函数的一种机制。它是由多层嵌套的作用域环境构成的链式结构。
当在 JavaScript 中访问一个变量时,解释器会根据变量所处的作用域链来查找该变量的值。作用域链的构成如下:
- 在函数定义时,每个函数都会创建一个新的作用域(函数作用域)。
- 当函数执行时,会创建一个新的执行上下文,并将其添加到作用域链的顶部。
- 每个执行上下文都有自己的词法环境和变量对象,它们构成了作用域链的一部分。
- 当在函数中查找变量时,首先会在当前作用域的变量对象中查找,如果找不到,则沿着作用域链向上查找,直到最外层的全局作用域。
- 如果在全局作用域中仍然找不到变量,则返回 undefined。
作用域链的特点和作用:
- 内部函数可以访问外部函数的变量,由于作用域链的存在,内部函数可以沿着作用域链向上查找变量,这也是闭包的机制之一。
- 作用域链的维护是在函数定义时就确定的,和函数的执行无关。
- 当函数执行完毕后,其对应的执行上下文会被销毁,作用域链也随之销毁。
需要注意的是,在ES6引入块级作用域之前,JavaScript只有全局作用域和函数作用域,没有块级作用域。块级作用域中的变量在作用域链中不可访问,只能在块级作用域内部有效。
总结起来,作用域链是 JavaScript 中用于查找变量和函数的机制,通过嵌套的作用域环境构成链式结构,允许内部函数访问外部函数的变量,是实现闭包的基础。
*7.11*
1.JavaScript原型,原型链 ? 有什么特点?
每个对象都会在其内部初始化一个属性,就是 proto ,当我们访问一个对象的属性时如果这个对象内部不存在这个属性,那么他就会去_proto 里找这个属性,这个 proto 又会有自己的proto ,于是就这样一直找下去,也就是我们平时所说的原型链的概念。按照标准,proto 是不对外公开的,也就是说是个私有属性
关系: instance.constructor.prototype == instance. proto
var a = f
a.constructor.prototype == a. proto
特点:
Javascript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变当我们需要一个属性的时,Javascript引擎会先看当前对象中是否有这个属性,如果没有的就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到 object 内建对象
原型:
Javascript的所有对象中都包含了一个[proto ] 内部属性,这个属性所对应的就是该对象的原型
JavaScript的函数对象,除了原型 [ proto ]之外,还预置了 prototype 属性
当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型[ proto ]。
_原型链:当一个对象调用的属性/方法自身不存在时,就会去自己[_proto ]关联的前辈prototype 对象上去找如果没找到,就会去该 prototype 原型[_proto ]关联的前 prototype 去找。依次类推,直到找到属性方法或 undefined 为止。从而形成了所谓的“原型链
原型特点:
JavaScript对象是通过引用来传递的,当修改原型时,与之相关的对象也会继承这一改变
2.请解释什么是事件代理
事件代理,俗地来讲,就是把一个元素响应事件(click
、keydown
…)的函数委托到另一个元素
前面讲到,事件流的都会经过三个阶段: 捕获阶段 -> 目标阶段 -> 冒泡阶段,而事件委托就是在冒泡阶段完成
事件委托,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,而不是目标元素
当事件响应到目标元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数
3.谈谈This对象的理解
this总是返回一个对象,this就是属性或方法“当前”所在对象。由于对象的属性可以赋值给另一个对象,所以属性所在的当前对象是可变的,即this指向是可变的。
谈谈 This 对象的理解_51CTO博客_this对象的理解
4.new操作符具体干了什么
new操作符用于创建一个给定构造函数的实例对象
一、new操作符的作用new操作符用于创建一个新的对象实例。当我们使用new操作符时,它会执行以下步骤:
-
创建一个新的空对象。
-
将这个新对象的原型指向构造函数的原型。
-
将构造函数的this指向这个新对象。
-
执行构造函数中的代码,初始化这个新对象。
-
返回这个新对象。
5.null,undefined 的区别
null的字面意思是“空值”,这个值的语义是,希望表示一个对象被人为的重置为空对象,而非一个变量最原始的状态。在内存里的表示就是,栈中的变量没有指向堆中的内存对象。
undefined的字面意思就是未定义的值,这个值的语义是,希望表示一个变量最原始的状态,而非人为操作的结果。这种原始状态会在以下4种场景中出现:
(1) 声明了一个变量,但没有赋值
(2) 访问对象上不存在的属性
(3)函数定义了形参,但没有传递实参
(4)使用void对表达式求值
6.javascript 代码中的"use strict";是什么意思
“use strict” 指令不是一条语句,但是是一个字面量表达式,在 JavaScript 旧版本中会被忽略。
“use strict” 的目的是指定代码在严格条件下执行。
严格模式下不能使用未声明的变量。
*7.12*
1. 同步任务和异步任务的区别
同步任务是按照顺序依次执行的。当一个任务开始执行时,程序会等待该任务完成后才继续执行下一个任务。这意味着在执行同步任务时,程序会阻塞(或者说暂停)其他操作,直到该任务完成。
异步任务是在后台同时进行的。当遇到一个异步任务时,程序会继续执行后面的代码而不会等待任务完成。异步任务通常会通过回调函数、Promise、async/await或其他机制来处理任务完成后的结果。
区别:
-
执行顺序:同步任务按照顺序依次执行,而异步任务可以在后台同时进行,不会阻塞程序的其他操作。
-
阻塞与非阻塞:同步任务会阻塞其他操作,直到任务完成;异步任务不会阻塞其他操作,允许程序继续执行其他任务。
-
结果处理:
同步任务的结果会立即返回,程序可以直接处理;
异步任务执行完成后,可以通过回调函数、Promise等方式获取结果。
在编程中,异步操作常用于处理耗时的任务,例如网络请求、文件读写等。通过异步方式可以避免程序的阻塞,提升了程序的执行效率和用户体验。
优缺点:
1、同步的执行效率会比较低,耗费时间,但有利于我们对流程进行控制,避免很多不可掌控的意外情况;
2、异步的执行效率高,节省时间,但是会占用更多的资源,也不利于我们对进程进行控制
(扩展到事件循环)
2.谈一谈箭头函数与普通函数的区别
- 外形不同:箭头函数使用箭头定义, 更加简洁、清晰;普通函数不用箭头
- 命名上:箭头函数全都是匿名函数,普通函数可以是匿名函数,也可以是具名函数
- 箭头函数不能用于构建函数,普通函数可以用于构建函数,以此创建对象实例。因为构造函数的new会先在js内部生成一个对象,再把函数中的this指向该对象,然后执行构造函数中的语句, 最终返回该对象实例,但是箭头函数没有自己的this,它的this其实是继承了父作用域中的this,且this指向永远不会随在哪里调用、被谁调用而改变,所以箭头函数不能作为构造函数使用,或者说构造函数不能定义成箭头函数,否则用new调用时会报错!
- this指向上:在普通函数中,this总是指向调用它的对象,如果用作构造接收,它指向创建的对象实例; 箭头函数没有自己的
this
,它会捕获自己在定义时(注意,是定义时,不是调用时)所处的父作用域的this
,并继承这个this
值。所以,箭头函数中this
的指向在它被定义的时候就已经确定了,之后永远不会改变。 .call()
/.apply()
/.bind()
方法可以用来动态修改普通函数执行时this
的指向,但由于箭头函数的this
定义时就已经确定且永远不会改变。- 箭头函数没有原型prototype
- 箭头函数没有自己的arguments
3.JS 数组和对象的遍历方式,以及几种方式的比较
在 JavaScript 中,可以使用不同的方式遍历数组和对象。
数组遍历方式:
- for 循环:
const array = [1, 2, 3, 4, 5];
for (let i = 0; i < array.length; i++) {
console.log(array[i]);
}
- forEach() 方法:
const array = [1, 2, 3, 4, 5];
array.forEach((item) => {
console.log(item);
});
- for…of 循环:
const array = [1, 2, 3, 4, 5];
for (let item of array) {
console.log(item);
}
- map() 方法:
const array = [1, 2, 3, 4, 5];
array.map((item) => {
console.log(item);
});
对象遍历方式:
- for…in 循环:
const obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
console.log(obj[key]);
}
- Object.keys() 方法:
const obj = { a: 1, b: 2, c: 3 };
Object.keys(obj).forEach((key) => {
console.log(obj[key]);
});
比较不同的遍历方式:
- for 循环:灵活性最高,可以根据索引进行精确控制,适用于对数组的全部或部分元素进行遍历和操作。
- forEach() 方法:简单易用,适用于数组的全部元素遍历,但无法使用 break 或 continue 控制循环。
- for…of 循环:更直观和简洁,遍历数组的全部元素,可以使用 break 和 continue 控制循环,但无法获取索引。
- map() 方法:用于遍历数组的全部元素并对其进行操作,返回一个新数组。
在对象的遍历中,常用的方式是 for…in 循环和 Object.keys() 方法。它们能够遍历对象的键,但遍历顺序可能不一致。
5.对于对象的遍历,for-in循环是一种常见的方式,但需要注意的是它会遍历对象的所有可枚举属性,包括继承自原型链的属性。
需要根据具体的需求选择合适的遍历方式。通常情况下,推荐使用 forEach() 方法和 for…of 循环进行数组遍历,使用 for…in 循环和 Object.keys() 方法进行对象遍历。
4.如何解决跨域问题
读取不用解决跨域
Ajax是局部更新页面 需要跨域
同源策略 协议 域名 端口号一致
跨域就是当在页面上发送ajax请求时,由于浏览器同源策略的限制,要求当前页面和服务端必须同源,也就是协议、域名和端口号必须一致
1、JSONP方式解决跨域
jsonp的原理就是利用了script标签的src的属性,script标签不受浏览器同源策略的限制,然后和后端一起配合来解决跨域问题的。
jsonp的优点就是兼容性好,可以解决主流浏览器的跨域问题,缺点是仅支持GET请求,不安全,可能遭受xss攻击。
2、CORS方式解决跨域:
cors是跨域资源共享,是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),使得浏览器允许这些 origin 访问加载自己的资源。服务端设置了Access-Control-Allow-Origin就开启了CORS,所以这种方式只要后端实现了CORS,就解决跨域问题,前端不需要配置。
3、搭建Node代理服务器解决跨域
我们在客户端请求自己的node代理服务器,然后在node代理服务器中转发客户端的请求访问服务器,服务器处理请求后给代理服务器响应数据,然后在代理服务器中把服务器响应的数据再返回给客户端。客户端和自己搭建的代理服务器之间也存在跨域问题,所以需要在代理服务器中设置CORS
4、Nginx反向代理解决跨域:
nginx通过反向代理解决跨域也是利用了服务器请求服务器不受浏览器同源策略的限制实现的。
客户端请求nginx服务器,在nginx.conf配置文件中配置server监听客户端的请求,然后把location匹配的路径代理到真实的服务器,服务器处理请求后返回数据,nginx再把数据给客户端返回。
5、postMessage万式解决跨域:
6、Websocket方式解决跨域:
使用Websocket也可以解决跨域问题,因为WebSocket本身不存在跨域问题,所以我们可以利用webSocket来进行非同源之间的通信,
WebSocket 规范定义了一个在 Web 浏览器和服务器之间建立“套接字”连接的 API。 简单来说:客户端和服务器之间存在持久连接,双方可以随时开始发送数据。
5.XML和JSON的区别
1、JSON是JavaScript Object Notation;XML是可扩展标记语言。
2、JSON是基于JavaScript语言;XML源自SGML。
3、JSON是一种表示对象的方式;XML是一种标记语言,使用标记结构来表示数据项。
4、JSON不提供对命名空间的任何支持;XML支持名称空间。
5、JSON支持数组;XML不支持数组。
6、XML的文件相对难以阅读和解释;与XML相比,JSON的文件非常易于阅读。
7、JSON不使用结束标记;XML有开始和结束标签。
8、JSON的安全性较低;XML比JSON更安全。
9、JSON不支持注释;XML支持注释。
10、JSON仅支持UTF-8编码;XML支持各种编码。
6.谈谈你对webpack的看法
-
WebPack 是一个模块打包工具,可以使用WebPack管理模块,并分析模块间的依赖关系,最终编绎输出模块为HTML、JavaScript、CSS以及各种静态文件(图片、字体等),让开发过程更加高效。
-
对于不同类型的资源,webpack有对应的模块加载器loader, 比如,解析css有css-loader,解析ES6成ES5可以用Babel-loader加载器等
-
webpack的基本功能(也就是各种loader的作用)
- 代码转换:TypeScript 编译成 JavaScript、ES6转ES5、SCSS 编译成 CSS 等等(
各种loader
) - 代码语法检测:自动检测代码是否符合语法 (
eslint-loader
) - 代码分割:打包代码时,可以将代码切割成不同的chunk(块),实现按需加载,降低了初始化时间,提升了首屏渲染效率
- 监测代码更新,自动编译,刷新页面:监听本地源代码的变化,自动构建,刷新浏览器(
自动刷新
) - 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统(没用过)。
- 文件压缩:压缩 JavaScript、CSS、HTML 代码,缩小文件体积(
比如说,打包后的js、css、html文件会去掉代码之间的空隔,紧凑显示
) - 模块合并:由于模块化的开发,一个页面可能会由多个模块组成,所以编译时需要把各个模块合并成一个文件(
模块化开发引出的功能
)
- 代码转换:TypeScript 编译成 JavaScript、ES6转ES5、SCSS 编译成 CSS 等等(
-
webpack的两大特色
-
自动分割(code splitting)
code splitting,即打包代码时,可以将代码切割成不同的chunk(块),实现按需加载,降低了初始化时间,提升了首屏渲染效率
-
loader 加载器可以处理各种类型的静态文件,并且支持串联操作
-
*7.13*
\31. webpack的打包原理
webpack打包原理是根据文件间的依赖关系对其进行静态分析,将这些模块按指定规则生成静态资源,当 webpack处理程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,将所有这些模块打包成一个或多个bundle。
\32. 如何优化webpack打包速度
要优化Webpack的打包速度,可以考虑以下几个方面:
-
使用更快的构建工具:升级Webpack到最新版本可以获取最新的性能优化改进。此外,使用Yarn或pnpm等替代npm的包管理工具也可以提升安装依赖的速度。
-
配置合理的Loader:Loader的处理过程会消耗时间,因此需要选择效率高的Loader,并合理配置它们的选项。尽量避免不必要的转换和处理,例如使用exclude或include选项明确指定需要处理的文件范围。
-
减少文件搜索范围:通过配置resolve.modules和resolve.extensions,可以缩小Webpack的模块搜索范围和文件扩展名搜索范围,加快模块解析速度。
-
使用代码分割(Code Splitting):通过将应用程序拆分为更小的代码块并按需加载,可以减小每次打包的体积,提高加载速度。可以使用Webpack的SplitChunksPlugin或动态导入(Dynamic Import)实现代码分割。
-
开启持久化缓存:使用Webpack的缓存功能,可以将已处理过的模块结果缓存到磁盘,下次构建时可以直接使用缓存结果,避免重复操作。
-
并行构建:可以使用工具如HappyPack或thread-loader等,将Webpack的构建过程并行化,加速构建速度。
-
压缩和缩小输出文件:通过Webpack的插件如UglifyJsPlugin和optimize-css-assets-webpack-plugin等,可以对输出文件进行压缩和缩小,减小文件体积。
-
移除不必要的插件和功能:移除不需要的插件和功能,以减少Webpack的工作量,并提高整体性能。
-
避免在开发环境中做过多优化:在开发环境中,通常不需要进行过多的优化,可以尽量简化配置,避免使用压缩等耗费时间的操作。
-
使用Webpack的缓存组件:使用webpack-md5-hash或contenthash等插件,在文件名中添加hash值,可以利用浏览器缓存,减少重复下载。
记住,优化Webpack的打包速度是一个综合性的任务,需要结合具体项目的特点和需求进行调整和优化。通过合理配置和选用适合的插件,可以显著提高Webpack的打包性能。
\33. 说说webpack中常见的Loader?解决了什么问题?
Webpack中有许多常见的Loader,它们用于处理和转换不同类型的文件,解决了一些问题,如:
-
babel-loader
:用于将ES6+的JavaScript代码转换为浏览器兼容的ES5代码,解决了跨浏览器兼容性问题。 -
css-loader
:用于处理CSS文件,支持导入和理解CSS文件中的@import
和url()
等语法,解决了在模块化开发中处理CSS文件的问题。 -
style-loader
:将CSS代码以style标签的形式插入到页面中,解决了使用Webpack打包时将样式应用到页面的问题。 -
file-loader
:用于处理文件资源(如图片、字体等),将它们复制到输出目录,并返回文件路径,解决了在代码中引用文件资源的路径问题。 -
url-loader
:与file-loader
类似,但它可以将小尺寸的文件资源编码为DataURL,避免多次请求,解决了在某些场景下减少请求数量的问题。 -
sass-loader
、less-loader
、stylus-loader
:用于处理Sass、Less和Stylus等预处理器的文件,将其转换为CSS,解决了在项目中使用这些预处理器的问题。 -
postcss-loader
:使用PostCSS工具,通过插件来处理CSS代码,实现自动添加前缀、压缩、处理浏览器兼容性等问题。 -
ts-loader
、ts-node
:用于处理TypeScript文件,将其转换为JavaScript代码,解决了在Webpack中使用TypeScript的问题。
这些Loader扩展了Webpack的功能,通过解析、转换和处理各种资源文件,使开发者可以在项目中使用不同类型的文件,并将它们集成到最终的打包结果中。Loader的存在大大提高了开发效率,降低了开发过程中的复杂性,使得前端工程化更加灵活和高效。
\34. 说说webpack中常见的Plugin?解决了什么问题?
Webpack中有许多常见的插件(Plugins),它们用于执行各种任务,解决了一些问题,如:
-
HtmlWebpackPlugin
:生成HTML文件,并自动引入Webpack打包后的输出文件,解决了手动管理HTML文件和script标签的问题。 -
MiniCssExtractPlugin
:将CSS代码从JavaScript文件中提取出来,单独生成CSS文件,解决了将样式代码与逻辑代码分离的问题。 -
CleanWebpackPlugin
:在构建前清理输出目录,解决了每次构建时旧文件遗留的问题。 -
CopyWebpackPlugin
:将指定的文件或文件夹复制到输出目录,解决了需要复制静态资源(如图片、字体等)到输出目录的问题。 -
DefinePlugin
:定义全局变量,在编译过程中替换源代码中的相应值,解决了在不同环境中使用不同的配置或变量的问题。 -
HotModuleReplacementPlugin
:启用热模块替换(Hot Module Replacement,HMR)功能,实现模块的热更新,解决了在开发过程中修改代码后需要手动刷新浏览器的问题。 -
ProvidePlugin
:自动加载模块,使得在每个模块中不必显式引入依赖,解决了在多个模块中重复引入相同依赖的问题。 -
OptimizeCssAssetsWebpackPlugin
:压缩CSS代码,减小文件体积,解决了优化输出CSS文件大小的问题。 -
WebpackBundleAnalyzer
:可视化分析Webpack打包文件的大小和依赖关系,帮助优化项目的性能。
这些插件为Webpack提供了丰富的功能和扩展性,通过执行各种任务,如文件处理、资源管理、代码压缩、环境配置等,解决了在项目构建过程中的各类问题。开发者可以根据项目需求选择并配置适当的插件,提高开发效率和项目质量。插件的使用可以让Webpack更好地适应多样化的开发场景,并提供更多的工具和功能支持。
\35. 说说你对promise的了解
Promise是JavaScript中用于处理异步操作的对象。它是一种用于更简洁、可读性更高的方式来处理异步代码的解决方案。
Promise对象代表了一个尚未完成的操作,并可以用来处理异步操作的结果或错误。它有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。一旦Promise从pending状态转变为fulfilled或rejected状态,就称为Promise已settled。
Promise对象有两个重要的方法:
-
then()
: 用于指定当Promise变为fulfilled状态时应执行的回调函数。它接收两个可选的参数,第一个参数是当Promise状态变为fulfilled时要执行的回调函数,第二个参数是当Promise状态变为rejected时要执行的回调函数。 -
catch()
: 用于指定当Promise变为rejected状态时应执行的回调函数。
Promise的使用可以通过链式调用来简化和组织异步操作的处理逻辑。可以使用then()
和catch()
方法将多个Promise链接起来,形成一个Promise链。每个Promise在链中都可以返回一个新的Promise,可以继续处理后续的异步操作。这样的链式调用可以使异步代码结构更清晰、可读性更高,避免了回调地狱(Callback Hell)的问题。
除了使用原生的Promise,还可以使用一些第三方的库,如axios、fetch等,它们提供了更加便捷的接口和功能来处理异步操作。
需要注意的是,Promise是一次性的,即一旦Promise进入settled状态,就不能再次使用。为了处理多个Promise并发执行或按顺序执行的情况,可以使用Promise.all()来等待多个Promise执行完毕、Promise.race()来返回最先解决的Promise结果,或者使用async/await语法来更简洁地处理异步操作。
总结而言,Promise是一种用于处理异步操作的对象,它提供了更加简洁、可读性更高的方式来处理异步代码,使得代码结构更清晰、可维护性更强。
\36. async函数是什么,有什么作用
async函数是ES2017引入的一种特殊函数,用于更简洁和直观地处理异步操作。通过在函数前加上async
关键字,可以将普通函数转换为async函数。
async函数内部可以包含一个或多个异步操作,它们会返回一个Promise对象。在async函数内部使用await
关键字可以暂停函数的执行,等待Promise对象的解决(即状态变为fulfilled)后再继续执行剩余的代码。
async函数的作用主要有以下几点:
-
异步操作的简化:使用async函数可以以更同步化的方式编写异步代码,避免回调地狱(Callback Hell)和复杂的Promise链式调用。代码结构更清晰、可读性更高。
-
错误处理的简化:在async函数内部使用
try-catch
语句可以捕获并处理Promise的错误。如果在async函数中的await表达式后的Promise对象被reject,那么整个async函数会立即停止执行,并通过catch捕获错误。 -
链式调用的便利性:在async函数内部可以方便地使用
await
关键字按顺序处理多个异步操作,使代码更加直观和易于维护。 -
异步操作的并发处理:可以使用并发处理多个异步操作的能力,通过使用
Promise.all()
等来等待多个Promise对象的解决,以及使用Promise.race()
来返回最先解决的Promise结果。 -
支持同步和异步混合操作:async函数内部可以混合使用同步和异步操作,使得代码编写更加灵活和便捷。
需要注意的是,async函数始终返回一个Promise对象。如果在async函数中使用return
语句返回一个非Promise对象,将会被自动包装成一个以该值解决的Promise对象。如果在async函数中抛出异常,Promise对象将以该异常为拒绝原因。
总结而言,async函数是一种用于简化异步操作处理的函数,它通过async
和await
关键字使得异步代码的编写更加直观和流畅。async函数提供了简洁的语法和良好的错误处理机制,使得异步代码更易读、易维护。
ES6的新特性:
Let和const----与var的不同
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z8aiIMcC-1691760855922)(file:///C:\Users\lumos\AppData\Local\Temp\ksohtml14820\wps1.jpg)]
Const的基本数据类型可以监听到修改,引用数据类型不能
箭头函数,解构赋值,for。。。Of的for…in(遍历对象)
模块化,
Class,promise对象,属性代理,扩展运算符
新增的方法(数组和字符串的)
数组的扁平化
装饰器,代码生成器,迭代器(async和awite的语法糖)
7.16
37. 有使用过vue吗?说说你对vue的理解
vue 是一套用于构建用户界面的渐进式框架,vue 的核心库只关注视图层。vue 的渐进式表现在刚开始不会一股脑的把一些概念全抛给你,而是让你根据自己的需求来不断扩充。
vue是一个轻量级框架,大小只有几十kb,具有易用性、灵活性和高效性的特点。
vue 提供数据响应式、基于配置的组件系统以及大量的指令等,这些让开发者只需关心核心业务;
vue比较灵活,可以根据需要引入 vue-router、vuex、vue-cli 等工具;
vue 操作的是虚拟 DOM,采用 diff 算法更新 DOM,比传统的 DOM 操作更加的高效;
缺点是 不支持IE8及以下版本。
vue采用双向数据绑定, 是一种数据流动的方式,它可以使数据的变化自动同步到视图,同时视图中的变化也可以自动地更新数据。
38. 你对SPA单页面的理解,它的优缺点分别是什么?如何实现SPA应用呢
它的优点有三点:
(1)良好的交互式体验,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
(2)基于上面所说,SPA 对服务器的压力小;
(3)前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理。
它的缺点有四点:
(1)初次加载耗时多,为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载(可以使用路由懒加载解决)
(2)由于单页应用在一个页面中显示,所以不可以使用浏览器自带的前进后退功能,想要实现页面切换需要自己进行管理
(3)SEO 难度较大,由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
(4)有兼容性问题,部分平台不支持pushstate
39. SPA首屏加载速度慢的怎么解决?
当SPA(单页面应用)的首屏加载速度较慢时,可以采取以下几种方法来解决:
-
代码分割(Code Splitting): 将应用的代码分割成多个较小的模块,按需加载。这样可以将初始加载的代码量减少到最小,只加载当前页面所需的模块,提高首屏加载速度。在Vue中,可以使用动态导入(Dynamic Import)或路由懒加载等技术实现代码分割。
-
延迟加载非关键资源: 将非关键资源(如图片、视频、广告等)的加载延迟到页面加载完成后再进行。可以使用懒加载技术,如Intersection Observer API、Vue的
v-lazy
指令等来延迟加载图片和其他资源。 -
压缩和优化代码: 使用工具对代码进行压缩和优化,减小文件大小。可以使用压缩工具、如Webpack的UglifyJsPlugin等,对代码进行混淆、去除空白字符、删除注释等。此外,还可以使用如Tree Shaking、Code Minification等技术来清除不必要的代码和减小文件体积。
-
缓存优化: 合理利用浏览器缓存机制,对静态资源进行缓存。可以设置适当的缓存策略,如使用文件指纹(File Hash)来进行版本管理,启用Gzip压缩,设置合适的缓存过期时间,使用CDN(内容分发网络)等。
-
使用SSR(服务器端渲染): 对于对首屏加载速度要求较高的SPA,可以考虑使用服务器端渲染(SSR)技术,将初始页面的HTML内容直接由服务器生成,减少浏览器端的加载时间。Vue框架提供了Nuxt.js等工具来简化SSR的开发。
-
性能优化和懒加载组件: 对于SPA中使用到的组件,可以优化其性能,如减少渲染次数、使用虚拟列表等。同时,对于非首屏必要的组件,可以使用懒加载技术进行延迟加载。
-
优化网络请求: 减少网络请求次数和资源大小,例如合并CSS和JavaScript文件、使用雪碧图等。
除了上述方法,还可以通过浏览器开发者工具进行性能分析,找出瓶颈所在,并根据实际情况进行具体的优化。综合采用多种策略能够有效提升SPA的首屏加载速度。
40. VUE路由的原理
vue-router是Vue官方推出的路由管理器,主要用于管理URL,实现URL和组件的对应,以及通过URL进行组件之间的切换,从而使构建单页面应用变得更加简单。
vue-router的工作原理
单页面应用的核心思想之一,就是更新视图而不重新请求页面,简单来说,它在加载页面时,不会加载整个页面,只会更新某个指定的容器中的内容。对于大多数单页面应用,都推荐使用官方支持的vue-router。在实现单页面前端路由时,提供了两种方式,分别是hash模式和history模式,
1. hash模式
vue-router默认为hash模式,使用URL的hash来模拟一个完整的URL,当URL改变时,页面不会重新加载。#就是hash符号,在hash符号后的值称为hash值。
路由的hash模式是利用了window可以监听onhashchange事件来实现的,同时每一次改变hash值,都会在浏览器的访问历史中增加一个记录,使用“后退”按钮,就可以回到上一个位置。所以,hash 模式是根据hash值来发生改变,根据不同的值,渲染指定DOM位置的不同数据。
2. history模式
hash模式的URL中会自带#号,影响URL的美观,而history模式不会出现#号,这种模式充分利用了history.pushState()来完成URL的跳转,而且无须重新加载页面。使用history模式时,需要在路由规则配置中增加mode:‘history’
(扩展)路由守卫的方法
(4条消息) Vue学习第33天——路由守卫(导航守卫)超详解讲解及使用场景、案例练习_vue路由守卫使用场景_离奇6厘米的博客-CSDN博客
(全局 组件内 路由独享的)
Vue Router 提供了三种导航守卫的方法:
-
beforeEach
: 全局前置守卫beforeEach
方法可以注册一个全局前置守卫函数,该函数会在每次路由切换之前被调用。它接受三个参数:to、from 和 next。to
表示即将进入的目标路由对象,from
表示当前导航正要离开的路由对象,next
是一个必须调用的函数,可以控制路由是否继续进行。router.beforeEach((to, from, next) => { // 在路由切换之前进行一些操作,如权限验证 if (to.meta.requiresAuth && !isAuthenticated) { next('/login'); // 重定向到登录页 } else { next(); // 继续路由切换 } });
-
beforeResolve
: 全局解析守卫beforeResolve
方法类似于beforeEach
,但是它会在每次路由组件被解析之前被调用。它也接受三个参数:to、from 和 next。使用beforeResolve
可以确保所有异步路由组件都被解析完毕,然后再进行路由切换。router.beforeResolve((to, from, next) => { // 在路由组件解析之前进行一些操作 // ... next(); // 继续路由切换 });
-
afterEach
: 全局后置守卫afterEach
方法可以注册一个全局后置守卫函数,该函数会在每次路由切换之后被调用。它只接受一个参数 from,表示当前导航正要离开的路由对象。router.afterEach((to, from) => { // 在路由切换之后进行一些操作 });
除了全局导航守卫,Vue Router 还提供了组件级别的导航守卫,如 beforeRouteEnter
、beforeRouteUpdate
和 beforeRouteLeave
,这些守卫可在组件内部使用,用于控制组件的导航行为和数据处理。
导航守卫的使用可以实现诸如权限验证、页面切换动画、数据加载等功能,能够提供更精细的路由控制和用户体验。
41. Vue中组件和插件有什么区别?
组件 (Component) 是用来构成你的 App 的业务模块,它的目标是 App.vue
插件 (Plugin) 是用来增强你的技术栈的功能模块,它的目标是 Vue 本身,插件就是指对 Vue 的功能的增强或补充
组件具有:单一性,复用性(用的是节点结构),可扩展性(利用组件通信,插槽)
(扩展)为啥要封装组件
封装组件是一种常用的软件工程技术,在前端开发中也有很重要的作用。以下是几个封装组件的主要原因:
-
代码复用和维护:通过封装组件,可以将常用和复杂的功能逻辑抽象成可复用的组件,并在不同的场景中重复使用。这样可以减少重复编写相同代码的工作量,提高开发效率,并且有利于后期的代码维护和修改。
-
提高可维护性:将功能模块封装成组件后,可以增强代码的可读性和可维护性。组件封装可以使代码结构更清晰,各个功能模块相互独立,便于理解和修改。
-
抽象和解耦:组件封装可以将复杂的业务逻辑抽象成简单的接口,使组件对外提供统一的接口和功能。这样可以降低不同功能模块之间的耦合性,提高代码的灵活性和可扩展性。
-
提升开发效率:通过组件封装,可以促使团队成员更好地分工合作,重复的开发工作可以通过直接使用现有的组件进行快速开发。同时,优秀的第三方组件库也可以提供许多现成的组件供开发者使用,极大地加速开发进度。
-
用户体验的一致性:通过组件的封装和使用,可以确保应用程序在不同页面和不同功能之间拥有一致的用户体验。组件的统一样式和交互行为可以提升用户体验,并增加应用程序的整体品质。
总结来说,封装组件有助于提高代码复用性、可维护性和可扩展性,简化开发流程,保持代码结构的清晰和一致性,提升用户体验。因此,在前端开发中封装组件是非常有意义的一项工作。
42. Vue组件之间的通信方式都有哪些
-
Props(父子组件通信):父组件通过props将数据传递给子组件,在子组件中使用props接收数据。这种方式适合父组件向子组件传递数据。
-
e m i t 和事件(子父组件通信):子组件通过 emit和事件(子父组件通信):子组件通过 emit和事件(子父组件通信):子组件通过emit方法触发一个自定义事件,并将数据作为参数传递给父组件,在父组件中使用v-on监听该事件,并在对应的方法中获取传递过来的数据。这种方式适合子组件向父组件传递数据。
-
r e f s (父组件访问子组件):通过给子组件设置 r e f 属性,可以在父组件中通过 refs(父组件访问子组件):通过给子组件设置ref属性,可以在父组件中通过 refs(父组件访问子组件):通过给子组件设置ref属性,可以在父组件中通过refs来直接访问子组件的实例或DOM元素。
-
Provide和Inject(祖先组件向后代组件传递数据):通过在祖先组件中使用provide提供数据,然后在后代组件中使用inject接收数据。它可以实现跨层级组件的传递,但不推荐在中大型应用中使用,因为这样会使组件的依赖关系变得不明确。
-
Vuex(全局状态管理):Vuex是Vue.js官方提供的状态管理库,用于在Vue应用程序中集中管理和共享多个组件的状态。通过Vuex的store来存储和管理应用的状态,各个组件可以直接从store中取值或修改值,实现了组件之间的数据共享。
-
Event Bus(发布/订阅模式):通过创建一个全局的事件总线实例,通过发布和订阅事件来实现组件之间的通信。这种方式适合于非父子组件之间的通信,但使用过度可能导致代码难以维护,因此需谨慎使用。
7.16
43. 你了解vue的diff算法吗?说说看
Vue中的diff算法主要应用于虚拟DOM(Virtual DOM)的比较和更新过程,以提高渲染性能。
Diff算法的基本思想是通过比较新旧虚拟DOM树的差异,只对有变化的部分进行更新,而不是重新渲染整个DOM树。这样可以减少对真实DOM的操作次数,提高渲染效率。
具体来说,Vue的diff算法执行以下几个步骤:
-
生成虚拟DOM树:在组件更新过程中,Vue会根据组件的状态和属性生成新的虚拟DOM树。
-
比较新旧虚拟DOM树:Vue会逐层比较新旧虚拟DOM树的节点。开始比较的第一个节点通常是根节点。
-
更新差异节点:通过比较新旧节点的类型、属性和子节点等信息,确定节点的差异性。根据差异的类型,执行相应的更新操作。对于不同类型的差异,可能会执行插入、删除、替换或更新等操作。
-
递归比较子节点:如果差异节点包含子节点,则会递归地比较子节点及其后代节点,执行相应的更新操作。
在比较节点时,Vue使用一些优化策略来提高算法的效率和性能。一种常用的优化是使用节点的key属性进行比较,以便在列表渲染中识别出移动、更新和添加等操作。通过key属性,Vue可以更精确地定位和处理差异。
总体而言,Vue的diff算法通过比较新旧虚拟DOM树的差异,并灵活地应用相应的更新操作,实现了高效的DOM更新和渲染。这种优化使得Vue在大规模数据更新和高频率交互的应用中表现出色。
(扩展)vue2的diff算法与vue3的diff算法的不同
Vue 2 和 Vue 3 在 diff 算法的实现上有一些不同之处。以下是两者之间的主要区别:
-
Vue 2 的 diff 算法:
Vue 2 使用的是经典的 Virtual DOM diff 算法,也称为双端比较算法。算法的核心思想是将新旧虚拟 DOM 树进行逐层对比,找出差异,并最小化对实际 DOM 的操作。它遵循了以下策略:
- 从根节点开始对比,对整棵虚拟 DOM 树进行深度优先遍历。
- 对比同级节点,找出需要更新的节点,并进行相应处理:
- 如果节点类型不同,则直接替换;
- 如果节点类型相同,但节点对应的 key 不同,则认为是不同的节点,需要替换;
- 如果节点类型和 key 都相同,但是节点的内容或属性有变化,则更新节点。
这种算法的缺点是,无法跳过精确比较,需要对整个虚拟 DOM 树进行遍历和比较,即使只有一个小部分发生了变化。
-
Vue 3 的 diff 算法:
Vue 3 使用了一种优化过的 diff 算法,称为基于 Proxy 的观察机制。它基于一种新的响应式系统,利用 JavaScript 的 Proxy 对象来追踪数据的变化,并在事件循环结束时,按需更新视图。
使用 Proxy 可以实现精确的跟踪和对比,只在数据发生变化时才会触发相应的更新操作。这种算法的优势在于可以避免不必要的比较和更新,提高了性能。
此外,Vue 3 还使用了静态分析和编译优化等技术,生成更高效的渲染函数。它通过编译阶段的优化,消除了不必要的虚拟 DOM 对比,使得整个渲染过程更快速和高效。
总结来说,Vue 2 使用经典的 Virtual DOM diff 算法,对整个虚拟 DOM 树进行遍历和比较,性能相对较低。而 Vue 3 则使用了基于 Proxy 的观察机制,可以精确跟踪数据变化,避免不必要的比较和更新,性能更高。此外,Vue 3 还通过编译优化等技术,进一步提升了渲染性能。
44. 为什么需要 Virtual Dom
Virtual DOM(虚拟DOM)是一个在内存中构建和操作的轻量级的DOM表示,用于提高前端框架在渲染过程中的效率和性能。它的存在有以下几个主要原因:
-
性能优化:真实DOM的操作是昂贵的,每次对DOM的修改都需要进行重排和重绘,这会导致性能下降。而使用虚拟DOM,我们可以在内存中进行操作,通过比较虚拟DOM的差异,最终只更新真实DOM中实际变化的部分,减少了真实DOM操作的次数,从而提高了性能。
-
跨平台能力:虚拟DOM是独立于具体平台的,它可以运行在浏览器环境、移动端以及服务器端等不同的环境中。这使得跨平台应用开发成为可能,例如使用React Native可以同时开发iOS和Android应用。
-
简化开发难度:使用虚拟DOM可以提供更高层次的抽象,使得开发者可以更加专注于应用的逻辑和数据,而无需直接操作底层的DOM。这使得开发过程更加简单,代码易于维护。
-
提升可测试性:虚拟DOM可以方便地进行测试,因为测试可以在内存中操作虚拟DOM,无需依赖真实的浏览器环境。这使得编写测试用例变得更加容易,提高了代码质量和可靠性。
然而,需要注意的是,虚拟DOM并不是解决所有问题的银弹。在特定场景下,如应用中大量的动画效果,直接操作真实DOM可能会更为高效。因此,在选择使用虚拟DOM时,需要根据具体情况权衡利弊。
45. Vue3.0的设计目标是什么?做了哪些优化
Vue 3.0的设计目标主要包括:
-
更好的性能:Vue 3.0在许多方面进行了性能优化,其中包括使用了基于Proxy的新的响应式系统,提高了观察数据变化的效率。内部重写了虚拟DOM的实现,并引入了静态树提升(Static Tree Hoisting)和标记(Patch Flag)技术,以减少不必要的渲染操作和选择器的生成。
-
更小的体积:Vue 3.0在设计上更加模块化,可以按需加载所需的功能模块,减少了整体库的体积。同时,Vue 3.0的编译器也进行了重写,生成的生产代码更小,减少了运行时的负担。
-
更好的TypeScript支持:Vue 3.0完全重写了TypeScript声明文件(TypeScript declaration)并优化了类型推断,使得在使用TypeScript进行开发时,可以获得更好的类型检查和自动补全支持。
-
更强大的组合式API:Vue 3.0引入了Composition API,这是一种基于函数的API风格,使得开发者可以更方便地组织和重用组件逻辑。Composition API提供了更灵活的组合方式,更好地支持逻辑复用和代码组织。
-
更好的开发者体验:Vue 3.0改进了开发者工具,在浏览器插件中提供了更好的调试功能和组件层级显示。此外,Vue 3.0还引入了单文件组件的优化编译模式,加快了开发环境下的热重载速度。
这些优化和改进使得Vue 3.0在性能、体积、开发体验和TypeScript支持等方面都有了明显的提升,并提供了更好的开发工具和功能,以满足日益复杂和高效的现代Web应用程序的需求。
(扩展)vue2与vue3的不同
一. 根节点不同
vue2中必须要有根标签
vue3中可以没有根标签,会默认将多个根标签包裹在一个fragement虚拟标签中,有利于减少内存。
二. 组合式API和选项式API
在vue2中采用选项式API,将数据和函数集中起来处理,将功能点切割了当逻辑复杂的时候不利于代码阅读。
在vue3中采用组合式API,将同一个功能的代码集中起来处理,使得代码更加有序,有利于代码的书写和维护。
三. 生命周期的变化
创建前:beforeCreate -> 使用setup()
创建后:created -> 使用setup()
挂载前:beforeMount -> onBeforeMount
挂载后:mounted -> onMounted
更新前:beforeUpdate -> onBeforeUpdate
更新后:updated -> onUpdated
销毁前:beforeDestroy -> onBeforeUnmount
销毁后:destroyed -> onUnmounted
异常捕获:errorCaptured -> onErrorCaptured
被激活:onActivated 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行。
切换:onDeactivated 比如从 A 组件,切换到 B 组件,A 组件消失时执行
四. v-if和v-for的优先级
在vue2中v-for的优先级高于v-if,可以放在一起使用,但是不建议这么做,会带来性能上的浪费
在vue3中v-if的优先级高于v-for,一起使用会报错。可以通过在外部添加一个标签,将v-for移到外层
五. diff算法不同
vue2中的diff算法
遍历每一个虚拟节点,进行虚拟节点对比,并返回一个patch对象,用来存储两个节点不同的地方。
用patch记录的消息去更新dom
缺点:比较每一个节点,而对于一些不参与更新的元素,进行比较是有点消耗性能的。
特点:特别要提一下Vue的patch是即时的,并不是打包所有修改最后一起操作DOM,也就是在vue中边记录变更新。(React则是将更新放入队列后集中处理)。
vue3中的diff算法
在初始化的时候会给每一个虚拟节点添加一个patchFlags,是一种优化的标识。
只会比较patchFlags发生变化的节点,进行识图更新。而对于patchFlags没有变化的元素作静态标记,在渲染的时候直接复用。
六. 响应式原理不同
vue2通过Object.definedProperty()的get()和set()来做数据劫持、结合和发布订阅者模式来实现,Object.definedProperty()会遍历每一个属性。
vue3通过proxy代理的方式实现。
proxy的优势:不需要像Object.definedProperty()的那样遍历每一个属性,有一定的性能提升proxy可以理解为在目标对象之前架设一层“拦截”,外界对该对象的访问都必须通过这一层拦截。这个拦截可以对外界的访问进行过滤和改写。
当属性过多的时候利用Object.definedProperty()要通过遍历的方式监听每一个属性。利用proxy则不需要遍历,会自动监听所有属性,有利于性能的提升
七. 插槽方式不同
具名插槽使用方式不同:vue2使用slot=‘’,vue3使用v-slot:‘’
作用域插槽使用方式不同:vue2中在父组件中使用slot-scope=“data"从子组件获取数据,vue3中在父组件中使用 #data 或者 #default=”{data}"获取
46. Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同?
在 Vue3 中,Vue 团队引入了一种新的组织组件逻辑的方式,称为组合式 API(Composition API),与传统的选项 API 相对应。以下是组合式 API 和选项 API 的一些区别:
-
组织方式:选项 API 是基于选项的方式来组织组件逻辑,将各种选项(如 data、methods、computed 等)集中在一个对象中。而组合式 API 则是将逻辑按照功能特点进行组合,开发者可以根据功能将相关代码分组。
-
可复用性:组合式 API 更具可复用性,可以将逻辑组织成可独立使用的函数,可以在多个组件中共享和复用。这使得代码更易于维护和测试。
-
逻辑关注点分离:组合式 API 使得组件的逻辑关注点更加明确和分离。每个组合函数专注于一个特定的功能,如数据获取、副作用、状态管理等,使得组件更易于理解和修改。
-
自由组合:组合式 API 允许开发者按需组合逻辑,而不像选项 API 那样受到固定的生命周期钩子影响。开发者可以更自由地定义和组合逻辑,提高了开发灵活性和可扩展性。
-
较少的重复代码:由于组合式 API 可以将逻辑代码独立成函数,因此可以减少重复代码的编写。多个组件可以共享同一组合函数,使得代码更加简洁。
需要注意的是,组合式 API 并不代替选项 API,而是作为其补充和扩展,两者可以在同一个项目中共存。选项 API 仍然是 Vue 中的主要方式,特别适合简单的组件和快速开发;而对于复杂组件、逻辑复用和更细粒度的控制,组合式 API 提供了更好的选择。
总的来说,在组合式 API 中,开发者可以更自由地组织和复用组件逻辑,更好地封装和抽象功能,提高代码可读性和维护性。
组合式API的写法更加紧凑,适合可以清楚的捋清楚逻辑思路,并且组合式API可以提高代码的复用性,可维护性。
47. 说一下Vue数据响应式的原理
Vue2数据响应式的原理
vue响应式也叫作数据双向绑定,大致原理阐述:
首先我们需要通过Object.defineProperty()方法把数据(data)设置为getter和setter的访问形式,这样我们就可以在数据被修改时在setter方法设置监视修改页面信息,也就是说每当数据被修改,就会触发对应的set方法,然后我们可以在set方法中去调用操作dom的方法。
此外,如果页面有input用v-model绑定数据,我们需要在这种绑定了data的input元素上添加监听,添加input事件监听,每当input事件被触发时,就修改对应的data。
vue实现数据响应式,是通过数据劫持侦测数据变化,发布订阅模式进行依赖收集与视图更新,换句话说是Observe,Watcher以及Compile三者相互配合,
Observe实现数据劫持,递归给对象属性,绑定setter和getter函数,属性改变时,通知订阅者
Compile解析模板,把模板中变量换成数据,绑定更新函数,添加订阅者,收到通知就执行更新函数
Watcher作为Observe和Compile中间的桥梁,订阅Observe属性变化的消息,触发Compile更新函数
Vue3数据响应式的原理
Vue 3 中的响应式原理是通过使用 ES6 的 Proxy 对象来实现的。在 Vue 3 中,每个组件都有一个响应式代理对象,当组件中的数据发生变化时,代理对象会立即响应并更新视图。
具体来说,当一个组件被创建时,Vue 会为组件的 data 对象创建一个响应式代理对象。这个代理对象可以监听到数据的变化,并在数据变化时更新视图。
当组件的 data 对象发生变化时,代理对象会收到变化通知,然后将变化传递给相关的组件和子组件,从而触发组件的重新渲染。这种机制可以有效地保证视图和数据的同步
响应式的相关实现
我们都知道在vue3中主要是采取的ES6中的Proxy来进行数据的代理,通过reffect反射来实现动态的数据响应式,Vue3数据的更新是proxy配合Reffect来实现的
通过Proxy(代理): 拦截对象中任意属性的变化,属性值的读写,属性的增加,属性的删除等。
通过Reffect(反射): 对源对象的属性进行操作
48. 说说对 React 的理解?有哪些特性?
React,用于构建用户界面的 JavaScript 库,只提供了 UI 层面的解决方案
遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效
使用虚拟 DOM
来有效地操作 DOM
,遵循从高阶组件到低阶组件的单向数据流
帮助我们将界面成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,构成整体页面
react
类组件使用一个名为 render()
的方法或者函数组件return
,接收输入的数据并返回需要展示的内容
React
特性
-
JSX 语法
-
单向数据绑定
-
虚拟 DOM
-
声明式编程是一种编程范式,它关注的是你要做什么,而不是如何做
它表达逻辑而不显式地定义步骤。这意味着我们需要根据逻辑的计算来声明要显示的组件
-
Component
React
优势
通过上面的初步了解,可以感受到存在的优势React
高效灵活
声明式的设计,简单使用
组件式开发,提高代码复用率
单向响应的数据流会比双向绑定的更安全,速度更快
(扩展)pinia与vuex的区别
7.18
49. 说说 Real DOM 和 Virtual DOM 的区别?优缺点?
Real DOM(真实 DOM)和 Virtual DOM(虚拟 DOM)是在前端开发中常用的两种概念。它们有以下区别和各自的优缺点:
-
区别:
-
Real DOM 是浏览器中实际存在的 DOM 结构,代表了页面的真实结构。对 Real DOM 进行操作会触发浏览器的页面重绘和重排,是一种较慢的操作。
-
Virtual DOM 是通过 JavaScript 创建的虚拟的 DOM 对象。它是与真实 DOM 对应的 JavaScript 对象树,用于描述页面的结构和状态。操作 Virtual DOM 不会直接修改真实 DOM,而是在内存中进行计算和操作。最终,Virtual DOM 会与 Real DOM 进行比较,并仅对差异部分进行更新,以提高效率。
-
-
优缺点:
-
Real DOM:
- 优点:
- 具有直观的表示结构,是浏览器实际渲染页面的唯一标准。
- 可以直接操作真实 DOM,适用于对页面结构频繁进行更改的场景。
- 缺点:
- 操作 Real DOM 消耗较大的性能,特别是在大型和复杂的应用中,频繁的 DOM 操作会导致性能下降。
- 直接操作 DOM 面临许多细节问题,需要处理手动优化、避免重绘和重排等问题。
- 优点:
-
Virtual DOM:
- 优点:
- 在 Virtual DOM 中操作不直接影响真实 DOM,因此可以避免频繁的重绘和重排,提高性能。
- 可以进行批量操作和优化,将多次操作合并成一次,减少 DOM 操作的次数。
- 提供了简化的 API,使得开发者可以更方便地处理页面的状态和更新。
- 缺点:
- Virtual DOM 需要一定的计算和解析过程,可能会引入一些额外的开销。
- 对于小型应用或简单页面来说,使用 Virtual DOM 可能会过于复杂,增加不必要的复杂性。
- 优点:
-
总结来说,Real DOM 是真实的 DOM 结构,操作性能较低;Virtual DOM 是通过 JavaScript 创建的虚拟的 DOM 对象,可以提高性能。虚拟 DOM 具有更好的性能优化潜力,适用于大型和复杂应用。但在小型应用中,使用 Real DOM 更简单直接。选择使用哪种方法取决于具体的项目需求和开发场景。
50. 说说 React 生命周期有哪些不同阶段?每个阶段对应的方法是?
React组件的生命周期可以分为三个阶段:挂载阶段、更新阶段和卸载阶段。
-
挂载阶段:
- constructor:组件的构造函数,在组件创建时调用,用于初始化状态和绑定事件处理函数。
- static getDerivedStateFromProps:在挂载阶段和更新阶段之前调用,用于根据props的变化更新组件状态。
- render:根据组件的状态和属性渲染UI。
- componentDidMount:组件挂载后调用,可以进行异步操作、DOM操作和订阅事件。
-
更新阶段:
- static getDerivedStateFromProps:在挂载阶段和更新阶段之前调用,用于根据props的变化更新组件状态。
- shouldComponentUpdate:在更新阶段之前调用,用于判断组件是否需要重新渲染,默认返回true。
- render:根据组件的状态和属性重新渲染UI。
- componentDidUpdate:在组件更新后调用,可以进行DOM操作和订阅事件。
-
卸载阶段:
- componentWillUnmount:在组件卸载之前调用,用于清理定时器、取消订阅等清理操作。
此外,还有一些过时的生命周期方法,可以用于处理特定的情况:
- componentWillReceiveProps:在更新阶段之前调用,用于根据新的props更新状态。该方法已被getDerivedStateFromProps取代。
- componentWillUpdate:在更新阶段之前调用,用于准备组件更新的操作。该方法已被getDerivedStateFromProps和shouldComponentUpdate取代。
- componentWillMount:在挂载阶段之前调用,用于准备组件挂载的操作。该方法已被constructor取代。
请注意,以上方法是基于React 16及更高版本的生命周期。React 17中已经引入了一些改变,包括删除了一些生命周期方法,如componentWillReceiveProps、componentWillUpdate和componentWillMount。
51. 说说 React中的setState执行机制
在React中,通过setState
方法来更新组件的状态。setState
是一个异步方法,因此它并不会立即修改组件的状态值。
当调用setState
时,React会将新的状态合并到当前状态中,并将组件标记为“脏”(即需要重新渲染)。然后,React会使用一种称为“批处理”的机制来优化状态更新。
在一次事件循环中,React会收集所有的setState
调用,并将它们合并为一个单一的更新。这意味着即使你多次调用setState
,实际上只会触发一次重新渲染过程。
在组件重新渲染之前,React会将所有的setState
调用的状态变更合并为一个新的状态对象,以避免多次渲染。然后,React会调用render
方法来根据新的状态和属性生成新的UI,并将其应用到DOM中。
除了调用setState
方法,还可以将一个函数作为参数传递给setState
方法。这种方式被称为“函数式更新”,它能够接收先前的状态作为参数,并返回一个新的状态对象。
需要注意的是,由于setState
是异步的,所以不能直接通过setState
后立即获取到更新后的状态。如果需要在setState
之后获取最新的状态值,可以在setState
的第二个参数中传递一个回调函数,这个回调函数会在状态更新完成后被调用。
总结起来,setState
的执行机制如下:
- 将新的状态合并到当前状态中,并将组件标记为“脏”(需要重新渲染)。
- 在一次事件循环中,React会收集所有的
setState
调用,并将它们合并为一个单一的更新。 - 在组件重新渲染之前,React会将所有的状态变更合并为一个新的状态对象。
- React调用
render
方法生成新的UI,并将其应用到DOM中。 - 如果传递了回调函数,则在状态更新完成后触发回调函数。
52. 说说对React中类组件和函数组件的理解?有什么区别?
类组件是通过继承React.Component
类来创建的,它需要定义一个render
方法来返回组件的UI结构。类组件可以包含自己的状态(state),拥有生命周期方法和一些高级特性,如错误边界和获取refs等。类组件适用于复杂的逻辑和交互场景。
函数组件是使用函数定义的组件,它接收一个props
对象作为参数并返回一个React元素作为输出。函数组件通常用于只负责渲染UI,不包含自己的状态和生命周期方法。函数组件相对于类组件更简洁、清晰,易于理解和测试,并且在React的性能方面也有优势。
区别:
- 语法和定义方式:类组件是通过继承
React.Component
类并定义类方法来创建的,而函数组件是使用函数定义的组件。 - 状态和生命周期:类组件可以拥有自己的状态(state),可以在内部使用
this.setState
来更新状态,并具有生命周期方法(如componentDidMount
、componentDidUpdate
、componentWillUnmount
等)来处理组件的挂载、更新和卸载等情况。而函数组件使用React提供的useState
、useEffect
等钩子函数来管理状态和处理生命周期等操作。 - 性能:函数组件相对于类组件在性能方面有一些优势,因为函数组件没有实例化的过程和额外的实例方法,且React能够更高效地进行组件的比较和渲染。
- 代码复用性:使用类组件时,可以使用继承和面向对象的编程思想来实现代码复用。而函数组件通常倾向于使用React提供的钩子函数和自定义Hooks,以更灵活和可组合的方式实现代码复用。
总结:类组件适用于需要管理状态、处理生命周期和拥有复杂逻辑的情况,而函数组件适用于无状态、只负责渲染的场景,并具有更好的性能和代码简洁性。随着React Hooks的引入,函数组件在实际开发中越来越受欢迎,许多场景可以用函数组件和Hooks来取代传统的类组件。
53. 说说对React Hooks的理解?解决了什么问题?
React Hooks是React 16.8版本引入的一组函数,用于在函数组件中添加状态管理和其他React特性。Hooks解决了在函数组件中使用状态和生命周期方法的问题,使得函数组件能够承担更多的责任,替代了传统的类组件的一些用法。
Hooks解决的主要问题包括:
-
组件之间状态逻辑复用的困境:在类组件中,共享状态逻辑(如订阅、轮询和防抖等)需要使用高阶组件(Higher-Order Components)或Render Props等模式,导致组件结构复杂。使用Hooks,可以通过自定义Hook来提取并共享状态逻辑,使得组件之间的状态逻辑复用更加简洁和灵活。
-
类组件中的复杂性:在类组件中,处理状态和生命周期方法涉及到多个钩子方法(如
constructor
、componentDidMount
、componentDidUpdate
等),并且方法之间可能存在相互依赖的关系。这导致组件代码难以维护和理解。使用Hooks,可以通过使用useState
和useEffect
等钩子函数,将状态和副作用逻辑分割为更小、更独立的函数,并且无需关心生命周期方法的顺序和依赖关系,提升代码的可读性和可维护性。 -
对于无状态组件(stateless component)的需求:传统的类组件需要将无状态的组件转换为类组件以便更好地处理生命周期方法,这导致了额外的代码和类实例化的开销。使用Hooks,可以在无状态函数组件中方便地添加状态管理和副作用逻辑,使得组件开发更加简洁。
React提供了一些常用的Hooks,如useState
、useEffect
、useContext
、useReducer
等,同时也可以根据需要自定义Hooks。这些Hooks使得在函数组件中管理状态、处理副作用、订阅数据和访问上下文等操作更加方便和一致。使用Hooks还能提高代码的可测试性和可复用性,使得组件更易于理解和维护。
需要注意的是,使用Hooks时需要遵循一些规则,如确保在函数的顶层使用Hooks、在循环、条件判断或嵌套函数中使用Hooks都是不允许的等。此外,Hooks只能在React函数组件中使用,不能在类组件中使用。
54. 说说你对Redux的理解?其工作原理?
Redux是一个用于状态管理的JavaScript库。它提供了一个可预测性的状态容器,用于管理应用程序中的所有状态,并在不同组件之间进行共享和同步。
Redux的工作原理可以概括为以下几个关键概念:
-
Store(存储):Redux通过一个单一的状态树(state tree)来管理整个应用的状态。状态存储在一个称为Store的对象中,其中包含了应用的所有状态数据。
-
Action(动作):动作是一个简单的JavaScript对象,用于描述应用中的某个事件或操作。它必须包含一个
type
字段,表示该动作的类型。其他字段称为有效载荷(payload),可用于传递额外的数据。 -
Reducer(归纳器):Reducer是一个纯函数,接收当前的状态和一个动作,根据动作的类型来更新状态。它规定了如何处理每个动作,根据旧状态和动作返回新的状态。Reducer必须是一个纯函数,不应有副作用,且需要保持唯一来源的数据源。
-
Dispatch(派发):要触发状态变化,需要使用
dispatch
方法发送一个动作到Redux的Store。dispatch
方法接收一个动作对象作为参数,并将动作传递给Reducer进行处理。Reducer根据动作的类型更新状态。 -
Subscribe(订阅):可以通过
subscribe
方法注册一个回调函数,每当状态发生改变时,该回调函数会被触发。这样可以通过订阅来监听状态的变化,以做出相应的响应。
使用Redux的过程如下:
- 创建一个Redux的Store,并传入根Reducer。
- 定义和描述动作对象,以及相关的动作创建函数。
- 创建Reducer来处理各种动作,更新状态并返回新的状态。
- 在组件中使用
dispatch
方法派发动作,触发相应的状态变化。 - 在需要监听状态变化的组件中,使用
subscribe
方法订阅状态的改变,并在回调函数中对状态进行响应操作。
Redux的优点是可以使得状态的管理和变化更可控和可预测。它提供了一个明确的数据流流动方式,使得状态的变化可追踪和调试。同时,Redux也提供了一些中间件和插件来扩展其功能,使得在异步操作和应用调试等方面更加便捷。但是Redux也有一定的学习曲线和使用成本,对于小型简单的应用可能会显得繁琐和冗余。因此,在使用Redux时需要根据项目的规模和需求来权衡是否需要引入Redux。
7.19
55. 说说 React 性能优化的手段有哪些
1、使用纯组件;
2、使用 React.memo 进行组件记忆(React.memo 是一个高阶组件),对 于相同的输入,不重复执行;
3、如果是类组件,使用 shouldComponentUpdate(这是在重新渲染组件之前触发的其中一个生命周期事件)生命周期事件,可以利用此事件来决定何时需要重新渲染组件;
4、路由懒加载;
5、使用 React Fragments 避免额外标记;
6、不要使用内联函数定义(如果我们使用内联函数,则每次调用“render”函数时都会创建一个新的函数实例);
7、避免在Willxxx系列的生命周期中进行异步请求,操作dom等;
8、如果是类组件,事件函数在Constructor中绑定bind改变this指向;
9、避免使用内联样式属性;
10、优化 React 中的条件渲染;
11、不要在 render 方法中导出数据;
12、列表渲染的时候加key;
13、在函数组件中使用useCallback和useMemo来进行组件优化,依赖没有变化的话,不重复执行;
14、类组件中使用immutable对象;
56. vue、react、angular 区别
- Vue是一套用于构建用户界面的渐进式JavaScript框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,方便与第三方库或既有项目整合。与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计。Vue 的核心库只关注视图层,并且非常容易学习,非常容易与其它库或已有项目整合。另一方面,Vue 完全有能力驱动采用单文件组件和Vue生态系统支持的库开发的复杂单页应用。
- React是一个用于构建用户界面的JavaScript库,所有React应用程序的核心都是组件。组件是一个自包含的模块,可以呈现一些输出,组件是可组合的。组件可能在其输出中包含一个或多个其他组件。
- Angular是一个是一个用HTML,CSS和JavaScript / TypeScript构建客户端应用程序的开源Web应用程序框架。,由Google的Angular团队以及个人和公司社区领导。
Vue和React和Angular的区别
1、vue.js更轻量,压缩后大小只有20K+, 但React压缩后大小为44k,Angular压缩后大小有56k,所以对于移动端来说,vue.js更适合;
2、vue.js更易上手,学习曲线平稳,而Angular入门较难,概念较多(比如依赖注入),它使用java写的,很多思想沿用了后台的技术;react需学习较多东西,附带react全家桶。
3、vue.js吸收两家之长,借用了angular的指令(比如v-show,v-hide,对应angular的ng-show,ng-hide)和react的组件化(将一个页面抽成一个组件,组件具有完整的生命周期)
4、vue.js还有自己的特点,比如计算属性
57. 说说你对 TypeScript 的理解?与 JavaScript 的区别
1、类型
typescript:Typescript 是一种强类型化面向对象的编译语言。它是由微软开发的。
javascript:JavaScript是一种轻量级的解释型语言。它是由Netscape推出的。
2、实施端
typescript:Typescript的内部实现不允许在服务器端使用它。它只能在客户端使用。
javascript:JavaScript 可以在客户端和服务器端使用。
3、数据绑定
typescript:为了在代码级别绑定数据,Typescript 使用类型和接口等概念来描述正在使用的数据。
javascript:在JavaScript中没有引入这样的概念。
4、汇编
typescript:用TypeScript编写的代码首先需要编译,然后转换为JavaScript。此转换过程称为转译。
javascript:在JavaScript的情况下不需要编译。
5、模块化编程
typescript:TypeScript支持模块,因此它允许模块化编程。
javascript:JavaScript不支持模块,因此它不允许模块化编程。
6、函数中的可选参数
typescript:在用 Typescript 编写的函数代码中允许任意数量的可选参数。
javascript:JavaScript 不支持可选参数函数。
7、应用方向
typescript:JavaScript 的超集用于解决大型项目的代码复杂性。
javascript:一种脚本语言,用于创建动态网页。
8、发现错误时间
typescript:可以在编译期间发现并纠正错误。
javascript:作为一种解释型语言,只能在运行时发现错误。
58. 说说你对 TypeScript 中泛型的理解?应用场景?
TypeScript是JavaScript的一个超集,它为JavaScript增加了类型系统和编译时类型检查。TypeScript最终会被编译为纯JavaScript,因此,你可以在任何支持JavaScript的环境中运行TypeScript。
TypeScript的设计目的应该是解决JavaScript的痛点:弱类型和没有命名空间,导致很难的模块化,不适合开发大型的程序。另外它还提供了一些语法糖来帮助大家更方便的实践面向对象的编程。
泛型
的本质就是参数化类型,通俗的来说就是所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法的创建中,分别成为泛型类
、泛型接口
、泛型方法
应用场景:
- 大型项目:在大型项目中,TypeScript的类型系统和工具支持能够显著提高开发效率和代码质量。
- 团队合作:在团队开发中,TypeScript的类型注解和接口定义可以作为代码的文档,使得代码更易于理解和维护。
- Web开发:许多流行的前端框架和库,如React、Vue和Angular,都支持TypeScript,使用TypeScript可以提高这些框架的开发体验。
- Node.js开发:TypeScript也适用于Node.js开发,提供了更好的模块系统和类型安全。
59. 说说你对微信小程序的理解?优缺点?
微信小程序是一种基于微信平台的轻量级应用,它提供了一种快速搭建和开发应用的方式,可以在微信客户端内直接运行。以下是我对微信小程序的理解:
-
轻量化:微信小程序采用了精简的开发框架和运行环境,使得小程序的体积小、启动快,用户可以快速访问和使用。
-
无需下载安装:用户可以直接在微信中搜索并打开小程序,无需下载和安装额外的应用程序,降低了用户的使用门槛。
-
跨平台:微信小程序可以在iOS和Android等不同平台的微信客户端内运行,开发者只需要编写一套代码,可以同时适配多个平台。
-
丰富的功能和接口:微信小程序提供了丰富的开发接口,包括界面渲染、数据交互、文件操作、地理位置、摄像头、支付等功能,开发者可以根据需求灵活使用这些接口来构建小程序的各种功能。
-
广泛的应用场景:微信小程序可以用于各种应用场景,例如电商购物、社交通讯、在线支付、新闻阅读、出行导航等,用户可以通过微信中的小程序服务满足各种需求。
-
小程序商店:在微信中有一个小程序商店,用户可以通过搜索、推荐等方式发现和使用小程序,这为开发者提供了推广和获取用户的渠道。
-
开发者生态:微信小程序拥有庞大的开发者生态圈,开发者可以参与到社区中,分享经验、学习新技术,并且可以通过小程序开发获得商业价值。
总的来说,微信小程序为开发者和用户提供了一种快速、方便、丰富多样的应用开发和使用方式,成为了移动互联网应用的重要形态之一。
微信小程序优点:
1、对用户使用上来说,确实方便,要用的时候打开,不用的时候关掉,即用即走。这点比需要下载,还要占用手机内存空间的APP要好。
2、主要的样式代码都封装在微信小程序里面,所以打开速度比普通的H5要快,接近原生APP。
3、可以调用比H5更多的手机系统功能来进行开发,例如GPS定位、录音、拍视频、重力感应等,能开发更丰富的使用场景。
4、在安卓手机上可以添加到手机桌面,看上去跟原生APP差不多,但仅限安卓手机,iphone就不行了。
5、运行速度跟APP差不多,也能做出很多H5不做到的功能,开发成本跟H5差不多,相对来说开发成本比APP要低。
6、开放的入口比较多,除了通过扫码,发送朋友,搜索,附近等常用入口外,还能与公众号关联,群发文章嵌入,公众号菜单链接等
微信小程序缺点:
1、微信小程序只有2M的大小,这样导致无法开发大型一些的小程序。所以目前你会看到很多小程序真的很小很简单。
2、小程序的技术框架还不稳定,开发方法时常有修改,导致短时间内经常要升级维护,或许这能解释为什么小程序只能2M大小,怕部署太大型的项目会出大问题。
3、不能跳转外链网址,所以间接影响了小程序的开放性。
4、不能直接分享到朋友圈,哎呀,少了一个重要的推广方式。
5、需要像APP一样审核上架,这点比HTML5即做即发布要麻烦些。
(扩展)React中常见的Hooks方法有哪些?
-
useState
useState()用于为函数组件引入状态。在useState()中,数组第一项为一个变量,指向状态的当前值。类似this.state,第二项是一个函数,用来更新状态,类似setState
-
useEffect
useEffect()接受两个参数,第一个参数是你要进行的异步操作,第二个参数是一个数组,用来给出Effect的依赖项。只要这个数组发生变化,useEffect()就会执行
-
useRef
相当于class组件中的createRef的作用,ref.current获取绑定的对象
-
useContext
接受context状态树传递的数据内容
-
useReducer
接受reducer函数和状态的初始值作为参数,返回一个数组,其中第一项为当前的状态值,第二项为发送action的dispatch函数
-
userMemo useCallback
useMemo 和 useCallback接收的参数都是一样,第一个参数为回调,第二个参数为要依赖的数据
共同作用:仅仅依赖数据发生变化, 才会调用,也就是起到缓存的作用。useCallback缓存函数,useMemo 缓存返回值。
(扩展)react的优化方案
减少组件的不必要渲染,父组件更新,子组件可以缓存,使用react.Memo,useMemo,useCallback
内联函数会产生一个新的实例对象 ,减少使用
(扩展)useMemo 和 useEffect谁先执行?
useMemo 与 useEffect 作用类似,都会在依赖值改变时重新执行,但 useMemo 有一个缓存的返回值。
因此在组织渲染生命周期中,很自然地会把useMemo放到渲染DOM之前执行,如下:
useMemo => 渲染DOM => useEffect
(扩展) useEffect的第1个参数为啥不能加async
实际上,useEffect
的第一个参数可以是一个async
函数。但是,直接将一个async
函数作为useEffect
的第一个参数并不是一个常见的用法,因为async
函数返回的是一个Promise对象,并不符合useEffect
所需的函数类型。
useEffect
的第一个参数期望是一个函数,它会在组件挂载、更新或卸载时执行。由于async
函数返回的是一个Promise对象,useEffect
无法正确处理这个Promise对象,也无法对其进行状态管理和清理操作。
如果在useEffect
中需要使用async
函数,仍然需要通过包装函数的方式来处理。下面是一个示例:
import { useEffect } from 'react';
function Example() {
useEffect(() => {
const fetchData = async () => {
try {
// 执行异步操作
const response = await fetch('https://api.example/data');
const data = await response.json();
// 处理返回的数据
console.log(data);
} catch (error) {
// 处理错误
console.error(error);
}
};
fetchData();
// 仅在清理操作需要时返回清理函数
return () => {
// 执行清理操作
};
}, []); // 传入空数组作为依赖项
// 其他组件逻辑...
return (
// 组件渲染的内容
);
}
在上述示例中,我们定义了一个名为fetchData
的async
函数,并在useEffect
的回调函数内部调用它。这样我们可以使用async/await
语法来处理异步操作,并通过try/catch
块处理错误。仍然需要注意,在useEffect
的回调函数内部返回一个清理函数,或者省略返回值以避免执行清理操作。
60. 说说你对发布订阅、观察者模式的理解?区别?
发布-订阅模式和观察者模式都是常见的软件设计模式,用于实现对象之间的消息传递和通信。它们之间有一些区别,我将简要介绍它们的概念和区别。
发布-订阅模式(Publish-Subscribe Pattern):
发布-订阅模式是一种消息传递模式,其中消息的发送者(发布者)和接收者(订阅者)彼此解耦。在这种模式中,消息的发布者将消息发送到一个中间代理,通常称为消息队列或事件总线。然后,订阅者向这个中间代理注册,以接收感兴趣的消息。当发布者发布消息时,中间代理将消息推送给所有订阅者。
发布-订阅模式的特点是可以实现一对多的通信,即一个发布者可以同时发送消息给多个订阅者。它提供了一种松耦合的方式,使得发布者和订阅者之间不需要直接相互引用。
观察者模式(Observer Pattern):
观察者模式是一种对象行为模式,其中一组对象(观察者)在另一个对象(主题)的状态变化时得到通知并作出相应的响应。主题维护一个观察者列表,当主题的状态发生变化时,它会向所有观察者发送通知。
观察者模式的特点是一对多的依赖关系,即一个主题可以有多个观察者。当主题的状态变化时,所有观察者都会收到通知并更新自己的状态。
区别:
尽管发布-订阅模式和观察者模式都涉及到对象间的通信,但它们之间有一些关键区别:
-
耦合性不同:发布-订阅模式中发布者和订阅者之间解耦,它们不知道彼此的存在。观察者模式中,主题和观察者是紧密耦合的,它们互相知道对方的存在。
-
对象关系不同:在发布-订阅模式中,发布者和订阅者之间通过一个中间代理进行通信。而在观察者模式中,主题和观察者之间直接通信。
-
通知方式不同:在发布-订阅模式中,消息通常是异步的,发布者不需要等待订阅者处理消息。而在观察者模式中,主题通常以同步方式通知观察者,观察者必须立即作出响应。
-
使用场景不同:发布-订阅模式更适用于松散耦合的场景,特别是在需要一对多通信的情况下。观察者模式更适用于紧密耦合的场景,例如需要维护对主题状态的实时更新。
总的来说,发布-订阅模式和观察者模式都提供了一种对象间的通信机制,但它们在耦合性、对象关系、通知方式和使用场景等方面有所不同。选择使用哪种模式取决于具体的需求和设计目标。
61. 项目做过哪些性能优化
2、压缩源码和图片
JavaScript文件源代码可以采用混淆压缩的方式,CSS文件源代码进行普通压缩,JPG图片可以根据具体质量来压缩为50%到70%,PNG可以使用一些开源压缩软件来压缩,比如24色变成8色、去掉一些PNG格式信息等。
3.源码优化
1、代码模块化,咱们可以把很多常用的地方封装成单独的组件,在需要用到的地方引用,而不是写过多重复的代码,每一个组件都要明确含义,复用性越高越好,可配置型越强越好,包括咱们的css也可以通过less和sass的自定义css变量来减少重复代码。
2、for循环设置key值,在用v-for进行数据遍历渲染的时候,为每一项都设置唯一的key值,为了让Vue内部核心代码能更快地找到该条数据,当旧值和新值去对比的时候,可以更快的定位到diff。
3、Vue路由设置成懒加载,当首屏渲染的时候,能够加快渲染速度。
4、更加理解Vue的生命周期,不要造成内部泄漏,使用过后的全局变量在组件销毁后重新置为null。
5、可以使用keep-alive,keep-alive是Vue提供的一个比较抽象的组件,用来对组件进行缓存,从而节省性能。
4、选择合适的图片格式
如果图片颜色数较多就使用JPG格式,如果图片颜色数较少就使用PNG格式,如果能够通过服务器端判断浏览器支持WebP,那么就使用WebP格式和SVG格式。
5.项目打包优化
1、修改vue.config.js中的配置项,把productionSourceMap设置为false,不然最终打包过后会生成一些map文件,如果不关掉,生成环境是可以通过map去查看源码的,并且可以开启gzip压缩,使打包过后体积变小。
2、使用cdn的方式外部加载一些资源,比如vue-router、axios等Vue的周边插件,在webpack.config.js里面,externals里面设置一些不必要打包的外部引用模块。然后在入门文件index.html里面通过cdn的方式去引入需要的插件。
3、减少图片使用,因为对于网页来说,图片会占用很大一部分体积,所以,优化图片的操作可以有效的来加快加载速度。可以用一些css3的效果来代替图片效果,或者使用雪碧图来减少图片的体积。
4、按需引入,咱们使用的一些第三方库可以通过按需引入的方式加载。避免引入不需要使用的部分,无端增加项目体积。比如在使用element-ui库的时候,可以只引入需要用到的组件。
6、合并静态资源
包括CSS、JavaScript和小图片,减少HTTP请求。有很大一部分用户访问会因为这一条而取得最大受益
7、开启服务器端的Gzip压缩
这对文本资源非常有效,对图片资源则没那么大的压缩比率。
8、使用CDN
或者一些公开库使用第三方提供的静态资源地址(比如jQuery、normalize.css)。一方面增加并发下载量,另一方面能够和其他网站共享缓存。
9、延长静态资源缓存时间
这样,频繁访问网站的访客就能够更快地访问。不过,这里要通过修改文件名的方式,确保在资源更新的时候,用户会拉取到最新的内容。
10、把CSS放在页面头部,把JavaScript放在页面底部
这样就不会阻塞页面渲染,让页面出现长时间的空白。
62. 描述浏览器的渲染过程,DOM树和渲染树的区别
浏览器的渲染过程可以分为以下几个步骤:
-
构建DOM树:浏览器解析HTML文档,根据HTML标记构建DOM树。DOM树是由DOM节点组成的树形结构,每个节点代表文档中的一个元素、文本节点或注释。
-
构建CSSOM树:浏览器解析CSS样式表,根据CSS规则构建CSSOM树。CSSOM树是由CSS规则组成的树形结构,它描述了文档中所有元素的样式信息。
-
合并DOM树和CSSOM树,生成渲染树:将DOM树和CSSOM树合并,生成渲染树(Render Tree)。渲染树只包含需要显示的元素和相关的样式信息,它是一种用于渲染页面的树形结构。
在构建渲染树时,浏览器会忽略不需要显示的元素(如head标签内的内容),以及通过CSS属性
display: none;
隐藏的元素。 -
布局(Layout):渲染树构建完成后,浏览器根据渲染树的信息计算每个元素在屏幕上的位置和大小,即进行布局或排版。布局过程还包括计算盒模型、文本的换行等。
-
绘制(Painting):根据渲染树和布局信息,浏览器将每个元素绘制在屏幕上,形成最终的页面呈现。
DOM树和渲染树之间的区别主要在于内容和结构上:
-
DOM树(Document Object Model)是HTML文档的抽象表示,包含了HTML文档的所有节点,包括元素、文本和注释等。它是由浏览器解析HTML代码生成的树形结构,反映了文档的逻辑结构。
-
渲染树(Render Tree)是用于渲染页面的树形结构,是DOM树和CSSOM树的结合物。渲染树只包含需要显示的元素和相关的样式信息,它是由浏览器构建的,用于进行页面的布局和绘制。
渲染树比DOM树更符合实际页面的呈现情况,它不包括不需要显示的节点(如display: none;
)和不可见的节点(如head标签内的内容)。通过构建渲染树,浏览器可以更高效地进行页面布局和绘制,提高渲染性能。
63. 你认为什么样的前端代码是好的
好的前端代码应具备以下几个方面的特点:
1. 可读性(Readability):代码应该清晰、易读,易于理解和维护。合理的命名、注释和缩进都是提高代码可读性的重要因素。
2. 可维护性(Maintainability):良好的前端代码应易于维护和扩展。模块化、组件化的设计和代码结构可以提高代码的可维护性,使其易于修改和重构。
3. 可复用性(Reusability):通过代码的抽象和封装,可以使代码更具可复用性。可复用的代码可以减少重复劳动,提高开发效率。
4. 性能优化(Performance):好的前端代码应具备良好的性能。这包括优化加载速度、减少页面渲染时间、合理使用资源等方面的优化策略。
5. 可靠性(Reliability):前端代码应具备稳定可靠的特性,能够正确处理各种边界情况和异常情况。错误处理、数据验证和异常处理等方面的考虑都是提高代码可靠性的重要因素。
6. 可扩展性(Scalability):好的前端代码应能够适应未来的需求变化和扩展。通过良好的架构设计和可扩展的代码结构,可以方便地添加新功能或进行系统的扩展。
7. 兼容性(Compatibility):前端代码应考虑在不同浏览器和设备上的兼容性。尽量遵循Web标准并进行适当的兼容性测试,以确保在不同环境下的正常工作。
总之,好的前端代码应该注重可读性、可维护性、可复用性、性能优化、可靠性、可扩展性和兼容性等方面的考虑。这些特点可以使代码更具质量,提高开发效率和用户体验。同时,团队内的编码规范和代码审查机制也是保证代码质量的重要手段。
64. 从浏览器地址栏输入url到显示页面的步骤
从浏览器地址栏输入URL到显示页面,大体上经历以下几个步骤:
-
URL解析:浏览器会解析输入的URL,并将其分解成协议、主机、端口、路径和查询参数等组成部分。
-
DNS解析:浏览器将主机名(域名)发送给DNS服务器,进行域名解析,获取对应的IP地址。
-
建立TCP连接:浏览器通过HTTP或HTTPS协议与服务器建立TCP连接。这涉及到三次握手,即与服务器进行信息交换,确保连接的可靠性和稳定性。
-
发起HTTP请求:浏览器发送HTTP请求给服务器,包含请求的方法(GET、POST等)、路径、查询参数、头部信息等。请求可以携带Cookie、用户认证等相关信息。
-
服务器处理请求:服务器接收到请求后,会根据请求的路径、参数等,执行相应的处理逻辑。这可能涉及到动态生成页面、查询数据库等操作。
-
返回HTTP响应:服务器生成HTTP响应,包括状态码、响应头部、响应体等。响应体部分一般会包含HTML、CSS、JavaScript等用于组成页面的资源内容。
-
浏览器接收响应:浏览器接收到服务器返回的HTTP响应。
-
解析HTML并构建DOM树:浏览器解析响应中的HTML内容,构建DOM树,表示页面的结构。
-
解析CSS并构建CSSOM树:浏览器解析响应中的CSS样式,构建CSSOM树,表示页面的样式信息。
-
渲染页面:根据DOM树和CSSOM树,浏览器进行页面布局(Layout)和绘制(Painting)的过程,将页面的内容渲染到屏幕上。
-
执行JavaScript:如果HTML中包含了JavaScript代码,浏览器会执行这些脚本,可以修改页面的内容、样式和行为。
-
完成页面加载:当页面中的所有资源(如图片、媒体文件等)都加载完成后,页面加载过程完成,页面呈现给用户。
以上是一个简化的描述,实际的加载过程还可能包括缓存机制、重定向、安全检查等步骤。每个浏览器可能有略微不同的实现细节,但整体流程大致相似。
65. http 请求报文响应报文的格式
一、 HTTP请求报文主要由请求行、请求头、空行、请求正文
请求行:请求行是由请求方法字段、URL字段、和HTTP协议版本字段三部分组成,它们用空格分隔。比如GET /data/info.html HTTP/1.1,方法字段就是HTTP使用的请求方法。
请求头: 在请求头中存在cookie、客户端的主机名和端口等信息
空行:它的作用是通过一个空行,告诉服务器请求头部到此为止
请求体:可选部分,比如GET请求就没有请求正文
二、 HTTP响应报文主要由状态行、响应头部、响应正文
状态行:状态行格式分别为协议版本、状态码、状态码描述,之间由空格分隔。其中状态码由三位数字组成,第一个数字定义了响应的类别,且有物种可能取值
1XX:指示信息–表示请求已接受,继续处理
2XX:成功–表示请求已被成功接收、理解
3XX:重定向
4XX:客户端错误
5XX:服务端错误
响应头部:与请求头部类似,为响应报文添加了一些附加信息,常见的有Server服务器的应用程序的名称和版本,Content-Type响应正文的类型等。
响应正文:服务器返回给服务端的响应数据
HTTP请求报文和响应报文的基本格式如下所示:
HTTP请求报文的格式:
<请求行>
<请求头部>
<空行>
[请求体]
请求行包括请求方法、请求URL和HTTP协议版本。
请求头部包括一系列的键值对,每行一个键值对,用冒号进行分隔。
空行用于分隔头部和请求体。
请求体是可选的,主要用于携带数据,比如POST请求中的表单数据或请求的消息体。
HTTP响应报文的格式:
<状态行>
<响应头部>
<空行>
[响应体]
状态行包括HTTP协议版本、状态码和状态文本。
响应头部也是一系列的键值对,每行一个键值对,用冒号进行分隔。
空行用于分隔头部和响应体。
响应体是可选的,主要包含响应的实际内容(HTML、JSON、文件等)。
以下是一个示例,展示了一个简单的HTTP请求报文和响应报文的格式:
HTTP请求报文的示例:
GET /path/to/resource HTTP/1.1
Host: example
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Connection: keep-alive
HTTP响应报文的示例:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1234
Date: Sat, 24 Jul 2023 00:00:00 GMT
Server: Apache
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
这只是一个简单的示例,实际的HTTP请求报文和响应报文可能包含更多的请求头部、响应头部以及具体的请求体和响应体内容。具体的报文格式也可能会因HTTP协议版本的不同而有所差异。
66. Token cookie session 区别
Token、Cookie和Session是在Web应用程序中用于实现身份验证和会话管理的不同机制。
Token(令牌):
- Token是一串代表用户身份信息的字符串,通常是由服务器生成,然后返回给客户端。客户端将Token存储在本地,通常使用LocalStorage或者Cookie来保存。在每次请求中,客户端将Token添加到请求头中或作为参数发送给服务器。
- Token是无状态的,即服务器不需要保存Token的状态信息。服务器只需对Token进行验证,确认其是否有效,然后基于Token进行身份认证和授权操作。常见的Token机制有JWT(JSON Web Token)和OAuth等。
Cookie(HTTP Cookie):
- Cookie是由服务器发送给浏览器的一小段数据。浏览器会将Cookie存储在本地,并在每次请求中自动将Cookie发送给服务器。服务器可以使用Cookie来存储用户的会话信息,如用户ID、购物车状态等。
- Cookie存储在浏览器的本地文件中,并有不同的属性、过期时间和作用域。Cookie可以在发送请求时附加在请求头中,也可以被JavaScript脚本访问和操作。
Session(会话):
- 会话是指用于跟踪用户在应用程序中的交互状态的机制。服务器为每个用户创建一个会话,并为该会话存储用户的状态信息。在每个用户请求中,服务器会验证会话并对用户进行身份认证和授权。通常,服务器会将会话ID存储在Cookie或URL参数中,并在后续请求中使用该会话ID来关联用户的会话。
- 会话通常依赖于Cookie来存储会话ID,但也可以通过在URL参数中包含会话ID来实现会话跟踪。
区别和应用场景:
- Token适用于分布式系统和无状态(stateless)服务器,适合于跨多个服务和域的身份验证。
- Cookie适用于客户端和服务器之间的会话管理,可以在浏览器上存储会话状态信息,并与每个请求一起自动发送给服务器。
- Session适用于服务器端会话管理,服务器负责存储和管理会话状态信息,适合于需要对会话进行复杂操作和处理的场景。
选择使用哪种机制取决于具体的需求和情况。例如,如果需要一个无状态的、跨域和跨服务的身份验证机制,可以使用Token;如果需要在客户端和服务器之间进行会话管理,可以使用Cookie;如果需要在服务器端管理和处理会话状态,可以使用Session。
67. CORS跨域的原理
CORS(跨域资源共享)是一种用于在浏览器上进行跨域通信的机制。它允许在一个域中的Web应用程序向另一个域发送跨域请求,并获取到该域下的资源。
以下是 CORS 跨域的基本工作原理:
-
浏览器发送跨域请求: 当一个通过 JavaScript 创建的 XMLHttpRequest 或 Fetch API 发起的请求,且请求的目标与当前页面所在的域、协议、端口不一致时,就会触发跨域请求。
-
发送预检请求(OPTIONS): 浏览器会自动发起一个 OPTIONS 方法的请求,向目标域发送预检请求,以确定是否允许实际的跨域请求。
-
服务端返回CORS响应头: 目标域的服务器接收到预检请求后,会根据请求中的 Origin、Method、Headers 等信息来确定是否允许跨域访问。服务器通过在响应头中添加一些特定的 CORS 相关的头部信息来表示是否允许跨域访问。
-
浏览器校验CORS响应头: 浏览器接收到服务端的响应后,会检查响应中的 CORS 相关头部信息,包括 Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers 等字段。如果响应中的头部信息表示允许跨域访问,则浏览器会继续发送实际的跨域请求。
-
发送实际跨域请求: 如果预检请求的响应头部信息中表示允许跨域访问,那么浏览器会继续发送真正的跨域请求,请求将携带实际的数据或资源需求。
-
服务器响应请求: 目标域的服务器接收到跨域请求后,根据请求的内容进行相应的处理,并返回响应给浏览器。
需要注意的是,跨域请求必须满足目标域的服务器设置了 CORS 相关的响应头信息,才能实现跨域访问。如果服务器未设置相应的响应头信息,浏览器将不允许跨域请求,并在控制台中报错。
CORS 的原理是通过浏览器和服务器之间的通信,在请求和响应的过程中进行协商和验证,以确保有效且安全地进行跨域通信。
68. 什么是MVVM
MVVM(Model-View-ViewModel)是一种用于构建用户界面的软件架构模式,它将用户界面、业务逻辑和数据分离,并提供了一种可维护和可扩展的方式来管理和更新用户界面。
MVVM 模式由以下几个核心组件组成:
-
Model(模型): 模型表示应用程序中的数据和业务逻辑。它是应用程序的数据源,负责从数据源获取数据、进行数据操作、验证和处理。
-
View(视图): 视图是用户界面的可视部分,通常由HTML、XML或其他模板语言来表示。视图的主要责任是将模型中的数据呈现给用户,并显示用户交互的元素。
-
ViewModel(视图模型): 视图模型是连接模型和视图的中间层。它通过暴露公共属性和命令(方法)来公开视图所需的数据和行为。视图模型负责从模型获取数据,并将其转换为视图可以使用的格式。它还负责处理用户输入和视图中的事件,并对模型进行相应的操作。
在 MVVM 中,视图和视图模型之间通过数据绑定来实现双向通信。当模型中的数据发生变化时,视图模型会更新视图,将最新的数据呈现给用户。当用户与视图交互时,视图模型会捕获用户输入,并相应地更新模型中的数据。
MVVM 的优势包括:
- 分离关注点,促进代码的可维护性和可测试性。
- 提供了一种优雅的方式来处理视图和数据之间的两向绑定。
- 简化了前端开发过程,使开发人员可以更专注于业务逻辑而不是DOM操作。
- 支持代码的重用和组件化开发。
MVVM 模式广泛应用于许多前端框架和库,如Vue.js 和 Knockout.js,它们提供了对MVVM架构的内建支持。
69. 说说你对版本管理的理解?常用的版本管理工具有哪些?
版本管理是一种用于追踪和管理软件开发过程中代码的变化和演变的实践方法。它允许开发团队保存、比较和恢复代码的不同版本,以便有效地协作、追踪问题、管理变更和部署软件。
版本管理的核心目标包括:
-
版本控制: 跟踪和记录文件的变化,包括添加、修改和删除操作,以便能够回溯和比较不同版本之间的差异。
-
协作和合并: 允许开发人员同时在同一代码库上工作,并能够合并他们的工作成果。版本管理工具可以自动检测和解决代码冲突,确保多人协作的平稳进行。
-
版本回滚: 允许开发人员在出现问题或错误时回滚到先前的版本,恢复到一个可用的状态。
-
分支管理: 允许创建分支(Branches),即在代码库中创建独立的开发线,以便同时进行不同的开发工作,分支之间可以独立进行合并和管理。
常用的版本管理工具包括:
-
Git: Git 是目前最流行的分布式版本管理工具,提供了强大的分支管理、版本控制和协作特性。通过 Git 可以轻松地在本地和远程仓库之间同步代码。
-
Subversion(SVN): SVN 是集中式的版本管理系统,其中心化的代码版本库存储在单一位置。开发者可以从中获取最新的代码,提交更改,并且可以在历史版本之间切换。
-
Mercurial: Mercurial 是另一种分布式版本管理工具,与 Git 类似。它提供了简单的命令和易于使用的界面,适合小型团队和个人项目。
-
Perforce: Perforce 是一个商业化的版本管理工具,可供大型项目和团队使用。它提供了高性能、可定制的版本控制和协作特性。
这些版本管理工具都具有不同的特点和适用场景,开发者可以根据项目要求和团队协作方式选择合适的版本管理工具。
70. 说说你对Git的理解?
Git 是一个分布式版本控制系统,它能够有效地追踪和管理软件开发过程中的代码变化。以下是对 Git 的理解:
-
分布式版本控制系统: Git 是一种分布式版本控制系统,每个开发者都可以在本地维护完整的代码库副本。这意味着开发者可以在没有网络连接的情况下进行工作,并且可以在多个地方进行备份和同步代码。
-
代码追踪和管理: Git 能够跟踪和管理代码的变化,包括添加、修改和删除操作。它记录了完整的修改历史,允许开发者轻松地回顾和比较不同版本之间的差异。
-
分支管理: Git 提供了强大的分支管理功能,开发者可以创建和切换不同的分支来独立开发不同的功能或修复问题。分支可以同时进行合并和管理,这使得团队的协作更加灵活和高效。
-
远程仓库与协作: Git 支持与远程仓库的交互,开发者可以将本地的代码同步到远程仓库,也可以从远程仓库获取最新的代码。多个开发者可以同时在同一个代码库上进行工作,Git 提供了解决代码冲突的机制,确保协作的平稳进行。
-
快速和轻量级: Git 的设计目标之一是快速和轻量级。由于每个开发者都拥有完整的代码副本,并且 Git 只跟踪文件的变化而不是文件本身,因此代码的提交、合并和切换操作非常快速,并且占用较少的磁盘空间。
Git 是广泛应用的版本控制工具,被许多开发者和开发团队使用。它提供了强大的功能和灵活性,可以帮助开发者更好地进行版本管理、协作和追踪代码变化。
71. 说说Git常用的命令有哪些
Git 是一个功能强大的版本控制工具,具有许多常用的命令。以下是 Git 常用的命令示例:
-
git init: 初始化一个新的 Git 仓库。
-
git clone [url]: 克隆一个远程仓库到本地。
-
git add [文件名]: 将文件添加到暂存区。
-
git commit -m “提交信息”: 提交暂存区的改动到本地仓库。
-
git status: 查看当前仓库的状态,显示文件的修改、新增和删除等信息。
-
git diff: 显示当前工作树和暂存区之间的差异。
-
git branch: 显示本地分支列表。
-
git checkout [分支名]: 切换到指定分支。
-
git merge [分支名]: 将指定分支的改动合并到当前分支。
-
git pull: 从远程仓库拉取最新代码并合并到当前分支。
-
git push: 将本地仓库的改动推送到远程仓库。
-
git log: 查看提交日志。
-
git revert [commit hash]: 撤销指定提交。
-
git reset [commit hash]: 将 HEAD 指针回退到指定提交。
-
git stash: 暂存当前工作树的改动,以便切换到其他分支做其他工作。
-
git tag: 列出所有标签。
-
git remote -v: 显示远程仓库的详细信息。
-
git config: 配置 Git,例如设置用户名、邮箱等。
这只是 Git 命令的一小部分,Git 提供了大量的命令和选项,用于管理代码的版本、分支、提交等。开发者可以通过 git --help
或 git [command] --help
查看更详细的帮助文档。熟练掌握这些常用的 Git 命令将能够更好地使用和管理 Git 仓库。
72. 说说 git 发生冲突的场景?如何解决?
Git 在多人协作或分支合并的过程中,可能会出现冲突的情况。以下是几种可能会导致冲突的场景以及解决方法:
- 相同文件的并行修改: 当多个开发者同时修改同一个文件的相同位置时,Git 无法自动确定应该保留哪个修改。
解决方法:开发者之间需要进行沟通,协商并手动解决冲突。可以使用以下步骤:
- 运行
git pull
拉取最新的代码。 - Git 会将冲突标记出来,开发者需要手动编辑文件,解决冲突。
- 解决完所有冲突后,运行
git add
将解决冲突的文件标记为已解决。 - 最后运行
git commit
提交解决冲突的版本。
- 分支合并冲突: 当合并分支时,如果两个分支都对同一文件的相同位置进行了修改,Git 无法自动合并这些改动。
解决方法:和上述场景一样,需要手动解决冲突。可以使用以下步骤:
- 运行
git pull
拉取最新的代码,并切换到要合并的分支。 - 运行
git merge [目标分支]
合并目标分支到当前分支。 - Git 会将冲突标记出来,开发者需要手动编辑文件,解决冲突。
- 解决完所有冲突后,运行
git add
将解决冲突的文件标记为已解决。 - 最后运行
git commit
提交合并冲突的版本。
- 删除文件的冲突: 当一个分支删除了一个文件,而另一个分支修改了同一个文件,Git 无法自动确定处理方法。
解决方法:开发者之间需要沟通,协商并手动解决冲突。可以选择保留文件或者保留修改。
总的来说,解决冲突的关键是合理的沟通和协调。通过手动编辑冲突文件,解决冲突后再提交修复后的版本,确保代码的一致性和正确性。在解决冲突时,可以使用 Git 提供的工具和命令来辅助,如 git status
、git diff
等,以便更好地查看和处理冲突。
(73)使用历史路由模式打包上线项目 强制刷新页面会出现404,为啥?
因为历史路由模式在页面刷新后会向服务器发送请求,都不是找index.html,服务器是没有路由对应的文件的,是在js中有的
(74)vue2与vue3的区别
diff算法:
-
Vue 2 的 diff 算法:
Vue 2 使用的是经典的 Virtual DOM diff 算法,也称为双端比较算法。算法的核心思想是将新旧虚拟 DOM 树进行逐层对比,找出差异,并最小化对实际 DOM 的操作。它遵循了以下策略:
- 从根节点开始对比,对整棵虚拟 DOM 树进行深度优先遍历。
- 对比同级节点,找出需要更新的节点,并进行相应处理:
- 如果节点类型不同,则直接替换;
- 如果节点类型相同,但节点对应的 key 不同,则认为是不同的节点,需要替换;
- 如果节点类型和 key 都相同,但是节点的内容或属性有变化,则更新节点。
这种算法的缺点是,无法跳过精确比较,需要对整个虚拟 DOM 树进行遍历和比较,即使只有一个小部分发生了变化。
-
Vue 3 的 diff 算法:
Vue 3 使用了一种优化过的 diff 算法,称为基于 Proxy 的观察机制。它基于一种新的响应式系统,利用 JavaScript 的 Proxy 对象来追踪数据的变化,并在事件循环结束时,按需更新视图。
使用 Proxy 可以实现精确的跟踪和对比,只在数据发生变化时才会触发相应的更新操作。这种算法的优势在于可以避免不必要的比较和更新,提高了性能。
此外,Vue 3 还使用了静态分析和编译优化等技术,生成更高效的渲染函数。它通过编译阶段的优化,消除了不必要的虚拟 DOM 对比,使得整个渲染过程更快速和高效。
总结来说,Vue 2 使用经典的 Virtual DOM diff 算法,对整个虚拟 DOM 树进行遍历和比较,性能相对较低。而 Vue 3 则使用了基于 Proxy 的观察机制,可以精确跟踪数据变化,避免不必要的比较和更新,性能更高。此外,Vue 3 还通过编译优化等技术,进一步提升了渲染性能。
响应式:
Vue2.x 响应式原理
当创建 Vue 实例时,vue 会遍历 data 选项的属性,利用 Object.defineProperty 为属性添加 getter 和 setter 对数据的读取进行劫持(getter 用来依赖收集,setter 用来派发更新),并且在内部追踪依赖,在属性被访问和修改时通知变化。
每个组件实例会有相应的 watcher 实例,会在组件渲染的过程中记录依赖的所有数据属性,之后依赖项被改动时,setter 方法会通知依赖与此 data 的 watcher 实例重新计算(派发更新),从而使它关联的组件重新渲染。
一句话总结:
vue.js 采用数据劫持结合发布-订阅模式,通过 Object.defineproperty 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调
为了减少更新的次数和提高性能,Vue 会将数据的变化进行批量处理。在同一个事件循环内,所有的数据变化都会被放入一个队列中,等到下一个事件循环时,Vue 会遍历这个队列,执行相应的更新操作。
Vue3.x 响应式原理
Vue 3.x使用了ES6中的Proxy对象来跟踪对象的变化。通过使用Proxy,Vue能够在对象上拦截对属性的访问、赋值和删除操作,从而实现响应式的数据追踪 , 只跟踪访问过的属性,而不是对象上的所有属性。这样可以避免对一些不必要的属性进行监听,提高性能。 与Vue 2.x不同,Vue 3.x的响应式系统默认情况下会进行深度追踪。也就是说,当嵌套对象或数组中的属性发生变化时,也能够触发响应式更新。
当属性被修改时,会触发一个事件。通过监听该事件,可以进行下一步的逻辑处理和更新。
vue2的响应式为啥会出现数据更新 视图不更新的问题
vue3相比于vue2的提升
Proxy 相比于 defineProperty 的优势
(1)直接监听对象:Proxy 可以直接监听整个对象而不是单个属性。你只需要使用一个 Proxy 包装整个对象,就能够捕捉和响应对该对象的所有操作,而不需要逐个定义和监听属性。而 defineProperty 需要逐个定义属性,耗费时间和精力。
(2)功能丰富的拦截方法:Proxy 提供了一系列的拦截方法(如get、set、deleteProperty、has、apply等),可以在对对象进行操作的时候进行拦截和自定义行为。这使得我们能够更加灵活地控制对象的行为,并实现更复杂的操作逻辑。而 defineProperty 只能监听属性的读取和修改行为,没有提供像 Proxy 这样丰富的拦截方法。
(3)支持新增属性和删除属性:使用 Proxy,我们可以拦截对象的set和deleteProperty操作,从而能够捕捉对属性的新增和删除操作,并在需要时执行自定义的逻辑。而 defineProperty 无法监听属性的新增和删除操作。
(4)更好的性能:相比于 defineProperty,Proxy 通常会有更好的性能。由于 Proxy 是在运行时动态代理操作的,底层实现使用了更高效的底层机制,所以 Proxy 比 defineProperty 通常更快。而 defineProperty 的实现是静态的,需要逐个定义属性,因此在大规模属性定义时可能影响性能。
vue3是组合式API,vue2是选项式API:
Options API,即大家常说的选项API,即以vue为后缀的文件,通过定义methods,computed,watch,data等属性与方法,共同处理页面逻辑,当组件变得复杂,导致对应属性的列表也会增长,这可能会导致组件难以阅读和理解。当我们在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块
在 Vue3 Composition API 中,组件根据逻辑功能来组织的,一个功能所定义的所有 API 会放在一起(更加的高内聚,低耦合)
即使项目很大,功能很多,我们都能快速的定位到这个功能所用到的所有 API,不用跳来跳去,此外,Composition API中见不到this的使用,减少了this指向不明的情况
(75)JS 数组和对象的遍历方式,以及几种方式的比较
1.for循环
使用for循环是最基本的遍历方式之一。对于数组,可以通过索引来访问每个元素;对于对象,可以使用for-in循环来遍历属性。
// 遍历数组
const array = [1, 2, 3, 4, 5];
for (let i = 0; i < array.length; i++) {
console.log(array[i]);
}
// 遍历对象
const object = { a: 1, b: 2, c: 3 };
for (let key in object) {
console.log(key + ': ’ + object[key]);
}
2.forEach方法
数组提供了forEach方法,可以用于遍历数组的每个元素。这是一种更简洁的方式,可以使用回调函数对每个元素执行相应操作。
// 遍历数组
const array = [1, 2, 3, 4, 5];
array.forEach((element) => {
console.log(element);
});
// 注意:对象没有提供forEach方法,只能用于数组遍历。
3.for…of循环
for…of循环是ES6引入的一种遍历方式,用于遍历可迭代对象(如数组、字符串等)。它可以更简洁地遍历数组的元素。
// 遍历数组
const array = [1, 2, 3, 4, 5];
for (let element of array) {
console.log(element);
}
// 注意:对象不是可迭代对象,不能使用for…of循环遍历。 比较:
·for循环是最基本的遍历方式,适用于数组和对象的遍历,但代码相对冗长。
·forEach方法是数组特有的方法,语法简洁,但无法用于对象的遍历。
·for…of循环适用于数组遍历,语法简洁,但无法用于对象的遍历。
·对于对象的遍历,for-in循环是一种常见的方式,但需要注意的是它会遍历对象的所有可枚举属性,包括继承自原型链的属性。
hash路由上线出现问题