分集(与合集同步,方便专题学习)
2023年最全前端面试题考点HTML5+CSS3+JS_参宿7的博客-CSDN博客
2023年前端面试题考点之 通信(渲染、http协议、缓存、异步、跨域)_参宿7的博客-CSDN博客
2023年最全前端React18面试题考点_参宿7的博客-CSDN博客
2023年Vue3前端面试题考点_参宿7的博客-CSDN博客
2023年最全前端面试项目考点(npm,git,webpack,框架)_参宿7的博客-CSDN博客
前端笔试常考数据结构,ACM模板,经典算法_参宿7的博客-CSDN博客
合集(方便检索,⭐表示手写 和 重要程度,*表示了解即可)
目录
content-box 内容盒模型(W3C盒) 和 border-box 边框盒模型(IE 盒)
typeof运算符,instance of运算符,isPrototypeOf() 方法,constructor,Object prototype
Iterator,for in,for of,forEach,map循环遍历
正则表达式Regular Expression(RegExp) (⭐手写)
Cookie、localStorage和sessionStorage
Promise.all()哪怕一个请求失败了也能得到其余正确的请求结果的解决方案⭐⭐
正则表达式Regular Expression(RegExp)
HTML5
HTML5的设计目的是为了在移动设备上支持多媒体。
在HTML5出来之前,我们习惯于用没有语义的div来表示不同模块。
在HTML5中加入了一些语义化标签,来更清晰的表达文档结构。
语义化标签的好处⭐⭐
- 用户:提高体验,比如:title,alt用于解释名词和图片信息
- 非技术人员:能看懂代码,很好的呈现内容结构、代码结构
- 技术人员:便于团队开发与维护,语义化更具有可读性
- 搜索引擎:利于SEO。语义化能和搜索引擎建立更好的联系,优化搜索
Web标准和W3C标准⭐
网页组成
web标准
- 结构(骨架):HTML用于描述页面的结构
- 表现(皮肤):CSS用于控制页面中元素的样式
- 行为(交互):JavaScript用于响应用户操作
- W3C:World Wide Web(万维网) Consortium,对web标准提出了代码规范的要求
-
对结构的要求
1、标签字母要小写
2、标签要闭合
-
对行为的要求
1、建议使用外链CSS和js脚本,实现结构与表现分离、结构与行为分离,能提高页面的渲染效率,更快地显示网页内容
响应式布局⭐⭐
响应式布局指的是同一页面在不同屏幕尺寸下有不同的布局。传统的开发方式是PC端开发一套,手机端再开发一套,而使用响应式布局只要开发一套就够。
- 使用媒体查询(@media)
- 使用flex弹性布局
- 使用百分比单位:rem单位,VH、HW单位
浏览器的渲染过程⭐⭐⭐
1.解析HTML的所有标签,深度遍历生成DOM Tree
2.解析CSS,构建层叠样式表模型CSSOM(CSS Object Model)
2.5.JS脚本加载
a. 普通js/sync
文档解析的过程中,如果遇到script脚本,就会停止页面的解析进行下载,当脚本都执行完毕后,才会继续解析页面。
(因为JS可以操作DOM和CSS,可能会改动DOM和CSS,所以继续解析会造成浪费)。
如果脚本是外部的,会等待脚本下载完毕,再继续解析文档。
所以常见的做法是将js放到页脚部分。
b. async(异步:HTML加载和解析,js加载)
async脚本会在加载完毕后执行。
<script type="text/javascript" src="x.min.js" async="async"></script>
async脚本的加载不计入DOMContentLoaded事件统计,也就是说下图两种情况都是有可能发生的:
HTML 还没有被解析完的时候,async脚本已经加载完了,那么 HTML 停止解析,去执行脚本,脚本执行完毕后触发DOMContentLoaded事件。
HTML 解析完了之后,async脚本才加载完,然后再执行脚本,那么在HTML解析完毕、async脚本还没加载完的时候就触发DOMContentLoaded事件
c. defer(推迟)
文档解析时,遇到设置了defer的脚本,就会在后台进行下载,但是并不会阻止文档的渲染,当页面解析和渲染完毕后,会等到所有的defer脚本加载完毕并按照顺序执行完毕才会触发
<script type="text/javascript" src="x.min.js" defer="defer"></script>
DOMContentLoaded事件,也就是说下图两种情况都是有可能发生的:
HTML 还没有被解析完的时候,defer脚本已经加载完了,那么 等待HTML 解析完成后执行脚本,脚本执行完毕后触发DOMContentLoaded事件。
HTML 解析完了之后,defer脚本才加载完,然后再执行脚本,脚本执行完毕后触发DOMContentLoaded事件。
defer是“渲染完再执行”:依赖于页面中的DOM元素(文档是否解析完毕),或者被其他脚本文件依赖
async是“下载完就执行”:并不关心页面中的DOM元素(文档是否解析完毕),并且也不会产生其他脚本需要的数据。
3.构建Render Tree(渲染树)
DOM和CSSOM根据一定的规则组合起来生成了Render Tree
4.布局(Layout)
确定各个元素的位置,以及大小。浏览器使用一种流式处理的方法,只需要一次绘制操作就可以布局所有的元素。
5.绘制(Painting)
浏览器会遍历Render Tree渲染树,调用“paint”方法,将渲染树的各个节点绘制到屏幕上。
回流(重排)和重绘⭐⭐
回流(重排)
元素改变 尺寸,宽高,边框,内容,位置 都会引起重排,导致需要重新构建页面的时候
- 增删可见的 DOM 元素的时候
- 元素的位置发生改变
- 元素的尺寸发生改变
- 内容改变
- 页面第一次渲染的时候
重绘
外观发生改变,但没有改变布局
列举一些相关的 CSS 样式:color、background、background-size、visibility、box-shadow
CSS3
盒模型⭐⭐⭐
内容(content)、内边距/填充(padding)、外边距/边界(margin)、 边框(border);
content-box 内容盒模型(W3C盒) 和 border-box 边框盒模型(IE 盒)
width = content宽度
width = content宽度 + padding + border
<div class="content-box"></div>
<div class="border-box"></div>
实现梯形,三角形,扇形,圆形,半圆(⭐手写)
/* HTML CODE:
<div class="square">正方形</div>
*/
/* CSS CODE */
.square {
width: 100px;
height: 100px;
border-top: 50px solid red;<!--solid: 定义实线边框-->
border-right: 50px solid green;
border-bottom: 50px solid orangered;
border-left: 50px solid blue;
}
关键:
border: 50px solid transparent; border-color设置为【透明】
border-radius: 50%;
border-top-left-radius: 50px;
详情:
CSS实现各种图形 -- 梯形,三角形,扇形,圆形,半圆 - 掘金
盒子充满屏幕 (⭐手写)
相对当前屏幕高度
div.test
{
background-color:red;
width:100vw;
height:100vh;
}
选择器
ID选择器、类选择器、标签选择器(按优先级高到低排序)⭐⭐
<html>
<head>
<meta charset=utf-8>
<style type="text/css">
div{
color:#ff0000;
font-size:20px;
}
.green{
color:#008000;
}
#black{
color:#000000;
}
</style>
</head>
<body>
<div>红色</div>
<div class='green'>绿色</div>
<div id='black'>黑色</div>
</body>
</html>
属性选择器
属性选择元素
[title]
{
color:blue;
}
伪类和伪元素选择器
逻辑选择元素
- 伪类选择器:nth-child(n)
nth-child(n)匹配属于其父元素的第n个子元素,不论元素类型,n可以是数字、关键词、或公式。关键词odd和even是可用于匹配下标是奇数或偶数的子元素的关键词(第一个子元素的下标是 1)
<html>
<head>
<meta charset=utf-8>
<style type="text/css">
ul li:nth-child(even) {
background-color: rgb(255,0,0);
}
</style>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</body>
</html>
- 伪元素
<head>
<meta charset=utf-8>
<style type="text/css">
div::after{
content:"";
width: 20px;
height: 20px;
background-color: rgb(255,0,0);
display: block;
}
</style>
</head>
<body>
<div></div>
</body>
</html>
html模块的div元素加一个后伪元素
- 伪类只能使用“:”,伪元素既可以使用“:”,也可以使用“::”
- 伪元素其实相当于伪造了一个元素,伪类没有伪造元素,例如first-child只是给子元素添加样式而已。(本质区别就是是否抽象创造了新元素)
优先级⭐⭐⭐
- 在同一层级下:权值由高到低
- !important (会覆盖CSS的任何声明,其实与优先级毫无关系) 权值
- 内联样式(style=“ ”) 1000
- ID选择器(id=" “) 100
- 伪类选择器(如:hover)
- 属性选择器[title]{color:blue;})
- Class类选择器(class=” ") 10
- HTML标签选择器 (p{}) 1
- 通用选择器(*) 0
- 不同层级下:
正常来说权重值越高的优先级越高,但是一直以来没有具体的权重值划分,所以目前大多数开发中层级越深的优先级越高
样式方式(按优先级高到低排序)⭐⭐
内联样式表(在标签内设置元素的样式)
写一次只能设置一个
<p style="background:red"></p>
嵌入样式表(在head标签内)
<head>
<title></title>
<style type="text/css">
p{
background-color:yellow;
}
</style>
</head>
外部样式表(在head标签内)
rel=relationship
href=hypertext Reference
<head>
<title></title>
<link href="xxx.css" rel="stylesheet" type="text/css"/>
</head>
通过 link 进行对外部CSS样式文件的引用,也可以引用网上别人写好的样式
transform旋转,缩放,平移⭐⭐
修改 CSS 坐标空间,实现旋转,缩放,倾斜或平移
默认相对元素的中心点
position关键字⭐⭐⭐
relative
相对于其正常位置进行定位。
元素先放置在未添加定位时的位置,再在不改变页面布局的前提下调整元素位置(因此会在此元素未添加定位时所在位置留下空白)。
absolute
相对于 static 定位以外的第一个父元素进行定位。
元素会被移出正常文档流,并不为元素预留空间。绝对定位的元素可以设置外边距(margins),且不会与其他边距合并。
- fixed:相对于浏览器窗口进行定位。在屏幕滚动时不会改变
- sticky :基于用户滚动的位置,屏幕滚出时会粘住
水平 & 垂直对齐 (⭐手写)
水平居中
指在水平方向上处于中间的位置。
- 元素/图片:
margin: auto;
行内元素会占整行,看不出来水平居中,所以需要:width: 50%;
- 文本:
文本标签除了<p>都是行内元素,text-align=center
垂直居中
- 单行文本:
line-height = height
- 图片:
vertical-align: middle;
水平垂直居中⭐⭐⭐
top: 50%;left: 50%;, 是以元素左上角为原点,故不处于中心位置,
加上transform:translate(-50%,-50%) ,元素原点(中心点)往上(x轴),左(y轴)移动自身长宽的 50%,
flex布局⭐⭐⭐
布局的传统解决方案,基于盒状模型,依赖 display属性 + position属性 + float属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。
Flexible box设置或检索弹性盒模型对象的子元素如何分配空间
BFC规范⭐⭐⭐
问题:
- 外边距重叠:
块的上外边距margin-top和下外边距margin-bottom会合并为单个边距(为单个边距的最大值)
- 浮动导致父高度塌陷:
- 不浮动的元素被浮动元素覆盖:
BFC块级格式化上下文(Block Fromatting Context)
独立布局,盒子内子元素样式不会影响到外面的元素。
overflow属性指定如果内容溢出一个元素的框,会发生什么
值 | 描述 |
---|---|
visible | 默认值。内容不会被修剪,会呈现在元素框之外。 |
hidden | 内容会被修剪,并且其余内容是不可见的。 |
scroll | 内容会被修剪,但是浏览器会显示滚动条以便查看其余的内容。 |
auto | 如果内容被修剪,则浏览器会显示滚动条以便查看其余的内容。 |
inherit | 规定应该从父元素继承 overflow 属性的值。 |
- 避免外边距重叠
- 清除浮动
- 阻止元素被浮动元素覆盖:
三栏布局 :左右固定,中间自适应(⭐手写)⭐⭐⭐
flex布局(强烈推荐)
- 基础巩固
flex 属性用于设置或检索弹性盒模型对象的子元素如何分配空间。
flex 属性是 flex-grow、flex-shrink 和 flex-basis 属性的简写属性。
注意:如果元素不是弹性盒模型对象的子元素,则 flex 属性不起作用。
- 实现方法
左右两栏设置宽度,中间栏设置 flex:1,占满余下部分
<!DOCTYPE html>
<html lang="en">
<head>
<title>flex布局</title>
<style>
.main{
height: 60px;
display: flex;
}
.left,
.right{
height: 100%;
width: 200px;
background-color: #ccc;
}
.content{
flex: 1;
background-color: #eee;
}
</style>
</head>
<body>
<div class="main">
<div class="left"></div>
<div class="content"></div>
<div class="right"></div>
</div>
</body>
</html>
grid布局
- 基础巩固
grid:CSS 所有网格容器的简写属性
grid-template-rows / grid-template-columns :设置列和行的尺寸。
- 实现方法
左右两栏设置宽度,中间栏宽度auto
<!DOCTYPE html>
<html lang="en">
<head>
<title>grid布局</title>
<style>
body {
display: grid;
grid-template-columns: 200px auto 200px;
grid-template-rows: 60px;
}
.left,
.right {
background-color: #ccc;
}
.content {
background-color: #eee;
}
</style>
</head>
<body>
<div class="left"></div>
<div class="content"></div>
<div class="right"></div>
</body>
</html>
margin负值法
- 原理解释
- 实现方法:
左右两栏均左浮动,外层盒子左浮动,
中间栏设置左右两栏宽度的margin值,
左栏设置margin -100%(向左移动整个屏幕的距离),
右栏设置 margin值为负的盒子宽度。
<!DOCTYPE html>
<html lang="en">
<head>
<title>margin负值</title>
<style>
.left,
.right {
float: left;
width: 200px;
height: 60px;
background-color: #eee;
}
.left {
margin-left: -100%;
}
.right {
margin-left: -200px;
}
.main {
width: 100%;
float: left;
height: 60px;
}
.content {
height: 60px;
margin: 0 200px;
background-color: #ccc;
}
</style>
</head>
<body>
<div class="main">
<div class="content"></div>
</div>
<div class="left"></div>
<div class="right"></div>
</body>
</html>
自身浮动
<!DOCTYPE html>
<html lang="en">
<head>
<title>自身浮动法</title>
<style>
.left,
.right {
height: 60px;
width: 200px;
background-color: #eee;
}
.left {
float: left;
}
.right {
float: right;
}
.content{
height: 60px;
background-color: #ccc;
margin: 0 200px;
}
</style>
</head>
<body>
<div class="left"></div>
<div class="right"></div>
<div class="content"></div>
</body>
</html>
绝对定位
左右两栏绝对定位,分别定位到盒子的两侧,中间栏采用margin值撑开盒子
注意:采用定位时,浏览器默认的padding或margin值会影响布局,需要初始化样式 margin:0;padding:0;
圣杯布局 (⭐手写)
两边固定,中间自适应,且中间栏放在文档流的前面,率先渲染
基本的dom结构(注意center需要排在第一个位置)
<div class="header">header</div>
<div class="container">
<div class="center column">center</div>
<div class="left column" >left</div>
<div class="right column" >right</div>
</div>
<div class="footer">footer</div>
或者
<section class="container">
<article class="center"><br /><br /><br /></article>
<article class="left"><br /><br /><br /></article>
<article class="right"><br /><br /><br /></article>
</section>
<br> 标签插入一个简单的换行符
- 定位+浮动
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<style type="text/css">
* {
margin: 0;
padding: 0;
}
.container {
border: 1px solid black;
/* 防止容器盒子高度塌陷和给之后的左、右浮动元素预留位置 */
overflow: hidden;
padding: 0px 100px;
min-width: 100px;
}
.left {
background-color: greenyellow;
/* 保证之后的"margin-left"属性可以将自身拉到上一行 */
float: left;
/* 固定宽度 */
width: 100px;
/* 将元素向左移动属性值的单位,100%相对于父容器计算 */
margin-left: -100%;
/* 相对定位,需要将自身再向左移动自身的宽度,进入容器的"padding-left"区域 */
position: relative;
/* 自身的宽度,刚好进入容器的"padding-left"区域 */
left: -100px;
}
.center {
background-color: darkorange;
float: left;
width: 100%;
}
.right {
background-color: darkgreen;
float: left;
width: 100px;
margin-left: -100px;
position: relative;
left: 100px;
}
</style>
</head>
<body>
<section class="container">
<article class="center"><br /><br /><br /></article>
<article class="left"><br /><br /><br /></article>
<article class="right"><br /><br /><br /></article>
</section>
</body>
</html>
magin-left:-100%
这个百分比是以父元素内容长度的百分比,该父元素内容长度需要去除padding magin border。由于长度设置为了100%,需要一整行的宽度补偿margin,则移到最左边。
magin-left:-100px
margin负值会改变元素占据的空间,及移到父元素的最左边,并且该子元素width即为100px
单位⭐⭐⭐
- 绝对长度单位:px 像素
- 百分比: %
- 相对父元素字体大小单位: em
- 相对于根元素字体大小的单位: rem
- 相对于视口*宽度的百分比(100vw即视窗宽度的100%): vw
- 相对于视口*高度的百分比(100vh即视窗高度的100%): vh
pacity: 0、visibility: hidden、display: none⭐⭐⭐
区别 | pacity: 0 | visibility: hidden | display: none |
页面布局 | 不改变 | 不改变 | 改变 |
触发事件 | 能触发 | 不能触发 | 不能触发 |
img的 title 和 alt 有什么区别⭐
- 通常当鼠标滑动到元素上的时候显示
alt(alternative)
是<img>
的特有属性,是图片内容的等价描述,用于图片无法加载显示、读屏器阅读图片。可提图片高可访问性,除了纯装饰图片外都必须设置有意义的值,搜索引擎会重点分析。
行内元素、块级元素和行内块元素⭐⭐⭐
display:inline;// 转换为行内元素
display:block;// 转换为块级元素
display:inline-block// 转换为行内块元素
从 HTML 的角度来讲,标签分为:
- 文本级标签:p、span、a、b、i、u、em
- 容器级标签:div、h系列、li、dt、dd
行内元素:除了p之外,所有的文本级标签,都是行内元素,p是个文本级,但是是个块级元素
块级元素:所有的容器级标签都是块级元素,还有p标签
块标签:div、h1~h6、ul、li、table、p、br、form。
特征:独占一行,换行显示,可以设置宽高,可以嵌套块和行
行标签:span、a、img、textarea、select、option、input。
特征:只有在行内显示,不会自动进行换行,内容撑开宽、高,不可以设置宽、高(img、input、textarea等除外)。
- 对 margin 仅设置左右方向有效,上下无效,padding 设置上下左右都有效
溢出转省略 (⭐手写)⭐⭐
单行多行,都要overflow: hidden;
单行
定元素内的空白处理:white-space:nowrap; 文本不进行换行;默认值normal
overflow: hidden;
text-overflow:ellipsis; //ellipsis;省略
white-space: nowrap; //nowrap 不换行
多行
1.-webkit-line-clamp用来限制在一个块元素显示的文本的行数。 为了实现该效果,它需要组合其他的WebKit属性。常见结合属性:
2.display: -webkit-box; 必须结合的属性 ,将对象作为弹性伸缩盒子模型显示 。
3.-webkit-box-orient 必须结合的属性 ,设置或检索伸缩盒对象的子元素的排列方式 。
IE不兼容
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
.text2{
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
</style>
</head>
<body>
<div class="text2">
这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话
这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话
这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话这是一句话
</div>
</body>
</html>
JavaScript
语言区别
- 面向过程:通过函数一步一步实现这些步骤,接着依次调用即可
优点:性能上它是优于面向对象的,因为类在调用的时候需要实例化,开销过大。
缺点:不易维护、复用、扩展
用途:单片机、嵌入式开发、Linux/Unix等对性能要求较高的地方
- 面向对象:将数据与函数绑定到一起,进行封装减少了重复代码的重写过程
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护 。
缺点:性能比面向过程低
(多态:同类不同对象)
- 基于原型的面向对象:js
- 比喻:原型(原始吸血鬼),被传染的吸血鬼
前端,追求灵活性,
- 基于类的面向对象:c++,Java,Python
类是模具,对象是实体
多用于服务端,更追求稳定性
比喻:量产的机器人
BOM,DOM,文档,对象,模型
BOM,
Browser Object Model浏览器对象模型,是JavaScript的组成之一,它提供了独立于内容与浏览器窗口进行交互的对象,使用浏览器对象模型可以实现与HTML的交互。它的作用是将相关的元素组织包装起来,提供给程序设计人员使用,从而降低开发人员的劳动量,提高设计Web页面的能力。
window : alert() , prompt() , confirm() , setInterval() , clearInterval() , setTimeout() , clearTimeout() ;
history : go(参数) , back() , foward() ;
location : herf属性.
1、window.location.href = '你所要跳转到的页面'; 2、window.open('你所要跳转到的页面’); 2、window.history.back(-1):返回上一页 4、window.history.go(-1/1):返回上一页或下一页五、 3、history.go("baidu.com");
4、window.print() 直接掉用打印窗口可以用来拔面试题。
DOM,全称Document Object Model 文档对象模型。JS中通过DOM来对HTML文档进行操作
文档是整个的HTML网页文档
将网页中的每一个部分都转换为了一个对象
使用模型来表示对象之间的关系,方便获取对象
ES6新增
- 数据类型:基本数据类型Symbol,引用数据类型Set ,Map
- 运算符:变量的解构赋值,对象和数组新增了扩展运算符
- 块级作用域:let,const
- 原生提供 Proxy 构造函数,用来生成 Proxy 实例
- 定义类的语法糖(class)
- 模块化import/export
- 生成器(Generator)和遍历器(Iterator)
数据类型
基本数据类型
ES5:Null,Undefined,Number,String,Boolean
ES6新增:Symbol(表示独一无二的值)
ES10新增:BigInt(表示任意的大整数)
let bnum=1684424684321231561n //方式1:数组后加n
bunm=BigInt("1684424684321231561")//方式2:调用BigInt
存储在栈(大小固定,先进后出)
引用数据类型
Object,function,Array,Date,RegExp,ES6新增:Set,MAP
地址存储在栈,内容存储在堆(树形结构,队列,先进先出)
声明和定义
变量声明不开辟内存,只是告诉编译器,要声明的部分存在,要预留部分的空间。var i;
变量定义开辟内存。 var i=123;
Null,NaN,Undefined
null:空对象,一般作为对象的初值
Nan:浮点数中表示未定义或不可表示的值,例如0/0、∞/∞、∞/−∞、−∞/∞、−∞/−∞
undefined:未定义,声明但未定义,例如,形参未传参,获取return的函数返回,对象属性名不存在
toString,valueOf
javascript中所有数据类型都拥有valueOf和toString这两个方法,null和undefined除外
- valueOf偏向于运算,toString偏向于显示
对象字面量表达式是加 ():({}).toString()
- valueOf:除了date其他的都是返回数据本身
==,===,Object.is()
- ==:自动数据类型转换
强制转换规则
- string和number,string->number,
- 其他类型和boolean,bool->number
- 对象和非对象,对象先调用 ToPrimitive 抽象操作(调用
valueOf()或
toString()
) - null==undefined值转为Boolean值false
- NaN!=NaN
- ===:严格模式,不进行自动数据类型转换,比较的是栈中值(即基本数据类型的值,或者引用数据类型的地址)
- Object.is():在===基础上特别处理了NaN,-0,+0,保证-0与+0不相等,但NaN与NaN相等
Object.is(+0,-0) //false
Object.is(NaN,NaN) //true
typeof运算符,instance of运算符,isPrototypeOf()
方法,constructor,Object prototype
typeof:判断 基本数据类型
instance of:判断 引用数据类型,在其原型链中能否找到该类型的原型
isPrototypeOf()
:在表达式 "object instanceof AFunction
"中,object
的原型链是针对 AFunction.prototype
进行检查的,而不是针对 AFunction
本身。
Foo.prototype.isPrototypeOf(baz)
constructor:判断 所有数据类型(不包含继承引用数据类型的自定义类型)
(数据).constructor === 数据类型
Object.prototype.toString.call():Object 对象的原型方法 toString 来判断数据类型:
instance of (⭐手写)
第一个实例参数是否在第二个函数参数的原型链上
- 获取首个对象参数的原型对象
- 获取Fn函数的原型对象
- 进入死循环,当两个参数的原型对象相等时返回true
- 当两个参数的原型对象不相等时获取首个对象参数原型的原型并且循环该步骤直到null时返回false
const _instanceof = (target, Fn) => {
let proto = target.__proto__
let prototype = Fn.prototype
while(true) {
if(proto === Fn.prototype) return true
if(proto === null) return false
proto = proto.__proto__
}
}
const _instanceof = (target, Fn) => {
return Fn.prototype.isPrototypeOf(target);
}
new (⭐手写)
"_new"函数,该函数会返回一个对象,
该对象的构造函数为函数参数、原型对象为函数参数的原型,核心步骤有:
- 创建一个新对象
- 获取函数参数
- 将新对象的原型对象和函数参数的原型连接起来
- 将新对象和参数传给构造器执行
- 如果构造器返回的不是对象,那么就返回第一个新对象
const _new = function() {
const object1 = {}
const Fn = [...arguments].shift()
object1.__proto__ = Fn.prototype
const object2 = Fn.apply(object1, arguments)
return object2 instanceof Object ? object2 : object1
}
类型转换
- 转换为数字:
Number():可以把任意值转换成数字,如果要转换的字符串中有不是数字的值,则会返回NaN
parseInt(string,radix):解析一个字符串并返回指定基数的十进制整数,radix是2-36之间的整数,表示被解析字符串的基数。
parseFloat(string):解析一个参数并返回一个浮点数
隐式转换:
-
let str = '123'
-
let res = str - 1 //122
-
str+1 // '1231'
-
+str+1 // 124
- 转换为字符串
.toString() ⚠️注意:null,undefined不能调用
String() 都能转
隐式转换:当+两边有一个是字符串,另一个是其它类型时,会先把其它类型转换为字符串再进行字符串拼接,返回字符串
转换为布尔值
Boolean():0, ''(空字符串), null, undefined, NaN会转成false,其它都是true
隐式转换 !!
type of null
typeof null 的结果是Object。
在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits)
000: object - 当前存储的数据指向一个对象。
null 的值是机器码 NULL 指针(null 指针的值全是 0)
那也就是说null的类型标签也是000,和Object的类型标签一样,所以会被判定为Object。
事件
文档和浏览器窗口中发生的特定交互
事件流
页面接受事件的先后顺序
事件类型
- 事件捕获:由外往内,从事件发生的根节点开始,逐级往下查找,一直到目标元素。
- 事件冒泡:由内往外,从具体的目标元素触发,逐级向上传递,直到根节点。
事件委托(⭐手写)
又名事件代理。事件委托就是利用事件冒泡,就是把子元素的事件都绑定到父元素上。
好处:提高性能,减少了事件绑定,从而减少内存占用
阻止事件冒泡:event.stopPropagation() .stop修饰符
1. 给"ul"标签添加点击事件
2. 当点击某"li"标签时,该标签内容拼接"."符号。如:某"li"标签被点击时,该标签内容为".."
注意:
1. 必须使用DOM0级标准事件(onclick)
target表示当前触发事件的元素
currentTarget是绑定处理函数的元素
只有当事件处理函数绑定在自身的时候,target才会和currentTarget一样
<ul>
<li>.</li>
<li>.</li>
<li>.</li>
</ul>
<script type="text/javascript">
document.querySelector('ul').onclick=event=>{
event.target.innerText+='.'
}
</script>
发布订阅模式(⭐手写)
完成"EventEmitter"类实现发布订阅模式。
1. 同一名称事件可能有多个不同的执行函数:构造函数中创建”events“对象变量存放所有的事件
2. 通过"on"函数添加事件:订阅事件。当总事件中不存在此事件时创建新的事件数组,当存在时将”fn“函数添加在该事件对应数组中
3. 通过"emit"函数触发事件:用于发布事件,遍历该事件下的函数数组并全部执行
class EventEmitter {
constructor() {
this.events = {}//二维,events' funcs
}
on(event, fn) {
if(!this.events[event]) {
this.events[event] = [fn]
} else {
this.events[event].push(fn)
}
}
emit(event) {
if(this.events[event]) {
this.events[event].forEach(callback => callback())
}
}
}
观察者模式(⭐手写)
"Observerd"类实现观察者模式。要求如下:
"Observer"为观察者,"Observerd"为被观察者
- 被观察者构造函数声明三个属性分别为"name"用于保存被观察者姓名、"state"用于保存被观察者状态、"observers"用于保存观察者们
- 被观察者创建"setObserver"函数,用于保存观察者们,该函数通过数组的push函数将观察者参数传入"observers"数组中
- 被观察者创建"setState"函数,设置该观察者"state"并且通知所有观察者,该函数首先通过参数修改被观察者的"state"属性,然后通过遍历"observers"数组分别调用各个观察者的"update"函数并且将该被观察者作为参数传入
- 观察者创建"update"函数,用于被观察者进行消息通知,该函数需要打印(console.log)数据,数据格式为:小明正在走路。其中"小明"为被观察者的"name"属性,"走路"为被观察者的"state"属性
class Observerd {
constructor(name) {
this.name = name
this.state = '走路'
this.observers = []
}
setObserver(observer) {
this.observers.push(observer)
}
setState(state) {
this.state = state
this.observers.forEach(observer => observer.update(this))
}
}
class Observer {
constructor() {
}
update(observerd) {
console.log(observerd.name + '正在' + observerd.state)
}
}
Object.create (⭐手写)
该函数创建一个新对象,使用现有的对象来提供新创建的对象的proto,核心步骤有:
- 创建一个临时函数
- 将该临时函数的原型指向对象参数
- 返回该临时对象的实例
参考答案:
Object.create法创建一个新对象,使用现有的对象来提供新创建的对象的proto。
1 2 3 4 5 6 |
|
寄生组合式继承(⭐手写)
通过寄生组合式继承使"Chinese"构造函数继承于"Human"构造函数。要求如下:
1. 给"Human"构造函数的原型上添加"getName"函数,该函数返回调用该函数对象的"name"属性
2. 给"Chinese"构造函数的原型上添加"getAge"函数,该函数返回调用该函数对象的"age"属性
- 在"Human"构造函数的原型上添加"getName"函数
- 在”Chinese“构造函数中通过call函数借助”Human“的构造器来获得通用属性
- Object.create函数返回一个对象,该对象的__proto__属性为对象参数的原型。此时将”Chinese“构造函数的原型和通过Object.create返回的实例对象联系起来
- 最后修复"Chinese"构造函数的原型链,即自身的"constructor"属性需要指向自身
- 在”Chinese“构造函数的原型上添加”getAge“函数
function Human(name) {
this.name = name
this.kingdom = 'animal'
this.color = ['yellow', 'white', 'brown', 'black']
}
Human.prototype.getName = function() {
return this.name
}
function Chinese(name,age) {
Human.call(this,name)
this.age = age
this.color = 'yellow'
}
//返回的对象__proto__属性为对象参数的原型
Chinese.prototype = Object.create(Human.prototype)
//修复"Chinese"构造函数的原型链,即自身的"constructor"属性需要指向自身
Chinese.prototype.constructor = Chinese
Chinese.prototype.getAge = function() {
return this.age
}
Object.freeze (⭐手写)
Object.freeze = writable: false + Object.seal = writable: false + Object.preventExtensions + configable: false
- Symbol 类型作为 key 值的情况,也要冻结
- 只冻结对象自有的属性(使用 for ... in 会把原型链上的可枚举属性遍历出来)。
- 注意不可扩展性(不能添加新属性,使用 Object.preventExtensions() 或 Object.seal() 实现,同时也相当于把原型链冻结)。
key:
- Object.getOwnPropertyNames/Symbol
- forEach
- Object.defineProperty
- configurable,writable
const _objectFreeze = object => {
if(typeof object !== 'object' || object === null) {
throw new TypeError(`the ${object} is not a object`)
}
const keys = Object.getOwnPropertyNames(object)
const symbols = Object.getOwnPropertySymbols(object)
;[...keys, ...symbols].forEach(key => {
Object.defineProperty(object, key, {
configurable: false,
writable: false,
})
})
Object.preventExtensions(object)
}
封装事件绑定
绑定事件的元素.addEventListener(事件类型,执行函数,true/false) 默认值为false(即 使用事件冒泡)true 事件捕获
document.addEventListener("click", function(){
document.getElementById("demo").innerHTML = "Hello World";
});
执行上下文/作用域和作用链
作用域就是一个变量可以使用的范围
C/C++中有块级作用域,变量在声明它们的代码段之外是不可见的
javascript的作用域是相对函数而言的,可以称为函数作用域
全局执行上下文:只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象。
函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。
js为每一个执行环境关联了一个变量对象。环境中定义的所有变量和函数都保存在这个对象中。
全局执行环境被认为是window对象
这样由多个执行上下文的变量对象构成的链表就叫做作用域链,从某种意义上很类似原型和原型链。
当前作用域外的变量都是自由变量,一个变量在当前作用域没有定义,但是被使用了,就会向上级作用域
作用域链和原型继承查找时的区别:
查找一个普通对象的属性,但是在当前对象和其原型中都找不到时,会返回undefined
查找的属性在作用域链中不存在的话就会抛出ReferenceError。
this
若是在全局环境(例如,普通函数,匿名函数)中,则this指向window;( 严格模式下this会指向 undefined)
在对象里调用的this,指向调用函数的那个对象,
JS预解析(变量提升)
预编译/解析:JS代码在执行前,浏览器会对js代码进行扫描,默认的把所有带var和function声明的变量进行定义,创建执行上下文,初始化一些代码执行时需要用到的对象。
var,let 和 const关键字
在 ES6 之前,JavaScript 只有两种作用域: 全局变量 与 函数内的局部变量。
ES6新增,块级作用域(由大括号包裹,比如:if(){},for(){}等)
- var:可以跨块访问, 不能跨函数访问,允许重复声明,变量提升
- let、const:只能在块作用域里访问,不允许在相同作用域中重复声明,不存在变量提升
- const :声明一个只读的常量,使用时必须初始化(即必须赋值),一旦声明,常量的值就不能改变,(即,栈中的值不能变,引用类型,内存地址不能修改,可以修改里面的值。)。
原型链
console.log(Person.prototype);
// {constructor: ƒ}
// constructor: ƒ Person()
// arguments: null
// caller: null
// length: 0
// name: "Person"
// prototype: {constructor: ƒ}
// __proto__: ƒ ()
// [[FunctionLocation]]: 09-原型对象.html:8
// [[Scopes]]: Scopes[1]
// __proto__: Object
(引用类型统称为object类型)
所有引用类型
都有一个__proto__(隐式原型)
属性,属性值是一个普通的对象
所有函数
都有一个prototype(原型)
属性,属性值是一个普通的对象
构造函数和类同名
_ _ proto _ _
person.prototype.isPrototypeOf(stu)
只要调用对象在传入对象的原型链上都会返回true
首先,fn的构造函数是Foo()。所以:
fn._ _ proto _ _=== Foo.prototype
又因为Foo.prototype是一个普通的对象,它的构造函数是Object,
Foo.prototype=object
所以:
Foo.prototype._ _ proto _ _=== Object.prototype
【原型和原型链】什么是原型和原型链_TowYingWang的博客-CSDN博客_原型和原型链【原型和原型链】什么是原型和原型链_TowYingWang的博客-CSDN博客_原型和原型链
Iterator,for in,for of,forEach,map循环遍历
Iterator
一种接口,为各种不同的数据结构提供统一的访问机制
例如Array.prototype[@@iterator]()
Array
对象的 @@iterator
方法实现了迭代协议,并允许数组被大多数期望可迭代的语法所使用,例如展开语法和 for...of 循环。它返回一个迭代器,生成数组中每个索引的值。
for in
["a", "b", "c", "d"];for…in 循环读取键名 // 0 1 2 3
适用于遍历对象的可枚举属性
无法遍历 symbol 属性 可以遍历到公有中可枚举的
使用 for…in 遍历时,还需要使用 hasOwnProperty() 方法来判断属性是否来自对象本身,并避免遍历原型链上的属性。
const object1 = {};
object1.property1 = 42;
console.log(object1.hasOwnProperty('property1'));
// Expected output: true
console.log(object1.hasOwnProperty('hasOwnProperty'));
// Expected output: false
for of
["a", "b", "c", "d"];for…of 循环读取键值// a b c d
支持迭代协议的数据结构(数组、字符串、Set、Map 等),不包括对象。
对于字符串,类数组,类型数组的迭代,循环内部调用的是数据结构的Symbol.iterator
方法。
for...of 不能循环普通的对象,需要通过和 Object.keys()搭配使用
const object1 = {
a: 'somestring',
b: 42,
c: false
};
console.log(Object.keys(object1));
// Expected output: Array ["a", "b", "c"]
forEach
arr.forEach(value[,index,默认隐藏参数arr])
适用于需要知道索引值的数组遍历,但是不能中断( break 和 return )
如果需要跳出循环可以使用 some() 或 every() 方法
const isBelowThreshold = (currentValue) => currentValue < 30;
const array1 = [1, 30, 39, 29, 10, 13];
array1.forEach(element => console.log(element));
console.log(array1.every(isBelowThreshold));
// Expected output: false
//是不是至少有 1 个元素
console.log(array1.some(isBelowThreshold));//空数组,则返回false。
// Expected output: true
map
map 方法,基本用法与 forEach 一致
- forEach()方法不会返回执行结果,而是undefined
- map()方法会得到一个新的数组并返回
- 同样的一组数组,map()的执行速度优于 forEach()(map() 底层做了深度优化)
匿名函数和箭头函数
有关键词 function,没有函数名。
//声明匿名函数
let myFun = function( a,b ){
console.info( a+b);
};
//执行
myFun( 10,30 );
//等同于 立即执行匿名函数
(function(a,b){
console.info( a+b );
})(10,30);
连function都没有的匿名函数,箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this
箭头函数不绑定arguments,取而代之用rest参数解决
//传递给getVal函数内的this并不是调用者自身,而是外部的this,即window
this.val = 2;
var obj = {
val: 1,
getVal: () => {
console.log(this.val);
}
}
obj.getVal(); // 2
call、apply、bind改变this
call()和apply()唯一区别:
call()
接受的是一个参数列表
apply()
方法接受的是一个包含多个参数的数组。
var obj1 = {
name: 1,
getName: function (num = '') {
return this.name + num;
}
};
var obj2 = {
name: 2,
};
// 可以理解成在 obj2的作用域下调用了 obj1.getName()函数
console.log(obj1.getName()); // 1
console.log(obj1.getName.call(obj2, 2)); // 2 + 2 = 4
console.log(obj1.getName.apply(obj2, [2])); // 2 + 2 = 4
bind:语法和call一样,区别在于call立即执行,bind等待执行,bind不兼容IE6~8
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用
call (⭐手写)
// 给function的原型上面添加一个 _call 方法
Function.prototype._call = function (context) {
// 判断调用者是否是一个函数 this 就是调用者
if (typeof this !== 'function') {
throw new TypeError('what is to be a function')
}
// 如果有 context 传参就是传参者 没有就是window
context = context || window
// 保存当前调用的函数
context._this = this
// 截取传过来的参数
/*
arguments
a: 1
fn: ƒ fns()
*/
// 通过 slice 来截取传过来的参数
const local = [...arguments].slice(1)
// 传入参数调用函数
let result = context._this(...local)
// 删属性
delete context._this
return result
}
let obj = { a: 1 }
function fns(a, b) {
console.log(a, b);
console.log(this)
}
fns._call(obj, 23, 555)
apply(⭐手写)
// 给function的原型上面添加一个 _apply 方法
Function.prototype._apply= function (context) {
// 判断调用者是否是一个函数 this 就是调用者
if (typeof this !== 'function') {
throw new TypeError('what is to be a function')
}
// 如果有 context 传参就是传参者 没有就是window
context = context || window
// 保存当前调用的函数
context._this = this
// 截取传过来的参数
/*
arguments
a: 1
fn: ƒ fns()
*/
//!!!!!!!!!!!!!!与call的唯一区别!!!!!!!!!!!
// 这里开始判断传入的参数是否存在,此时参数是一个数组形式[thisArg,[传参]]
// 那么如果arguments[1]即传参存在的时候,就是需要传参调用保存的函数
// 如果不存在就直接调用函数
if (arguments[1]) {
result = context._this(...arguments[1])//!!!!将数组展开!!!!
} else {
result = context._this()
}
//!!!!!!!!!!!!!!与call的唯一区别!!!!!!!!!!!
// 删属性
delete context._this
return result
}
let obj = { a: 1 }
function fns(a, b) {
console.log(a, b);
console.log(this)
}
fns._call(obj, 23, 555)
bind(⭐手写)
Function.prototype._bind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('what is to be a function')
}
var _this = this; // 保存调用bind的函数
var context = context || window; // 确定被指向的this,如果context为空,执行作用域的this就需要顶上喽
return function(){
return _this.apply(context, [...arguments].slice(1)); // 如果只传context,则[...arguments].slice(1)为空数组
}
};
var obj = {
name: 1,
getName: function(){
console.log(this.name)
}
};
var func = function(){
console.log(this.name);
}._bind(obj);
func(); // 1
闭包
- 函数内的所有内部函数都共享一个父作用域,因此创建的闭包是共用的。
- 利用闭包隔离作用域的特性可以解决共享作用域的问题
- 闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包
- 使用场景 : 防抖、节流、函数套函数避免全局污染
正则表达式Regular Expression(RegExp) (⭐手写)
字符串搜索模式。
/正则表达式主体/修饰符(可选)
RegExp 对象是一个预定义了属性和方法的正则表达式对象
regexp.test(str)返回Bool
regexp.exec(str)返回匹配的子串 或者 null
常用修饰符
i | ignoreCase 执行对大小写不敏感的匹配。 |
g | global 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。 |
常用字符
\标记下一个字符是特殊字符或文字。例如,"n”和字符"n”匹配。"\n"则和换行字符匹配。
^匹配输入的开头.
$匹配输入的末尾
·匹配除换行字符外的任何单个字符
*匹配前一个字符零或多次。例如,"zo*”与"z”或"zoo”匹配。
+匹配前一个字符一次或多次。例如,"zo+"与"zoo”匹配,但和"z”不匹配。
?匹配前一个字符零或一次。例如,"a?ve?”和"never"中的“"ve”匹配。
x|y 匹配x或y
{n}匹配n次。n是非负整数
{n,} n是一个非负整数。至少匹配n次。例如,"o{2,)"和"Bob”中的"o”不匹配,但和"foooood"中的所有o匹配。"o{1}”与"o+”等效。"o{0,}”和"o*”等效。
{n,m}m和n是非负整数。至少匹配n次而至多匹配 m次。例如,"o{1,3]"和"fooooood”中的前三个o匹配。"o{0,1}”和“o?”等效。
[xyz]匹配括号内的任一字符。例如,"[abc]"和"plain”中的"a”匹配。
[^xyz]匹配非括号内的任何字符。例如,"[^abc]"和“plain”中的"p”匹配。
[a-z]字符范围。和指定范围内的任一字符匹配。例如,"[a-z]”匹配"a"到"z"范围内的任一小写的字母表字符。
[^m-z]否定字符范围。匹配不在指定范围内的任何字符。例如,"[m-z]”匹配不在"m"到"z"范围内的任何字符。
助记:digital
\d匹配数字字符。等价于[0-9]。
\D匹配非数字字符。等价于[^0-9]。
助记:space
\s匹配任何空白,包括空格、制表、换页等。与"[ \fn\rlt\v]”等效。
\S匹配任何非空白字符。与"[^ \fn\rlt\v]”等效。
\w匹配包括下划线在内的任何字字符。与"[A-Za-z0-9_]”等效。
\W匹配任何非字字符。与"[^A-Za-z0-9_]”等效。
合法的URL
URL结构一般包括协议、主机名、主机端口、路径、请求信息、哈希
- 首先必须是以http(s)开头并且可以不包含协议头部信息
- 主机名可以使用"-"符号,所以两种情况都要判断,包含"-"或不包含"-"
- 顶级域名很多,直接判断"."之后是否为字母即可
- 最后判断端口、路径和哈希,这些参数可有可无
域名中只能包含以下字符
1. 26个英文字母
2. "0,1,2,3,4,5,6,7,8,9"十个数字
3. "-"(英文中的连词号,但不能是第一个字符)
https://www.bilibili.com/video/BV1F54y1N74E/?spm_id_from=333.337.search-card.all.click&vd_source=6fd32175adc98c97cd87300d3aed81ea
//开始: ^
//协议: http(s)?:/\/\
//域名: [A-z0-9]+-[A-z0-9]+|[A-z0-9]+
//顶级域名 如com cn,2-6位: [A-z]{2,6}
//端口 数字: (\d+)?
//路径 任意字符 如 /login: (\/.+)?
//哈希 ? 和 # ,如?age=1: (\?.+)?(#.+)?
//结束: $
// https:// www.bilibili com /video/BV1F54y1N74E ?spm..
/^(http(s)?:\/\/)?(([A-z0-9]+-[A-z0-9]+|[A-z0-9]+)\.)+([A-z]{2,6})(:\d+)?(\/.+)?(\?.+)?(#.+)?$/.test(url)
函数
函数的length
属性
将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length
属性将失真。
function fun(a = 1, b, c, d) { }
console.log(fun.length) // 0
函数声明与函数表达式的区别
函数声明会将那个函数提升到最前面(即使你写代码的时候在代码块最后才写这个函数),成为全局函数。
函数声明要指定函数名,而函数表达式不用,可以用作匿名函数。
立即执行函数(iife)
( function( ){ })( )
原理:括号内部不能包含语句,当解析器对代码进行解释的时候,先碰到了(), 然后碰到function关键字
就会自动将()里面的代码识别为函数表达式而不是函数声明。
作用:立即执行函数会形成一个单独的作用域,我们可以封装一些临时变量或者局部变量,避免污染全局变量。
Generator
常用方法
异或运算^
按位异或,相同为0,不同为1
运算法则:
1.交换律(随便换像乘一样):a ^ b ^ c === a ^ c ^ b
2.任何数于0异或为任何数 0 ^ n === n
3.相同的数异或为0: n ^ n === 0
Math
//e=2.718281828459045
Math.E;
//绝对值
Math.abs()
//基数(base)的指数(exponent)次幂,即 base^exponent。
Math.pow(base, exponent)
//max,min不支持传递数组
Math.max(value0, value1, /* … ,*/ valueN)
Math.max.apply(null,array)
apply会将一个数组装换为一个参数接一个参数
null是因为没有对象去调用这个方法,只需要用这个方法运算
//取整
Math.floor() 向下取一个整数(floor地板)
Math.ceil(x) 向上取一个整数(ceil天花板)
Math.round() 返回一个四舍五入的值
Math.trunc() 直接去除小数点后面的值
Number
0B,0O为ES6新增
- 二进制:有前缀0b(或
0B
)的数值,出现0,1以外的数字会报错(b:binary) - 八进制:有前缀0o(或
0O
)的数值,或者是以0后面再跟一个数字(0-7)。如果超出了前面所述的数值范围,则会忽略第一个数字0,视为十进制数(o:octonary) - 注意:八进制字面量在严格模式下是无效的,会导致支持该模式的JavaScript引擎抛出错误
- 十六进制:有前缀0x,后跟任何十六进制数字(0~9及A~F),字母大小写都可以,超出范围会报错
特殊值:
- Number.MIN_VALUE:5e-324
- Number.MAX_VALUE:1.7976931348623157e+308
- Infinity ,代表无穷大,如果数字超过最大值,js会返回Infinity,这称为正向溢出(overflow);
- -Infinity ,代表无穷小,小于任何数值,如果等于或超过最小负值-1023(即非常接近0),js会直接把这个数转为0,这称为负向溢出(underflow)
- NaN ,Not a number,代表一个非数值
- isNaN():用来判断一个变量是否为非数字的类型,如果是数字返回false;如果不是数字返回true。
- isFinite():数值是不是有穷的
var result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(isFinite(result)); //false
typeof NaN // 'number' ---
NaN
不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于Number
NaN === NaN // false ---
NaN
不等于任何值,包括它本身(1 / +0) === (1 / -0) // false ---除以正零得到
+Infinity
,除以负零得到-Infinity
,这两者是不相等的
科学计数法:
对于那些极大极小的数值,可以用e表示法(即科学计数法)表示的浮点数值表示。
等于e前面的数值乘以10的指数次幂
numObj.toFixed(digits)//用定点表示法来格式化一个数值
function financial(x) {
return Number.parseFloat(x).toFixed(2);
}
console.log(financial(123.456));
// Expected output: "123.46"
console.log(financial(0.004));
// Expected output: "0.00"
console.log(financial('1.23e+5'));
// Expected output: "123000.00"
取余是数学中的概念,
取模是计算机中的概念,
两者都是求两数相除的余数
1.当两数符号相同时,结果相同,比如:7%4 与 7 Mod 4 结果都是3
2.当两数符号不同时,结果不同,比如 (-7)%4=-3和(-7)Mod4=1
取余运算,求商采用fix 函数,向0方向舍入,取 -1。因此 (-7) % 4 商 -1 余数为 -3
取模运算,求商采用 floor 函数,向无穷小方向舍入,取 -2。因此 (-7) Mod 4 商 -2 余数为 1
key:((n % m) + m) % m;
Number.prototype.mod = function(n) {
return ((this % n) + n) % n;
}
// 或
function mod(n, m) {
return ((n % m) + m) % m;
}
Map
保存键值对,任何值(对象或者基本类型)都可以作为一个键或一个值。
Map的键可以是任意值,包括函数、对象或任意基本类型。
object的键必须是一个String或是Symbol 。
const contacts = new Map()
contacts.set('Jessie', {phone: "213-555-1234", address: "123 N 1st Ave"})
contacts.has('Jessie') // true
contacts.get('Hilary') // undefined
contacts.delete('Jessie') // true
console.log(contacts.size) // 1
function logMapElements(value, key, map) {
console.log(`m[${key}] = ${value}`);
}
new Map([['foo', 3], ['bar', {}], ['baz', undefined]])
.forEach(logMapElements);
// Expected output: "m[foo] = 3"
// Expected output: "m[bar] = [object Object]"
// Expected output: "m[baz] = undefined"
Set
值的集合,且值唯一
let setPos = new Set();
setPos.add(value);//Boolean
setPos.has(value);
setPos.delete(value);
function logSetElements(value1, value2, set) {
console.log(`s[${value1}] = ${value2}`);
}
new Set(['foo', 'bar', undefined]).forEach(logSetElements);
// Expected output: "s[foo] = foo"
// Expected output: "s[bar] = bar"
// Expected output: "s[undefined] = undefined"
Array
//创建字符串
//join() 方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串,用逗号
或指定的分隔符字符串分隔。如果数组只有一个元素,那么将返回该元素而不使用分隔符。
Array.join()
Array.join(separator)
//################创建数组:
//伪数组转成数组
Array.from(arrayLike, mapFn)
console.log(Array.from('foo'));
// Expected output: Array ["f", "o", "o"]
console.log(Array.from([1, 2, 3], x => x + x));
// Expected output: Array [2, 4, 6]
console.log( Array.from({length:3},(item, index)=> index) );// 列的位置
// Expected output:Array [0, 1, 2]
//################原数组会改变:
arr.reverse()//返回翻转后的数组
// 无函数
arr.sort()//默认排序顺序是在将元素转换为字符串,然后比较它们的 UTF-16
// 比较函数
arr.sort(compareFn)
function compareFn(a, b) {
if (在某些排序规则中,a 小于 b) {
return -1;
}
if (在这一排序规则下,a 大于 b) {
return 1;
}
// a 一定等于 b
return 0;
}
//升序
function compareNumbers(a, b) {
return a - b;
}
//固定值填充
arr.fill(value)
arr.fill(value, start)
arr.fill(value, start, end)
//去除
array.shift() //方法从数组中删除第一个元素,并返回该元素的值。
//unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度
array.unshift(element0, element1, /* … ,*/ elementN)
//粘接,通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。
array.splice(start)
array.splice(start, deleteCount)
array.splice(start, deleteCount, item1)
array.splice(start, deleteCount, item1, item2...itemN)
//################原数组不会改变:
//切片,浅拷贝(包括 begin,不包括end)。
array.slice()
array.slice(start)
array.slice(start, end)
//展平,按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
array.flat()//不写参数默认一维
array.flat(depth)
//过滤器,函数体 为 条件语句
// 箭头函数
filter((element) => { /* … */ } )
filter((element, index) => { /* … */ } )
filter((element, index, array) => { /* … */ } )
array.filter(str => str .length > 6)
//遍历数组处理
// 箭头函数
map((element) => { /* … */ })
map((element, index) => { /* … */ })
map((element, index, array) => { /* … */ })
array.map(el => Math.pow(el,2))
//map和filter同参
//接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
// 箭头函数
reduce((previousValue, currentValue) => { /* … */ } )
reduce((previousValue, currentValue, currentIndex) => { /* … */ } )
reduce((previousValue, currentValue, currentIndex, array) => { /* … */ } )
reduce((previousValue, currentValue) => { /* … */ } , initialValue)
reduce((previousValue, currentValue, currentIndex) => { /* … */ } , initialValue)
array.reduce((previousValue, currentValue, currentIndex, array) => { /* … */ }, initialValue)
//一个“reducer”函数,包含四个参数:
//previousValue:上一次调用 callbackFn 时的返回值。
//在第一次调用时,若指定了初始值 initialValue,其值则为 initialValue,
//否则为数组索引为 0 的元素 array[0]。
//currentValue:数组中正在处理的元素。
//在第一次调用时,若指定了初始值 initialValue,其值则为数组索引为 0 的元素 array[0],
//否则为 array[1]。
//currentIndex:数组中正在处理的元素的索引。若指定了初始值 initialValue,则起始索引号为 0,//否则从索引 1 起始。
//array:用于遍历的数组。
//initialValue 可选
//作为第一次调用 callback 函数时参数 previousValue 的值。
//若指定了初始值 initialValue,则 currentValue 则将使用数组第一个元素;
//否则 previousValue 将使用数组第一个元素,而 currentValue 将使用数组第二个元素。
Array.filter(⭐手写)
Array.prototype._filter = function(Fn) {
if (typeof Fn !== 'function') return
const array = this
const newArray = []
for (let i=0; i<array.length; i++) {
const result = Fn.call(null, array[i], i, array)
result && newArray.push(array[i])
}
return newArray
}
Array.map(⭐手写)
Array.prototype._map = function(Fn) {
if (typeof Fn !== 'function') return
const array = this
const newArray = []
for (let i=0; i<array.length; i++) {
const result = Fn.call(null, array[i], i, array)
//##########与filter的唯一不同
newArray.push(result)
}
return newArray
}
Array.reduce(⭐手写)
Array.prototype._reduce = function(fn,initialValue = 0){
if(typeof fn !== 'function') return;
let res = initialValue
this.forEach((value,index,arr)=>{
res = fn(res,value,index,arr)
})
return res
}
String
str.charAt(index)//获取第n位字符
str.charCodeAt(n)//获取第n位UTF-16字符编码 (Unicode)
String.fromCharCode(num1[, ...[, numN]])//根据UTF编码创建字符串
String.fromCharCode('a'.charCodeAt(0))='a'
str.trim()//返回去掉首尾的空白字符后的新字符串
str.split(separator)//返回一个以指定分隔符出现位置分隔而成的一个数组,数组元素不包含分隔符
const str = 'The quick brown fox jumps over the lazy dog.';
const words = str.split(' ');
console.log(words[3]);
// Expected output: "fox"
str.toLowerCase( )//字符串转小写;
str.toUpperCase( )//字符串转大写;
str.substring(indexStart[, indexEnd]) //提取从 indexStart 到 indexEnd(不包括)之间的字符。
str.substr(start[, length]) //没有严格被废弃 (as in "removed from the Web standards"), 但它被认作是遗留的函数并且可以的话应该避免使用。它并非 JavaScript 核心语言的一部分,未来将可能会被移除掉。
str.indexOf(searchString[, position]) //在大于或等于position索引处的第一次出现。
indexOf (⭐手写)
在函数前加上波浪号,其作用是把函数声明转换为表达式,这样就可以直接运行
~function sayHello(){
console.log('hello');
}()
//Expected output: hello
~ function () {
function myIndexOf(searchStr) {
// 这个也可以正则实现 下面代码
// let reg = new RegExp(searchStr)
// res = reg.exec(this)
// return res === null ? -1 : res.index
let len = this.length
let searchLen=searchStr.length
if (searchLen > len) return -1
// 如果输入的字符串大于要检测的字符串直接 -1
for (var i = 0; i <= len-searchLen; i++) {
if (this.substring(i,searchLen+i) === searchStr) {
return i
}
}
return -1
}
String.prototype.myIndexOf = myIndexOf
}()
let str = 'dwanlghMappaw'
let searchStr= 'hM'
console.log(str.myIndexOf(searchStr));
高阶函数和函数的珂里化Currying
高阶函数:参数 或者 返回值为函数
函数柯里化:返回值为函数,实现多次接收参数最后统一处理的函数编码
作用:能进行部分传值,而传统函数调用则需要预先确定所有实参。如果你在代码某一处只获取了部分实参,然后在另一处确定另一部分实参。
用途:延迟计算、参数复用、动态生成函数(都是闭包的用途)。
function sum(a){
return(b)=>{
return (c)=>{
return a+b+c
}
}
}
Arguments
对象
是所有(非箭头)函数中都可用的局部变量。类似于Array
,但除了 length 属性和索引元素之外没有任何Array
属性。
function add() {
var sum =0,
len = arguments.length;
for(var i=0; i<len; i++){
sum += arguments[i];
}
return sum;
}
add() // 0
add(1) // 1
add(1,2,3,4); // 10
深浅拷贝
基本类型:内存区域存储的是值,不存在深拷贝和浅拷贝
引用类型:内存区域存储的是地址,浅拷贝只拷贝一层(内存地址),而深拷贝是层层拷贝(拷贝内容,新开辟内存)。
深拷贝(⭐手写)
function cloneDeep(arr = {}) {
// 终止递归 判断如果传进来的数据不是 object 或者 传进来的是一个 null 直接返回
if (!arr || typeof arr != 'object' || arr == null) return arr
// 用 instanceof 判断原型链上是否有该类型的原型 是 Array => [] ! Arrays =>{}
let result=arr instanceof Array ? [] : {}
// forin 循环对象的key值
for (const key in arr) {
// 对象 key 赋值 result
result[key] = cloneDeep(arr[key])
}
return result
}
严格模式
严格模式通过抛出错误来消除了一些原有静默错误。
- 严格模式下,不允许给未声明的变量赋值
严格模式修复了一些导致 JavaScript 引擎难以执行优化的缺陷:有时候,相同的代码,严格模式可以比非严格模式下运行得更快。
严格模式禁用了在 ECMAScript 的未来版本中可能会定义的一些语法。
防抖 (⭐手写)
触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
防抖: <input id="input" type="text">
</body>
<script>
// 防抖的核心代码
function debounce(fun,time) {
let flag // 定义状态
return function () {
clearTimeout(flag)// 在执行之前 清除 定时器的 flag 不让他执行
flag = setTimeout(() => {
fun.call(this,arguments)//拿到正确的this对象,即事件发生的dom
}, time)
}
}
let val = debounce(function (val) {
console.log(val)
},1000)
// 监听拿到input输入的值
input.addEventListener('input', function (e) {
val(e.target.value)
})
</script>
</html>
节流(⭐手写)
连续触发事件但是在 n 秒中只执行一次函数。两种方式可以实现,分别是时间戳版和定时器版。
<body>
<button id="button">1秒执行一次</button>
</body>
<script>
/*
定时器版本的
fns 回调函数
time 间隔时间
function throttle(fun, time) {
let flag // 定义一个空状态
return function () { // 内部函数访问外部函数形成闭包
if (!flag) { // 状态为空执行
flag = setTimeout(() => {
fns.apply(this, arguments) // 改变this指向 把 event 事件对象传出去
flag = null
}, time)
}
}
}
*/
function throttle(fun, time) {
let last = 0
return function () {
let now = Date.now()
// 当前的值 减去上一次的值 >= 传过来的事件 执行
if (now - last >= time) {
fun.apply(this, arguments)
last = now
}
}
}
button.onclick = throttle((e) => {
console.log(e)
}, 1000)
</script>
垃圾回收
浏览器的js具有自动垃圾回收机制,垃圾回收机制也就是自动内存管理机制,垃圾收集器会定期的找出那些不在继续使用的变量,然后释放内存,所以将不需要的对象设为null即可。
内存泄漏
如果 那些不再使用的变量,它们所占用的内存 不去清除的话就会造成内存泄漏
造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果。
比如说:
1、闭包:在闭包中引入闭包外部的变量时,当闭包结束时此对象无法被垃圾回收(GC)。
2、DOM:当原有的DOM被移除时,子结点引用没有被移除则无法回收
JS中拥有自动的垃圾回收机制,
宏任务和微任务
js引擎会优先执行微任务,例如:网页加载完毕,但是图片没加载出来
- 微任务microtask(异步):可以理解为task执行完后立刻执行,Promise async/await。
- 宏任务macrotask: setTimeout,setInterval一类的定时事件,Ajax,DOM事件,script 脚本的执行、 I/O 操作、UI 渲染等。
JS延迟加载的方式
JavaScript 是单线程(js不走完下面不会走是因为同步)会阻塞DOM的解析,因此也就会阻塞DOM的加载。所以有时候我们希望延迟JS的加载来提高页面的加载速度。
1.把JS放在页面的最底部(css放顶部,js放底部是框架常见优化)
2.script标签的defer属性:脚本会立即下载但延迟到整个页面加载完毕再执行。该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。
3.是在外部JS加载完成后,浏览器空闲时,Load事件触发前执行,标记为async的脚本并不保证按照指定他们的先后顺序执行, 该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。
4.动态创建script标签,监听dom加载完毕再引入js文件
Vue3
补充:前端面试题(附答案)完善中……_Jet_closer_burning的博客-CSDN博客
React18
React是用来构造用户界面的JS库
在React18中,需要使用两个文件来初始化框架:
-
react.development.js 或 react模块 -> 生成虚拟DOM
-
react-dom.development.js 或 react-dom/client模块 -> Diff算法 + 处理真实DOM
下面就是初始化React程序的代码。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../react.development.js"></script>
<script src="../react-dom.development.js"></script>
</head>
<body>
<div id="app"></div>
<script>
// React对象 -> react.development.js(产生虚拟DOM)
// ReactDOM对象 -> react-dom.development.js(渲染成真实DOM)
//原生DOM获取
let app = document.querySelector('#app');
// root根对象,渲染react的DOM,React18
let root = ReactDOM.createRoot(app);
// React.createElement() -> 创建虚拟DOM
//createElement(标签,属性,内容)
let element = React.createElement('h2', {title: 'hi'}, 'hello world');
root.render(element);
</script>
</body>
</html>
特点
虚拟DOM,组件化设计模式,声明式代码,单向数据流,使用jsx描述信息
声明式编码
- 命令式编程:流程(每一步指令怎么去做)
- 声明式编码:目标,而非流程
因没有指令的概念,所以条件渲染和列表渲染都要通过命令式编程来实现(即JS本身的能力)
map()方法
let app = document.querySelector('#app');
let root = ReactDOM.createRoot(app);
let data = [
{ id: 1, text: 'aaa' },
{ id: 2, text: 'bbb' },
{ id: 3, text: 'ccc' }
];
let element = (
<ul>
{
data.map(v=><li key={v.id}>{v.text}</li>)
}
</ul>
);
root.render(element);
单向数据流
数据主要从父节点传到子节点(通过props),如果父级的某个props改变了,React会重新渲染所有子节点
组件化
可复用的代码可以抽成组件共同使用(UI,方法等)
每个UI块都是一个组件,每个组件都有一个状态。
虚拟DOM(Virtual Dom)(同Vue)
虚拟 DOM,根据模板生成一个js对象(使用createElement,方法),取代真实的 DOM 。
当页面打开时浏览器会解析 HTML 元素,构建一颗 DOM 树,将状态全部保存起来
Vue和React框架都会自动控制DOM的更新,而直接操作真实DOM是非常耗性能的,所以才有了虚拟DOM的概念
React遵循可观察的模式,并监听状态变化。当组件的状态改变时,React更新虚拟DOM树。
缺点:首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢
Diff算法(同Vue)
通过同层的树节点进行比较的高效算法,比较方式:diff整体策略为:深度优先,同层比较
总的来说就是减少DOM,重绘和回流
react生成的新虚拟DOM和旧虚拟DOM的比较规则:
- 如果旧的虚拟DOM中找到了与新虚拟DOM相同的key:
如果内容没有变化,就直接只用之前旧的真实DOM
如果内容发生了变化,就生成新的真实DOM
- 如果旧的虚拟DOM中没有找到与新虚拟DOM相同的key:
根据数据创建新的真实的DOM,随后渲染到页面上
组件属性
每个 React 组件强制要求必须有一个 render()。它返回一个 React 元素,是原生 DOM 组件的表示。
props
对外公开属性,只读
传递数据
<body>
<div id = "div">
</div>
</body>
<script type="text/babel">
// 函数组件
function Welcome(props){
return (
<div>hello world, {props.msg}</div>
);
}
let element = (
<Welcome msg="hi react" />
);
// 类组件
class Person extends React.Component{
render(){
return (
<ul>
//接受数据并显示
<li>{this.props.name}</li>
<li>{this.props.age}</li>
<li>{this.props.sex}</li>
</ul>
)
}
}
//等同
const p = {name:"张三",age:"18",sex:"女"}
ReactDOM.render(<Person {...p}/>,document.getElementById("div"));
//传递数据
ReactDOM.render(<Person name="tom" age = "41" sex="男"/>,document.getElementById("div"));
</script>
传递函数
// 子组件
class Head extends React.Component {
render(){
this.props.getData('子组件的问候~~~')
return (
<div>Head Component</div>
);
}
}
// 父组件
class Welcome extends React.Component {
getData = (data) => {
console.log(data)
}
render(){
return (
<div>
hello world, {this.props.msg}
<br />
<Head getData={this.getData} />
</div>
);
}
}
构造函数获取props
class Foo {
constructor(props){
this.props = props;
}
}
class Bar extends Foo {
constructor(props){
super(props);
console.log(this.props);
}
render(){
console.log(this.props);
return '';
}
}
let props = {
msg: 'hello world'
};
let b = new Bar(props);
b.props = props;
b.render();
多属性传递props
class Welcome extends React.Component {
render(){
//解构
let { msg, username, age } = this.props;
console.log( isChecked );
return (
<div>hello world, {msg}, {username}, {age}</div>
);
}
}
let info = {
msg: 'hi react',
username: 'xiaoming',
age: 20
};
let element = (
<Welcome {...info} />
);
设置props初始值和类型
import PropTypes from 'prop-types'
class Welcome extends React.Component {
static defaultProps = {
age: 0
}
static propTypes = {
age: PropTypes.number
}
...
}
state
组件的私有属性,值是对象(可以包含多个key-value的组合)
通过state的变化设置响应式视图,受控于当前组件
class Welcome extends React.Component {
state = {
msg: 'hello',
count: 0
}
handleClick = () => {
this.setState({
msg: 'hi'
});
}
render(){
console.log('render');
return (
<div>
<button onClick={this.handleClick}>点击</button>
{this.state.msg}, {this.state.count}
</div>
);
}
}
let element = (
<Welcome />
);
refs
React操作原生DOM跟Vue框架是类似的,都是通过ref属性来完成的
class Welcome extends React.Component {
myRef = React.createRef()
handleClick = () => {
//console.log(this.myRef.current); // 原生DOM input
this.myRef.current.focus();//获取焦点
}
render(){
return (
<div>
<button onClick={this.handleClick}>点击</button>
<input type="text" ref={this.myRef} />
</div>
);
}
}
总结
props
公开,单向数据流值,父子组件间的唯一通信,不可改
1.每个组件对象都会有props(properties的简写)属性
2.组件标签的所有属性都保存在props中
3.内部读取某个属性值:this.props.propertyName
state
私有(通过Ajax获取回来的数据,一般都是私有数据),
React 把组件看成是一个状态机(State Machines),
只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。
refs
当需要获取某一个真实的DOM元素来交互,比如文本框的聚焦、触发强制动画等
当需要操作的元素和获取的元素是同一个时,无需ref
受控组件和非受控组件
非受控组件
现用现取,官方建议尽量少用ref,用多了有一定的效率影响
handleSubmit = (event) => {
event.preventDefault() //阻止表单提交
const {username, password} = this//拿到的是form下的username, password结点
alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type="text" name="username"/>
密码:<input ref={c => this.password = c} type="password" name="password"/>
<button>登录</button>
受控组件
将输入维护到state,等需要时再从state取出来
class Login extends React.Component {
//state最好要初始化状态
state = {
username: '', //用户名
password: '' //密码
}
//保存用户名到状态中
saveUsername = (event) => {
this.setState({username: event.target.value})
}
//保存密码到状态中
savePassword = (event) => {
this.setState({password: event.target.value})
}
//表单提交的回调
handleSubmit = (event) => {
event.preventDefault() //阻止表单提交
const {username, password} = this.state//获得的是state下的username,password值
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
事件event
React中的事件都是采用事件委托的形式,所有的事件都挂载到组件容器上,其次event对象是合成处理过的
事件处理的几种方法
import React from 'react'
class Test extends React.Component{
handleClick2(){
console.log('click2')
}
hangleClick4 = () =>{
console.log('click4')
}
render(){
return(
<button onClick={ console.log('click1')}>click1</button>
<button onClick={ this.handleClick2.bind(this)}>click2</button>
<button onClick={ () => {console.log('click3')}>click3</button>
<button onClick={ this.hangleClick4 }>click3</button>
)
}
}
export default Test
事件中this的处理
class Welcome extends React.Component {
handleClick = (ev) => { //推荐 public class fields语法,箭头函数不会创建自己的 this,它只会从自己的作用域链的上一层继承 this。
console.log(this); //对象
console.log(ev);
}
handleClick(){ //不推荐 要注意修正指向
console.log(this); //按钮
}
render(){
return (
<div>
<button onClick={this.handleClick}>点击</button>
hello world
</div>
);
}
}
let element = (
<Welcome />
);
事件传参处理
推荐采用函数的高阶方式,具体代码如下:
class Welcome extends React.Component {
handleClick = (num) => { // 高阶函数
return (ev) => {
console.log(num);
}
}
render(){
return (
<div>
<button onClick={this.handleClick(123)}>点击</button>
hello world
</div>
);
}
}
let element = (
<Welcome />
);
鼠标事件 mouseenter与mouseover区别
mouseenter: 鼠标进入被绑定事件监听元素节点时触发一次,再次触发是鼠标移出被绑定元素,再次进入时。而当鼠标进入被绑定元素节点触发一次后没有移出,即使鼠标动了也不再触发。
mouseover: 鼠标进入被绑定事件监听元素节点时触发一次,如果目标元素包含子元素,鼠标移出子元素到目标元素上也会触发。
mouseenter 不支持事件冒泡 mouseover 会冒泡
跨组件通信
Welcome传递给Title:
let MyContext = React.createContext();
class Welcome extends React.Component {
state = {
msg: 'welcome组件的数据'
}
render(){
return (
<div>
Hello Welcome
<MyContext.Provider value={this.state.msg}>
<Head />
</MyContext.Provider>
</div>
);
}
}
class Head extends React.Component {
render(){
return (
<div>
Hello Head
<Title />
</div>
);
}
}
class Title extends React.Component {
static contextType = MyContext
componentDidMount = () => {
console.log( this.context );
}
render(){
return (
<div>
Hello Title <MyContext.Consumer>{ value => value }</MyContext.Consumer>
</div>
);
}
}
let element = (
<Welcome />
);
通过<MyContext.Provider>
组件携带value
属性进行向下传递的,
那么接收的语法是通过<MyContext.Consumer>
组件。
也可以定义一个静态方法static contextType = MyContext
,这样就可以在逻辑中通过this.context
来拿到同样的值。
生命周期
生命周期钩子函数就是回调函数,
挂载
- constructor():在 React 组件挂载之前,会调用它的构造函数。(注:如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。)
- render(): class 组件中唯一必须实现的方法。
- componentDidMount():在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。
更新
- render(): class 组件中唯一必须实现的方法。
- componentDidUpdate():在更新后会被立即调用。首次渲染不会执行此方法。
卸载
- componentWillUnmount():在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。
可以看到挂载时和更新时都有render
这个方法。这就是为什么state改变的时候,会触发render
重渲染操作。
class Welcome extends React.Component {
state = {
msg: 'hello world'
}
constructor(props){
super(props);
console.log('constructor');
}
componentDidMount = () => {
// react中发起ajax请求的初始操作,在这个钩子中完成
console.log('componentDidMount');
}
componentDidUpdate = () => {
// 等DOM更新后触发的钩子
console.log('componentDidUpdate');
}
componentWillUnmount = () => {
console.log('componentWillUnmount');
}
handleClick = () => {
/* this.setState({
msg: 'hi react'
}); */
//this.forceUpdate();
root.unmount(); // 触发卸载组件
}
render(){
console.log('render');
return (
<div>
<button onClick={this.handleClick}>点击</button>
{ this.state.msg }
</div>
);
}
}
状态提升
多个组件需要共享的状态提升到它们最近的父组件上,在父组件上改变这个状态然后通过props分发给子组件。对子组件操作,子组件不改变自己的状态。
复用组件
Render Props模式
组件之间使用一个值为函数的 prop 共享代码的简单技术。
class MouseXY extends React.Component {
state = {
x: 0,
y: 0
}
componentDidMount = () => {
document.addEventListener('mousemove', this.move)
}
componentWillUnmount = () => {
document.removeEventListener('mousemove', this.move)
}
move = (ev) => {
this.setState({
x: ev.pageX,
y: ev.pageY
});
}
render(){
return (
<React.Fragment>
{ this.props.render(this.state.x, this.state.y) }
</React.Fragment>
);
}
}
class Welcome extends React.Component {
render(){
return (
<MouseXY render={(x, y)=>
<div>
hello world, {x}, {y}
</div>
} />
);
}
}
let element = (
<Welcome />
);
render属性后面的值是一个回调函数,通过这个函数的形参可以得到组件中的数据,从而实现功能的复用。
HOC高阶组件模式
参数为组件,返回值为新组件的函数
function withMouseXY(WithComponent){
return class extends React.Component {
state = {
x: 0,
y: 0
}
componentDidMount = () => {
document.addEventListener('mousemove', this.move)
}
componentWillUnmount = () => {
document.removeEventListener('mousemove', this.move)
}
move = (ev) => {
this.setState({
x: ev.pageX,
y: ev.pageY
})
}
render(){
return <WithComponent {...this.state} />
}
}
}
class Welcome extends React.Component {
render(){
return (
<div>
hello world, { this.props.x }, { this.props.y }
</div>
);
}
}
const MouseWelcome = withMouseXY(Welcome)
let element = (
<MouseWelcome />
);
Hooks
Hook 是 React 16.8 的新增特性,是一个特殊的函数,它可以在不写 class(即不用extends React.Component) 的情况下“钩入” React 特性(即组件化模块的特性)。
useState
等同组件中的setState()
let { useState } = React;//只能在最顶层使用Hook
let Welcome = (props) => {//只能在函数组件中使用Hook
//count的初始值0,设置count的函数
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1)
}
return (
<div>
<button onClick={handleClick}>点击</button>
<div>hello world, { count }</div>
</div>
);
}
与组件中的state一样自动批处理(即合并修改,每次set只渲染一次),可用flushSync
方法消除
(flush synchronization清洗同步)
setCount((count)=> count+1)
setCount((count)=> count+1)
setCount((count)=> count+1)
<div>{ count }</div> // 渲染 3
let { useState } = React;
let { flushSync } = ReactDOM;
let Welcome = (props) => {
const [count, setCount] = useState(0);
const [msg, setMsg] = useState('hello');
const handleClick = () => {
flushSync(()=>{
setCount(count + 1)
})
flushSync(()=>{
setMsg('hi')
})
}
return (
<div>
<button onClick={handleClick}>点击</button>
<div>hello world, { count }, { msg }</div>
</div>
);
}
useState中的值在修改的时候,并不会进行原值的合并处理,所以使用的时候要注意。可利用扩展运算符的形式来解决合并的问题。
const [info, setInfo] = useState({
username: 'xiaoming',
age: 20
})
setInfo({
...info,
username: 'xiaoqiang'
})
如果遇到初始值需要大量运算才能获取的话,可采用惰性初始state,useState()添加回调函数的形式来实现。
const initCount = () => {
console.log('initCount');
return 2*2*2;
}
const [count, setCount] = useState(()=>{
return initCount();
});
这样初始只会计算一次,并不会每次都重新进行计算。
useReducer
useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。
let { useReducer } = React;
let loginState = {//体现整体关联性与统一性**
isLogin: true,
isLogout: false
}
let loginReducer = (state, action) => {
switch(action.type){
case 'login':
return { isLogin: true, isLogout: false }
case 'logout':
return { isLogin: false, isLogout: true }
default:
throw new Error()
}
}
let Welcome = (props) => {
const [ state, loginDispatch ] = useReducer(loginReducer, loginState);
const handleLogin = () => {
loginDispatch({ type: 'login' });
}
const handleLogout = () => {
loginDispatch({ type: 'logout' });
}
return (
<div>
{ state.isLogin ? <button onClick={handleLogout}>退出</button> : <button onClick={handleLogin}>登录</button> }
</div>
);
}
useEffect
在函数组件中执行副作用操作,副作用即:DOM操作、获取数据、记录日志等
代替类组件中的生命周期钩子函数。
如果不传参:相当于render之后就会执行
如果传空数组:相当于componentDidMount
如果传数组:相当于componentDidUpdate
如果返回回调函数:相当于componentWillUnmount,会在组件卸载的时候执行清除操作。
effect 在每次渲染的时候都会执行。React 会在执行当前 effect 之前对上一个 effect 进行清除。
let Welcome = (props) => {
const [count, setCount] = useState(0);
//异步函数,在浏览器渲染DOM后触发的
useEffect(()=>{
// 初始 和 更新 数据的时候会触发回调函数
console.log('didMount or didUpdate');
return ()=>{ // 这里回调函数可以用来清理副作用
console.log('beforeUpdate or willUnmount');
}
})
const handleClick = () => {
//setCount(count + 1);
root.unmount();//卸载
}
return (
<div>
<button onClick={handleClick}>点击</button>
<div>hello world, { count }</div>
</div>
);
}
关注点分离后,改变一个数据后,例如count,那么msg相关的useEffect也会触发,
给useEffect设置第二个参数,只重新触发自己的useEffect回调函数,即响应式的数据
const [count, setCount] = useState(0);
useEffect(()=>{
console.log(count);
}, [count])
const [msg, setMsg] = useState('hello');
useEffect(()=>{
console.log(msg);
}, [msg])
- useEffect()是在渲染之后且屏幕更新之后,,是异步的;
- useLayoutEffect()是在渲染之后但在屏幕更新之前,是同步的。
大部分情况下我们采用useEffect(),性能更好。
但当你的useEffect里面的操作需要处理DOM,并且会改变页面的样式,
就需要useLayoutEffect,否则可能会出现闪屏问题。
useRef
let { useRef } = React;
let Welcome = (props) => {
const myRef = useRef()
...}
等同于
React.createRef()
函数转发
把ref添加到函数组件上,把ref对应的对象转发到子组件的内部元素身上。
let Head = React.forwardRef((props, ref) => {
return (
<div>
hello Head
<input type="text" ref={ref} />
</div>
)
})
let Welcome = (props) => {
const myRef = useRef()
const handleClick = () => {
myRef.current.focus();
}
return (
<div>
<button onClick={handleClick}>点击</button>
<Head ref={myRef} />
</div>
);
}
let count = useRef(0); // 与state类似,有记忆功能,可理解为全局操作
const handleClick = () => {
count.current++;
...
}
useCallback和useMemo
React 使用 Object.is 比较算法 来比较 state。
当组件数据没有变化时,是不会重新渲染试图,如下,cosole.log不会执行
let Welcome = (props) => {
const [ count, setCount ] = useState(0);
const handleClick= () => {
setCount(0);
}
console.log(123);
return (
<div>
<button onClick={handleClick}>点击</button>
hello Welcome { Math.random() }
</div>
);
}
当变化后state与当前state相同时,包括变化前的渲染一共会渲染两次
因为React 可能仍需要在跳过渲染前 渲染该组件。
但React 不会对组件树的“深层”节点进行不必要的渲染
React.memo
避免可以没有必要的重新渲染,类似于类组件中的纯函数概念。
将{ Math.random() } 包装成函数组件
let Welcome = (props) => {
const [ count, setCount ] = useState(0);
const handleClick= () => {
setCount(1);
}
return (
<div>
<button onClick={handleClick}>点击</button>
hello Welcome
<Head count={count} />
</div>
);
}
let Head = React.memo(() => {
return (
<div>hello Head, { Math.random() }</div>
)
})
在渲染期间执行了高开销的计算,则可以使用 useMemo
来进行优化。
useCallback返回一个可记忆的函数,useMemo返回一个可记忆的值,useCallback只是useMemo的一种特殊形式。
let Welcome = (props) => {
const [ count, setCount ] = useState(0);
const handleClick= () => {
setCount(count+1);
}
const foo = () => {}
return (
<div>
<button onClick={handleClick}>点击</button>
hello Welcome
<Head onClick={foo} />
</div>
);
}
let Head = React.memo(() => {
return (
<div>hello Head, { Math.random() }</div>
)
})
当点击按钮的时候,<Head>组件会进行重新渲染,因为每次重新触发<Welcome>组件的时候,后会重新生成一个新的内存地址的foo函数。
通过useCallback和useMemo可以不让foo函数重新生成,使用之前的函数地址
从而减少子组件的渲染,提升性能。
const foo = useCallback(() => {}, [])
const foo = useMemo(()=> ()=>{}, []) // 针对函数
const bar = useMemo(()=> [1,2,3], []) // 针对数组
const foo = useMemo(()=> ()=>{}, [count]) // 第二个参数为依赖项,当count改变时,函数重新创建
自定义Hook
实现函数组件复用
let { useState, useEffect } = React
let useMouseXY = () => {
const [x, setX] = useState(0)
const [y, setY] = useState(0)
useEffect(()=>{
function move(ev){
setX(ev.pageX)
setY(ev.pageY)
}
document.addEventListener('mousemove', move)
//如果返回回调函数:相当于componentWillUnmount,会在组件卸载的时候执行清除操作。
return () => {
document.removeEventListener('mousemove', move)
}
}, [])//如果传空数组:相当于componentDidMount
return {
x,
y
}
}
let Welcome = ()=>{
const {x, y} = useMouseXY()
return (
<div>
hello Welcome, {x}, {y}
</div>
)
}
StrictMode严格模式
StrictMode
是一个用来突出显示应用程序中潜在问题的工具。用于开发环境
与 Fragment
一样,StrictMode
不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。例如:
- 识别不安全的过时的生命周期
- 关于使用过时字符串 ref API 的警告
发布环境下关闭严格模式,以避免性能损失。
Router
路由是根据不同的url地址展示不同的内容或页面,是SPA(单页应用)的路径管理器,
1.一个路由就是一个映射关系(key:value)
2.key为路径, value可能是function或component
Router 用于定义多个路由,当用户定义特定的 URL 时,如果此 URL 与 Router 内定义的任何 “路由” 的路径匹配,则用户将重定向到该特定路由。
路由模式(同Vue)
React中的路由模式跟Vue中一样,分为history和hash模式。默认是hash
Hash模式
hash——即地址栏URL中的#符号(此hash不是密码学里的散列运算)。比如在 http://localhost:8080/#/donate 中,hash的值就是#/donate,我们在浏览器中可以通过BOM中的window.location.hash来获取当前URL的hash值
注:BOM(Browser Object Model) 是指浏览器对象模型,BOM由多个对象组成,其中代表浏览器窗口的Window对象是BOM的顶层对象,其他对象都是该对象的子对象。
history模式
通过在host后,直接添加斜线和路径来请求后端的一种URL模式。
图中pathname变量为/donate,而hash值为空。
原理
- hash通过window.addEventListener监听浏览器的onhashchange()事件变化,查找对应的路由规则
- HTML5的history API监听URL变化,所以有浏览器兼容问题
区别
- 是否向后端传参:
在hash模式中,我们刷新一下上面的网页,可以看到请求的URL为http://localhost:8080/,没有带上#/donate,说明hash 虽然出现 URL 中,#后面的内容是不会包含在http请求中的,对后端完全没有影 响,因此改变 hash 不会重新加载页面。
在history模式中,刷新一下网页,明显可以看到请求url为完整的url,url完整地请求了后端:
前端的 URL 必须和实际向后端发起请求的 URL 一致,否则返回 404 错误
在React中
-
history模式:createBrowserRouter
IE9及以下不兼容,需要由web server支持,在web client这边window.location.pathname被react router解析
-
hash模式:createHashRouter
不需要由web server支持,因为它的只有‘/’path需要由web server支持,而#/react/route URL不能被web server读取,在web client这边window,location.hash被react router解析
history的好处是可以进行修改历史记录,并且不会立刻像后端发起请求。不过如果对于项目没有硬性标准要求,可以直接使用hash模式开发。
基础路由搭建
import { createBrowserRouter, createHashRouter } from 'react-router-dom'
//路由表
export const routes = [];
//路由对象
const router = createBrowserRouter(routes);
export default router;
接下来让路由配置文件与React结合,需要在主入口index.js进行操作,如下:
import { RouterProvider } from 'react-router-dom'
import router from './router';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<RouterProvider router={router}></RouterProvider>
</React.StrictMode>
);
路由表的配置字段如下:
-
path:指定路径
-
element:对应组件
-
children:嵌套路由
//路由表
export const routes = [
{
path: '/',
element: <App />,
children: [
{
path: '',
element: <Home />
},
{
path: 'about',
element: <About />,
children: [
{
path: 'foo',
element: <Foo />,
},
{
path: 'bar',
element: <Bar />,
}
]
}
]
}
];
接下来就是显示路由区域,利用<outlet>组件占位,表明子路由渲染的位置
import React from "react";
import { Outlet, Link } from 'react-router-dom'
function App() {
return (
<div className="App">
<h2>hello react</h2>
<Link to="/">首页</Link> | <Link to="/about">关于</Link>
<Outlet />
</div>
);
}
export default App;
可以看到 <Link>组件用于声明式路由切换使用。同样<outlet>组件也可以给嵌套路由页面进行使用,从而完成二级路由的切换操作。
import React from 'react'
import './About.scss'
import { Outlet, Link } from 'react-router-dom'
export default function About() {
return (
<div>
<h3>About</h3>
<Link to="/about/foo">foo</Link> | <Link to="/about/bar">bar</Link>
<Outlet />
</div>
)
}
重定向路由
访问的URL跳转到另一个URL上,从而实现重定向的需求。
<Navigate>组件就是实现重定向需求的组件。
import { createBrowserRouter, createHashRouter, Navigate } from 'react-router-dom'
children: [
{
index: true,// 默认路由
element: <Navigate to="/about/foo/123" />,
},
{
path: 'foo',
element: <Foo />
},
{
path: 'bar',
element: <Bar />
}
]
自定义全局守卫
全局守卫:包裹根组件,访问根组件下面的所有子组件都要先通过守卫进行操作
在React里面,它不像Vue一样,为我们提供和很多方便的功能,一些功能都需要自己去进行封装比如说路由拦截
在/src/components/BeforeEach.jsx下创建守卫的组件。
import React from 'react'
import { Navigate } from 'react-router-dom'
import { routes } from '../../router';
export default function BeforeEach(props) {
if(true){
return <Navigate to="/login" />
}
else{
return (
<div>{ props.children }</div>
)
}
}
根据判断的结果,是否进入到组件内,还是重定向到其他的组件内。
调用BeforeEach.jsx,index.js通过路由配置文件引入,如下:
export const routes = [
{
path: '/',
element: <BeforeEach><App /></BeforeEach>//包裹根组件APP
}
]
动态路由
根据不同的URL,可以访问同一个组件。在React路由中,通过path字段来指定动态路由的写法。
foo/xxx都能访问到Foo组件,本身就有实现了动态路由
import { Outlet, Link } from 'react-router-dom'
export default function About() {
return (
<div>
<Link to="/about/foo/123">foo 123</Link> | <Link to="/about/foo/456">foo 456</Link>
</div>
)
}
{
path: 'foo/:id',
element: <Foo />
}
id
就是变量名,可以在组件中用useParams
来获取到对应的值。
import { useParams } from 'react-router-dom'
export default function Foo() {
const params = useParams()
return (
<div>Foo, { params.id }</div>
)
}
Redux状态管理库
Redux就像Vue中的Vuex或Pinia是一样专门用于做状态管理的JS库(不是react插件库)。
只不过Redux比较独立,可以跟很多框架结合使用,不过主要还是跟React配合比较好,也是最常见的React状态管理的库。
redux相当于在顶层组件之上又加了一个组件
//state存储共享数据
function counterReducer(state={count: 0}, action) {//reducer修改state
switch(action.type){
case 'inc':
return {count: state.count + state.payload}
default:
return state;
}
}
const store = createStore(counterReducer)
export default store
这样store对象就可以在其他组件中进行使用了,例如在<Bar>组件中。
import React from 'react'
import './Bar.scss'
import { useSelector,useDispatch } from 'react-redux'
export default function Bar() {
const count = useSelector((state)=> state.count)//获取共享状态
const dispatch=useDispatch();//修改共享状态
const handleClick = () => {
dispatch({//dispatch触发Reducer,分发action
type: 'inc',
payload: 5
})
}
return (
<div>
<button onClick={handleClick}>修改count</button>
Bar, { count }
</div>
)
}
在主模块中进行注册。
import { RouterProvider } from 'react-router-dom'
import router from './router';
import { Provider } from 'react-redux'
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>//注册状态管理与React结合,自动完成重渲染
<RouterProvider router={router}></RouterProvider>
</Provider>
</React.StrictMode>
);
应用
从项目的整体看
- 不同身份的用户有不同的使用方式(比如普通用户和管理员)
- 多个用户之间可以协作
- 与服务器大量交互,或者使用了 WebSocket
- View 要从多个来源获取数据
从组件角度看
- 某个组件的状态,需要共享
- 组件有相当大量的,随时间变化的数据
- state 需要有一个单一可靠数据源
React脚手架
开始一个react项目,不用手动配置,直接开发
Angular,Vue,React对比
Angular
框架比较成熟完整,过于庞大,上手难;
React和Vue
相同点 :
创建UI的js库
都是使用了虚拟dom
组件化开发
父子之间通信单项数据流
都支持服务端渲染
不同点:
vue轻量级框架,其核心库只关注视图层,简单小巧、易学易上手;
个人维护项目; 适合于移动端开发;
reacct 的jsx,可读性好
vue的是 template
数据变化,react 手动 setState vue自动响应式处理 proxy object.DefineProperty
react 单向数据流 ,vue双向数据流
react 的 redux mobx
vue 的vuex pinia
MVC、MVP、MVVM模式
MVC (Model View Controller)
- Model(模型):提供数据
- View(视图):显示数据
- Controller(控制器):用户交互
【优点】
耦合性低,方便维护,可以利于分工协作 重用性高
【缺点】
使得项目架构变得复杂,对开发人员要求高
MVP(Model View Presenter)
从MVC演变而来,它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理,
MVVM (Model View View Model)
视图和业务逻辑分开。
View视图层,Model 数据模型,ViewModel 是它们双向绑定的桥梁,自动同步更新
【优点】
相比mvp各层的耦合度更低,一个viewmodel层可以给多个view层共用(一对多),提高代码的可重用性。
*耦合度:模块间依赖的程度。
【缺点】
因为使用了dataBinding,增加了大量的内存开销,增加了程序的编译时间,所以适合轻量级项目。
数据绑定使得 Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题
React和Vue都用了MVVM
React单向数据流:只能由数据层的变化去影响视图层的变化
Vue双向数据绑定
服务器端渲染
基本概念
SSR (server side render)服务端渲染,是指由服务侧(server side)完成页面的DOM结构拼接,然后发送到浏览器,为其绑定状态与事件,成为完全可交互页面的过程。
CSR(client side render)客户端渲染,是指由客户端(client side)JS完成页面和数据的拼接,生成DOM结构再交由浏览器渲染成页面的过程。
SPA(single page application)单页面应用,只是局部更新内容。SPA实现的原理就采用了CSR,页面中所有内容由JS控制,需要浏览器进行JS解析才能显示出来。
SEO(search engine optimization)搜索引擎优化,利用搜索引擎的规则提高网站在有关搜索引擎内的自然排名。
服务器端渲染
前端耗时少。因为后端拼接了html,浏览器只需直接渲染出来。不利于前后端分离,开发效率低。
有利于SEO。因为在后端有完整的html页面,所以爬虫更容易爬取获得信息,更有利于seo。
后端生成静态化文件。即生成缓存片段,这样就可以减少数据库查询浪费的时间了,且对于数据变化不大的页面非常高效 。
占用服务器端资源。无需占用客户端资源。即解析模板的工作完全交由后端来做。
vue,react都是推荐通过服务端渲染来实现路由的。
客户端渲染
浏览器从输入url到渲染页面 过程⭐⭐⭐
查找缓存
- 合成 URL:
浏览区会判断用户输入是合法 URL(Uniform Resource Locator,统一资源定位器),比如用户输入的是搜索的关键词,默认的搜索引擎会合成新的,
如果符合url规则会根据url协议,在这段内容加上协议合成合法的url
- 查找缓存:
网络进程获取到 URL,
先去本地缓存中查找是否有缓存资源,如果有则拦截请求,直接将缓存资源返回给浏览器进程;
否则,进入网络请请求阶段;
- DNS 解析:(域名系统Domain Name System)
DNS 查找数据缓存服务中是否缓存过当前域名信息,有则直接返回;
否则,会进行 DNS 解析返回域名对应的 IP 和端口号,
如果没有指定端口号,http 默认 80 端口,https 默认 443。
如果是 https 请求,还需要建立 TLS 连接;(传输层安全性协议Transport Layer Security)
TCP连接
- 建立 TCP 连接:
TCP 三次握手与服务器建立连接,然后进行数据的传输;
- 发送 HTTP 请求:
浏览器首先会向服务器发送请求行,它包含了请求方法、请求 URI (统一资源标识符Uniform Resource Identifier)和 HTTP 协议的版本;
还会发送请求头,告诉服务器一些浏览器的相关信息,比如浏览器内核,请求域名;
- 服务器处理请求:
服务器首先返回响应头+响应行,响应行包括协议版本和状态码
- 页面渲染:
查看响应头的信息,做不同的处理,比如重定向,存储cookie 看看content-type的值,根据不同的资源类型来用不同的解析方式
渲染详情可见2023年最全前端面试题考点HTML5+CSS3+JS_参宿7的博客-CSDN博客
- 断开 TCP 连接:
数据传输完成,正常情况下 TCP 将四次挥手断开连接。
DNS
因特网使用的命名系统,用来把人们方便记忆的主机名转换为机器方便处理的IP地址。
DNS协议属于应用层协议,一般是运行在UDP协议之上,使用53端口。
解析过程⭐⭐
1.当客户端需要域名解析时,通过本机的DNS客户端构造一个DNS请求报文,以UDP数据报的形式发往本地域名服务器。
2.域名解析有两种方式:递归查询和迭代查询相结合的查询。
由于递归查询给根域名服务器的负载过大,所以一般不使用。
OSI模型和TCP/IP协议⭐
HTTP协议
HTTP:基于TCP/IP的关于数据如何在万维网中如何通信的协议。
Http和Https区别⭐⭐⭐
1.`HTTP` 的URL 以http:// 开头,而HTTPS 的URL 以https:// 开头
2.``HTTP` 无法加密,而HTTPS 对传输的数据进行加密,安全
3.`HTTP` 标准端口是80 ,而 HTTPS 的标准端口是443
4.`在OSI` 网络模型中,HTTP工作于应用层,而HTTPS 的安全传输机制工作在传输层
GET和POST发送请求⭐⭐⭐
HTTP协议中的两种发送请求的方法。
异同
同:GET和POST本质上就是TCP链接
异:
数据包数量:GET产生一个TCP数据包;POST产生两个TCP数据包。
(并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。)
过程:
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
应用:
在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。
在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。
因为GET一般用于查询信息,POST一般用于提交某种信息进行某些修改操作(私密性的信息如注册、登陆)
所以GET在浏览器回退不会再次请求,POST会再次提交请求
因为GET在浏览器回退不会再次请求,POST会再次提交请求
所以GET请求会被浏览器主动缓存,POST不会,要手动设置
GET请求参数会被完整保留在浏览器历史记录里,POST中的参数不会
因为 GET请求参数会被完整保留在浏览器历史记录里
所以GET请求在URL中传送的参数是有长度限制的,而POST没有限制
因为GET参数通过URL传递,POST放在Request body中
所以GET参数暴露在地址栏不安全,POST放在报文内部更安全
POST的content-type数据编码
Content-Type(MediaType),即是Internet Media Type,互联网媒体类型,也叫做MIME类型。(最初MIME是用于电子邮件系统的)
在HTTP协议消息头中,使用Content-Type来表示请求和响应中的媒体类型信息。
它用来告诉服务端如何处理请求的数据,以及告诉客户端(一般是浏览器)如何解析响应的数据,比如显示图片,解析并展示html等等。
Content-Type的格式:type/subtype ;parameter
type:主类型,任意的字符串,如text,如果是*号代表所有;
subtype:子类型,任意的字符串,如html,如果是*号代表所有,用“/”与主类型隔开;
parameter:可选参数,如charset,boundary等。
POST 方法中对发送数据编码的方式,也就是 Content-Type 有四种方式,
application/x-www-form-urlencoded (URL encoded)(默认)
multipart/form-data (键值对型数据)
application/json (Json 类型数据)(最方便)
text/xml (xml)(HTML文档标记)
传统的ajax请求时候,Content-Type默认为"文本"类型。
传统的form提交的时候,Content-Type默认为"Form"类型。
axios传递字符串的时候,Content-Type默认为"Form"类型。
axios传递对象的时候,Content-Type默认为"JSON"类型
http报文
响应报头
常见的响应报头字段有: Server, Connection...。
响应报文
你从服务器请求的HTML,CSS,JS文件就放在这里面
HTTP请求(Request)报文
报文格式为:请求行 – HTTP头(通用信息头,请求头,实体头) – 请求报文主体(只有POST才有报文主体)
HTTP响应(Response)报文
报文格式为:状态行 – HTTP头(通用信息头,响应头,实体头) – 响应报文主体
http版本⭐⭐⭐
1.0->1.1(一次传输多个文件,默认Connection: keep-alive)
http1.x解析基于文本,
http2.0采用二进制格式,新增特性 多路复用、header压缩、服务端推送(静态html资源)
http状态码⭐⭐⭐
状态码是由3位数组成,第一个数字定义了响应的类别,且有五种可能取值:
1xx Informational(信息状态码) 接受请求正在处理
2xx Success(成功状态码) 请求正常处理完毕
3xx Redirection(重定向状态码) 需要附加操作已完成请求
4xx Client Error(客户端错误状态码) 服务器无法处理请求
5xx Server Error(服务器错误状态码) 服务器处理请求出错
常见状态码:
200 响应成功
204 返回无内容
301永久重定向 (请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。)
302临时重定向(服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。)
304资源缓存(自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。)
400 错误请求(请求格式错误,服务器不理解请求的语法。)
422 无法处理(请求格式正确,但是由于含有语义错误,无法响应)
401 未授权(请求要求身份验证。)
403服务器禁止访问
404服务器找不到请求的网页
500 502服务器内部错误
504 服务器繁忙
UDP⭐
应用层协议DNS基于UDP
- `TCP`向上层提供面向连接的可靠服务 ,`UDP`向上层提供无连接不可靠服务。
- `TCP`准确(文件传输),`UDP`实时(视频会议、直播)
- `TCP`仅支持一对一,`UDP`支持一对一,一对多,多对一和多对多交互通信
- `TCP`面向字节流传输,`UDP`面向报文传输
TCP⭐⭐⭐
三次握手
四次挥手
TIME-WAIT:2 MSL (Maximum segment lifetime) 最长报文最大生存时间
流量控制(滑动窗口机制)
让发送方的发送速率不要太快,要让接收方来得及接收。
还可接收的窗口是 rwnd = 400 ”(receiver window) 。
发送方的发送窗口不能超过接收方给出的接收窗口的数值。TCP的窗口单位是字节,不是报文段。
拥塞控制
拥塞:资源供不应求
- 慢开始、拥塞避免
- 快重传、快恢复
keep-alive持久连接
为了能够提升效率,在 HTTP 1.1 规范中把 Connection 头写入了标准,并且默认启用。
浏览器 或 服务器在HTTP头部加上 Connection: keep-alive,TCP 就会一直保持连接。
它会在隔开一段时间之后发送几次没有数据内容的网络请求来判断当前连接是不是应该继续保留。
可能会造成大量的无用途连接,白白占用系统资源
*粘包
在流传输中出现,UDP不会出现粘包,因为它有消息边界
TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
情况
粘包情况有两种,一种是粘在一起的包都是完整的数据包
,另一种情况是粘在一起的包有不完整的包
。
措施
(1)对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push
,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;
(2)对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施
,使其及时接收数据,从而尽量避免出现粘包现象;
(3)由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。分包多发
。
问题
(1)设置方法虽然可以避免发送方引起的粘包,但它关闭了优化算法,降低了网络发送效率,影响应用程序的性能,一般不建议使用。
(2)只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较高时,或由于网络突发可能使某个时间段数据包到达接收方较快,接收方还是有可能来不及接收,从而导致粘包。
(3)避免了粘包,但应用程序的效率较低,对实时应用的场合不适合。
总结
接收方创建一预处理线程,对接收到的数据包进行预处理,将粘连的包分开。实验证明这种方法是高效可行的。
缓存⭐⭐⭐
存储方式
白话:
强制缓存就是根据headers中的信息(expires,cache-control)强行从本地拿缓存,
拿不到再和服务器协商拿缓存,
如果服务器返回304(缓存无更新),就又从本地拿缓存。
否则,将从服务器那拿到的新资源存入浏览器
强制缓存
浏览器在加载资源的时候,会根据本地缓存中的headers中的信息(expires,cache-control)是否要强缓存,如果命中的话,则会使用缓存中的资源,否则继续发送请求。
其中Cache-Control优先级比Expires高。
Exprires的值为服务端返回的数据到期时间。当再次请求时的请求时间小于返回的此时间,则直接使用缓存数据。
但由于服务端时间和客户端时间可能有误差,这也将导致缓存命中的误差,另一方面,Expires是HTTP1.0的产物,故现在大多数使用Cache-Control替代。
协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程
- 协商缓存生效,返回304(缓存无更新),如下
- 协商缓存失效,返回200和请求结果结果,如下
强制缓存优先于协商缓存进行
本地存储
cookie
Cookie的安全规范:
- 不放重要数据,重要数据放Session。
- Cookie 数据加签名。对 Cookie 数据添加签名,这样 Cookie 如果被篡改了,服务端使用的时候通过校验签名就能发现了。
- Cookie数据加密。加密后数据就很难篡改了,但是加解密过程会带来性能损耗,
session
浏览器端第一次发送请求到服务器端,服务器端创建一个Session,同时会创建一个特殊的Cookie(name为JSESSIONID的固定值,value为session对象的ID)(JSESSIONID:j session id)
服务器端根据该Cookie查询Session对象,从而区分不同用户。
共同点
cookie和session都是用来跟踪浏览器用户身份的会话方式。
用于浏览器中存储数据的
浏览器的本地存储主要分为Cookie、WebStorage和IndexDB
(运行在浏览器的非关系型数据库)
其中WebStorage
又可以分为localStorage和sessionStorage
。
Cookie、
localStorage和sessionStorage
同:都是保存在浏览器端、且同源的
异:
cookie
在浏览器和服务器间来回传递
存储地
- cookie数据保存在客户端,session数据保存在服务端
- sessionStorage和localStorage不会把数据发给服务器,仅在本地保存
有效期
-
localStorage
:始终有效,窗口或浏览器关闭也一直保存,本地存储,因此用作持久数据; -
cookie的有效期是可以设置的,默认情况下是关闭浏览器后失效。
-
sessionStorage的有效期是仅存在于当前会话,关闭当前会话或者浏览器后就会失效。
作用域不同
-
sessionStorage
:不在不同的浏览器窗口中共享,即使是同一个页面; -
cookie,localstorage
:在所有同源窗口中都共享;也就是说只要浏览器不关闭,数据仍在
存储地
状态码为灰色的请求则代表使用了强制缓存,请求对应的Size值则代表该缓存存放的位置,分别为from memory cache 和 from disk cache。
- 浏览器读取缓存的顺序为memory –> disk。
- 访问URL–> 200 –> 关闭标签页 –> 重新打开URL–> 200(from disk cache) –> 刷新 –> 200(from memory cache)
内存缓存(from memory cache)
- 快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。
- 时效性:一旦该进程关闭,则该进程的内存则会清空。
硬盘缓存(from disk cache)
重新解析该缓存内容,读取复杂,速度比内存缓存慢。
应用
js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取;
css文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)。
前端基础-浏览器缓存/HTTP缓存机制(面试常考)_浏览器缓存机制 面试_LYFlied的博客-CSDN博客
js的运行环境
脚本语言需要一个解析器才能运行,每一种解析器都是一个运行环境
JavaScript是脚本语言,在不同的位置有不一样的解析器,
浏览器
如写入html的js语言,浏览器是它的解析器角色。
浏览器中的js的用途是操作DOM,浏览器就提供了document之类的内置对象。
Node
需要独立运行的JS,nodejs就是一个解析器
nodejs中的js的用途是操作磁盘文件或搭建http服务器,nodejs就相应提供了fs,http等内置对象。
特点
- 简单:javascript,json进行编码,
- 强大:非阻塞IO,可以适应分块传输数据,较慢的网络环境,尤其擅长高并发访问,
- 轻量:node本身既是代码,又是服务器,前后端使用统一语言;
- 可扩展体:轻松应对多实例,多服务器架构,同时有海量的第三方应用组件。
npm
Node Package Manager ,即:node包管理器
是nodeJS的一个程序包管理和分发的管理工具,npm完全用 JavaScript 写成
数据交换格式
服务器端与客户端之间进行数据传输与交换的格式。
XML
HTML和XML
HTML(HyperText Markup Language超文本标记语言)
XML(Extensiable Markup Language可扩展标记语言)
发展史:HTML->XHTML->XML
HTML缺点:
- 不能自定义标签
- 本身缺少含义
应用
- 程序间的数据通信(例:QQ之间传输文件)
- 配置文件(例:structs-config.xml)
- 小型数据库(例:msn中保存用户聊天记录就是用XML文件)直接读取文件比读数据库快。
JSX
JSX =JavaScript + XML。是一个 JavaScript 的语法扩展。
let element = React.createElement('h2', {title: 'hi'}, [
'hello world',
React.createElement('span', null, '!!!!!!')
]);
JSX写起来就方便很多了,在内部会转换成React.createElement(),然后再转换成对应的虚拟DOM,但是JSX语法浏览器不认识,所以需要利用babel插件进行转义处理。
Babel⭐
JavaScript 编译器
将es6、es7、es8等语法转换成浏览器可识别的es5或es3语法,即浏览器兼容的语法,比如将箭头函数转换为普通函数
将jsx转换成浏览器认的js
JSON
JSON,全称是 JavaScript Object Notation,即 JavaScript对象标记法。
是一种轻量级(Light-Meight)、基于文本的(Text-Based)、可读的(Human-Readable)格式。
名称中虽然带有JavaScript,但是指其语法规则是参考JavaScript对象的,而不是指只能用于JavaScript 语言。
相比 XML(另一种常见的数据交换格式),文件更小
//JSON
{
"name":"参宿", //"key":value,value可以str、num、bool、null、obj,arr。
"id":7, //并列的数据之间用逗号(“,”)分隔
"fruits":["apple","pear","grape"] //数组用[],对象用{}
}
//XML
<root>
<name>"参宿"</name>
<id>7</id>
<fruits>apple</fruits>
<fruits>pear</fruits>
<fruits>grape</fruits>
</root>
JSON解析和生成
var str = '{"name": "参宿","id":7}'; //'JSON字符串'
var obj = JSON.parse(str); //JSON.parse(str)
console.log(obj); //JSON的解析(JSON字符串转换为JS对象)
var jsonstr = JSON.stringify(obj); //JSON.stringify(obj)
console.log(jsonstr); //JSON的生成(JS对象转换为JSON字符串)
JSON.parse(text[, reviver])//reviver函数参数,修改解析生成的原始值,调用时机在 parse 函数返回之前。
JSON.parse('{"p": 5}', function (k, v) {
if(k === '') return v; // 如果到了最顶层,则直接返回属性值,
return v * 2; // 否则将属性值变为原来的 2 倍。
}); // { p: 10 }
JSON.parse('{"1": 1, "2": 2,"3": {"4": 4, "5": {"6": 6}}}', function (k, v) {
console.log(k); // 输出当前的属性名,从而得知遍历顺序是从内向外的,
// 最后一个属性名会是个空字符串。
return v; // 返回原始属性值,相当于没有传递 reviver 参数。
});
// 1
// 2
// 4
// 6
// 5
// 3
// ""
异步
- 异步是通过一次次的循环事件队列来实现的.
- 同步是阻塞式的IO,在高并发环境会是一个很大的性能问题,
所以同步一般只在基础框架的启动时使用,用来加载配置文件,初始化程序什么的.
node是单线程的,网络请求,浏览器事件等操作都需要使用异步的方法。
AJAX
-
ajax 全名 async javascript and XML(异步JavaScript和XML)
前后端交互的重要工具
在无需重新加载整个网页的情况下,能够更新部分网页
原生AJAX创建
//1.创建xhr 核心对象
var xhr=new XMLHttpRequest();
//2.调用open 准备
xhrReq.open(method, url, [async, user, password]);//请求方式,请求地址,true异步,false 同步
xhr.open('post','http://www.baidu.com/api/search',true)
//3.如果是post请求,想要传json格式数据,必须设置请求头。
//xhr.setRequestHeader('Content-Type', 'application/json')
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
//4.调用send 发送请求 (如果不需要参数,就写null)
xhr.send('user=tom&age=10&sex=女')
//5.监听异步回调 onreadystatechange
xhr.onreadystatechange=function(){
if(xhr.readyState==4){ //表示请求完成
if(xhr.status==200){ //状态码 为 200 表示接口请求成功
console.log(xhr.responseText); //responeseText 为相应数据。字符串类型。
var res=JSON.parse(xhr.responseText);
console.log(res);
if(res.code==1){
modal.modal('hide');
location.reload();
}
}
Axios⭐⭐
axios 是一个基于Promise 的ajax的封装,用于浏览器和 nodejs 的 HTTP 客户端。
自动转换JSON数据
console.log(response)
fetch
console.log(response)
console.log(response.json())
axios.get(`http://localhost:3000/search/users?q=${keyWord}`)
get('') (自动默认) 和get(``)(反单引号)均可
但${ }是es6新增的字符串方法
需要配合单反引号完成字符串拼接的功能
axios基本用法_面条请不要欺负汉堡的博客-CSDN博客_axios
Promise⭐⭐⭐
Promise:ES6异步操作的一种解决方案
Promise 构造函数接受一个函数作为参数,该函数是同步的并且会被立即执行,称为起始函数。
起始函数包含两个函数参数 resolve 和 reject,分别表示 Promise 成功和失败的状态。
起始函数执行成功时,它应该调用 resolve 函数并传递成功的结果。
起始函数执行失败时,它应该调用 reject 函数并传递失败的原因。
promise 有三个状态:
pending[待定]初始状态,fulfilled[实现]操作成功,rejected[被否决]操作失败
Promise 构造函数返回一个 Promise 对象,该对象具有以下几个方法:
- then:用于处理 Promise 成功状态的回调函数,参数为resolve传递的参数。
- catch:用于处理 Promise 失败状态的回调函数,参数为reject传递的参数。
- finally:无论 Promise 是成功还是失败,都会执行的回调函数。
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
if (Math.random() < 0.5) {
resolve('success');
} else {
reject('error');
}
}, 1000);
});
promise.then(result => {
console.log(result);
}).catch(error => {
console.log(error);
});
new Promise(function (resolve, reject) {
var a = 0;
var b = 1;
if (b == 0) reject("Divide zero");
else resolve(a / b);
}).then(function (value) {
console.log("a / b = " + value);
}).catch(function (err) {
console.log(err);
}).finally(function () {
console.log("End");
});
new Promise(function (resolve, reject) {
console.log(1111);
resolve(2222);
}).then(function (value) {
console.log(value);
return 3333;
}).then(function (value) {
console.log(value);
throw "An error";
}).catch(function (err) {
console.log(err);
});
then 返回值:传递给 下一个then
new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("First");
resolve();
}, 1000);
}).then(function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("Second");
resolve();
}, 4000);
});
}).then(function () {
setTimeout(function () {
console.log("Third");
}, 3000);
});
then 返回Promise 对象:
那么下一个 then 将相当于对这个返回的 Promise 进行操作,能保证 then 方 可以进行链式调用
Promise.all()哪怕一个请求失败了也能得到其余正确的请求结果的解决方案⭐⭐
await与并行:如果在一个async的方法中,有多个await操作的时候,程序会变成完全的串行操作,一个完事等另一个但是为了发挥node的异步优势,当异步操作之间不存在结果的依赖关系时,可以使用promise.all来实现并行,all中的所有方法是一同执行的。
执行后的结果:async函数中,如果有多个await关键字时,如果有一个await的状态变成了rejected,那么后面的操作都不会继续执行,promise也是同理await的返回结果就是后面promise执行的结果,可能是resolves或者rejected的值使用场景循环遍历方便了代码需要同步的操作(文件读取,数据库操作等)
promise.all并行(同时)执行promise,当其中任何一个promise 出现错误的时候都会执行reject,导致其它正常返回的数据也无法使用
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// Expected output: Array [3, 42, "foo"]
map的每一项都是promise,catch方法返回值会被promise.reslove()包裹,这样传进promise.all的数据都是resolved状态的。
let p1 = Promise.resolve(1)
let p2 = Promise.resolve(2)
let p3 = Promise.resolve(3)
let p4 = Promise.resolve(4)
let p5 = Promise.reject("error")
let arr = [p1,p2,p3,p4,p5];
let all = Promise.all(arr.map((promise)=>promise.catch((e)=>{console.log("错误信息"+e)})))
all.then(res=>{console.log(res)}).catch(err=>console.log(err));
fetch
以前发送请求,使用ajax或者axios,现在还可以使用fetch。这个是window自带的方法,和xhr是一个级别的。(xhr=new XMLHttpRequest())
// url (必须), options (可选)
fetch('/some/url', {method: 'get'})
.then(function(response) {
})
.catch(function(err) {
// 出错了;等价于 then 的第二个参数,但这样更好用更直观 :(
});
第二个then接收的才是后台传过来的真正的数据
fetch('/some/url', { method: 'get'})
// 第一个then 设置请求的格式
.then(e => e.json())
// 第二个then 处理回调
.then((data) => {
<!-- data为真正数据 -->
}).catch(e => console.log("Oops, error", e))
Fetch()方法介绍_小钢炮vv的博客-CSDN博客_fetch
async和await
异步函数实际上原理与 Promise 原生 API 的机制是一模一样的,只不过更便于程序员阅读
函数前面的async
关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise
对象。
await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西,
await关键字后面需要一个promise对象(不是的话就调用resolve转换它
await命令后面对应的是Promise对象或值,如果是值,就会转到一个立即resolve
的Promise对象。async
函数返回的是一个Promise对象,如果结果是值,会经过Promise包装返回。
如果是promise则会等待promaise 返回结果,否则,就直接返回对应的值,
async function asyncFunc() {
let value = await new Promise(
function (resolve, reject) {
resolve("Return value");
}
);
console.log(value);
}
asyncFunc();
await只能在async函数中出现, 普通函数直接使用会报语法错误SyntaxError
await语句后的Promise对象变成reject状态时,那么整个async函数会中断,后面的程序不会继续执行
处理异常的机制将用 try-catch 块实现
async function asyncFunc() {
try {
await new Promise(function (resolve, reject) {
throw "Some error"; // 或者 reject("Some error")
});
} catch (err) {
console.log(err);
// 会输出 Some error
}
}
asyncFunc();
1.看能不能连接服务器
2.拿到真正的数据
async和await的讲解_傲娇的koala的博客-CSDN博客_async await
SPA和MPA
SEO是由英文Search Engine Optimization缩写而来, 中文意译为“搜索引擎优化”。
SEO是指通过对网站进行站内优化和修复(网站Web结构调整、网站内容建设、网站代码优化和编码等)和站外优化,从而提高网站的网站关键词排名以及公司产品的曝光度。
- 爬虫在爬取的过程中,不会去执行js,所以隐藏在js中的跳转也不会获取到
单页Web应用(single page web application,SPA)。
整个应用只有一个完整的页面。
点击页面中的链接不会刷新页面,只会做页面的局部更新。
数据都需要通过ajax请求获取,并在前端异步展现
跨域通信⭐⭐⭐
所谓同源(域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能
同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。
服务器与服务器之间是可以通信的不受同源策略的影响:Nginx反向代理,proxy代理
JSONP跨域
服务器与客户端跨源通信的常用方法。
简单适用,兼容性好(兼容低版本IE)
但只支持get请求,不支持post请求。
- 网页通过添加一个
<script>元素
,向服务器请求 JSON 数据, - 服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。
原生实现
<script src="http://test.com/data.php?callback=dosomething"></script>
// 向服务器test.com发出请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字
// 处理服务器返回回调函数的数据
<script type="text/javascript">
function dosomething(res){
// 处理获得的数据
console.log(res.data)
}
</script>
jQuery ajax
$.ajax({
url: 'http://www.test.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "handleCallback", // 自定义回调函数名
data: {}
});
jQuery是一个 JavaScript 工具库
跨域资源共享(CORS)
(Cross-Origin Resource Sharing) W3C 标准,属于跨源 AJAX 请求的根本解决方法,最常用的一种解决办法
- 普通跨域请求:只需服务器端设置Access-Control-Allow-Origin
- 带cookie跨域请求:前后端都需要进行设置
【前端设置】根据xhr.withCredentials字段判断是否带有cookie
原生ajax
代理服务器
proxy
Nginx反向代理
是最简单的跨域方式,只需要修改 nginx 的配置即可解决跨域问题
- node中间件和nginx反向代理,都是搭建一个中转 nginx 服务器,用于转发请求。
- 请求发给代理服务器,静态页面面和代理服务器是同源的,
- 代理服务器再向后端服务器发请求,服务器和服务器之间不存在同源限制。
正向代理和反向代理
在代替client,还是代替server
向server申请服务的是正向代理,反向代理是server方的行为
websocket协议
HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信
WebSocket 和 HTTP 都是应用层协议,都基于 TCP 协议。
WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
web安全及防护
XSS攻击
跨站脚本攻击Cross-Site Scripting,
代码注入攻击。
当被攻击者登陆网站时就会执行这些恶意代码,这些脚本可以读取 cookie,session tokens,或者其它敏感的网站信息,对用户进行钓鱼欺诈,甚至发起蠕虫攻击等。
- 解决:
url参数使用encodeURIComponent方法转义
尽量不用InnerHtml插入HTML内容
使用特殊符号、标签转义符。
CSRF攻击⭐⭐⭐
跨站请求伪造Cross-site request forgery,在第三方网站中,向被攻击网站发送跨站请求。
利用用户在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
- 解决:添加验证码、使用token
基于token的登录流程⭐⭐
1. 客户端使用用户名跟密码请求登录
2. 服务端收到请求,去验证用户名与密码
3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
SQL注入攻击
SQL命令插入到Web表单递交或输入域名,最终达到欺骗服务器执行恶意的SQL命令。
解决:表单输入时通过正则表达式将一些特殊字符进行转换
DDoS攻击
分布式拒绝服务,全称 Distributed Denial of Service
,其原理就是利用大量的请求造成资源过载,导致服务不可用。
解决:
-
限制单IP请求频率。
-
防火墙等防护设置禁止
ICMP
包等 -
检查特权端口的开放
面试官基本就是照着简历里面的项目技术点切入然后深入展开。
模块化规范
一个模块是实现一个特定功能的一组方法。
- 由于函数具有独立作用域的特点,最原始的写法是使用函数来作为模块,几个函数作为一个模块,但是这种方式容易造成全局变量的污染,并且模块间没有联系。
- 后面提出了对象,通过将函数作为一个对象的方法来实现,但是这种办法会暴露所 有的所有的模块成员,外部代码可以修改内部属性的值。
- 现在最常用的是立即执行函数的写法,通过利用闭包来实现模块私有作用域的建立,同时不会对全局作用域造成污染。
- ES6 :使用 import 和 export 的形式来导入导出模块。
- CommonJS : require 来引入模块,通过 module.exports 定义模块的输出接口。
这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。
但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。
- AMD :Asynchronous Module Definition,这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。
- CMD :Common Module Definition,这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。
AMD是预加载,CMD是懒加载。
AMD是提前执行,CMD是延迟执行。
AMD在对应的加载之前导入,CMD在用的时候导入
懒加载
(Load On Demand)延迟加载、按需加载
可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,减少了服务器的负载。懒加载适用于图片较多,页面列表较长(长列表)的场景中。
require与import的区别和使用
require/import
// CommonJS 的写法
const moduleA = require('moduleA');
const func1 = moduleA.func1;
const func2 = moduleA.func2;
// ES6 的写法
import { func1, func2 } from 'moduleA';
module.exports/export
// commonJS 的写法
var React = require('react');
var Breadcrumbs = React.createClass({
render() {
return <nav />;
}
});
module.exports = Breadcrumbs;
// ES6 的写法
import React from 'react';
class Breadcrumbs extends React.Component {
render() {
return <nav />;
}
};
export default Breadcrumbs;
- 规范:require是CommonJS,AMD规范的模块化语法,import是ECMAScript 6规范的模块化语法,如果要兼容浏览器的话必须转化成es5的语法;CommonJS模块默认export的是一个对象,即使导出的是基础数据类型。
- 本质:require是赋值过程,其实require 的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量,引入复杂数据类型时,数据浅拷贝该对象。。import是解构过程。
- 加载:require是运行时加载,import是编译时加载;
- 位置:require可以写在代码的任意位置,import只能写在文件的最顶端且不可在条件语句或函数作用域中使用;
- 改变:require通过module.exports导出的值就不能再变,import通过export导出的值可以改变;
js的运行环境
脚本语言需要一个解析器才能运行,每一种解析器都是一个运行环境
JavaScript是脚本语言,在不同的位置有不一样的解析器,
浏览器
写入html的js语言,浏览器是它的解析器角色。
浏览器中的js的用途是操作DOM,浏览器就提供了document之类的内置对象。
Node
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,独立于浏览器的运行环境
nodejs中的js的用途是操作磁盘文件或搭建http服务器,nodejs就相应提供了fs,http等内置对象。
特点
- 简单:javascript,json进行编码,
- 强大:非阻塞IO,可以适应分块传输数据,较慢的网络环境,尤其擅长高并发访问,
- 轻量:node本身既是代码,又是服务器,前后端使用统一语言;
- 可扩展体:轻松应对多实例,多服务器架构,同时有海量的第三方应用组件。
npm
Node Package Manager ,即:node包管理器
是nodeJS的一个程序包管理和分发的管理工具,npm完全用 JavaScript 写成
允许用户从NPM服务器下载安装上传包
项目规范
整个项目不再使用class组件,统一使用函数式组件,并且全面用Hooks;
所有的函数式组件,为了避免不必要的渲染,全部使用memo进行包裹;
组件内部的状态,使用useState、业务数据全部放在redux中管理;
函数组件内部逻辑代码基本按照如下顺序编写代码:
组件内部state管理;
redux的hooks代码;
其他hooks相关代码(比如自定义hooks);
其他逻辑代码;
返回JSX代码;
redux代码规范如下:
redux目前我们学习了两种模式, 根据自己的情况选择普通模式还是rtk模式
每个模块有自己独立的reducer或者slice,之后合并在一起;
redux中会存在共享的状态、从服务器获取到的数据状态;
网络请求采用axios
对axios进行二次封装;
所有的模块请求会放到一个请求文件中单独管理;
项目使用AntDesign或者MUI(Material UI)
其他规范在项目中根据实际情况决定和编写
命令
创建项目的方式:create-react-app
npm run start 来运行启动项目并打开页面
项目文件结构
package.json
配置jsconfig.json:这个文件在Vue通过脚手架创建项目时自动生成, React是没有自动生成
package.json相当于说明书,其重点字段:
{
"name": "react-ts-app",
"version": "0.1.0",
"private": true,//是否私有,设置为 true 时,npm 拒绝发布
"dependencies": {//生产环境下,项目运行所需依赖
"@ant-design/icons": "^4.8.0",
...
"@types/node": "^16.18.6",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"antd": "^5.0.4",
"axios": "^1.2.1",
"classnames": "^2.3.2",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.4.4",
"react-scripts": "5.0.1",
"redux": "^4.2.0",
"redux-persist": "^6.0.0",
"sass": "^1.56.1",
"typescript": "^4.9.3",
"web-vitals": "^2.1.4"
},
"scripts": {//执行npm脚本命令简写,比如“start" : "react-scripts start",执行npm start就是运行“react-scripts start"
"start": "SET PORT=8080 && react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
....
"devDependencies": {//开发环境下,项目所需依赖
"@types/lodash": "^4.14.191"
}
}
package-lock.json
用一句话来概括很简单,就是锁定安装时的包的版本号,并且需要上传到git,以保证其他人在npm install时大家的依赖能保证一致。
node_modules
是安装node后用来存放用包管理工具下载安装的包的文件夹。比如webpack、gulp、grunt这些工具。
git代码管理
常用命令
git init 初始化git仓库
git status 查看文件状态
git add 文件列表 追踪文件
git commit -m 提交信息 向仓库中提交代码
git log 查看提交记录
分支
1.分支明细
(1)主分支(master):第一次向 git 仓库中提交更新记录时自动产生的一个分支。
(2)开发分支(develop):作为开发的分支,基于 master 分支创建。
(3)功能分支(feature):作为开发具体功能的分支,基于开发分支创建
2.分支命令
(1)git branch 查看分支
(2)git branch 分支名称 创建分支
(3)git checkout 分支名称 切换分支
(4)git merge 来源分支 合并分支 (备注:必须在master分支上才能合并develop分支)
(5)git branch -d 分支名称 删除分支(分支被合并后才允许删除)(-D 强制删除)
git多人协同merge冲突
是当前修改是左箭头方向,传入的是右箭头的方向,
中间用等于号分割,等号上边是当前修改(本地),下边是传入的修改(线上的代码)。
两人同时提交可能会出现冲突,解决办法是手动修改冲突
暂时保存更改
存储临时改动:git stash(藏匿)
恢复改动:git stash pop
- 应用场景:
对于多人并行开发,维护同一仓库工作场景,经常会出现文件合并冲突的情况
- 作用:
能够将所有未提交的修改(工作区和暂存区)保存至堆栈中,用于后续恢复当前工作目录。
git add
只是把文件加到 git 版本控制里
性能优化
压缩js,css,js分包,优化图片(webp),开启gzip(后端开启),配置缓存(强制缓存协商缓存),使用cdn,
webpack分包,减少重绘回流
webpack打包管理
它将根据模块的依赖关系进行静态分析,然后将这些模块( js、css、less )按照指定的规则生成对应的静态资源,减少了页面的请求。Webpack是以公共JS的形式来书写脚本的,方便旧项目进行代码迁移。
原理
Webpack通过一个给定的主文件(如:index.js)开始找到项目的所有依赖文件,
使用loaders处理它们,plugin可以压缩代码和图片,
把所有依赖打包成一个 或多个bundle.js文件(捆bundle)浏览器可识别的JavaScript文件。
Babel
JavaScript 编译器
将es6、es7、es8等语法转换成浏览器可识别的es5或es3语法,即浏览器兼容的语法,比如将箭头函数转换为普通函数
将jsx转换成浏览器认的js
loader
webpack只认识JS和JSON,所以Loader相当于翻译官,将其他类型资源进行预处理,最终变为js代码。
- less-loader:将less文件编译成css文件(开发中,会使用less预处理器编写css样式,使开发效率提高)
- css-loader:将css文件变成commonjs模块(模块化的规范)加载到js中,模块内容是样式字符串
- style-loader:创建style标签,将js中的样式资源插入标签内,并将标签添加到head中生效
- ts-loader:打包编译Typescript文件
plugin
Plugin解决loader 无法实现的事情,比如打包优化和代码压缩等。
- html-webpack-plugin 处理html资源,默认会创建一个空的HTML,自动引入打包输出的所有资源(js/css)
- mini-css-extract-plugin 打包过后的css在js文件里,该插件可以把css单独抽出来
- clean-webpack-plugin 每次打包时候,CleanWebpackPlugin 插件就会自动把上一次打的包删除
loader和plugin的区别
运行时机
1.loader运行在编译阶段
2.plugins 在整个周期都起作用
热加载原理
浏览器热更新:开发时能够在浏览器页面中实时看到我们代码的变化产生效果的流程。
热加载是通过内置的 HotModuleReplacementPlugin 实现的
- 构建 bundle 的时候,监听文件变化。
- 文件修改会触发 webpack 重新构建,
- 服务器通过向浏览器发送更新消息,
- 浏览器通过 jsonp 拉取更新的模块文件,
- jsonp 回调触发模块热替换逻辑。
共性
问项目中有哪些难点,怎么解决的
代码遇到冲突怎么办
个性
介绍
使用ant-design做ui,redux做状态管理,typescript增加类型,axios网络库
后端接口
- Navicat创建数据库和空表
- 加载MySQL等模块
- 创建MySQL连接池(设置服务器地址host,服务器端口号port) 和 服务器对象server
- (127.x.x.x)是本机回送地址(Loopback Address),即主机IP堆栈内部的IP地址,主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,协议软件立即返回,不进行任何网络传输。
- 3306是MySQL的默认端口
- 默认的服务端口就是8080。
- 跨源资源共享CORS
// 创建MySQL连接池
const pool = mysql.createPool({
host: '127.0.0.1', //MySQL服务器地址
port: 3306, //MySQL服务器端口号
user: 'root', //数据库用户的用户名
password: '', //数据库用户密码
database: 'reg_log', //数据库名称
connectionLimit: 20, //最大连接数
charset: 'utf8' //数据库服务器的编码方式
});
// 创建服务器对象
const server = express();
// 加载CORS模块
const cors = require('cors');
// 使用CORS中间件
server.use(cors({
origin: ['http://localhost:8080', 'http://127.0.0.1:8080']
}));
登陆注册
示例都使用端口3000作为HTTP服务器的默认监听端口。
3000 是整数。 0 ~ 1023 (常用端口)49152 ~ 65535 号端口是动态端口
//用户注册接口
server.post('/register', (req, res) => {
//console.log(md5('12345678'));
// 获取用户名和密码信息
let username = req.body.username;
let password = req.body.password;
...
// 将用户的相关信息插入到数据表
});
// 用户登录接口
server.post('/login', (req, res) => {
//获取用户名和密码信息
let username = req.body.username;
let password = req.body.password;
// 获取SQL数据并进行判断
...
});
// 指定服务器对象监听的端口号
server.listen(3000, () => {
console.log('server is running...');
})
注册页
checkForm() {
// 点击注册按钮后调用此方法,验证用户名、密码、二次密码是否均正确,正确则发送axios请求
if (this.checkName() && this.checkPwd() && this.checkrePwd()) {
console.log(`验证成功,执行注册业务......`);
// 发送注册(post)请求
this.axios
.post("/register", `username=${this.name}&password=${this.pwd}`)
.then((result) => {
console.log(result);
if (result.data.code == 200) {
// 弹窗提示注册成功
...
// 注册成功后直接跳转到登录页
this.$router.push("/login");
} else if (result.data.code == 201) {
...
}
});
}
分页
currentPage(当前页码)、total(总条数)、pageSize(每页展示的数据量)
把所有的数据请求回后,通过arr.slice(开始索引,结束索引)来进行截取每一页的数据;
假设当前页是currentPage = 1,pageSize = 5,那么应该从(currentPage-1)*pageSize开始截取,到currentPage*pageSize结束
弹窗组件modal
.modal {
display: none; /* 默认隐藏 */
position: fixed; /* 固定定位 */
z-index: 1; /* 设置在顶层 */
...
overflow: auto;
}
// 点击按钮打开弹窗
btn.onclick = function() {
modal.style.display = "block";
}
笔试
考试时允许使用草稿纸,请提前准备纸笔。考试过程中允许上厕所等短暂离开,但请控制离开时间
笔试得分60%一般通过,面试答对80%才能通过
一般过了3道编程,过了1.5就差不多,2就稳了。但是不绝对,有的一道题也会让你面试,有的a了2,也不一定有面试机会
有没有面试机会更多看的是卷的程度,学历能提分
- 运用示例,摸清规律,弄懂整个逻辑后,再动手
- 10min没有完整思路的先跳过,有时候局限了,回过头可能想得出来
- 随手保存
- 不要追求AC率,后面有空再返回完善,
- 注意题目中说明输入的上限,如下
-
read_line()//将读取至多1024个字符,一定注意看题目字符上限 gets(n)//将读取至多n个字符,当还未达到n个时如果遇到回车或结束符
范围
选择题总集合={前端,计算机基础(数据库,操作系统,数据结构与算法,计算机网络),行测};
编程题总集合={常规算法(到具体情景),js手写,Dom操作}
例如:
- 美团:前端,计算机基础,行测,常规算法(前端:计算机基础=1:1)
- 小红书:前端,计算机基础,常规算法(前端:计算机基础=3:1)
- SHINE:前端,js手写
- 携程,京东:行测
- 百度:前端,计算机基础,常规算法,Dom操作
数据结构(选择题)
二叉树
N0=1+N2
满二叉树
- 结点数:2^h-1
结点i的
- 父结点:└i/2┘
- 左孩子结点:2i
- 右孩子结点:2i+1
完全图
- 无向:任意两个顶点间,只有一条边,n(n-1)/2条边
- 有向:任意两个顶点间,只有方向相反的两条弧,n(n-1)条弧
最小生成树
- 定义:连通,无向带权 图的生成树,权值之和最小的
- 唯一:当任意环中边的权值相异,则最小生成树唯一
普里姆Prim算法 |
克鲁斯卡Kruskal算法 |
|
共同 |
基于贪心算法 |
|
特点 |
从顶点开始扩展最小生成树 |
按权递增次序,选择不构成环的边 |
T(n) |
O(|V|^2) |
O(|E|log2|E|) 堆存放E,每次选最小权值边O(log2|E|) T所有边看成等价类,每次+边,看成求等价类∴并查集描述T, ∴构造T需O(|E|) |
适用 |
稠密图 |
稀疏图 |
最短路径
Dijkstra算法 |
Floyd算法 |
|
问题 |
单源最短路径(单起源到各个顶点的最短距离,从源点的临近点开始) |
各个顶点之间的最短路径 |
拓扑排序
- DAG:有向无环图Directed Acycline Graph
- 拓扑排序:DAG中,每个顶点只出现一次,对每个<u,v>,序列中,u在v前
- 唯一:图为线性有序序列时,唯一;若存在顶点 有多个后继则不唯一
- 邻接矩阵:
- 算法:
- 输出并删除 一个 没有前驱 的结点
- 删除 以该结点 为弧头 的边
- 重复(1)(2),直到 DAG为空 或者 不存在 无前驱的 结点(环)
关键路径
- 关键路径:最长路径,工程所需时间
模式匹配
主串S,长n,模式串T,长m。T在S中首次出现的位置
BF模式匹配
最坏T(n)=O(m*n)
KMP模式匹配
- next[j]:T的第j个字符失配于S中的第i个字符,需要用T的第next[j]个字符与S中的第i个字符 比较
abcdeabf(f失配,第next[j]=3个字符c比较)T起点开始,和失配点结束的最大公共前缀
- next[1]=0:i++;
- next[2]=1,next[j]:i不变;
模式匹配过程:
- S中第i个char,T中第j个char
- j指向 失配点/ j=m(全部匹配成功) 为 一趟
虽KMP的T(n)=O(m+n),
但实际中BF的T(n)接近O(m+n),
∴至今采用
只有T中有很多部分匹配,KMP才明显快
*命名规范
建议养成每句后加;的好习惯
- Pascal Case 大驼峰式命名法:首字母大写。eg:StudentInfo、UserInfo、ProductInfo
- Camel Case 小驼峰式命名法:首字母小写。eg:studentInfo、userInfo、productInfo
常量
命名方法:名词全部大写
命名规范:使用大写字母和下划线来组合命名,下划线用来分割单词
const MAX_COUNT = 10;
变量,函数
命名方法: 小驼峰式命名法
命名规范:前缀为形容词(变量) ,前缀为动词(函数)
let maxCount = 10;
/**
*
* @param n int整型 the n
* @return int整型
*/
function setConut(n){
this.count=n;
return n
}
类
类 & 构造函数
命名方法:大驼峰式命名法,首字母大写。
命名规范:前缀为名称。
- 公共属性和方法:跟变量和函数的命名一样。
- 私有属性和方法:前缀为_(下划线),后面跟公共属性和方法一样的命名方式。
class Person {
private _name: string;
constructor() { }
// 公共方法
getName() {
return this._name;
}
// 公共方法
setName(name) {
this._name = name;
}
}
const person = new Person();
person.setName('mervyn');
person.getName(); // ->mervyn
算法(编程题)
场景题千千万,但都是由经典算法变换而来。
优化一般靠牺牲空间换时间
考核方式
ACM模式
自己构造输入格式,控制返回格式,OJ不会给任何代码,不同的语言有不同的输入输出规范。
JavaScript(V8)
readline()获取单行字符串
key:
line.split(' ').map(Number);//str->arr
arr.push([]);//arr[]->arr[][]
//单行输入
while(line=readline()){
//字符数组
var lines = line.split(' ');
//.map(Number)可以直接将字符数组变为数字数组
var lines = line.split(' ').map(Number);
var a = parseInt(lines[0]);//效果等同下面
var b = +lines[1]; //+能将str转换为num
print(a+b);
}
//矩阵的输入
while (line = readline()) {
let nums = line.split(' ');//读取第一行
var row = +nums[0];//第一行的第一个数为行数
var col = +nums[1];//第一行的第二个数为列数
var map = [];//用于存放矩阵
for (let i = 0; i < row; i++) {
map.push([]);
let mapline = readline().split(' ');
for (let j = 0; j < col; j++) {
map[i][j] = +mapline[j];
}
}
}
JavaScript(Node)
华为只可以采用Javascript(Node)
模板1
var readline = require('readline')
// 创建读取行接口对象
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
单行
//监听换行,接受数据
rl.on('line', function(line) {
//line为输入的单行字符串,split函数--通过空格将该行数据转换为数组。
var arr= line.split(' ')
//数组arr的每一项都是字符串格式,如果我们需要整型,则需要parseInt将其转换为数字
console.log(parseInt(arr[0]) + parseInt(arr[1]));
})
多行
const inputArr = [];//存放输入的数据
rl.on('line', function(line){
//line是输入的每一行,为字符串格式
inputArr.push(line.split(' '));//将输入流保存到inputArr中(注意为字符串数组)
}).on('close', function(){
console.log(fun(inputArr))//调用函数并输出
})
//解决函数
function fun() {
xxxxxxxx
return xx
}
模板2
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
// Write your code here
while(line = await readline()){
let tokens = line.split(' ');
let a = parseInt(tokens[0]);
let b = parseInt(tokens[1]);
console.log(a + b);
}
}()
常用输出
let a=[1,2,3];
console.log(a.toString()); //1,2,3 arr->str
console.log(a.join(' ')); // 1 2 3 arr->str
console.log(...a); // 1 2 3 展开运算符...
核心代码模式
只需要实现函数核心功能并返回结果,无须处理输入输出
例如力扣上是核心代码模式,就是把要处理的数据都已经放入容器里,可以直接写逻辑
常用方法
异或运算^
按位异或,相同为0,不同为1
运算法则:
1.交换律(随便换像乘一样):a ^ b ^ c === a ^ c ^ b
2.任何数于0异或为任何数 0 ^ n === n
3.相同的数异或为0: n ^ n === 0
Math
//e=2.718281828459045
Math.E;
//绝对值
Math.abs()
//基数(base)的指数(exponent)次幂,即 base^exponent。
Math.pow(base, exponent)
//max,min不支持传递数组
Math.max(value0, value1, /* … ,*/ valueN)
Math.max.apply(null,array)
apply会将一个数组装换为一个参数接一个参数
null是因为没有对象去调用这个方法,只需要用这个方法运算
//取整
Math.floor() 向下取一个整数(floor地板)
Math.ceil(x) 向上取一个整数(ceil天花板)
Math.round() 返回一个四舍五入的值
Math.trunc() 直接去除小数点后面的值
Number
0B,0O为ES6新增
- 二进制:有前缀0b(或
0B
)的数值,出现0,1以外的数字会报错(b:binary) - 八进制:有前缀0o(或
0O
)的数值,或者是以0后面再跟一个数字(0-7)。如果超出了前面所述的数值范围,则会忽略第一个数字0,视为十进制数(o:octonary) - 注意:八进制字面量在严格模式下是无效的,会导致支持该模式的JavaScript引擎抛出错误
- 十六进制:有前缀0x,后跟任何十六进制数字(0~9及A~F),字母大小写都可以,超出范围会报错
特殊值:
- Number.MIN_VALUE:5e-324
- Number.MAX_VALUE:1.7976931348623157e+308
- Infinity ,代表无穷大,如果数字超过最大值,js会返回Infinity,这称为正向溢出(overflow);
- -Infinity ,代表无穷小,小于任何数值,如果等于或超过最小负值-1023(即非常接近0),js会直接把这个数转为0,这称为负向溢出(underflow)
- NaN ,Not a number,代表一个非数值
- isNaN():用来判断一个变量是否为非数字的类型,如果是数字返回false;如果不是数字返回true。
- isFinite():数值是不是有穷的
var result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(isFinite(result)); //false
typeof NaN // 'number' ---
NaN
不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于Number
NaN === NaN // false ---
NaN
不等于任何值,包括它本身(1 / +0) === (1 / -0) // false ---除以正零得到
+Infinity
,除以负零得到-Infinity
,这两者是不相等的
科学计数法:
对于那些极大极小的数值,可以用e表示法(即科学计数法)表示的浮点数值表示。
等于e前面的数值乘以10的指数次幂
numObj.toFixed(digits)//用定点表示法来格式化一个数值
function financial(x) {
return Number.parseFloat(x).toFixed(2);
}
console.log(financial(123.456));
// Expected output: "123.46"
console.log(financial(0.004));
// Expected output: "0.00"
console.log(financial('1.23e+5'));
// Expected output: "123000.00"
取余是数学中的概念,
取模是计算机中的概念,
两者都是求两数相除的余数
1.当两数符号相同时,结果相同,比如:7%4 与 7 Mod 4 结果都是3
2.当两数符号不同时,结果不同,比如 (-7)%4=-3和(-7)Mod4=1
取余运算,求商采用fix 函数,向0方向舍入,取 -1。因此 (-7) % 4 商 -1 余数为 -3
取模运算,求商采用 floor 函数,向无穷小方向舍入,取 -2。因此 (-7) Mod 4 商 -2 余数为 1
key:((n % m) + m) % m;
Number.prototype.mod = function(n) {
return ((this % n) + n) % n;
}
// 或
function mod(n, m) {
return ((n % m) + m) % m;
}
Map
保存键值对,任何值(对象或者基本类型)都可以作为一个键或一个值。
Map的键可以是任意值,包括函数、对象或任意基本类型。
object的键必须是一个String或是Symbol 。
const contacts = new Map()
contacts.set('Jessie', {phone: "213-555-1234", address: "123 N 1st Ave"})
contacts.has('Jessie') // true
contacts.get('Hilary') // undefined
contacts.delete('Jessie') // true
console.log(contacts.size) // 1
function logMapElements(value, key, map) {
console.log(`m[${key}] = ${value}`);
}
new Map([['foo', 3], ['bar', {}], ['baz', undefined]])
.forEach(logMapElements);
// Expected output: "m[foo] = 3"
// Expected output: "m[bar] = [object Object]"
// Expected output: "m[baz] = undefined"
Set
值的集合,且值唯一
let setPos = new Set();
setPos.add(value);//Boolean
setPos.has(value);
setPos.delete(value);
function logSetElements(value1, value2, set) {
console.log(`s[${value1}] = ${value2}`);
}
new Set(['foo', 'bar', undefined]).forEach(logSetElements);
// Expected output: "s[foo] = foo"
// Expected output: "s[bar] = bar"
// Expected output: "s[undefined] = undefined"
Array
//创建字符串
//join() 方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串,用逗号
或指定的分隔符字符串分隔。如果数组只有一个元素,那么将返回该元素而不使用分隔符。
Array.join()
Array.join(separator)
//################创建数组:
//伪数组转成数组
Array.from(arrayLike, mapFn)
console.log(Array.from('foo'));
// Expected output: Array ["f", "o", "o"]
console.log(Array.from([1, 2, 3], x => x + x));
// Expected output: Array [2, 4, 6]
console.log( Array.from({length:3},(item, index)=> index) );// 列的位置
// Expected output:Array [0, 1, 2]
//################原数组会改变:
arr.reverse()//返回翻转后的数组
// 无函数
arr.sort()//默认排序顺序是在将元素转换为字符串,然后比较它们的 UTF-16
// 比较函数
arr.sort(compareFn)
function compareFn(a, b) {
if (在某些排序规则中,a 小于 b) {
return -1;
}
if (在这一排序规则下,a 大于 b) {
return 1;
}
// a 一定等于 b
return 0;
}
//升序
function compareNumbers(a, b) {
return a - b;
}
//固定值填充
arr.fill(value)
arr.fill(value, start)
arr.fill(value, start, end)
//去除
array.shift() //方法从数组中删除第一个元素,并返回该元素的值。
//unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度
array.unshift(element0, element1, /* … ,*/ elementN)
//粘接,通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。
array.splice(start)
array.splice(start, deleteCount)
array.splice(start, deleteCount, item1)
array.splice(start, deleteCount, item1, item2...itemN)
//################原数组不会改变:
//切片,浅拷贝(包括 begin,不包括end)。
array.slice()
array.slice(start)
array.slice(start, end)
//展平,按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
array.flat()//不写参数默认一维
array.flat(depth)
//过滤器,函数体 为 条件语句
// 箭头函数
filter((element) => { /* … */ } )
filter((element, index) => { /* … */ } )
filter((element, index, array) => { /* … */ } )
array.filter(str => str .length > 6)
//遍历数组处理
// 箭头函数
map((element) => { /* … */ })
map((element, index) => { /* … */ })
map((element, index, array) => { /* … */ })
array.map(el => Math.pow(el,2))
//map和filter同参
//接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
// 箭头函数
reduce((previousValue, currentValue) => { /* … */ } )
reduce((previousValue, currentValue, currentIndex) => { /* … */ } )
reduce((previousValue, currentValue, currentIndex, array) => { /* … */ } )
reduce((previousValue, currentValue) => { /* … */ } , initialValue)
reduce((previousValue, currentValue, currentIndex) => { /* … */ } , initialValue)
array.reduce((previousValue, currentValue, currentIndex, array) => { /* … */ }, initialValue)
//一个“reducer”函数,包含四个参数:
//previousValue:上一次调用 callbackFn 时的返回值。
//在第一次调用时,若指定了初始值 initialValue,其值则为 initialValue,
//否则为数组索引为 0 的元素 array[0]。
//currentValue:数组中正在处理的元素。
//在第一次调用时,若指定了初始值 initialValue,其值则为数组索引为 0 的元素 array[0],
//否则为 array[1]。
//currentIndex:数组中正在处理的元素的索引。若指定了初始值 initialValue,则起始索引号为 0,//否则从索引 1 起始。
//array:用于遍历的数组。
//initialValue 可选
//作为第一次调用 callback 函数时参数 previousValue 的值。
//若指定了初始值 initialValue,则 currentValue 则将使用数组第一个元素;
//否则 previousValue 将使用数组第一个元素,而 currentValue 将使用数组第二个元素。
String
str.charAt(index)//获取第n位字符
str.charCodeAt(n)//获取第n位UTF-16字符编码 (Unicode)
String.fromCharCode(num1[, ...[, numN]])//根据UTF编码创建字符串
String.fromCharCode('a'.charCodeAt(0))='a'
str.trim()//返回去掉首尾的空白字符后的新字符串
str.split(separator)//返回一个以指定分隔符出现位置分隔而成的一个数组,数组元素不包含分隔符
const str = 'The quick brown fox jumps over the lazy dog.';
const words = str.split(' ');
console.log(words[3]);
// Expected output: "fox"
str.toLowerCase( )//字符串转小写;
str.toUpperCase( )//字符串转大写;
str.substring(indexStart[, indexEnd]) //提取从 indexStart 到 indexEnd(不包括)之间的字符。
str.substr(start[, length]) //没有严格被废弃 (as in "removed from the Web standards"), 但它被认作是遗留的函数并且可以的话应该避免使用。它并非 JavaScript 核心语言的一部分,未来将可能会被移除掉。
str.indexOf(searchString[, position]) //在大于或等于position索引处的第一次出现。
str.match(regexp)// Expected output: Array
/*
match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
该方法类似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置。
var str = '123123000'
str.match(/\w{3}/g).join(',') // 123,123,000
*/
正则表达式Regular Expression(RegExp)
字符串搜索模式。
/正则表达式主体/修饰符(可选)
RegExp 对象是一个预定义了属性和方法的正则表达式对象
regexp.test(str)返回Bool
regexp.exec(str)返回匹配的子串 或者 null
常用修饰符
i | ignoreCase 执行对大小写不敏感的匹配。 |
g | global 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。 |
常用字符
\标记下一个字符是特殊字符或文字。例如,"n”和字符"n”匹配。"\n"则和换行字符匹配。
^匹配输入的开头.
$匹配输入的末尾
·匹配除换行字符外的任何单个字符
*匹配前一个字符零或多次。例如,"zo*”与"z”或"zoo”匹配。
+匹配前一个字符一次或多次。例如,"zo+"与"zoo”匹配,但和"z”不匹配。
?匹配前一个字符零或一次。例如,"a?ve?”和"never"中的“"ve”匹配。
x|y 匹配x或y
{n}匹配n次。n是非负整数
{n,} n是一个非负整数。至少匹配n次。例如,"o{2,)"和"Bob”中的"o”不匹配,但和"foooood"中的所有o匹配。"o{1}”与"o+”等效。"o{0,}”和"o*”等效。
{n,m}m和n是非负整数。至少匹配n次而至多匹配 m次。例如,"o{1,3]"和"fooooood”中的前三个o匹配。"o{0,1}”和“o?”等效。
[xyz]匹配括号内的任一字符。例如,"[abc]"和"plain”中的"a”匹配。
[^xyz]匹配非括号内的任何字符。例如,"[^abc]"和“plain”中的"p”匹配。
[a-z]字符范围。和指定范围内的任一字符匹配。例如,"[a-z]”匹配"a"到"z"范围内的任一小写的字母表字符。
[^m-z]否定字符范围。匹配不在指定范围内的任何字符。例如,"[m-z]”匹配不在"m"到"z"范围内的任何字符。
助记:digital
\d匹配数字字符。等价于[0-9]。
\D匹配非数字字符。等价于[^0-9]。
助记:space
\s匹配任何空白,包括空格、制表、换页等。与"[ \fn\rlt\v]”等效。
\S匹配任何非空白字符。与"[^ \fn\rlt\v]”等效。
\w匹配包括下划线在内的任何字字符。与"[A-Za-z0-9_]”等效。
\W匹配任何非字字符。与"[^A-Za-z0-9_]”等效。
合法的URL
URL结构一般包括协议、主机名、主机端口、路径、请求信息、哈希
- 首先必须是以http(s)开头并且可以不包含协议头部信息
- 主机名可以使用"-"符号,所以两种情况都要判断,包含"-"或不包含"-"
- 顶级域名很多,直接判断"."之后是否为字母即可
- 最后判断端口、路径和哈希,这些参数可有可无
域名中只能包含以下字符
1. 26个英文字母
2. "0,1,2,3,4,5,6,7,8,9"十个数字
3. "-"(英文中的连词号,但不能是第一个字符)
https://www.bilibili.com/video/BV1F54y1N74E/?spm_id_from=333.337.search-card.all.click&vd_source=6fd32175adc98c97cd87300d3aed81ea
//开始: ^
//协议: http(s)?:/\/\
//域名: [A-z0-9]+-[A-z0-9]+|[A-z0-9]+
//顶级域名 如com cn,2-6位: [A-z]{2,6}
//端口 数字: (\d+)?
//路径 任意字符 如 /login: (\/.+)?
//哈希 ? 和 # ,如?age=1: (\?.+)?(#.+)?
//结束: $
// https:// www.bilibili com /video/BV1F54y1N74E ?spm..
/^(http(s)?:\/\/)?(([A-z0-9]+-[A-z0-9]+|[A-z0-9]+)\.)+([A-z]{2,6})(:\d+)?(\/.+)?(\?.+)?(#.+)?$/.test(url)
链表
判断链表是否有环
key:遍历链表,判断相邻结点是否相等,若结点为空,则false,若相等,则true
function ListNode(x){
this.val = x;
this.next = null;
}
/**
*
* @param head ListNode类
* @return bool布尔型
*/
function hasCycle( head ) {
// write code here
if(!head || !head.next){return false}
let fast = head.next
let slow = head
while(slow !== fast){
if(!fast || !fast.next){
return false
}
fast = fast.next.next
slow = slow.next
}
return true
}
module.exports = {
hasCycle : hasCycle
};
二叉树
(反)序列化二叉树
序列化二叉树,key:
- let arr = Array.isArray(s) ? s : s.split("");
- let a = arr.shift();
- let node = null;
- if (typeof a === "number")
function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
}
//反序列化二叉树:tree->str 把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串
function Serialize(pRoot, arr = []) {
if (!pRoot) {
arr.push("#");
return arr;
} else {
arr.push(pRoot.val);//注意是val。而不是root
Serialize(pRoot.left, arr);
Serialize(pRoot.right, arr);
}
return arr;
}
//序列化二叉树:str->tree 根据字符串结果str,重构二叉树
function Deserialize(s) {
//转换为数组
let arr = Array.isArray(s) ? s : s.split("");
//取出val
let a = arr.shift();
//构建二叉树结点
let node = null;
if (typeof a === "number") {
//还有可能等于#
node = new TreeNode(a);
node.left = Deserialize(arr);
node.right = Deserialize(arr);
}
return node;
}
module.exports = {
Serialize: Serialize,
Deserialize: Deserialize,
};
前序遍历(迭代)
入栈:中右左
出栈:中左右
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function(root) {
let stack=[]
let res = []
let cur = null;
if(!root) return res;
root&&stack.push(root)
while(stack.length){
cur = stack.pop()
res.push(cur.val)
cur.right&&stack.push(cur.right)
cur.left&&stack.push(cur.left)
}
return res
};
中序遍历(迭代)
指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function(root) {
let stack = []
let res = []
let cur = root
while(cur||stack.length){
if(cur){
stack.push(cur)
cur = cur.left
} else {
cur = stack.pop()
res.push(cur.val)
cur = cur.right
}
}
return res
};
后序遍历(迭代)
和前序遍历不同:
入栈:中左右
出栈:中右左
rever出栈:左右中
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var postorderTraversal = function(root) {
let stack = []
let res = []
let cur = root
if(!root) return res
stack.push(root)
while(stack.length){
cur = stack.pop()
res.push(cur.val)
cur.left&&stack.push(cur.left)
cur.right&&stack.push(cur.right)
}
return res.reverse()
};
层序遍历
/*
* function TreeNode(x) {
* this.val = x;
* this.left = null;
* this.right = null;
* }
*/
/**
*
* @param root TreeNode类
* @return int整型二维数组
*/
function levelOrder(root) {
// write code here
if (root == null) {
return [];
}
const arr = [];
const queue = [];
queue.push(root);
while (queue.length) {
const preSize = queue.length;
const floor = [];//当前层
for (let i = 0; i < preSize; ++i) {
const v = queue.shift();
floor.push(v.val);
v.left&&queue.push(v.left);
v.right&&queue.push(v.right);
}
arr.push(floor);
}
return arr;//[[1],[2,3]]
}
module.exports = {
levelOrder: levelOrder,
};
判断对称二叉树
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
let flag = true;
function deep(left, right) {
if (!left && !right) return true; //可以两个都为空
if (!right||!left|| left.val !== right.val) {//只有一个为空或者节点值不同,必定不对称
return false;
}
return deep(left.left, right.right) && deep(left.right, right.left); //每层对应的节点进入递归比较
}
function isSymmetrical(pRoot) {
return deep(pRoot, pRoot);
}
module.exports = {
isSymmetrical: isSymmetrical,
};
判断完全二叉树
完全二叉树:叶子节点只能出现在最下层和次下层,且最下层的叶子节点集中在树的左部。
/*
* function TreeNode(x) {
* this.val = x;
* this.left = null;
* this.right = null;
* }
*/
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @return bool布尔型
*/
function isCompleteTree(root) {
// write code here
if (root == null) return true;
const queue = [];
queue.push(root);
let flag = false; //是否遇到空节点
while (queue.length) {
const node = queue.shift();
if (node == null) {
//如果遇到某个节点为空,进行标记,代表到了完全二叉树的最下层
flag = true;
continue;
}
if (flag == true) {
//若是后续还有访问,则说明提前出现了叶子节点,不符合完全二叉树的性质。
return false;
}
queue.push(node.left);
queue.push(node.right);
}
return true;
}
module.exports = {
isCompleteTree: isCompleteTree,
};
判断平衡二叉树
平衡二叉树是左子树的高度与右子树的高度差的绝对值小于等于1,同样左子树是平衡二叉树,右子树为平衡二叉树。
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
function IsBalanced_Solution(pRoot)
{
if(!pRoot) return true;
// write code here
return (Math.abs(getMaxDepth(pRoot.left) - getMaxDepth(pRoot.right)) <=1) && IsBalanced_Solution(pRoot.left) && IsBalanced_Solution(pRoot.right)
}
function getMaxDepth(root) {
if(!root) return 0;
return Math.max(getMaxDepth(root.left)+1,getMaxDepth(root.right)+1)
}
module.exports = {
IsBalanced_Solution : IsBalanced_Solution
};
二叉树的镜像
先序遍历
/*
* function TreeNode(x) {
* this.val = x;
* this.left = null;
* this.right = null;
* }
*/
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pRoot TreeNode类
* @return TreeNode类
*/
function Mirror( pRoot ) {
function traversal(root){
if(root===null) return ;
//交换左右孩子
let temp = root.left;
root.left = root.right;
root.right = temp;
traversal(root.left);
traversal(root.right);
return root;
}
return traversal(pRoot);
// write code here
}
module.exports = {
Mirror : Mirror
};
数组和树
扁平结构(一维数组)转树
key:
- pid:parent id
- obj[item.id] = { ...item, children: [] }
- pid === 0
- !obj[pid]
- obj[pid].children.push(treeitem)
//pid:parent id
let arr = [
{ id: 1, name: '部门1', pid: 0 },
{ id: 2, name: '部门2', pid: 1 },
{ id: 3, name: '部门3', pid: 1 },
{ id: 4, name: '部门4', pid: 3 },
{ id: 5, name: '部门5', pid: 4 },
]
// // 上面的数据转换为 下面的 tree 数据
// [
// {
// "id": 1,
// "name": "部门1",
// "pid": 0,
// "children": [
// {
// "id": 2,
// "name": "部门2",
// "pid": 1,
// "children": []
// },
// {
// "id": 3,
// "name": "部门3",
// "pid": 1,
// "children": [
// {
// id: 4,
// name: '部门4',
// pid: 3,
// "children": [
// {
// id: 5,
// name: '部门5',
// pid: 4,
// "children": []
// },
// ]
// },
// ]
// }
// ]
// }
// ]
function tree(items) {
// 1、声明一个数组和一个对象 用来存储数据
let arr = []
let obj = {}
// 2、给每条item添加children ,并连带一起放在obj对象里
for (let item of items) {
obj[item.id] = { ...item, children: [] }
}
// 3、for of 逻辑处理
for (let item of items) {
// 4、把数据里面的id 取出来赋值 方便下一步的操作
let id = item.id
let pid = item.pid
// 5、根据 id 将 obj 里面的每一项数据取出来
let treeitem = obj[id]
// 6、如果是第一项的话 吧treeitem 放到 arr 数组当中
if (pid === 0) {
// 把数据放到 arr 数组里面
arr.push(treeitem)
} else {
// 如果没有 pid 找不到 就开一个 obj { }
if (!obj[pid]) {
obj = {
children: []
}
}
// 否则给它的 obj 根基 pid(自己定义的下标) 进行查找 它里面的children属性 然后push
obj[pid].children.push(treeitem)
}
}
// 返回处理好的数据
return arr
}
console.log(tree(arr))
数组扁平化
要求将数组参数中的多维数组扩展为一维数组并返回该数组。
数组参数中仅包含数组类型和数字类型
function flatten(arr){
// toString() + split() 实现
return arr.toString().split(',').map(item => Number(item));
// reduce 实现
// return arr.reduce((target, item) => {
// return target.concat(Array.isArray(item) ? flatten(item) : item);
// }, [])
// join() + split() 实现
// return arr.join(',').split(',').map(item => Number(item));
// 递归实现
// let res = [];
// arr.forEach(item => {
// if (Array.isArray(item)) {
// res = res.concat(flatten(item))
// } else {
// res.push(item);
// }
// });
// return res;
// 扩展运算符实现
// while(arr.some(item => Array.isArray(item))){
// arr = [].concat(...arr);
// }
// return arr;
// flat()实现(这里不支持使用)
// return arr.flat(Infinity);
}
排序
快速排序
快速排序的基本思想是通过分治来使一部分均比另一部分小(大)再使两部分重复该步骤而实现有序的排列。核心步骤有:
- 选择一个基准值(pivot)
- 以基准值将数组分割为两部分
- 递归分割之后的数组直到数组为空或只有一个元素为止
key:
- pivot = array.splice(pivotIndex, 1)[0]
- _quickSort(left).concat([pivot], _quickSort(right))
const _quickSort = array => {
if(array.length <= 1) return array
var pivotIndex = Math.floor(array.length / 2)
var pivot = array.splice(pivotIndex, 1)[0]
var left = []
var right = []
for (var i=0 ; i<array.length ; i++){
if (array[i] < pivot) {
left.push(array[i])
} else {
right.push(array[i])
}
}
return _quickSort(left).concat([pivot], _quickSort(right))
}
*归并排序
思想:两个/两个以上有序表 合并成 新 有序表
- 2路-归并排序:两两归并
- key:
- left=arr.slice(0,mid)
- mergeLeft=mergeSort(left)
- res.push(leftArr.shift())
- res=res.concat(leftArr)
function mergesort(arr){
if(arr.length<2)return arr
let len=arr.length
let mid=parseInt(len/2)
let l1=arr.slice(0,mid)
let r1=arr.slice(mid,len)
let mergeleft=mergesort(l1)
let mergeright=mergesort(r1)
return merge(mergeleft,mergeright)
function merge(left,right){
let res=[]
while(left.length!=0 &&right.length!=0){
if(left[0]<=right[0]){
res.push(left.shift())
}else{
res.push((right.shift()))
}
}
if(left.length){
res=res.concat(left)
}
if(right.length){
res=res.concat(right)
}
return res
}
}
*堆排序
1.首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
2.将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
3.将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
注意:升序用大根堆,降序就用小根堆(默认为升序)
key:
headAdjust:
- for (var i = 2 * start + 1; i <= end; i = i * 2 + 1)
- if (i < end && arr[i] < arr[i-1])
buildHeap://从最后一棵子树开始,从后往前调整
- //最大元素保存于尾部,并且不参与后面的调整
- //进行调整,将最大元素调整至堆顶
- headAdjust(arr, 0, i);
//每次调整,从上往下调整
//调整为大根堆
function headAdjust(arr, start, end){
//将当前节点值进行保存
var tmp = arr[start];
//遍历孩子结点
for (var i = 2 * start + 1; i <= end; i = i * 2 + 1)
{
if (i < end && arr[i] < arr[i-1])//有右孩子并且左孩子小于右孩子
{
i--;//i一定是左右孩子的最大值
}
if (arr[i] > tmp)
{
arr[start] = arr[i];
start = i;
}
else
{
break;
}
}
arr[start] = tmp ;
}
}
//构建堆
function buildHeap(arr){
//从最后一棵子树开始,从后往前调整
for(var i= Math.floor(arr.length/2) ; i>=0; i--){
headAdjust(arr, i, arr.length);
}
}
function heapSort(arr){
//构建堆
buildHeap(arr);
for(var i=arr.length-1; i>0; i--){
//最大元素保存于尾部,并且不参与后面的调整
var swap = arr[i];
arr[i] = arr[0];
arr[0] = swap;
//进行调整,将最大元素调整至堆顶
headAdjust(arr, 0, i);
}
}
回溯
如果不能成功,那么返回的时候我们就还要把这个位置还原。这就是回溯算法,也是试探算法。
全排列
通过回溯剪枝。修剪掉有当前元素的path,最后保留与原字符串长度相等的所有元素。
key:
- path.length == string.length
- path.includes(item)
const _permute = string => {
const res = [];
const backtrace = path => {
if(path.length == string.length){
res.push(path);
return;
}
for(const item of string) {
if(path.includes(item)) continue;
backtrace(path + item);
}
};
backtrace('');
return res;
}
N皇后
N 皇后问题是指在 n * n 的棋盘上要摆 n 个皇后,
要求:任何两个皇后不同行,不同列也不在同一条斜线上,
求给一个整数 n ,返回 n 皇后的摆法数。
要求:空间复杂度 O(1) ,时间复杂度O(n!)
- 要确定皇后的位置,其实就是确定列的位置,因为行已经固定了
- 进一步讲,也就是如何摆放 数组
arr
[0,1,2,3,...,n-1]
- 如果没有【不在同一条斜线上】要求,这题其实只是单纯的全排列问题
- 在全排列的基础上,根据N皇后的问题,去除一些结果
-
arr
n个皇后的列位置 -
res
n皇后排列结果 -
ruler
记录对应的列位置是否已经占用(也是是否有皇后),如果有,那么设为1,没有设为0 -
setPos
哈希集合,标记正斜线(从左上到右下)位置,如果在相同正斜线上,坐标(x,y)满足 y-x 都相同 -
setCon
哈希集合,标记反正斜线(从y右上到左下)位置,如果在相同反斜线上,坐标(x,y)满足 x+y 都相同 -
是否在同一斜线上,其实就是这两个点的所形成的斜线的斜率是否为±1。点P(a,b) ,点Q(c,d)
(1)斜率为1 (d-b)/(c-a) = 1,横纵坐标之差相等
(2)斜率为-1 (d-b)/(c-a) = -1 ,等式两边恒等变形 a+b = c + d ,横纵坐标之和相等
/**
*
* @param n int整型 the n
* @return int整型
*/
function Nqueen(n) {
let res = []; //二维数组,存放每行Q的列坐标
let isQ = new Array(n).fill(0); //记录该列是否有Q
let setPos = new Set(); //标记正对角线
let setCon = new Set(); // 标记反对角线
//给当前row找一个col
const backTrace = (row, path) => {
if (path.length === n) {
res.push(path);
return;
}
for (let col = 0; col < n; col++) {
if (
isQ[col] == 0 &&
!setPos.has(row - col) &&
!setCon.has(row + col)
) {
path.push(col);
isQ[col] = 1;
setPos.add(row - col);
setCon.add(row + col);
backTrace(row + 1, path);
path.pop();
isQ[col] = 0;
setPos.delete(row - col);
setCon.delete(row + col);
}
}
};
backTrace(0, []);
return res.length;
}
module.exports = {
Nqueen: Nqueen,
};
动态规划(Dynamic Programming,DP)
用来解决一类最优化问题的算法思想。考虑最简单的情况,以及下一级情况和它的关系
简单来说,动态规划将一个复杂的问题分解成若干个子问题,通过综合子问题的最优解来得到原问题的最优解。需要注意的是,动态规划会将每个求解过的子问题的解记录下来,这样
一般可以使用递归或者递推的写法来实现动态规划,其中递归写法在此处又称作记忆化搜索。
斐波那契(Fibonacci)数列(递归)
function F(n){
if(n= 0||n== 1) return 1;
else return F(n-1)+F(n-2);
}
dp[n]=-1表示F(n)当前还没有被计算过
function F(n) {
if(n == 0lIn-1) return 1;//递归边界
if(dp[n] != -1) return dp[n]; //已经计算过,直接返回结果,不再重复计算else {
else dp[n] = F(n-1) + F(n-2); 1/计算F(n),并保存至dp[n]
return dp [n];//返回F(n)的结果
}
数塔(递推)
第i层有i个数字。现在要从第一层走到第n层,最后将路径上所有数字相加后得到的和最大是多少?
dp[i][j]表示从第i行第j个数字出发到达最底层的所有路径中能得到的最大和
dp[i][i]=max(dp[i-1][j],dp[i-1][j+1])+f[i][j]
递归和递推的区别
- 递归:
- 设置递归边界
- 判断已经计算过,直接返回结果
- 返回关系式
- 递推:
- 初始化边界
- 根据初始化边界 开始 递推
- 循环递推
最长公共子序列(LCS)
Longest Common Subsequence:子序列可以不连续
“sadstory”与“adminsorry”最长公共子序列为“adsory”
dp[i][j]表示字符串A的i号位和字符串B的j号位之前的LCS 长度下标从1开始)
最长回文子串
dp[i][j]表示S[i]至S[j]所表示的子串是否是回文子串,是则为1,不是为0
最小路径和
mxn矩阵 a,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,输出所有的路径中最小的路径和。
dp[i][j]代表i到j的最短路径
求解子问题时的状态转移方程:从「上一状态」到「下一状态」的递推式。
dp[i, j] = min(dp[i - 1][j], dp[i][j - 1]) + matrix[i][j]
JavaScript中没有二维数组的概念,但是可以设置数组元素的值等于数组
key:
- dp[0][i] = dp[0][i - 1] + matrix[0][i];
- dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + matrix[i][j];
function minPathSum(matrix) {
var row = matrix.length,
col = matrix[0].length;
var dp = new Array(row).fill(null).map(() => new Array(col).fill(0));
dp[0][0] = matrix[0][0]; // 初始化左上角元素
// 初始化第一行
for (var i = 1; i < col; i++) dp[0][i] = dp[0][i - 1] + matrix[0][i];
// 初始化第一列
for (var j = 1; j < row; j++) dp[j][0] = dp[j - 1][0] + matrix[j][0];
// 动态规划
for (var i = 1; i < row; i++) {
for (var j = 1; j < col; j++) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + matrix[i][j];
}
}
return dp[row - 1][col - 1]; // 右下角元素结果即为答案
}
背包
01背包
有n件物品,每件物品的重量为w[i],价值为c[j]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品都只有1件。
dp[i][v]表示前i件物品(1≤i≤n, 0≤v≤V)恰好装入容量为v的背包中所能获得的最大价值。
这样修改对应到图中可以这样理解:v的枚举顺序变为从右往左,dp[i][v]右边的部分为刚计算过的需要保存给下一行使用的数据,而dp[i][v]左上角的阴影部分为当前需要使用的部分。将这两者结合一下,即把 dp[i][v]左上角和右边的部分放在一个数组里,每计算出一个dp[i][v],就相当于把 dp[i - 1][v]抹消,因为在后面的运算中 dp[i- 1][v]再也用不到了。我们把这种技巧称为滚动数组。
特别说明:
如果是用二维数组存放,v的枚举是顺序还是逆序都无所谓;
如果使用一维数组存放,则v的枚举必须是逆序!
完全背包
与01背包问题不同的是其中每种物品都有无数件。
写成一维形式之后和01背包完全相同,唯一的区别在于这里v的枚举顺序是正向枚举,而01背包的一维形式中v必须是逆向枚举。
散列/哈希Hash
空间换时间,即当读入的数为x时,就令hashTable[x]=true(说明: hashTable数组需要初始化为false,表示初始状态下所有数都未出现过)。
数字千位分割
const format = (n) => {
let num = n.toString() // 拿到传进来的 number 数字 进行 toString
let len = num.length // 在拿到字符串的长度
// 当传进来的结果小于 3 也就是 千位还把结果返回出去 小于3 不足以分割
if (len < 3) {
return num
} else {
let render = len % 3 //传入 number 的长度 是否能被 3 整除
console.log(render)
if (render > 0) { // 说明不是3的整数倍
return num.slice(0, render) + ',' + num.slice(render, len).match(/\d{3}/g).join(',')
} else {
return num.slice(0, len).match(/\d{3}/g).join(',')
}
}
}
let str = format(298000)
console.log(str)