前端知识总结

一、HTML篇

1.语义话的目的是什么?

提高代码的可读性,页面内容结构化,便于开发人员的代码编写,同时提高的用户体验;有利于SEO ,便于搜索引擎爬虫爬取有效信息。
常见的语义化标签:

~

标签:标题标签,h1等级最高,h6等级最低
:用于定义页面的介绍展示区域,通常包括网站logo、主导航、全站链接以及搜索框 :定义页面的导航链接部分区域
:定义页面的主要内容,一个页面只能使用一次。
:定义页面独立的内容,它可以有自己的header、footer、sections等
:元素用于标记文档的各个部分,例如长表单文章的章节或主要部分 :一般用于侧边栏
:文档的底部信息 :呈现小号字体效果 :用于强调文本

2.HTML5新特征

  1. Canvas绘图以及SVG绘图。
  2. 拖放(Drag and drop)API
  3. 语义化标签(header、nav、footer、article、section)
  4. 音频、视频(audio、video)API
  5. 地理定位(Geolocation)
  6. 本地离线存储(localStorage),长期存储数据,关闭浏览器后不丢失。
  7. 会话储存(sessionStorage),数据在关闭浏览器后自动删除。
  8. 表单控件(calendar、date、time、email、url、search)
  9. 新技术如Web Worker、Web Socket。

src和href的区别

src和href都是用来引用外部的资源,它们的区别如下:
src:
表示对资源的引用,它指向的内容会嵌入到当前标签所在的位置。src会将其指向的资源下载并应⽤到⽂档内,如请求js脚本。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执⾏完毕,所以⼀般js脚本会放在页面底部。
href:
表示超文本引用,它指向一些网络资源,建立和当前元素或本文档的链接关系。当浏览器识别到它他指向的⽂件时,就会并⾏下载资源,不会停⽌对当前⽂档的处理。
常用在a、link等标签上。

常⽤的meta标签有哪些

meta 标签由 name 和 content 属性定义,用来描述网页文档的属性,比如网页的作者,网页描述,关键词等,除了HTTP标准固定了一些name作为大家使用的共识,开发者还可以自定义name。

常用的meta标签:

charset,用来描述HTML文档的编码类型:

<meta charset="UTF-8" >

keywords,页面关键词:

<meta name="keywords" content="关键词" />

description,页面描述:

<meta name="description" content="页面描述内容" />

refresh,页面重定向和刷新:

<meta http-equiv="refresh" content="0;url=" />

viewport,适配移动端,可以控制视口的大小和比例:

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

viewport的content 参数有以下几种:

  1. width viewport :宽度(数值/device-width)
  2. height viewport :高度(数值/device-height)
  3. initial-scale :初始缩放比例
  4. maximum-scale :最大缩放比例
  5. minimum-scale :最小缩放比例
  6. user-scalable :是否允许用户缩放(yes/no)

robots,搜索引擎索引方式:

<meta name="robots" content="index,follow" />

robots的content 参数有以下几种:

  1. all:文件将被检索,且页面上的链接可以被查询;
  2. none:文件将不被检索,且页面上的链接不可以被查询;
  3. index:文件将被检索;
  4. follow:页面上的链接可以被查询;
  5. noindex:文件将不被检索;
  6. nofollow:页面上的链接不可以被查询。

iframe 标签有那些优点和缺点?

iframe 元素会创建包含另外一个文档的内联框架(即行内框架)。
优点:

  1. 用来加载速度较慢的内容(如广告)
  2. 可以使脚本可以并行下载
  3. 可以实现跨子域通信

缺点:

  1. iframe 会阻塞主页面的 onload 事件
  2. 无法被一些搜索引擎索识别
  3. 会产生很多页面,不容易管理

Canvas和SVG标签的区别

(1)SVG: SVG可缩放矢量图形(Scalable Vector Graphics)是基于可扩展标记语言XML描述的2D图形的语言,SVG基于XML就意味着SVG DOM中的每个元素都是可用的,可以为某个元素附加Javascript事件处理器。在 SVG 中,每个被绘制的图形均被视为对象。如果 SVG 对象的属性发生变化,那么浏览器能够自动重现图形。
其特点如下:

  1. 不依赖分辨率
  2. 支持事件处理器
  3. 最适合带有大型渲染区域的应用程序(比如谷歌地图)
  4. 复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快)
  5. 不适合游戏应用

(2)Canvas: Canvas是画布,通过Javascript来绘制2D图形,是逐像素进行渲染的。其位置发生改变,就会重新进行绘制。
其特点如下:

  1. 依赖分辨率

  2. 不支持事件处理器

  3. 弱的文本渲染能力

  4. 能够以 .png 或 .jpg 格式保存结果图像

  5. 最适合图像密集型的游戏,其中的许多对象会被频繁重绘

渐进增强和优雅降级之间的区别

(1)渐进增强(progressive enhancement) : 主要是针对低版本的浏览器进行页面重构,保证基本的功能情况下,再针对高级浏览器进行效果、交互等方面的改进和追加功能,以达到更好的用户体验。

(2)优雅降级 graceful degradation: 一开始就构建完整的功能,然后再针对低版本的浏览器进行兼容。
两者区别:

  • 优雅降级是从复杂的现状开始的,并试图减少用户体验的供给;而渐进增强是从一个非常基础的,能够起作用的版本开始的,并在此基础上不断扩充,以适应未来环境的需要;
  • 降级(功能衰竭)意味着往回看,而渐进增强则意味着往前看,同时保证其根基处于安全地带。

前缀为 data- 开头的元素属性是什么?

这是一种为 HTML 元素添加额外数据信息的方式,被称为 自定义属性

我们可以直接在元素标签上声明这样的数据属性:

<div id="mydiv" data-message="Hello,world" data-num="123"></div>

也可以使用 JavaScript 来操作元素的数据属性:

let mydiv = document.getElementById('mydiv')// 读取
console.log(mydiv.dataset.message)// 写入
mydiv.dataset.foo = "bar!!!"

注意:在各种现代前端框架出现后,这种原生的自定义属性已经变得不太常用了, 以前的使用频率非常高, 。

说一下HTML5的离线存储?

指的是没有网络连接的时候,可以正常访问应用,与网络连接时更新缓存文件

在 cache.manifest 文件中编写需要离线存储的资源:

在离线状态时,操作 window.applicationCache 进行离线缓存的操作。

如何清除缓存:更新 manifest 文件,通过 javascript 操作,清除浏览器缓存。

二、CSS篇

CSS选择器及其优先级

选择器 格式 优先级权重
id选择器 #id 100
类选择器 #classname 10
属性选择器 a[ref=“eee”] 10
伪类选择器 li:last-child 10
标签选择器 div 1
伪元素选择器 li:after 1
相邻兄弟选择器 h1+p 0
子选择器 ul>li 0
后代选择器 li a 0
通配符选择器 * 0

注意事项:

  • !important声明的样式的优先级最高;
  • 如果优先级相同,则最后出现的样式生效;
  • 继承得到的样式的优先级最低;
  • 通用选择器(*)、子选择器(>)和相邻同胞选择器(+)并不在这四个等级中,所以它们的权值都为 0 ;
  • 样式表的来源不同时,优先级顺序为:内联样式 > 内部样式 > 外部样式 > 浏览器用户自定义样式 > 浏览器默认样式。

常用的块与行属性内标签有哪些?有什么特征

块标签:div、h1~h6、ul、li、table、p、br、form。

  • 特征:独占一行,换行显示,可以设置宽高,可以嵌套块和行
  • 行标签:span、a、img、textarea、select、option、input。
  • 特征:只有在行内显示,内容撑开宽、高,不可以设置宽、高(img、input、textarea等除外)。

CSS3新特征

  • 圆角(border-radius)
  • 阴影(box-shadow)
  • 文字特效(text-shadow)
  • 线性渐变(gradient)
  • 变换(transform)
  • 更多的CSS选择器
  • 更多背景设置(background)
  • 色彩模式(rgba)
  • 伪元素(::selection)
  • 媒体查询(@media)
  • 多栏布局(column)
  • 图片边框(border-image)

link和@import的区别

两者都是外部引用CSS的方式,它们的区别如下:

link是XHTML标签,除了加载CSS外,还可以定义RSS等其他事务;@import属于CSS范畴,只能加载CSS。
link引用CSS时,在页面载入时同时加载;@import需要页面网页完全载入以后加载。
link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。
link支持使用Javascript控制DOM去改变样式;而@import不支持。

  1. link是XHTML标签,除了加载CSS外,还可以定义RSS等其他事务;@import属于CSS范畴,只能加载CSS。
  2. link引用CSS时,在页面载入时同时加载;@import需要页面网页完全载入以后加载。
  3. link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。
  4. link支持使用Javascript控制DOM去改变样式;而@import不支持。

transition和animation的区别

transition是过度属性, 强调过度,它的实现需要触发一个事件(比如鼠标移动上去,焦点,点击等)才执行动画。它类似于flash的补间动画,设置一个开始关键帧,一个结束关键帧。

animation是动画属性, 它的实现不需要触发事件,设定好时间之后可以自己执行,且可以循环一个动画。它也类似于flash的补间动画,但是它可以设置多个关键帧(用@keyframe定义)完成动画。

伪元素和伪类的区别和作用?

伪类是通过在元素选择器上加⼊伪类改变元素状态,⽽伪元素通过对元素的操作进⾏对元素的改变
伪元素: 在内容元素的前后插入额外的元素或样式,但是这些元素实际上并不在文档中生成。它们只在外部显示可见,但不会在文档的源代码中找到它们,因此,称为“伪”元素。例如:

p::before {content:"第一章:";}
p::after {content:"Hot!";}
p::first-line {background:red;}
p::first-letter {font-size:30px;}

**伪类:**将特殊的效果添加到特定选择器上。它是已有元素上添加类别的,不会产生新的元素。

