大前端高频面试题详解 确定不看看?(持续更新)

HTML5

1.如何理解HTML5结构语义化?

 段落用 p 标签,标题用 h 系列标签,边栏用 aside 标签,主要内容用 main 标签

对开发者:

  • 便于团队的开发和维护
  • 在没有加载 CSS 的情况下也能呈现较好的内容结构与代码结构,易于阅读 

 对浏览器:

  •  有利于 SEO,搜索引擎的爬虫依赖于标签来确定上下文和各个关键字的权重
  • 方便其他设备的解析(如屏幕阅读器、盲人阅读器等),利于无障碍阅读,提高可访问性。

 2.HTML5的新特性?

HTML5 现在已经不是 SGML(Standard Generalized Markup Language,标准通用标记语言) 的子集,1️⃣主要是关于图像,位置,存储,多任务等功能的增加(语义化标签):

  • 用于媒介回放的 video 和 audio 元素
  • 本地离线存储 localStorage 长期存储数据,浏览器关闭后数据不丢失
  • sessionStorage 的数据在浏览器关闭后自动删除
  • 语意化更好的内容元素,比如 article、footer、header、nav、section
  • 表单控件,calendar、date、time、email、url、search
  • 新的技术webworker(子线程), websocket(即时通讯), Geolocation(地理位置信息)

2️⃣移除的元素:

  • 纯表现的元素: basefont,big,center,font,s,strike,tt,u。
  • 对可用性产生负面影响的元素: frame,frameset,noframes

3️⃣支持HTML5新标签:

  •  IE8/IE7/IE6支持通过 document.createElement 方法产生的标签
  • 可以利用这一特性让这些浏览器支持HTML5新标签
  • 浏览器支持新标签后,还需要添加标签默认的样式

4️⃣直接使用成熟的框架、比如HTML5shim 

HTML5Shiv(或称为 HTML5Shim)是一个 JavaScript 文件,用于解决旧版 Internet Explorer(IE6-IE8)不支持 HTML5 新元素的问题。在 IE6-IE8 浏览器中,如果网页中出现了 HTML5 新元素,如 article、section、header、footer 等标签,这些元素会被视为未知元素并无法正常显示。HTML5Shiv 可以通过动态向 DOM 中添加所需元素来解决这个问题。

该工具由 Remy Sharp 创建,并于 2011 年发布。它的代码非常简单,只有几十行,核心实现原理即通过创建将 HTML5 新元素以 display:block; 显示的 HTML 元素,来触发 IE 的默认样式规则,从而让这些新元素可以正常显示。

使用 HTML5Shiv 的方法也很简单,只需要在 head 标签中添加如下代码即可:

<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<![endif]-->

 该段代码的作用是判断当前浏览器是否为 IE9 及以上版本,如果是,则不加载 HTML5Shiv 文件,避免产生额外的性能消耗。如果是 IE6-IE8,则加载 HTML5Shiv 文件,以确保 HTML5 新元素能够正常显示。目前,HTML5Shiv 已经成为了 HTML5 开发的必备工具之一。

补充:如何区分HTML5: DOCTYPE声明新增的结构元素功能元素 

HTML5 引入了一些新的结构元素和功能元素,通过 DOCTYPE 声明可以区分文档使用的 HTML 版本,从而正确地解释文档中使用的元素和属性。 


 3.请描述一下 cookies,sessionStorage 和 localStorage 的区别?

  • cookie是网站为了标示用户身份而储存在用户本地终端 (Client Side) 上的数据 (通常经过加密)。
  • cookie数据始终在同源的http请求中携带 (即使不需要),记会在浏览器和服务器间来回传递
  • sessionStorage和localStorage 不会自动把数据发给服务器,仅在本地保存
  • 存储大小:
    • cookie 数据大小不能超过4k
    • sessionstorage和1ocalstorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
  • 有期时间:
    • localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据
    • sessionStorage 数据在当前浏览器窗口关闭后自动删除(存私密的信息)
    • cookie 设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭

4.浏览器的渲染机制一般分为几个步骤?

  • 处理 HTML 并构建 DOM 树。
  • 处理 CSS构建 CSSOM 树。
  • 将 DOM 与 CSSOM 合并成一个渲染树
  • 根据渲染树来布局,计算每个节点的位置。
  • 调用 GPU 绘制,合成图层,显示在屏幕上。


⭕注意: 

  • 在构建 CSSOM 树时,会阻塞染,直至 CSSOM 树构建完成。并且构建 CSSOM 树是一个十分消耗性能的过程,所以应该尽量保证层级扁平,减少过度层叠,越是具体的 CSS 选择器,执行速度越慢
  • 当 HTML解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载JS 文件。(按需加载)

5.重绘 (Repaint) 和回流 (Reflow)

  • 重绘 是当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘
  • 回流 是布局或者几何属性需要改变就称为回流(需要计算它们在设备视口(viewport)内的确切位置和大小)

回流必定会发生重绘,重绘不一定会引发回流。

回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流(必须重新计算网页的布局流程和所有相关元素的位置和大小)。

可能会导致性能问题

  • 添加或删除可见的DOM元素
  • 元素的位置发生变化
  • 元素的尺寸发生变化 (包括外边距、内边框、边框大小、高度和宽度等)。
  • 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。
  • 定位或者浮动
  • 浏览器的窗口尺寸变化 (因为回流是根据视口的大小来计算元素的位置和大小的)

减少重绘和回流

  • 批量修改DOM(虚拟DOM)
  • 对于复杂动画效果, 使用绝对定位让其脱离文档流
  • CSS3硬件加速 (GPU加速) transform、opacity、 filters这些动画不会引起回流、重绘

 6.简述data:属性的用法 (如何设置,如何获取) ,有何优势?🌟

data-* 的值的获取和设置,2种方法:
(1)传统方法 getAttribute( ) 获取data-属性值;  setAttribute( ) 设置data-属性值

getAttribute( ) 获取data-属性值;
setAttribute( ) 设置data-属性值
(2)HTML5新方法
例如 data-kerwin
dataset.kerwin 获取data-kerwin属性值
dataset.kerwin =“赵钱孙李”  设置data-kerwin属性值

⭕注意:

  • 传统方法无兼容性问题,但是不够优雅、方便
  • HTML5新方法很优雅、方便,但是有兼容性问题

 优势: 自定义的数据可以让页面拥有更好的交互体验 (不需要使用 Ajax 或去服务端查询数据)


7.null和undefined区别

  • null : 定义了值,但是没有指向任何的对象 --- 声明式 空值
  • undefined : 这个变量没有定义 --- 隐藏式 空值
  • null 表示一个已定义但是没有值的对象,可以理解为变量被赋值为 null,表示该变量指向的对象不存在或者被清空了。一般来说,null 是由程序员主动赋值而来的,并且它是一个对象类型(object),使用 typeof 操作符返回值为 object,这也是 null 的一个特例。
  • undefined 表示一个变量已经声明,但是尚未被初始化或者赋值,可以理解为变量还没有指向任何内存地址,或者虽然指向了内存地址,但是该内存地址的值为 undefined,也可以理解成该变量没有被分配内存空间。和 null 一样,undefined 也是一种数据类型,使用 typeof 操作符返回值为 undefined。

需要注意的是,在使用比较运算符时,null 和 undefined 的行为有所不同。例如,使用 == 运算符进行比较时,如果其中一个操作数为 null 或者 undefined,则会进行类型转换。在这个过程中,null、undefined 会被转换为布尔值 false,其他任何值都不会进行类似的转换。


8.精灵图和base64的区别是什么?

 ①概念

  • 精灵图(Sprite)是一个用来存放多个小图标的图片,通过CSS的background-position属性来定位显示对应的小图标。
  • Base64 是一种将二进制数据编码成 ASCII 字符串的方式,可以将图片等二进制数据转换为字符串进行传输或嵌入代码中,从而避免了引用外部文件的过程。

②加载和渲染

  • 精灵图需要将所有小图标放在一张大图上并加载,而且需要在CSS中通过background-position等属性来定位和渲染。
  • Base64 直接将二进制数据编码为字符串,可以直接在HTML、CSS或JS中使用,不需要额外的文件加载。

③压缩和性能

  • 使用精灵图可以减少HTTP请求次数,提升页面的加载速度。同时,可以压缩精灵图来减小文件大小,以提高传输效率。
  • Base64 直接将图片数据转换为字符串,会导致文件体积增大,降低传输效率,因此只适合小文件或者需要实现内联加载的场景。

综上所述,精灵图适用于需要渲染多个小图标的场景,可以减少HTTP请求次数和文件大小,提高渲染性能;Base64 适用于需要内联编码的小文件或者需要实现图片预览等功能的场景,可以避免引入外部文件。


9.浏览器的渲染过程

  1. HTML解析:浏览器加载HTML文件时,会进行解析和构建DOM树,然后将其转换为可供渲染引擎使用的Document Object Model(DOM)。
  2. CSS解析:当浏览器遇到CSS样式信息时,会同时构建CSS规则树。CSS规则树表示了HTML元素应该如何呈现在浏览器中。
  3. 构建Render Tree:浏览器将DOM树和CSS规则树合并到一起构建渲染树(Render Tree)。渲染树只包含需要显示的元素和对应的样式信息,而像<head> 和display: none 这样的无用元素则不会出现在渲染树上。
  4. 布局:渲染树构建完成后,浏览器会计算每个元素的位置、大小等布局信息,称为Layout/Reflow。布局是一个比较消耗性能的阶段,因此浏览器尽可能地优化这个过程。
  5. 绘制:在布局完成之后,浏览器会遍历渲染树,将每个元素绘制在屏幕上,形成最终的画面。
  6. 重绘:当元素的样式改变时,浏览器会进行重绘(Repaint),这个过程只会重新绘制元素的外观,而不会影响布局。
  7. 重排:当元素的尺寸、位置等布局属性改变时,浏览器会进行重排(Reflow),也就是重新计算元素的大小、位置等属性,并重新对页面进行布局。重排比重绘更加消耗性能,因此应该尽可能避免多次重排。

  •  解析HTML,生成DOM树,解析CSS,生成CSSOM树
  • 将DOM树和CSSOM树结合,生成渲染树(Render Tree)

  • Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)

  • Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素

  • Display:将像素发送给GPU,展示在页面上


10.页面导入样式时,使用link和@import有什么区别? 

  1. 加载方式:<link>标签会在页面加载时同时加载外部样式文件,而@import语句会在页面加载完毕后再加载样式文件,这意味着使用@import可能更慢一些。
  2. 浏览器兼容性:<link>标签是HTML标准中的元素,可以被所有浏览器正确解析和执行,而@import语句是CSS2.1中的规定,虽然现代浏览器都支持,但是一些老旧的浏览器可能不支持或者只支持部分语法。
  3. 作用范围:<link>标签可以用于引入HTML中的多个外部样式文件,而@import语句只能引入一个外部样式文件,并且只能在CSS文件内部使用。这意味着如果需要引入多个样式文件,需要使用<link>标签。
  4. 权重:在CSS规则中,<link>标签的权重高于@import语句。这意味着如果同一个样式被两者引入,<link>标签所引入的样式会优先生效。


 CSS

l.display: none; visibility: hidden; 的区别

  • 联系:它们都能让元素不可见
  • 区别:
    • display:none; 会让元素完全从染树中消失(DOM还在),渲染的时候不占据任何空间; visibility: hidden; 不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见
    • 修改常规流中元素的 display 通常会造成文档重排(回流)。修改 visibility 属性只会造成本元素的重绘(因此visbility 性能会相对应好一点)

2.外边距折叠(collapsing margins)

毗邻的两个或多个 margin 会合并成一个margin,叫做外边距折叠。规则如下。

  • 两个或多个毗邻的普通流中的块元素垂直方向上的 margin会折叠
  • 浮动元素 或  inline-block 元素 或绝对定位元素的 margin 不会和垂直方向上的其他元素的margin折叠。
  • 创建了块级格式化上下文 (BFC) 的元素,不会和它的子元素发生margin折叠

块级格式化上下文(Block Formatting Context,BFC)是 Web 页面中的一个渲染规范,是页面中盒子布局和浮动的计算规范,它可以看成是一个独立的渲染区域,规定了内部元素如何布局,并且与 BFC 外部毫不相干。BFC 中的元素布局不会影响到外部元素,同时也不受外部元素的影响。

在 BFC 中,每个盒子的左外边缘将触碰到容器的左侧边界,而不会出现浮动的情况。换言之,BFC 可以保证其中的元素垂直方向的位置不会发生重叠,当一个元素被设置为 BFC 后,它内部的元素就会按照一定的规则进行排列和布局,从而产生一种独立的渲染环境。

BFC 在页面中起着很重要的作用,它可以解决很多布局问题,例如:

  • 清除浮动:当一个父元素包含了子元素的浮动时,将父元素设为 BFC 即可清除浮动。
  • 防止 margin 重叠:当两个块级元素的 margin-top 和 margin-bottom 重叠时,将其中一个元素设为 BFC 即可避免 margin 重叠。
  • 解决宽度塌陷问题:当一个浮动元素出现在一个没有设置宽度的父元素里,将父元素设为 BFC 即可防止宽度塌陷。

BFC 可以通过以下方式产生:

  • 根元素或其他包含它的元素;
  • 浮动 (float 不为 none);
  • 绝对定位 (position 值为 absolute 或 fixed);
  • 行内块元素 (display 值为 inline-block);
  • 表格单元格(display 值为 table-cell);
  • overflow 值不为 visible 的块级元素;

3.z-index是什么? 在position的值什么时候可以触发?

z-index 属性设置元素的堆叠顺序。拥有更高堆叠顺序的元素总是会处于堆叠顺序较低的元素的前面,当脱离文档流内容较多,并且相互重叠的时候,就有可能发生本想完全显示的内容被其他内容遮挡的结果,这时就需要人为指定哪个层在上面,哪个在下面,z-index属性就是干这个用的。
⭕注意: Z-index 仅能在定位元素上奏效
在position的值是 relative、absolute、 fixed、sticky 时候可以触发。


4.简述box-sizing的有效值以及所对应的盒模型规则🌟

box-sizing 属性允许您以特定的方式定义匹配某个区域的特定元素

语法: box-sizing: content-box | border-box | inherit:
(1)box-sizing:content-box; 普通盒模型 这是由 CSS2.1 规定的宽度高度行为。宽度和高度分别应用到元素的内容框。在 宽度和高度 之外绘制元素的 内边距 padding 和边框 border 。是默认值。如果你设置一个元素的宽为100px,那么这个元素的内容区会有100px 宽,并且任何 边框 内边距宽度 都会被增加到最后绘制出来的元素宽度中。
(2)box-sizing:border-box; IE盒模型(避免子盒子从浮动的父盒子中掉下) 为元素指定的任何内边距和边框都将在已设定的宽度和高度内进行绘制。告诉浏览器去理解你设置的边框和内边距的值是包含在width内的。也就是说,如果你将一个元素的width设为100px,那么这100px会包含其它的border和padding,内容区的实际宽度会是width减去border + padding 的计算值。大多数情况下这使得我们更容易的去设定一个元素的宽高。

(3)box-sizing:inherit; 规定应从父元素继承 box-sizing 属性的值 


5.移动端适配怎么做?

(1) meta viewport (视口)

