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进行操作 <svg></svg>
2.SVG可作为文件被引入 <img src="pic.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盒子模型
JS 动画与 CSS 动画区别是什么
CSS 动画
- 性能优化: CSS 动画通常由浏览器的 GPU 加速来执行,因此在性能上通常比 JS 动画更高效。
- 简单动画: 适用于简单的动画效果,如过渡、渐变、旋转等。
- 关键帧动画: 支持使用
@keyframes
规则定义复杂的动画序列。 - 响应式设计: 可以很好地与响应式设计结合,根据媒体查询或其他 CSS 条件自动适应不同的屏幕尺寸和设备。
JS 动画
- 灵活性: 通过 JavaScript 编程能力,可以实现更复杂、更具交互性的动画效果。
- 动画控制: 可以更精确地控制动画的开始、暂停、取消和结束,以及在动画执行过程中的状态监控。
- 动态性: 可以根据用户交互、数据变化或其他动态条件来触发动画,实现更加动态和个性化的效果。
- 跨浏览器兼容性: 在一些旧版浏览器中,可能对 CSS 动画支持不完善,而通过 JavaScript 编写的动画可以更好地实现跨浏览器兼容性。
综合考虑
在实际开发中,通常会根据具体需求来选择使用 CSS 动画还是 JS 动画。简单的动画效果可以通过 CSS 轻松实现,而复杂的、交互性强的动画则可能需要借助 JavaScript 来实现。同时,对于性能要求较高的动画,尤其是需要在移动设备上流畅运行的动画,通常会倾向于使用 CSS 动画以获得更好的性能表现。
相对单位
在 CSS 中,相对单位有以下几种:
em
:相对于父元素的字体大小。例如,如果父元素的字体大小为 16px,子元素的font-size
设为 1.5em,则子元素的字体大小为 24px。rem
:相对于根元素的字体大小。例如,如果根元素的字体大小为 16px,元素的font-size
设为 1.5rem,则元素的字体大小为 24px。与em
不同的是,rem
取决于根元素的字体大小,而不是父元素的字体大小。vw
和vh
:相对于视口宽度和高度的百分比。例如,如果视口宽度为 1000px,元素的width
设为 50vw,则元素的宽度为 500px。vmin
和vmax
:相对于视口宽度和高度中较小或较大的那个值的百分比。例如,如果视口宽度为 1000px,视口高度为 800px,元素的width
设为 50vmin,则元素的宽度为 400px(因为视口宽度为较大的值,所以按照视口宽度计算)。%
:相对于父元素的宽度或高度的百分比。例如,如果父元素的宽度为 1000px,元素的width
设为 50%,则元素的宽度为 500px。
相对单位与绝对单位(如像素、英寸等)相比,具有更好的响应式特性,可以根据不同的屏幕尺寸和设备类型自适应地调整大小,因此在响应式设计中得到广泛应用
transform: scale
transform: scale
是 CSS 中的一个属性,用于对元素进行缩放变换。通过 transform: scale
,你可以改变元素的大小,而不会影响到其它布局属性。
css兼容性
-webkit-、-moz-、-ms-、-o-解决浏览器css兼容性
display:none与visibility:hidden的区别
- display:none会让元素完全从渲染树中消失,渲染时不会占据任何空间;
- visibility:hidden不会让元素从渲染树中消失,渲染的元素还会占据相应的空间,只是内容不可见。
- display:none是非继承属性,子孙节点会随着父节点从渲染树消失,通过修改子孙节点的属性也无法显示;
- visibility:hidden是继承属性,子孙节点消失是由于继承了hidden,通过设置visibility:visible可以让子孙节点显示
选择器的优先级
- 标签选择器、伪元素选择器:1
- 类选择器、伪类选择器、属性选择器:10
- id 选择器:100
- 内联样式:1000
- !important:无限大
!important声明的样式的优先级最高;
如果优先级相同,则最后出现的样式生效;
继承得到的样式的优先级最低;
通用选择器(*)、子选择器(>)和相邻同胞选择器(+)并不在这四个等级中,所以它们的权值都为 0 ;
样式表的来源不同时,优先级顺序为:内联样式 > 内部样式 > 外部样式 > 浏览器用户自定义样式 > 浏览器默认样式。
如何用CSS3画一个三角形
.up{
width:0;
height:0;
border: 100px solid transparent;
border-top: 100px solid red;/*红色*/
}
<div class="up"></div>
垂直居中
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
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);
}
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;
}
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不支持。
5. 引用外部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 文件
<link>是HTML标签,只能在HTML中使用
import是css样式,可以在HTML中通过<style>标签使用,或在css中直接使用。
页面被加载时,link会同时被加载,而@import引用的css会等到页面被加载完再加载
link方式的样式的权重高于@import的权重
重绘与回流
重绘:当元素的一部分属性发生改变,如外观、背景、颜色等不会引起布局变化,只需要浏览器根据元素的新属性重新绘制,使元素呈现新的外观叫做重绘
重排(回流):当 页面布局发生改变而需要 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
JS数据基础类型
String Number Boolean Null undefined,ES6新增,Symbol独一无二的、BigInt最大的值
JS有三种 复杂类型 (引用数据类型):
Object(对象)、Array(数组)、function(函数)
基本数据类型存储的时候是在计算机的栈内存中
-引用数据类型储存的时候也是在计算的栈内存中(地址)储存的是地址 地址指向的才是堆内存(数据)
阻止事件冒泡和默认行为
阻止事件冒泡:
event.stopPropagation(); 破破给讯
铺肉稳T
阻止默认行为: event.preventDefault();
attribute和property的区别和作用
-
存储位置
- attribute:attribute存储在HTML代码中,用于描述元素的特性。
- property:property存储在浏览器的内存中,是DOM对象的一部分,提供对元素的深入访问和控制。
-
数据类型
- attribute:attribute的值总是字符串类型。
- property:property可以有多种数据类型,如Boolean、number、string等。
-
访问方式
- attribute:可以通过element.getAttribute()和element.setAttribute()方法来访问和修改。
- property:可以直接使用点符号(.)来访问和修改。
-
同步性
- attribute:修改attribute时,会更新对应的property值。
- property:修改property时,不会影响到HTML代码本身,但会改变DOM对象的状态。
-
特性
- attribute:通常用于描述元素的初始状态和行为,如id、class等。
- property:提供了更强大和灵活的功能,如style、className等。
document.onload和document.ready区别
-
加载内容
- document.onload需要等待所有元素,包括图片、脚本等全部加载完毕后才会触发。
- document.ready仅在DOM结构绘制完成后触发,不等待其他资源如图片等的加载完成。
-
执行顺序
- document.onload:document.onload在整个页面完全加载后执行,
- document.ready:document.ready在DOM就绪时立即执行,可以更早操作dom
-
编写方式
- document.onload只能定义一个函数,有多个,后面的会覆盖前面的。
- document.ready可以同时定义多个函数,并且它们都会按顺序执行
script标签能否自闭合
根据HTML5规范,<script>
标签不可以被自闭合的。因为它们可以包含内联的脚本代码或引用外部脚本文件。
const定义的对象是否可以修改对象中的属性
对象是引用类型的,const定义的对象t中保存的是指向对象t的指针,这里的“不变”指的是指向对象的指针不变,而修改对象中的属性并不会让指向对象的指针发生变化,所以用const定义对象,对象的属性是可以改变的。
== ===区别
- ===:三个等号我们称为等同符,当等号两边的值为相同类型的时候,直接比较等号两边 的值,值相同则返回 true,若等号两边的值类型不同时直接返回 false。也就是说三个等号既要判断值也要判断类型是否相等。
- ==:两个等号我们称为等值符,当等号两边的值为相同类型时比较值是否相同,类型不同时会发生类型的自动转换,转换为相同的类型后再作比较。也就是说两个等号只要值相等就可以。
0.1+0.2为什么不等于0.3?
0.1和0.2在转换成二进制后会无限循环,由于标准位数的限制后面多余的位数会被截掉,此时就已经出现了精度的损失,相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成 0.30000000000000004。
Js 为什么会存在数字精度丢失的问题,如何解决
计算机存储双精度浮点数需要先把十进制数转换为二进制的科学记数法的形式,然后计算机以自己的规则{符号位+(指数位+指数偏移量的二进制)+小数部分}存储二进制的科学记数法
因为存储时有位数限制(64位),并且某些十进制的浮点数在转换为二进制数时会出现无限循环,会造成二进制的舍入操作(0舍1入),当再转换为十进制时就造成了计算误差
解决方法
使用 toPrecision
凑整并 parseFloat
转成数字后再显示
src 和 href 的区别
href
主要用于超链接,定义链接目标,<a>
、<link>。
src
主要用于引入外部资源,定义资源来源,<img>
、<script>
、<iframe>。
this
this
的指向取决于函数被调用的方式
- 默认绑定
- 隐式绑定
- new绑定
- 显示绑定
箭头函数没有自己的this 外层作用域this绑定谁箭头函数的this就绑定谁
事件循环 微任务和宏任务
在JavaScript的执行中,代码分为同步和异步两种。首先,引擎会优先执行所有的同步代码。随后,才会转向异步代码的执行。异步代码进一步细分为微任务和宏任务。在异步执行过程中,优先处理所有的微任务,随后再按顺序执行宏任务。每当一个宏任务执行完毕后,引擎都会检查微任务队列是否为空,如果不为空,则继续执行微任务。这样的循环过程会持续进行,直至所有的微任务和宏任务都被处理完毕。
-
首先执行所有的同步代码。
-
执行完所有同步代码后,会检查微任务队列,如果有任务就全部执行完。
-
执行完微任务队列后,会从宏任务队列中取出第一个宏任务执行。
-
执行完这个宏任务后,再次检查微任务队列,如果有微任务就全部执行完,然后会接着检查宏任务队列,如果有会接着执行宏任务,执行完当前宏任务,会查看微任务队列是否有微任务,有即清空一直循环直到宏任务队列清空
浅拷贝深拷贝
深拷贝和浅拷贝都是对于复杂数据类型进行复制的操作,区别在于复制的方式不同。
浅拷贝是指创建一个新对象,这个新对象有着原始对象属性值的一份精确拷贝,如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝是指创建一个新对象,这个新对象的值和原始对象的值完全没有关联,即便原始对象中有引用类型的属性,新对象也会开辟新的内存地址,完全拷贝一份新的对象,修改一个对象不会影响到另一个对象。
闭包
闭包是定义在一个函数内部的函数,内层函数可以访问外层函数的局部变量,这些变量被内层函数引用不会被回收,好处是使局部变量拥有更长的生命周期可以用来封装一段逻辑,坏处是闭包常驻内存造成内存泄露。
- 一个普通的函数function,如果它可以访问外层作用域的自由变量,那么这个函数和周围环境就是一个闭包;
- 从广义的角度来说:JavaScript中的函数都是闭包;
- 从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用域的自由变量,那么它是一个闭包
当闭包内部的函数持续引用外部函数的变量时,这些变量无法被垃圾回收机制回收,导致内存泄漏
箭头函数和普通函数区别
-
this 指向不同:
- 普通函数中的
this
指向调用该函数的对象。this
的值是动态确定的,取决于函数的调用方式。 - 箭头函数中的
this
指向定义箭头函数时所在的外层作用域中的this
值。箭头函数中的this
是静态确定的,不会随着调用方式的改变而改变。
- 普通函数中的
-
构造函数:
- 普通函数可以作为构造函数使用,使用
new
关键字创建对象实例。 - 箭头函数是没有显式原型prototype,不能作为构造函数使用,如果尝试使用
new
关键字调用箭头函数,会抛出错误。
- 普通函数可以作为构造函数使用,使用
-
arguments 对象:
- 普通函数可以访问
arguments
对象,该对象包含函数调用时传入的所有参数。 - 箭头函数没有自己的
arguments
对象,但可以访问外层作用域的arguments
对象。不绑定this、arguments、super
- 普通函数可以访问
-
返回值:
- 普通函数如果没有显式返回值,会隐式返回
undefined
。 - 箭头函数如果函数体只有一个表达式,可以省略
return
关键字,表达式的结果会自动返回。
- 普通函数如果没有显式返回值,会隐式返回
JavaScript中的模块化编程
JavaScript 模块是指将代码封装在单独的文件中,并按需导出和导入其中的功能。它们可以包含变量、函数、类和其他任何 JavaScript 实体。模块有自己的作用域,而不是和脚本一样在全局作用域内执行代码。一组相互导入、导出的模块组成的图形通常被称为模块树。
实现模块化编程的主要方式有以下几种:
- CommonJS 模块: Node.js 中使用的模块化系统,使用 require() 导入块, module.exports 导出模块。
- ES6 模块: 从 ECMAScript 2015 (ES6) 开始引入的原生模块系统,使用 import 和 export 关键字。
- AMD (Asynchronous Module Definition): 一种异步加载模块的规范,使用 define() 和 require() 函数。
- UMD (Universal Module Definition): 一种兼容多种模块系统的模块定义方式。
- 自定义模块模式: 使用立即执行函数表达式 (IIFE) 或对象字面量来实现模块化。
数据类型判断
- 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
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
。
当一个对象实现了Iterable Protocol int罗波 破ruiT口(可迭代协议)它就是一个可迭代对象,这个对象要求包含一个键为 Symbol.iterator 的属性,int瑞t 该属性的值是一个函数iterator
方法,通过for of遍历其实就是通过Symbol.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)
;
什么是生成器
生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。生成器函数使用 function* 关键字定义,其中包含了一个或多个 yield 表达式,用于指示生成器函数在暂停时要返回的值。
cookie与本地存储的区别
1、存储容量
Cookie:每个域名下的Cookie总容量通常为4KB,每个Cookie的大小限制为几KB左右。
本地存储:Local Storage和Session Storage的总容量通常为5MB或更大,每个浏览器可能有不同的限制。
2. 生命周期
Cookie:会话级的(浏览器关闭时失效)也可以设置过期时间持久性的(指定过期时间)
本地存储:Local Storage的数据永久保存在浏览器中,除非代码或用户手动删除;Session Storage的数据仅在当前会话中有效,关闭浏览器或标签页后将被清除。
3. 数据传输
Cookie:,Cookie 在每次 HTTP 请求中会被自动发送到服务器,增加数据传输的开销。
本地存储:不会随每个请求发送给服务器,仅在浏览器端使用,因此不会增加数据传输的开销。
4. 数据安全性
Cookie:由于Cookie是存储在浏览器中的,所以可能受到跨站脚本攻击(XSS)和跨站请求伪造(CSRF)等安全问题的影响。
本地存储:由于本地存储不会自动附加到每个请求中,所以相对于Cookie来说更加安全,但仍然需要注意XSS攻击。
5. 数据类型
Cookie:只能存储字符串类型的数据,如果要存储复杂的数据结构,需要进行序列化和反序列化。
本地存储:可以存储各种数据类型,包括字符串、数字、布尔值、对象、数组等。
6. 数据访问
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事件用了未卸载
- 闭包未正确使用 使用闭包函数,并且闭包内部引用了外部作用域的变量,如果这些变量在之后不再需要但闭包仍然存在,会导致内存泄漏
垃圾回收机制
作用:清除不在使用的对象,腾出内存空间
- 对象不再被引用的时候是垃圾;
- 对象不能从根上访问到时也是垃圾;
标记清除法
标记清除法分为标记和清除两个阶段,标记阶段需要从根节点遍历内存中的所有对象,并为可达的对象做上标记,清除阶段则把没有标记的对象(非可达对象)销毁。
缺点
- 首先是内存碎片化。这是因为清理掉垃圾之后,未被清除的对象内存位置是不变的,而被清除掉的内存穿插在未被清除的对象中,导致了内存碎片化。
- 内存分配速度慢。由于空闲内存不是一整块,假设新对象需要的内存是
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 来更新页面内容,比如切换不同的组件或视图。
-
无刷新切换: 因为页面没有刷新,所以用户体验更流畅,不会出现白屏或闪烁的情况。
-
状态管理:
history
API 还提供了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的优点
- 更加安全:能够降低网站和用户的安全风险,防止恶意网站窃取用户信息。
- 更加灵活:可以选择性地允许特定域名或一组域名来访问资源,具有更灵活的控制权限。
错误捕获方式
settimeout和setinterval的那个时间更准
在JavaScript中,setTimeout
和setInterval
是两个常用的定时器函数,用于在特定的时间延迟后执行代码。这两个函数在事件循环影响、浏览器限制以及累积误差等方面有所区别。具体分析如下:
-
事件循环影响
- setTimeout:如果事件循环中有其他任务阻塞了执行,
setTimeout
的回调可能会延迟执行。 - setInterval:同样会受到事件循环的影响。如果前一个回调函数耗时过长,会导致后续的回调堆积,并可能产生累积误差。
- setTimeout:如果事件循环中有其他任务阻塞了执行,
-
浏览器限制
- setTimeout:大多数浏览器对
setTimeout
有一个最小时间间隔的限制,通常是4毫秒。 - setInterval:受到浏览器相同的限制,并且由于它是基于固定时间间隔的,所以实际调用的时间可能会比预期晚。
- setTimeout:大多数浏览器对
-
累积误差
- setTimeout:没有累积误差的问题,因为它每次只在指定的时间后执行一次。
- setInterval:存在累积误差问题,因为如果前一个回调未执行完成,下一个时间点又到了,会再次添加回调,导致误差累积。
-
页面不可见情况
- setTimeout:页面不可见时,行为不会受到太大影响。
- setInterval:页面不可见时,浏览器可能会降低或停止其执行,以节省资源。
-
跨浏览器兼容性
- setTimeout:在不同浏览器中的表现基本一致。
- setInterval:不同浏览器对
setInterval
的实现方式和准确性有所差异。
-
使用建议
- setTimeout:适用于只执行一次的场景,或者需要模拟
setInterval
但避免其缺点的情况。 - setInterval:适合重复执行的场景,但要注意其可能导致的误差和问题。
- setTimeout:适用于只执行一次的场景,或者需要模拟
针对上述分析,可以考虑以下几点建议:
- 如果对时间的精确性有较高要求,应优先考虑使用
setTimeout
,并通过递归调用来实现类似setInterval
的效果。 - 对于长时间运行或有可能阻塞的事件,应避免使用
setInterval
,以免造成更大的误差。 - 当页面不可见时,如果仍需要定时器继续工作,可考虑使用
requestAnimationFrame
或Web Workers
作为替代方案。
面向对象
继承
对象的 [[prototype]] __proto__
(隐式原型)
- 每个对象在创建时都有一个内部的
[[prototype]]
属性,它指向该对象的原型对象。这个原型对象可以是另一个对象,也可以是null
。 - 这个原型对象被用于实现对象之间的继承关系,当我们访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到为止。
函数的 prototype
(显示原型)
- 每个函数对象都有一个名为
prototype
的属性,它指向一个对象。这个对象通常被用作构造函数创建的实例对象的原型对象。也就是被创建出来的原型对象它的__proto__
属性所指向的构造函数prototype
对象 - 通过操作函数的
prototype
属性,我们可以为该函数创建的实例对象添加共享的属性和方法,实现基于原型的继承机制。
__proto__
__proto__
是每个对象都具有的属性,它指向该对象的原型。通过 __proto__
属性,对象可以访问和继承原型对象的属性和方法。当你访问一个对象的属性或方法时,如果对象本身没有该属性或方法,JavaScript 会沿着原型链(通过 __proto__
)向上查找,直到找到该属性或方法或者到达原型链的顶端(即 null
)。推荐使用 Object.getPrototypeOf(obj)
方法来获取对象的原型。
prototype
prototype
是函数对象特有的属性。当你创建一个函数时,JavaScript 会为该函数自动创建一个 prototype
属性,并将其初始化为一个空对象。这个 prototype
对象是函数对象的一个属性,它被用作构造函数,用于创建新对象实例时的原型对象。
当你使用 new
关键字调用一个函数时,
- JavaScript 会创建一个新的对象,
- 并将该对象的
__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)
数组
数组方法大全
顺序 | 方法名 | 功能 | 返回值 | 改变 | 版本 |
---|---|---|---|---|---|
1 | push() | (在结尾)向数组添加一或多个元素 | 返回新数组长度 | Y | ES5 |
2 | unshift() | (在开头)向数组添加一或多个元素 | 返回新数组长度 | Y | ES5 |
3 | pop() | 删除数组的最后一位 | 返回被删除的数据 | Y | ES5 |
4 | shift() | 移除数组的第一项 | 返回被删除的数据 | Y | ES5 |
5 | reverse() | 反转数组中的元素 | 返回反转后数组 | Y | ES5 |
6 | sort() | 以字母顺序(字符串Unicode码点)对数组进行排序 | 返回新数组 | Y | ES5 |
7 | splice() | 在指定位置删除指定个数元素再增加任意个数元素 (实现数组任意位置的增删改) | 返回删除的数据所组成的数组 | Y | ES5 |
8 | concat() | 通过合并(连接)现有数组来创建一个新数组 | 返回合并之后的数组 | N | ES5 |
9 | join() | 用特定的字符,将数组拼接形成字符串 (默认",") | 返回拼接后的新数组 | N | ES5 |
10 | slice() | 裁切指定位置的数组 | 被裁切的元素形成的新数组 | N | ES5 |
11 | toString() | 将数组转换为字符串 | 新数组 | N | ES5 |
12 | valueOf() | 查询数组原始值 | 数组的原始值 | N | ES5 |
13 | indexOf() | 查询某个元素在数组中第一次出现的位置 | 存在该元素,返回下标,不存在 返回 -1 | N | ES5 |
14 | lastIdexOf() | 反向查询数组某个元素在数组中第一次出现的位置 | 存在该元素,返回下标,不存在 返回 -1 | N | ES5 |
15 | forEach() | (迭代) 遍历数组,每次循环中执行传入的回调函数 | 无/(undefined) | N | ES5 |
16 | map() | (迭代) 遍历数组, 每次循环时执行传入的回调函数,根据回调函数的返回值,生成一个新的数组 | 有/自定义 | N | ES5 |
17 | filter() | (迭代) 遍历数组, 每次循环时执行传入的回调函数,回调函数返回一个条件,把满足条件的元素筛选出来放到新数组中 | 满足条件的元素组成的新数组 | N | ES5 |
18 | every() | (迭代) 判断数组中所有的元素是否满足某个条件 | 全都满足返回true 只要有一个不满足 返回false | N | ES5 |
19 | some() | (迭代) 判断数组中是否存在,满足某个条件的元素 | 只要有一个元素满足条件就返回true,都不满足返回false | N | ES5 |
20 | reduce() | (归并)遍历数组, 每次循环时执行传入的回调函数,回调函数会返回一个值,将该值作为初始值prev,传入到下一次函数中 | 最终操作的结果 | N | ES5 |
21 | reduceRight() | (归并)用法同reduce,只不过是从右向左 | 同reduce | N | ES5 |
22 | includes() | 判断一个数组是否包含一个指定的值. | 是返回 true,否则false | N | ES6 |
23 | Array.from() | 接收伪数组,返回对应的真数组 | 对应的真数组 | N | ES6 |
24 | find() | 遍历数组,执行回调函数,回调函数执行一个条件,返回满足条件的第一个元素,不存在返回undefined | 满足条件第一个元素/否则返回undefined | N | ES6 |
25 | findIndex() | 遍历数组,执行回调函数,回调函数接受一个条件,返回满足条件的第一个元素下标,不存在返回-1 | 满足条件第一个元素下标,不存在=>-1 | N | ES6 |
26 | fill() | 用给定值填充一个数组 | 新数组 | Y | ES6 |
27 | flat() | 用于将嵌套的数组“拉平”,变成一维的数组。 | 返回一个新数组 | N | ES6 |
28 | flatMap() | flat()和map()的组合版 , 先通过map()返回一个新数组,再将数组拉平( 只能拉平一次 ) | 返回新数组 | N | ES6 |
29 | Array.isArray() | 判断是否是数组 | 返回布偶值 | N | ES5 |
find和filter的区别
区别一:返回的内容不同
filter 返回是新数组
find 返回具体的内容
区别二:
find :匹配到第一个即返回
filter : 返回整体(没一个匹配到的都返回)
some和every的区别
some 如果有一项匹配则返回true
every 全部匹配才会返回true
数组和伪数组区别
数组:
- 具有数组的方法,如
push
、pop
、slice
等。 - 可以通过索引访问元素。
Array.isArray()
方法返回true
。
伪数组:
- 外观上类似数组,具有索引和元素。
- 但可能不具备所有真正数组的方法。
Array.isArray()
方法返回false
let realArr = Array.from(pseudoArr); Array.from()
let realArr = [...pseudoArr]; 扩展运算符
Promise
一种用于处理异步操作的对象,它提供了一种更优雅和可控的方式来处理异步代码,解决回调地狱问题:使用回调函数处理异步操作时,容易出现"回调地狱"的问题,通过链式调用解决问题.then(data).then(data)
- 处理异步操作的结果:Promise 可以将异步操作的结果封装在一个对象中,方便进行后续的处理。
- 链式调用:Promise 可以通过链式调用的方式,依次处理多个异步操作,提高代码的可读性和可维护性。
- 错误处理:Promise 提供了一种统一的错误处理方式,方便捕获和处理异步操作中的错误。
三种状态
-
Pending(待定):这是 Promise 的初始状态。在异步操作开始时,Promise 处于此状态。
-
Fulfilled(已成功):当异步操作成功完成时,Promise 转换为此状态。在此状态下,Promise 会返回一个值,称为 fulfillment value。
-
Rejected(已拒绝):当异步操作失败时,Promise 转换为此状态。在此状态下,Promise 会返回一个原因,称为 rejection reason,表示为什么异步操作会失败。
Promise的实现
讲了一下Promise实现的几个关键技术点:
- 重点是需要实现
Promise.then
方法- 维护一个
fullfilled
的事件队列和一个rejected
事件队列- 在
Promise.then
方法里需要判断一下当前Promise的状态以及参数类型- 最后需要实现两个事件队列的自执行,用来处理链式调用的情况
- 在执行方法时使用
setTimeout
模拟异步任务
Promise的九大方法
Promise.resolve
将传递给它的参数填充到 (fulfilled),Promise 对象会立即进入确定(fulfilled)状态
Promise.reject
Promise.reject
方法的作用是创建一个被拒绝的 Promise
对象。它接受一个参数,通常是一个错误对象或描述错误的消息,用于表示拒绝的原因。
当使用 Promise.reject
创建的 Promise
对象被调用时,它会立即进入拒绝状态,并触发相应的错误处理逻辑。你可以通过 then
方法的第二个回调函数或 catch
方法来处理拒绝的情况。
const rejectedPromise = Promise.reject(new Error('出现了错误'));
//1.catch(()=>{})
rejectedPromise.then(res=>{
})
.catch(err => {
console.error(error);
}
))
2.then(res=>{},err=>{})
rejectedPromise.then(
(value) => {
// 不会执行
},
(error) => {
console.error(error);
}
);
Promise.then
用于注册当 Promise 解决(fulfilled)或拒绝(rejected)时应当调用的回调函数
- 如果Promise对象状态变为fulfilled,则会执行then()方法传入的第一个回调函数(onFulfilled)。
- 如果Promise对象状态变为rejected,则会执行then()方法传入的第二个回调函数(onRejected)。
- 如果then()方法中返回一个新的Promise对象,那么下一个then()方法会等待这个新的Promise完成后再执行。
- 如果then()方法中返回一个普通值,那么会将这个值传递给下一个then()方法的回调函数。
- 链式调用:
.then
方法返回一个新的 Promise,允许链式调用,即可以连续调用多个.then
方法。 - 非阻塞:
.then
方法不会阻塞代码的执行,它会按照 JavaScript 的事件循环机制在适当的时候执行回调函数。 - 错误处理:如果在
.then
的回调函数中抛出错误,返回的新的 Promise 将被拒绝,并将错误作为拒绝理由。 - 穿透值:如果
.then
方法中的回调函数没有显式返回任何值(或者返回undefined
),那么链中的下一个.then
将接收到上一个 Promise 的值。
Promise.catch
用于处理 Promise 在执行过程中可能出现的错误或拒绝情况
- 为Promise对象添加一个拒绝(rejected)的回调函数。
- 当Promise对象状态变为rejected时,catch()方法中的回调函数就会被执行。
- catch()方法也可以链式调用,返回一个新的Promise对象。
- 如果Promise对象状态变为rejected,则会执行catch()方法传入的回调函数(onRejected)。
- 在then()方法中如果发生了异常,也会被catch()方法捕获到。
- catch()方法可以用来处理Promise链中任何一个环节出现的错误,起到错误处理的作用。
- 如果catch()方法中返回一个新的Promise对象,那么下一个then()或catch()方法会等待这个新的Promise完成后再执行。
- 如果catch()方法中返回一个普通值,那么会将这个值传递给下一个then()方法的回调函数。
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 塞 t e D
有多个不依赖于彼此成功完成的异步任务时,或者你总是想知道每个 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+自动执行器)
在底层,async
函数通过将 async
关键字添加到函数声明来自动将函数转换为一个 Generator
函数。await
关键字则被转换成 yield
表达式。此外,内置的自动执行器会处理 Promise
的链式调用,从而无需手动执行 next
方法。
- 基本概念: async/await 是 JavaScript 中用于处理异步操作的语法糖,用同步的思维去解决异步的代码,使得代码更加清晰易读。
- async 函数: 是用来声明一个异步函数的关键字,异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行,async函数它会返回一个 Promise 对象。在 async 函数内部,可以使用 await 关键字来等待一个 Promise 对象的解析。
//异步函数也可以有返回值,但是异步函数的返回值会被包裹到Promise.resolve中: async function foo() { return "abc" } //foo() //Promise {<fulfilled>: 'abc'} foo().then(res => { console.log("res:", res)//abc }) //如果我们的异步函数的返回值是Promise,Promise.resolve的状态会由Promise决定: async function foo() { return new Promise((resolve, reject) => { // resolve("aaa") reject("bbb") }) } foo().then(res => { console.log("res:", res) }).catch(err => { console.log("err:", err) }) //如果我们的异步函数的返回值是一个对象并且实现了thenable,那么会由对象的then方法来决定: async function foo() { return { then: function(resolve, reject) { // resolve(111) reject(222) } } } foo().then(res => { console.log("res:", res) }).catch(err => { console.log("err:", err) })
- await 关键字: await 关键字用于等待一个 Promise 对象的解析结果还可以接 async 函数。当遇到 await 关键字时,函数会暂停执行,直到 Promise 对象状态变为 resolved,然后将 resolved 的结果返回。
//如果await后面是一个普通的值,那么会直接返回这个值: async function foo() { console.log("foo函数开始~") const result = await 123 console.log("代码继续执行:", result) } //如果await后面是一个theable的对象,那么会根据对象的then方法调用来决定后续的值: async function foo() { console.log("foo函数开始~") const result = await { then: function(resolve, reject) { resolve("aaa") } } console.log("代码继续执行:", result) } //如果await后面的表达式,返回的Promise是reject的状态,那么会将这个reject结果直接作为foo的Promise的reject值: function requestData(url) { console.log("调用了requestData请求") return new Promise((resolve, reject) => { setTimeout(() => { if (url === "coderwhy") { // 发送成功了 resolve("一组成功数据") } else { // 发送失败了 reject("请求url错误") } }, 1000); }) } async function foo() { console.log("foo函数开始~") const result = await requestData("kobe") console.log("代码继续执行:", result) } foo().then(res => { console.log("res:", res) }).catch(err => { console.log("err:", err) })
- 错误处理: 在 async 函数中,可以使用 try/catch 来捕获异步操作中的错误,并进行相应的处理,使用 async 关键字声明的函数会被自动包装成 Promise 对象所以也可以对函数使用.then()或.catch()来捕获异常
function requestData(url) { console.log("调用了requestData请求") return new Promise((resolve, reject) => { setTimeout(() => { if (url === "coderwhy") { // 发送成功了 resolve("一组成功数据") } else { // 发送失败了 reject("请求url错误") } }, 1000); }) } async function foo() { console.log("foo函数开始~") const result = await requestData("coderwhy") return result console.log("代码继续执行:", result) } foo().then(res => { console.log("res:", res) }).catch(err => { console.log("err:", err) })
- 与 Promise 的关系: async/await 是建立在 Promise 基础之上的一种语法糖,它可以更加方便地处理异步操作,减少了回调函数的嵌套。
Generator 生成器函数
Generator 函数是 ES6 引入的一种特殊的函数,它允许你在函数执行过程中暂停执行,并在稍后的某个时刻从停止的地方恢复执行。这种能力使得 Generator 函数非常适合处理异步操作,因为它可以在等待异步操作完成时暂停执行,而不需要复杂的回调嵌套。
基本语法
Generator 函数通过在函数声明之前添加一个星号 *
来定义。在函数体内,你可以使用 yield
表达式来指定暂停和恢复执行的位置。
function* generatorFunction() {
yield 'Hello';
yield 'World';
return 'End';
}
使用方法
当你调用一个 Generator 函数时,它不会立即执行,而是返回一个迭代器(Iterator)对象。要执行函数中的代码,你需要调用迭代器的 next()
方法。
const iterator = generatorFunction();
console.log(iterator.next()); // { value: 'Hello', done: false }
console.log(iterator.next()); // { value: 'World', done: false }
console.log(iterator.next()); // { value: 'End', done: true }
每次调用 next()
方法时,函数会执行到下一个 yield
表达式,并返回一个包含 value
和 done
属性的对象。value
是 yield
表达式的结果,而 done
是一个布尔值,表示函数是否已经执行完毕。
特点
- 暂停和恢复执行:通过
yield
表达式,Generator 函数可以在任何地方暂停执行,并在需要时恢复。 - 惰性求值:Generator 函数只有在请求下一个值时才会执行,这种惰性求值的特点使得它非常适合于生成序列和异步操作。
- 状态保持:当 Generator 函数暂停时,它的状态会被保留,下次恢复执行时可以从该状态继续执行。
应用场景
Generator 函数通常用于以下场景:
- 异步流程控制:处理基于回调的异步代码时,使用 Generator 函数可以使代码看起来像同步代码一样。
- 数据生成:可以用来生成序列数据,例如斐波那契数列或其他复杂的数据结构。
- 协程:在多线程编程中,Generator 函数可以用来实现协作式多任务处理。