a:hover {color: #FF00FF}
p:first-child {color: red}

介绍一下盒模型

盒模型由内容(content)、内边距(padding)、边框(border)、外边距(margin)组成。盒模型分为IE盒模型和W3C标准盒模型。

W3C标准盒模型 又叫content-box,元素宽度/高度由border+padding+content组成。(属性width,height只包含内容content,不包含border和padding)

IE盒模型 又叫border-box,元素宽度/高度由content组成。(属性width,height包含border和padding,指的是content+padding+border。)

box-sizing: content-box表示标准盒模型(默认值)
box-sizing: border-box表示IE盒模型(怪异盒模型)

CSS中有哪些长度单位?

  1. 绝对长度单位:px
  2. 百分比: %
  3. 相对父元素字体大小的倍数单位: em
  4. 相对于根元素字体大小的倍数的单位: rem
  5. 相对于视口*宽度的百分比(100vw即视窗宽度的100%): vw
  6. 相对于视口*高度的百分比(100vh即视窗高度的100%): vh

使用场景:

  • 对于只需要适配少部分移动设备,且分辨率对页面影响不大的,使用px即可 。
  • 对于需要适配各种移动设备,使用rem,例如需要适配iPhone和iPad等分辨率差别比较挺大的设备。

rem 适配方法如何计算 HTML 根字号及适配方案

通用方案

使用媒体查询,根据不同设备按比例设置html文字大小,然后页面元素使用rem作为尺寸单位,当html大小改变时,元素也会发生改变,从而达到等比缩放的适配

  1. 最后的公式:页面元素的rem值=页面元素值(px)/(屏幕宽度/划分的份数)
  2. 屏幕宽度/划分的份数 就是html font-size的大小
  3. 或者:页面元素的rem值,页面元素值(px)/html font-size字体大小

以750的尺寸为例,把屏幕划分为15等份,那么html根字号的大小就是750/15=50px。rem就是元素的px/根子号。100px的宽度就等于100/50=2rem

优: 有一定适用性,换算也较为简单
劣: 有兼容性的坑,对不同手机适配不是非常精准;需要设置多个媒体查询来适应不同 手机,单某款手机尺寸不在设置范围之内,会导致无法适配
网易方案

拿到设计稿除以 100,得到宽度 rem 值
通过给 html 的 style 设置 font-size,把 1 里面得到的宽度 rem 值代入 x document.documentElement.style.fontSize = document.documentElement.clientWidth / x + ‘px’;
设计稿 px/100 即可换算为 rem

优: 通过动态根 font-size 来做适配,基本无兼容性问题,适配较为精准,换算简便
劣: 无 viewport 缩放,且针对 iPhone 的 Retina 屏没有做适配,导致对一些手机的适配不是很到位
手淘方案

拿到设计稿除以 10,得到 font-size 基准值
引入 flexible
不要设置 meta 的 viewport 缩放值
设计稿 px/ font-size 基准值,即可换算为 rem

优: 通过动态根 font-size、viewport、dpr 来做适配,无兼容性问题,适配精准。
劣: 需要根据设计稿进行基准值换算,在不使用 sublime text 编辑器插件开发时, 单位计算复杂

display:none和visibility:hidden的区别

  • display:none:隐藏元素,在文档布局中不在给它分配空间(从文档中移除),会引起回流(重排)。
  • visibility:hidden: 隐藏元素,但是在文档布局中仍保留原来的空间(还在文档中),不会引起回流(重绘)。

CSS 优化和提高性能的方法有哪些?

加载性能:

  • (1)css压缩:将写好的css进行打包压缩,可以减小文件体积。
  • (2)css单一样式:当需要下边距和左边距的时候,很多时候会选择使用 margin:top 0 bottom 0;但margin-bottom:bottom;margin-left:left;执行效率会更高。
  • (3)减少使用@import,建议使用link,因为后者在页面加载时一起加载,前者是等待页面加载完成之后再进行加载。

选择器性能:

  • (1)关键选择器(key selector)。选择器的最后面的部分为关键选择器(即用来匹配目标元素的部分)。CSS选择符是从右到左进行匹配的。当使用后代选择器的时候,浏览器会遍历所有子元素来确定是否是指定的元素等等;
  • (2)如果规则拥有ID选择器作为其关键选择器,则不要为规则增加标签。过滤掉无关的规则(这样样式系统就不会浪费时间去匹配它们了)。
  • (3)避免使用通配规则,如*{}计算次数惊人,只对需要用到的元素进行选择。
  • (4)尽量少的去对标签进行选择,而是用class。
  • 最高不要超过三层
  • (5)尽量少的去使用后代选择器,降低选择器的权重值。后代选择器的开销是最高的,尽量将选择器的深度降到最低,最高不要超过三层,更多的使用类来关联每一个标签元素。
  • (6)了解哪些属性是可以通过继承而来的,然后避免对这些属性重复指定规则。

渲染性能:

  • (1)慎重使用高性能属性:浮动、定位。
  • (2)尽量减少页面重排、重绘。
  • (3)去除空规则:{}。空规则的产生原因一般来说是为了预留样式。去除这些空规则无疑能减少css文档体积。
  • (4)属性值为0时,不加单位。
  • (5)属性值为浮动小数0.**,可以省略小数点之前的0。
  • (6)标准化各种浏览器前缀:带浏览器前缀的在前。标准属性在后。
  • (7)不使用@import前缀,它会影响css的加载速度。
  • (8)选择器优化嵌套,尽量避免层级过深。
  • (9)css雪碧图,同一页面相近部分的小图标,方便使用,减少页面的请求次数,但是同时图片本身会变大,使用时,优劣考虑清楚,再使用。
  • (10)正确使用display的属性,由于display的作用,某些样式组合会无效,徒增样式体积的同时也影响解析性能。
  • (11)不滥用web字体。对于中文网站来说WebFonts可能很陌生,国外却很流行。web fonts通常体积庞大,而且一些浏览器在下载web fonts时会阻塞页面渲染损伤性能。

可维护性、健壮性:

  • (1)将具有相同属性的样式抽离出来,整合并通过class在页面中进行使用,提高css的可维护性。
  • (2)样式与内容分离:将css代码定义到外部css中。

display :inline-block 什么时候会显示间隙?

  • 有空格时会有间隙,可以删除空格解决;
  • margin正值时,可以让margin使用负值解决;
  • 使用font-size时,可通过设置font-size:0、letter-spacing、word-spacing解决;

怎么实现单行、多行文本溢出隐藏

  • 单行文本溢出
overflow: hidden;            // 溢出隐藏
text-overflow: ellipsis;      // 溢出用省略号显示
white-space: nowrap;         // 规定段落中的文本不进行换行
  • 多行文本溢出
overflow: hidden;            // 溢出隐藏
text-overflow: ellipsis;     // 溢出用省略号显示
display:-webkit-box;         // 作为弹性伸缩盒子模型显示。
-webkit-box-orient:vertical; // 设置伸缩盒子的子元素排列方式:从上到下垂直排列
-webkit-line-clamp:3;        // 显示的行数

用CSS 实现长宽为浏览器窗口一半的正方形
已知父元素宽高用%

width: 50%;
padding-top: 50%;
background-color: red;

用vw

width: 50vw;
height: 50vh;
background-color: red;

用CSS 实现高度为0.5像素的线条

这个可以用伪类来实现

.line::before {
            display: block;
            content: "";
            height: 1px;
            left: -50%;
            position: absolute;
            background-color: #333333;
            width: 200%; //设置为插入元素的两倍宽高
            -webkit-transform: scale(0.5);
            transform: scale(0.5);
            box-sizing: border-box;
        }

用CSS 实现三角形

向上

     width:0;
     height:0;   
     border-left:30px solid transparent;   
     border-right:30px solid transparent;   
     border-bottom:30px solid red;

实现一个扇形

用CSS实现扇形的思路和三角形基本一致,就是多了一个圆角的样式,实现一个90°的扇形:

div{
    border: 100px solid transparent;
    width: 0;
    heigt: 0;
    border-radius: 100px;
    border-top-color: red;
}

什么是BFC?BFC有什么特性?如何创建BFC?BFC有什么作用?

什么是BFC?

BFC全称是Block Formatting Context,意思就是块级格式化上下文。你可以把BFC看做一个容器,容器里边的元素不会影响到容器外部的元素。

BFC有什么特性?

  1. BFC是一个块级元素,块级元素在垂直方向上依次排列。
  2. BFC是一个独立的容器,内部元素不会影响容器外部的元素。
  3. 属于同一个BFC的两个盒子,外边距margin会发生重叠,并且取最大外边距。
  4. 计算BFC高度时,浮动元素也要参与计算。

如何创建BFC?

给父级元素添加以下任意样式

  • overflow: hidden;
  • display: flex;
  • display: inline-flex;
  • display: inline-block;
  • position: absolute;
  • position: fixed;

BFC有什么作用?

  1. 清除元素内部浮动
    只要把父元素设为BFC就可以清理子元素的浮动了,最常见的用法就是在父元素上设置overflow: hidden样式。
  2. 解决外边合并问题
    盒子垂直方向的距离由margin决定。属于同一个BFC的两个相邻盒子的margin会发生重叠。
  3. 阻止元素被浮动元素覆盖
    普通流体元素BFC后,为了和浮动元素不产生任何交集,顺着浮动边缘形成自己的封闭上下文。

常见的布局方法有哪些?他们的优缺点是什么

页面布局常用的方法有浮动、定位、flex、grid网格布局、栅格系统布局

浮动:

优点:兼容性好。

缺点:浮动会脱离标准文档流,因此要清除浮动。我们解决好这个问题即可。

绝对定位

优点:快捷。

缺点:导致子元素也脱离了标准文档流,可实用性差。

flex 布局(弹性盒)

优点:解决上面两个方法的不足,flex布局比较完美。移动端基本用 flex布局。
阮一峰Flex 布局教程

网格布局(grid)

CSS3中引入的布局,很好用。代码量简化了很多。

栅格系统布局
优点:可以适用于多端设备

页面两栏布局

一般两栏布局指的是左边一栏宽度固定,右边一栏宽度自适应,两栏布局的具体实现: 两栏布局

实现三栏布局

三栏布局一般指的是页面中一共有三栏,左右两栏宽度固定,中间自适应的布局,三栏布局的具体实现:三栏布局

实现双飞翼(圣杯)布局

1、利用定位实现两侧固定中间自适应

1.1)父盒子设置左右 padding 值
1.2)给左右盒子的 width 设置父盒子的 padding 值,然后分别定位到 padding 处.
1.3)中间盒子自适应

2、利用 flex 布局实现两侧固定中间自适应

2.1)父盒子设置 display:flex;
2.2)左右盒子设置固定宽高
2.3)中间盒子设置 flex:1 ;

3、利用 bfc 块级格式化上下文, 实现两侧固定中间自适应

3.1)左右固定宽高,进行浮动
3.2)中间 overflow: hidden;

如何实现水平垂直居中

  • 利用绝对定位,先将元素的左上角通过top:50%和left:50%定位到页面的中心,然后再通过translate来调整元素的中心点到页面的中心。该方法需要考虑浏览器兼容问题。
.father {   
    position: relative;
} 
.child {    
    position: absolute;    
    left: 50%;    top: 50%;    
    transform: translate(-50%,-50%);
}
  • 利用绝对定位,设置四个方向的值都为0,并将margin设置为auto,由于宽高固定,因此对应方向实现平分,可以实现水平和垂直方向上的居中。该方法适用于盒子有宽高的情况:
.father {
    position: relative;
}
 
.child {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
  • 利用绝对定位,先将元素的左上角通过top:50%和left:50%定位到页面的中心,然后再通过margin负值来调整元素的中心点到页面的中心。该方法适用于盒子宽高已知的情况
.father {
    position: relative;
}
 
.child {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -50px;     /* 自身 height 的一半 */
    margin-left: -50px;    /* 自身 width 的一半 */
}
  • 使用flex布局,通过align-items:center和justify-content:center设置容器的垂直和水平方向上为居中对齐,然后它的子元素也可以实现垂直和水平的居中。该方法要考虑兼容的问题,该方法在移动端用的较多:
.father {
    display: flex;
    justify-content:center;
    align-items:center;
}

响应式设计的概念及基本原理

响应式网站设计(Responsive Web design)是一个网站能够兼容多个终端,而不是为每一个终端做一个特定的版本。

关于原理: 基本原理是通过媒体查询(@media)查询检测不同的设备屏幕尺寸做处理。 关于兼容: 页面头部必须有mate声明的viewport。

<meta name="’viewport’" content="”width=device-width," initial-scale="1." maximum-scale="1,user-scalable=no”"/>

三、JS篇

Js有哪些数据类型

基本类型

  1. Number:数值,包括整型和浮点型。
  2. String:字符型。
  3. Undefined:未定义,声明变量时未赋值。
  4. Null:定义为空或者不存在。
  5. Boolean:布尔值,true or false。
  6. Symbol:独一无二的值。

引用数据类型

  1. Object:对象。
  2. Array:数组。
  3. Function:函数。

注:**Object.prototype.toString.call()**适用于所有类型的判断检测
参考详情
let const参考详情
ES6新特性?

  • 新增块级作用域let定义变量和const定义常量
  • 变量的解构赋值
  • 模板字符串 (‘${}’)
  • 默认参数(key=value)
  • 箭头函数(=>)
  • 扩展运算符(…)
  • 模块(import/export)
  • 类(class/extends)
  • Promise
  • Proxy
  • Symbol

了解关于es6的更多知识可以看阮一峰——ES6 入门教程

数据结构:八大数据结构分类

1、数组
2、栈
3、队列
4、链表
5、树
6、散列表
7、堆
8、图
八大数据结构分类 参考详见

说说你对堆区和栈区的理解

在操作系统中,内存被分为栈区和堆区

  • 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  • 堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。

在数据结构中:

  • 在数据结构中,栈中数据的存取方式为先进后出
  • 堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。

数据的储存方式

  • 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
  • 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

闭包的理解

理解:主要是为了设计私有的方法和变量。

  • 优点:可以避免全局变量造成污染。
  • 缺点:闭包会常驻内存,增加内存使用量,使用不当会造成内存泄漏。
  • 特征:(1)函数嵌套函数。(2)在函数内部可以引用外部的参数和变量。(3)参数和变量不会以垃圾回收机制回收。
  • 内存泄漏的解决方案:

1、尽量避开,不使用闭包,或者不同的使用场景使用对应的替代技术

2、在可能存在泄漏的地方把标识符引用null ==> re=null

数据类型检测的方式有哪些

数据类型的方法一般可以通过:typeof、instanceof、constructor、toString 四种常用方法

  • typeof
  • 判断基本数据类型,对于引用数据类型除了function返回’function‘,其余全部返回’object’。
console.log(typeof("123")) // string
  • instanceof
  • 区分引用数据类型,检测方法是检测的类型在当前实例的原型链上,用其检测出来的结果都是true,不太适合用于简单数据类型的检测,检测过程繁琐且对于简单数据类型中的undefined, null, symbol检测不出来。
let str = "123"
console.log(str instanceof String)  //true
  • constructor
  • 检测引用数据类型,检测方法是获取实例的构造函数判断和某个类是否相同,如果相同就说明该数据是符合那个数据类型的,这种方法不会把原型链上的其他类也加入进来,避免了原型链的干扰。
console.log((10).constructor) // Number() { [native code] }
  • Object.prototype.toString.call()
  • 适用于所有类型的判断检测,检测方法是Object.prototype.toString.call(数据) 返回的是该数据类型的字符串。(举例:字符串返回的是[object String])

- instanceof的实现原理:
验证当前类的原型prototype是否会出现在实例的原型链__proto__上,只要在它的原型链上,则结果都为true。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,找到返回true,未找到返回false。

- Object.prototype.toString.call原理
Object.prototype.toString 表示一个返回对象类型的字符串,call()方法可以改变this的指向,那么把Object.prototype.toString()方法指向不同的数据类型上面,返回不同的结果

判断数组的方式有哪些

  1. 通过Object.prototype.toString.call()做判断
  2. 通过原型链做判断
  3. 通过ES6的Array.isArray()做判断
  4. 通过instanceof做判断
  5. 通过Array.prototype.isPrototypeOf

null和undefined区别

  • 首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
  • undefined 代表的含义是未定义 ,null 代表的含义是空对象 。一般变量声明了但还没有定义的时候会返回
    undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。
  • undefined 在 JavaScript 中不是一个保留字,这意味着可以使用 undefined
    来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 undefined值,比如说 void 0。
  • 当对这两种类型使用 typeof 进行判断时,Null 类型化会返回 “object”undefined返回undefined
<script>
    var exp1 ;
    console.log(typeof exp1);//undefined
    console.log(typeof exp2);//undefined
    console.log(exp1 == undefined);//true   
    console.log(exp2 == undefined);//报错
    console.log(typeof exp1 == 'undefined');//true
    console.log(typeof exp2 == 'undefined');//true  typeof运算符可以用于未被声明的变量
</script>
  var func = function(){
        console.log("你好");
    }
    var obj = {"name":"john"};          
    console.log(typeof 5);            //number
    console.log(typeof 'name');        //string
    console.log(typeof false);        //boolean
    console.log(typeof null);        // object
    console.log(typeof undefined);    // undefined
    console.log(typeof func);        // object
    console.log(typeof obj);        // object
//所有的返回值的类型都会 字符串  类型,注意都是小写字母;但是有一点缺陷就是函数和对象以及DOM对象返回的值都是object,所以typeof用来监测数据类型的时候,如果监测基本数据类型还比较可靠,但是监测对象的时候却无太大作用。
</script>

typeof null 的结果为什么是 Object

在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits) 以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有五种数据类型:

000: object   - 当前存储的数据指向一个对象。
  1: int      - 当前存储的数据是一个 31 位的有符号整数。
010: double   - 当前存储的数据指向一个双精度的浮点数。
100: string   - 当前存储的数据指向一个字符串。
110: boolean  - 当前存储的数据是布尔值。

如果最低位是 1, 则类型标签标志位的长度只有一位, 如果最低位是 0, 则类型标签标志位的长度占三位, 为存储其他四种数据类型提供了额外两个 bit 的长度。

有两种特殊数据类型:

  • undefined 的值是 (-2)30 (一个超出整数范围的数字)
  • null 的值是机器码 NULL 指针(null 指针的值全是 0)

那也就是说 null 的类型标签也是 000,和 Object 的类型标签一样, 所以会被判定为 Object。

|| 和 && 操作符的返回值

|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先强制转换为布尔类型,然后再执行条件判断。

  • 对于 || 来说,如果条件判断结果为 true 就返回第一个操作数的值,如果为 false 就返回第二个操作数的值。
  • && 则相反,如果条件判断结果为 true 就返回第二个操作数的值,如果为 false 就返回第一个操作数的值。

|| 和 && 返回它们其中一个操作数的值,而非条件判断的结果

如何判断一个对象是否存在

if(typeof obj == 'undefined'){
    var obj = {}
}

if (myObj === undefined) {
  var myObj = { };
}

Object.is() 与比较操作符 == 和 === 的区别?

  • 使用双等号(==) 进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。
  • 使用三等号(===)进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false。
  • 使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个NaN 是相等的。

相同点: 都是判定两个值是否相等 不同点:== 只比较值不比较类型,而 ===会判断类型

原型,原型链

原型

①所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象

②所有函数都有一个prototype(原型)属性,属性值是一个普通的对象

③所有引用类型的__proto__属性指向它构造函数的prototype

var a = [1,2,3];
a.__proto__ === Array.prototype; // true

原型链
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。

原型,原型链 参考详见

对象的继承

常见的:

  • 原型链继承
  • 借用构造函数继承
  • 原型链+借用构造函数的组合继承(使用 call 或 applay 方法)
  • ES6中class 的继承(class可以通过extends关键字实现继承)