移动端初始视口的大小为什么默认是980px?
因为世界上绝大多数PC网页的版心宽度为 980px,如果网页没有专门做移动端适配,此时用手机访问网 页旁边刚好没有留白,不过页面缩放后文字会变得非常小。
为了解决页面缩放的体验问题,在网页代码的头部,加入一行viewport元标签。

这里的device-width告诉浏览器,将视口的宽度设置为设备宽度(这个宽度是人为预设的,不设的话就是980px)。 属性含义

initia-scale: 第一次进入页面的初始比例

minimum-scale: 允许缩小最小比例

maximum-scale: 允许放大最大比例

user-scalable: 允许使用者缩放,1 or 0 (yes or no)

(2)图片适配

img( max-width: 100%;}  此时图片会自动缩放,同时图片最大显示为其自身的100% (即最大只可以显示为自身那么大)

为什么不用 img{ width: 100%;}?  当容器大于图片宽度时,图片会无情的拉伸变形

(3)媒体

为什么要媒体查询?
针对不用的设备提前为网页设定各种 CSS 样式CSS3中的Media Query模块,自动检测屏幕宽度,然后加载相应的CSS文件
语法举例 :

@media screen and (min-width:1200px){
    body{
        background-color: red;
    }
}

当屏幕宽度大于1200px时,背景色变为红色。

(4)动态 rem 方案

为什么要用rem?

  • 和媒体查询配合,实现响应式布局

px、em、rem 有什么不同?

  • px是pixel (像素),是屏幕上显示数据的最基本的点,在HTML中,默认的单位就是px;
  • em 是一个相对大小相对于父元素font-size的百分比大小
  • rem 是相对于根元素的font-size

 6.什么是CSS3 transform? transition? animtion? 区别是什么?

CSS3属性中关于制作动画的三个属性: Transform,Transition,Animation。

1️⃣transform:  描述了元素的静态样式,本身不会呈现动画效果,可以对元素进行旋转 rotate、扭曲 skew、缩放 scale和 移动 transate 以及 矩阵变形 matrix。

div{
    transform:scale(2);
}
  • transition 和 animation 两者都能实现动画效果
  • transform 常常配合 transition 和 animation 使用

  2️⃣transition 样式过渡,从一种效果逐渐改变为另一种效果(主动触发)

transition是一个合写属性

transition: transition-property transition-duration transition-timing-functiontransition-delay
从左到右分别是: css属性、过渡效果花费时间、速度曲线、过渡开始的延迟时间。

div{
    width: 100px;
    height:100px;
    transition:transform 2s;
}
div: hover{
    transform:rotate(180deg);
}

transition通常和hover等事件配合使用,需要由事件来触发过渡

我们知道 transition 虽然简单好用,但是我们会发现它受到各种限制。

(1)transition需要一个事件来触发,比如hover,所以没法在网页加载时自动发生

(2) transition是一次性的,不能重复发生,除非一再触发。

(3) transition只能定义开始状态和结束状态,不能定义中间状态,也就是说只有两个状态

(4)一条transition规则,只能定义一个属性的变化,不能涉及多个属性。


3️⃣animation动画 由@keyframes来描述每一帧的样式

div{
    animation:myAnimation 5s infinite
}
@keyfirames myAnimation {
    0% {left:0;transform:rotate(0);}
    100%{left:200px;transform:rotate(180deg);}
}

区别:

  1. transform仅描述元素的静态样式,常常配合transition和animation使用
  2. transition通常和hover等事件配合使用,animation是自发的,立即播放
  3. animation可设置循环次数
  4. animation可设置每一帧的样式和时间,transition只能设置头尾
  5. transition可与js配合使用,js设定要变化的样式,transition负责动画效果

animation属性类似于transition,他们都是随着时间改变元素的属性值,

其主要区别在于: transition需要触发一个事件才会随着时间改变其CSS属性;

animation在不需要触发任何事件的情况下,也可以显式的随时间变化来改变元素CSS属性,达到一种动画的效果

  1. 动画不需要事件触发,过渡需要。
  2. 过渡只有一组(两个: 开始-结束) 关键,动画可以设置多个。

7.父元素和子元素宽高不固定,如何实现水平垂直居中 

<style>
.parent {
  position: relative;
  width: 100%;
  height: 50vh;
  background-color: #f5f5f5;
}

.child {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: #ccc;
  padding: 20px;
}
</style>

父元素的高度设置为 50vh,表示占据视口高度的一半。子元素的宽高不固定,内容也可能不确定,因此对 padding 进行了设置。通过应用上述 CSS 样式,就可以实现父元素和子元素的水平垂直居中,无论子元素宽高如何变化都能保持居中状态。

<style>
.parent {
    display: flex;
    justify-content:center;
    align-items:center;
    width: 600px;
    height: 600px;
    background:yellow;
}
.parent .child {
    color:#fff;
    width: 300px;
    height: 300px;
    text-align: center;
    background-color:blue;
}

</style>

8.假设高度默认100px,请写出三栏布局,其中左栏、右栏各为 300px,中间自适应

方法1:浮动
方法2:绝对定位
方法3: flexbox。移动开发里经常用到

<style>
.container {
  height: 100px;
}

.left {
  float: left;
  width: 300px;
  height: 100%;
  background-color: red;
}

.middle {
  margin-left: 300px;
  margin-right: 300px;
  height: 100%;
  background-color: green;
}

.right {
  float: right;
  width: 300px;
  height: 100%;
  background-color: blue;
}

</style>

<div class="container">
  <div class="left"></div>
  <div class="middle"></div>
  <div class="right"></div>
</div>

1️⃣首先将容器的高度设置为100px。然后,将左栏设置为float:left并固定宽度为300px,中间栏通过设置margin-left和margin-right为300px来自适应宽度,并且由于浮动元素的存在,中间栏会自动填满剩余的宽度。最后,将右栏设置为float:right并固定宽度为300px。这样就可以实现左、中、右三栏布局,其中中间栏自适应宽度。

<style>
.container {
  height: 100px;
  position: relative;
}

.left {
  position: absolute;
  left: 0;
  width: 300px;
  height: 100%;
  background-color: red;
}

.middle {
  position: absolute;
  left: 300px;
  right: 300px;
  height: 100%;
  background-color: green;
}

.right {
  position: absolute;
  right: 0;
  width: 300px;
  height: 100%;
  background-color: blue;
}

</style>

2️⃣首先将容器的高度设置为100px,并对其设置相对定位。然后,将左栏设置为position:absolute并固定宽度为300px,可以通过设置left值为0来让左栏靠左对齐。中间栏同样设置为position:absolute,通过设置left和right值为300px来占据剩余的宽度,从而实现自适应宽度。最后,将右栏设置为position:absolute并固定宽度为300px,通过设置right值为0来让右栏靠右对齐。

需要注意的是,当使用绝对定位布局时,必须为父元素设置position:relative以作为参照物。

<style>
.container {
  height: 100px;
  display: flex;
}

.left {
  width: 300px;
  height: 100%;
  background-color: red;
}

.middle {
  flex: 1;
  height: 100%;
  background-color: green;
}

.right {
  width: 300px;
  height: 100%;
  background-color: blue;
}

</style>

首先将容器的高度设置为100px,并为其设置display:flex属性。然后,将左栏和右栏的宽度都固定为300px,中间栏通过设置flex:1来占据剩余的宽度,即自适应宽度。最后,分别对三栏元素设置不同的背景颜色。

需要注意的是,在使用flexbox布局时,父元素必须具有display:flex或display:inline-flex属性,而子元素则可以通过flex-grow、flex-shrink和flex-basis等属性来实现自适应布局。


 9.CSS布局模式

1️⃣定位流(Positioning Context)

是指使用 position 属性(如:position: relative/absolute/fixed/sticky)设置的元素所在的渲染区域,该区域相对于其最近的块级祖先元素进行定位。在定位流中,可以使用 top、bottom、left、right 属性控制元素的位置,以及 z-index 属性控制元素的层级关系。定位流的主要作用是实现页面中元素的绝对定位,即脱离文档流独立布局。

2️⃣浮动流(Floating Context)

则是指使用 float 属性对元素进行浮动时所形成的渲染区域。在浮动流中,浮动元素会尽量出现在其包含块的顶部,并且不会覆盖行中的文本内容,以使页面排版更加合理。同时,浮动元素会影响父容器和其他兄弟元素的布局,需要适当清除浮动以避免造成布局混乱。浮动流的主要作用是实现页面中的多列布局、图片环绕文字等效果。

3️⃣普通流(Normal Flow)

是 HTML 文档中元素默认的布局方式,也是 CSS 中最基本的布局模式之一。在普通流中,元素按照其默认的块级或行内级别排列,如块级元素会单独占据一行,行内元素会按照字母的顺序排列在一行。可以通过控制元素的 display 属性、float 属性以及 position 属性等来影响元素的位置和排列顺序。

📜FC 基础模块

FC(Formatting Context,格式化上下文)是页面渲染时的一种概念,它是 CSS 布局的基础模块之一,能够约束页面元素的排版和渲染方式。

  • BFC (Block Formatting Context):块级格式化上下文,形成一个独立的渲染区域,在区域内部有一些特定的布局规则,如:margin 不会重叠、可以清除浮动等。
  • IFC (Inline Formatting Context):内联格式化上下文,包含行内元素,内部元素按照文本的书写方向从左到右或从右到左依次排列,可以设置 vertical-align 属性来控制元素的垂直对齐方式。
  • GFC(Grid Formatting Context):网格格式化上下文,是 CSS 网格布局的基础,能够实现复杂的网格结构布局,元素可以随意跨越单元格,从而实现更加灵活的页面布局。
  • FFC(Flexible Box Layout)是 CSS3 提供的新的布局方式,即弹性盒子布局。它是一种非常灵活的布局方式,通过 flex 容器和其中包含的 flex 项目实现排版和对齐效果。在 FFC 中,可以通过设置 flex-direction、justify-content、align-items 等属性来控制 flex 容器中的子元素如何排列和对齐,从而实现各种灵活的布局效果。

10. z-index 属性在什么情况下会失效 

在 HTML 页面中,所有的元素都是以层叠的方式排列在一起的,而 z-index 属性则用于控制元素的堆叠顺序。但是,在某些情况下,z-index 属性可能会失效,以下是几个常见的原因:

  1. 父元素的 z-index 值过低:如果一个元素的 z-index 值比其父元素的 z-index 值低,那么该元素的堆叠顺序会被父元素的堆叠顺序所覆盖。因此,在设置 z-index 值时,需要检查其父元素的 z-index 值是否满足要求。
  2. 元素所在的容器没有设置 position 属性或者 position 属性值为 static:只有设置了 position 属性为 relative、absolute 或 fixed 的元素才可以使用 z-index 属性,因为这些元素属于“定位元素”,具有堆叠上下文,并能够通过 z-index 属性来控制层叠顺序。
  3. 元素的 display 属性值为 inline:display 属性定义了元素的布局类型,如果一个元素的 display 属性值为 inline,那么它的 z-index 值会被忽略,因为 inline 元素不具有堆叠上下文。
  4. 元素的 opacity 属性值小于 1:如果一个元素的 opacity 属性值小于 1,那么它会创建一个新的堆叠上下文,导致其 z-index 值与其他元素的 z-index 值无法比较。
  5. 元素被裁剪或者被遮挡:如果一个元素被裁剪或者被遮挡,那么它的 z-index 值也会失效,因为它所占据的空间已经被限制或者被其他元素覆盖了。

11.🌟盒子模型详解 

当对一个文档进行布局(layout)的时候,浏览器的渲染引擎会根据标准之一的 CSS 基础框盒模型(CSS basic box model),将所有元素表示为一个个矩形的盒子(box)

一个盒子由四个部分组成:contentpaddingbordermargin(如下图)

  • content,即实际内容,显示文本和图像
  • boreder,即边框,围绕元素内容的内边距的一条或多条线,由粗细、样式、颜色三部分组成
  • padding,即内边距,清除内容周围的区域,内边距是透明的,取值不能为负,受盒子的background 属性影响
  • margin,即外边距,在元素外创建额外的空白,空白通常指不能放其他元素的区域

 三维视角如图:

1️⃣标准盒子模型

标准盒子模型,是浏览器默认的盒子模型。

标准盒子模型的模型图如下图:

从上图可以看到:

  • 盒子总宽度 = width + padding + border + margin;
  • 盒子总高度 = height + padding + border + margin

也就是,width/height 只是内容高度,不包含 padding 和 border 


2️⃣ IE 怪异盒子模型

  • 盒子总宽度 = width + margin;
  • 盒子总高度 = height + margin;

也就是,width/height 包含了 padding 和 border 


3️⃣Box-sizing

 CSS 中的 box-sizing 属性定义了引擎应该如何计算一个元素的总宽度和总高度

box-sizing: content-box|border-box|inherit;
  • content-box 默认值,元素的 width/height 不包含padding,border,与标准盒子模型表现一致
  • border-box 元素的 width/height 包含 padding,border,与怪异盒子模型表现一致
  • inherit 指定 box-sizing 属性的值,应该从父元素继承

12.CSS 实现垂直水平居中方法详解

①使用 Flex 布局(最常用)

.container {
  display: flex;
  align-items: center; /*在交叉轴上居中*/
  justify-content: center; /*在主轴上居中*/
}
  1. display: flex; 将容器设置为 Flex 布局
  2. align-items 属性将子元素在竖直方向上居中对齐
  3. justify-content 属性将子元素在水平方向上居中对齐。

⭕注意:父容器必须具有宽度和高度,子元素的宽度和高度最好是固定的,否则可能会出现不一致的排列方式。

②使用绝对定位实现

