---
highlight: a11y-light theme: juejin --- HTML
html代码第一行的作用
HTML 代码的第一行用于声明文档的类型,并且告诉浏览器使用哪种 HTML 的标准来解析页面
HTML中meta属性作用
HTML 中的 <meta> 标签用于提供有关页面的元数据信息,它们通常位于 <head> 部分。<meta> 标签的主要作用包括:
- 页面描述:
<meta name="description" content="页面描述内容">: 提供页面的简短描述,通常会在搜索引擎结果中显示。 - 关键词:
<meta name="keywords" content="关键词1, 关键词2, 关键词3">: 提供页面的关键词,帮助搜索引擎更好地理解页面内容。 - 字符编码:
<meta charset="UTF-8">: 指定页面的字符编码,通常使用 UTF-8。 - 视口设置:
<meta name="viewport" content="width=device-width, initial-scale=1.0">: 控制移动设备上页面的缩放和布局。 - HTTP-equiv 属性:
<meta http-equiv="X-UA-Compatible" content="IE=edge">: 指定 Internet Explorer 的兼容模式。
<meta http-equiv="refresh" content="5;url=https://example.com">: 在 5 秒后重定向到指定的 URL。 - 其他属性:
<meta name="author" content="作者名称">: 指定页面的作者。
<meta name="robots" content="noindex, nofollow">: 告诉搜索引擎不要索引和跟踪此页面。
总的来说,<meta> 标签提供了丰富的页面元数据信息,可以帮助搜索引擎、浏览器和其他应用程序更好地理解和处理页面内容。合理使用 <meta> 标签可以提高页面的可发现性和可访问性。
行内元素 块级元素 空(void)元素有那些
块级元素在页面上以块的形式展现,它会占据一整行的空间,可以设置宽度、高度、内边距和外边距等属性。而行内元素则不会独占一行,它们在一行内按照从左到右的顺序排列,并且不能设置宽度、高度和内边距等属性。
- 行内元素: `a`, `b`, `span`, `img`, `input`, `select`, `strong`;
- 块级元素: `div`, `ul`, `li`, `dl`, `dt`, `dd`, `h1-5`, `p`等;
- 空元素: `<br>`, `<hr>`, `<img>`, `<link>`, `<meta>`;
H5 C3 的新特性有哪些
h5的新特性有: css3的新特性有:
1、语义化标签; 1、rgba和hsla颜色模式;
2、表单增强; 2、文本阴影;
3、视频和音频支持; 3、边框圆角
4、canvas绘图; 4、盒模型;
5、本地存储; 5、多列布局;
6、拖拽释放api; 6、弹性盒子布局;
7、地理api。 7、网格布局;
8、渐变和阴影;
9、过渡和动画
input上传文件的时候,可以同时选择多个文件吗?
HTML中的<input>标签的type属性设置为file时,可以同时选择多个文件进行上传。
可以通过在<input>标签上添加multiple属性来启用多文件选择功能
<input type="file" multiple>
svg格式
基于XML语法格式的图像格式,可缩放矢量图,其他图像是基于像素的,SVG是属于对图像形状的描述,本质是文本文件,体积小,并且不管放大多少倍都不会失真
1.SVG可直接插入页面中,成为DOM一部分,然后用JS或css进行操作
2.SVG可作为文件被引入
3.SVG可以转为base64引入页面
CSS
css的盒子模型
css的盒子模型有哪些:标准盒子模型,IE盒子模型。 盒子设置为IE盒模型,不论内边距距,边框如何改变盒子的真实宽高都不会发生改变。
标准盒子模式:margin boreder padding content
IE盒子模型:margin content(boreder+padding+content)
通过css如何转换盒子模型
box-sizing:content-box 标准盒子模型
box-sizing:boreder-box IE盒子模型
相对单位
在 CSS 中,相对单位有以下几种:
em:相对于父元素的字体大小。例如,如果父元素的字体大小为 16px,子元素的font-size设为 1.5em,则子元素的字体大小为 24px。rem:相对于根元素的字体大小。例如,如果根元素的字体大小为 16px,元素的font-size设为 1.5rem,则元素的字体大小为 24px。与em不同的是,rem取决于根元素的字体大小,而不是父元素的字体大小。vw和vh:相对于视口宽度和高度的百分比。例如,如果视口宽度为 1000px,元素的width设为 50vw,则元素的宽度为 500px。vmin和vmax:相对于视口宽度和高度中较小或较大的那个值的百分比。例如,如果视口宽度为 1000px,视口高度为 800px,元素的width设为 50vmin,则元素的宽度为 400px(因为视口宽度为较大的值,所以按照视口宽度计算)。%:相对于父元素的宽度或高度的百分比。例如,如果父元素的宽度为 1000px,元素的width设为 50%,则元素的宽度为 500px。
相对单位与绝对单位(如像素、英寸等)相比,具有更好的响应式特性,可以根据不同的屏幕尺寸和设备类型自适应地调整大小,因此在响应式设计中得到广泛应用
css兼容性
-webkit-、-moz-、-ms-、-o-解决浏览器css兼容性
垂直居中
1.使用 Flexbox 布局:
.parent {
display: flex;
justify-content: center;
align-items: center;
}
2.使用绝对定位:
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
3.使用 CSS 表格布局:
.parent {
display: table;
width: 100%;
height: 100vh;
}
.child {
display: table-cell;
vertical-align: middle;
}
4.使用 Grid 布局:
.parent {
display: grid;
justify-content: center;
align-items: center;
}
5.使用 CSS 属性 line-height:
.parent {
height: 100vh;
line-height: 100vh;
text-align: center;
}
.child {}
6.使用 CSS 属性 margin:
.parent {
position: relative;
height: 100vh;
}
.child {
position: absolute;
top: 50%;
margin-top: -50px; /* 高度的一半 */
}
水平居中
1.使用 Flexbox 布局:
.parent {
display: flex;
justify-content: center;
}
2.使用绝对定位和 transform 属性:
.parent {
position: relative;
}
.child {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
3.使用 CSS 表格布局:
.parent {
display: table;
margin-left: auto;
margin-right: auto;
}
4.使用margin auto
.parent {
width: 100px;
margin: 0 auto;
}
5.使用 CSS 属性 text-align:
.parent {
text-align: center;
}
垂直水平居中
1.使用 Flexbox 布局:
.parent {
display: flex;
justify-content: center;
align-items: center;
}
2.使用绝对定位和 transform 属性:
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
3.使用 CSS 表格布局:
.parent {
display: table;
width: 100%;
height: 100vh;
}
.child {
display: table-cell;
vertical-align: middle;
text-align: center;
}
display有哪些常见的取值
在CSS中,display 属性用于控制元素的显示类型。它有多种常见的取值,包括:
block: 元素将作为块级元素显示,会在父容器中占据整个可用宽度,并在下一行开始显示。inline: 元素将作为内联元素显示,只占据其内容所需的宽度,并不强制换行。inline-block: 元素会作为内联元素显示,但具有块级元素的特性,可以设置宽度和高度,并在同一行内显示多个元素。none: 元素将被隐藏,不占据任何空间,即完全隐藏该元素。flex: 元素将作为弹性容器显示,可以通过设置弹性属性来控制其子元素的排列方式。grid: 元素将作为网格容器显示,可以通过设置网格属性来控制其子元素的布局方式。table: 元素将作为表格显示,具有表格元素的特性,例如,可以设置单元格宽度、行高等。
BFC
-
什么是 BFC:
- BFC 是一个独立的布局环境,在该环境中的元素布局不会影响到外面的元素。
- BFC 内的元素垂直方向的边距会发生重叠 相邻开启了BFC的元素不会重叠。
- BFC 在页面中拥有自己的渲染规则,它可以包含浮动元素,并阻止父元素因子元素浮动引起的高度塌陷。
-
如何创建 BFC:
- 根元素()或包含根元素的盒子。
- 浮动(float 不为 none)的元素。
- 绝对定位元素(position 为 absolute 或 fixed)。
- 行内块(inline-block)元素。
- 表格单元格(table-cell)元素。
- overflow 值不为 visible 的块级盒子。
解决了什么问题
- 解决margin塌陷的问题
- 避免外边距margin重叠(margin合并)
- 清除浮动
- 阻止元素被浮动元素覆盖
【CSS】什么是BFC?BFC有什么作用?_css bfc-CSDN博客
高度塌陷
父元素的高度无法自动适应子元素的高度,导致父元素高度塌陷,常见于使用浮动或绝对定位的子元素
- 给父元素写固定高度
- 给外部的父盒子也添加浮动,让其也脱离标准文档流
- 父元素添加声明overflow:hidden;(触发一个BFC) 或者float
- 在元素中内容的最后添加一个伪元素
- 额外添加一个兄弟元素 clear: both; 清除浮动
box:after{
content:"";
clear: both;
display: block;
height: 0;
overflow: hidden;
visibility: hidden;
}
flex:1
flex: 1 用于设置弹性盒子(Flexbox)项目的伸缩比例,实际上是三个属性的缩写:flex-grow: 1; flex-shrink: 1 flex-basis: auto;
flex-grow:指定了项目的放大比例,默认为0,即如果存在剩余空间,也不放大。flex-sh:指定了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。flex-basis:指定了项目在分配多余空间之,占据的轴空间(main size)。
当设置 flex: 1 时,实际上表示 flex-grow 1,这味着该项目会根据剩余空间进行伸展,占据所有的剩空间。常用灵活布局让该项目沾满剩余空间。
Sass Scss Less
1.Less 是一种 CSS 预处理器,它扩展了 CSS 的功能,如变量、继承、运算、函数、Mixin。Less 既可以在客户端上运行 (支持IE 6+, Webkit, Firefox),也可在服务端运行。
使用内置函数:Less 提供了一些内置函数,在样式中使用 darken()、lighten()、mix() 等。这些函数可以在样式属性值中使用,以实现颜色值的计算和转换。
// 使用内置函数示例
.element {
color: darken(#3498db, 10%);
background-color: mix(#3498db, #f39c12, 50%);
}
自定义函数:除了内置函数外,Less 还支持自定义函数,以扩展样式表的功能。定义自定义函数的语法为 functionName(arguments) { ... }。
// 自定义函数示例
.calcWidth(@width) {
width: @width * 2;
}
.element {
.calcWidth(50px);
}
2.Sass是一种动态样式语言,Sass语法属于缩排语法,比css比多出好些功能(如变量、嵌套、运算,混入(Mixin)、继承、颜色处理,函数等),更容易阅读。
在 SCSS 中,可以使用 $ 符号定义变量
$primary-color: #3498db;
SCSS 中如何使用 Mixin?
Mixin 可以使用 @mixin 定义,并使用 @include 来调用。
定义 Mixin:
@mixin rounded-corners {
border-radius: 5px;
}
调用 Mixin:
.element {
@include rounded-corners;
}
SCSS 中的嵌套规则是什么?
SCSS 支持样式规则的嵌套书写,使得代码结构更清晰,例如:
.container {
h1 {
color: #000;
}
}
SCSS 中如何进行继承?
可以通过 @extend 实现样式的继承,减少重复代码,例如:
.btn {
color: #fff;
background-color: #007bff;
}
.btn-primary {
@extend .btn;
}
3.SCSS Sass的缩排语法,对于写惯css前端的web开发者来说很不直观,也不能将css代码加入到Sass里面,因此sass语法进行了改良,Sass 3就变成了Scss(sassy css)。与原来的语法兼容,只是用{}取代了原来的缩进。
区别
1.编译环境不一样
Sass的安装需要Ruby环境,是在服务端处理的,而Less是需要引入less.js来处理Less代码输出css到浏览器,也可以在开发环节使用Less,然后编译成css文件,直接放到项目中,也有 Less.app、SimpleLess、CodeKit.app这样的工具,也有在线编译地址。
2.变量符不一样,Less是@,而Scss是$,而且变量的作用域也不一样 Less作用域变量有内找内 无内找外 Sass有内找内无内找相邻的内存变量
3.输出设置,Less没有输出设置,Sass提供4中输出选项:nested, compact, compressed 和 expanded。输出样式的风格可以有四种选择,默认为nested
- nested:嵌套缩进的css代码
- expanded:展开的多行css代码
- compact:简洁格式的css代码
- compressed:压缩后的css代码
4.Sass支持条件语句,可以使用if{}else{},for{}循环等等。而Less不支持。
- 引用外部CSS文件
- scss引用的外部文件命名必须以_开头, 如下例所示:其中_test1.scss、_test2.scss、_test3.scss文件分别设置的h1 h2 h3。文件名如果以下划线_开头的话,Sass会认为该文件是一个引用文件,不会将其编译为css文件.
- Less引用外部文件和css中的@import没什么差异。
6.Sass和Less的工具库不同
Sass有工具库Compass, 简单说,Sass和Compass的关系有点像Javascript和jQuery的关系,Compass是Sass的工具库。在它的基础上,封装了一系列有用的模块和模板,补充强化了Sass的功能。
Less有UI组件库Bootstrap,Bootstrap是web前端开发中一个比较有名的前端UI组件库,Bootstrap的样式文件部分源码就是采用Less语法编写。
总结
不管是Sass,还是Less,都可以视为一种基于CSS之上的高级语言,其目的是使得CSS开发更灵活和更强大,Sass的功能比Less强大,基本可以说是一种真正的编程语言了,Less则相对清晰明了,易于上手,对编译环境要求比较宽松。考虑到编译Sass要安装Ruby,而Ruby官网在国内访问不了,个人在实际开发中更倾向于选择Less。
link和@import区别
<link> 标签是 HTML 中的标签,用于在 HTML 文件中引入外部资源;而 @import 是 CSS 中的规则,用于在 CSS 文件中引入外部 CSS 文件
重绘:当元素的一部分属性发生改变,如外观、背景、颜色等不会引起布局变化,只需要浏览器根据元素的新属性重新绘制,使元素呈现新的外观叫做重绘
重排(回流):当 页面布局发生改变而需要 DOM 树重新计算的过程
重绘不一定需要重排(比如颜色的改变),重排必然导致重绘(比如改变网页位置)
1.多个属性尽量使用简写,例如:boder可以代替boder-width、boder-color、boder-style
2.创建多个dom节点时,使用documentfragment创建
3.避免使用table布局
4.避免设置多层内联样式,避免节点层级过多
5.避免使用css表达式
6.将频繁重绘或回流的节点设置为图层,图层能够阻止该节点的渲染行为影响到别的节点(例:will-change\video\iframe等标签),浏览器会自动将该节点变为图层
多列布局
在CSS3之前,想要设计类似报纸那样的多列布局,有两种方式可以实现:一种是"浮动布局"float,另一种是“定位布局”。
这两种方式都有缺点:浮动布局比较灵活,但不容易控制;定位布局可以精准定位,但是不够灵活
为了解决这多列布局的难题,CSS3新增了一种布局方式-多列布局。多列布局提供了一种多列组织内容的方式,可以简单的实现类似报纸格式的布局。
在CSS3中,多列布局常用的属性有以下属性
- column-count 定义元素的列数
- column-width 定义每一列的宽度
- column-gap 定义两列之间的距离
- column-rule 定义两列之间的边框样式
- column-span 定义跨列样式
JS
script标签能否自闭合
根据HTML5规范,<script>标签不可以被自闭合的。因为它们可以包含内联的脚本代码或引用外部脚本文件。
0.1+0.2为什么不等于0.3?
0.1和0.2在转换成二进制后会无限循环,由于标准位数的限制后面多余的位数会被截掉,此时就已经出现了精度的损失,相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成 0.30000000000000004。
Js 为什么会存在数字精度丢失的问题,如何解决
计算机存储双精度浮点数需要先把十进制数转换为二进制的科学记数法的形式,然后计算机以自己的规则{符号位+(指数位+指数偏移量的二进制)+小数部分}存储二进制的科学记数法
因为存储时有位数限制(64位),并且某些十进制的浮点数在转换为二进制数时会出现无限循环,会造成二进制的舍入操作(0舍1入),当再转换为十进制时就造成了计算误差
解决方法
使用 toPrecision 凑整并 parseFloat 转成数字后再显示
this
this 的指向取决于函数被调用的方式
- 默认绑定
- 隐式绑定
- new绑定
- 显示绑定
箭头函数没有自己的this 外层作用域this绑定谁箭头函数的this就绑定谁
???js微任务和宏任务
浅拷贝深拷贝
深拷贝和浅拷贝都是对于复杂数据类型进行复制的操作,区别在于复制的方式不同。
浅拷贝是指创建一个新对象,这个新对象有着原始对象属性值的一份精确拷贝,如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝是指创建一个新对象,这个新对象的值和原始对象的值完全没有关联,即便原始对象中有引用类型的属性,新对象也会开辟新的内存地址,完全拷贝一份新的对象,修改一个对象不会影响到另一个对象。
闭包
闭包是定义在一个函数内部的函数,内层函数可以访问外层函数的局部变量,这些变量被内层函数引用不会被回收,好处是使局部变量拥有更长的生命周期可以用来封装一段逻辑,坏处是闭包常驻内存造成内存泄露。
- 一个普通的函数function,如果它可以访问外层作用域的自由变量,那么这个函数和周围环境就是一个闭包;
- 从广义的角度来说:JavaScript中的函数都是闭包;
- 从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用域的自由变量,那么它是一个闭包
当闭包内部的函数持续引用外部函数的变量时,这些变量无法被垃圾回收机制回收,导致内存泄漏
箭头函数和普通函数区别
-
this 指向不同:
- 普通函数中的
this指向调用该函数的对象。this的值是动态确定的,取决于函数的调用方式。 - 箭头函数中的
this指向定义箭头函数时所在的外层作用域中的this值。箭头函数中的this是静态确定的,不会随着调用方式的改变而改变。
- 普通函数中的
-
构造函数:
- 普通函数可以作为构造函数使用,使用
new关键字创建对象实例。 - 箭头函数是没有显式原型prototype,不能作为构造函数使用,如果尝试使用
new关键字调用箭头函数,会抛出错误。
- 普通函数可以作为构造函数使用,使用
-
arguments 对象:
- 普通函数可以访问
arguments对象,该对象包含函数调用时传入的所有参数。 - 箭头函数没有自己的
arguments对象,但可以访问外层作用域的arguments对象。不绑定this、arguments、super
- 普通函数可以访问
-
返回值:
- 普通函数如果没有显式返回值,会隐式返回
undefined。 - 箭头函数如果函数体只有一个表达式,可以省略
return关键字,表达式的结果会自动返回。
- 普通函数如果没有显式返回值,会隐式返回
数据类型判断
- typeof:一般判断基本数据类型
typeof 判断基础数据类型(数组、对象、null都会被判断为object)typeof 并不能准确地判断复杂数据类型的具体类型。
用于判断数据类型,返回值为6个[字符串],分别为string、Boolean、number、function、object、undefined。
- instanceof :一般判断引用数据类型,不能判断基本数据类型,判断一个对象是否属于某个类或者其子类的实例。它可以判断复杂数据类型的具体类型。instanceof 检测的是原型,检查的是对象的原型链中是否包含指定构造函数的
prototype属性
'hello' instanceof String // false undefined instanceof Undefined // 报错 [] instanceof Array // true
- constructor:通过原型链继承属性判断。null和undefined是无效的对象,JS对象的constructor是不稳定的,这个主要体现在自定义对象上,当开发者重写prototype后,原有的constructor会丢失,constructor会默认为Object。
console.log([].constructor === Array) // true
console.log(window.constructor === Window) // true
console.log(new Number(22).constructor === Number) // true
console.log((new Date()).constructor === Date) // true
- Object.prototype.toString.call() :较为准确的判断方法。toString是Object原型对象上的一个方法,该方法默认返回其调用者的具体类型。
console.log(Object.prototype.toString.call([])) // [object Array]
console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
var let const
let和var用来声明变量赋值后可以改变它的值,const用来声明常量赋值后就不能改变它的值。
const不允许只声明不赋值,一旦声明就必须赋值
var是函数作用域,let和const是块级作用域。
var有提升的功能,let和const没有
| 关键字 | 变量提升 | 块级作用域 | 重复声明同名变量 | 重新赋值 |
|---|---|---|---|---|
| var | √ | × | √ | √ |
| let | × | √ | × | √ |
| const | × | √ | × | × |
在最外层的作用域,也就是全局作用域,用var声明的变量,会作为window的一个属性
花括号{}就是块级作用域,函数作用域就是函数里面的内容
变量就是赋值后可以改变它的值,常量就是赋值后就不能改变它的值。
let和const作用域提升
在执行上下文的词法环境创建出来的时候,变量事实上已经被创建了,只是这个变量是不能被访问的。
在声明变量的作用域中,如果这个变量可以在声明之前被访问,那么我们可以称之为作用域提升 定义let和const它虽然被创建出来了,但是不能被访问,我认为不能称之为作用域提升;
事件委托
事件委托,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,而不是目标元素,它利用了事件冒泡的特性,只需要在某个祖先元素上注册一个事件,就能管理其所有后代元素上同一类型的事件,而不需要给子元素一个一个的注册事件。
- 减少整个页面所需的内存,提升整体性能
- 动态绑定,减少重复工作
如果把所有事件都用事件代理,可能会出现事件误判,即本不该被触发的事件被绑定上了事件 有些没有事件冒泡机制,所以无法进行委托绑定事件
判断一个对象是不是空对象
Object.keys(obj).length === 0
JSON.stringify(obj) === '{}'
for...in 循环
如何判断一个元素是否在可视区域中
判断一个元素是否在可视区域,我们常用的有三种办法:
- offsetTop、scrollTop
- getBoundingClientRect
- Intersection Observer
图片懒加载
- 最简单的实现方式是给
img标签加上loading="lazy" - 把图片的地址放入到
data-src属性里,然后监听图片是否进入可视区域内,把data-src赋值给src
延迟加载JS
迟加载:async defer /dɪˈfɜːr/
defer :在 HTML 解析完成后,顺次执行js脚本,多个defer按照出现在HTML顺序加载
- 使用
defer属性加载的 JavaScript 文件会在 HTML 解析完成后,顺次执行js脚本,DOM Tree 构建完成后,并且在DOMContentLoaded事件之前执行,不会阻塞DOM Tree页面的渲染。 - 多个带有
defer属性的脚本会按照它们在 HTML 中出现的顺序执行。 - 适合需要等待整个文档解析完成后执行的 JavaScript 文件。
- 对于没有外部引用的script无效
- 性能的角度来说放在head更好
async :async和HTML同步解析,谁先加载完谁先执行
- 使用
async属性加载的 JavaScript 文件会在加载完成后立即执行,不会阻塞页面的渲染。 - 异步加载的 JavaScript 文件不会按照它们在 HTML 中出现的顺序执行,他们是同步解析,谁先加载完谁先执行。
- 适合那些不需要依赖页面中其他元素的 JavaScript 文件因为可能拿不到Dom,独立下载独立运行。
高阶函数
一个函数接受一个或多个函数作为参数,或者返回值是函数,满足其中一个就是高阶函数
作用域作用域链
作用域就是变量的可用性的代码范围,就叫做这个变量的作用域。简单理解,就是在这个范围内,变量是可以使用的,超过这个范围,变量就无法使用,这个范围就是作用域
作用域分为三种:全局作用域、局部作用域、块级作用域。
- 全局作用域:顾名思义,全局作用域就是能够在全局使用,可以在代码的任何地方被调用
- 局部作用域:局部作用域只能作用于局部的代码片段,常见于函数内部,即函数内创建的变量,只能作用于函数内部,函数外部无法使用函数内部创建的变量。
- 块级作用域:块级作用域是es6新增的,使用let关键字创建变量、const关键字创建常量(当然let、 const也会有自己的语法规范,这里不过多展开),作用域只存在于{}花括号内
什么是作用域链
当你要访问一个变量时,首先会在当前作用域下查找,如果当前作用域下没有查找到,则返回上一级作用域进行查找,直到找到全局作用域,这个查找过程形成的链条叫做作用域链。
for of和for in的区别
for...of 和 for...in 是两种不同的循环方式,用于遍历数据结构,它们的主要区别在于适用的数据结构和遍历方式不同,for...in 循环出的是 key,for...of 循环出的是 value
for…of
for...of ,如数组、Map、Set、字符串等,不会遍历对象(可枚举的)和原型链上的属性或方法。 ES6 中引入的遍历方法,适用于遍历可迭代对象(Iterables)的值
for...of 不能循环普通的对象,构造函数创造的对象,需要通过和 Object.keys()转换为一个可迭代对象搭配使用
for…in
for...in 主要用于遍历对象的属性,包括原型链上可枚举的属性。在遍历数组时,for...in 也会遍历数组的索引(属性名),不仅仅是数组元素本身。因此,通常不推荐在遍历数组时使用 for...in。
可迭代对象是具有一个名为 iterator 的属性的对象。这个属性是一个函数,当对象被迭代时会返回一个迭代器对象。通过实现 iterator 属性,对象可以定义自己的迭代行为,允许通过 for…of 循环、扩展运算符(…)、Array.from() 等方式进行迭代
可枚举对象是指对象的属性可以通过对象遍历机制(如 for…in 循环)访问到的属性除非该属性名是一个Symbol,可枚举属性是对象的一种属性特性,用来控制属性是否会被遍历到通过 Object.keys()、Object.values()、Object.entries() 等方法可以提取对象的可枚举属性,
Set与Map的区别
Set 和 Map 是 ES6 中新增的两种数据结构,它们都用于存储数据集合
-
Set:
- Set 对象允许你存储任何类型的唯一值,无论是原始值还是引用值。它不允许重复值存在。
- Set 中的值是唯一的,可以用于去除数组中的重复元素。
- Set 内部的元素只能通过值来操作,不能直接访问到特定位置的元素。
- Set 通常用于存储一组不重复的值,并且不需要与特定值相关联的情况。
- Set 提供了迭代器(Iterator)接口,可以使用 for...of 循环或 forEach 方法对 Set 进行迭代
-
Map:
- Map 是一组键值对的集合,其中键是唯一的,值可以重复。
- Map 中的键可以是任意数据类型,包括原始值、对象引用等。
- Map 中的元素可以通过键来访问和操作,可以根据键对值进行增删改查。
- Map 提供了遍历方法和属性,可以方便地操作键值对集合。
- Map 的大小是根据其键值对的数量来计算的。对于需要频繁增删键值对的操作,Map 通常具有更好的性能。
如果只需要存储唯一值并且不关心顺序,使用 Set;如果需要将值与键关联,并且需要根据键进行查找和操作,使用 Map。
Set
const set = new Set([1, 2, 3, 4, 5]);
console.log(set.has(3)); // true
console.log(set.has(6)); // false
set.add(6);
set.delete(5);
for (const value of set) {
console.log(value);
}
Map
const map = new Map([
['name', 'John'],
['age', 30]
]);
console.log(map.get('name')); // John
console.log(map.get('age')); // 30
map.set('country', 'USA');
map.delete('age');
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}
const originalMap = new Map([
['key1', 'value1'],
['key2', 'value2'],
['key3', 'value3']
]);
const newMap = new Map([...originalMap].map(([key, value]) => [key, value.toUpperCase()]));
迭代器
在 JavaScript 中,迭代器(Iterator)本身是对象,实现了next() 方法,返回一个对象 ,对象具有两个属性{done:false/true,value:value/undefined}。
它提供了一种访问数据结构(比如数组、集合、字符串等)元素的统一接口。迭代器对象具有一个 next() 方法,通过调用这个方法可以依次访问数据结构中的每一个元素。每次调用 next() 方法都会返回一个包含 value 和 done 两个属性的对象,其中 value 表示当前遍历到的元素的值,done 为 true 表示遍历结束。
迭代器为 JavaScript 提供了一种通用的遍历机制,使得可以用统一的方式来遍历不同类型的数据结构,而不需要了解数据结构的内部实现。在 JavaScript 中,许多数据结构都可以通过迭代器来遍历,比如数组、Map、Set 等。通过迭代器,我们可以使用 for…of 循环、展开运算符(spread operator)、解构赋值等方式来遍历数据结构,从而更加方便和灵活地操作数据。
可迭代对象
当一个对象实现了Iterable Protocol(可迭代协议)它就是一个可迭代对象,这个对象要求包含一个键为 Symbol.iterator 的属性,该属性的值是一个函数iterator 方法,通过for of遍历其实就是通过Symbol.iterator 属性返回的方法去拿到需要遍历的值。
- JavaScript中语法:for ...of、展开语法(spread syntax)、yield*、解构赋值(Destructuring_assignment);
- 创建一些对象时:
new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable]); - 一些方法的调用:
Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable);
cookie与本地存储的区别
1、存储容量
Cookie:每个域名下的Cookie总容量通常为4KB,每个Cookie的大小限制为几KB左右。
本地存储:Local Storage和Session Storage的总容量通常为5MB或更大,每个浏览器可能有不同的限制。
- 生命周期
Cookie:会话级的(浏览器关闭时失效)也可以设置过期时间持久性的(指定过期时间)
本地存储:Local Storage的数据永久保存在浏览器中,除非代码或用户手动删除;Session Storage的数据仅在当前会话中有效,关闭浏览器或标签页后将被清除。
- 数据传输
Cookie:,Cookie 在每次 HTTP 请求中会被自动发送到服务器,增加数据传输的开销。
本地存储:不会随每个请求发送给服务器,仅在浏览器端使用,因此不会增加数据传输的开销。
- 数据安全性
Cookie:由于Cookie是存储在浏览器中的,所以可能受到跨站脚本攻击(XSS)和跨站请求伪造(CSRF) 等安全问题的影响。
本地存储:由于本地存储不会自动附加到每个请求中,所以相对于Cookie来说更加安全,但仍然需要注意XSS攻击。
- 数据类型
Cookie:只能存储字符串类型的数据,如果要存储复杂的数据结构,需要进行序列化和反序列化。
本地存储:可以存储各种数据类型,包括字符串、数字、布尔值、对象、数组等。
- 数据访问
Cookie: 可以在同一站点的不同页面之间共享,也可以设置跨域共享,可以通过document.cookie来访问和操作Cookie。
本地存储:只能被同源页面访问,不能被其他域名的页面访问
7.用途和性能
Cookie 主要用于会话管理、用户身份认证、跟踪用户行为等。但由于每次请求都会携带 Cookie 数据,可能会增加网络传输开销。
本地存储适合用于存储较大量级的数据,如用户配置、缓存数据等,且不会随每次请求发送到服务器,对性能影响较小。
new
new 关键字在 JavaScript 中用于调用构造函数,通过 new 关键字调用构造函数可以创建一个新的对象。构造函数可以看作是用来初始化对象的特殊函数,它会为新对象设置属性和方法。当使用 new 关键字和构造函数一起调用时,会创建一个空对象,并将这个空对象绑定到构造函数中的 this 关键字。然后构造函数中的代码会初始化这个对象的属性和方法,最终返回这个新的对象。通过这种方式我们可以轻松地创建多个拥有相似属性和方法的对象,实现了代码的重用和结构的清晰化
- 创建一个新的对象obj
- 将对象与构建函数通过原型链连接起来
- 将构造函数中的this绑定到新建的对象obj上
- 根据构造函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理
function myNew(fn,...args){
//1.创建一个空对象
let obj={}
//2.将新创建对象的原型指向构造函数的原型对象上
obj.__ptoto__=fn.prototype
//3.将构造函数的this指向新创建的对象上
let result = fn.apply(obj,args)
//判断返回值是否是对象是的话直接返回不是不处理
return result instanceof Object ? result : obj
}
function Person(name, age) {
this.name = name
this.age = age
}
let Person1 = myNew(Person,'yuyss', 17)
严格模式
严格模式是一种JavaScript的执行模式,它提供了更严格的语法和错误检查。在严格模式下,一些不安全或不推荐的语法会被禁用,同时会引入一些新的特性,如变量必须先声明才能使用、禁止使用this指向全局对象等
JavaScript中的事件模型
javascript中的事件,可以理解就是在HTML文档或者浏览器中发生的一种交互操作,使得网页具备互动性, 常见的有加载事件、鼠标事件、自定义事件等
事件流都会经历三个阶段:
- 事件捕获阶段(capture phase)
- 处于目标阶段(target phase)
- 事件冒泡阶段(bubbling phase)
事件模型可以分为三种:
- 原始事件模型(DOM0级)
- 标准事件模型(DOM2级)
- IE事件模型(基本不用
js事件监听
on事件属性是元素对象的属性,直接赋值一个函数;addEventListener是元素对象的方法,通过方法进行事件监听器的添加。on事件属性每次只能保存一个事件处理函数;addEventListener可以添加多个不同的事件处理函数。addEventListener支持事件捕获和事件冒泡,而on事件属性没有这个功能。
通常来说,推荐使用 addEventListener 方法来添加事件监听,特别是在需要添加多个事件处理函数或需要更灵活地控制事件监听时。
js的变量提升
变量提升是指在JavaScript中,变量和函数声明会在代码执行之前被提升到作用域的顶部。这意味着可以在声明之前使用变量和函数。
变量和函数的声明会被提升到最顶部执行 函数提升高于变量的提升 函数内部如果用var声明了相同名称的外部变量,函数将不再向上寻找 匿名函数不会提升,但是只提升声明本身,不会提升赋值。这意味着,无论在代码中的哪个位置声明变量,在执行阶段,变量的声明都会被提升到所在作用域的顶部。但变量的赋值不会提升,仍然保留在原来的位置。
使用let和const声明的变量也存在变量提升,但与var有所不同。let和const存在暂时性死区,在声明前访问这些变量会导致引发 ReferenceError 错误。这是因为let和const不允许在声明前使用变量,而var会将这些变量提升为undefined。
内存泄漏
-
对象循环引用
-
定时器未清除
-
全局变量未清除
-
DOM元素用了未正确删除 addEventListener事件用了未卸载
-
闭包未正确使用 使用闭包函数,并且闭包内部引用了外部作用域的变量,如果这些变量在之后不再需要但闭包仍然存在,会导致内存泄漏
href 与 src
href主要用于超链接,定义链接目标,<a>、<link>。
src主要用于引入外部资源,定义资源来源,<img>、<script>、<iframe>。
垃圾回收机制
作用:清除不在使用的对象,腾出内存空间
- 对象不再被引用的时候是垃圾;
- 对象不能从根上访问到时也是垃圾;
标记清除法
标记清除法分为标记和清除两个阶段,标记阶段需要从根节点遍历内存中的所有对象,并为可达的对象做上标记,清除阶段则把没有标记的对象(非可达对象)销毁。
缺点
- 首先是内存碎片化。这是因为清理掉垃圾之后,未被清除的对象内存位置是不变的,而被清除掉的内存穿插在未被清除的对象中,导致了内存碎片化。
- 内存分配速度慢。由于空闲内存不是一整块,假设新对象需要的内存是
size,那么需要对空闲内存进行一次单向遍历,找出大于等于size的内存才能为其分配
引用计数法
引用计数法主要记录对象有没有被其他对象引用,如果没有被引用,它将被垃圾回收机制回收。它的策略是跟踪记录每个变量值被使用的次数,当变量值引用次数为0时,垃圾回收机制就会把它清理掉。
缺点
- 首先它需要一个计数器,这个计数器可能要占据很大的位置,因为我们无法知道被引用数量的多少。
- 无法解决当出现循环引用时无法回收的问题。例如
a引用了b,b也引用了a,两个对象相互引用,引用计数不为0,因此无法进行内存清理
扩展
V8引擎的优化
标记整理(Mark-Compact)和“标记-清除”相似;
- 不同的是,回收期间同时会将保留的存储对象搬运汇集到连续的内存空间,从而整合空闲空间,避免内存碎片化;
分代收集(Generational collection)—— 对象被分成两组:“新的”和“旧的”。
- 许多对象出现,完成它们的工作并很快死去,它们可以很快被清理;
- 那些长期存活的对象会变得“老旧”,而且被检查的频次也会减少;
增量收集(Incremental collection)
- 如果有许多对象,并且我们试图一次遍历并标记整个对象集,则可能需要一些时间,并在执行过程中带来明显的延迟。
- 所以引擎试图将垃圾收集工作分成几部分来做,然后将这几部分会逐一进行处理,这样会有许多微小的延迟而不是一个大的延迟;
闲时收集(Idle-time collection)
- 垃圾收集器只会在 CPU 空闲时尝试运行,以减少可能对代码执行的影响。
- 这种算法通常用于移动设备或其他资源受限的环境,以确保垃圾收集对用户体验的影响最小。
JavaScript垃圾回收机制_新生代垃圾回收策略-CSDN博客
浏览器为什么支持单页面路由呢
浏览器支持单页面路由的一个重要原因是History API。
在传统的多页面应用中,页面之间的跳转通过超链接或表单提交等方式实现,每个页面都有一个唯一的URL地址。而在单页面应用中,页面的跳转是通过JavaScript代码控制,使用history API可以更加方便地实现这种页面切换逻辑。
history API是HTML5规范中新增的一组API,可以让开发者更加方便地操作浏览器的历史记录。通过history API,开发者可以在不重新加载整个页面的情况下,改变浏览器的URL地址,添加或修改历史记录,以及监听历史记录的变化等操作。
在单页面应用中,开发者可以使用history API来实现前端路由,即在不重新加载整个页面的情况下,通过改变URL地址,实现不同页面之间的切换。这样可以提高应用程序的性能,并且使得应用程序更具交互性和动态性
使用history进行导航的时候,我们的页面真的进行了一个切换吗
当使用 history API 进行导航时,页面确实会进行切换,但是这个切换是在前端完成的,而不是像传统页面跳转那样进行整个页面的刷新。
这是通过以下几个步骤实现的:
- URL 更新: 当你调用
history.pushState()或history.replaceState()时,浏览器会更新当前 URL,但不会触发页面刷新。 - DOM 更新: 在 URL 更新后,你可以通过 JavaScript 操作 DOM 来更新页面内容,比如切换不同的组件或视图。
- 无刷新切换: 因为页面没有刷新,所以用户体验更流畅,不会出现白屏或闪烁的情况。
- 状态管理:
historyAPI 还提供了popstate事件,可以监听浏览器历史记录的变化,从而实现前进、后退等操作。
这种基于 history API 的前端路由机制,可以让单页应用(SPA)实现无刷新的页面切换,提升用户体验。同时,它也避免了整页刷新带来的性能损耗。
获取url 参数获取的 API
使用 URLSearchParams 接口来处理 URL 查询参数。URLSearchParams 接口提供了一种简单的方式来操作 URL 查询参数,包括获取、添加、删除参数等操作
const url = new URL('https://www.example.com/?name=John&age=30');
// 获取 URL 中的查询参数
const searchParams = url.searchParams;
// 获取特定参数的值
const name = searchParams.get('name');
const age = searchParams.get('age');
console.log(name); // 输出 "John"
console.log(age); // 输出 "30"
递归和迭代的区别
-
递归:
- 递归是指一个函数在执行过程中调用自身的行为。
- 递归函数需要一个终止条件,以避免无限循环调用。
- 递归思想简洁清晰,可以解决问题的复杂性,但过度深度的递归可能会导致栈溢出错误。
- 在某些情况下,递归的效率可能低于迭代方法。
- 适用于深度优先搜索等场景。
-
迭代:
- 迭代是通过循环控制结构反复执行同一段代码。
- 迭代通常使用循环结构(如
for、while)来实现。 - 迭代不会增加函数调用栈的深度,可以更节省内存。
- 通常情况下,迭代比递归更加高效。
- 适用于广度优先搜索等场景。
递归和迭代都是循环的一种。 简单地说,递归是重复调用函数自身实现循环。迭代是函数内某段代码实现循环,而迭代与普通循环的区别是:循环代码中参与运算的变量同时是保存结果的变量,当前保存的结果作为下一次循环计算的初始值
JSONP跨域原理
JSONP(JSON with Padding)是一种跨域数据请求的解决方案,其原理是利用<script>标签的src属性没有跨域限制的特点来实现跨域请求数据 JSONP只支持GET请求 JSONP存在一些安全性风险,比如可能会遭受XSS(跨站脚本)攻击 一般会使用CORS(跨域资源共享)来进行跨域请求,它更加灵活和安全。
CORS的原理
CORS通过在HTTP响应头中添加一些特定的字段来告诉浏览器允许跨域请求。当浏览器发起跨域请求时,先进行预检请求(OPTIONS请求),根据服务器返回的响应头信息来判断是否允许跨域请求。如果满足条件,则浏览器会发送实际的跨域请求,并在响应中获取数据。
CORS的优点
- 更加安全:能够降低网站和用户的安全风险,防止恶意网站窃取用户信息。
- 更加灵活:可以选择性地允许特定域名或一组域名来访问资源,具有更灵活的控制权限。
错误捕获方式
编辑
面向对象
继承
对象的 [[prototype]] __proto__(隐式原型)
- 每个对象在创建时都有一个内部的
[[prototype]]属性,它指向该对象的原型对象。这个原型对象可以是另一个对象,也可以是null。 - 这个原型对象被用于实现对象之间的继承关系,当我们访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到为止。
函数的 prototype(显示原型)
- 每个函数对象都有一个名为
prototype的属性,它指向一个对象。这个对象通常被用作构造函数创建的实例对象的原型对象。 - 通过操作函数的
prototype属性,我们可以为该函数创建的实例对象添加共享的属性和方法,实现基于原型的继承机制。
__proto__
__proto__ 是每个对象都具有的属性,它指向该对象的原型。通过 __proto__ 属性,对象可以访问和继承原型对象的属性和方法。当你访问一个对象的属性或方法时,如果对象本身没有该属性或方法,JavaScript 会沿着原型链(通过 __proto__)向上查找,直到找到该属性或方法或者到达原型链的顶端(即 null)。推荐使用 Object.getPrototypeOf(obj) 方法来获取对象的原型。
prototype
prototype 是函数对象特有的属性。当你创建一个函数时,JavaScript 会为该函数自动创建一个 prototype 属性,并将其初始化为一个空对象。这个 prototype 对象是函数对象的一个属性,它被用作构造函数,用于创建新对象实例时的原型对象。
当你使用 new 关键字调用一个函数时,
JavaScript 会创建一个新的对象,
并将该对象的 __proto__ 属性指向构造函数的 prototype 对象。
这样,新创建的对象就可以访问和继承构造函数的原型对象上的属性和方法。
总结:
__proto__ 是对象特有的属性,用于指向对象的原型,而 prototype 是函数特有的属性,用于指向构造函数的原型对象。通过 __proto__ 属性,对象可以访问和继承原型对象的属性和方法,而通过 prototype 属性,函数可以定义和共享供实例对象继承的属性和方法。
原型链
在 JavaScript 中,每个对象都有一个指向原型对象的引用,这个引用通常被称为 __proto__。当访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的顶端(即 null)。
这条从对象到原型对象再到原型对象的原型对象,直到 null 的链条被称为原型链。通过原型链,对象可以继承其原型对象的属性和方法,实现了在 JavaScript 中的简单继承机制。
当查找一个属性或方法时,对象会顺着 __proto__ 不断向上查找,直到找到属性或方法或者到达原型链的末端(null)。这种从对象到原型对象再到原型对象的原型对象的链条就是原型链。
继承方法放原型上属性在函数里
在使用构造函数创建对象时,将属性放在函数内部而将方法放在原型上是一种常见的最佳实践
-
内存优化:
- 如果将方法也放在构造函数内部, 那么每次创建新对象时, 都会重新创建一份方法的副本。这样会占用更多的内存空间。
- 而将方法放在原型上, 所有通过该构造函数创建的对象都可以共享同一个方法, 不需要为每个对象重新创建。这样可以大大节省内存。
-
封装性:
- 将属性放在构造函数内部可以更好地实现数据封装。构造函数内部的属性是私有的, 外部无法直接访问和修改。
- 而将方法放在原型上, 可以被所有实例对象访问和使用, 方便实现共享功能。
-
继承:
- 将方法放在原型上, 可以更方便地实现基于原型的继承。子类可以直接继承父类原型上的方法, 而不需要重复定义。
-
性能:
- 相比于每次创建对象时都要初始化属性, 从原型上查找方法的性能开销要小得多。
constructor
-
对象的
constructor属性:可以帮助我们了解对象的构造来源,创建新的对象实例,判断对象的类型,以及在继承中配置constructor保持正确的构造函数引用。-
constructor属性指向创建当前对象的构造函数。通过
obj.constructor可以获取创建该对象的构造函数。 -
通过
new obj.constructor()可以创建一个新的对象实例,该对象与原对象具有相同的构造函数。 -
可以使用
obj.constructor === SomeConstructor来判断对象是否由某个特定的构造函数创建。 -
在实现继承时,可以通过
SubClass.prototype.constructor = SubClass来设置子类的constructor属性,以保证对象实例的constructor属性指向正确的构造函数。
-
-
函数的
constructor属性:主要用于标识函数的类型,创建新的函数实例,判断函数的类型,以及在继承中配置constructor保持正确的构造函数引用- 标识函数的类型,函数本身也是对象,因此函数也可以有
constructor属性。对于函数对象来说,constructor属性指向Function构造函数,箭头函数,constructor属性不存在,因为箭头函数没有自己的constructor, new Function()语法来创建新的函数实例,这个新函数的constructor属性会指向Function构造函数- 可以使用
func.constructor === Function来判断一个函数是否是普通函数。
可以使用func.constructor.name来获取函数的名称。 - 在实现函数继承时,可以通过
SubFunc.prototype.constructor = SubFunc来设置子函数的constructor属性,以保证对象实例的constructor属性指向正确的构造函数。
- 标识函数的类型,函数本身也是对象,因此函数也可以有
对象的 constructor 属性指向创建该对象的构造函数,而函数的 constructor 属性指向 Function 构造函数。这些 constructor 属性在一些情况下可以用来确定对象的类型或者函数的构造函数。
共享new对象创建的原型方法
编辑
function Person(name, age, height, address) {
this.name = name
this.age = age
this.height = height
this.address = address
}
Person.prototype.eating = function() {
console.log(this.name + "在吃东西~")
}
Person.prototype.running = function() {
console.log(this.name + "在跑步~")
}
var p1 = new Person("why", 18, 1.88, "广州市")
var p2 = new Person("kobe", 30, 1.98, "北京市")
p1.eating()
p2.running()
继承的方法
//原型链继承 实现了基本继承但有重复的属性代码
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.running = function () {
console.log('me is running');
}
function Student(name, age, height) {
this.name = name
this.age = age
this.height = height
}
let p = new Person('oyss',18)
Student.prototype = p
Student.prototype.studying=function(){
console.log('me is studying');
}
let s1 = new Student('yuyss',22,180)
console.log(s1.name,s1.age,s1.height);
s1.running()
s1.studying()
//构造函数继承(借助 call) 只继承了属性没有方法
function Person(name,age){
this.name=name
this.age=age
}
Person.prototype.running=function (){
console.log('me is running call')
}
function Student(name,age,height){
Person.call(this,name,age)
this.height=height
}
Student.prototype.studying=function(){
console.log('me is studying call');
}
let stu=new Student('yuyss',22,180)
console.log(stu);
// 组合原型借用继承 结合继承了属性和方法
function Person(name,age){
this.name=name
this.age=age
}
Person.prototype.running=function (){
console.log('me is running call')
}
function Student(name,age,height){
Person.call(this,name,age)
this.height=height
}
Student.prototype=new Person('oyss',18)
//let p = new Person('oyss',18)
//Student.prototype=p
Student.prototype.studying=function(){
console.log('me is studying call');
}
let stu=new Student('yuyss',22,180)
console.log(stu);
stu.running()
stu.studying()
// 原型式方法继承 借助Object.create方法
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.running = function () {
console.log('me is running create')
}
function Student(name, age, height) {
Person.call(this, name, age)
this.height = height
}
// 1.使用Object.create创建一个新对象传入的参数就是这个新对象的显示原型
// Student.prototype=Object.create(Person.prototype)
// 2.创建一个函数这个函数创建一个新对象,父类的显示原型赋值给新对象的隐式原型,最好将这
个对象赋值到子类的显示原型上
// function object(obj) {
// let newObj = {}
// newObj.__proto__ = Person.prototype
// return obj.prototype = newObj
// }
// object(Student)
//function object(obj) {
// var newObj = {}
// Object.setPrototypeOf(newObj, obj)
// console.log(newObj);
// return newObj
//}
Student.prototype=object(Person.prototype)
Student.prototype.studying = function () {
console.log('me is studying create');
}
let stu = new Student('yuyss', 22, 180)
console.log(stu);
stu.running()
stu.studying()
//寄生式继承函数 寄生:寄生到传入的对象身上,缺点:如果函数中有自定义方法每次执行都会创建重复方法
function object(obj) {
function Func() { }
Func.prototype = obj
return new Func()
}
function createStudent(person) {
var newObj = object(person)
console.log(newObj);
newObj.studying = function () {
console.log(this.name + ' me is studying 寄生式继承函数');
}
return newObj
}
var person = {
name: 'yuyss',
age: 18
}
var stu = createStudent(person)
stu.studying()
// 寄生组合式继承
function object(obj) {
function Func() { }
Func.prototype = obj
return new Func()
}
function inheritPrototype(subType, superType) {
subType.prototype = object(superType.prototype)
Object.defineProperty(subType.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: subType
})
}
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.running = function () {
console.log("running~")
}
Person.prototype.eating = function () {
console.log("eating~")
}
function Student(name, age, friends, sno, score) {
Person.call(this, name, age, friends)
this.sno = sno
this.score = score
}
inheritPrototype(Student, Person)
Student.prototype.studying = function () {
console.log("studying~")
}
let stu = new Student('yuyss', 18, 180, 20, 100)
console.log(stu);
stu.running()
stu.studying()
多态
维基百科对多态的定义:多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口,或使用一个单一的符号来表示多个不同的类型。
个人的总结:不同的数据类型进行同一个操作,表现出不同的行为,就是多态的体现。
function sum(a,b){//上面定义来看一定存在多态
return a+b
}
sum(1,2)
sum(abc,cba)
数组
find和filter的区别
区别一:返回的内容不同
filter 返回是新数组
find 返回具体的内容
区别二:
find :匹配到第一个即返回
filter : 返回整体(没一个匹配到的都返回)
some和every的区别
some 如果有一项匹配则返回true
every 全部匹配才会返回true
Promise
Promise的实现
讲了一下Promise实现的几个关键技术点:
- 重点是需要实现
Promise.then方法- 维护一个
fullfilled的事件队列和一个rejected事件队列- 在
Promise.then方法里需要判断一下当前Promise的状态以及参数类型- 最后需要实现两个事件队列的自执行,用来处理链式调用的情况
- 在执行方法时使用
setTimeout模拟异步任务
Promise的九大方法
Promise.resolve
Promise.resolve方法的话,它的作用就是将传递给它的参数填充 Fulfilled 到 Promise 对象后并返回这个 Promise 对象
Promise.reject
Promise.reject方法的话:它的功能是调用该 Promise对象通过then指定的 onRejected函数,并讲错误(Error)对象传递给这个onRejected函数
Promise.then
不管你在回调函数 onFulfilled中会返回一个什么样的值,或者不返回值,该值都会由 Promis.resolve(return 的返回值) 进行响应的包装处理。因此,最终 then的结果都是返回一个新创建的 Promise对象。穿透拿到第一个.then返回的结果
Promise.catch
整个Promise链中,catch只属于异步触发它当中回调函数 执行的那个Promise,并不属于所有 Promise
Promise.finally
promise.finally方法的回调函数不接受任何参数,这意味着finally没有办法 知道,前面的Promise状态到底是fulfilled还是rejected 。这表明,finally方法里面的操作,应该是与Promise状态无关的,不依赖于 Promise的执行结果,无论失败成功都会执行
Promise.all
用于将多个 Promise 对象组合成一个新的 Promise 对象。它接收一个可迭代(数组)作为参数,返回一个新的 Promise 对象这个新的 Promise,等所有Promise 对象都成功,.then返回结果,如果任何一个输入的 Promise 被拒绝(rejected),那么整个 Promise.all() 就会立即被拒绝,.catch返回被拒绝的 Promise 的原因。返回的结果顺序不会改变,按照传入数据的顺序返回,即使更快返回也会按照传入的数据顺序返回
做一个操作可能得同时需要不同的接口返回的数据,这时我们就可以使用Promise.all
问题:使用Promise.all里面有一个报错了如果继续执行下去
//核心就是在调用.all的时候,map全部函数,就算失败了也给一个返回值,确保函数全部执行
Promise.all([fn,fn1...].map(p=>{
//.then返回需要的东西 .catch返回一些错误信息
return p.then(e=> {
return p
}).catch(err=> return '错误了')
})).then( res => {
//拿到需要的数据
}).catch(reason => {
console.log(reason)
})
和try catch一样,你把错误截获了,也就是你把throw new Error()替换成了你catch里的内容
Promise.allSettled
有多个不依赖于彼此成功完成的异步任务时,或者你总是想知道每个 promise 的结果时,使用 Promise.allSettled() 无论成功失败一起返回then数据不会走进catch
{ status: 'fulfilled', value:value }:resolve{ status: 'rejected', reason: reason }:reject
Promise.race
当你想要第一个异步任务完成时,但不关心它的最终状态(即它既可以成功也可以失败)时,它就非常有用。
好几个服务器的好几个接口都提供同样的服务,不知道哪个快,就可以使用Promise.race
Promise.any
传入数组只要其中有一个Promise成功执行,就会返回已经成功执行的Promise的结果和all不同我们只会得到一个兑现值,如果都失败了会走catch返回一个**AggregateError** 错误
AggregateError 对象代表了包装了多个错误对象的单个错误对象。当一个操作需要报告多个错误时,例如 Promise.any(),当传递给它的所有承诺都被拒绝时,就会抛出该错误。
Promise的九大方法(resolve、reject、then、catch、finally、all、allSettled、race、any)你都用过那些?_promise方法有哪些-CSDN博客
asyan await
async await 原理(generator+自动执行器)
generator 函数是协程在 ES6 的实现。协程简单来说就是多个线程互相协作,完成异步任务。
- 基本概念: async/await 是 JavaScript 中用于处理异步操作的语法糖,用同步的思维去解决异步的代码,使得代码更加清晰易读。
- async 函数: async 函数是用来声明一个异步函数的关键字,它会返回一个 Promise 对象。在 async 函数内部,可以使用 await 关键字来等待一个 Promise 对象的解析。
- await 关键字: await 关键字用于等待一个 Promise 对象的解析结果还可以接 async 函数。当遇到 await 关键字时,函数会暂停执行,直到 Promise 对象状态变为 resolved,然后将 resolved 的结果返回。
- 错误处理: 在 async 函数中,可以使用 try/catch 来捕获异步操作中的错误,并进行相应的处理。
- 与 Promise 的关系: async/await 是建立在 Promise 基础之上的一种语法糖,它可以更加方便地处理异步操作,减少了回调函数的嵌套。
Vue2
??虚拟DOM是什么?为什么要使用虚拟 DOM?
虚拟 DOM(Virtual DOM) 它将真实的 DOM 树抽象为一个轻量级的 JavaScript 对象树。当应用状态发生变化时,Vue 会先比较新的虚拟 DOM 树和旧的虚拟 DOM 树之间的差异,然后只更新这些差异部分对应的真实 DOM,而不是重新渲染整个页面。这种方式大大提高了渲染性能。
为什么要使用虚拟 DOM 呢?虚拟 DOM 技术能够帮助我们优化页面渲染过程,减少不必要的 DOM 操作,从而提升页面的性能,主要为解决渲染效率的问题
-
性能优化:通过比较虚拟 DOM 的差异,只对实际需要更新的部分进行操作,减少了不必要的 DOM 操作,提高了页面渲染效率。
-
批量更新:虚拟 DOM 可以将需要更新的操作批量处理,减少了频繁的 DOM 操作,减轻了浏览器的负担。
-
跨平台兼容:虚拟 DOM 抽象了真实 DOM 结构,使得可以在不同平台(例如浏览器、移动端等)上进行统一的页面渲染,提高了跨平台兼容性。
-
更好的开发体验:通过虚拟 DOM,开发者能够更方便地管理页面的状态和更新逻辑,使得代码更加清晰和易于维护。
???虚拟节点
data 为什么必须是函数
每个组件都是 Vue 的实例,组件共享 data 属性,当 data 的值是同一个引用类型的值时,改变其中一个会影响其他
组件中的 data 写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果。
v-show和v-if
控制手段:
v-show隐藏则是为该元素添加css--display:none,dom元素依旧还在。
v-if显示隐藏是将dom元素整个添加或删除
编译过程:
v-if切换有一个局部编译/卸载的过程切换过程中合适地销毁和重建内部的事件监听和子组件;
v-show只是简单的基于css切换
编译条件:
v-if是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;
ref
ref 的作用是被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。其特点是:
- 如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素
- 如果用在子组件上,引用就指向组件实例
所以常见的使用场景有:
- 基本用法,本页面获取 DOM 元素
- 获取子组件中的 data
- 调用子组件中的方法
this.不是this.data.
Vue 内部会对组件的 data 对象进行代理到 Vue 实例上 extend执行data合并 Vue 在组件实例化时会执行 extend 方法,这个方法会将组件的选项进行合并
数组响应式更新方法
- push() 尾部添加
- pop() 尾部删除
- shift() 头部删除
- unshift() 头部添加
- splice() 数组的添加 删除 替换
- sort() 数组的排序
- reverse()数组的翻转
利用索引直接设置一个数组项 X 使用this.$set解决
直接修改数组的长度 X 使用splice解决
filter()、concat()、 slice()
- 纯函数操作:
filter()、concat()、slice()这些数组方法都是纯函数,它们不会改变原始数组,而是返回一个新的数组。Vue 的响应式系统是基于侦测数据对象的属性的变化来进行视图更新的,而不是检测整个数组对象的变化。因此,只有在直接修改原始数组的情况下,Vue 才能够检测到数组的变化并触发响应式更新。 - 引用发生变化:使用这些数组方法会返回一个新数组,而不是直接修改原始数组,导致数据的引用发生变化。Vue 在检测数据变化时是基于引用的变化来进行的,如果引用发生了改变,Vue 会认为数据已经发生变化,并进行视图更新。
可以通过将新的数组赋值给 Vue 实例中的数据属性来触发更新
index作key有什么问题
key的作用主要就是为了高效的更新虚拟DOM,使用key值,它会基于key的变化重新排列元素顺序,并且会移除key不存在的元素。它也可以用于强制替换元素/组件而不是重复的使用它。
为何不推荐index作为key值
当以数组为下标的index作为key值时,其中一个元素(例如增删改查)发生了变化就有可能导致所有的元素的key值发生改变。
diff算法时比较同级之间的不同,以key来进行关联,当对数组进行下标的变换时,比如删除第一条数据,那么以后所有的index都会发生改变,那么key自然也跟着全部发生改变,所以index作为key值是不稳定的,而这种不稳定性有可能导致性能的浪费,导致diff无法关联起上一次一样的数据。因此,都不使用index作为key就不使用index。
用 index 拼接一个随机字符串做 key 可以解决v-for问题吗
使用 index 拼接一个随机字符串作为 key 可以在一定程度上解决在 v-for 循环中遇到的问题,但并不是一种推荐的做法。这种方式可能会导致以下问题:
- 性能问题: 使用随机字符串作为
key可能会增加页面重新渲染的开销,因为每次更新时都会生成新的key,导致不必要的重新渲染。 - 内存问题: 大量的随机字符串作为
key也可能导致内存占用过高,特别是在大数据量的列表中。 - 可维护性问题: 使用随机字符串作为
key可能会使代码难以维护和理解,降低代码可读性。
更推荐的做法是尽可能使用稳定性且唯一的 id 或数据中的某个唯一标识作为 key。如果您在使用 v-for 循环时遇到具体问题,可以考虑调整数据结构或者其他方式来解决,或者给数据中的每个元素添加一个稳定的唯一标识。
watch 和 computed 和 methods 区别是什么
watch 是监听数据,computed 是计算属性,methods 是方法。
- computed是计算属性,依赖其他属性值,并且computed的值有缓存。只有computed依赖的属性值发生变化,computed的值才会重新计算。 运用场景:一个数据属性在它所依赖的属性发生变化时,也要发生变化。对于任何复杂逻辑,你都应当使用计算属性。
- watch:没有缓存性,起到观察的作用,即监听数据的变化。watch为一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。 运用场景:侦听一个数的变化,当该数据变化,来处理其他与之相关数据的变化,即一个数据影响别的多个数据。
- methods:定义函数,它需要手动调用才能执行
vue自定义指令
全局自定义指令 局部自定义指令 /dəˈrektɪv/ 自动聚焦输入框
自定义指令中的钩子函数参数
- el: 绑定元素的真实Dom
- binding:是个对象,包含了指令的信息,如指令的值、修饰符、传递的参数等。
- vnode: Vue 实例中的虚拟 DOM 节点
自定义指令中的钩子函数
- bind:只调用一次,在指令绑定到元素上时触发。
- inserted:当被绑定的元素插入到DOM中触发
- update:当被绑定的元素所在的模板更新时调用,而不论绑定值是否变化
- componentUpdated:当被绑定的元素所在的模板完成一次更新周期时调用
- unbind:只调用一次,在指令与元素解绑时触发
??this.$nextTick
主要用于处理数据动态变化后,DOM还未及时更新的问题,使用nextTick回调中获取更新后的 DOM
keep-alive
**keep-alive**是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。比如列表页,去了详情页 回来,还是在原来的页面,保留其状态或避免重复渲染。
- 缓存组件状态:
keep-alive可以缓存动态组件的实例,包括组件的状态、数据和DOM结构。当组件被切换出去再切换回来时,能够保持之前的状态,避免重新渲染和重新加载数据。 - 节省性能消耗:
通过缓存组件的实例,keep-alive能够减少组件的销毁与重建,从而节省了性能消耗。特别是对于一些开销较大的组件,如列表页或复杂表单,使用keep-alive可以有效减少页面切换时的渲染时间和数据请求时间。 - 保持页面状态:
在一些需要保持页面状态的场景下,比如在表单填写过程中切换路由需要保留填写内容,可以使用keep-alive来保存组件状态,确保输入内容不丢失。 - 动态组件缓存:
keep-alive适用于动态组件,即需要根据某个条件动态加载的组件通过include和exclude属性可以指定哪些组件需要被缓存或排除缓存。
在keep-alive中,有两个生命周期钩子函数,分别是activated()和deactivated()。我们可以使用activated(),在组件激活的时候会被调用,每次进入页面的时候,都会执行,在这个里面进行数据的请求,用来替代mounted。所以,只要将mounted替换为activated就可以解决问题了也可以对数据进行重新渲染。
include(包含)和 exclude(除了)的属性值是组件的名称,也就是组件的name属性值,值为字符串或正则表达式或数组,max(最多缓存数量 控制一下被缓存的组件的个数,后缓存的就会把先缓存的给挤掉线了)
插槽区别 作用域插槽特点
父组件向子组件传递内容的方式。通过插槽,决定父组件的内容放在子组件位置,实现动态传递内容的效果
- 默认插槽:又名匿名插槽,当slot没有指定name属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。内容在那里展示子组件决定
- 具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。
- 作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件。
内容分发 父组件的数据和子组件的模板配合起来使用就是内容分发
vue内置组件
1.component组件
渲染一个“元组件”为动态组件,由“is”属性指定当前渲染的组件
2.transition组件
元素作为单个元素/组件的过渡效果,只会把过渡效果应用到其包裹的内容上,而不会额外渲染 DOM 元素,也不会出现在可被检查的组件层级中
-
transition-group组件
为多个元素/组件的过渡效果。渲染一个真实的 DOM 元素。默认渲染,可以通过tagattribute 配置哪个元素应该被渲染,每个<transition-group>的子节点必须有独立的 key,动画才能正常工作 -
keep-alive组件
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和相似, 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中 -
solt组件
元素作为组件模板之中的内容分发插槽。元素自身将被替换
6.Fragment组件
是一个特殊的内置组件,用于包裹多个子元素而不需要创建额外的 DOM 元素。Fragment 允许你在模板中拥有多个根元素,而不违反单根元素的规则。以帮助简化模板结构,避免额外的 DOM 层级,提高代码的可读性和维护性
6.Teleport组件 将指定 DOM 内容移动到指定的某个节点里面(可以理解为将组件挂载到指定节点上面)
使用场景: 弹框、播放器组件的定位,脱离父组件的限制,实现更灵活的布局和渲染方式。
7.Suspense 组件:
加载异步组件的时候,渲染一些其他内容 展示 loading 状态,让用户知道正在加载内容
异步数据加载:当异步请求数据时,可以使用 <Suspense> 在数据加载过程中展示 loading 状态或者 fallback 内容,以提升用户体验
场景: 加载异步组件的时候渲染 加载中
<Suspense> 接受两个插槽: #default 和 #fallback。它将在内存中渲染默认插槽的同时展示后备插槽内容。
<template>
<Suspense>
<template #default>
<AsyncComponent />//准备显示的内容
</template>
<template #fallback>
<div>Loading...</div>//还未加载出来显示的loading
</template>
</Suspense>
</template>
provide和inject
传递数据 为了避免重复声明props ,一般用于封装组件库。通过 provide 可以在祖先组件中提供数据,然后在任何后代组件中通过 inject 来接收这些数据
本身不是响应式的vue2函数返回一个对象可以通过写成对象形式,或者函数形式变成响应式的函数形式的话可以用计算属性去处理一下直接返回结果,Vue3可以通过ref和reactive
全局传递可以通过在**App.vue绑定provide**,所有的子组件就都能注入inject,从而达到全局传递
vue2中provide/inject的使用和响应式传值_vue2 provide-CSDN博客
mixin
mixin是对vue组件的一种扩展,将一些公用的常用数据或者方法,构建一个可被混入的数据结构,被不同的vue组件进行合并,就可以在不同的vue组件中使用相同的方法或者基础数据
作用: 更高效的实现组件内容的复用
代码重用: Mixin 提供了一种代码复用的方式,可以将一组组件选项抽取成 Mixin,并在多个组件中重复使用,避免代码冗余 以组件为优先。
逻辑抽象: Mixin 可以将组件中的一些通用逻辑抽象出来,在多个组件中共享这些逻辑,提高代码的可维护性和可复用性。
功能扩展: Mixin 可以用于扩展 Vue 组件的功能,比如添加生命周期钩子、方法、数据等,可以在组件中通过混入 Mixin 来增强组件功能。
Vue 中 Mixin 的原理主要是基于选项合并和混入的方式实现的,通过合并不同对象的选项,达到代码复用和功能扩展的效果。它使得组件之间可以共享公共逻辑、数据和方法,提高了代码的可维护性和复用性
$set Vue.set()
Vue.set()是将set函数绑定在Vue构造函数上,this.set是一个用于在响应式数据对象中添加新属性的方法。通常直接给响应式对象赋值新属性时,新属性可能不会触发视图更新,这时就可以使用$set` 来手动触发响应式更新
- 如果属性已经存在,则会更新该属性的值,并触发响应式更新。
- 如果属性不存在,则会添加新的属性,并确保该属性具有响应式特性。
1. 动态添加数组元素:当使用Vue的响应式数组时,直接使用索引添加新元素时,
Vue无法检测到新元素的添加。这时可以使用`$set`来动态添加数组元素,并触发响应式更新。
Vue.$set(array, index, value);
2. 动态添加对象属性:当使用Vue的响应式对象时,如果需要在运行时添加新属性,
可以使用`$set`来添加新属性,并触发响应式更新。
Vue.$set(object, propertyName, value);
vue父子组件生命周期执行顺序
1.挂载
父组件beforeCreate => 父组件created => 父组件beforeMount => 子组件beforeCreate => 子组件created => 子组件beforeMount => 子组件mounted => 父组件mounted
2.更新阶段
父组件beforeUpdate => 子组件beforeUpdate => 子组件updated => 父组件updated
3.销毁阶段
父组件beforeDestroy => 子组件beforeDestroy => 子组件destroyed => 父组件destroyed
父组件操作子组件的方法
ref
<el-button type="primary" @click="parentOpenDialog">打开弹窗</el-button>
//父组件使用子组件,写上ref=""
<dialog ref="dialog"></dialog>
//父组件methods
parentOpenDialog() {
this.$nextTick(() => {
this.$refs["dialog"].openDialog();
});
},
//子组件
openDialog() {
this.dialogVisible = true;
}
通过组件的on方法
<Button @click="handleClick">点击调用子组件方法</Button>
<Child ref="child"/>
//父组件
methods: {
handleClick() {
this.$refs.child.$emit("childmethod") //子组件$on中的名字
},
}
//子组件
mounted() {
this.$nextTick(function() {
this.$on('childmethods', function() {
console.log('我是子组件方法');
});
});
},
监听vue组件报错
vue.config.errorHandler
接收三个参数:错误、触发错误的组件实例、指定错误源类型的信息字符串。
app.config.errorHandler = (err, instance, info) => {
// handle error, e.g. report to a service
}
它可以从以下来源捕获错误:
- 组件渲染
- 事件处理程序
- 生命周期钩子
- setup()函数
- Watchers
- 自定义指令钩子
- 过渡钩子
vue容易踩的坑
- 在
data中定义的数据如果需要响应式更新,必须在初始化的时候就存在。$set vue.set - 避免在
computed中修改响应式数据。computed属性应该是只读的,不应该用来修改数据。如果需要修改响应式数据,请使用methods方法。 watch监听数组或对象的变化时需要使用deep选项。 如果需要监听数组或对象内部属性的变化,需要在watch选项里设置deep: true。- 在 created 操作 dom 的时候,是报错的,获取不到 dom,这个时候实例 Vue实例没有挂载Vue.nextTick 回调函数进行获取
- 组件之间通信时,避免直接修改 props 的值。 父组件通过
props向子组件传递数据时,子组件不应该直接修改props的值,而应该通过$emit触发事件,父组件监听事件来修改数据。 - 在使用 Vuex 进行状态管理时,注意避免直接修改 state。 应该通过 mutations 来修改 state,保证状态变更的可追溯性。
VueX
Vuex 由哪几部分组成
- State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
- Getter:允许组件从
Store中获取数据,mapGetters辅助函数仅仅是将store中的getter映射到局部计算属性。 - Mutation:是唯一更改
store中状态的方法,且必须是同步函数。 - Action:用于提交
mutation,而不是直接变更状态,可以包含任意异步操作。 - Module:允许将单一的
Store拆分为多个store且同时保存在单一的状态树中
Vuex中mutation为什么是同步的action为什么是异步
Vuex 的核心理念是单向数据流,通过 mutation 来修改状态,确保所有状态的变更都可以被追踪。由于 mutation 是同步的,可以更精准地追踪状态的变化。Action 是异步因为在实际开发中,很多情况下需要进行异步操作,比如发起网络请求、定时器等。将异步操作放到 action 中,可以更好地管理异步任务的流程和状态
如果我们在mutation中写了异步,commit在触发mutation事件时,异步的回调函数不知道是什么时候执行的,所以在devtools中难以追踪变化,actions 可以做异步操作,但是并不是直接修改数据,而是通过提交mutations 里面的方法
vuex存储和本地存储区别
Vuex 是 Vue.js 应用的状态管理模式,而本地存储(Local Storage)是浏览器提供的一种机制,用于在用户的浏览器中长期保存数据,即使页面刷新或浏览器关闭后数据也不会丢失。
- 存储位置不同:Vuex 存储在 Vue 应用的内存中,而本地存储存储在用户浏览器本地中。
- 数据持久性:本地存储会将数据永久保存在浏览器中,除非主动删除,否则数据不会消失;Vuex 的状态只在当前页面会话中保存,页面刷新或关闭后状态会丢失。
- 访问方式不同:本地存储需要使用浏览器提供的 API(如 localStorage 或 sessionStorage),Vuex 状态通过组件的计算属性或方法访问。
- 应用范围:Vuex 用于 Vue 应用内组件之间的状态管理,本地存储可以跨多个网页或多个 Vue 应用共享数据
VueX持久化
localStorage
vuex-persistedstate 坡c丝听
注意Vuex本身做不到,需要通过vuex-persistedstate插件去做持久化存储
vue-router
路由守卫
路由钩子函数有三种:
全局钩子: beforeEach(全局前置守卫)、 afterEach(全局后置钩子)
路由独享的守卫(单个路由里面的钩子): beforeEnter
组件路由:beforeRouteEnter、 beforeRouteUpdate、 beforeRouteLeave
1、to:即将要进入的目标路由对象;
2、from:当前导航即将要离开的路由对象;
3、next :调用该方法后,才能进入下一个钩子函数(afterEach)。
vue底层/原理/理论
vue2和vue3响应式
在 Vue 2 中,Vue 通过 Object.defineProperty() 来实现响应式系统。当一个对象被传入 Vue 实例进行响应式处理时,Vue 会遍历这个对象的每一个属性,并使用 Object.defineProperty() 把这个属性转换成 getter 和 setter。当这个属性被读取时,getter 会被触发,这个属性就会被添加到依赖中;当这个属性被修改时,setter 会被触发,这个属性的依赖就会被通知,并执行相应的更新操作。这样,当数据被修改时,所有依赖这个数据的地方都会自动更新。
但是,Vue 2 的响应式系统存在一些问题。首先,它只能监听对象的属性,而不能监听新增的属性和删除的属性;其次,它无法监听数组的变化,只能监听数组的索引变化,即当使用数组的 push、pop、shift、unshift、splice 等方法时才能触发更新。
在 Vue 3 中,Vue 引入了 Proxy 对象来实现响应式系统。当一个对象被传入 Vue 实例进行响应式处理时,Vue 会使用 Proxy 对象对这个对象进行代理,这样就可以监听新增的属性和删除的属性,同时也可以监听数组的变化。当一个属性被读取或修改时,Proxy 对象的 get 和 set 方法会被触发,这样就可以实现响应式更新。
Vue 3 的响应式系统还有一个优点,就是它支持了多个根节点,也就是 Fragment。这样可以在不需要添加额外的 DOM 节点的情况下,返回多个元素。
总体来说,Vue 3 的响应式系统更加灵活和高效,能够更好地应对复杂的应用场景
Vue 数据双向绑定的原理是什么
Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调,实现数据驱动视图的自动更新
vue实例挂载的过程
- new Vue的时候调用会调用_init方法
- 定义 get 、watch 等方法
- 定义 off、off等事件
- 定义 _update、destroy生命周期
- 调用$mount进行页面的挂载
- 挂载的时候主要是通过mountComponent方法
- 定义updateComponent更新函数
- 执行render生成虚拟DOM
- _update将虚拟DOM生成真实DOM结构,并且渲染到页面中
vue中如何进行依赖收集
在getter中收集依赖,在setter中触发依赖。先收集依赖,即把用到该数据的地方收集起来,然后等属性发生变化时,把之前收集好的依赖循环触发一遍组件更新。
Vue的异步更新机制是通过使用事件循环(Event Loop)和任务队列(Task Queue)来实现的。当触发数据变化时,Vue会对依赖于这些数据的组件进行重新渲染。具体的异步更新机制分为以下几个步骤:
- 数据变化:当响应式数据发生变化时(例如通过 Vue.set 或者数组的变异方法),Vue会追踪这些变化。
- 记录依赖:Vue会记录所有依赖于这些数据的组件。
- 触发更新:Vue会将需要更新的组件放入一个队列中。
- 事件循环:在下一个事件循环周期开始前,Vue会检查队列中的组件。
- 组件更新:Vue会根据依赖的更新顺序依次通知组件重新渲染。
vue本身如何侦测一个data的变化
首先会遍历data,使用Object.defineproperty拦截所有的属性,当用户操作视图会触发set拦截器,set首先会改变当前的数据,然后通知watch,让watch通知视图进行更新,视图重绘。再次从get中获取相应的数据
在vue3中,它重写响应式原理实现了深度响应式,首先会使用proxy进行代理,拦截data中属性的所有操作,包括属性的读写,添加删除等等,其次会使用reflect进行反射,动态对被代理的对象的响应属性进行特定的操作,代理对象和反射对象必须相互配合才能实现响应式
Vue3
Object.defineProperty 和 Proxy 的区别
- Proxy 可以直接监听对象而非属性;
- Proxy 可以直接监听数组的变化;
- Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等
- 等是 Object.defineProperty 不具备的;
- Proxy 返 回 的 是 一 个 新 对 象 , 我 们 可 以 只 操 作 新 的 对 象 达 到 目 的 , 而Object.defineProperty 只能遍历对象属性直接修改;
Object.defineProperty 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题
- object.defineproperty 无法监控到数组下标的变化,导致通过数组下标添加元素,无法实时响应
- object.defineProperty 只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。
- 无法检测到对象属性的新增或删除
如何看待Composition API 和 Options API
Composition API和Options API是Vue.js中的两种组件编写方式。
Options API是Vue.js早期版本中使用的编写方式,通过定义一个options对象进行组件的配置,包括props、data、methods、computed、watch等选项。这种方式的优点在于结构清晰、易于理解,在小型项目中比较实用。
Composition API是Vue.js 3.x版本中新引入的一种组件编写方式,它以函数的形式组织我们的代码,允许我们将相关部分组合起来,提高了代码的可维护性和重用性。Composition API还提供了模块化、类型推断等功能,可以更好地实现面向对象编程的思想。
Composition API 更好的代码组织,更好的逻辑服用;可维护性,更好的类型推导,可拓展性更好;
两种API各有优缺点,使用哪种API取决于具体的项目需求。对于小型项目,Options API更为简单方便;对于大型项目,Composition API可以更好地组织代码。
总之,Vue.js的Composition API和Options API是为了满足不同开发者的需求而存在的,我们应该根据具体的场景选择使用哪种API,以达到更好的开发效果和代码质量。
watch和watchEffect区别
watch 和 watchEffect 都是用来监视数据变化并执行相应操作的函数
watch
watch是一个选项或方法,用于监视特定的数据变化,并在数据变化时执行回调函数。- 可以监视一个或多个响应式数据的变化,当监视的数据发生变化时,执行回调函数。
- 可以通过配置选项来控制何时开始监视和停止监视数据变化。
watch是基于依赖进行响应,只有在数据变化时才会执行回调函数。
watchEffect
watchEffect是一个函数,用于立即执行传入的函数并响应式追踪其依赖,并在依赖变化时重新运行该函数。- 监视的是函数内部访问的响应式数据,只要被访问的数据发生变化,函数就会被重新执行。
watchEffect是立即执行的,无需等待数据的变化,它会立即执行一次并在数据变化时重新执行。
区别总结
-
watch是通过配置选项或方法来监视特定数据的变化,而watchEffect是立即执行并追踪函数内部的数据依赖。 -
watch是基于依赖进行响应,只有监视的属性变化时才会执行回调,而watchEffect是基于函数内部的响应式数据访问。 -
在复杂逻辑处理时,优先考虑
watch,而在简单响应式依赖追踪时,可以使用watchEffect。
微信小程序
this.setDate
this.setData() 方法是用来更新微信小程序页面数据的方法。它基于小程序框架底层的数据劫持和响应式机制实现。调用 this.setData() 方法时,传入需要更新的数据字段和对应的新值,框架会比对新旧数据,找出变化的部分并及时更新到页面上,实现页面数据的实时渲染。
this.setData() 方法是异步调用的。当调用 this.setData() 方法时,并不会立即执行更新操作,而是将数据变更请求添加到一个队列中。当当前同步任务执行完毕后,微信小程序框架会按照一定的策略去执行这个队列中的更新操作,从而更新页面上的数据
生命周期
- onLoad 监听页面加载=>发送请求 pages页面上的属性初始化完毕 options获取参数
- onShow 页面首次显示
- onReady 页面加载完毕=>可以做dom操作 ruai迪
- onHide 页面隐藏 害d
- onUnload 页面摧毁 昂漏的
组件生命周期
- attached
在组件完全初始化完毕、进入页面节点树后被触发呃塔去T - detached
在组件离开页面节点树后被触发滴塔去T - show组件页面展示执行
- hide组件所在的页面隐藏执行
- resize组件所在的页面尺寸变化执行
微信小程序组件之间传值
父组件给子组件传递数据自定义属性,子组件properties接收
子组件给父组件传值this.trggerEvent('事件名称',传递的数据) bind事件名() 接收一个e参数
使用本地存储传递数据
使用路由传递数据 字符串拼接 模板字符串
使用全局变量传递数据 globalData 将数据存储为全局变量,在需要使用的页面通过 getApp().globalData 获取。
路由跳转区别
- wx.navigateTo( ):保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面
- wx.redirectTo( ):关闭当前页面,跳转到应用内的某个页面。但是不能跳转 tabbar 页面 瑞得莱克T
- wx.switchTab( ):跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面 它波
- wx.navigateBack( )关闭当前页面,返回上一页面或多级页面。可通过
- getCurrentPages() 获取当前的页面栈,决定需要返回几层 卡瑞T
- wx.reLaunch( ):关闭所有页面,打开到应用内的某个页面 瑞luang去
微信支付
wx.requestPayment({
timeStamp: '',时间戳,从 1970 年 1 月 1 日 00:00:00 至今的秒数,即当前的时间
nonceStr: '',随机字符串,长度为32个字符以下
package: '',统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=***
signType: 'MD5',签名算法,应与后台下单时的值一致
paySign: '',签名,具体见微信支付文档
success (res) { },接口调用成功的回调函数
fail (res) { }接口调用失败的回调函数
})
微信小程序存储
wx.setStorageSync('name', 'mxf') 存 name,可以直接存对象、布尔类型的值
wx.getStorageSync('name') 取 name
wx.removeStorageSync('name') 删name
clearstorage 删除全部
微信小程序的底层架构原理
微信小程序的底层架构主要由两部分组成:小程序开发框架和小程序解析器。小程序开发框架包括了视图层框架、逻辑层框架和底层 API 的封装,而小程序解析器则主要负责解析 WXML、WXSS 和 JS 代码,将其渲染成页面。
微信小程序的框架包含两部分 View 视图层、App Service逻辑层。View层用来渲染页面结构,App Service层用来逻辑处理、数据请求、接口调用,它们在两个线程(Webview)里运行。
视图层和逻辑层通过系统层的JSBridage进行通信,逻辑层把数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层进行业务处理。
下面是一些微信小程序底层架构的关键原理:
- 双线程模型: 微信小程序采用双线程模型,即视图线程和逻辑线程分离,分别对应视图层和逻辑层。视图层负责页面渲染和用户交互,逻辑层负责页面逻辑处理和数据请求。两者通过消息队列进行通信。 那小程序又是如何做到双线程的呢,根本原因就是微信小程序禁止js操作DOM。
- 虚拟 DOM: 微信小程序使用虚拟 DOM 技术进行页面更新,通过比对虚拟 DOM 的差异来最小化页面渲染的成本。这有助于提高页面渲染性能。
- 组件化开发: 微信小程序采用组件化开发思想,将页面拆分成多个组件,提高了代码的复用性和可维护性。每个组件有自己的 WXML 模板、WXSS 样式和 JS 逻辑文件。
git
常用命令
**基本操作**
git init 初始化仓库,默认为 master 分支
git add . 提交全部文件修改到缓存区
git add <具体某个文件路径+全名> 提交某些文件到缓存区
git diff 查看当前代码 add后,会 add 哪些内容
git diff --staged查看现在 commit 提交后,会提交哪些内容
git status 查看当前分支状态
git pull <远程仓库名> <远程分支名> 拉取远程仓库的分支与本地当前分支合并
git pull <远程仓库名> <远程分支名>:<本地分支名> 拉取远程仓库的分支与本地某个分支合并
git commit -m "<注释>" 提交代码到本地仓库,并写提交注释
git commit -v 提交时显示所有diff信息
git commit --amend [file1] [file2] 重做上一次commit,并包括指定文件的新变化
**提交规则**
feat: 新特性,添加功能
fix: 修改 bug
refactor: 代码重构
docs: 文档修改
style: 代码格式修改, 注意不是 css 修改
test: 测试用例修改
chore: 其他修改, 比如构建流程, 依赖管理
**分支操作**
git branch 查看本地所有分支
git branch -r 查看远程所有分支
git branch -a 查看本地和远程所有分支
git merge <分支名> 合并分支
git merge --abort 合并分支出现冲突时,取消合并,一切回到合并前的状态
git branch <新分支名> 基于当前分支,新建一个分支
git checkout --orphan <新分支名> 新建一个空分支(会保留之前分支的所有文件)
git branch -D <分支名> 删除本地某个分支
git push <远程库名> :<分支名> 删除远程某个分支
git branch <新分支名称> <提交ID> 从提交历史恢复某个删掉的某个分支
git branch -m <原分支名> <新分支名> 分支更名
git checkout <分支名> 切换到本地某个分支
git checkout <远程库名>/<分支名> 切换到线上某个分支
git checkout -b <新分支名> 把基于当前分支新建分支,并切换为这个分支
**远程操作**
git fetch [remote] 下载远程仓库的所有变动
git remote -v 显示所有远程仓库
git pull [remote] [branch] 拉取远程仓库的分支与本地当前分支合并
git fetch 获取线上最新版信息记录,不合并
git push [remote] [branch] 上传本地指定分支到远程仓库
git push [remote] --force 强行推送当前分支到远程仓库,即使有冲突
git push [remote] --all 推送所有分支到远程仓库
**撤销操作**
git checkout [file] 恢复暂存区的指定文件到工作区
git checkout [commit] [file] 恢复某个commit的指定文件到暂存区和工作区
git checkout . 恢复暂存区的所有文件到工作区
git reset [commit] 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
git reset --hard 重置暂存区与工作区,与上一次commit保持一致
git reset [file] 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
git revert [commit] 后者的所有变化都将被前者抵消,并且应用到当前分支
reset:真实硬性回滚,目标版本后面的提交记录全部丢失了
revert:同样回滚,这个回滚操作相当于一个提价,目标版本后面的提交记录也全部都有
**存储操作**
git stash 暂时将未提交的变化移除
git stash pop 取出储藏中最后存入的工作状态进行恢复,会删除储藏
git stash list 查看所有储藏中的工作
git stash apply <储藏的名称> 取出储藏中对应的工作状态进行恢复,不会删除储藏
git stash clear 清空所有储藏中的工作
git stash drop <储藏的名称> 删除对应的某个储藏
Echarts
Echarts常用配置项
-
grid 选项:直角坐标系内绘图区域
-
yAxis 选项 :直角坐标系 grid 中的y轴
-
xAxis 选项:直角坐标系 grid 中的x轴
-
title :图表的标题
-
legend:图例,展现了不同系列的标记、颜色和名字
-
tooltip:提示框
-
toolbox:工具栏 提供操作图表的工具 导出图片 数据视图 动态类型切换 数据区域缩放 重置
-
series:系列,配置系列图表的类型和图形信息数据
-
visualMap:视觉映射,可以将数据值映射到图形的形状、大小、颜色等
-
geo:地理坐标系组件。用于地图的绘制,支持在地理坐标系上绘制散点图,线集。
TS
type和interface
1.语法:
interface:使用interface关键字来定义接口。
type:使用type关键字来定义类型别名。
2.对象类型的描述
interface:可以描述对象的结构,包括属性、方法和索引签名等。接口可以被实(implements)
type:可以描述对象的结构,包括属性、方法和索引签名等。类型别名不能被实现。
3.类型合并
interface:可以多次声明同一个接口,并且会自动合并接口中相同名称的成员。
type:不支持声明合并。
4.对于其他类型的描述
interface:主要用于描述对象类型,虽然可以描述函数类型,但不能直接描述基本类型、联合类型等。
type:可以描述对象类型、基本类型、联合类型、交叉类型、元组等。
5.继承和实现
interface:可以通过继承其他接口来扩展自身,使用extends关键字。
type:不支持直接的继承,但可以使用交叉类型(Intersection Types)来合并多个类型。
总的来说,interface和type在某些方面有相似的功能,但它们也有一些区别。一般来说,当你要描述对象的结构和行为时,优先选择使用interface,当你需要使用联合类型、交叉类型等高级类型时,或者需要给现有类型起一个别名时,可以使用type来定义类型别名。
uni-app
React
性能优化
???前端长列表优化
网络/浏览器
SEO优化
SEO是搜索引擎优化的英文缩写
如果构建大型网站,如商城类=》SSR服务器渲染
如果正常公司官网,播客网站等=》预渲染/静态化/Phantomjs都比较方便
如果是已用SPA开发完成的项目进行SEO优化,而且部署环境支持node服务器,使用Phantomjs 芬特木
http状态码
1xx 信息性状态码:
100 Continue:服务器已经收到了请求的首部,并且客户端应该继续发送请求的主体部分。
101 Switching Protocols:服务器已经理解了客户端的请求,并将通过协商的方式更改协议。
2xx 成功状态码:
200 OK:请求已成功。
201 Created:请求已经被实现,而且有一个新的资源已经依据请求的需要而建立。
3xx 重定向状态码:
300 Multiple Choices:被请求的资源有一系列可供选择的回馈信息,用户或浏览器能够自行选择一个首选的。
301 Moved Permanently:请求的资源已被分配了新的 URI。
4xx 客户端错误状态码:
400 Bad Request:服务器未能理解请求。
401 Unauthorized:请求要求身份验证。
404 Not Found:服务器找不到请求的资源。
5xx 服务器错误状态码:
500 Internal Server Error:服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。
503 Service Unavailable:服务器当前无法处理请求。
说说地址栏输入 URL 敲下回车后发生了什么? | 前端面试题整理
地址栏输入 URL 敲下回车后发生了什么
-
对www.baidu.com这个网址进行DNS域名解析,得到对应的IP地址
-
根据这个 IP,找到对应的服务器,发起 TCP 的三次握手
-
建立 TCP 连接后, 发起 HTTP 请求
-
服务器响应 HTTP 请求,浏览器得到 html 代码
-
浏览器解析 html 代码,并请求 html 代码中的资源(如 js、css、图片等)(先得到 html 代码,才能去找这些资源)
-
服务器响应对应的资源
-
响应数据完毕, 四次挥手,关闭 TCP 连接
-
浏览器对页面进行渲染呈现给用户
页面渲染
当浏览器接收到服务器响应的资源后,首先会对资源进行解析:
查看响应头的信息,根据不同的指示做对应处理,比如重定向,存储 cookie,解压 gzip,缓存资源等等
查看响应头的 Content-Type 的值,根据不同的资源类型采用不同的解析方式
- 解析 HTML,构建 DOM 树
- 解析 CSS ,生成 CSS 规则树
- 合并 DOM 树和 CSS 规则,生成 render 树
- 布局 render 树( Layout / reflow ),负责各元素尺寸、位置的计算
- 绘制 render 树( paint ),绘制页面像素信息
- 浏览器会将各层的信息发送给 GPU,GPU 会将各层合成( composite ),显示在屏幕上
Token
Token是一种用于身份验证和授权的令牌。在用户登录成功后,服务器会生成一个Token,并将其颁发给客户端。客户端在后续的请求中使用Token作为凭证,向服务器证明其身份和权限。Token通常包含一些加密的信息,如用户ID、角色、访问权限等。
同源策略
http:// www. aaa.com:8080/index/vue.js
协议 子域名 主域名 端口号 资源
主要指的就是协议+域名+端口号三者一致,若其中一个不一样则不是同源,会产生跨域
三个允许跨域加载资源的标签:img link script
跨域是可以发送请求,后端也会正常返回结果,只不过这个结果被浏览器拦截了
这些可以解决跨域问题 JSONP CORS websocket 反向代理
浏览器的缓存策略
强缓存(本地缓存) 协商协议(弱缓存)
强缓:不发起请求,直接使用缓存里的内容,浏览器把js,css,image等存到内存中,下次用户访问直接从内存中取,提高性能。
协缓:需要像后台发送请求,通过判断来决定是否使用协商缓存,如果请求内容没有变化,则返回304,浏览器就用缓存里的内容
get和post区别
-
get一般是获取数据,post一般是提交数据
-
get参数会放在url上,所以安全性比较差,post是放在body中
-
get请求时会被缓存,post请求不会被缓存
-
get请求刷新服务器或退回是没有影响的,post请求退回时会重新提交数据
-
get请求会被保存在浏览器历史记录中,post不会
-
get请求只能进行url编码,post可以表单 在提交表单数据时,数据可以使用多种编码方式,包括 URL 编码、Multipart 编码等
post请求发送2次
- 当你的
POST请求包含一些特殊的头部信息(如Content-Type: application/json)时,浏览器会先发送一个OPTIONS请求作为 Preflight 请求,用于检查服务器是否支持该请求。 - 只有在 Preflight 请求得到肯定响应后,浏览器才会发送实际的
POST请求。这是为了确保跨域请求的安全性。
CDN的好处
- 隐藏服务器源ip。
- 连接所响应速度最快的节点提高访问速度。
- CDN节点缓存减少网站服务器访问压力
前端如何实现即时通讯
- 短轮询。即客户端每隔一段时间就向服务器发送消息,询问有没有新的数据
- 长轮询,发起一次请求询问服务器,服务器可以将该请求挂起,等到有新消息时再进行响应。响应后,客户端立即又发起一次请求,重复整个流程。
- websocket,握手完毕后会建立持久性的连接通道,随后服务器可以在任何时候推送新消息给客户端
WebSocket
websocket 协议 HTML5 带来的新协议,即时通信 不刷新获取数据,WebSocket是一种在单个 TCP 连接上进行全双工通信的协议,它允许在客户端和服务器之间进行实时、双向的数据传输。在前端开发中,WebSocket可以用于实现实时通信,例如聊天应用、在线游戏等,在WebSocket连接时使用wss协议(WebSocket over TLS)来加密数据传输
const socket = new WebSocket('ws://example.com/socketServer');//创建的办法
WebSocket实例提供了一些事件,可以通过事件监听来处理连接状态、接收消息等操作。常见的事件包括:
open:连接建立时触发。message:接收到服务器发送的消息时触发。close:连接关闭时触发。error:连接出现错误时触发。``
socket.onopen = function(event) {
console.log('WebSocket连接已建立');
};
socket.onmessage = function(event) {
console.log('接收到消息:', event.data);
};
socket.onclose = function(event) {
console.log('WebSocket连接已关闭');
};
socket.onerror = function(event) {
console.error('WebSocket连接出错');
};
使用WebSocket实例的send()方法向服务器发送消息,服务器接收到消息后可以进行相应的处理。服务器同样可以通过发送消息到客户端来实现实时通信。``
// 发送消息
socket.send('Hello, WebSocket!');
// 接收消息会触发onmessage事件
socket.onmessage = function(event) {
console.log('接收到消息:', event.data);
};
在不需要使用WebSocket时,可以使用方法close()来显式关闭连接。
socket.close()
css加载
css不会阻塞dom树的解析 css会阻塞dom树的渲染 css加载会阻塞后面js的执行
- CSS不会阻塞DOM树的解析。在浏览器解析HTML文档时,如果遇到外部CSS文件,浏览器会并行下载CSS资源,同时继续解析HTML文档中的DOM结构。这意味着即使CSS文件还未完全加载和解析完成,DOM树仍然会继续解析构建,而不会被CSS加载所阻塞。
- 当浏览器解析HTML文档时,会逐行加载解析,而当遇到外部CSS文件时,浏览器会停止解析HTML文档,去下载并解析CSS文件,然后再继续解析HTML文档。这个过程会阻塞页面的渲染,可能会导致页面的延迟加载。
- JavaScript执行阻塞:在某些情况下,CSS加载也可能会阻塞JavaScript的执行,尤其是在加载过程中可能会抢占网络资源,导致后续JavaScript脚本的加载和执行受到影响。
情景
[] == ![]结果是什么?
== 中,左右两边都需要转换为数字然后进行比较。 []转换为数字为0。 ![] 首先是转换为布尔值,由于[]作为一个引用类型转换为布尔值为true, 因此![]为false,进而在转换成数字,变为0。 0 == 0 , 结果为true
文案少的时候是居中的,文案多的时候是换行左对齐的
/*当文字为一行是,则P的宽度小于div的宽度,p标签居中显示在盒子内,文字也就居中了 ;当大于一行时,P的宽度和div的宽度是一致的 ,文字就居左对齐了*/
.content {
width: 200px;
border: 1px solid #ee2415;
text-align: center;
padding: 2px 5px
}
/*display: inline-block使P的宽度根据文字的宽度伸缩 */
.content p {
text-align: left;
display: inline-block
}
无感登录
- 在相应其中拦截,判断token返回过期后,调用刷新token的接口
- 后端返回过期时间,前端判断token的过期时间,去调用刷新token的接口
- 写定时器,定时刷新token接口
方法1流程
-
登录成功后保存token 和 refresh_token
-
在响应拦截器中对401状态码引入刷新token的api方法调用
-
替换保存本地新的token
-
把错误对象里token替换
-
再次发送未完成的请求
-
如果refresh_token过期了,判断是否过期,过期了就清楚所有token重新登录
发送多次请求只需要最后一次结果
fetch:使用**AbortController** 是一个原生的API,可以用来取消一个或多个Web请求。
AbortController 实例用于取消请求:通过将 AbortController 实例的 signal 属性传递给 fetch 方法的 signal 参数,可以在调用 AbortController 的 abort 方法时取消对应的 fetch 请求。
let currentController = new AbortController();
signal属性主要用于与当前控制器相关联的信号对象,这个信号对象可以用来中止或取消 fetch 请求。
const { signal } = currentController;
fetch(url, { signal }).then((res)=>{})
在 Fetch API 中,并没有内置的 abort() 方法用于取消请求,但我们可以利用 new AbortController 来实现取消 Fetch 请求的功能 currentController.abort()
currentController.abort();
在 fetch 请求的 .catch() 中,可以对取消的请求进行特殊处理,一般会抛出一个名为 'AbortError' 的异常,用于识别请求是否被取消
if (error.name === ‘AbortError’) {
console.log(‘请求被取消:’, url);
}
axios:使用 canceltoken 取消请求
CancelToken.source() 是 axios library 中用来创建取消令牌(CancelToken)的静态方法。
- 创建取消令牌:调用
CancelToken.source()方法会返回一个包含token和cancel两个属性的对象。token是一个取消令牌实例,而cancel是一个函数,调用该函数将取消与该取消令牌相关联的请求。 - 取消请求:将
token属性传递给 axios 请求的cancelToken配置项中,当需要取消请求时,调用cancel函数即可取消该请求。
下面我们首先使用 CancelToken.source() 创建了一个取消令牌,并从中获取了取消函数 cancel 和取消令牌实例 token。然后将 token 传递给 axios 请求的配置中。最后,在某个条件满足时,调用 cancel() 方法来取消请求
可以使用 CancelToken.source 工厂方法创建 cancel token,像这样:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
还可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token:
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
});
// cancel the request
cancel();
注意: 可以使用同一个 cancel token 取消多个请求
把一个 div 做成宽高比 4:3 的矩形,距离屏幕两边有 50px 的距离
padding 的尺寸可以根据元素的宽度进行计算,所以只需要保证 padding-top 和 width 的比例是 4:3 就行了。
width:100%;
margin:0 50px;
padding-top:75%
provide和inject实现页面刷新
1 . 用vue-router重新路由到当前页面,页面是不进行刷新的
2 . 采用window.reload(),或者router.go(0)刷新时,整个浏览器进行了重新加载,闪烁,体验不好
我们只在控制路由的组件中写一个函数 使用 v-if 控制 router-view 的显示隐藏 v-if重新加载组件走生命周期, 然后把这个函数传递给后代,然后在后代组件中调用这个方法即可刷新路由啦。
<router-view v-if="isShowRouter"/>
export default {
name: 'App',
provide(){
return{
reload:this.reload
}
},
data(){
return{
isShowRouter:true,
}
},
methods:{
reload(){
this.isShowRouter = false;
this.$nextTick(()=>{
this.isShowRouter = true;
})
}
}
}
//后代组件
export default {
inject:['reload'],
}
??上拉加载,下拉刷新
上拉加载及下拉刷新都依赖于用户交互 什么时机下触发交互动作 上拉加载的本质是页面触底,或者快要触底时的动作 下拉刷新的本质是页面本身置于顶部时,用户下拉时需要触发的动作
localStorage 或 sessionStorage变响应式
在 Vue 中,如果直接使用本地存储 localStorage 或 sessionStorage 存储数据,这些数据并不是响应式的,也就是说当本地存储的数据发生变化时,视图不会自动更新。为了将本地存储的数据变成响应式,可以结合 Vue 提供的响应式数据和生命周期钩子函数来实现。以下是一种可能的做法:
- 在 Vue 组件中,可以在
data钩子中声明一个用于存储本地数据的变量,并在created钩子中读取本地存储的数据将其赋值给该变量。 - 使用 Vue 提供的计算属性来实现对本地存储数据的访问,并在需要时更新视图。
- 在本地存储数据变化时,手动更新 Vue 组件中的响应式数据,以触发视图更新。
export default {
data() {
return {
localStorageData: ''
};
},
computed: {
localStorageValue() {
return localStorage.getItem('myData');
}
},
created() {
this.localStorageData = localStorage.getItem('myData');
},
methods: {
handleLocalStorageChange(value) {
this.localStorageData = value;
}
},
watch: {
localStorageValue(newVal) {
localStorage.setItem('myData', newVal);
}
}
};
首先在 data 钩子中声明了一个变量 localStorageData 用来存储本地数据。在 created 钩子中从本地存储读取数据并赋值给这个变量。利用计算属性 localStorageValue 来实现对本地存储数据的访问。在监听计算属性中的 localStorageValue的变化时,通过 watch 实现数据变化时更新本地存储的操作
项目两小时未动未发送请求,退出登录
设置 请求的token在请求头中 请求后最新的token在返回头中
java后台服务 需要拦截器 每次请求查看前端heards里边有没有你设置的token如果有而且没有过期是正确的 那就重新生成最新的token 放在返回的heards里边
在vue的main.js中写入拦截器 拦截所有请求 查看返回的heards里边是否存在token 如果存在更新你本地的token
methods: {
testTime() {
this.currentTime = new Date().getTime(); //更新当前时间
if (this.currentTime - parseInt(localStorage.getItem('lastTime')) > this.timeOut) { //判断是否超时(超时自动退出登录)
this.cleanAllCache(); //清除所有缓存数据
//下面就可以按自己的方式跳转到登录页
window.location.href = `${process.env.VUE_APP_OAUTH_URL}${this.OAUTH}logout?redirect_uri=${window.location.origin}${this.FOLDER_SUFFIX}`;
}
},
setLastTime() {
localStorage.setItem('lastTime', new Date().getTime().toString());//更新操作时间
}
}
created() {
//保存上次进去的时间戳
this.setLastTime();
//用定时器监听是否长时间未操作
window.setInterval(this.testTime, 5 * 1000);
}
扩展
函数缓存
函数缓存,就是将函数运算过的结果进行缓存本质上就是用空间(缓存存储)换时间(计算过程)常用于缓存数据计算结果和缓存对象
实现函数缓存主要依靠闭包、柯里化、高阶函数
函数式编程的理解
函数是'一等公民'
函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值
1. 代码简洁,开发较快
函数式编程大量使用函数,功能抽离,减少了代码的重复,代码复用性高,因此程序比较短,开发速度较快。
2. 易于理解,接近自然语言
用描述性的表达式组合不同的函数形成程序,易于理解
比如:(1 + 2) * 3 - 4 <-------> subtract(multiply(add(1,2), 3), 4)
3. 易于代码的维护和管理
函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。
4. 易于"并发编程"
函数式编程不需要考虑"死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"(concurrency)
5. 易于代码升级和扩展
函数式编程没有副作用,只要保证接口不变,内部实现是外部无关的。
vue 当中使用了哪些设计模式
- 观察者模式(Observer Pattern) :
Vue使用观察者模式来实现数据绑定和响应式更新。Vue中的数据和视图是通过观察者模式进行绑定,当数据发生变化时,会通知视图进行更新。 - 发布订阅模式(Publish-Subscribe Pattern) :
Vue也使用了发布订阅模式来实现组件间的通信。Vue实例通过$emit方法发布事件,其他组件通过$on方法订阅事件,从而实现了解耦和灵活的组件通信。 - 工厂模式(Factory Pattern) :在
Vue中,组件的创建使用了工厂模式。Vue组件是通过Vue.extend方法创建的构造函数,然后使用new关键字实例化组件对象。 - 装饰器模式(Decorator Pattern) :
Vue中的指令、计算属性、过滤器等功能都使用了装饰器模式来扩展组件的功能。通过在组件上添加不同的装饰器,可以实现不同的功能。 - 单向数据流模式(One-Way Data Flow Pattern) :
Vue推崇单向数据流,即数据从父组件向子组件传递,子组件通过props接收父组件的数据,子组件不能直接修改父组件数据,通过触发事件的方式向父组件传递数据变化。
import 和 require 有什么区别
import 是 ESM 中的模块导入语法,使用关键字 import 加上模块路径来导入模块。例如:
import { something } from 'module';
而 require 是 CommonJS 中的模块导入语法,使用 require 函数来导入模块。例如:
const something = require('module');
import 和 require 实现了相同的功能,即将一个模块引入到当前模块中。但是,import 语句在导入模块时会进行静态解析,通过静态分析代码来确定导入的模块,只能在顶层使用,不能在条件语句或循环中使用。而 require 函数在运行时根据传入的模块路径动态加载模块,可以在代码的任何位置使用。
???模板和虚拟dom的关系
???虚拟dom是如何转换为真实dom
前端多线程Web Workers
前端多线程 Worker 是指在浏览器中可以通过 Web Workers 来创建额外的线程,以便在后台执行脚本,从而提高性能并避免阻塞主线程。以下是关于前端多线程 Worker 的一些重要信息:
Web Workers 的特点:
- 独立的运行环境: Web Workers 在单独的线程中运行,不会影响主线程的执行。这意味着可以在 Worker 线程中执行长时间运行的任务而不会造成页面的卡顿。
- 无法访问 DOM: Worker 线程不能直接访问 DOM 元素,也不能操作 DOM。因为它们运行在独立的线程中,所以没有与主线程中的 DOM API 通信的能力。
- 通过消息传递进行通信: 主线程与 Worker 之间通过消息传递进行通信。可以通过 postMessage() 方法发送数据,并通过 onmessage 事件监听来接收数据。
- 支持多线程: 可以创建多个 Worker 线程,从而实现更复杂的并行计算或任务。
Web Workers 的使用场景:
- 复杂的计算任务: 对于需要进行大量计算的任务,可以将这部分任务放到 Worker 线程中执行,避免影响页面交互和渲染。
- 后台数据处理: 可以在 Worker 线程中处理后台数据的请求,例如执行数据加密、解密、压缩等操作。
- 实现高性能应用: 使用 Worker 线程可以提高应用的性能,保持页面的响应速度,提升用户体验。
创建和使用 Worker 线程的步骤:
- 创建 Worker 对象:通过 new Worker(‘worker.js’) 创建一个 Worker 线程,worker.js 是线程执行的脚本文件。
- 监听消息:在主线程中通过 worker.onmessage 监听 Worker 线程发送的消息。
- 发送消息:通过 worker.postMessage() 方法向 Worker 线程发送消息。
注意事项:
- Worker 线程无法直接访问主线程的变量和函数,需要通过消息传递进行通信。
- Worker 线程运行在一个受限的环境中,无法访问 DOM 和相关 API。
使用 Web Workers 可以提高前端应用的性能和响应速度,在合适的场景下合理使用 Worker 线程是前端开发中的一种优化手段。
了解过JWT吗?
JSON web Token 通过JSON形式作为在web应用中的令牌,可以在各方之间安全的把信息作为JSON对象传输,信息传输,授权
JWT的认证流程
1.前端把账号密码发送给后端的接口
2.后端核对账号密码成功后,把用户id等其他信息作为JWT 负载,把它和头部分别进行base64编码拼接后签名,形成一个JWT(token)
3.前端每日请求时都会把JWT放在HTTP请求头的Authorization字段内
4.后端检查是否存在,如果存在就验证JWT的有效性 (签名是否正确,token是否过期)
5.验证通过后后端使用JWT中包含的用户信息进行其他的操作,并返回对应结果
优点 简洁 包含性 包含了很多用户信息 因为token是JSON加密的形式保存在客户端,所以JWT是跨语言的,原则上是任何web形式都支持
JWT(JSON Web Token)通常用于在不同系统之间传递信任信息,常见的应用场景包括:
- 用户认证和授权:用户登录后生成JWT,客户端保存JWT,并在后续请求中携带JWT,服务器验证JWT来确定用户的身份和权限。
- 单点登录(SSO):在多个相关系统间实现单点登录,用户只需登录一次,然后可以访问所有系统而不需要重复登录。
- 安全API通信:在微服务架构中,不同的服务之间使用JWT进行身份验证和授权,确保通信的安全性。
- 前后端分离应用:前端和后端分离时,前端使用JWT来管理用户的认证信息,后端验证JWT来确定用户的身份。
- 信息交换:在信息交换的过程中,用JWT来传递需要信任的信息,保证信息安全传输和完整性。
总的来说,JWT适用于需要在不同系统或组件之间传递信任信息的场景,特别是在跨域、跨系统、跨平台的通信过程中,JWT是一个轻量级、安全可靠的选择
手写
???深拷贝
???防抖节流
bind、call、apply
call、apply、bind作用是改变函数执行时的上下文,简而言之就是显示改变函数运行时的this指向
apply 以数组的形式传入 改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
call 跟apply一样,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
bind 改变this指向后不会立即执行,而是返回一个永久改变this指向的函数
//call
function foo(name, age) {
console.log(this, name, age)
}
Function.prototype.myCall = function (thisArg, ...args) {
thisArg = thisArg === null || thisArg === undefined ? window : Object(thisArg)
thisArg.fn = this
// Object.defineProperty(thisArg, 'fn', {
// enumerable: false,
// configurable: true,
// value: this
// })
thisArg.fn(...args)
delete thisArg.fn
}
foo.myCall({ name: 'oyss' }, 'yuyss', 18)
//apply
function foo(arr) {
console.log(this, arr)
}
Function.prototype.myApply = function (thisArg, ...args) {
thisArg = thisArg === null || thisArg === undefined ? window : Object(thisArg)
thisArg.fn = this
// Object.defineProperty(thisArg, 'fn', {
// enumerable: false,
// configurable: true,
// value: this
// })
thisArg.fn(...args)
delete thisArg.fn
}
foo.myApply({ name: 'oyss' }, ['yuyss', 18])
//bind
Function.prototype.myBind = function (thisArg, ...args) {
thisArg.fn = this
return (...newArgs) => {
thisArg.fn(...args, ...newArgs)
}
}
function foo(name, age, num, a) {
console.log(name, age, num, a, this)
}
let result = foo.myBind({ name: 'oyss' }, 'yuyss', 18)
result(188, '深圳市')
利用闭包实现函数缓存
memoize函数是一个高阶函数,它接受一个函数func作为参数,并返回一个新的函数。- 在
memoize函数内部,我们定义了一个cache对象,用于存储函数的计算结果。这个cache对象是在memoize函数执行时创建的,并且在返回的新函数中可以访问到它。这就是闭包的体现。 - 返回的新函数会接受任意数量的参数
...args。我们将这些参数转换为字符串作为cache对象的键。 - 在每次调用新函数时,我们首先检查
cache对象中是否已经存在该键。如果存在,则直接返回缓存的结果。如果不存在,则调用原始函数func,并将计算结果存储在cache对象中。
function memoize(func){
const cache = {}
return function (...args){
const key = JSON.stringify(args)
if(cache[key]==undefined){
cache[key]=func(...args)
}
}
}
function foo(a,b){
console.log(a+b)
}
const result = memoize(foo)
result(1,2)
result(1,2)
eventBus
- 创建中央事件总线:在 Vue 应用中通常会创建一个专门用于事件发布和订阅的中央事件总线实例,通常被命名为 EventBus 或 eventBus。
- 注册事件:组件可以在中央事件总线上注册自己感兴趣的事件,包括事件的名称和对应的回调函数。
- 发布事件:当某个组件触发了特定的事件时,它会通过中央事件总线发布该事件,同时传递需要传递的数据。
- 订阅事件:其他组件可以在中央事件总线上订阅这个事件,并在事件发生时执行对应的回调函数,以应该事件。
const pubsub = {
evens:{},
//订阅事件
on:function (eventName,callback){
if(!this.evens[eventName]){
this.evens[eventName]=[]
}
this.evens[eventName].push(callback)
},
//发布事件
emit:function (eventName,data){
if(this.evens[eventName]){
this.evens[eventName].forEach(callback=> {
callback(data)
})
}
},
//取消订阅
off:function (eventName,callback) {
if(this.evens[eventName]){
this.evens[eventName]=this.evens[eventName].filter(cb => cb !==callback)
}
}
}
//使用
const fn = data =>{
console.log(data)
}
pubsub.on('foo',fn)
pubsub.emit('foo','你好event-bus')
pubsub.off('foo',fn)