简述一下你理解的面向对象

  • 面向对象是基于万物皆对象这个哲学观点. 把一个对象抽象成类,具体上就是把一个对象的静态特征和动态特征抽象成属性和方法,也就是把一类事物的算法和数据结构封装在一个类之中,程序就是多个对象和互相之间的通信组成的。
  • 面向对象具有封装性,继承性,多态性。
  • 封装: 隐蔽了对象内部不需要暴露的细节,使得内部细节的变动跟外界脱离,只依靠接口进行通信.封装性降低了编程的复杂性。
    - 继承: 使得新建一个类变得容易,一个类从派生类那里获得其非私有的方法和公用属性的繁琐工作交给了编译器。
    - 多态: 继承和实现接口和运行时的类型绑定机制所产生的多态,使得不同的类所产生的对象能够对相同的消息作出不同的反应,极大地提高了代码的通用性。

对this的理解

this是一个在运行时才进行绑定的引用,在不同的情况下它可能会被绑定不同的对象。

如何判断 this 的指向

第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。
第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时,this 指向这个对象。
第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。
第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。

this绑定的优先级

new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级

数组方法

js 数组详细操作方法及解析合集 --掘金
JavaScript常用数组操作方法,包含ES6方法
ES6——数组(方法总结)

————————————————
是否会改变原数组
会改变: push()pop(),shift(),unshift(),splice(),sort(),reverse()。

不变: concat(),split(),slice()。
参考详见

数组去重

借助ES6提供的Set结构 new Set() 简单好用 强烈推荐
直接给一个新的数组里面,利用es6的延展运算符

 var arr = [1,9,8,8,7,2,5,3,3,3,2,3,1,4,5];
  console.log(arr);   
  function noRepeat(arr){
    var newArr = [...new Set(arr)]; //利用了Set结构不能接收重复数据的特点
    return newArr
  }
  var arr2 = noRepeat(arr)
  console.log(arr2);

利用 filter() 去重

var arr =['apple','apps','pear','apple','orange','apps']; 
  var newArr = arr.filter(function(item,index){
     return arr.indexOf(item) === index;  // 因为indexOf 只能查找到第一个  
  });
console.log(newArr); 

利用双重for循环

var arr = [1,9,8,8,7,2,5,3,3,3,2,3,1,4,5,444,55,22];
console.log(arr);    
function noRepeat(arr){
   for (var i = 0; i < arr.length; i++) {
       for (var j = 0; j < arr.length; j++) {
           if (arr[i] == arr[j] && i != j) { 
           //将后面重复的数删掉
              arr.splice(j, 1);
            }
       }
    }
    return arr;
}
var arr2  = noRepeat(arr);
console.log(arr2);

注 :如果有多维数组如 [1,[2],[3,[2,3,4,5]] ] 先扁平化再去重,用Array.flat(Infinity)实现扁平化。

数组排序

var arr = [123,203,23,13,34,65,65,45,89,13,1];
function func(a,b){
	return a-b;
}
console.log(arr.sort(func)); 

数组扁平化处理

//循环递归
var arr = [[1, 2, 8, [6, 7]], 3, [3, 6, 9], 4]
 
function getNewArr(arr) {
  // 定义新数组用于存储所有元素
  var newArr = []
  // 遍历原数组中的每个元素
  for (var i = 0; i < arr.length; i++) {
    // 判断当前元素是否为数组
    if (Array.isArray(arr[i])) {
      // 若当前元素为数组时,调用函数本身继续判断,通过 concat 方法连接函数返回的数组
      newArr = newArr.concat(getNewArr(arr[i]))
    } else { // 若不是数组直接将当前元素追加到新数组的末尾
      newArr.push(arr[i])
    }
  }
  // 循环结束将新数组返回
  return newArr
}
 
console.log(arr) // [[1, 2, 8, [6, 7]], 3, [3, 6, 9], 4]
console.log(getNewArr(arr)) // [1, 2, 8, 6, 7, 3, 3, 6, 9, 4]

//flat()
let arr = [[1,2],[3,4],[[6,7],[[8],[9,10]]]];
let el = arr.flat(Infinity);
console.log(el);  // [1, 2, 3, 4, 6, 7, 8, 9, 10];

// 利用arr.some判断当数组中还有数组的话,递归调用flatten扁平函数(利用es6展开运算符扁平), 用concat连接,最终返回arr;
function steamroller4(arr){
  while(arr.some(item=> Array.isArray(item))){
    // arr=[].concat.apply([],arr)
    arr=[].concat(...arr)
  }
  return arr
}
console.log(steamroller4(arr))

//toString、split和map结合
var arr = [[1, 2, 8, [6, 7]], 3, [3, 6, 9], 4]
 
function getNewArr(arr) {
  var newArr = arr.toString().split(',').map(function(item) {
    // 使用 + 号将当前元素转成数字
    return +item
  })
 
  return newArr
}
 
console.log(getNewArr(arr)) // [1, 2, 8, 6, 7, 3, 3, 6, 9, 4]

数组多维转一维去重排序

  • 扁平化:arr.flat(Infinity)
  • 去重:[…new Set(arr)] Array.from(new
  • Set(arr)) 排序:sort()
let arr = [2, 2, 1, 45, 58, [154, 4, 14, 14, [4587, 45, 125, [145, 20, 145, 23, [12, 10]]]]]
 
arr = Array.from(new Set(arr.flat(Infinity))).sort((a, b) => a - b)
 
console.log(arr)

for…in和for…of的区别

参考详见

for…of 是ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,和ES3中的for…in的区别如下

  • for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;
  • for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;
  • 对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;

总结: for…in 循环主要是为了遍历对象而生,不适用于遍历数组;for…of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。

forEach和map的区别

1、相同点

  • 1) 都是循环遍历数组中的每一项。
  • 2) 每次执行匿名函数都支持三个参数,参数分别为item(当前每一项),index(索引值),arr(原数组)。
  • 3) 匿名函数中的this都是指向window。
  • 4)只能遍历数组。

2、不同点

  1. forEach没有返回值,map返回新的数组。
  2. map创建新数组,forEach不修改原数组。

3.使用场景

  1. forEach适合于你并不打算改变数据的时候。
  2. map()适用于你要改变数据值的时候。不仅仅在于它更快,而且返回一个新的数组。这样的优点在于你可以使用复合(composition)(map(), filter(), reduce()等组合使用)。

性能上来说for>forEach>map

排序方式

- 冒泡排序: 比较所有相邻元素,如果第一个比第二个大,则交换它们。
- 选择排序: 找到数组中的最小值,选中它并将其放置在第一位。
- 插入排序: 从第二个数开始往前比,比它大就往后排。
- 归并排序: 把数组劈成两半,再递归地对数组进行“分”操作,直到分成一个个单独的数。
- 快速排序: 从数组中任意选择一个基准,所有比基准小的元素放到基准前面,比基准大的元素放到基准的后面。
参考详见

事件循环(Event Loop)

原因:JavaScript是单线程,所有任务需要排队,前一个任务结束,才会执行后一个任务。

所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

同步任务: 在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务: 不进入主线程、而进入"任务队列"的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

同步和异步任务分别进入不同的执行环境, 先执行同步任务,把异步任务放入循环队列当中挂起,等待同步任务执行完,再执行队列中的异步任务。异步任务先执行微观任务,再执行宏观任务。一直这样循环,反复执行。

事件循环Event Loop又叫事件队列,两者是一个概念

微任务: Promise.then、catch、finally、async/await。
宏任务: 整体代码 Script、UI 渲染、setTimeout、setInterval、Dom事件、ajax事件。
详情可看JavaScript 运行机制详解:再谈Event Loop

注:js中同步队列、异步队列

同步: 前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的,同步的;同步任务都在主线程上执行,形成一个执行栈;
异步: 同时执行多个任务,异步任务(大部分异步任务为回调函数)会被放在任务队列当中

JavaScript中的异步机制可以分为以下几种:

  • 回调函数 的方式
  • Promise 的方式
  • generator 的方式
  • async 函数 的方式

什么是回调地狱?回调地狱会带来什么问题?

回调函数的层层嵌套,就叫做回调地狱。回调地狱会造成代码可复用性不强,可阅读性差,可维护性(迭代性差),扩展性差等等问题。

浏览器中的事件循环面试题

async function async1() {
  console.log("async1 start")  //二
  await async2()   
  console.log("async1 end")   //六
}

async function async2() {
  console.log("async2")  //三
}

console.log("script start")   //一

setTimeout(function () {
  console.log("setTimeOut")  //八
}, 0)

async1()  

new Promise(function (resolve) {
  console.log("promise1")  //四
  resolve()
}).then(function () {
  console.log("promise2")   //七
})

console.log("script end")  //五


事件参考详见

setTimeout(function () {
  console.log("setTimeOut1") //六
  new Promise(function (resolve) {
    resolve()
  }).then(function () {
    new Promise(function (resolve) {
      resolve()
    }).then(function () {
      console.log("then4") //八
    })
    console.log("then2") //七
  })
})


new Promise(function (resolve) {
  console.log("promise1") //一
  resolve()
}).then(function () {
  console.log("then1") //三
})



setTimeout(function () {
  console.log("setTimeOut2") //九
})



console.log(2) //二


queueMicrotask(() => {
  console.log("queueMicrotask1") //四
})


new Promise(function (resolve) {
  resolve()
}).then(function () {
  console.log("then3") //五
})


Promise

Promise参考详见

  • 含义:异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,用来解决回调地狱。

promise本身只是一个容器,真正异步的是它的两个回调resolve()和reject()

promise本质 不是控制 异步代码的执行顺序(无法控制) , 而是控制异步代码结果处理的顺序

promise实例有哪些状态,怎么改变状态

(1)有三种状态

  1. pending: 初始状态,既不是成功,也不是失败状态。
  2. resolved(fulfilled): 意味着操作成功完成。
  3. rejected: 意味着操作失败。

当把一件事情交给promise时,它的状态就是Pending,任务完成了状态就变成了Resolved、没有完成失败了就变成了Rejected。

如何改变 promise 的状态

resolve(value): 如果当前是 pending 就会变为 resolved
reject(error): 如果当前是 pending 就会变为 rejected
抛出异常: 如果当前是 pending 就会变为 rejected

注意: 一旦从进行状态变成为其他状态就永远不能更改状态了。

创建Promise实例有哪些方法

new Promise((resolve,reject)=>{ … })

一般情况下都会使用new Promise()来创建promise对象,但是也可以使用promise.resolve和promise.reject这两个方法:
Promise.resolve

Promise.resolve(value)的返回值也是一个promise对象,可以对返回值进行.then调用,代码如下:

//成功函数
Promise.resolve(11).then(function(value){
  console.log(value); // 打印出11
});

Promise.reject

Promise.reject 也是new Promise的快捷形式,也创建一个promise对象。代码如下:

//失败函数
Promise.reject(new Error(“出错了!!”));

Promise有哪些实例方法