.parent {
  position: relative;
}
.child {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
  1. 将父容器设置为相对定位(position: relative;)
  2. 使子元素的定位基准点为父元素而不是文档流。
  3. 将子元素设置为绝对定位(position: absolute;)
  4. top: 50%; 和 left: 50% 将子元素放置于父元素的中心点。
  5. 使用 transform 属性的 translate(-50%, -50%) 参数将子元素向左上方移动自身宽度和高度的一半,使其居中显示。

⭕注意:在使用绝对定位实现垂直水平居中时,父容器必须要有宽度和高度,否则无法保证子元素的准确位置。此外,使用绝对定位将会使元素脱离普通的文档流,可能会对其他元素造成影响,因此在使用时要选择合适的场景。

③使用 table 实现垂直水平居中 

将子元素设置为 display:table-cell;、vertical-align:middle; 以及父元素设置为 display: table;。这种方法需要保证各个单元格的宽度相等,因此适用范围较窄。

.container {
  display: table;
}
.child {
  display: table-cell;
  vertical-align: middle;
}

 ④使用 Grid 布局

将容器设置为 Grid 布局,然后将子元素放置于网格的中心点。⭕注意:这里不能使用 align-items 和 justify-content 属性,而是需要使用 grid-template-columns、grid-template-rows 和 grid-column、grid-row 属性。

.container {
  display: grid;
}
.child {
  grid-column: 1 / 2; /* 放置于第一列 */
  grid-row: 1 / 2; /* 放置于第一行 */
  justify-self: center; /* 在水平方向上居中 */
  align-self: center; /* 在垂直方向上居中 */
}

将容器设置为 Grid 布局,然后将子元素放在了网格的左上角(第一行第一列),并使用 justify-self 和 align-self 将子元素在水平和垂直方向上居中。需要注意的是,父容器和子元素都必须具有固定的宽度和高度,否则无法保证位置的准确性。

 ⑤使用 line-height

(特殊情况)将子元素的 line-height 属性设置为与其父元素相同的值,然后将子元素的 vertical-align 属性设置为 middle。

.parent {
  height: 200px;
  line-height: 200px;
  text-align: center; /* 为了使子元素水平居中 */
}
.child {
  display: inline-block;
  vertical-align: middle;
}

将父容器的高度和 line-height 属性都设置为 200px,然后将文本对齐方式设为居中(text-align: center;)。接着,将子元素设置为 inline-block 元素,并使用 vertical-align: middle; 将其在垂直方向上居中。这种方法只适用于单行文本的情况。  

 ⑥使用 transform

将父容器设置为相对定位,将子元素设置为绝对定位,并使用 translate 属性将其移动到父容器的正中央。 将父容器设置为相对定位,将子元素设置为绝对定位,并使用 top: 50%; 和 left: 50%; 将其放置于父元素的中心点。然后,使用 transform 属性的 translate(-50%, -50%) 将子元素向左上方移动自身宽度和高度的一半,使其居中显示。这种方法需要保证子元素具有固定的宽度和高度。

.parent {
  position: relative;
}
.child {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

⭕ 注意:在使用 transform 属性实现垂直水平居中时,父容器必须要有宽度和高度,否则无法保证子元素的准确位置。此外,使用绝对定位将会使元素脱离普通的文档流,可能会对其他元素造成影响,因此在使用时要选择合适的场景。


JS

1.vue相比原生JS有什么优点?

  1. 解耦视图和数据:Vue.js 采用 MVVM 架构模式(Model(模型)、View(视图)和 ViewModel(视图模型)),实现了 视图 和 数据 的双向绑定。这意味着修改视图中的数据会自动更新到相对应的数据中,反之亦然,大大降低了代码耦合度。
  2. 双向数据绑定  v-model:Vue.js 支持双向数据绑定,使得数据的变化能够实时更新到视图中,同时视图中的数据变化也可以立即同步更新到数据模型中,从而实现了数据与视图的实时同步。
  3. 可复用的组件:Vue.js 实现了组件化的思想,可以将页面拆分成多个独立、可重用的组件,可以在多个页面之间共享使用。通过将页面分解为小的组件,我们可以更好地维护代码,提高代码的可读性和可维护性。
  4. 前端路由技术(单页):Vue.js 支持前端路由技术,可以实现单页应用程序的多个页面之间的无缝切换,不需要每次切换页面时都重新请求服务端,提高了应用程序的用户体验和性能表现。
  5. 状态管理:Vue.js 提供了 Vuex 状态管理库,用于管理应用程序中的状态。通过它的帮助,我们可以实现更好的状态管理和跨组件通讯。
  6. 虚拟DOM:Vue.js 使用虚拟 DOM 技术,将页面抽象成一个树形结构,每次数据变化只需要重新计算需要更新的部分,自动更新并渲染到真实的 DOM 上。这种方式相比于直接操作真实的 DOM 对象,可以提高应用程序的性能表现,同时减少了开发者代码的复杂度。


2.JS的异步

JS是单线程
执行任务的模式: 同步 异步
Javascript 语言的执行环境是“单线程”(一次只能完成一件任务。如果有多个任务就必须排队,前面一个任务完成,再执行后面一个任务,以此类推)。
“单线程” 这种模式的好处是实现起来比较简单,执行环境相对单纯; 坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行 (比如死循环)导致整个页面卡在这个地方,其他任务无法执行。

为了解决这个问题,Javascript语言将任务的执行模式分成两种:  同步和异步。

  • “同步模式”就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;  "异步模式”则完全不同,每一个任务有一个或多个 回调函数 (callback) ,前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的,异步的 
  • “异步模式” 非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,“异步模式” 甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有HTTP请求,服务器性能会急剧下降,很快就会失去响应

3.常见的异步编程方式

JavaScript 异步编程是指在程序执行过程中,某些操作不会阻塞后续代码的执行,以提高程序的运行效率和用户体验。常见的异步编程方式包括回调函数、Promise 和 async/await。其中,回调函数是最早也是最基础的异步编程方式,但由于回调函数嵌套多层容易引起代码可读性、维护性等问题,因此后来出现了 Promise、async/await 等更加优雅的方式来处理异步编程。


4.Promise 的基本用法,以及它的优缺点 

 Promise 是 ECMAScript 6 中引入的一种处理异步操作的机制。它的基本用法是通过 Promise 构造函数来创建一个 Promise 实例,然后通过 then() 方法来指定成功(resolve)和失败(reject)时的回调处理函数,或通过 catch() 方法来指定处理异常情况时的回调函数。Promise 的两个主要优点是:

  • 可以避免回调地狱,代码可读性更强。
  • 可以方便地进行错误处理,不需要像回调函数那样需要自己编写一大堆的 if 判断语句。

Promise 缺点:例如无法取消 Promise、无法立即得到 Promise 的返回结果等。 


5.AJAX 工作原理以及使用方法 

Asynchronous JavaScript and XML(异步 JavaScript 和 XML)的缩写,它是一种无需重新载入整个页面而实现局部更新的技术。工作原理是通过 XMLHttpRequest 对象向服务器发送请求,接收服务器返回的数据并更新页面内容,从而实现异步局部更新的效果。

使用 AJAX 可以减少页面加载时间,提高用户体验。在使用 AJAX 时,通常需要用到的 API 包括:XMLHttpRequest 对象、readyState 属性、onreadystatechange 事件、open() 方法、send() 方法和 responseText 属性等。 


6.做到大文件上传

1️⃣分片上传:

  1. 把需要上传的文件按照一定的规则,分割成相同大小的数据块
  2. 初始化一个分片上传任务,返回本次分片上传的唯一标识
  3. 按照一定的规则把各个数据块上传
  4. 发送完成后,服务端会判断数据上传的完整性,如果完整,那么就会把数据库合并成原始文件

2️⃣断点续传: 服务端返回,从哪里开始 浏览器自己处理

在大文件上传时,需要考虑上传进度、断点续传、并发上传等问题。如果没有实现断点续传,上传过程中如果网络出现故障,就需要重新上传整个文件。因此,如果要上传较大的文件,最好是使用第三方库或者插件来完成,例如使用Axios、fetch-upload等库来进行大文件上传。这些库能够更好地对上传的请求进行管理,从而提高上传的稳定性和速度。


7.无感登录 token 

 Token(令牌)是指在网络通信中为了确认请求方的身份而进行的身份验证方式,通过在用户进行登录认证之后,获取到一个特定权限的 Token,来代表该用户的身份信息。Token 由服务器颁发,客户端收到后可以存储在本地,之后每次发送请求时都需要携带该 Token 以进行身份验证。

Token 的优点包括:

  1. 提高系统的安全性:采用 Token 鉴权可以降低攻击风险,并保证了数据的隐私性和完整性。
  2. 提高用户体验:不需要频繁输入用户名和密码,也不需要每次都进行重复的认证操作,用户可以更快速地完成登录以及频繁的操作。
  3. 可扩展性:可以通过 Token 携带用户身份信息以外的数据,如权限、过期时间等,并且可以基于 Token 构建多种开放平台和 API 第三方接口。
  1. 登录成功后保存 token 和 refresh_token 刷新token
  2. 在响应拦截器中对 401 状态码引入刷新 token 的 API 方法调用
  3. 替换保存本地新的 token
  4. 把错误对象里的token替换
  5. 再次发送未完成的请求
  6. 如果 refresh_token 过期了,判断是否过期,过期了就清楚所有token重新登录

 8.TS的 type 和 interface 区别

1️⃣type 是用来定义类型别名(Type Aliases)的关键字,它可以将一个已有的类型起一个新的名字,从而使得代码更加易读和易维护,例如:

type Name = string;
type Age = number;

type Person = {
  name: Name;
  age: Age;
};

使用 type 定义了三个类型别名,分别是 Name、Age 和 Person。这样在后续的代码中,我们就可以使用这些别名来表示相应的数据类型,而不必每次都写出完整的类型声明。

2️⃣interface 则是用来定义接口(Interface)的关键字,它描述了一个对象的结构和属性,可以用来约束对象的形状和方法的参数和返回值类型。例如:

interface IUser {
  name: string;
  age: number;
  sayHello: () => void;
}

使用 interface 定义了一个 IUser 接口,它包含了 name、age 和 sayHello 三个属性。这样在后续的代码中,我们就可以使用 IUser 接口来限制对象的结构,例如: 

function greet(user: IUser) {
  console.log(`Hello, ${user.name}!`);
}

const user = { name: 'Tom', age: 18, sayHello() {} };
greet(user);

定义了一个函数 greet,它的参数类型为 IUser 接口,这样就可以确保传递的对象包含了 name 和 sayHello 属性,并且 sayHello 必须是一个函数。


9.type of null 

  • 解释为什么 typeof null 返回 "object":这是 JavaScript 语言本身的一个设计缺陷,早期版本的 JavaScript 中将 null 值的二进制表示中的前三位全置为 0,这被当作一个对象标识符。后来该问题无法修复,因为这会破坏现有的代码。
  • JavaScript 的基本数据类型包括哪些,它们的特点和应用场景;typeof 操作符的使用场景和返回值类型;JavaScript 中的类型转换(强制类型转换和隐式类型转换)。
  • 当使用 typeof 判断某个变量是否为 null 时,需要注意其返回值为 "object",不能通过该操作符进行准确判断。可以采用其他方式(如严格等于运算符 ===)来进行 null 值的判断。

10.判断对象是否为空

 1️⃣使用 Object.keys() 方法和 length 属性

function isEmptyObject(obj) {
  return Object.keys(obj).length === 0;
}

使用 Object.keys() 方法获取对象的所有属性名,并返回一个包含所有属性名的数组。然后我们可以通过数组的 length 属性来判断属性的数量是否为 0,如果为 0,则说明该对象为空。

 2️⃣使用 for...in 循环 

function isEmptyObject(obj) {
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      return false;
    }
  }
  return true;
}

使用 for...in 循环遍历对象的所有属性,并通过 hasOwnProperty() 方法来判断该属性是不是该对象自己的属性(而不是从原型链继承来的)。如果存在至少一个属性,则说明该对象不为空。

3️⃣使用 JSON.stringify() 方法

function isEmptyObject(obj) {
  return JSON.stringify(obj) === '{}';
}

使用 JSON.stringify() 方法将对象序列化成一个字符串,并检查该字符串是否等于 '{}'。如果相等,则说明该对象为空。


 11.如何遍历对象,以及for···in的坏处

 1️⃣for...in 循环

const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
  console.log(key + ': ' + obj[key]);
}

 使用 for...in 循环可以遍历对象的所有属性,包括从原型链中继承来的属性。需要注意的是,在使用 for...in 循环遍历对象时,最好使用 hasOwnProperty() 方法来判断该属性是不是该对象自己的属性,而不是从原型链继承来的。

 2️⃣Object.keys() 方法

const obj = { a: 1, b: 2, c: 3 };
Object.keys(obj).forEach(function (key) {
  console.log(key + ': ' + obj[key]);
});

使用 Object.keys() 方法可以获取对象的属性名组成的数组,然后可以使用 forEach() 方法来遍历该数组,从而遍历对象的所有属性。

 3️⃣Object.values() 方法

const obj = { a: 1, b: 2, c: 3 };
Object.values(obj).forEach(function (value) {
  console.log(value);
});

使用 Object.values() 方法可以获取对象的属性值组成的数组,然后可以使用 forEach() 方法来遍历该数组,从而遍历对象的所有属性值。 

⭕注意:使用 for...in 循环遍历对象的坏处是,它会遍历对象的所有可枚举属性包括从原型链中继承来的属性。而且,如果对象本身有一些不希望被遍历的属性,比如通过 Object.defineProperty() 方法定义的属性,那么这些属性也会被遍历到,导致遍历结果不准确。此外,使用 for...in 循环还存在一些性能问题,因为它需要遍历整个原型链,比较耗时。


12.Map和Object区别 

 Map 和 Object 都是 JavaScript 中用来存储键值对的数据结构,主要区别:

  1. 键名类型:Object 的键名只能是字符串或 Symbol 类型,而 Map 的键名可以是任何类型,包括基本数据类型、对象、函数等。
  2. 继承机制:Object 是 JavaScript 中所有对象的基类,它的原型链尽头是 Object.prototype。而 Map 没有继承自普通对象,它是一个独立的数据类型,不需要使用 new Object() 等构造函数创建,直接使用 new Map() 就可以创建一个新的 Map 对象。
  3. 键值对数量:由于 Object 的键名只能是字符串或 Symbol 类型,而 Map 的键名可以是任何类型,因此 Map 可以储存更多的键值对。另外,Map 还提供了方便的 size 属性,可以返回其包含的键值对数量。
  4. 遍历方式:遍历 Object 的属性时,只能使用 for...in 循环、Object.keys() 方法、Object.values() 方法等几种方式。而遍历 Map 时,有多种方式可供选择,如 for...of 循环、forEach() 方法等。

 13.const声明的对象可不可以修改

在 JavaScript 中,使用 const 声明的变量不能被重新赋值,但是如果这个变量是一个对象,则对象本身的属性是可以修改的。

const obj = { a: 1, b: 2 };
obj.a = 3;
console.log(obj); // { a: 3, b: 2 }

虽然 obj 是用 const 声明的,但仍然可以通过给其属性重新赋值的方式修改对象本身。

⭕如果使用 const 声明的对象本身被重新赋值,那么就会报错,例如: 

const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
obj1 = obj2; // 报错 "Uncaught TypeError: Assignment to constant variable."

如果尝试将 obj1 的值替换为 obj2,就会报错,因为 const 声明的变量不允许被重新赋值。 


14.new操作符具体做了什么 

  1. 创建一个空对象
  2. 将这个空对象的原型指向构造函数的原型对象(即 __proto__)
  3. 执行构造函数,并将这个空对象作为构造函数的上下文(this)传递进去
  4. 如果构造函数返回一个对象,则返回这个对象;否则返回第一步创建的新对象
function Person(name, age) {
  this.name = name;
  this.age = age;
}

let person = new Person('Tom', 25);
console.log(person); // 输出:{name: "Tom", age: 25}

上述代码定义了一个构造函数 Person,并且在使用 new 操作符时传递了两个参数 "Tom" 和  25。new Person('Tom', 25) 这行代码的执行流程如下:

  1. 创建一个空对象 {};
  2. 将这个空对象的原型指向 Person.prototype,也就是 __proto__ 属性指向 Person.prototype;
  3. 执行 Person 构造函数,并将 {} 作为上下文(this)传递进去;
  4. 在该构造函数的执行过程中给 this 绑定两个属性 name 和 age,此时 {name: "Tom", age: 25} 对象就已经初始化完成了;
  5. 如果构造函数返回一个对象,则返回这个对象;否则返回第一步创建的新对象。在本例中,构造函数没有显式返回任何值,因此会返回新创建的对象 {name: "Tom", age: 25}。

 15. async await 

async/await 是 ES2017 (ES8)中新增的异步编程语法糖,它们提供了一种更加简单、直观的方式来处理异步操作。

1️⃣async 关键字定义了一个函数,该函数返回一个基于 Promise 的异步操作,例如:

async function fetchData() {
  const data = await fetch('/api/data');
  return data.json();
}

 fetchData 函数前面使用了 async 关键字,表明该函数是一个异步函数。函数体内部可以使用 await 关键字等待异步操作执行完成,然后返回结果。在这个例子中,fetchData 函数包含两个异步操作:调用 fetch API 发起一个网络请求,以及对服务器响应数据进行解析的 data.json() 操作。

2️⃣await 是一个暂停异步函数的执行,等待一个 Promise 对象产生结果,它只能在 async 函数内部使用await 表达式在产生结果时会恢复异步函数的执行。如果 await 后面的表达式是一个 Promise 对象,则表示异步操作需要等待该 Promise 对象的状态变为 resolved,并返回 resolve 的结果。例如:

async function fetchData() {
  const response = await fetch('/api/data');
  const data = await response.json();
  console.log(data);
}

将 fetch 返回的 Response 对象存储在 response 变量中,然后使用 await 等待 response.json() 的执行结果,并将返回的 JSON 数据赋值给 data 变量。最终在控制台中打印出解析出来的数据。


16.Promise在项目中的使用 

 Promise 是 ES6 标准中新增加的一个异步编程解决方案。在前端项目中,Promise 可以用于处理异步请求、数据的处理和处理多个异步请求之间的依赖关系等方面,主要应用于以下几个方面:

  1. 处理异步请求:在前端开发中,经常会进行网络请求操作,而网络请求是一个异步操作。使用 Promise 可以更好地处理异步请求,在异步操作完成后执行相应的逻辑。
  2. 处理并发请求:在某些场景下,需要发起多个请求,但是这些请求的结果并不互相依赖。Promise 提供了 Promise.all 方法可以并行处理这些请求,并返回一个 Promise 对象,等待所有请求完成后执行相应逻辑。
  3. 处理异步依赖关系:在一些复杂的业务场景下,不同的异步请求之间可能存在依赖关系,需要根据一个异步请求的结果再进行另外的异步请求。在这种情况下,可以使用 Promise 的 then 方法链式调用多个异步请求,实现依赖关系的处理。
  4. 封装插件库:在封装一些插件库时,为了简化使用方式,可能会返回一个 Promise 对象。调用方可以使用 then 方法来获取异步操作的结果。

17.怎么实现Promise.all

Promsie.all 是一个常用的 Promise 方法,它可以接收一个 Promise 数组作为参数,返回一个新的 Promise 对象,当所有的 Promise 都成功时,返回的 Promise 对象会变成 fulfilled 状态,此时它的返回值是一个包含所有成功 Promise 返回值的数组。如果其中一个 Promise 出现了错误,则返回的 Promise 对象会变成 rejected 状态,此时它的错误信息是第一个被 reject 的 Promise 的错误信息。

function myPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    let count = 0;
    const results = [];
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(res => {
        count++;
        results[i] = res;
        if (count === promises.length) {
          resolve(results);
        }
      }).catch(err => {
        reject(err);
      });
    }
  });
}

创建了一个新的 Promise 对象,然后遍历传入的 Promise 数组,在每个 Promise 对象上使用 then() 方法注册一个回调函数,当 Promise 对象成功时将返回值存入结果数组中,并递增计数器 count,当所有 Promise 对象都成功后,判断计数器是否等于 Promise 数组的长度,如果是则调用 resolve() 方法,返回结果数组;反之则不处理。另外,当一个 Promise 对象出现错误时,则直接调用 reject() 方法,将错误信息返回给 Promise.all 的调用者。

⭕注意:实现 Promise.all 时需要考虑到一些边界情况,例如传入的参数不是一个数组、数组是空的、Promise 对象数量为0 等情况。同时也需要注意异步操作中的错误处理,防止出现未捕获的异常导致程序崩溃。


18. JWT   JSON Web Token

 JWT(JSON Web Token)是一种轻量级的跨域身份验证方案,它通过在用户和服务器之间传输加密的 JSON 数据来实现认证授权操作。JWT 由三部分组成:头部、载荷和签名。其中,头部包含了令牌的元数据,例如令牌类型和加密算法;载荷则包含了包含用户信息等有意义的数据;签名则通过密钥进行加密,用来保证数据不被篡改。

JWT 的使用流程通常如下:

  1. 用户向服务器发送请求,并提供身份验证信息,例如用户名和密码;
  2. 服务器验证身份信息是否正确,如果通过,则生成 JWT 并返回给客户端;
  3. 客户端将 JWT 保存在本地,并在请求时通过 HTTP 头部携带该令牌;
  4. 服务器收到请求后,通过解密 JWT 并验证签名来判断用户是否有权访问该资源。
  5. JWT 作为一种基于 token 的认证机制,优点在于可以有效地避免 CSRF 攻击,同时也可以支持多种平台上的认证和授权场景。此外,JWT 还可以根据应用场景进行自由扩展,在实现内部管理系统时非常实用。

⭕注意:由于 JWT 是基于 token 的认证机制,所以需要特别注意令牌的安全性,避免泄露加密密钥或遭到 CSRF 攻击等安全问题。同时也要注意令牌过期时间的设置,及时更新令牌以保证身份验证的安全性。


19.Promise的内部原理?优缺点

 Promise 是一种用于解决 JavaScript 异步编程的技术。在原始的回调函数模式下,容易出现回调地狱、难以维护等问题,而 Promise 可以将异步操作以更优雅的方式组织起来,提高可读性和可维护性。

Promise 的内部原理主要包括以下三个部分:

  • Promise 是一个对象,它有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。这些状态之间的变化由异步操作的结果决定。
  • Promise 支持链式调用。通过 then() 方法,可以将多个 Promise 实例串联起来。当前一个 Promise 实例的状态变为 fulfilled 时,会自动调用下一个 Promise 实例
  • Promise 包含一个 Executor 函数,这个函数会在 new Promise() 的时候立即执行,并且接收 resolve 和 reject 两个参数,用于处理异步操作的结果。

Promise 的优点:

  • 简洁明了:相对于传统的回调函数模式,Promise 可以让代码更加简洁、清晰,易于维护。
  • 可读性强:通过链式调用,Promise 可以让异步操作的代码更加直观、易于理解。
  • 错误处理方便:Promise 提供了 catch() 方法用于捕获异步操作中的错误,使错误处理变得更加方便。
  • 解决了回调地狱问题:Promise 的链式调用可以很好地处理多个异步操作之间的关系,避免了回调地狱问题。

Promise 的缺点

  • 需要学习新的概念:相对于传统的回调函数模式,Promise 需要学习一些新的概念和语法,有一定的学习成本。
  • 兼容性不好:ES6 才引入了 Promise,因此在旧版本的浏览器中可能无法使用。
  • 无法取消 Promise:一旦创建了一个 Promise,就无法取消它,这意味着 Promise 在某些场景下可能不太适合使用。若当前处于 pending 状态时,无法得知目前在哪个阶段。

20. call, apply, bind 三者有什么区别?

1️⃣call() apply() 的作用是一样的,都是在指定的 this 指向的情况下调用函数。区别在于传参的方式不同:call() 的参数是一个一个传递的,而 apply() 的参数是以数组的形式传递的。

func.call(thisObj, arg1, arg2, ...);
func.apply(thisObj, [arg1, arg2, ...]);

2️⃣bind() 方法与 call() 和 apply() 的作用类似,但是它不会立即执行函数,而是返回一个新的函数,并将 this 的指向绑定到第一个参数中。

let newFunc = func.bind(thisObj, arg1, arg2, ...);

 bind() 除了能改变 this 的指向外,还可以在新函数被调用时,指定一些默认参数,这在一些场景下非常实用。


21.内存泄漏

内存泄漏指的是在应用程序中,对象已经不再被使用,但是由于某些原因还是占用了内存空间,导致系统的可用内存逐渐减少,最终可能会导致系统崩溃或者应用程序崩溃的情况。

在前端开发中,一些常见的导致内存泄漏的原因包括:

  • 全局变量:将一个对象赋值给全局变量,如果该对象没有在后续代码中被清除或者删除,那么这个对象将一直存在于内存中,从而导致内存泄漏。
  • DOM 引用:在 JavaScript 中,当我们移除一个 DOM 元素时,它还是可能被其他引用所持有,这也会导致内存泄漏。
  • 定时器和回调函数:定时器、事件监听器以及回调函数等都有可能导致内存泄漏,特别是当它们的生命周期长而被频繁使用时。
  • 闭包:闭包可以让外部作用域中的变量一直存在于内存中,如果不注意释放这些变量,就可能导致内存泄漏。

为了避免内存泄漏,需要注意以下几点:

  • 及时释放引用:在不再需要一个变量或者对象时,应该及时释放相关的引用。
  • 避免使用全局变量:全局变量应该尽可能少地使用。
  • 合理使用闭包和回调函数:在使用闭包或者回调函数时,应该注意释放不再需要的变量和对象。
  • 使用浏览器提供的工具进行内存分析和性能调优:现代浏览器提供了一些开发者工具,可以帮助我们分析内存使用情况和性能问题,及时发现并解决内存泄漏问题。

22.操作数组的方法

  1. push() 和 pop()push() 方法用于向数组的末尾添加一个或多个元素,pop() 方法则用于删除并返回数组的最后一个元素。
  2. sort()sort() 方法用于对数组元素进行排序,默认是按照字符顺序进行排序。
  3. splice()splice() 方法用于在数组中添加或删除元素。它可以删除元素、插入元素或者同时实现这两个操作。
  4. unshift() 和 shift()unshift() 方法用于向数组的开头添加一个或多个元素,shift() 方法则用于删除并返回数组的第一个元素。
  5. reverse()reverse() 方法用于颠倒数组中元素的顺序。
  6. concat():🌟concat() 方法用于连接两个或多个数组,并返回结果数组。
  7. join()join() 方法用于将数组的所有元素放入一个字符串中,可以指定分隔符。
  8. map():map() 方法用于将数组中的每个元素通过指定的函数进行处理,返回处理后的新数组
  9. filter():filter() 方法用于筛选出符合条件的数组元素,并返回一个新的数组。
  10. every() 和 some():every() 🌟方法用于判断数组中的所有元素是否都满足指定条件,some() 方法则判断数组中是否至少有一个元素满足指定条件。
  11. reduce():reduce() 方法用于通过累计器来计算数组中的元素值,并返回计算结果。
  12. isArray()isArray() 方法用于判断一个对象是否为数组类型。
  13. findIndex():findIndex() 方法用于查找数组中符合条件的元素,并返回该元素在数组中的索引。

🌟 哪些方法会改变原数组?

  1. push()
  2. pop()
  3. unshift()
  4. shift()
  5. sort()
  6. reverse()
  7. splice()

 23.JS对数据类的检测方式

1️⃣typeof 操作符

typeof 操作符是最基本、最常用的一种检测数据类型的方式,它返回一个字符串,表示变量的数据类型。例如:

typeof 123         // 返回 "number"
typeof 'hello'     // 返回 "string"
typeof true        // 返回 "boolean"
typeof null        // 返回 "object"
typeof undefined   // 返回 "undefined"
typeof [1,2,3]     // 返回 "object"
typeof {name:'Tom'} // 返回 "object"
typeof function(){} // 返回 "function"

 ⭕注意:typeof 对 null 值的检测会返回 "object",这是一个历史遗留问题

2️⃣instanceof 关键字

instanceof 操作符用于检测某个变量是否属于指定的对象类型,它的语法如下:

obj instanceof Object

obj 表示要检测的变量,Object 表示指定的对象类型。例如:

var arr = [1,2,3];
arr instanceof Array    // 返回 true
arr instanceof Object   // 返回 true

var date = new Date();
date instanceof Date     // 返回 true
date instanceof Object   // 返回 true

function fn(){}
fn instanceof Function  // 返回 true
fn instanceof Object    // 返回 true

⭕注意:instanceof 只能用于检测对象类型,不能用于检测基本数据类型 

3️⃣constructor 属性

constructor 属性指向对象的构造函数,通过它可以判断一个对象的类型。例如: 

var arr = [1,2,3];
arr.constructor === Array    // 返回 true
arr.constructor === Object   // 返回 false

var date = new Date();
date.constructor === Date     // 返回 true
date.constructor === Object   // 返回 false

function fn(){}
fn.constructor === Function  // 返回 true
fn.constructor === Object    // 返回 false

var num = 123;
num.constructor === Number    // 返回 true

⭕注意:constructor 属性只能用于检测对象类型,不能用于检测基本数据类型 

4️⃣Object.prototype.toString() 方法

Object.prototype.toString() 方法可以返回一个对象的类型,它比 typeof 更加准确。例如:

Object.prototype.toString.call(123)                      // 返回 "[object Number]"
Object.prototype.toString.call('hello')                  // 返回 "[object String]"
Object.prototype.toString.call(true)                     // 返回 "[object Boolean]"
Object.prototype.toString.call(undefined)                // 返回 "[object Undefined]"
Object.prototype.toString.call(null)                     // 返回 "[object Null]"
Object.prototype.toString.call([1, 2, 3])                 // 返回 "[object Array]"
Object.prototype.toString.call({name: 'Tom', age: 18})    // 返回 "[object Object]"
Object.prototype.toString.call(function () {})           // 返回 "[object Function]"
Object.prototype.toString.call(new Date())                // 返回 "[object Date]"

 ⭕注意:需要将 Object.prototype.toString() 方法与 call() 或者 apply() 方法一起使用


前端常见的设计模式和使用场景?

1.工厂模式 

工厂模式是一种用于创建对象的设计模式,它可以将具体的对象创建逻辑从客户端代码中分离出去,从而提高应用程序的灵活性和可维护性。 

用一个函数来创建实例,返回 new 创建的实例 (隐藏了new关键字) 场景:

  • jQuery的 $ 函数
  • React createElement函数 (JSX语法的底层函数 )
function $(...rest){  //无论多少参数都一并解构在这里
    return new Foo(...rest)
}
$("xxx")

2.单例模式

单例模式是一种用于保证对象仅被实例化一次的设计模式,它在需要频繁创建对象的场景中可以减少系统开销,提高应用程序的性能表现。

是一个全局的,唯一的实例。

  • Vue 项目中的Vue实例
  • Node 项目中的App实例
  • Vuex、 React-Redux 中的 store
  • 全局唯一个组件,像弹出窗,模态窗口
  • Mysql实例
class Dog{
    constructor(){}
    static getInstance(){
        return Dog._instance
    }
}
    let dl = new Dog();
    let d2 = new Dog();
    console.log(d1===d2); // false
    let d3 = Dog.getInstance(); // 这样才能创建单例模式的对象
    let d4 = Dog.getInstance();
console.log(d3===d4); // true

模态窗口是一种常见的前端界面设计,它可以在弹出窗口后阻止用户对其他窗口的访问,同时强制用户完成窗口内的操作。模态窗口通常用于提示信息、交互式表单、登录窗口等场景。

实现模态窗口通常有两种方法:

  1. 使用 JavaScript 和 CSS 实现:通过 JavaScript 控制 CSS 样式的变化,实现弹出窗口的显示和隐藏,并使用绝对定位将其居中显示。为了阻止用户对其他窗口的访问,可以设置一个遮罩层,将弹出窗口放置在其上方,并将遮罩层的 z-index 属性设置为较高的值,使其占据整个视图并阻止用户对其他元素的操作。
  2. 使用框架组件实现:现代前端框架通常都提供了模态窗口的组件,例如 Bootstrap 中的 Modal 组件、Ant Design 中的 Modal 组件等。使用这些组件可以快速地实现模态窗口,并且组件通常已经包含了大多数常见的需求,例如自动居中显示、遮罩层的添加等。

 3.代理模式