then

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中第二个参数可以省略。 then方法返回的是一个新的Promise实例(不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

catch (Promise原型上的)

该方法相当于then方法的第二个参数,指向reject的回调函数。不过catch方法还有一个作用,就是在执行resolve回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入catch方法中。

finally

finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。

server.listen(port)
  .then(function () {
    // ...
  })
  .finally(server.stop);

Promise有哪些静态方法

all

all方法可以完成并发任务, 它接收一个数组,数组的每一项都是一个promise对象,返回一个Promise实例。当数组中所有的promise的状态都达到resolved的时候,all方法的状态就会变成resolved,如果有一个状态变成了rejected,那么all方法的状态就会变成rejected。

race

race方法和all一样,接受的参数是一个每项都是promise的数组,但是与all不同的是,当最先执行完的事件执行完之后,就直接返回该promise对象的值。如果第一个promise对象状态变成resolved,那自身的状态变成了resolved;反之第一个promise变成rejected,那自身状态就会变成rejected。

any

它接收一个数组,数组的每一项都是一个promise对象,该方法会返回一个新的 promise,数组内的任意一个 promise 变成了resolved状态,那么由该方法所返回的 promise 就会变成resolved状态。如果数组内的 promise 状态都是rejected,那么该方法所返回的 promise 就会变成rejected状态,

resolve、reject

用来生成对应状态的Promise实例

Promise.all、Promise.race、Promise.any的区别

all: 成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。

race: 哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

any: 返回最快的成功结果,如果全部失败就返回失败结果。

改变 promise 状态和指定回调函数谁先谁后?

  1. 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
  2. 如何先改状态再指定回调?
  • 在执行器中直接调用 resolve()/reject()
  • 延迟更长时间才调用 then()
  1. 什么时候才能得到数据?
  • 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
  • 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据

promise.then()返回的新 promise 的结果状态由什么决定?

简单表达: 由 then()指定的回调函数执行的结果决定

详细表达:

如果抛出异常, 新 promise 变为 rejected, 参数为抛出的异常
如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果

如何中断 promise 链?

参考详见

async/await语法

async 函数它就是 Generator 函数的语法糖,也就是处理异步操作的另一种高级写法

async 函数的实现原理

async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

async function fn(args) {
  // ...
}
// 等同于
function fn(args) {
  return spawn(function* () {  // spawn函数就是自动执行器
    // ...
  });
}

async函数的返回值

  • async函数返回一个 Promise 对象。
  • async函数内部return语句返回的值,会成为then方法回调函数的参数。
  • async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。

await 到底在等待什么?

await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。

await 表达式的运算结果取决于它等的是什么。

  • 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
  • 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到
    resolve 的值,作为 await 表达式的运算结果。

什么是顶层await

从 ES2022 开始,允许在模块的顶层独立使用await命令,使得上面那行代码不会报错了。它的主要目的是使用await解决模块异步加载的问题

import { AsyncFun } from 'module'
await AsyncFun()
console.log(123)

await的使用注意点

  • await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。
  • 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
  • await命令只能用在async函数之中,如果用在普通函数,就会报错。
  • async 函数可以保留运行堆栈。

async语法怎么捕获异常

async函数内部的异常可以通过 .catch()或者 try/catch来捕获,区别是

  • try/catch 能捕获所有异常,try语句抛出错误后会执行catch语句,try语句内后面的内容不会执行
  • catch()只能捕获异步方法中reject错误,并且catch语句之后的语句会继续执行
async函数错误捕获,以登录功能为例
      async function getCatch () {
        await new Promise(function (resolve, reject) {
          reject(new Error('登录失败'))
        }).catch(error => {
          console.log(error)  // .catch()能捕获到错误信息
        })
        console.log('登录成功') //  但是成功信息也会执行
      }
      
     async function getCatch () {
        try {
          await new Promise(function (resolve, reject) {
            reject(new Error('登录失败'))
          })
          console.log('登录成功')  // try抛出错误之后,就不会执行这条语句
        } catch (error) {
          console.log(error)  //  catch语句能捕获到错误信息
        }
      }

async/await对比Promise的优势

  • 代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调⽤也会带来额外的阅读负担
  • Promise传递中间值⾮常麻烦,⽽async/await⼏乎是同步的写法,⾮常优雅
  • 错误处理友好,async/await可以⽤成熟的try/catch,Promise的错误捕获⾮常冗余
  • 调试友好,Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then代码块,因为调试器只能跟踪同步代码的每⼀步。

async/await与promise的区别

1.Promise的出现解决了传统callback函数导致的“地狱回调”问题,但它的语法导致了它向纵向发展行成了一个回调链,遇到复杂的业务场景,这样的语法显然也是不美观的。而async await代码看起来会简洁些,使得异步代码看起来像同步代码,await的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。
2.async await与Promise一样,是非阻塞的。
3.async await是基于Promise实现的,可以说是改良版的Promise,它不能用于普通的回调函数。

Set、Map、WeakSet、WeakMap

详情参考

Set、Map、WeakSet、WeakMap是ES2015中新增的几个对象:

set类似于数组,但是成员的值都是唯一的,没有重复的值。

Set本身是一个构造函数,用来生成 Set 数据结构。

Set和WeakSet与数组类似,准确的它他们是集合,这两者的区别就是Set可以存储任何数据类型,而WeakSet只能存储对象的引用,而且是弱引用;

Set对象在实际开发中最常见的就是实现数据去重,示例代码如下:

const arr = [1, 2, 2, 3, 4, 3, 5]
const set = new Set(arr)
// set对象可以使用 ... 展开 所有项
console.log([...set]) // [ 1, 2, 3, 4, 5 ]

map类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

MapWeakMap 与对象类似,存储方式是键值对形式的,这两者的区别Map的键值对都是可以是任意的而WeakMap键必须是对象的引用而值可以是任意类型的。

JS中new操作符有什么用?

创建临时对象,并将this指向临时对象

  • 将构造函数的原型属性和方法挂载到新对象的__proto__(原型指针)上
  • return 临时对象

DOM元素的方法

//创建节点
document.createElement("div");
//创建一个文本节点
document.createTextNode("content");
//用来创建一个文档碎片
document.createDocumentFragment();
//创建属性节点
document.createAttribute('custom');
//获取节点 传入任何有效的css 选择器,即可选中单个 DOM元素(首个)
document.querySelector('.element')
document.querySelector('#element')
document.querySelector('div')
document.querySelector('[name="username"]')
document.querySelector('div + p > span')
//所有
document.querySelectorAll("p");
//获取DOM元素的方法
document.getElementById('id属性值');返回拥有指定id的对象的引用
document.getElementsByClassName('class属性值');返回拥有指定class的对象集合
document.getElementsByTagName('标签名');返回拥有指定标签名的对象集合
document.getElementsByName('name属性值'); 返回拥有指定名称的对象结合
document/element.querySelector('CSS选择器');  仅返回第一个匹配的元素
document/element.querySelectorAll('CSS选择器');   返回所有匹配的元素
document.documentElement;  获取页面中的HTML标签
document.body; 获取页面中的BODY标签
document.all[''];  获取页面中的所有元素节点的对象集合型
//更新节点
innerHTML
//删除节点 删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild把自己删掉
**removeChild**
// 拿到待删除节点:
const self = document.getElementById('to-be-removed');
// 拿到父节点:
const parent = self.parentElement;
// 删除:
const removed = parent.removeChild(self);
removed === self; // true

DOM 事件流

⼜称为事件传播,是⻚⾯中接收事件的顺序。DOM2级事件规定的事件流包括了3个阶段:

  • 事件捕获阶段(capture phase)
  • 处于⽬标阶段(target phase)
  • 事件冒泡阶段(bubbling phase)

前端知识总结_第1张图片
如上图所示,事件流的触发顺序是:

1.事件捕获阶段,为截获事件提供了机会
2.实际的⽬标元素接收到事件
3.事件冒泡阶段,可在这个阶段对事件做出响应

什么是事件冒泡(Event Bubbling)

事件开始由最具体的元素(⽂档中嵌套层次最深的那个节点)接收到后,开始逐级向上传播到较为不具体的节点。

如果点击了上面页面代码中的 按钮,那么该 click 点击事件会沿着 DOM 树向上逐级传播,在途经的每个节点上都会发生,具体顺序如下:

  1. button 元素
  2. body 元素
  3. html 元素
  4. document 对象

什么是事件捕获(Event Capturing)

事件开始由较为不具体的节点接收后,然后开始逐级向下传播到最具体的元素上。

事件捕获的最大作用在于:事件在到达预定⽬标之前就可以捕获到它。

如果仍以上面那段 HTML 代码为例,当点击按钮后,在事件捕获的过程中,document 对象会首先接收到这个 click 事件,然后再沿着 DOM 树依次向下,直到 。具体顺序如下:

  1. document 对象
  2. html 元素
  3. body 元素
  4. button 元素

什么是事件委托

事件委托,就是利用了事件冒泡的机制,在较上层位置的元素上添加一个事件监听函数,来管理该元素及其所有子孙元素上的某一类的所有事件。

适用场景:在绑定大量事件的时候,可以选择事件委托

优点

  • 事件委托可以减少事件注册数量,节省内存占⽤!
  • 当新增⼦元素时,⽆需再次做事件绑定,因此非常适合动态添加元素 (vue解析模板时, 会对新创建的元素, 额外进行绑定的)

怎么阻止事件冒泡、阻止默认事件?

阻止事件冒泡

e.stopPropagation**()

阻止默认事件,3种方式

e.preventDefault();//谷歌及IE8以上
window.event.returnValue = false; //IE8及以下
return false; //无兼容问题(但不能用于节点直接onclick绑定函数)

什么是BOM对象

前端知识总结_第2张图片

location对象的常用方法

前端知识总结_第3张图片

navigator对象 (获取浏览器平台和版本数据)

前端知识总结_第4张图片

histroy对象 (管理浏览器历史记录)

前端知识总结_第5张图片

Axios

  • 浏览器端发起XMLHttpRequests请求
  • node端发起http请求
  • 支持Promise API
  • 监听请求和返回
  • 对请求和返回进行转化
  • 取消请求
  • 自动转换json数据
  • 客户端支持抵御XSRF攻击

浅拷贝和深拷贝

浅拷贝

  • 浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝
  • 如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址
  • 即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址

常见的浅拷贝:

  • Object.assign
  • Object.create
  • slice
  • 展开运算符 …

参考详见
深拷贝

深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

常见的深拷贝方式有:

  • 使用JSON.stringify()以及JSON.parse()
  • 通过for in实现
function deepCopy1(obj) {
  let o = {}
  for(let key in obj) {
    o[key] = obj[key]
  }
  return o
}
 
let obj = {
  a:1,
  b: undefined,
  c:function() {},
}
console.log(deepCopy1(obj))
  • 递归 (自身调用自身)

function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};
    if(obj && typeof obj==="object"){
        for(key in obj){
            if(obj.hasOwnProperty(key)){
                //判断ojb子元素是否为对象,如果是,递归复制
                if(obj[key]&&typeof obj[key] ==="object"){
                    objClone[key] = deepClone(obj[key]);
                }else{
                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}   
let a = [1,2,3,4]
let b = deepClone(a);
a[0] = 2;
console.log(a,b);

  • concat(数组的深拷贝)
    使用concat合并数组,会返回一个新的数组。
    对象是一个引用数据类型 普通的复制是一个浅拷贝

JS中如何将页面重定向到另一个页面?

1、使用 location.href:window.location.href =“url”

2、使用 location.replace: window.location.replace(“url”);

深度遍历广度遍历的区别?

对于算法来说 无非就是时间换空间 空间换时间

1、深度优先不需要记住所有的节点, 所以占用空间小, 而广度优先需要先记录所有的节点占用空间大

2、深度优先有回溯的操作(没有路走了需要回头)所以相对而言时间会长一点

3、深度优先采用的是堆栈的形式, 即先进后出

4、广度优先则采用的是队列的形式, 即先进先出

什么是箭头函数,有什么特征
使用 “箭头” ( => ) 来定义函数. 箭头函数相当于匿名函数, 并且简化了函数定义
箭头函数的特征:

  • 箭头函数没有this, this指向定义箭头函数所处的外部环境
  • 箭头函数的this永远不会变,call、apply、bind也无法改变
  • 箭头函数只能声明成匿名函数,但可以通过表达式的方式让
  • 箭头函数具名
  • 箭头函数没有原型prototype
  • 箭头函数不能当做一个构造函数 因为 this 的指向问题
  • 箭头函数没有 arguments 在箭头函数内部访问这个变量访问的是外部环境的arguments, 可以使用 …代替

call、apply、bind三者的异同

共同点 :

  • 都可以改变this指向;
  • 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window

不同点:

  • call 和 apply 会调用函数, 并且改变函数内部this指向.
  • call 和
    apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递,且apply和call是一次性传入参数,而bind可以分为多次传入
  • bind是返回绑定this之后的函数

应用场景

  • call 经常做继承.
  • apply经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
  • bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向

15.require和import区别

调用时间

  • require运行时调用,理论上可以运用在代码任何地,甚至不需要赋值给某个变量之后再使用。
  • lmport是编译时候调用,必须放在文件开头,而且使用格式也是确定的。
  • 遵循规范
  • require 是 AMD规范引入方式
  • import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法
  • 本质
  • require是赋值过程,其实require 的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量。
  • import是解构过程。
  • 通过require 引入基础数据类型时,属于复制该变量。
  • 通过require 引入复杂数据类型时,数据浅拷贝该对象。
  • 出现模块之间的循环引用时,会输出已经执行的模块,而未执行的模块不输出(比较复杂)。CommonJS模块默认export的是一个对象,即使导出的是基础数据类型。
  • ES6 模块语法是 JavaScript 模块的标准写法,坚持使用这种写法,取代 Node.js 的 CommonJS 语法。
  • 使用import取代require()。
// CommonJS 的写法
const moduleA = require('moduleA');
const func1 = moduleA.func1;
const func2 = moduleA.func2;
// ES6 的写法
import { func1, func2 } from 'moduleA';

使用export取代module.exports。

// commonJS 的写法
var React = require('react');
var Breadcrumbs = React.createClass({
  render() {
    return <nav />;
  }
});
module.exports = Breadcrumbs;

// ES6 的写法
import React from 'react';
class Breadcrumbs extends React.Component {
  render() {
    return <nav />;
  }
};
export default Breadcrumbs;

参考详见1
参考详见2

四、计算机网络与其他知识篇

HTTP与HTTPS

HTTP:客户端与服务器之间数据传输的格式规范,表示“超文本传输协议”。

  • HTTPS:在HTTP与TCP之间添加的安全协议层。
  • 默认端口号:HTTP:80,HTTPS:443。
  • 传输方式:http是明文传输,https则是具有安全性的ssl加密传输协议。
  • 连接方式:http的是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

TCP与UDP的区别

TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。

  • TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
  • 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
  • TCP首部开销20字节;UDP的首部开销小,只有8个字节。
  • TCP提供可靠的服务。UDP适用于一次只传少量数据、对可靠要求不高的环境。

HTTP常见的状态码

  • 1XX:(Informational) 信息性状态码,表示正在处理。
  • 2XX:(Success) 成功状态码,表示请求正常。
  • 3XX:(Redirection) 重定向状态码,表示客户端需要进行附加操作。
  • 4XX:(Client Error) 客户端错误状态码,表示服务器无法处理请求。
  • 5XX:(Server Error) 服务器错误状态码,表示服务器处理请求出错。
  • 100:表示继续,一般在发送post请求时,已发送了HTTP header之后,服务器端将返回此信息,表示确认,之后发送具体参数信息。
  • 200:OK 表示正常返回信息。
  • 201:Created 表示请求成功并且服务器创建了新的资源。
  • 202:Accepted 表示服务器已接受请求,但尚为处理。
  • 301:Moved Permanently 表示请求的网页已永久移动到新的位置。
  • 302:Found 表示临时重定向
  • 303:See Other 表示临时重定向,且总是使用GET请求新的URL。
  • 304:Not Modified 表示自从上次请求后,请求的网页未修改过。
  • 400:Bad Request 表示服务器无法理解请求的格式,客户端不应该尝试再次使用相同的内容发起请求。
  • 401:Unauthorized 表示请求未授权。
  • 403:Forbidden 表示禁止访问。
  • 404:Not Found 表示服务器上无法找到指定的资源。
  • 405:Method Not Allowed客户端请求中的方法被禁止。
  • 408:Request Time-out服务器等待客户端发送的请求时间过长,超时。
  • 500:Internal Server Error 表示服务器端错误。
  • 502:Bad Gateway 表示服务器网关错误。
  • 503:Service Unavailable 表示服务器暂时无法处理请求(可能是过载或维护)。

GET和POST的请求的区别

表面区别

  1. 应用场景: (GET 请求是一个幂等的请求)一般 Get 请求用于对服务器资源不会产生影响的场景,比如说请求一个网页的资源。(而 Post 不是一个幂等的请求)一般用于对服务器资源会产生影响的情景,比如注册用户这一类的操作。
  2. 后退/刷新: GET无害,POST数据会被重新提交。
  3. 书签: GET产生的URL地址可以被收藏为书签,而POST不可以。
  4. 数据: GET一般是用来获取数据,POST提交数据。
  5. 数据类型: GET只允许ASCII字符,POST无限制。
  6. 数据大小: GET大小有限制(一般来说1024字节),POST理论上来说没有大小限制。
  7. 安全性: GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
  8. 可见性: GET参数通过URL传递对所有人可见,POST数据不可见。
  9. 历史保留: GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
  10. 传参方式不同: Get 通过查询字符串传参,Post 通过请求体传参。

POST和PUT请求的区别

PUT请求 是向服务器端发送数据,从而修改数据的内容,但是不会增加数据的种类等,也就是说无论进行多少次PUT操作,其结果并没有不同。(可以理解为时更新数据

POST请求 是向服务器端发送数据,该请求会改变数据的种类等资源,它会创建新的内容。(可以理解为是创建数据

网页从输入url到页面加载发生了什么

  1. DNS解析
  2. TCP连接
  3. 发送HTTP请求
  4. 服务器处理请求并返回HTTP报文
  5. 浏览器解析并渲染页面——>1.解析文档构建dom树。2.构建渲染树。3.布局与绘制渲染树。
  6. 连接结束

HTTP 传输过程

含义:从建立连接到断开连接一共七个步骤,就是三次招手四次挥手

  1. TCP 建立连接
  2. 浏览器发送请求命令
  3. 浏览器发送请求头
  4. 服务器应答
  5. 服务器回应信息
  6. 服务器发送数据
  7. 断开TCP连接

浏览器如何渲染页面的?

  • 浏览器解析html源码,将HTML转换成dom树,
  • 将CSS样式转换成stylesheet(CSS规则树),
  • 浏览器会将CSS规则树附着在DOM树上,并结合两者生成渲染树(Render Tree)
  • 生成布局(flow),浏览器通过解析计算出每一个渲染树节点的位置和大小,在屏幕上画出渲染树的所有节点
  • 合成绘制生成页面。

HTTP的keep-alive有什么作用?

http1.0默认关闭,需要手动开启。http1.1后默认开启

作用: 使客户端到服务器端的链接持续有效(长连接),当出现对服务器的后续请求时,keep-Alive功能避免了建立或者重新建立链接。

使用方法: 在请求头中加上Connection:keep-alive。
优点:

  • 较少的CPU和内存的占用(因为要打开的连接数变少了,复用了连接)
  • 减少了后续请求的延迟(无需再进行握手)

缺点: 本来可以释放的资源仍旧被占用。有的请求已经结束了,但是还一直连接着。

解决方法: 服务器设置过期时间和请求次数,超过这个时间或者次数就断掉连接。

OSI的七层模型是什么

ISO于1978年开发的一套标准架构ISO模型,被引用来说明数据通信协议的结构和功能。

OSI在功能上可以划分为两组:

网络群组:物理层、数据链路层、网络层

使用者群组:传输层、会话层、表示层、应用层

OSI七层网络模型 TCP/IP四层概念模型 对应网络协议
7:应用层 应用层 HTTP、RTSP TFTP(简单文本传输协议)、FTP、 NFS(数域筛法,数据加密)、WAIS`(广域信息查询系统)
6:表示层 应用层 Telnet(internet远程登陆服务的标准协议)、Rlogin、SNMP(网络管理协议)、Gopher
5:会话层 应用层 SMTP(简单邮件传输协议)、DNS(域名系统)
4:传输层 传输层 TCP(传输控制协议)、UDP(用户数据报协议))
3:网络层 网际层 ARP(地域解析协议)、RARP、AKP、UUCP(Unix to Unix copy)
2:数据链路层 数据链路层 FDDI(光纤分布式数据接口)、Ethernet、Arpanet、PDN(公用数据网)、SLIP(串行线路网际协议)PPP(点对点协议,通过拨号或专线方建立点对点连接发送数据)
1:物理层 物理层 SMTP(简单邮件传输协议)、DNS(域名系统)

其中高层(7、6、5、4层)定义了应用程序的功能,下面三层(3、2、1层)主要面向通过网络的端到端的数据流

对称加密、非对称加密是什么,有什么区别?

对称加密和非对称加密是安全传输层里的加密算法

对称加密

对称加密的特点是文件加密和解密使用相同的密钥,即加密密钥也可以用作解密密钥,这种方法在密码学中叫做对称加密算法,对称加密算法使用起来简单快捷,密钥较短,且破译困难通信的双⽅都使⽤同⼀个秘钥进⾏加密, 解密。 ⽐如,两个人事先约定的暗号,就属于对称加密。
优点:
计算量小、加密速度快、加密效率高。
缺点:
在数据传送前,发送方和接收方必须商定好秘钥,然后双方保存好秘钥。如果一方的秘钥被泄露,那么加密信息也就不安全了

使用场景: 本地数据加密

非对称加密

通信的双方使用不同的秘钥进行加密解密,即秘钥对(私钥 + 公钥)。

特征: 私钥可以解密公钥加密的内容, 公钥可以解密私钥加密的内容

优点:
非对称加密与对称加密相比其安全性更好

缺点:
加密和解密花费时间长、速度慢,只适合对少量数据进行加密。

使用场景: https 会话前期、CA 数字证书、信息加密、登录认证等

token是什么

token也可以称做令牌,一般由 uid+time+sign(签名)+[固定参数] 组成

uid: 用户唯一身份标识
time: 当前时间的时间戳
sign: 签名, 使用 hash/encrypt 压缩成定长的十六进制字符串,以防止第三方恶意拼接
固定参数(可选): 将一些常用的固定参数加入到 token 中是为了避免重复查库

token在客户端一般存放于localStorage,cookie,或sessionStorage中。在服务器一般存于数据库中

token 的认证流程

用户登录,成功后服务器返回Token给客户端。
客户端收到数据后保存在客户端
客户端再次访问服务器,将token放入headers中 或者每次的请求 参数中
服务器端采用filter过滤器校验。校验成功则返回请求数据,校验失败则返回错误码

token可以抵抗csrf,cookie+session不可以

session时有状态的,一般存于服务器内存或硬盘中,当服务器采用分布式或集群时,session就会面对负载均衡问题。负载均衡多服务器的情况,不好确认当前用户是否登录,因为多服务器不共享session

客户端登陆传递信息给服务端,服务端收到后把用户信息加密(token)传给客户端,客户端将token存放于localStroage等容器中。客户端每次访问都传递token,服务端解密token,就知道这个用户是谁了。通过cpu加解密,服务端就不需要存储session占用存储空间,就很好的解决负载均衡多服务器的问题了。这个方法叫做JWT(Json Web Token)

token过期后,页面如何实现无感刷新

什么是无感刷新

后台返回的token是有时效性的,时间到了,你在交互后台的时候,后台会判断你的token是否过期(安全需要),如果过期了就会逼迫你重新登陆!

token无感刷新其本质是为了优化用户体验,当token过期时不需要用户跳回登录页重新登录,而是当token失效时,进行拦截,发送刷新token的ajax,获取最新的token进行覆盖,让用户感受不到token已经过期

实现无感刷新

1、后端返回过期时间,前端判断token过期时间,去调用刷新token接口。

缺点:需要后端额外提供一个Token过期时间的字段;使用了本地时间判断,若本地时间篡改,特别是本地时间比服务器时间慢时,拦截会失败。

2、写个定时器,定时刷新Token接口。缺点:浪费资源,消耗性能,不建议采用。

3、在响应拦截器中拦截,判断Token 返回过期后,调用刷新token接口。

进程与线程的概念

从本质上说,进程和线程都是 CPU 工作时间片的一个描述:

  • 进程描述了 CPU 在运行指令及加载和保存上下文所需的时间,放在应用上来说就代表了一个程序。
  • 线程是进程中的更小单位,描述了执行一段指令所需的时间。

进程是资源分配的最小单位,线程是CPU调度的最小单位。

进程和线程的区别

  • 进程可以看做独立应用,线程不能
  • 资源: 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位);线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)。
  • 通信方面: 线程间可以通过直接共享同一进程中的资源,而进程通信需要借助 进程间通信。
  • 调度: 进程切换比线程切换的开销要大。线程是CPU调度的基本单位,线程的切换不会引起进程切换,但某个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
  • 系统开销: 由于创建或撤销进程时,系统都要为之分配或回收资源,如内存、I/O 等,其开销远大于创建或撤销线程时的开销。同理,在进行进程切换时,涉及当前执行进程 CPU
    环境还有各种各样状态的保存及新调度进程状态的设置,而线程切换时只需保存和设置少量寄存器内容,开销较小。

浏览器本地存储的方式 cookie,localStorage,sessionStorage的区别

cookie: 其实最开始是服务器端用于记录用户状态的一种方式,由服务器设置,在客户端存储,然后每次发起同源请求时,发送给服务器端。cookie 最多能存储 4 k 数据,它的生存时间由 expires 属性指定,并且 cookie 只能被同源的页面访问共享。

sessionStorage: html5 提供的一种浏览器本地存储的方法,它借鉴了服务器端 session 的概念,代表的是一次会话中所保存的数据。它一般能够存储 5M 或者更大的数据,它在当前窗口关闭后就失效了,并且 sessionStorage 只能被同一个窗口的同源页面所访问共享。

localStorage: html5 提供的一种浏览器本地存储的方法,它一般也能够存储 5M 或者更大的数据。它和 sessionStorage 不同的是,除非手动删除它,否则它不会失效,并且 localStorage 也只能被同源页面所访问共享。

API  localStorage和sessionStorageAPI是类似的

xxxxxStorage.setItem('key',value)
// 接收一个键和值作为参数,把键值对添加到存储中
// 如果键名存在,则更新其对应的值。

xxxxxStorage.getItem('key')
// 接收一个键名作为参数,返回键名对应的值

xxxxxStorage.removeItem('key')
// 接收一个键名作为参数,并把该键名从存储中删除

xxxxxStorage.clear()
// 清空存储中的所有数据

什么是同源策略,什么是跨域

同源策略: protocol(协议)、domain(域名)、port(端口)三者必须一致。

跨域 指的是浏览器不能执行其他网站的脚本,它是由浏览器的同源策略造成的,是浏览器对 javascript 施加的安全限制,防止他人恶意攻击网站,跨域问题 其实就是浏览器的同源策略造成的。

如何解决跨越问题

  • 常见的:

  • 1、JSONP跨域

  • 原理:利用script标签可以跨域请求资源,将回调函数作为参数拼接在url中。后端收到请求,调用该回调函数,并将数据作为参数返回去,注意设置响应头返回文档类型,应该设置成javascript。只支持 GET 请求

  • 2、跨域资源共享(CORS)

  • 目前最常用的一种解决办法,通过设置后端允许跨域实现。

  • 3、nginx反向代理

  • 跨域限制的时候浏览器不能跨域访问服务器,node中间件和nginx反向代理,都是让请求发给代理服务器,静态页面面和代理服务器是同源的,然后代理服务器再向后端服务器发请求,服务器和服务器之间不存在同源限制。

  • 4、WebSocket协议跨域 websocket是一种网络通信协议,是HTML5开始提供的一种在单个TCP连接上进行全双工通信的协议,这个对比着HTTP协议来说,HTTP协议是一种无状态的、无连接的、单向的应用层协议,通信请求只能由客户端发起,服务端对请求做出应答处理。
    HTTP协议无法实现服务器主动向客户端发起消息,websocket连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。websocket只需要建立一次连接,就可以一直保持连接状态

  • 5、proxy

  • 前端配置一个代理服务器(proxy)代替浏览器去发送请求:因为服务器与服务器之间是可以通信的不受同源策略的影响。

  • 详细可看九种常见的前端跨域解决办法

  • 6、iframe跨域

  • 7、postMessage

webpack、性能优化

参考详见

CND

CDN(Content Delivery Network,内容分发网络) 是指一种通过互联网互相连接的电脑网络系统,利用最靠近每位用户的服务器,更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户,来提供高性能、可扩展性及低成本的网络内容传递给用户。

CDN的作用

CDN一般会用来托管Web资源(包括文本、图片和脚本等),可供下载的资源(媒体文件、软件、文档等),应用程序(门户网站等)。使用CDN来加速这些资源的访问。

CDN的使用场景

使用第三方的CDN服务: 如果想要开源一些项目,可以使用第三方的CDN服务

使用CDN进行静态资源的缓存: 将自己网站的静态资源放在CDN上,比如js、css、图片等。可以将整个项目放在CDN上,完成一键部署。

直播传送: 直播本质上是使用流媒体进行传送,CDN也是支持流媒体传送的,所以直播完全可以使用CDN来提高访问速度。CDN在处理流媒体的时候与处理普通静态文件有所不同,普通文件如果在边缘节点没有找到的话,就会去上一层接着寻找,但是流媒体本身数据量就非常大,如果使用回源的方式,必然会带来性能问题,所以流媒体一般采用的都是主动推送的方式来进行。

懒加载(图片)

懒加载的概念

懒加载也叫做延迟加载、按需加载,指的是在长网页中延迟加载图片数据,是一种较好的网页性能优化的方式。在比较长的网页或应用中,如果图片很多,所有的图片都被加载出来,而用户只能看到可视窗口的那一部分图片数据,这样就浪费了性能。

如果使用图片的懒加载就可以解决以上问题。在滚动屏幕之前,可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,减少了服务器的负载。懒加载适用于图片较多,页面列表较长(长列表)的场景中。

懒加载的特点

减少无用资源的加载: 使用懒加载明显减少了服务器的压力和流量,同时也减小了浏览器的负担。

提升用户体验: 如果同时加载较多图片,可能需要等待的时间较长,这样影响了用户体验,而使用懒加载就能大大的提高用户体验。

防止加载过多图片而影响其他资源文件的加载 : 会影响网站应用的正常使用。

懒加载的实现原理
图片的加载是由src引起的,当对src赋值时,浏览器就会请求图片资源。根据这个原理,我们使用HTML5 的data-xxx属性来储存图片的路径,在需要加载图片的时候,将data-xxx中图片的路径赋值给src,这样就实现了图片的按需加载,即懒加载。
注意:data-xxx 中的xxx可以自定义,这里我们使用data-src来定义。

 <img data-src="./pic/1.png" alt="">
  <img data-src="./pic/2.png" alt="">

懒加载的实现重点在于确定用户需要加载哪张图片,在浏览器中,可视区域内的资源就是用户需要的资源。所以当图片出现在可视区域时,获取图片的真实地址并赋值给图片即可。
参考详见1 参考详见2

Vue3实现图片懒加载

导入vueuse插件,使用vueuse封装的useIntersectionObserver监听对应的DOM元素,通过里面的isIntersecting属性的布尔值判断来设置img的src

<template>
   <div>
    <div style="height:2000px"></div>
    <img ref="target" src="" alt="">
   </div>
</template>
 
<script lang='ts' setup>
import { ref,reactive } from 'vue'
import {useIntersectionObserver} from '@vueuse/core'
const target = ref<HTMLImageElement|null>(null)

useIntersectionObserver(target, ([{isIntersecting}]) =>{
const src = 'https://ts3.cn.mm.bing.net/th?id=OIP-C.Zxtf2X2EddV-g7hKyBhilAHaQB&w=161&h=350&c=8&rs=1&qlt=90&o=6&pid=3.1&rm=2'
if(isIntersecting){
    if(target.value){
        target.value.src = src 
    }
}
})
</script>

封装为自定义指令

export default function (app: App<Element>) {

  app.directive('lazy', {
    mounted(el, binding) {
      // el:选中的元素,value:传过来的属性,这里是图片地址
      const { stop } = useIntersectionObserver(el,([{ isIntersecting }]) => {
        if(isIntersecting){ // 判断元素是否在可视区域
          el.src = binding.value
          el.onerror = function() {
            el.src = defaultImg//默认图片
          }  
        }
        stop()
      })
    },
  })
}

列表数据懒加载

我们可以使用 @vueuse/core 中的 useIntersectionObserver 来实现监听进入可视区域行为,但是必须配合vue3.0的组合API的方式才能实现。


import { useIntersectionObserver } from '@vueuse/core'
import { ref } from 'vue'
 
/**
 * 数据懒加载函数
 * @param {Function} apiFn - Api函数
 * @constructor
 */
export const useLazyData = (apiFn) => {
  const target = ref(null)
  const result = ref([])
  // stop 停止观察
  const { stop } = useIntersectionObserver(
    // 监听的目标函数
    target,
    ([{ isIntersecting }], observeElement) => {
      // isIntersecting是否进入可视区
      if (isIntersecting) {
        stop()
        // 调用API函数获取数据
        apiFn().then(data => {
          result.value = data.result
        })
      }
    }
  )
  return { result, target }
}

<template>
      <div ref="target">
        <!-- 面板内容 -->
          <ul>
            <li v-for="item in goods" :key="item.id">
                <img :src="item.picture" alt="">
                <p>{{ item.name }}</p>
                <p>{{ item.price }}</p>
            </li>
          </ul>
      </div>
</template>
<script setup>
import { findNew } from '@/api/home'
import { useLazyData } from '@/hooks'
 
const { result: goods, target } = useLazyData(findNew)
</script>

懒加载和预加载的区别

这两种方式都是提高网页性能的方式,两者主要区别是一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。

- 懒加载也叫延迟加载,指的是在长网页中延迟加载图片的时机,当用户需要访问时,再去加载,这样可以提高网站的首屏加载速度,提升用户的体验,并且可以减少服务器的压力。它适用于图片很多,页面很长的电商网站的场景。
- 预加载指的是将所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。 通过预加载能够减少用户的等待时间,提高用户的体验。我了解的预加载的最常用的方式是使用 js 中的 image 对象,通过为 image对象来设置 scr 属性,来实现图片的预加载。

仅使用CSS实现预加载。


body:after {
    content: "";
    display: block;
    position: absolute;
    background: url("../image/manage/help/help_item2_01.png?v=201707241359") no-repeat 
    -10000px -1000px,url("../image/manage/help/help_item2_02.png?v=201707241359") no-repeat 
    -10000px -1000px,url("../image/manage/help/help_item2_03.png?v=201707241359") no-repeat 
    -10000px -1000px,url("../image/manage/help/help_item2_04.png?v=201707241359") no-repeat 
    -10000px -1000px;
    width: 0;
    height: 0
}

仅使用JavaScript实现预加载。
//存放图片路径的数组

    var imgSrcArr = [
        'imgsrc1',
        'imgsrc2',
        'imgsrc3',
        'imgsrc4'
    ];
 
    var imgWrap = [];
 
    function preloadImg(arr) {
        for(var i =0; i< arr.length ;i++) {
            imgWrap[i] = new Image();
            imgWrap[i].src = arr[i];
        }
    }
 
    preloadImg(imgSrcArr);
 
//或者延迟的文档加载完毕在加载图片
    $(function () {
        preloadImg(imgSrcArr);
    })

回流(重排)与重绘

什么是回流(重排),哪些操作会导致回流
当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为回流。

下面这些操作会导致回流:

  • 页面的首次渲染
  • 浏览器的窗口大小发生变化
  • 元素的内容发生变化
  • 元素的尺寸或者位置发生变化
  • 元素的字体大小发生变化
  • 激活CSS伪类
  • 查询某些属性或者调用某些方法
  • 添加或者删除可见的DOM元素

在触发回流(重排)的时候,由于浏览器渲染页面是基于流式布局的,所以当触发回流时,会导致周围的DOM元素重新排列,它的影响范围有两种:

  • 全局范围:从根节点开始,对整个渲染树进行重新布局
  • 局部范围:对渲染树的某部分或者一个渲染对象进行重新布局
    什么是重绘,哪些操作会导致重绘

当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘。

下面这些操作会导致回流:

  • color、background 相关属性:background-color、background-image 等
  • outline 相关属性:outline-color、outline-width 、text-decoration
  • border-radius、visibility、box-shadow

注意: 当触发回流时,一定会触发重绘,但是重绘不一定会引发回流。

如何避免回流与重绘?

减少回流与重绘的措施:

  • 操作DOM时,尽量在低层级的DOM节点进行操作
  • 不要使用table布局, 一个小的改动可能会使整个table进行重新布局
  • 使用CSS的表达式
  • 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式。
  • 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
  • 避免频繁操作DOM,可以创建一个文档片段
  • documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中
  • 将元素先设置display: none,操作结束后再把它显示出来。
    因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
  • 将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于浏览器的渲染队列机制。

浏览器的渲染队列

浏览器针对页面的回流与重绘,进行了自身的优化——渲染队列

浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。

防抖与节流

**防抖:**触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间

  • 应用场景:
  • 提交按钮、用户注册时候的手机号验证、邮箱验证、

节流: 高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。

  • 应用场景:

  • window对象的resize、scroll事件

  • 拖拽时候的mousemove

  • 射击游戏中的mousedown、keydown事件

  • 文字输入、自动完成的keyup事件

代码实现

// //防抖
function debounce(fn, date) {
  let timer  //声明接收定时器的变量
  return function (...arg) {  // 获取参数
    timer && clearTimeout(timer)  // 清空定时器
    timer = setTimeout(() => {  //  生成新的定时器
      //因为箭头函数里的this指向上层作用域的this,所以这里可以直接用this,不需要声明其他的变量来接收
      fn.apply(this, arg) // fn()
    }, date)
  }
}
//--------------------------------
// 节流
function debounce(fn, data) {
  let timer = +new Date()  // 声明初始时间
  return function (...arg) { // 获取参数
    let newTimer = +new Date()  // 获取触发事件的时间
    if (newTimer - timer >= data) {  // 时间判断,是否满足条件
      fn.apply(this, arg)  // 调用需要执行的函数,修改this值,并且传入参数
      timer = +new Date() // 重置初始时间
    }
  }
}
// --------------------------------
box.addEventListener('click', debounce(function (e) {
  if (e.target.tagName === 'BUTTON') {
    console.log(111);
  }
}, 2000))

webpack知识总结

什么是webpack

静态模块打包工具

webpack作用

分析、压缩、打包代码

webpack好处

  • 减少文件体积、减少文件数量
  • 提高网页加载速度

webpack工作流程

  • 1.初始化参数: 从配置文件读取与合并参数,得出最终的参数
  • 2.开始编译: 用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,开始执行编译
  • 3.确定入口: 根据配置中的 entry 找出所有的入口文件
  • 4.编译模块: 从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
  • 5.完成模块编译: 在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
  • 6.输出资源: 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
  • 7.输出完成: 在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

什么是loader,什么是Plugin

  • loader直译为"加载器"。webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 所以loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。

      loader就是用于解析文件的 (类似游戏地图)
      例如:css-loader 、style-loader、image-loader
    
  • Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 webpack
    运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 API 改变输出结果。
    说人话:插件就是拓展功能的 (类似游戏的作弊器)
    例如:html-webpack-plugin,

插件范围很广 : 只要不是webapck原生的功能,都可以理解为插件

loader : 一种特殊的插件,主要是用在webpack编译环节,帮我们编译各种文件的

有哪些常见的Loader?你用过哪些Loader?

raw-loader:加载文件原始内容(utf-8)
file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)
url-loader:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader 处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)
source-map-loader:加载额外的 Source Map 文件,以方便断点调试
svg-inline-loader:将压缩后的 SVG 内容注入代码中
image-loader:加载并且压缩图片文件
json-loader 加载 JSON 文件(默认包含)
handlebars-loader: 将 Handlebars 模版编译成函数并返回
babel-loader:把 ES6 转换成 ES5
ts-loader: 将 TypeScript 转换成 JavaScript
awesome-typescript-loader:将 TypeScript 转换成 JavaScript,性能优于 ts-loader
sass-loader:将SCSS/SASS代码转换成CSS
css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
postcss-loader:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀
eslint-loader:通过 ESLint 检查 JavaScript 代码
tslint-loader:通过 TSLint检查 TypeScript 代码

有哪些常见的Plugin?你用过哪些Plugin?

define-plugin:定义环境变量 (Webpack4 之后指定 mode 会自动配置)
ignore-plugin:忽略部分文件
html-webpack-plugin:简化 HTML 文件创建 (依赖于 html-loader)
web-webpack-plugin:可方便地为单页应用输出 HTML,比 html-webpack-plugin 好用
uglifyjs-webpack-plugin:不支持 ES6 压缩 (Webpack4 以前)
terser-webpack-plugin: 支持压缩 ES6 (Webpack4)
webpack-parallel-uglify-plugin: 多进程执行代码压缩,提升构建速度
mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代extract-text-webpack-plugin)
serviceworker-webpack-plugin:为网页应用增加离线缓存功能
clean-webpack-plugin: 目录清理
ModuleConcatenationPlugin: 开启 Scope Hoisting
speed-measure-webpack-plugin: 可以看到每个 Loader 和 Plugin 执行耗时 (整个打包耗时、每个 Plugin 和 Loader 耗时)
webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)

那你再说一说Loader和Plugin的区别?

Loader 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。 因为 Webpack 只认识
JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。

Plugin 就是插件,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

Loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。

Plugin 在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入

说一下 Webpack 的热更新原理吧

Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。

HMR的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上 WDS 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该chunk的增量更新。

后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loader 和 vue-loader 都是借助这些 API 实现 HMR。

代码分割的本质是什么?有什么意义呢?

代码分割的本质其实就是在源代码直接上线打包成唯一脚本main.bundle.js这两种极端方案之间的一种更适合实际场景的中间状态。

「用可接受的服务器性能压力增加来换取更好的用户体验。」

源代码直接上线:虽然过程可控,但是http请求多,性能开销大。

打包成唯一脚本:一把梭完自己爽,服务器压力小,但是页面空白期长,用户体验不好。

Webpack优化

如何提⾼webpack的打包速度?

(1)优化 Loader

对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码,项目越大,转换代码越多,效率就越低。

(2)HappyPack

受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。
HappyPack 可以将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来加快打包效率了

(3)DllPlugin

DllPlugin 可以将特定的类库提前打包然后引入。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。

(4)代码压缩

在 Webpack3 中,一般使用 UglifyJS 来压缩代码,但是这个是单线程运行的,为了加快效率,可以使用 webpack-parallel-uglify-plugin 来并行运行 UglifyJS,从而提高效率。
在 Webpack4 中,不需要以上这些操作了,只需要将 mode 设置为 production 就可以默认开启以上功能。代码压缩也是我们必做的性能优化方案,当然我们不止可以压缩 JS 代码,还可以压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 console.log 这类代码的功能。

如何减少 Webpack 打包体积

(1)按需加载

在开发 SPA 项目的时候,项目中都会存在很多路由页面。如果将这些页面全部打包进一个 JS 文件的话,虽然将多个请求合并了,但是同样也加载了很多并不需要的代码,耗费了更长的时间。那么为了首页能更快地呈现给用户,希望首页能加载的文件体积越小越好,这时候就可以使用按需加载,将每个路由页面单独打包为一个文件。当然不仅仅路由可以按需加载,对于 loadash 这种大型类库同样可以使用这个功能。

(2)Scope Hoisting

Scope Hoisting 会分析出模块之间的依赖关系,尽可能的把打包出来的模块合并到一个函数中去。

(3)Tree Shaking

Tree Shaking 可以实现删除项目中未被引用的代码。可以通过在启动webpack时追加参数 --optimize-minimize 来实现

如何⽤webpack来优化前端性能?

⽤webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。

压缩代码: 删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件, 利⽤ cssnano (css-loader?minimize)来压缩css

利⽤CDN加速: 在构建过程中,将引⽤的静态资源路径修改为CDN上对应的路径。可以利⽤webpack对于 output 参数和各loader的 publicPath 参数来修改资源路径

Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动webpack时追加参数 --optimize-minimize 来实现

Code Splitting (自动): 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存
提取公共第三⽅库: SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码

如何提⾼webpack的构建速度?

  • 多⼊⼝情况下,使⽤ CommonsChunkPlugin 来提取公共代码
  • 通过 externals 配置来提取常⽤库
  • 利⽤ DllPlugin 和 DllReferencePlugin 预编译资源模块 通过 DllPlugin
    来对那些我们引⽤但是绝对不会修改的npm包来进⾏预编译,再通过 DllReferencePlugin 将预编译的模块加载进来。
  • 使⽤ Happypack 实现多线程加速编译
  • 使⽤ webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。 原理上webpack-uglify-parallel 采⽤了多核并⾏压缩来提升压缩速度
  • 使⽤ Tree-shaking 和 Scope Hoisting 来剔除多余代码

什么是长缓存?在Webpack中如何做到长缓存优化?

1、什么是长缓存

浏览器在用户访问页面的时候,为了加快加载速度,会对用户访问的静态资源进行存储,但是每一次代码升级或者更新,都需要浏览器去下载新的代码,最方便的更新方式就是引入新的文件名称,只下载新的代码块,不加载旧的代码块,这就是长缓存。

2、具体实现

在Webpack中,可以在output给出输出的文件制定chunkhash,并且分离经常更新的代码和框架代码,通NameModulesPlugin或者HashedModulesPlugin使再次打包文件名不变

怎么实现Webpack的按需加载

在Webpack中,import不仅仅是ES6module的模块导入方式,还是一个类似require的函数,我们可以通过import(‘module’)的方式引入一个模块,import()返回的是一个Promise对象;使用import()方式就可以实现 Webpack的按需加载

什么是神奇注释?

在import()里可以添加一些注释,如定义该chunk的名称,要过滤的文件,指定引入的文件等等,这类带有特殊功能的注释被称为神器注释。

Babel的原理是什么?

Babel 的主要工作是对代码进行转译。 (解决兼容, 解析执行一部分代码)

let a = 1 + 1 => var a = 2

复制代码
转译分为三阶段:

  • 解析(Parse),将代码解析⽣成抽象语法树 AST,也就是词法分析与语法分析的过程
  • 转换(Transform),对语法树进⾏变换方面的⼀系列操作。通过 babel-traverse,进⾏遍历并作添加、更新、删除等操作
  • ⽣成(Generate),通过 babel-generator 将变换后的 AST 转换为 JS 代码

我们可以通过 AST Explorer 工具来查看 Babel 具体生成的 AST 节点。

npm run dev的时候webpack做了什么事情

执行npm run dev 时候最先执行的build/dev-server.js文件,该文件主要完成下面几件事情:

1、检查node和npm的版本、引入相关插件和配置

2、webpack对源码进行编译打包并返回compiler对象

3、创建express服务器

4、配置开发中间件(webpack-dev-middleware)和热重载中间件(webpack-hot-middleware)

5、挂载代理服务和中间件

6、配置静态资源

7、启动服务器监听特定端口(8080)

8、自动打开浏览器并打开特定网址(localhost:8080)

参考详见

五、VUE篇

虚拟dom

定义:
虚拟DOM本质上是一个树型结构的JS对象,通过对象来表示真实的DOM结构。因为它不是真实的dom,所以才叫做虚拟dom。 tag用来描述标签,props用来描述属性,children用来表示嵌套的层级关系。
原理
虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要 的 dom 操作,从而提高性能。

const vnode = {
    tag: 'div',
    props: {
        id: 'container',
    },
    children: [{
        tag: 'div',
        props: {
            class: 'content',
        },
          text: 'This is a container'
    }]
}

具体实现步骤如下:

  1. 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树, 插到文档当中;
  2. 当状态变更的时候,重新构造一棵新的对象树。通过diff 算法,比较新旧虚拟 DOM 树的差异。
  3. 根据差异,对真正的 DOM 树进行增、删、改。

步骤简化

  1. 通过js建立节点描述对象
  2. diff算法比较分析新旧两个虚拟DOM差异
  3. 将差异patch到真实dom上实现更新

在vue中一般都是通过修改元素的state,订阅者根据state的变化进行编译渲染,底层的实现可以简单理解为三个步骤:

  • 1、用JavaScript对象结构表述dom树的结构,然后用这个树构建一个真正的dom树,插到浏览器的页面中。

  • 2、当状态改变了,也就是我们的state做出修改,vue便会重新构造一棵树的对象树,然后用这个新构建出来的树和旧树进行对比(只进行同层对比),记录两棵树之间的差异。

  • 3、把记录的差异再重新应用到所构建的真正的dom树,视图就更新了。

虚拟DOM,解决了操作真实DOM的三个问题。

  1. 无差别频繁更新导致DOM频繁更新,造成性能问题
  2. 频繁回流与重绘
  3. 开发体验

优点

  1. 小修改无需频繁更新DOM,框架的diff算法会自动比较,分析出需要更新的节点,按需更新
  2. 更新数据不会造成频繁的回流与重绘
  3. 表达力更强,数据更新更加方便
  4. 保存的是js对象,具备跨平台能力

不足

虚拟DOM同样也有缺点,首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。

Diff算法

为了避免不必要的渲染,按需更新,虚拟DOM会采用Diff算法进行虚拟DOM节点比较,比较节点差异,从而确定需要更新的节点,再进行渲染。vue采用的是深度优先,同层比较的策略。(在diff中,只对同层的子节点进行比较,放弃跨级的节点比较,使得时间复杂从O(n3)降低值O(n),也就是说,只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。)
参考详情
新节点与旧节点的比较主要是围绕三件事来达到渲染目的

  • 创建新节点
  • 删除废节点
  • 更新已有节点

在新老虚拟DOM对比时:

  • 首先,对比节点本身,判断是否为同一节点,如果不为相同节点,则删除该节点重新创建节点进行替换
  • 如果为相同节点,进行patchVnode,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
  • 比较如果都有子节点,则进行updateChildren,判断如何对这些新老节点的子节点进行操作(diff核心)。
  • 匹配时,找到相同的子节点,递归比较子节点

SPA单页面应用

  • 一个系统只加载一次资源,之后的操作交互、数据交互是通过路由、ajax来进行,页面并没有刷新。
  • 在一个页面上集成多种功能,甚至整个系统就只有一个页面,所有的业务功能都是它的子模块,通过特定的方式挂接到主界面上。

优点:

  • 前后端分离
  • 良好的交互体验——用户不用刷新页面,页面显示流畅
  • 减轻服务器压力——服务器只出数据
  • 共用一套后端代码——多个客户端可共用一套后端代码
  • 加载速度快,内容的改变不需要重新加载整个页面,对服务器压力小

缺点:

  • 页面初次加载比较慢,页面复杂提高很多
  • SEO难度高——数据渲染在前端进行

解决首次加载慢的问题

  1. 减少入口文件体积
  2. 静态资源本地缓存
  3. UI框架按需加载
  4. 路由懒加载

多页面:
一个应用多个页面,页面跳转时整个页面都刷新,每次都请求一个新的页面

  • 优点: SEO效果好
  • 缺点: 页面切换慢,每次切换页面需要选择性的重新加载公共资源

对MVC和MVVM的理解

(1)MVC
M: model数据模型, V:view视图模型, C: controller控制器
MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。Controller 层是 View 层和 Model 层的纽带,它主要负责用户与应用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知View视图更新。

(2)MVVM

MVVM 分为 Model、View、ViewModel:

  • Model代表数据模型,数据和业务逻辑都在Model层中定义;
  • View代表UI视图,负责数据的展示;
  • ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;

Vue 的基本原理 (数据双向绑定原理)

详情参考
当一个Vue实例创建时,Vue会遍历data中的属性,用 Object.defineProperty(vue3.0使用proxy )将它们转为 getter/setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。 每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。

Vue的两核心

  • 响应式的数据绑定:当数据发生改变,视图可以自动更新,可以不用关心dom操作,而专心数据操作。
  • 可组合的视图组件:把视图按照功能切分成若干基本单元,组件可以一级一级组合整个应用形成倒置组件树,可维护,可重用,可测试

响应式原理
Vue响应式的原理就是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

Object.defineProperty的使用方式,有什么缺点

使用方法

**Object.defineProperty( obj, prop, descriptor )**

三个参数:

obj 要定义的对象

prop 要定义或修改的属性名称或 Symbol

descriptor 要定义或修改的属性描述符(配置对象)

缺点:

在对一些属性进行操作时,使用这种方法无法拦截,比如通过下标方式修改数组 数据或者给对象新增属性,这都不能触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作。更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写函数的方式解决了这个问题。

在 Vue3.0 中已经不使用这种方式了,而是通过使用 Proxy 对对象进行代理,从而实现数据劫持。 使用Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法。

Proxy 与 Object.defineProperty 优劣对比

  1. Proxy 可以直接监听对象而非属性;
  2. Proxy 可以直接监听数组的变化;
  3. Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
  4. Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
  5. Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;

Object.defineProperty 的优势如下:
兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。

vue2和vue3的区别

参考详见
监测机制的改变

  • vue3 中使用了ES6 的 ProxyAPI 对数据代理,监测的是整个对象,而不再是某个属性。
  • 消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制
  • vue3可以监测到对象属性的添加和删除,可以监听数组的变化;
  • vue3支持 Map、Set、WeakMap 和 WeakSet。

Vue3支持碎片(Fragments)

  • Vue2在组件中只有一个根节点。
  • Vue3在组件可以拥有多个根节点。

API模式不同

  • Vue2与Vue3 最大的区别:Vue2使用选项式API(Options API)对比Vue3组合式API(Composition
    API)

建立数据的方式不同

  • Vue2:这里把数据放入data属性中
  • Vue3:需要使用一个新的setup()方法,此方法在组件初始化构造的时候触发。
  • 使用以下三步来建立响应式数据:
  • 从vue引入ref或reactive
  • 简单数据类型使用ref()方法处理,复杂类型数据用reactive()处理
  • 使用setup()方法来返回我们的响应性数据,从而我们的template可以获取这些响应性数据

生命周期钩子不同 — Lifecyle Hooks

setup() :开始创建组件之前,在beforeCreate和created之前执
行。创建的是data和method

onBeforeMount() : 组件挂载到节点上之前执行的函数。

onMounted() : 组件挂载完成后执行的函数。

onBeforeUpdate(): 组件更新之前执行的函数。

onUpdated(): 组件更新完成之后执行的函数。

onBeforeUnmount(): 组件卸载之前执行的函数。

onUnmounted(): 组件卸载完成后执行的函数

若组件被包含,则多出下面两个钩子函

onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行 。
onDeactivated(): 比如从 A组件,切换到 B 组件,A 组件消失时执行。

父子传参不同,子组件通过defineProps()进行接收,并且接收这个函数的返回值进行数据操作。

总结: vue3 性能更高, 体积更小, 更利于复用, 代码维护更方便
vue2.0生命周期

  1. beforeCreate:创建前。此时,组件实例刚刚创建,还未进行数据观测和事件配置,拿不到任何数据。
  2. created:创建完成。vue 实例已经完成了数据观测,属性和方法的计算(比如props、methods、data、computed和watch此时已经拿得到),未挂载到DOM,不能访问到el属性,el属性,ref属性内容为空数组常用于简单的ajax请求,页面的初始化。
  3. beforeMount:挂载前。挂在开始之前被调用,相关的render函数首次被调用(虚拟DOM)。编译模板,把data里面的数据和模板生成html,完成了el和data 初始化,注意此时还没有挂在html到页面上。
  4. mounted:挂载完成。也就是模板中的HTML渲染到HTML页面中,此时可以通过DOM API获取到DOM节点,$ref属性可以访问常用于获取VNode信息和操作,ajax请求,mounted只会执行一次。
  5. beforeUpdate:在数据更新之前被调用,发生在虚拟DOM重新渲染和打补丁之前,不会触发附加地重渲染过程。
  6. updated:更新后。在由于数据更改导致地虚拟DOM重新渲染和打补丁之后调用,
  7. beforeDestroy;销毁前。在实例销毁之前调用,实例仍然完全可用。(一般在这一步做一些重置的操作,比如清除掉组件中的定时器 和 监听的dom事件)
  8. destroyed:销毁后。在实例销毁之后调用,调用后,vue实列指示的所有东西都会解绑,所有的事件监听器会被移除。
  9. activated:在keep-alive组件激活时调用
  10. deactivated:在keep-alive组件停用时调用

v-if、v-show、v-html 的原理

v-if 会调用addIfCondition方法,生成vnode的时候会忽略对应节点,render的时候就不会渲染;

v-show 会生成vnode,render的时候也会渲染成真实节点,只是在render过程中会在节点的属性中修改show属性值,也就是常说的display;

v-html 会先移除节点下的所有节点,调用html方法,通过addProp添加innerHTML属性,归根结底还是设置innerHTML为v-html的值。

为什么避免v-for和v-if在一起使用?

Vue 处理指令时,v-for 比 v-if 具有更高的优先级, 虽然用起来也没报错好使, 但是性能不高, 如果你有5个元素被v-for循环, v-if也会分别执行5次.而VUE3 里 v-if 比v-for 具有更高的优先级。

v-for 循环为什么一定要绑定key ?

提升vue渲染性能

  • 1.vue在渲染的时候,会 先把 新DOM 与 旧DOM 进行对比, 如果dom结构一致,则vue会复用旧的dom。 (此时可能造成数据渲染异常)
  • 2.使用key可以给dom添加一个标识符,让vue强制更新dom

为什么不建议用index索引作为key?

使用index 作为 key和没写基本上没区别,因为不管数组的顺序怎么颠倒,index 都是 0, 1, 2…这样排列,导致 Vue 会复用错误的旧子节点,做很多额外的工作。

计算属性computed 和watch 的区别是什么?

Computed:

支持缓存,只有依赖的数据发生了变化,才会重新计算
不支持异步,当Computed中有异步操作时,无法监听数据的变化
computed的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。
如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用computed
如果computed属性的属性值是函数,那么默认使用get方法,函数的返回值就是属性的属性值;在computed中,属性有一个get方法和一个set方法,当数据发生变化时,会调用set方法。

Watch:

不支持缓存,数据变化时,它就会触发相应的操作

支持异步监听

监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值

当一个属性发生变化时,就需要执行相应的操作

监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作,函数有两个的参数:

  • immediate: 组件加载立即触发回调函数
  • deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化。

当想要执行异步或者昂贵的操作以响应不断的变化时,就需要使用watch。

总结:

computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
watch 侦听器 : 更多的是观察的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。

运用场景:

  • 当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed的缓存特性,避免每次获取值时都要重新计算。
  • 当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

组件通信/ 组件传值的方法

(1)props / $emit (父子)

vue2 父组件通过props向子组件传递数据,子组件通过$emit和父组件通信。
vue3子组件通过defineProps()进行接收,并且接收这个函数的返回值进行数据操作。

子组件向父组件传值

$emit绑定一个自定义事件,当这个事件被执行的时就会将参数传递给父组件,而父组件通过v-on监听并接收参数。

(2)依赖注入 provide / inject(父子、祖孙)

这种方式就是Vue中的依赖注入,该方法用于父子组件之间的通信。当然这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方法来进行传值。就不用一层一层的传递了。

provide / inject是Vue提供的两个钩子,和data、methods是同级的。并且provide的书写形式和data一样。

  • provide 钩子用来发送数据或方法
  • inject钩子用来接收数据或方法

在父组件中:

<template>
  <div>
    <h2>CHINESE PORT</h2>
    <h2>这是父组件: {{ father }}</h2>
    <son-com />
  </div>
</template>
 
<script>
import sonCom from './sonCom.vue'
export default {
  components: { sonCom },
  provide() {
    return {
      sonDate: '子组件数据',
    }
  },
  data() {
    return {
      father: '父组件数据'
    }
  },
}
</script>

在子组件中:


<template>
  <div>
    <h3>这是子组件: {{ sonDate }}</h3>
  </div>
</template>
 
<script>
export default {
  inject: ['sonDate']
}
</script>

注意: 依赖注入所提供的属性是非响应式的。

(3)ref / $refs (父子,兄弟)

ref: 这个属性用在子组件上,它的引用就指向了子组件的实例。可以通过实例来访问组件的数据和方法。

给dom元素加 ref="refname",然后通过this.$refs.refname进行获取dom元素
this.$refs.refname   获取dom元素               
this.$refs.refname.msg   获取子组件中的data 
this.$refs.refname.open()  调用子组件中的方法 

例子

//子组件
 
<template>
    <div>{{lcmsg}}</div>
</template>
<script>
export default {
  data() {
    return {
      lcmsg: '我是子组件'
    }
  },
  methods: {
    watchMsg() {
      this.lcmsg = '点我重新赋值'
    }
  }
}
</script>

//父组件
<template>
  <div @click="getData">
    <children ref="children"></children>
  </div>
</template>
<script>
import children from 'components/children.vue'
export default {
  components: { 
    children 
  },
  data() {
    return {}
  },
  methods: {
    getData() {
      //返回一个对象
      this.$refs.children  
      //返回子组件中的lcmsg
      this.$refs.children.lcmsg
      //调用children的watchMsg方法
      this.$refs.children.watchMsg() 
    }
  }
}
</script>

(4)$parent / $children (父子)

  • 父组件中使用$children 获取子组件的实例,但是children并不能保证顺序,并且访问的数据也不是响应式的。
  • 子组件使用$parent获取父组件的实例,

子组件获取到了父组件的parentVal值,父组件改变了子组件中message的值。 需要注意:

  • 通过parent访问到的是上一级父组件的实例,可以使用$root来访问根组件的实例
  • 在组件中使用$children拿到的是所有的子组件的实例,它是一个数组,并且是无序的
  • 在根组件#app上拿parent得到的是new Vue()的实例,在这实例上再拿 parent得到的是undefined,而在最底层的子组件拿$children是个空数组
  • children 的值是数组,而$parent是个对象

(5)eventBus事件总线($emit / $on)(任意组件通信)
eventBus事件总线适用于父子组件、非父子组件等之间的通信
参考详见

(6)vuex
原理: Vuex是专门为vue.js应用程序设计的状态管理工具。

构成:

  1. state:vuex的基本数据,用来存储变量,存放的数据是响应式的。
  2. mutations:提交更改数据,同步更新状态。
  3. actions:提交mutations,可异步操作。
  4. getters:是store的计算属性。
  5. modules:模块,每个模块里面有四个属性。
  • state相当于data,里面定义一些基本数据类型;
  • mutations相当于methods,里面定义一些方法来操作state里面的数据;
  • actions用来存放一些异步操作;
  • getters里存放一些基本数据类型的派生类型,相当于computed
  • modules内部仍然可以继续存放一些子store,内部再有五种属性,模块化vuex
this.$store.state. 获取对应的state值
...mapState([]) computed 里获取对应的值
mapMutations mapgetters mapActions
this.$store.commit('方法','传值') 执行对应方法
store.dispatch('increment') action异步
  • vue3.0是支持vuex的,而且vuex 是一个比较完善的状态管理库。它很简单,并与 Vue 集成的非常好。不使用vuex的原因是vue3 版本重新编写了底层的响应式系统(reactive,ref),并介绍了构建应用程序的新方法。新的响应式系统非常强大,它可以直接用于集中的状态管理,既然自身已经具备这样的功能,何必还要单独安装vuex舍近求远呢
    Vuex和单纯的全局对象有什么区别?

Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一
为什么 Vuex 的 mutation 中不能做异步操作?

每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。

如何解决vuex页面刷新数据丢失问题?

原因: 因为vuex里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,vuex里面的数据就会被清空。

解决方法: 将vuex中的数据直接保存到浏览器缓存中。(一般是用sessionStorage)

slot插槽的理解?slot使用场景有哪些?

slot是什么
slot又名插槽,是Vue的内容分发机制,组件内部的模板引擎使用slot元素作为承载分发内容的出口。插槽slot是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。

比如布局组件、表格列、下拉选、弹框显示内容等

默认插槽 (匿名插槽)

<template>
  <div>
    <slot></slot>
  </div>
</template>

具名插槽

有时我们一个组件里面需要多个插槽。我们怎么来区分多个slot,而且不同slot的显示位置也是有差异的.对于这样的情况, 元素有一个特殊的特性:name。这个特性可以用来定义额外的插槽:

注意:一个不带 name 的 出口会带有隐含的名字“default”。

 <div>
      <slot name="header"></slot>
 </div>
//父
<children>
  <template slot="header">
    <h1>Here might be a page title</h1>
  </template>

作用域插槽

自 2.6.0 起有所更新。已废弃的使用 slot-scope



<template>
  <div>
    <slot :user="user">{{user.firstName}}</slot>
  </div>
</template>

<!-- old -->
<children>
  <template slot="default" slot-scope="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</children>

<!-- new -->
<children>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</children>

组件缓存 keep-alive的作用是什么

< keep-alive >是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。

< keep-alive > 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

使用场景: 多表单切换,对表单内数据进行保存

当组件在内被切换,它的activated和 deactivated这两个生命周期钩子函数将会被对应执行 。

keep-alive的使用

  • 搭配使用
  • 搭配路由使用 ( 需配置路由meta信息的keepAlive属性 )
  • 清除缓存组件

在组件跳转之前使用后置路由守卫判断组件是否缓存

( beforeRouteLeave( to, from, next ){ from.meta.keepAlive = false }
keep-alive的两个钩子函数

activated deactivated
在 keep-alive 组件激活时调用 在keep-alive 组件停用时调用
该钩子函数在服务器端渲染期间不被调用 该钩子在服务器端渲染期间不被调用

使用keep-alive会将数据保留在内存中,如果要在每次进入页面的时候获取最新的数据,需要在 activated阶段获取数据,承担原来created钩子函数中获取数据的任务。

注意: 只有组件被keep-alive包裹时,这两个生命周期函数才会被调用,如果作为正常组件使用,是不会被调用的

$nextTick 原理及作用

其实一句话就可以把$nextTick这个东西讲明白:就是你放在nextTick当中的操作不会立即执行,而是等数据更新、DOM更新完成之后再执行,
作用 解决了异步渲染获取不到更新后DOM的问题

nextTick的原理:nextTick本质是返回一个Promise 。

vue 路由router

Route和router的区别

  • route:是路由信息对象,包括“path,parms,hash,name“等路由信息参数。
  • Router:是路由实例对象,包括了路由跳转方法,钩子函数等。

1.router-view 是渲染视图的
2.router-link组件支持用户在具有路由功能的应用中 (点击) 导航。 通过to属性指定目标地址,默认渲染成带有正确链接的a标签(超链接或锚)
路由模式设置

hash路由 : 地址栏URL中的#符号 http://localhost:3000/#/abc ,不会被包括在HTTP请求中,对后端完全没有影响,改变hash不会重新加载页面 ,每次改变会触发hashchange事件

history路由: http://localhost:3000/abc(需要服务器支持,否则找的是文件夹) 利用了 HTML5 新增的 pushState() 和 replaceState() 方法,在原有的back、forward、go 的基础上,添加对历史记录修改的功能,history需要后端配合,每次刷新会发送请求。因此,如果后端没有做处理的话,就会因找不到资源而报404错误。

动态路由

const routes = [
  {
    path: "/",
    component: () => import("@/views/Main.vue"),
    redirect: "/index",
    children: [
      {
        path: "/index/:id",
        component: () => import("@/views/Index.vue"),
      },
    ],
  },
];

一个“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用。

 methods: {
    goPage(val) {
      this.state = val;
      this.$router.push({
        path: "/index2/" + val,
      });
    },
  },
、、
<louter-link to ='/index2/123'>

路由懒加载

- 方式一: 结合Vue的异步组件和Webpack的代码分析 const Home = resolve => { require.ensure(['../ components/Home.vue'], () => { resolve(require('../components/Home.vue')) })};
- 方式二: AMD写法 const About = resolve => require([' ../ components/ About.vue'], resolve);
- 方式三: 在ES6中,我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割 const Home = () => import(' . ./ components/Home.vue ' )

vue路由之间如何传参

  1. 第一种方式:router-link (声明式路由)
  2. 第二种方式:router.push(编程式路由)
  3. 第三种方式:this.$router.push() (函数里面调用)
  4. 第四种方式:this.$router.replace() (用法同上,push)
  5. 第五种方式:this.$router.go(n)

通过router-link路由导航跳转传递

<router-link to=`/a/${id}`>routerlink传参</router-link>

跳转时使用push方法拼接携带参数。

  this.$router.push({
          path: `/getlist/${id}`,
        })
```javascript
**通过路由属性中的name来确定匹配的路由,通过params来传递参数。**

```javascript
this.$router.push({
          name: 'Getlist',
          params: {
            id: id
          }
        })

使用path来匹配路由,然后通过query来传递参数。

this.$router.push({
          path: '/getlist',
          query: {
            id: id
          }
        })
  • 注意:query有点像ajax中的get请求,而params像post请求。
  • params在地址栏中不显示参数,刷新页面,参数丢失,
  • 其余方法在地址栏中显示传递的参数,刷新页面,参数不丢失。

路由守卫

分别是:全局路由守卫、组件路由守卫、独享路由守卫

一.全局守卫

  • 全局前置守卫
 router.beforeEach((to,from,next)=>{})

to:进入到哪个路由去,from:从哪个路由离开,next:函数,决定是否展示你要看到的路由页面。 它是全局的,即对 整个单页应用(SPA) 中的所有路由都生效,所以当定义了全局的前置守卫,在进入每一个路由之前都会调用这个回调,那么如果你在回调中对路由的跳转条件判断出错,简单点就是死循环…因为你遗漏了某种路由跳转的情况,守卫会一直执行。所以在使用全局前置守卫的时候一定要判断清楚可能会出现的路由跳转的情况。

  • 后置守卫
router.afterEach((to, from) => {}

只有两个参数,to:进入到哪个路由去,from:从哪个路由离全局后置守卫是整个单页应用中每一次路由跳转后都会执行其中的回调。所以多用于路由跳转后的相应页面操作,并不像全局前置守卫那样会在回调中进行页面的重定向或跳转。
全局解析守卫
其中也接收三个参数to、from、next,并且这个钩子函数与beforeEach类似,也是路由跳转前触发,官方解释其与beforeEach的区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

即在 beforeEach 和 组件内beforeRouteEnter 之后,afterEach之前调用。

  router.beforeResolve((to,from,next) => {
  console.log(to,from);
  next()
})

二.组件内的守卫

  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }

三.路由独享的守卫

因为独享守卫通常是某一个或几个页面独有的,所以我们通常会将路由独享守卫写在/src/router/index.js里;

// router/index.js

{
   path: '/detail',
   name: 'detail',
   component: () => import('@/views/Detail.vue'),
   beforeEnter: (to,from,next) => {
     if(!sessionStorage.getItem('username')) {
       alert('请先登录')
       // next({name: 'login'})
       router.push('/login')
     } else {
       next()
     }
   }
 }
 注:因为我们在index.js里我们使用脚手架(vue.cli)创建Vue项目时,
 已经声明了router,所以我们可以使用 router.push('/')跳转页面。
 const router = new VueRouter({
 mode: 'history',
 base: process.env.BASE_URL,
 routes
})

vue混入(mixin) ?

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue
组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

它就是一个对象而已,只不过这个对象里面可以包含Vue组件中的一些常见配置,如data、methods、created等等。

分为局部混入和全局混入,一般是局部混入,全局混入注册后,在任意组件都可以使用,谨慎使用全局混入对象,因为会影响到每个单独创建的 Vue 实例 (包括第三方模板)。

全局使用:

import baseMixin from './mixin/baseMixin'
createApp(App).mixin(baseMixin).mount('#app')

局部引用

export default {
		mixins: [baseMixin],
		setup() {
			let text = '这个是vue3版本mixin的方法记录'
			return {
				text
			}
		}
	}

使用场景

多个组件都会使用到的数据和方法,可以使用mixin,通过mixin文件里面定义好属性和方法,组件调用的时候会产生属性和方法的合并,共同使用。其中mixin中的钩子会优先执

mixin 和 mixins 区别

mixin 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。

虽然文档不建议在应用中直接使用 mixin,但是如果不滥用的话也是很有帮助的,比如可以全局混入封装好的 ajax 或者一些工具函数等等。

mixins 应该是最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。 另外需要注意的是 mixins 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并。

data为什么是一个函数而不是对象

因为JavaScript的特性所导致,在component中,data必须以函数的形式存在,不可以是对象。

组建中的data写成一个函数,数据以函数返回值的形式定义,这样每次复用组件的时候,都会返回一份新的data,相当于每个组件实例都有自己私有的数据空间,它们只负责各自维护的数据,不会造成混乱。而单纯的写成对象形式,就是所有的组件实例共用了一个data,这样改一个全都改了。

动态给vue的data添加一个新的属性时会发生什么?怎样解决?

问题: 数据虽然更新了, 但是页面没有更新

原因:

  • vue2是用过Object.defineProperty实现数据响应式
  • 当我们访问定义的属性或者修改属性值的时候都能够触发setter与getter
  • 但是我们为obj添加新属性的时候,却无法触发事件属性的拦截
  • 原因是一开始obj的要定义的属性被设成了响应式数据,而新增的属性并没有通过Object.defineProperty设置成响应式数据

解决方案:

  • Vue.set()

通过Vue.set向响应式对象中添加一个property,并确保这个新 property同样是响应式的,且触发视图更新

  • Object.assign()

直接使用Object.assign()添加到对象的新属性不会触发更新
应创建一个新的对象,合并原对象和混入对象的属性

  • $forceUpdate

如果你发现你自己需要在 Vue中做一次强制更新,99.9% 的情况,是你在某个地方做错了事
$forceUpdate迫使Vue 实例重新渲染
PS:仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。

总结

如果为对象添加少量的新属性,可以直接采用Vue.set()
如果需要为新对象添加大量的新属性,则通过Object.assign()创建新对象
如果你实在不知道怎么操作时,可采取$forceUpdate()进行强制刷新 (不建议)

PS:vue3是用过proxy实现数据响应式的,直接动态添加新属性仍可以实现数据响应式

vue更新数组时触发视图更新的方法

push(); pop(); shift(); unshift(); splice(); sort(); reverse()

Vue的性能优化(项目优化)有哪些

(1)编码阶段

尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
v-if和v-for不能连用
如果需要使用v-for给每项元素绑定事件时使用事件代理
SPA 页面采用keep-alive缓存组件
在更多的情况下,使用v-if替代v-show
key保证唯一
使用路由懒加载、异步组件
防抖、节流
第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载

(2)SEO优化

预渲染
服务端渲染SSR

(3)打包优化

压缩代码
Tree Shaking/Scope Hoisting
使用cdn加载第三方模块
多线程打包happypack
splitChunks抽离公共文件
sourceMap优化

(4)用户体验

对SSR的理解

SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端

SSR的优势:

  • 更好的SEO
  • 首屏加载速度更快

SSR的缺点:

  • 开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子;
  • 当需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境;
  • 更多的服务端负载。

参考详情

SPA首屏加载慢如何解决

安装动态懒加载所需插件;使用CDN资源。

reactive() 的局限性

reactive() API 有两条限制:

  1. 仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。
  2. 因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:

用 ref() 定义响应式变量

  1. reactive() 的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的 “引用” 机制。为此,Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref:

Vue 角色权限管理设计与实现

角色权限管理共分为三级,分别是页面级、行为级(控件级)、接口级

1.页面级

通过 vue-router 中的路由守卫来进行控制

{
    path: 'roles',
    name: 'Roles',
    component: RouterView,
    meta: {
      title: '角色管理',
    },
    // 自定义 PermissionGuard 方法,见下文
    beforeEnter: PermissionGuard,
    children: [
      // 此处省略
    ]
  }

自定义 PermissionGuard 方法

export default function (to: RouteLocationNormalized, from: RouteLocationNormalized, next: Function) {
  // PermissionHandler 为验证是否有权限的方法,返回一个布尔值,见下文
  const hasPrivilege = PermissionHandler(to.meta?.roleKeys);
    if (!hasPrivilege) {
      // 没有权限跳转到无权限页,并将原本要访问的路由记录在 query 中
      return next(`/unauthorized?origin_target=${to.path}`);
    }
    return next();
}

2.行为级(控件级)

通过自定义的指令来实现,行为级就是针对那些可能会发生操作行为的控件进行显示隐藏的设置,比如最常见的增删改查按钮等。

3.接口级

接口级则是需要后台实现,在接口设计时每个接口会对应一个权限,前端需要收集不同角色在每个页面及控件处使用到的接口。前端动态配置角色权限时,向后台发送新的接口权限列表,后台则根据当前用户的角色查找对应的接口权限列表,来判断该用户请求的接口是否可以执行,功能逻辑与前端类似,前端那要做的则是维护这个接口权限列表。

Vite 和Webpack的区别

都是现代化打包工具
启动方式不一样。vite在启动的时候不需要打包,所以不用分析模块与模块之间的依赖关系,不用进行编译。这种方式就类似于我们在使用某个UI框架的时候,可以对其进行按需加载。同样的,vite也是这种机制,当浏览器请求某个模块时,再根据需要对模块内容进行编译。按需动态编译可以缩减编译时间,当项目越复杂,模块越多的情况下,vite明显优于webpack.
热更新方面,效率更高。当改动了某个模块的时候,也只用让浏览器重新请求该模块,不需要像webpack那样将模块以及模块依赖的模块全部编译一次。

缺点

vite相关生态没有webpack完善,vite可以作为开发的辅助。

参考详情

六、React篇

参考详见

小程序、git

参考详见

你可能感兴趣的:(javascript,html5,vue.js)