Proxy 访问一个对象属性之前先做一个拦截(做一些额外的业务或者逻辑操作)。

Vue3响应式原理

let obj = {
    name:"Vue"
    age:9
}
let obj2 = new Proxy(obj,{
    get(target,property)
        // 这个get函数 什么时候执行? 访问obj2的某个属性的时候就来执行
        // target 就是obj这个对象
        // property 就是访问的这个属性
        return target[property]
    }
    set(target,property,newVa1){
        // 这个set函数 什么时候执行? 修改obj2属性的时候执行
        // 数据的劫持/拦截
        //target 就是obj这个对象
        //property就是访问的这个属性 
        //newVal 接收新的值
        target[property] = newVal;
    }
})
obj2.age=10 // 触发set
console.1og(obj2.age)


4.观察者模式

观察者模式是一种基于事件驱动的设计模式,它主要用于处理对象之间的消息通信。观察者模式可以将消息的发送者和接收者解耦,从而提高应用程序的可复用性和可扩展性。

一个主题,一个观察者。主题发生变化,就触发了观察者的执行(没有媒介)。

btn.addEventListener('click',()=>{})

 5.发布订阅者模式

发布订阅者模式是一种常见的设计模式,用于实现多个组件之间的松耦合通信。在这种模式中,发布者向订阅者发送消息,订阅者可以根据自己的需要订阅感兴趣的消息,从而实现针对性的消息处理。

针对不同的场景和需求,可以使用不同的实现方式来实现发布订阅者模式,例如自定义事件、Vue.js 中的事件处理、Redux 中的状态管理等。

发布者  订阅者  第三方 

function fn1(){
    //事件函数1
}
function fn2(){
    // 事件函数2
}
//第三方 eventBus
//开启自定义事件监听
eventBus.on("eventName",fn1); //订阅者1
eventBus.on("eventName",fn2); //订阅者2

// 触发自定义事件eventName,从而执行fn1函数
eventBus.$emit('eventName');// 发布者$emit


// 删除自定义事件,防止内存泄漏。 在声明周期中的销毁阶段
eventBus.off('eventName',fn1);

6.装饰器模式

保证原有函数功能不变的同时,增加一个新的功能(AOP面向切面编程)。

用于动态地给对象添加新的行为或修改原有行为。

装饰器是一种包装对象的方式,它可以增强对象的功能,同时又不会对原有对象造成任何改变。 装饰器模式可以应用于很多场景,例如实现日志记录、性能监控、数据埋点、缓存数据、权限控制等。应用于 Vue.js 中的 Mixin 混入、React 中的高阶组件(HOC)等

场景:

  • ES 和 TypeScript 的 Decorator 语法
  • 类装饰器,函数(方法)装饰器,属性装饰器
function deco(){
    return function(){
        console.log("给fn函数增加新的功能!")
    }
}
@deco()
function fn(){
    console.log("这是我正常的功能函数")
}
fn()

缓存

在任何一个前端项目中,访问服务器获取数据都是很常见的事情,但是如果相同的数据被重复请求了不止一次,那么多余的请求次数必然会浪费网络带宽,以及延迟浏览器渲染所要处理的内容,从而影响用户的使用体验。如果用户使用的是按量计费的方式访问网络,那么多余的请求还会隐性地增加用户的网络流量资费。因此考虑使用缓存技术对已获取的资源进行重用,是一种提升网站性能与用户体验的有效策略。

缓存的原理是在首次请求后保存一份请求资源的响应副本,当用户再次发起相同请求后,如果判断缓存命中则拦截请求,将之前存储的响应副本返回给用户,从而避免重新向服务器发起资源请求
缓存的技术种类有很多,比如代理缓存、浏览器缓存、网关缓存、负载均衡器及内容分发网络等,它们大致可以分为两类:共享缓存和私有缓存。共享缓存指的是缓存内容可被多个用户使用,如公司内部架设的Web代理;私有缓存指的是只能单独被用户使用的缓存,如浏览器缓存。
HTTP 缓存应该算是前端开发中最常接触的缓存机制之一,它又可细分为强制缓存协商缓存,二者最大的区别在于判断缓存命中时,浏览器是否需要向服务器端进行询问以协商缓存的相关信息,进而判断是否需要就响应内容进行重新请求。下面就来具体看HTTP缓存的具体机制及缓存的决策策略。 


关于HTTP缓存

        在任何一个前端项目中,访问服务器获取数据都是很常见的事情,但是如果相同的数据被重复请求了不止一次,那么多余的请求次数必然会浪费网络带宽,以及延迟浏览器渲染所要处理的内容,从而影响用户的使用从验。如果用户使用的是按量计费的方式访问网络,那么多余的请求还会隐性地增加用户的网络流量资费。因此考虑使用缓存技术对已获取的资源进行重用,是一种提升网站性能与用户体验的有效策略。

        缓存的原理是在首次请求后保存一份请求资源的响应副本,当用户再次发起相同请求后,如果判断缓存命中则拦截请求,将之前存储的响应副本返回给用户,从而避免重新向服务器发起资源请求。
        缓存的技术种类有很多,比如代理缓存、浏览器缓存、网关缓存、负载均衡器及内容分发网络等,它们大致可以分为两类:共享缓存和私有缓存。共享缓存指的是缓存内容可被多个用户使用,如公司内部架设的Web代理; 私有缓存指的是只能单独被用户使用的缓存,如浏览器缓存。

        HTTP 缓存应该算是前端开发中最常接触的缓存机制之一,它又可细分为强制缓存和协商缓存,二者最大的区别在于判断缓存命中时,浏览器是否需要向服务器端进行询问以协商缓存的相关信息,进而判断是否需要就响应内容进行重新请求。下面就来具体看HTTP缓存的具体机制及缓存的决策策略


浏览器缓存策略

浏览器缓存(Brower Caching) 是浏览器在本地磁盘对用户最近请求过的文档进行存储时,浏览器就可以直接从本地磁盘加载文档。
浏览器是如何判断是否使用缓存的:

浏览器会比较服务器返回的响应头中的“Expires”字段(指定了缓存过期时间)和“Cache-Control”字段(指定了缓存的验证方式和缓存过期时间)与本地缓存中的相应信息,来决定是否使用缓存。如果响应头中的缓存控制信息与本地缓存中的匹配,则浏览器可以直接从缓存中获取资源,否则就需要重新向服务器发起请求获取最新资源。

另外,浏览器还可以通过发送“If-Modified-Since”或“If-None-Match”等条件请求头来验证缓存是否仍然有效,从而避免重复下载资源,提高页面加载速度。

📜浏览器缓存的优点有

  1. 减少了几余的数据传输,节省了网费
  2. 减少了服务器的负担,大大提升了网站的性能
  3. 加快了客户端加载网页的速度

浏览器缓存主要有两类: 缓存协商 和 彻底缓存,也有称之为协商缓存和强缓存。

1.强制缓存: 不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的network选项中可以看到该请求返回 200的状态码
2.协商缓存: 在使用本地缓存之前,需要向服务器发送请求,服务器会根据这个请求的request header的一些参数来判断是否命中协商缓存,如果命中,则返回304状态码并带上新的response header通知浏览器从缓存中读取资源; 协商缓存可以解决强制缓存的情况下,资源不更新的问题。

⭕两者的共同点:都是从客户端缓存中读取资源,区别是强缓存不会发请求,协商缓存会发请求。


强制缓存中header的参数 (响应头)

Expires: response header里的过期时间,浏览器再次加载资源时,如果在这个过期时间内,则命中强缓存。

Cache-Control: 当值设为 max-age=300 时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。
cache-control 除了该字段外,还有下面几个比较常用的设置值:

  • -no-cache: 不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载
  • -no-store: 直接禁止浏览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。
  • -public: 可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器
  • -private: 只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存。

Expires: 设置以分钟为单位的绝对过期时间,设置相对过期时间,max-age指明以秒为单位的缓存时间.
Expires优先级比Cache-Control低,同时设置Expires和Cache-Control则后者生效 


跨域

1.跨域简介

跨域指的是在浏览器中,当一个网页的脚本在访问另一个网页的内容时,如果这两个网页的协议、域名或端口号有任何不同,都会触发跨域问题。

当前页面URL被请求页面URL是否跨域原因
http://www.test.com/http://www.test.com/index.html同源 (协议、域名、端口号相同)
http://www.test.com/https://www.testcom/index.html跨域协议不同 (http/https)
http://www.test.com/百度一下,你就知道跨城主域名不同 (test/baidu)
http://ww.test .com/http://blog.test.com/跨城子域名不同 (www/blog)
http://www.testcom:8080/http//www.test.com:7001/跨城端口号不同 (80807001) 

2.跨域的原因

浏览器的同源策略 是造成 跨域 的主要原因。
同源策略是浏览器中的一种安全策略,保证当两个URL的协议、域名、端口有任何不同时,限制它们之间的交互不加限制则可能导致:
恶意网页获取其它网页的数据、修改DOM、操作DOM获取用户输入的值、读取Cookie、发送Ajax请求。

let document2 = opener.document;
document2.body.style.display = "none";

3.如何解决跨域

①JSONP (前端+后端)

JSON with Padding 不受同源策略影响的标签: <img>、<script>、<link>、<iframe>JSONP 利用 script标签,将请求放在src属性中,前端通过解析 (一段Javascript代码) 拿到数据。

<script src="http://localhost:4000/getJSONP?fn=set]SONP"></script>
  • 优势: 轻量、兼容性高,不需要XMLHttpRequest
  • 缺点:
    • (1) 只支持GET,不支持POST;
    • (2)存在被注入恶意脚本的风险
    • (3)接口异常,无法监听

②CORS(跨域资源共享) - 后端

设置响应头中的 Access-Control-Allow-Origin 字段,该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。

res.header('Access-Control-Allow-Origin'.'*')
// res.header('Access-Control-Allow-Origin'http://localhost:3000')
  • 优点: 操作简单、支持所有http请求类型 (get、post、put等)
  • 缺点: IE8以下的浏览器不支持

③反向代理转发  ( 前端+nginx )

同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。

 

反向代理指的是客户端与服务器之间存在一个代理服务器,客户端通过向代理服务器发送请求,代理服务器再将请求转发给实际的服务器进行处理,并将处理结果返回给客户端。在这个过程中,由于客户端始终是向同源的代理服务器发送请求,因此不会出现跨域问题,同时代理服务器也可以利用自身的能力对请求进行拦截、缓存、负载均衡等操作,提高系统的可靠性和性能。

在使用反向代理解决跨域问题时,通常需要在服务器端配置相应的代理规则。例如,在使用 Nginx 作为代理服务器时,可以通过配置 Nginx 的 location、proxy_pass、proxy_set_header 等指令,将客户端的请求转发到实际的服务器中,并支持对响应结果的加工和修改。

⭕注意:反向代理虽然可以有效地解决跨域问题,但也会带来额外的系统开销和维护成本。

 vue.config.js 中的配置

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
    transpileDependencies: true,
    devServer:{
        proxy:{
            '/api':{
                    target:'http://localhost:3000',
                    changeOrigin: true,// 是否开启跨域,值为 true 就是开启,false 不开启                        
                    pathRewrite: {
                        '^/api':''
                    }
            }
        }
     }  
}) 

把实际请求转到了本地运行的服务,再转发到后端地址,从而跳过了浏览器及其同源策略,也就不会跨域。

server{
    listen  5000;
    server_name localhost;
    location /api {
        add header Access-Control-Allow-Origin 'http://localhost:3000';
        proxy_pass http://localhost:4000:
    }
}

④postMessage

postMessage 是 HTML5 中新增的跨文档消息传递机制,是为数不多可以跨域操作的 window属性之一,它可用于解决以下方面的问题:

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的iframe消息传递

 前后端分离

1️⃣ 什么是前端?

那前端用的是什么语言呢?

  • 网页语言三件套: HTML,CSS,JavaScriptlOS app: Objective-C
  • Android app: Java
  • 桌面程序: C++

2️⃣ 什么是后端?

那后端用的是什么语言呢?
Java,PHP,Python,Go,JavaScript等等

3️⃣🌟前后端关系

那前端代码和后端代码放哪里呢?

  • 前端代码: 理论上放哪都行,只要有网络并且能够请求到(有些情况会限制)后端的数据就可以
  • 后端代码: 只能放服务器!

既然前端代码放哪都行,所以一般情况,前后端代码都放在服务器 (不同的位置)

⭕注意 代码运行:

  • 前端代码运行在客服端(如你的手机app,浏览器等
  • 后端代码运行在服务器(如远程服务器,阿里云)


 前端性能优化

  1. 减少HTTP请求:尽量减少网页中的外部资源请求(如CSS、JS、图片等),可以通过合并文件、使用CSS Sprite等优化手段来达到减少HTTP请求的目的。
  2. 压缩代码:可以通过压缩JavaScript和CSS代码来减小文件的体积,从而加快页面的加载速度。
  3. 加载顺序优化:将页面必要的CSS放在头部进行加载,在脚本底部加载JavaScript文件,这样可以先让网页框架展现出来,增强用户体验。
  4. 图片优化:将大图片进行压缩,使用WebP格式图片来替代传统的JPG,PNG等格式的图片,以减少文件大小和加载时间。
  5. 缓存利用:利用浏览器缓存来减少服务器对浏览器的请求次数,降低网络负载。
  6. 代码简化:优化代码结构,减少代码量,提高代码复用性,从而减小文件大小,加快渲染速度。
  7. 懒加载:在需要时再去网络请求需要的资源,例如图片懒加载。这样可以优化页面加载速度,减小首屏渲染成本。等等

  


VUE

1.🌟VUE的key为什么最好不是数组下标

 key值 的主要作用:给元素添加一个唯一标识符,  用于提高Vue渲染性能。

  • 当 data 发生变化的时候,Vue会使用 diff算法 来对比 新旧虚拟dom ,
  • 如果key值相同, 才会考虑复用元素。如果 key值 不同, 则会强制更新元素。
  • 一般通过给元素 key值 设置为 id , 来保证 Vue 在更新数据的时候可以最大限度复用相同 key值的元素

⭕key值为什么不能是下标:

  • 因为数组的长度发生变化的时候,其他的元素下标会受到影响。
  • 而如果把下标作为 key值,由于其他的元素下标变化。所以VUE会认为你的key值也变化了,就会强制更新你的元素,影响性能

 2.🌟VUE 组件5种通信方式

 组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。

  1. 父子组件通信:父组件通过 props 向子组件传递数据,子组件通过 $emit() 向父组件传递事件。
  2. 子父组件通信:子组件通过 $emit() 向父组件传递事件,父组件通过 v-on 监听子组件的事件实现数据更新。
  3. 兄弟组件通信:可以通过一个共同的父组件来实现兄弟组件之间的通信。父组件中维护一个数据状态,通过 props 分别将数据状态传递给子组件,并通过 $emit() 触发事件更新数据状态。
  4. 跨级组件通信:可以通过 provide 和 inject API 来实现跨级组件之间的通信。父组件通过 provide 提供一些数据或方法,子组件通过 inject 注入需要的数据或方法来访问父组件提供的内容。
  5. 非组件通信:可以使用 Vuex 来进行非组件间的状态共享和事件通知。Vuex 将状态存储在一个全局的 store 中,不同组件通过 this.$store 访问 store 中的状态和方法。

状态提升:指将组件间共享的状态提升到它们的最近的公共祖先组件中,然后通过 props 将状态传递给兄弟组件,从而实现兄弟组件之间的通信。 

事件总线(Event Bus):被看作是一个共享的中央事件管理器,组件都可以向该事件管理器注册事件监听器和触发事件,从而实现组件之间的通信。

事件总线可以通过一个单独的 Vue 实例来创建,这个实例可以作为一个中央事件管理器,用于分发事件和接收事件。在一个 Vue 实例中,通过 Vue.prototype.$bus 来创建事件总线,并将其挂载到 Vue 的原型上:

Vue.prototype.$bus = new Vue()

可以在任何组件中访问 $bus 实例,例如可以通过 $bus.$on() 方法注册事件监听器,通过 $bus.$emit() 方法触发事件:

<!-- childA.vue -->
<template>
  <div>
    <button @click="sendMessage">发送消息到 Brother 组件</button>
  </div>
</template>

<script>
export default {
  methods: {
    sendMessage() {
      this.$bus.$emit('send-message', '你好,Brother 组件!')
    }
  }
}
</script>
<!-- childB.vue -->
<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: ''
    }
  },
  created() {
    this.$bus.$on('send-message', message => {
      this.message = message
    })
  }
}
</script>

当 childA 组件中的按钮被点击时,它会通过 $bus.$emit() 方法触发 send-message 事件,并向 Brother 组件发送一条消息。在 childB 组件中,可以通过 $bus.$on() 方法监听 send-message 事件,并更新页面上的消息内容。

⭕注意:在事件总线中注册的事件监听器可能会引起命名冲突和内存泄漏等问题。

情况分类:

1️⃣常用情况:

  1. 父组件 一> 子组件 props
  2. 子组件 一> 父组件 emits
  3. 父组件调用子组件方法 ref expose

2️⃣层级比较深  provide  inject

provide 可以在父组件中声明一个对象、函数或者属性,使其子孙组件(不管嵌套多深)可以通过 inject 获取到这些内容,并在组件中直接使用。

⭕注意:provide 和 inject 并不是响应式的,只能用于单向数据流的情况下,也就是父组件提供的数据不能够在子组件中修改。如果想要实现双向数据流,则需要使用 v-model 或者事件触发等其他方式。 

3️⃣mitt 全局事件总线 跨组件传参

发布-订阅模式,因此可以在任意组件中发送和接收事件。在 Vue.js 的组件中,可以在 mounted() 钩子函数中注册事件的监听器,并在 beforeDestroy() 钩子函数中注销监听器,以避免出现内存泄漏的问题。


4️⃣全局状态 Pinia / Vuex

① Vuex 的设计思想基于 Flux 模式,通过单向数据流的方式来管理应用状态。它将 Vue.js 应用中的状态抽象成一个个 store,每个 store 包含了一些状态(state)、状态的修改方法(mutation)和异步修改状态的方法(action)。同时,Vuex 还提供了 getter 来获取派生状态(derivative state)。

在使用 Vuex 时,需要先创建一个 Vuex store 实例,并将其传递给根组件。然后,在组件中可以通过 $store.state 访问状态、通过 $store.commit() 提交 mutation、通过 $store.dispatch() 触发 action、通过 $store.getters 获取派生状态。


② Pinia 设计思想基于 Vue.js 3 的新特性,使用了 Reactivity API 和 Composables 的概念,并采用了类似于 Vuex 的 store 架构来管理应用的状态。Pinia 在语法和使用方式上都比 Vuex 更加简洁、灵活,同时具有更好的性能、类型安全和开发体验。

在使用 Pinia 时,需要先创建一个 Pinia store 实例,并将其传递给根组件。然后,在组件中可以通过 useStore() 函数来获取 store 实例,并访问它的状态、提交 mutation 和 action、获取派生状态。


📜总结:Vuex 更加成熟、稳定,具有较好的生态和社区支持,更适合于大型应用的状态管理;而 Pinia 更加轻量、灵活,具有更好的 TypeScript 支持和开发体验,更适合于小型、中型应用的状态管理。选择哪个状态管理库需要根据实际情况进行权衡和取舍。


3.Proxy 与 Object.defineProperty 响应式数据绑定的实现方式

1️⃣功能上:Object.defineProperty 只能劫持对象的属性,它需要遍历对象属性进行劫持,对于新增、删除属性不够友好;而 Proxy 可以劫持整个对象,包括内部属性,同时还支持新增和删除属性,因此具有更好的灵活性。(Object.defineProperty 主要用于定义对象属性的特性,例如:可读、可写、枚举等;而 Proxy 可以劫持对象的底层操作,包括 get、set、has、deleteProperty 等多种操作。)

2️⃣性能上:由于 Object.defineProperty 需要遍历对象属性进行劫持,所以当对象属性较多时,遍历的开销也会增加,影响代码的性能;而 Proxy 则不需要这些操作,它处理数据的性能比 Object.defineProperty 更快。

3️⃣兼容性:Object.defineProperty 出现较早,但是在某些浏览器中不被支持(如 IE8),所以在开发时需要考虑兼容性问题;而 Proxy 是 ES6 标准中新增的功能,如果需要支持旧版浏览器,就需要使用 polyfill 进行兼容。

4️⃣动态特性:Object.defineProperty 只能对已有的属性进行更改或监听,并且需要显式地为属性设置特性;而 Proxy 可以动态地针对对象上的任何操作进行拦截,并且可以根据自定义逻辑实现更为灵活和高效的代理。

①Object.defineProperty 是 JavaScript 中一个用于定义对象属性的方法,它可以给一个对象定义一个新属性或修改一个已有属性,并控制对这个属性的访问和操作。

其本质是通过代理模式来实现的,当我们调用 Object.defineProperty 方法时,它会在目标对象上创建一个新的属性或修改一个已有属性,并返回目标对象本身。

在定义属性时,可以使用一些特殊的描述符来控制属性的特性,例如:value、writable、enumerable 和 configurable 等。其中,value 表示属性的值,writable 表示属性是否可写,enumerable 表示属性是否可枚举,configurable 表示属性是否可删除或修改特性。

当定义一个属性时,Object.defineProperty 方法会将该属性转化为 getter 和 setter 形式,从而实现控制对属性的访问和操作。当我们读取属性时,会调用 getter 方法;当我们修改属性时,会调用 setter 方法。

需要注意的是,由于 Object.defineProperty 方法只能监听到对象的属性变化,而不能监听到对象本身的变化,因此它无法对对象的新增或删除操作进行监听。如果需要监听对象的新增或删除操作,应该使用 Proxy 对象。


②Proxy 是 ES6 中新增的一个对象,它可以用于拦截对象的底层操作,并在拦截时执行自定义的逻辑。本质是一种元编程机制,在目标对象之间创建一个代理层,并拦截目标对象上的底层操作。当我们对代理对象进行读取、赋值、方法调用等操作时,该操作会被拦截到,进而触发相应的拦截器函数。这些拦截器函数包括了多种操作,例如:get、set、has、deleteProperty、apply 等。

过程为:使用 Proxy 对象时,需要传入一个目标对象和一个处理程序对象。处理程序对象中包含了一些拦截器函数,用于拦截该目标对象上的不同操作。当对目标对象进行操作时,Proxy 会自动调用相应的拦截器函数,并以该操作为参数来调用该函数,从而实现对目标对象的拦截。

⭕注意:由于 Proxy 对象是在目标对象之间创建代理层,因此使用 Proxy 可能带来一些性能损失。另外,由于 Proxy 对象是 ES6 新增的特性,不是所有浏览器都支持,因此在使用时需要注意兼容性。


4.Vue.js 的生命周期钩子函数,以及它们各自的作用

  • beforeCreate:在实例初始化之后、数据观测和事件配置之前被调用,此时组件实例的选项对象中的所有属性都不能访问
  • created:在实例创建完成之后被调用,此时组件实例的 data、watch、computed 和 methods 都初始化完成了。
  • beforeMount:在模板编译成渲染函数之前被调用,此时还没有渲染 DOM。
  • mounted:在模板编译成渲染函数之后,将渲染结果挂载到 DOM 上时被调用,此时已经完成了 DOM 的渲染。
  • beforeUpdate:在数据更新之前被调用,此时尚未更新 DOM。
  • updated:在数据更新之后被调用,此时渲染 DOM 已完成。
  • beforeDestroy:在实例销毁之前被调用,此时实例还可以访问。
  • destroyed:在实例销毁之后被调用,此时实例所有的指令和事件监听器都已经解除,无法再访问实例。 

5.Vue 组件的理解

Vue组件 是一个可复用、独立、有相对完整功能的 UI 组件。在 Vue 中,组件可以看做是拥有自己作用域和状态(数据)的 Vue 实例。每个组件都包含了模板、脚本、样式和各种功能逻辑,在应用中可以被反复使用。

Vue 组件的优点包括:

  1. 可复用:组件可以被多次使用,且一个组件可以由多个组件组成。这样就能有效避免代码重复和冗余。
  2. 独立:每个组件都是独立的,一个组件的变化不会影响到其他组件。这样就使得开发更加灵活和高效。可以抽离单独的公共模块。
  3. 数据驱动:Vue 组件中的数据是响应式的,即当数据发生变化时,相关的组件会自动更新。这样就能让应用的状态更加清晰可控。
  4. 模块化开发:使用组件化可以将一个大型应用程序拆分成多个小的、独立的部分。这样就能大大降低应用程序开发的难度、提高开发效率。
  5. 方便维护:组件化开发能够更好地实现代码复用,并且能够提高代码的可维护性。因为在组件化开发中,每个组件都是独立的,逻辑也比较清晰。

6.Vite 和 wekpack 区别

Vite采用ESM原生模块作为开发模式,在编译前不会将所有代码打包成一个文件,而是以“即时编译”的方式运行,仅编译当前正在编辑的文件及其相关依赖模块,从而提高了项目的启动速度和开发效率。在生产模式下,Vite会将所有需要的模块打包为一个或多个文件。另外,Vite还内置了对.vue文件的支持,并使用了Rollup作为构建工具以提供更快的构建速度。

Webpack则相对复杂一些,它采用CommonJS模块,需要通过配置文件来进行打包构建,所有模块都会被打包成一个或多个文件,因此启动时间较长,但它支持的功能和插件非常丰富,能够实现许多高级的构建场景和优化策略。另外,Webpack还支持热更新、Code Splitting等功能,能够实现更多的构建需求。

综上所述,Vite适合开发小型项目、快速迭代和H5开发等场景,而Webpack适合更加复杂的项目,它能够通过各种插件和配置文件来实现更加灵活和强大的构建功能。


7.vue打包后 assets 和 static 的区别 :

assets 目录和 static 目录都是存放静态资源文件的目录,但

  1. assets 目录:assets 目录专门用于存放应用程序中需要经过 webpack 编译的资源文件,如 CSS、LESS、SASS、JS 等文件。这些文件一般通过 import 或 require 等方式在 Vue 组件中进行引用,并会被 webpack 编译打包后输出到 dist 目录下的对应路径中。
  2. static 目录:static 目录用于存放应用程序中不需要经过 webpack 编译的资源文件,如图片、字体、第三方库(jQuery、Swiper)等文件。这些文件可以通过相对路径直接在 HTML 文件或 Vue 组件中进行引用,也可以通过 nginx、CDN 等方式进行部署。

 ⭕区别:

  • assets 目录中的文件需要通过 webpack 编译,在打包时被打包进入最终的 JS 文件中,而 static 目录下的文件不需要被编译,被拷贝到最终的打包目录中;
  • 相对于 assets 目录,static 目录更适用于静态资源文件,如图片、字体、第三方库等非 JS 类型文件。

  1. 相同点:  static 和assets下方的内容都是静态资源 比如 图片 js 字体图标 样式....
  2. 不同点:  assets静态资源: 是在运行npm run build进行静态资源打包;  static中的静态资源 一般不需要走打包压缩,直接进入打包好的目录,直接上传到服务器,由于没打包上传,传到服务器上之后,占用空间比较大


8.🌟v-if 和 v-show 的区别?

相同点:都可以控制元素的显示和隐藏

📜区别:

  1. v-show 时控制元素的 display值 来让元素显示和隐藏;v-if 显示隐藏时把 DOM元素 整个添加和删除
  2. v-if 有一个 局部编译/卸载 的过程,切换这个过程中会适当的销毁和重建内部的事件监听和子组件; v-show 只是简单的 css切换
  3. v-if 才是真正的 条件渲染;v-show 从 false 变成 true 的时候不会触发组件的生命周期,v-if 会触发生命周期
  4. v-if 的切换效率比较低 v-show 的效率比较高

9.Vue3 常用API有哪些?

1.createApp() —> 创建一个应用实例

说明: 等于vue2的—>new Vue()

使用场景: 写插件(封装全局组件会使用)

2.provide / inject —> 依赖注入

说明: 其实就是传值
使用场景: 某一个父组件传值 到后代组件,如果层级过多传递麻烦,所以使用缺点:不好维护和查询数据来源

3.directive
说明:自定义指令
场景:后台管理系统中的按钮权限控制( 一个用户拥有某些权限,但是只能查看和修改,不能删除)

4.mixin 将一组复用的选项合并成一个对象,并注入到组件中的方式

  • 说明: 1.全局混入 2.局部
  • 场景:可以添加生命周期
  • 缺点:不好维护和查询数据来源

5.app.config.globalProperties

说明:获取vue这个全局对象的属性和方法

场景:自己封装插件的时候需要把方法添加到对象中 

6.nextTick
说明: 等待下一次 DOM 更新刷新的工具方法: nextTick返回一个Pormise,回调函数是放在Promise中的,所以是异步执行的
场景: 就是把dom要更新,那么Vue是数据驱动dom,所以数据的赋值就要在nextTick进行

7.computed

  • 说明:计算属性
  • 场景:有缓存

8. reactive、ref
说明: 来定义数据的和Vue2的data类似 

9.watch
说明: 监听 (Vue3不需要深度监听)

10.markRaw()
说明: 不被 new Proxy 代理,说白了就是静态的数据

11. defineProps()
说明: 父组件传递的值,子组件使用 setup 的形式,需要用 defineProps 接收

12. defineEmits()
当前组件使用setup形式,自定义事件需要使用defineEmits

13. slot  一种用于插入和分发组件内容的机制。它可以让父组件向子组件中动态地插入内容,从而实现更加灵活和可复用的组件设计。

  • 说明: 分为 1.匿名 2. 具名 3.作用域
  • 场景: 后台管理系统,左侧是固定菜单,右侧是不固定内容,那么右侧就是slot

10.🌟 Vue2 和 Vue3 有什么区别

(1)Vue2 和 Vue3 双向绑定 方法不同

  •  Vue2 : Object.defineProperty()  后添加的属性是劫持不到的
  •  Vue3 : new Proxy()   即使后添加的也可以劫持到,还不需要循环

(2)$set 在 Vue3 中没有,因为 new Proxy 不需要

(3) v-if 和 v-for 优先级不同了

(4)关于写法

  • vue2是选项式AP
  • vue3可以向下兼容(选项式API),也可以组合式 API 或 setup 语法糖形式 

 (5)ref 和 $children 也不同

  • 作用对象不同:ref 用于在组件之间建立父子关系或在组件内部访问子组件,而 $children 只能访问当前组件的直接子组件。
  • 使用方式不同:ref 通过在组件或元素上设置 ref 属性来访问该组件或元素,可以通过在 this.$refs 中使用组件的 ref 名称来获取对应的组件或元素;而 $children 是当前组件的一个属性,可以直接在 this.$children 中访问其子组件列表。
  • 执行时机不同:ref 是在组件渲染后才能访问到对应的组件或元素,因为它们还没有生成对应的 DOM 元素;而 $children 是在组件挂载期间就能访问到其所有子组件
  • ⭕注意:由于 $children 只能访问当前组件的直接子组件,因此在嵌套较深的组件结构中使用 $children 可能会比较麻烦,而 ref 则不受这种限制,可以在任意层级的组件之间进行访问。

Vue3做了如下更新:

  1. ref 的使用方式发生了变化。在 Vue3 中,ref 被用作一种新的数据类型,用于对普通数据类型进行包装并提供响应式效果,而不再用于访问 DOM 元素。在使用 ref 创建 ref 对象时,需要通过 .value 访问其内部原始值,并且可以使用新的函数 reactive 和 toRefs 来替代部分使用 ref 的场景。
  2. $children 的使用方式没有太大变化。在 Vue3 中,它仍然是当前组件的一个属性,用于访问其直接子组件列表。但需要注意的是,在 Vue3 中,$children 会在组件挂载完成后再生成,因此在组件实例的 created 生命周期钩子中无法访问到它。(

    子组件可能会依赖父组件的状态或 props 来进行渲染。如果 $children 的访问时间太早,那么子组件有可能获取不到它所需要的数据,导致渲染错误。

    因此,在 Vue3 中,$children 的生成被调整到了组件挂载完成后。这样就可以在组件的 mounted 生命周期钩子函数中安全地访问 $children,确保我们访问到的子组件都已经正确地渲染和挂载了。

VUE 2-3 知识框架_星辰大海1412的博客-CSDN博客Vue.js 是一个由尤雨溪创建的渐进式 JavaScript 框架,是一款轻量级、高效、易用的前端开发框架。它采用了响应式数据绑定和组件化视图构建的方式,使得页面开发更加简单、直观,同时也提高了代码的可重用性和可维护性。无论是 Vue.js 2.x 还是 Vue.js 3.x 版本,都具有优秀的性能、良好的开发体验和强大的生态系统,是前端开发中不可或缺的重要工具。所以这篇文章也用于复习这些知识。https://blog.csdn.net/m0_61662775/article/details/131089903?spm=1001.2014.3001.5501


11.使用nuxt.js

是基于Vue的应用框架,关注的是渲染,可以开发服务端渲染应用的配置,帮助开发者快速构建 SSR 应用程序和静态站点。

(1)SSR: 服务端渲染(Server-Side Rendering)
⭕优点:

  • SSR 生成的是有内容的 HTML 页面,有利于搜索引擎的搜索
  • 优化了首屏加载时间

(2)SEO 搜索引擎优化(Search Engine Optimization)
(3)SPA  单页应用(Single Page Application) 的应用不利于搜索引擎SEO的操作

1️⃣在传统的浏览器端渲染(Client-Side Rendering)中,Vue 组件的渲染是通过 JavaScript 在浏览器中动态生成的。这种方式可以使得用户体验更加流畅,因为组件的渲染和更新都是在客户端完成的。但是,由于首次加载时需要下载并执行大量的 JavaScript 代码,相比于传统的服务器端渲染,这会导致页面加载速度较慢,以及 SEO 和首屏渲染等问题。

而应用 SSR 技术后,当服务器收到请求时,它会使用 Vue 的 SSR 引擎将组件在服务器上预渲染成 HTML 字符串,然后将此字符串直接发送给浏览器。这样就可以在浏览器中直接呈现出完整的 HTML 内容,从而减少了页面加载所需的时间和所需的 JavaScript 数据量,提高了网页的可访问性和搜索引擎优化效果。

SSR 可以让 Vue 应用程序具有快速的首屏加载体验,提高应用程序的可访问性,并有助于提升搜索引擎优化。但是,它也有一些注意事项和限制,例如需要考虑服务端渲染和客户端渲染之间的差异,以及需要更多的负载均衡和缓存策略等。


2️⃣SEO 的目的是通过对目标受众、竞争关键词、网站结构、页面内容、外部链接等进行分析和调整,使得网站在搜索引擎中的排名更加靠前,提高网站的曝光度和流量。

SEO 可以分为内部优化和外部优化两种方式。内部优化主要包括网站结构、URL 设计、页面内容、关键词密度、图片优化、站点地图等方面;外部优化则主要包括外部链接引用和社交媒体等方面。


3️⃣SPA,即单页应用(Single Page Application),是一种使用 JavaScript 等技术在单个页面上实现多个视图的应用程序。相比于传统的多页应用(MPA),SPA 不需要每次加载新的页面,而是动态地更新 DOM 元素来呈现不同的视图。

在 SPA 中,首先通过浏览器请求加载一个基础的 HTML、CSS 和 JavaScript 文件,然后在这些文件的基础上,根据用户的操作和应用程序的状态动态更新 DOM 元素,实现不同的视图。由于只有一次完整的页面加载,因此可以提高应用程序的性能和响应速度,并更好地使用现代 Web 技术,例如 AJAX、Web 套接字等。

SPA 能够提供良好的用户体验,并且具有较高的性能表现,但是也有一些缺点,例如:

  1. 首屏加载时间可能会较长,需要预加载或延迟加载的优化手段;
  2. 对 SEO 不友好,需要使用预渲染等技术来解决;
  3. 复杂的状态管理和路由控制可能需要使用第三方框架进行支持。

Nuxt.js 具有以下特点:

  • 支持服务端渲染和静态站点生成,支持 PWA;
  • 自动化路由配置和代码分割,提高页面加载性能;
  • 提供基于 Webpack 的模块化开发方式,并支持一些流行的插件如 Vuex、Vue-Router、Axios 等;
  • 提供一些常用的功能如异步数据获取、中间件、自动生成 sitemap 等。

使用 Nuxt.js 可以带来如下优势:

  • 通过服务端渲染和预渲染,可以提高 SEO(Search Engine Optimization)效果,使搜索引擎更好地爬取网站内容;
  • 通过静态站点生成,可以将网站部署到一些不支持服务器端运行的平台上,如 CDN 和静态托管服务等;
  • 统一设定、约定规则和自动化路由配置可以减少团队成员之间的沟通成本,并提高代码的可维护性和可读性;
  • 提供了丰富的插件库和模板,能够快速上手开发。

CDN,即内容分发网络(Content Delivery Network),是一种基于互联网的分布式网络技术,通过将静态资源文件(如图片、音视频等)缓存到分布在全球各地的节点服务器上,在用户请求时选择最近的节点进行响应,从而提高用户访问速度和体验。

传统的 Web 服务器部署只有一台或少数几台,当用户请求流量过大时,可能会导致 Web 服务器瘫痪或响应时间变慢。而 CDN 可以通过缓存和就近访问等技术,有效地分担 Web 服务器的流量压力,加速用户的访问速度和降低 Web 服务器负载。

CDN 的工作原理如下:

  1. CDN 首先会对静态资源进行缓存,缓存的位置通常是距离用户最近或比较重要的节点服务器上。
  2. 用户请求访问静态资源时,CDN 的服务节点会根据用户的地理位置和网络条件等,自动选择最近或最优的服务器节点响应请求。
  3. 如果 CDN 的缓存中没有用户请求的资源,它会向源站请求获取资源后缓存并返回给用户。源站也可以主动更新缓存中的资源。
  4. CDN 通过多层缓存和智能路由等方案,为用户提供稳定、快速的访问服务。
  5. CDN 的优点是可以提高访问速度、降低流量压力、保护源站的安全等。但它也需要一定的成本,同时对于动态资源如 HTML、PHP 等,CDN 的优化效果较差。因此在使用 CDN 时,需要根据具体情况进行选择和发挥其优势。

12.🌟watch 和 computed 的区别是什么?它们的使用场景分别是什么?

⭕数据响应式方面

1️⃣watch
可以监测数据的变化并触发回调函数。通过 watch 可以实现对某个数据进行监听,当这个数据变化时,可以触发相关操作,实现数据的同步和响应

watch 的使用场景主要包括:

  • 对于同步或异步操作需要在数据变化时触发的情况。
  • 当需要执行一些开销较大的计算或者异步操作时

watch 的本质是使用 Object.defineProperty 或者 Proxy 监听数据的变化,在数据变化时触发相应的回调函数。当我们使用 watch 监听一个数据时,Vue 内部会自动为这个数据创建一个 watcher,并将其添加到依赖列表中。当数据发生变化时,watcher 会自动被调度执行,从而实现数据的响应式更新。

watch 使用时可以通过 immediate 和 deep 选项来控制其行为:

  • immediate:当 immediate 为 true 时,watcher 会在初始值被设置后立即执行回调函数,不需要等到数据发生变化后再执行。
  • deep:当 deep 为 true 时,watcher 会递归遍历对象或数组的所有子属性,监听它们的变化。

除了这两个选项外,watch 还可以接收一个 handler 函数和一个 options 对象作为参数。handler 函数接收两个参数,分别是新值和旧值;options 对象包含了一些其他选项,例如:immediate、deep、flush 等。

⭕注意:由于 watch 使用了 Object.defineProperty 或者 Proxy 来监听数据的变化,因此它只能监听到数据的变化,而不能监听到数据的新增或删除操作。如果需要监听数组中元素的新增或删除操作,应该使用 Vue.set 或者 splice 方法来修改数组,以触发数据的响应式更新。

2️⃣computed
主要是用于计算衍生数据。computed 中的属性值可以访问其他观察的数据属性,当这些属性变化时,computed 中的属性也会重新求值。

 computed 的使用场景主要包括:

  • 对于需要根据其他数据计算得到的数据,可以使用 computed 进行计算。
  • 当一个数据需要被多个组件共享时,可以使用 computed 将其定义在根组件中,以便各个子组件进行访问

computed 的本质是 getter 函数,在定义 computed 属性时,我们需要指定一个 getter 函数,该函数根据需要的计算逻辑来返回相应的计算结果。

computed 的主要作用有:

  1. 计算属性:将一些复杂的计算逻辑封装到计算属性中,使得模板中的代码更简洁,易于理解和维护。
  2. 缓存:computed 的计算结果会被缓存起来,在下次使用该计算属性时,如果依赖的数据没有发生变化,就会立即返回缓存中的计算结果,避免了重复计算和不必要的性能浪费。
  3. 依赖追踪:computed 能够自动追踪数据依赖关系,只要依赖的数据发生了变化,computed 就会重新计算新值,保证了组件数据的响应式更新。

 13.如何SEO 优化

  • SSR 服务端渲染(Server-Side Rendering)
  • 预渲染 prerender-spa-plugin  webpack 插件,用于在构建静态单页应用时生成预渲染的静态 HTML 页面,可以提高页面的加载速度和 SEO 优化。

14.什么是 ref 和 $nextTick,如何使用?

  • ref 是一个指令,用于为 HTML 元素或子组件赋予一个唯一的标识(ID),通过这个标识可以在 Vue.js 实例的 JavaScript 代码中访问到该元素或组件。使用 ref 可以在 Vue.js 中进行 DOM 操作或获取子组件的实例
  • $nextTick 是 Vue.js 中的一个方法,用于在 DOM 更新之后执行一些操作。当我们需要在某个数据更新后立即执行操作,但此时新的 DOM 尚未渲染完成时,可以使用 $nextTick 方法。
<template>
  <div>
    <input type="text" ref="myInput">
  </div>
</template>

<script>
export default {
  mounted() {
    this.$refs.myInput.focus();
  },
};
</script>

使用 ref 指令定义输入框元素的标识符 myInput,然后在 JS 实例中使用 this.$refs.myInput 访问这个元素,并调用原生 focus() 方法将光标自动聚焦到这个输入框中。 

<template>
  <div>
    <p>{{ message }}</p>
    <button @click="updateMessage">更新消息</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: '旧消息',
    };
  },
  methods: {
    updateMessage() {
      this.message = '新消息';
      this.$nextTick(() => {
        console.log(this.$el.innerHTML);
      });
    },
  },
};
</script>

 当点击“更新消息”按钮时,会将 message 数据更新为“新消息”。实际上,这个更新操作并不会立即更新 DOM,而是等到下一个事件循环周期。因此,如果我们需要获取更新后的 DOM 内容,就需要使用 $nextTick 方法。

在实现方式上,$nextTick 接收一个回调函数作为参数,在 DOM 更新完成后执行该回调函数。在回调函数中可以访问更新后的 DOM 内容,或者执行其他需要在 DOM 更新后才能执行的逻辑。


 15.jQuery与vue、react有什么区别

  • jQuery 是一款备受欢迎的 JavaScript 库,主要用于 DOM 操作、事件处理和 AJAX 请求等。jQuery 的语法简洁明了,易于上手,可以快速地处理页面元素、响应用户操作和请求后端数据,因此在过去的几年中广泛应用于网页开发。
  • Vue.js 是一款渐进式 JavaScript 框架,它将视图层和数据层分离,提供了更加灵活和高效的组件化开发模式,并且支持响应式数据绑定、虚拟 DOM 等高级特性,是一个轻量、易用和高效的框架。React 是由 Facebook 开源的一款 JavaScript 库,它采用组件化开发模式,支持高性能的虚拟 DOM,可以用于构建大型、复杂的 Web 应用程序,具有良好的可扩展性和易维护性。 

📜总结:jQuery 作为一个纯粹的 JavaScript 库,可以快速地对页面进行操作和交互,但其门槛较低,适用于基础的网页开发。Vue.js 和 React 则注重组件化开发,提供了更加高效和灵活的开发方式,并且支持响应式数据绑定、虚拟 DOM 等高级特性,适用于构建大型、复杂的 Web 应用程序。


 16.虚拟 VDOM 性能会更好吗

①虚拟 DOM 所提供的最主要的优势是减少 DOM 操作的次数,从而减少浏览器的重绘和重新排版,达到提高性能的目的。当我们使用 React、Vue 等框架时,它们都会通过 diff 算法比较新旧两个虚拟 DOM 的差异,并且只更新发生变化的部分,而不是每次都重新渲染整个页面。这就避免了大量的无效 DOM 操作,从而提高了应用的性能。

②虚拟 DOM 还可以跨平台使用,因为它并不依赖于具体的浏览器实现,而是通过 JavaScript 实现一个轻量、可移植的 DOM 模型。这使得我们可以在 Web、移动端、桌面端等多个平台上开发相同的代码,提高了效率和一致性。

⭕ 虚拟 DOM 缺点:

需要在 JavaScript 中构建和比较虚拟 DOM 树,这可能会占用一定的 CPU 资源和内存,从而对于特别大的应用或低端设备可能会存在性能问题。此外,虚拟 DOM 也需要学习一些新的概念和技术,入门门槛较高。


 17.Vue2的 data 为什么是函数

在 Vue2 中,data 选项是一个函数,而不是一个普通的对象。这是因为在组件中,如果直接使用一个对象作为 data 选项,那么所有使用该组件的实例都会共享同一个数据对象,这在某些情况下可能会导致数据混乱的问题。

为了避免这种情况的发生,Vue2 引入了“组件作用域”的概念,即每个组件实例都有自己独立的作用域。因此,为了保证每个组件实例都有自己独立的数据,我们需要将 data 选项改成一个函数。这个函数返回一个新的对象,用于作为当前实例的数据。 

⭕ 注意:如果 data 选项返回的是一个对象,而不是一个函数,Vue2 会给出一个警告提示。


18.Pinia的使用场景 

 Pinia 是一个基于 Vue 3 Composition API 的状态管理库,它具有轻量、简单易用、类型安全等特点。它主要适用于以下场景:

  1. 中小型项目:对于中小型的项目,使用 Vuex 或者其他类似的状态管理库可能会有些过度设计。而 Pinia 提供了一种更加轻量、简单易用的状态管理方案。
  2. 多个 Vuex module:如果项目中存在多个 Vuex 的 module,而这些 module 可能会被不同页面使用,那么为了避免命名冲突等问题,就需要为它们命名不同的空间。而 Pinia 不需要使用命名空间,可以直接使用模块的名字来进行访问。
  3. TypeScript 项目:由于 Pinia 是基于 TypeScript 开发的,因此对于 TypeScript 项目是非常友好的。使用 Pinia 可以带来更好的类型推断和安全性。
  4. 使用 Composition API:Vue 3 推出了 Composition API,在这种情况下,Pinia 可以完美地与 Composition API 配合使用,进一步简化我们的代码逻辑。

19. $nextTick 的机制,React 类似的 Hook

Vue 中的 nextTick 方法是用来在 DOM 更新之后执行回调函数的函数。在 Vue 中对数据进行修改时,由于 Vue 是基于异步渲染的,所以数据更新并不会立即同步到 DOM 上,而是在下一个 Event Loop 中进行处理。因此,我们有时候需要等待 Vue 完成 DOM 更新后再执行一些操作,这时候就可以使用 nextTick 方法。

✍🏻案例:假设要在 Vue 实例的某个数据变化后获取某个元素的高度,但是由于数据还未同步到 DOM 上,此时直接获取元素高度是无法得到正确结果的。这时,就可以在 nextTick 回调函数中进行操作。nextTick 方法提供了两种使用方式:

①调用 Vue 实例对象上的 $nextTick 方法,传入回调函数作为参数:

this.$nextTick(() => {
  // 获取元素的高度
  const height = this.$refs.myElement.clientHeight;
  console.log(`Element's height is ${height}px`);
});

② 使用 Promise 返回值,通过 then 方法实现回调函数:

Vue.nextTick().then(() => {
  // 获取元素高度
  const height = this.$refs.myElement.clientHeight;
  console.log(`Element's height is ${height}px`);
});

 ⭕在 React 中,类似的机制是使用 Hook 来实现,其中 useEffect Hook 可以在组件渲染完成后执行副作用(effect),可以用来监听 DOM 的更新状态。例如,我们可以使用 useEffect 来在组件渲染后获取元素的高度:

import { useState, useEffect, useRef } from 'react';

function MyComponent() {
  const [height, setHeight] = useState(0);
  const myElementRef = useRef(null);

  useEffect(() => {
    // 获取元素的高度
    const height = myElementRef.current.clientHeight;
    setHeight(height);
  }, []); // 第二个参数表示依赖项为空数组,只有在组件挂载后执行一次

  return (
    <div ref={myElementRef}>
      This is a component.
      <br />
      The height of this component is {height}px.
    </div>
  );
}

使用 useRef Hook 来获取组件中的 DOM 元素,然后在 useEffect 中获取元素的高度并更新状态。useEffect 的第二个参数是一个数组,用于指定依赖项,如果依赖项中的值发生变化,useEffect 会重新执行。在这个示例中,由于依赖项为空数组,所以只会在组件挂载后执行一次。通过 useEffect,我们可以实现类似 Vue 中的 nextTick 的效果,等待组件渲染完成后再执行回调函数。


20.MVVM,MVC,MVP的区别 

MVVM、MVC 和 MVP 是常见的软件架构模式,用于组织和管理软件的代码和逻辑。

1️⃣MVC(Model-View-Controller):

  • 概念:MVC 是最早出现的架构模式,将应用程序分为三个主要部分:模型、视图和控制器。
  • 模型(Model):负责处理应用程序的数据和业务逻辑。
  • 视图(View):负责显示数据或向用户展示界面,并接收用户的输入。
  • 控制器(Controller):负责处理用户的输入,根据输入更新模型和视图之间的关系。
  • 特点:MVC 具有松耦合的特点,使得各个组件可以独立修改和测试,提高了代码的可维护性和可扩展性。

2️⃣MVP(Model-View-Presenter):

  • 概念:MVP 是一种演化自 MVC 的模式,依然将应用程序分为三个主要部分:模型、视图和呈现器。
  • 模型(Model):负责处理应用程序的数据和业务逻辑。
  • 视图(View):负责显示数据或向用户展示界面,并处理用户输入事件。
  • 呈现器(Presenter):负责处理用户输入事件和更新模型和视图之间的关系。
  • 特点:MVP 的特点是视图和呈现器之间有明确的接口,可实现更好的单元测试和模块化开发,使得视图可以独立于模型进行修改,增强了代码的可测试性和可维护性。 

 3️⃣MVVM(Model-View-ViewModel):

  • 概念:MVVM 是一种基于数据绑定的架构模式,将应用程序分为三个主要部分:模型、视图和视图模型。
  • 模型(Model):负责处理应用程序的数据和业务逻辑。
  • 视图(View):负责显示数据或向用户展示界面,通过数据绑定将视图与视图模型关联。
  • 视图模型(ViewModel):负责管理视图所需的数据和行为,将模型数据转换成视图可以使用的形式。
  • 特点:MVVM 的特点是通过数据绑定机制实现视图和视图模型之间的自动同步,使得开发者只需要关注数据的变化和业务逻辑的处理,减少了界面和逻辑的耦合,提高了代码的可维护性和可扩展性。

📜总结:

  • MVC 是最早的架构模式,MVP 是在 MVC 的基础上演化而来,而 MVVM 是基于数据绑定的架构模式。
  • MVC 和 MVP 都是通过控制器或呈现器来处理用户输入和更新视图与模型之间的关系,而 MVVM 则通过数据绑定机制实现自动同步。
  • MVVM 的数据绑定机制可以减少界面和逻辑的耦合,开发者只需关注数据变化和业务逻辑的处理,提高了可维护性和可扩展性。
  • MVC 和 MVP 更加适用于传统的客户端开发,而 MVVM 更适用于前端开发和大规模数据驱动的应用程序。

21.mixin,extends的覆盖逻辑 

1️⃣Mixin(混入): Mixin 是一种将类的功能注入到另一个类中的机制。通过 mixin,一个类可以复用其他类的方法和属性,而无需继承整个类层次结构。

  • 添加 Mixin:一个类可以使用多个 Mixin,通过将 Mixin 的方法和属性复制到目标类中实现。
  • 方法解决冲突:当多个 Mixin 中有相同的方法时,解决冲突的方式通常是采用“最后一个定义优先”的规则,即最后混入的 Mixin 的方法会覆盖前面混入的 Mixin 的方法。
  • 属性解决冲突:当多个 Mixin 中有相同的属性时,解决冲突的方式通常是采用“就近访问”的规则,即访问属性时会使用最后混入的 Mixin 中的属性。
  • 🌟优点:可以灵活地组合和复用代码,并能避免多重继承可能引发的冲突和复杂性。

Mixin 的使用场景:

  1. 多重继承替代:如果语言不支持多重继承,可以使用 Mixin 来模拟多重继承的效果,将多个类的功能注入到目标类中,实现代码的复用。
  2. 组件复用:Mixin 可以用于将通用的行为和功能注入到多个组件中,使得组件之间具有共享的功能和特性,提高组件的复用率。
  3. 扩展框架或库:Mixin 可以用于扩展已有的框架或库的功能,通过将新增的方法和属性注入到现有的类中,实现对框架或库的功能定制和扩展。

2️⃣Extends(继承):Extends 是一种类之间的关系,子类继承父类的属性和方法,并可以在子类中进行修改和扩展。

  • 继承关系:子类继承父类,子类可以访问和重写父类的方法和属性。
  • 方法覆盖:当子类和父类中有相同的方法时,子类可以通过重写父类的方法来改变方法的实现逻辑。
  • 属性覆盖:当子类和父类中有相同的属性时,子类可以通过重写父类的属性来改变属性的初始值。 
  • 🌟优点:能够建立类之间的层次结构,子类可以继承和扩展父类的功能,提高了代码的可维护性和可扩展性

Extends 的使用场景:

  1. 建立类层次结构:通过继承来建立类之间的层次结构,将具有共同属性和行为的类组织在一起,提高代码的可读性和可维护性。
  2. 重用父类的功能:子类可以继承父类的方法和属性,并可以在子类中修改、扩展或重写这些方法和属性,实现对父类功能的重用和定制。
  3. 实现接口或抽象类:通过继承抽象类或实现接口的方式,子类可以强制性地实现父类定义的方法,从而保证代码的一致性和可靠性。

📜总结:

  • Mixin 是一种复用代码的机制,通过将其他类的方法和属性注入到目标类中实现。
  • Mixin 采用“最后一个定义优先”的规则解决方法冲突,采用“就近访问”的规则解决属性冲突。
  • Extends 是一种继承关系,子类继承父类的方法和属性,并可以修改和扩展它们。
  • Extends 通过方法重写和属性重写来改变继承自父类的方法和属性的实现和初始值。
  • Mixin 和 Extends 都能提高代码的复用性和灵活性,但应根据具体场景选择适合的方式。

22.路由拦截怎么实现?

在 Vue Router 中,路由拦截可以通过导航守卫(Navigation Guards)来实现。导航守卫是一组回调函数,用于控制路由导航的行为,包括在跳转前、跳转后、跳转取消等不同的阶段执行相应的逻辑。

Vue Router 提供了三种类型的导航守卫:

1️⃣全局守卫:全局守卫会在整个路由跳转过程中被触发。包含三个导航守卫:beforeEach、beforeResolve 和 afterEach。

  • beforeEach:在路由跳转前执行,可以用来做权限验证、登录状态检查等操作。
  • beforeResolve:在路由跳转确认前执行,和 beforeEach 类似,但在它之后解析异步路由组件。
  • afterEach:在路由跳转后执行,可以用来进行页面统计、滚动行为控制等操作。

2️⃣路由独享守卫:路由独享守卫只对指定的路由生效,它们定义在路由配置中的 beforeEnter 字段中。

3️⃣组件内守卫:组件内守卫分别在组件被复用时、路由参数发生变化时以及组件被销毁时触发。包含三个导航守卫:beforeRouteEnter、beforeRouteUpdate 和 beforeRouteLeave。

  • beforeRouteEnter:在路由进入组件前被调用,无法访问组件实例(this),但可以通过回调函数访问到组件实例。
  • beforeRouteUpdate:在当前路由改变,但是该组件被复用时调用。可以访问组件实例。
  • beforeRouteLeave:在离开当前路由时调用,可以用来做用户提示、保存草稿等操作。


通过使用这些导航守卫,你可以在不同的阶段对路由进行拦截,执行相应的逻辑控制。例如,可以在 beforeEach 守卫中判断用户是否有权限访问某个页面,如果没有则取消跳转;或者在 beforeRouteLeave 守卫中提示用户是否放弃未保存的内容等。

⭕注意:在导航守卫中,你可以通过调用 next 方法来控制路由的行为。next 方法接受一个参数,可以指定跳转的路径或取消导航。通过使用不同的参数,你可以实现不同的路由跳转控制逻辑。

Vue-Router 动态展示路由_星辰大海1412的博客-CSDN博客Vue Router 是 Vue.js 官方提供的路由管理器,它能够实现单页面应用(SPA)中的前端路由功能。其中的动态展示路由是指根据用户的操作或其他条件,动态地加载和展示相应的页面组件。这篇文章将详细地介绍相关的操作。https://blog.csdn.net/m0_61662775/article/details/131411487?spm=1001.2014.3001.5501


Node.JS

1.Node.js 的运行机制 

  • 单线程和异步 I/O:Node.js 引擎采用单线程模型,但通过事件循环和异步 I/O 实现了非阻塞 I/O 操作。这意味着,在 Node.js 中,所有的 I/O 操作都是异步的,并且操作完成后会通过回调函数来通知程序运行结果。这样可以避免在 I/O 操作中阻塞主线程,从而提高应用程序的响应速度和吞吐量。
  • 事件驱动:Node.js 采用事件驱动的编程模型,所有的异步 I/O 操作都会触发相应的事件。在 Node.js 中,事件的注册、监听和处理都是通过事件触发器 EventEmitter 来实现。通过 EventEmitter,可以在应用程序中对各种事件进行监听和处理,以便及时地响应各种异步操作的结果


2.市场上使用 Node.js 的原因

  1. 异步 I/O 模型:Node.js 采用异步 I/O 模型,可以高效地处理大量并发请求以及 I/O 密集型应用程序。这使得 Node.js 成为开发高性能服务器端应用程序的理想选择。
  2. JavaScript 统一:Node.js 使用 JavaScript 编程语言来编写服务器端代码,避免了前后端分离时因为语言不统一而引入的代码风格和技术栈不统一等问题。
  3. 生态环境丰富:Node.js 拥有大量的模块和库,可以为开发者提供全面的支持和功能扩展,比如 Express、Socket.io 等等。
  4. 跨平台:Node.js 可以运行在 Windows、Mac 和 Linux 等各种操作系统上,并且具有一致的 API 接口和行为,可方便在不同平台之间进行迁移。
  5. 社区活跃:Node.js 在全球范围内拥有广泛的社区支持,不断涌现新的优秀项目和工具,可以为开发者提供最新的技术和解决方案。


Wekpack

1. loader 以及 plugin

1️⃣Loader 是 Webpack 中用来对模块进行预处理的工具,它允许对不同类型的文件进行转换,以满足项目所需。在 Webpack 的执行流程中,loader 会在编译阶段执行,在加载模块时将源代码转换为可执行的 JavaScript 代码。 

module.exports = function(source) {
  // 对模块源代码进行处理
  const result = someTransformation(source);
  // 返回处理后的结果
  return result;
};

2️⃣Webpack 的插件(Plugin)则是用来扩展 Webpack 功能的一种机制,它可以监听 Webpack 构建过程中的各种事件,执行自定义的逻辑,实现一些特殊的需求功能。

Plugin 通常会包含一个 apply 方法,它接受一个 Compiler 对象作为参数,在 Compiler 对象的生命周期事件中注册相应的钩子函数。当 Webpack 在执行过程中触发了这些事件后,对应的钩子函数将被依次执行。通过插件机制,我们可以在 Webpack 构建过程中对各个阶段进行干预,完成一些定制化的操作,例如代码压缩、资源优化、添加环境变量等等。

class MyPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('MyPlugin', stats => {
      // 构建完成后执行的逻辑
      console.log('Build is done!');
    });
  }
}

定义了一个名为 MyPlugin 的插件类,它注册了 Compiler 的 done 钩子函数,在构建完成后执行一些简单的输出逻辑。在使用插件时,只需要将其实例化并添加到 plugins 配置数组中即可使用。例如:

const webpackConfig = {
  // ...
  plugins: [new MyPlugin()],
};

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值