2022-05-07-前端面试HTML5CSS3JSTS4Vue3React18八股文手写项目笔试
前端面试HTML5+CSS3+JS+TS4+Vue3+React18+八股文+手写+项目+笔试
高频
面试考点分集,适用于
基础面
和
临时抱佛脚复习
分集 方便专题学习( 合集因码字过多,太卡,优先更新分集,合集与分集同步率最低98% )
⭐表示手写 和 重要程度,*表示了解即可
为了简洁,相关文章参考链接在标题里
常考考点也写的很详细,因为面试官会顺着深挖,所以导致篇幅全又长,见谅
长文不易,码字卡死,如果有帮助到你,可以点个赞吗?Thanks♪(・ω・)ノ
一开始就是在CSDN博客上写的,现在应部分同学的要求,我直接将博客内容复制成pdf,暂时没空整理格式,有的代码缺失还请见原文,见谅。
合集(方便检索,手机端有时文章会不显示目录,可以点下方的目录按钮)
目录
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脚本 ,实现 结构与表现分离 、 结构与行为分离 ,能提高页面的 渲染效率 ,更快地显示网页内容
浏览器的渲染过程 ⭐⭐⭐
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脚本会在 加载完毕后执行 。
async脚本的加载不计入DOMContentLoaded事件统计,也就是说下图两种情况都是有可能发生的:
HTML 还没有被解析完的时候,async脚本已经加载完了,那么 HTML 停止解析,去执行脚本,脚本执行完毕后触发DOMContentLoaded事件。
HTML 解析完了之后,async脚本才加载完,然后再执行脚本,那么在HTML解析完毕、async脚本还没加载完的时候就触发DOMContentLoaded事件
c. defer(推迟)
文档解析时,遇到设置了defer的脚本,就会在后台进行下载,但是并不会阻止文档的渲染,当页面解析和渲染完毕后,会等到所有的defer脚本加载完毕并按照顺序执行完毕才会触发
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
常用手写⭐
获取标签
<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>
document.getElementById('id')//全局唯一
document.getElementByClassName('className')//获得数组
document.getElementByTagName('div')//获得数组
js中插入标签
let head = document.head;
let style = document.createElement("style");
style.type = "text/css";
style.innerHTML = "p {color: rgb(255,0,0);}";
head.appendChild(style);
获取第n个标签
let pArr = document.getElementsByTagName('p')
for(let i = 0; i < pArr.length; i++){
if(i===n-1){
...
}
}
<html>
<head>
<meta charset=utf-8>
<style type="text/css">
ul li:nth-child(2) {
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>
<input type="text" onkeyup="myFunction()">
//不用 "on" 前缀。例如,使用 "click" 来取代 "onclick"。
//true - 事件在捕获阶段执行
//false- 默认。事件在冒泡阶段执行
document.addEventListener(event, function[, useCapture])
鼠标事件
属性 | 描述 | DOM |
---|---|---|
当用户点击某个对象时调用的事件句柄。 | 2 | |
当用户双击某个对象时调用的事件句柄。 | 2 | |
鼠标按钮被按下。 | 2 | |
鼠标被移动。 | 2 | |
鼠标移到某元素之上。 | 2 | |
鼠标从某元素移开。 | 2 | |
鼠标按键被松开。 | 2 |
键盘事件
属性 | 描述 | DOM |
---|---|---|
某个键盘按键被按下。 | 2 | |
某个键盘按键被按下并松开。 | 2 | |
某个键盘按键被松开。 |
表单事件
属性 | 描述 | DOM |
---|---|---|
该事件在表单元素的内容改变时触发( , |
2 | |
元素获取焦点时触发 | 2 | |
元素即将获取焦点时触发 | 2 | |
元素即将失去焦点时触发 | 2 | |
元素获取用户输入时触发 | 3 | |
表单重置时触发 | 2 | |
用户向搜索域输入文本时触发 ( <input=“search”>) | ||
用户选取文本时触发 ( 和 | 2 | |
表单提交时触发 | 2 |
系统屏幕( window.screen.availHeight,height )
- window.screen.height
这个是设备显示屏的高度,各个机型的显示屏高度都不一样,可以在系统设置中看
- window.screen.availHeight
屏幕的可用高度,一般是显示屏高度减去显示屏任务栏的高度
screen.availHeight = screen.height - 任务栏高度
注; 更改显示器的缩放倍数,会影响到获取的值,比如屏幕放大125%,则原本1080高度的值,读取后为864。即1080 / 1.25 = 864
浏览器( window.outerHeight,innerHeight )
-
window.outerHeight
浏览器的高度,高度改变,会改变值的大小
-
window.innerHeight
浏览器的 可用 高度 = 浏览器高度 - 顶部工具栏
若有调试面板还会再减去调 度面板 的高度,最后得出的才是可用高度
元素( clientHeight,offsetHeight,scrollHeight
)
获取body的高(不含边框)
element.clientHeight
:body的高度
clientHeight = padding + height
获取body的高(含边框)
element.offsetHeight
:body的高度(包含border)
offsetHeight = padding + height + border
element.scrollHeight
,为可见高度加上未显示的高度(滚动条未显示部分)。
相对距离或位置( offsetTop,scrollTop,getBoundingClientRect
)
- 获取到顶部或左部的距离
element.offsetTop
,element.offsetLeft
offsetTop :元素到 offsetParent 顶部的距离
offsetParent :距离元素最近的一个 具有定位的父元素 ( relative,absolute,fixed ),
若 父元素 都不符合条件,offsetParent为 body 。
- 注意:只有元素show( 渲染完成 )才会计算入offsetTop,若是中间有元素数据需要异步获取,会导致最终获取的offsetTop值偏小
- 获取滚动条到top,left的距离:
element.scrollTop,element.scrollLeft
- 获取相对于视窗的
位置集合
element.getBoundingClientRect()
集合中有top, right, bottom, left等属性。
- rectObject.top :元素上边到视窗上边的距离;
- rectObject.right :元素右边到视窗左边的距离;
- rectObject.bottom :元素下边到视窗上边的距离;
- rectObject.left :元素左边到视窗左边的距离;
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;
详情:
盒子充满屏幕 ( 手写)
相对当前屏幕高度
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;
}
( 手写)
伪类 选择器 : 逻辑选择元素
selector:pseudo-class {property:value;}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
a:link {color:#000000;} /* 未访问链接*/
a:visited {color:#00FF00;} /* 已访问链接 */
a:hover {color:#FF00FF;} /* 鼠标移动到链接上 */
a:active {color:#0000FF;} /* 鼠标点击时 */
</style>
</head>
<body>
<p><b><a href="/css/" target="_blank">这是一个链接</a></b></p>
<p><b>注意:</b> a:hover 必须在 a:link 和 a:visited 之后,需要严格按顺序才能看到效果。</p>
<p><b>注意:</b> a:active 必须在 a:hover 之后。</p>
</body>
</html>
- 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样式文件的引用,也可以引用网上别人写好的样式
旋转,缩放,平移⭐⭐
修改 CSS 坐标空间,实现旋转,缩放,倾斜或平移
默认相对元素的中心点
关键字⭐⭐⭐
static(默认)
该关键字指定元素使用正常的布局行为,即元素在文档常规流中当前的布局位置。此时
top
,
right
,
bottom
,
left
和
z-index
属性无效。
z-index 属性指定一个元素的 堆叠顺序 。
拥有更 高 堆叠顺序的元素总是会处于堆叠顺序较低的元素的 前面 。
- inherit
从 父元素继承 position 属性的值。
relative
相对于其 正常位置 进行定位。
元素 先放置 在 未添加定位时的位置 ,再在不改变页面布局的前提下调整元素位置(因此会在此元素未添加定位时所在位置留下空白)。
absolute
相对于 static 定位以外 的 第一个父元素 进行定位。
元素会被 移出正常文档流 ,并 不为元素预留空间 。绝对定位的元素可以设置外边距(margins),且不 会与其他边距合并 。
- fixed:相对于浏览器 窗口 进行定位。在屏幕滚动时不会改变
- sticky( CSS3新增 ) :基于用户 滚动 的位置,屏幕滚出时会粘住
( 手写)
水平居中
指在 水平 方向上处于 中间 的位置。
- 元素/图片:
margin: auto ;
行内元素会占整行,看不出来水平居中,所以需要:width: 50%;
- 文本:
文本标签除了
都是行内元素, text-align=center
垂直居中
- 单 行 文本:
line-height = height
- 图片 :
- vertical-align
- middle;
水平垂直居中
transform:translate
top: 50%;left: 50%;, 是以元素 左上角为原点 ,故不处于中心位置,
加上transform:translate(-50%,-50%) ,元素 原点(中心点) 往上(x轴),左(y轴)移动自身长宽的 50%,
flex(强烈推荐)
只需要设置 align-items:center; 属性
.wrap {
width: 300px;
height: 300px;
border: 1px solid red;
display:flex;
justify-content:center;
align-items:center;
}
.box {
height: 100px;
width: 100px;
border: 1px solid blue;
}
⭐⭐⭐
布局的传统解决方案,基于 ,依赖 属性 + 属性 + 属性。它对于那些特殊布局非常不方便,比如, 就不容易实现。
关键是flex布局能触发 BFC 规范
Flexible box设置或检索弹性盒模型对象的子元素如何分配空间
水平的主轴(main axis)和垂直的交叉轴(cross axis)
flex-direction 属性决定主轴的方向(也就是排列方向)。有4个属性值可以设置。
- column:主轴为垂直方向,起点在上沿。
- column-reverse:主轴为垂直方向,起点在下沿。
- row(默认值):主轴为水平方向,起点在左端。
- row-reverse:主轴为水平方向,起点在右端。
- align-items 属性定义项目在 交叉轴 上如何对齐。
baseline | 元素位于容器的基线上。 如弹性盒子元素的行内轴与侧轴为同一条,则该值与’flex-start’等效。其它情况下,该值将参与基线对齐。 |
stretch | 默认值。元素被拉伸以适应容器。 如果指定侧轴大小的属性值为’auto’,则其值会使项目的边距盒的尺寸尽可能接近所在行的尺寸,但同时会遵照’min/max-width/height’属性的限制。 |
- justify-content 属性定义了项目在 主轴 上的对齐方式。
space-between | 均匀排列每个元素, 首个元素 放置于 起点 , 末尾元素 放置于 终点 。 |
space-evenly | 均匀排列每个元素,每个元素之间的 间隔相等 。 |
space-around | 均匀排列每个元素,每个元素 周围分配相同的空间 。 |
flex:1
flex 属性是 flex-grow, flex-shrink 和 flex-basis 的简写,默认值为 0 1 auto。后两个属性可选。
flex 属性属性有两个快捷值:
auto
(
1 1 auto
)
和 none
(
0 0 auto
)
。
建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。
/* 一个值,width/height: flex-basis */
flex: 10em;
flex: 30px;
flex: min-content;
/* 两个值:flex-grow | flex-basis */
flex: 1 30px;
/* 两个值:flex-grow | flex-shrink */
flex: 2 2;
/* 三个值:flex-grow | flex-shrink | flex-basis */
flex: 2 2 10%;
flex-grow 属性定义项目的 放大 比例,默认为0,即如果存在剩余空间,也不放大。
如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。
flex-shrink属性定义了项目的 缩小 比例,默认为1,即如果空间不足,该项目将缩小。
如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。
负值对该属性无效。
flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小
⭐⭐⭐
问题
- 外边距重叠:
块的上外边距margin-top和下外边距margin-bottom会合并为单个边距(为单个边距的最大值)
- 浮动导致父高度塌陷:
- 不浮动的元素被浮动元素覆盖:
BFC块级格式化上下文 (Block Fromatting Context)
决定了元素如何对其 内容 进行 定位 ,以及与其它元素的关系和 相互 作用
独立布局 ,盒子内子元素样式 不会影响到外面 的元素。
常见触发条件
-
- overflow
- hidden
-
- display
- flex | inline-block | table-cell
-
- position
- absolute | fixed
规则
BFC
就是一个 块级 元素,块级元素会在 垂直方向 一个接一个的排列BFC
就是页面中的一个隔离的独立容器, 容器里的标签 不会影响到外部标签- 垂直方向的距离
由
margin
决定, 属于
同一个
BFC
的两个相邻的标签外边距会发生重叠 - 计算
BFC
的高度时, 浮动元素也参与计算
overflow : hidden示例
overflow属性指定如果内容溢出一个元素的框,会发生什么
值 | 描述 |
---|---|
visible | 默认值。内容不会被修剪,会呈现在元素框之外。 |
hidden | 内容会被修剪,并且其余内容是不可见的。 |
scroll | 内容会被修剪,但是浏览器会显示滚动条以便查看其余的内容。 |
auto | 如果内容被修剪,则浏览器会显示滚动条以便查看其余的内容。 |
inherit | 规定应该从父元素继承 overflow 属性的值。 |
overflow:hidden
- 避免外边距重叠
- 清除浮动
- 阻止元素被浮动元素覆盖:
*
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>float实现浮动</title>
</head>
<style>
.z1{
height: 200px;
width: 200px;
float: left;
text-align: center;
line-height: 200px;
background: skyblue;
}
.fu {
width: 400px;
}
</style>
<body>
<div class="fu clearfix">
<div class="z1">设置了float为left的图片</div>
<div class="z2">你看,我没有被浮动哥哥挡住哦,这是一段神奇旅行,一天我遇上了白雪公主</div>
</div>
</body>
</html>
即使图片浮动了, 破坏了文档流 ,也覆盖在没有浮动的元素上了,但是其并没有将文本内容也覆盖掉 ,证明了被设计出来的主要目的:实现 文字环绕图片 排版功能
absolute
的容器,才是意义上的
完全脱离
文档流。
覆盖
在当前位置上的
所有
容器和文本内容之上。
absolute
和
float
都不会去覆盖掉在他们之前的正常文档流,这应该和浏览器渲染机制有关系,会从
上到下依次渲染
内容,渲染成功后,就不会因为后续元素浮动而使其被覆盖住(不考虑使用fix等强行覆盖的情况)。
三栏布局 :左右固定,中间自适应( 手写)
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>
标签插入一个简单的换行符
- 定位+浮动
<!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%,需要一整行的宽度补偿 ,则移到最左边。
magin-left:-100px
margin负值会改变元素占据的空间,及移到父元素的最左边,并且该子元素width即为100px
单位⭐⭐⭐
- 绝对长度单位: px 像素
- 百分比: %
- 相对 父 元素字体大小单位: em
- 相对于 根 元素字体大小的单位: rem (默认 16px )
- 相对于视口*宽度的百分比(100vw即视窗宽度的100%): vw
- 相对于视口*高度的百分比(100vh即视窗高度的100%): vh
px转rem
相对于 根 元素字体大小的单位: rem (默认16px)
PostCss
是一个用JavaScript工具和插件转换CSS代码的工具。
postcss-pxtorem
px转vw
网页宽度=1920px 网页高度=1080px
1920px = 100vw
1080px = 100vh
宽 300px 和 200px 的 div ,其所占的宽高,以 vw 和 vh 为单位
vwDiv = (300px / 1920px ) * 100vw
vhDiv = (200px / 1080px ) * 100vh
当屏幕放大或者缩小时,div 还是以 vw 和 vh 作为宽高的,就会自动适应不同分辨率的屏幕
可 自定义scss函数。
$vm_base: 1920;
$vh_base: 1080;
@function vw($px) {
@return ($px / $vm_base) * 100vw;
}
@function vh($px) {
@return ($px / $vh_base) * 100vh;
}
.head{
font-size:vh(100);
}
opacity: 0 、visibility: hidden、display: none⭐⭐⭐
区别 | opacity: 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等除外)。(设置float后可以设置宽、高)
对 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>
布局
静态布局
- 描述:就是 设定 好的 长和宽 ,大小不会改变,不管你多大的屏幕它都是那么大,分辨率是不会改变的
- 优点:这个对于开发者来说是最简单的一种布局方式,没有什么兼容性的问题
- 缺点: 窗口 的大小比 显示 的内容小 。,会展现出滚动条
- 场景:传统web网站
响应式布局⭐⭐
- 使用媒体 查询 (@media)
- 使用flex 弹性 布局
- 使用 百分比 单位:rem单位,VH、HW单位
pc端(>1024)一套适配,平板(768-1024)一套适配,手机端(<768)
自适应设计(AWD):
- 需要开发 多套 界面;
- 通过检测视口分辨率,来判断当前访问的设备是:pc端、平板、手机,
- 从而请求服务层,返回不同的页面;
响应式设计(RWD):
- 只需开发 一套 界面。
- 通过检测视口分辨率,
- 针对不同客户端在客户端做代码处理,通过CSS Media Query ,Content - Based Breakpoint等技术展现不同的布局和内容。
弹性布局(flex布局)
- 描述:目前比较流行的一种布局,使用传统布局难以实现一些复杂的布局,使用flex布局实现就变得非常容易
- 优点:简便、完整、响应式地实现各种页面布局
- 缺点: 只兼容IE10+ 的浏览器
- 场景:三栏式布局、垂直水平居中布局
流式布局
- 描述:页面元素的 宽度 按照 屏幕分辨率进行适配调整 ,但 整体布局不变 。主要特征是像瀑布一样往下流, 有规律的无限遍历模块 。
- 优点:灵活,充分利用浏览器的空间
- 缺点:宽度按照屏幕进行适配调整,对于大屏幕来说用户体验并不是特别好,有些布局元素会显得很长
- 场景:类似抖音视频、微博消息、微信朋友圈等布局
(css3)
@media可以针对不同的屏幕尺寸设置不同的样式。 例如,可以缩小小型设备上的字体大小
@media
规则可置于代码的
顶层
或位于其它任何
内
/* 在 screen 类型 大于560px 小于 700px 加载 */
@media screen and (min-width: 560px) and (max-width: 700px) {
.box1 {
background-color: burlywood;
}
}
媒体类型
① all,适用于所有设备。默认使用该值。
② print,适用于在打印预览模式下在屏幕上查看的分页材料和文档。
③ screen,主要屏幕设备,例如电脑屏幕,平板电脑,智能手机等。
④ speech,主要用于语音合成器。
逻辑运算符(logical operators)
not, and, 似于逻辑或or运算符),only
逗号分隔多个媒体查询来将它们合并为一个规则
/* 在 screen 类型 大于560px 或 小于240px 加载 */
@media screen and (min-width: 560px), (max-width: 240px) {
.box {
background-color: red;
}
}
/* 在 screen 类型 小于 240px 或 大于360px 小于 700px 加载 */
@media screen and (max-width: 240px), (min-width: 360px) and (max-width: 700px) {
.box1 {
background-color: burlywood;
}
}
-
height
输出设备中的页面可见区域高度。 -
width
输出设备中的页面可见区域宽度。 -
max-aspect-ratio
输出设备的 页面 可见宽度与高度的最大比率。 -
max-device-aspect-ratio
输出设备的屏幕可见宽度与高度的最大比率。 -
max-device-height
输出设备的 屏幕 可见的最大高度。 -
max-device-width
输出设备的屏幕最大可见宽度。 -
max-height
输出设备中的 页面 最大可见区域高度。 -
max-width
输出设备中的页面最大可见区域宽度。 -
min-height
输出设备中的页面最小可见区域高度。 -
min-width
输出设备中的页面最小可见区域宽度。
其他加载方式
style
标签上加载
<style media="(min-width: 500px)">
.box {
background-color: red;
}
</style>
<style media="(max-width: 500px">
.box {
background-color: burlywood;
}
</style>
- 根据
media属性
定义的媒体查询判断加载那个样式。
@import 使用时加载
@import url(./index.css) (min-width:350px);
@import url(./home.css) (max-width:750px);
- 在加载最后添加
()
定义 媒体查询 判断加载那个样式。
<picture>
标签
<picture>
<source media="(min-width: 650px)" srcset="demo1.jpg">
<source media="(min-width: 465px)" srcset="demo2.jpg">
<img src="img_girl.jpg">
</picture>
- 根据屏幕匹配的不同尺寸显示不同图片,如果没有匹配到或浏览器不支持
picture
属性则使用img
元素
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
- 运算符:变量 的解构赋值, 对象和数组 新增了 扩展运算符
- 字符串方法
:${ },需要配合单反引号好完成
拼接的功能,eg:
http://localhost:3000/search/users?q=${keyWord}
- 块级 作用域: let,const
- 原生提供 Proxy 构造函数,用来生成 Proxy 实例
- 定义类 的语法糖(class)
- 模块化 import/export
- 生成器(Generator)和遍历器(Iterator)
数据类型
基本数据类型
ES5:Null,Undefined,Number,String,Boolean
ES6新增:Symbol(仅有目的:作为 对象属性 的标识符,表示 唯一 的值)
var obj = {};
obj[Symbol("a")] = "a";
//会根据给定的键 key,来从运行时的 symbol 注册表中找到对应的 symbol,
//如果找到了,则返回它,
//否则,新建一个与该键关联的 symbol,并放入全局 symbol 注册表中。
Symbol.for(key);
Symbol.for("bar") === Symbol.for("bar"); // true,证明了上面说的
Symbol("bar") === Symbol("bar"); // false,Symbol() 函数每次都会返回新的一个 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的函数返回,对象属性名不存在
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。
事件
文档和浏览器窗口中发生的特定交互
先捕获再冒泡。存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。
- 事件 捕获 :由外往内,从事件发生的 根节点 开始,逐级往下查找,一直到 目标 元素。
- 事件 冒泡 :由内往外,从具体的 目标 元素触发,逐级向上传递,直到 根节点 。
element.addEventListener(event, function[, useCapture]);
//useCapture 默认为false,即冒泡阶段调用事件处理函数,
//为ture时,在事件捕获阶段调用处理函数
事件委托/ 代理 ( 手写)
事件委托就是利用事件冒泡,就是把子元素的事件都绑定到父元素上。
应用 :
1000
个
button
注册点击事件。如果循环给每个按钮添加点击事件,那么会增加内存损耗,影响性能
好处:
-
替代循环绑定事件的操作, 减少内存消耗,提高性能 。比如:
ul
上代理所有li
的click
事件。 -
简化了
dom
节点更新时,相应事件的更新。比如:- 不用在新添加的
li
上 绑定click
事件。 - 当删除某个
li
时,不用 解绑 上面的click
事件。
- 不用在新添加的
缺点 :
- 事件委托基于冒泡,对于 不冒泡 的事件不支持。
- 层级过多,冒泡过程中,可能会被某层 阻止掉 。
- 理论上委托会导致浏览器
频繁调用处理函数
,虽然很可能不需要处理。所以建议
就近委托
,比如在
table
上代理td
,而不是在document
上代理td
。
阻止事件冒泡 :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"类实现发布订阅模式。
-
同一名称事件可能有多个不同的执行函数:构造函数中创建”events“对象变量存放所有的事件
-
通过"on"函数添加事件:订阅事件。当总事件中 不存在 此事件时 创建新 的 事件数组 ,当存在时将”fn“函数添加在该事件对应数组中
-
通过"emit"函数触发事件:发布事件, 遍历 该事件下的 函数数组 并 全部执行
class EventEmitter {
constructor() {
this.events = {}//二维,events' funcs
}
//添加事件:订阅事件
on(event, fn) {
if(!this.events[event]) {//当总事件中不存在此事件时创建新的事件数组
this.events[event] = [fn]
} else { //当存在时将”fn“函数添加在该事件对应数组中
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)
}
}
封装事件绑定
绑定事件的元素.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
寄生组合式继承( 手写)
通过寄生组合式继承使"Chinese"构造函数继承于"Human"构造函数。要求如下:
-
给"Human"构造函数的原型上添加"getName"函数,该函数返回调用该函数对象的"name"属性
-
给"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)//call函数借助”Human“的构造器来获得通用属性
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
}
Iterator, for in,for of,forEach,map循环遍历
Iterator
一种 接口 ,为各种 不同的数据结构 提供 统一 的访问机制
例如Array.prototype
Array
对象的
@@iterator
方法实现了
,并允许数组被大多数期望可迭代的语法所使用,例如
和
循环。它返回一个迭代器,生成数组中每个索引的值。
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"]
for in
[“a”, “b”, “c”, “d”];for…in 循环读取 键名 // 0 1 2 3
适用于遍历 对象 的 公有 可枚举属性,无法遍历 symbol 属性
hasOwnProperty() 方法来判断属性是否来自对象本身,并避免遍历 原型链 上的属性。
var triangle = {a: 1, b: 2, c: 3};
function ColoredTriangle() {
this.color = 'red';
}
ColoredTriangle.prototype = triangle;
var obj = new ColoredTriangle();
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
console.log(`obj.${prop} = ${obj[prop]}`);
}
}
// Output:
// "obj.color = red"
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参数 解决
不可以使用yield ,因此箭头函数不能用作Generator函数。
没有原型prototype ,没有super用于访问原型属性。
//传递给getVal函数内的this并不是调用者自身,而是外部的this,即window
this.val = 2;
var obj = {
val: 1,
getVal: () => {
console.log(this.val);
}
}
obj.getVal(); // 2
构造函数
function Person(name,id)
{
this.name=name;
this.id=id;
this.sayHi=function() {
alert("Hi")
}
}
var p= new Person('参宿','7');
- 习惯上首字母大写
- 使用new关键字进行调用
this
普通函数的this在 运行 时创建,匿名函数 this指向window ,箭头函数的this是 定义 时确定。
箭头函数不能用作构造函数:
因为构造函数的 this 指向 实例对象 ,但是箭头函数无法对创建出来的实例 进行this绑定
也不能使用call()、apply()、bind() 去 改变this 的指向。
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
闭包(closure)
类比背包,当一个函数被创建并 传递 或从另一个函数返回时,它会携带一个背包。背包中是函数 声明时作用域内的所有变量 。
var name = '余光';
function foo() {
console.log(name); // 余光
}
(function (func) {
var name = '老王';
func()
})(foo); // 余光
因为js作用域生命周期在于 内部脚本是否全部执行完毕才会销毁 ,并且 不会带到父级作用域 ;
当 函数内部返回一个函数 ,子函数没在父级作用域内完成整个生命周期的话,父级函数是没办法完成一整个生命周期的,闭包正是利用这一点卡住了父级函数的作用域。
因为被 下级作用域内引用 ,而 没有被释放 。就导致上级作用域内的变量,等到下级作用域执行完以后才正常得到释放。
面试 的时候,直接回答 函数嵌套函数 ,且 内部函数调用父级作用域的变量 就可以称之为闭包了。
1: function createCounter() {
2: let counter = 0
3: const myFunction = function() {
4: counter = counter + 1
5: return counter
6: }
7: return myFunction
8: }
9: const increment = createCounter()
10: const c1 = increment()
11: const c2 = increment()
12: const c3 = increment()
13: console.log('example increment', c1, c2, c3)
- 闭包会使得函数中的变量都被保存在内存中, 内存消耗 很大,所以不能滥用闭包
- 滥用闭包容易 内存泄漏 。
- 使用场景 : 防抖、节流 、 函数套函数 避免 全局污染
正则表达式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)开头并且可以不包含协议头部信息
- 主机名可以使用”-“符号,所以两种情况都要判断,包含”-“或不包含”-”
- 顶级域名很多,直接判断”.“之后是否为字母即可
- 最后判断端口、路径和哈希,这些参数可有可无
域名中只能包含以下字符
-
26个英文字母
-
“0,1,2,3,4,5,6,7,8,9"十个数字
-
“-"(英文中的连词号,但不能是第一个字符)
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-zA-Z0-9]+-[a-zA-Z0-9]+|[a-zA-Z0-9]+))+([a-zA-Z]{2,6})(:\d+)?(\/.+)?(\?.+)?(#.+)?$/.test(url)
函数
函数的 length
属性
将返回没有指定默认值的参数个数。也就是说,指定了默认值后,
length
属性将失真。
function fun(a = 1, b, c, d) { }
console.log(fun.length) // 0
函数声明与函数表达式的区别
函数声明会将那个函数提升到最前面(即使你写代码的时候在代码块最后才写这个函数),成为全局函数。
函数声明要 指定函数名 ,而 函数表达式 不用,可以用作 匿名 函数。
立即执行函数(iife)
( function( ){ })( )
原理 :括号内部不能包含语句,当解析器对代码进行解释的时候,先碰到了(), 然后碰到function关键字
就会自动将()里面的代码识别为 函数表达式 而不是 函数声明 。
作用: 立即执行函数会形成一个 单独的作用域 ,我们可以 封装 一些临时变量或者局部变量,避免 污染 全局变量。
常用方法
异或运算^
按位异或,相同为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及AF),字母大小写都可以,超出范围会 报错
特殊值
- 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
值的集合,且值唯一
虽然NaN !== NaN,但set中NaN 被认为是相同的
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"
set判断值相等的机制
//Set用===判断是否相等
const set= new Set();
const obj1={ x: 10, y: 20 },obj2={ x: 10, y: 20 }
set.add(obj1).add(obj2);
console.log(obj1===obj2);//false
console.log(set.size);// 2
set.add(obj1);
console.log(obj1===obj1);//true
console.log(set.size);//2
数组去重 ( 手写)
// Use to remove duplicate elements from the array
const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]
console.log([...new Set(numbers)])
// [2, 3, 4, 5, 6, 7, 32]
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() //从数组中删除第一个元素,并返回该元素的值。
array.pop() //从数组中删除最后一个元素,并返回该元素的值。此方法会更改数组的长度。
array.push() //将一个或多个元素添加到数组的末尾,并返回该数组的新长度
//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 将使用数组第二个元素。
const array1 = [1, 2, 3, 4];
// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
(accumulator, currentValue) => accumulator + currentValue,
initialValue
);
console.log(sumWithInitial);
// Expected output: 10
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)A是65,a是97
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.concat(str2, [, ...strN])
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)//找到一个或多个正则表达式的匹配。
const paragraph = 'The quick brown fox jumps over the lazy dog. It barked.';
let regex = /[A-Z]/g;
let found = paragraph.match(regex);
console.log(found);
// Expected output: Array ["T", "I"]
regex = /[A-Z]/;
found = paragraph.match(regex);
console.log(found);
// Expected output: Array ["T"]
//match类似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置。
var str = '123123000'
str.match(/\w{3}/g).join(',') // 123,123,000
str.search(regexp)//如果匹配成功,则 search() 返回正则表达式在字符串中首次匹配项的索引;否则,返回 -1
const paragraph = '? The quick';
// Any character that is not a word character or whitespace
const regex = /[^\w\s]/g;
console.log(paragraph.search(regex));
// Expected output: 0
str.repeat(count)//返回副本
str.replace(regexp|substr, newSubStr|function)//返回一个由替换值(replacement)替换部分或所有的模式(pattern)匹配项后的新字符串。
const p = 'lazy dog.Dog lazy';//如果pattern是字符串,则仅替换第一个匹配项。
console.log(p.replace('dog', 'monkey'));
// "lazy monkey.Dog lazy"
let regex = /dog/i;//如果非全局匹配,则仅替换第一个匹配项
console.log(p.replace(regex, 'ferret'));
//"lazy ferret.Dog lazy"
regex = /d|Dog/g;
console.log(p.replace(regex, 'ferret'));
//"lazy ferretog.ferret lazy"
//当使用一个 regex 时,您必须设置全局(“g”)标志, 否则,它将引发 TypeError:“必须使用全局 RegExp 调用 replaceAll”。
const p = 'lazy dog.dog lazy';//如果pattern是字符串,则仅替换第一个匹配项。
console.log(p.replaceAll('dog', 'monkey'));
// "lazy monkey.monkey lazy"
let regex = /dog/g;//如果非全局匹配,则仅替换第一个匹配项
console.log(p.replaceAll(regex, 'ferret'));
//"lazy ferret.ferret lazy"
Class
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
// 未命名/匿名类
let Rectangle = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Rectangle.name);
// output: "Rectangle"
// 命名类
let Rectangle = class Rectangle2 {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Rectangle.name);
// 输出:"Rectangle2"
关键字用于调用对象的父对象上的函数。
class Cat {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Lion extends Cat {
constructor(name,id) {
super(name);
this.id = id;
}
speak() {
super.speak();
console.log(this.name + ' roars.');
}
}
private
类属性在默认情况下是
的,但可以使用增加哈希前缀
#
的方法来定义私有类字段
从类外部引用私有字段是错误的。它们只能在类里面中读取或写入。
class ClassWithPrivateField {
#privateField;
}
class ClassWithPrivateMethod {
#privateMethod() {
return 'hello world';
}
}
class ClassWithPrivateStaticField {
static #PRIVATE_STATIC_FIELD;
}
class ClassWithPrivateStaticMethod {
static #privateStaticMethod() {
return 'hello world';
}
}
static 关键字
定义静态方法和值。不能在类的实例上调用静态方法,而应该通过类本身调用
静态方法调用同一个类中的其他静态方法,可使用
this
关键字
class StaticMethodCall {
static staticMethod() {
return 'Static method has been called';
}
static anotherStaticMethod() {
return this.staticMethod() + ' from another static method';
}
}
StaticMethodCall.staticMethod();
// 'Static method has been called'
StaticMethodCall.anotherStaticMethod();
// 'Static method has been called from another static method'
非静态方法中,不能直接使用 关键字来访问静态方法。
而是要用类名来调用:
CLASSNAME.STATIC_METHOD_NAME()
,
或者用构造函数的属性来调用该方法:
this.constructor.STATIC_METHOD_NAME()
.
class StaticMethodCall {
constructor() {
console.log(StaticMethodCall.staticMethod());
// 'static method has been called.'
console.log(this.constructor.staticMethod());
// 'static method has been called.'
}
static staticMethod() {
return 'static method has been called.';
}
}
**1、{}
2、new Object()**
/** 使用{}创建对象,等同于 new Object(); **/ var o = {};
3、使用字面量
var person = {name: 'zhang', age:20}
4、工厂模式
'use strict'; // 使用工厂模式创建对象 // 定义一个工厂方法 function createObject(name){ var o = new Object(); o.name = name; o.sayName = function(){ alert(this.name); }; return o; } var o1 = createObject('zhang'); var o2 = createObject('li'); //缺点:调用的还是不同的方法 //优点:解决了前面的代码重复的问题 alert(o1.sayName===o2.sayName);//false
5、构造函数模式(constructor)
<script> 'use strict'; /** * 构造函数模式创建对象 **/ function Person(name){ this.name = name; this.sayName = function(){ alert(this.name); }; } var p1 = new Person('zhang'); var p2 = new Person('li'); p1.sayName(); p2.sayName(); alert(p1.constructor === p2.constructor);//true alert(p1.constructor === Person);//true alert(typeof(p1));//object alert(p1 instanceof Object); //true alert(p2 instanceof Object); //trueb alert(p1.sayName===p2.sayName);//false </script>
6、原型模式(prototype)
<script> 'use strict'; /* * 原型模式创建对象 */ function Animal() { } Animal.prototype.name = 'animal'; Animal.prototype.sayName = function () { alert(this.name); }; var a1 = new Animal(); var a2 = new Animal(); a1.sayName(); alert(a1.sayName === a2.sayName);//true alert(Animal.prototype.constructor);//function Animal(){} alert(Animal.prototype.constructor==Animal);//true </script>
如果往新建的对象中加入属性,那么这个属性是放在对象中,如果存在与原型同名的属性,也不会改变原型的值。但是访问这个属性,拿到的是对象的值。
访问的顺序:对象本身>构造函数的prototype
如果对象中没有该属性,则去访问prototype,如果prototype中没有,继续访问父类,直到Object,如果都没有找到,返回undefined
<script> 'use strict'; /* * 原型模式创建对象 */ function Animal() { } Animal.prototype.name = 'animal'; Animal.prototype.sayName = function () { alert(this.name); }; var a1 = new Animal(); var a2 = new Animal(); a1.sayName(); alert(a1.sayName === a2.sayName);//true alert(Animal.prototype.constructor);//function Animal(){} //修改a2.name,a1的name不会变 a2.name = 'dog'; a2.sayName();//dog a1.sayName();//animal </script>
假如原型中包含有 引用类型的属性 ,那么如果某个对象修改了该属性的值,所有的该原型创建的对象访问的值都会改变。
<script> 'use strict'; //原型模式2 //存在的问题:如果原型中含有引用类型 function Animal (){} Animal.prototype = { name: 'animal', friends: ['dog','cat'], sayName: function(){ alert(this.name); } }; var a1 = new Animal(); var a2 = new Animal(); a2.friends.push('snake'); alert(a2.friends);//[dog,cat,snake] alert(a1.friends);//[dog,cat,snake] </script>
7、构造函数+原型模式
<script> 'use strict'; function Animal(name){ this.name = name; this.friends = ['dog','cat']; } Animal.prototype.sayName = function(){ alert(this.name); }; var a1 = new Animal('d'); var a2 = new Animal('c'); a1.friends.push('snake'); alert(a1.friends);//[dog,cat,snake] alert(a2.friends);//[dog,cat] </script>
Object
//创建的新对象.prototype=proto(参数对象)
Object.create(proto[, propertiesObject])
propertiesObject 可选
如果该参数被指定且不为 undefined,则该传入对象的 自有 可枚举属性 (即其自身定义的属性,而 不是其原型链上的枚举属性 )将为新创建的对象添加指定的 属性值 和对应的 属性描述符 。这些属性对应于 Object.defineProperties() 的第二个参数。
描述符
- 拥有布尔值的键
configurable
、enumerable
和writable
的 默认值都是false
。- 属性值和函数的键
value
、get
和set
字段的默认值为undefined
。数据描述符 :
configurable
true
时,描述符 才能够被改变 ,同时该属性也能从对应的对象上被 删除 。enumerable 属性
定义了对象的属性是否可以在 循环和 中被枚举。
value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。
writable
当且仅当该属性的
writable
键值为true
时,属性的值,也就是上面的value
,才能被 改变。存取描述符 :
get
属性的 getter 函数,如果没有 getter,则为
undefined
。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入this
对象(由于继承关系,这里的this
并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
set
属性的 setter 函数,如果没有 setter,则为
undefined
。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的this
对象。
描述符可拥有的键值
configurable |
enumerable |
value |
writable |
get |
set |
|
---|---|---|---|---|---|---|
数据描述符 | 可以 | 可以 | 可以 | 可以 | 不可以 | 不可以 |
存取描述符 | 可以 | 可以 | 不可以 | 不可以 | 可以 | 可以 |
如果一个描述符不具有
value
、
writable
、
get
和
set
中的任意一个键,那么它将被认为是一个数据描述符。
如果一个描述符同时拥有
value
或
writable
和
get
或
set
键,则会产生一个
异常
。
function Archiver() {
var temperature = null;
var archive = [];
Object.defineProperty(this, 'temperature', {
get: function() {
console.log('get!');
return temperature;
},
set: function(value) {
temperature = value;
archive.push({ val: temperature });
}
});
this.getArchive = function() { return archive; };
}
var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
//在一个对象上定义一个新的属性或修改现有属性,并返回该对象
Object.defineProperty(obj, prop, descriptor)
const object1 = {};
Object.defineProperty(object1, 'property1', {
value: 42,
writable: false
});
//用 Symbol 类型的值来做对象的 key 与常规的定义或修改不同
//Object.defineProperty 是定义 key 为 Symbol 的属性的方法之一。
Object.defineProperty(o, Symbol.for('e'), {
value: 5,
enumerable: true
});
//在一个对象上定义新的属性或修改现有属性,并返回该对象
Object.defineProperties(obj, props)
var obj = {};
Object.defineProperties(obj, {
'property1': {
value: true,
writable: true
},
'property2': {
value: 'Hello',
writable: false
}
// etc. etc.
});
//Object.prototype.hasOwnProperty()指示对象自身属性中是否具有指定的属性
const object1 = {};
object1.property1 = 42;
console.log(object1.hasOwnProperty('property1'));
// Expected output: true
console.log(object1.hasOwnProperty('hasOwnProperty'));
// Expected output: false
//Object.getPrototypeOf(object)返回对象原型
const prototype1 = {};
const object1 = Object.create(prototype1);
console.log(Object.getPrototypeOf(object1) === prototype1);
// Expected output: true
//Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致。
const object1 = {
a: 'somestring',
b: 42,
c: false
};
console.log(Object.keys(object1));
// Expected output: Array ["a", "b", "c"]
//Object.values() 方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用 for...in 循环的顺序相同(区别在于 for-in 循环枚举原型链中的属性)。
var obj = { foo: 'bar', baz: 42 };
console.log(Object.values(obj)); // ['bar', 42]
// non-object argument will be coerced to an object
console.log(Object.values('foo')); // ['f', 'o', 'o']
Object.create ( 手写)
该函数创建一个新对象,使用现有的对象来提供新创建的对象的proto,核心步骤有:
- 创建一个临时函数
- 将该临时函数的 原型 指向对象参数
- 返回该临时对象的 实例
Object.create法创建一个新对象,使用现有的对象来提供新创建的对象的proto。
const _objectCreate = proto => {
if(typeof proto !== 'object' || proto === null) return
const fn = function() {}
fn.prototype = proto
return new fn()
}
( 手写)
Object.freeze = writable: false + Object.seal = writable: false + Object.preventExtensions + configable: false
- Symbol 类型作为 key 值的情况,也要冻结
- 只冻结对象 自有 的属性(使用 for … in 会把 原型链 上的可枚举属性遍历出来)。
- 注意不可扩展性( 不能添加新属性 ,使用 Object.preventExtensions() 或 Object.seal() 实现,同时也相当于把原型链冻结)。
key:
- Object.get Own PropertyNames/Symbol
- forEach
- Object. define Property: configurable,writable
- Object. preventExtensions (object)
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)
}
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
}
}
}
A rguments
对象
是所有(非箭头)函数中都可用的
局部变量
。类似于
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>
防抖、节流应用
防止某一时间 频繁触发
- 防抖debounce:
time内只执行一次
- search搜索联想,用户 在不断输入值 时,用防抖来 节约请求资源 。
- window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
- 节流throttle:
间隔time执行
- 鼠标不断点击触发 ,mousedown(单位时间内只触发一次)
- 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
(GC)
GC
即
Garbage Collection
浏览器的js具有自动垃圾回收机制, 垃圾回收机制 也就是 自动内存管理 机制,垃圾收集器会 定期 的找出 不可访问的值 ,然后释放内存,所以将不需要的对象设为 null 即可。
内存分配
First-fit
,找到 第一个 的大于等于size
的块立即返回Best-fit
,遍历整个空闲列表,返回 大于等于size
的最小分块Worst-fit
,遍历整个空闲列表,找到 最大的分块 ,然后切成两部分,一部分size
大小,并将该部分返回
Worst-fit
的
空间利用率
看起来是最合理,但实际上切分之后
会造成更多的小块
,形成内存碎片,所以不推荐使用,
First-fit
和
Best-fit
来说,考虑到
分配的速度和效率
First-fit
是更为明智的选择
回收策略
标记清除 (Mark-Sweep):最常用
(根对象,在浏览器环境中包括
全局 Window 对象
、文档DOM树
等)
- 垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾, 全标记为0
- 然后 从各个根对象开始遍历 ,把 不是垃圾的节点改成1
- 清理所有标记为0 的垃圾,销毁并回收它们所占用的内存空间
- 最后,把所有内存中对象标记修改 为0 ,等待下一轮垃圾回收
优点
简单
缺点
- 内存碎片化 ,清除之后,剩余的 对象内存位置 是 不变 的,也会导致空闲内存空间是 不连续 的,出现了
内存碎片
,存在 内存分配 的问题- 分配速度慢 ,因为即便是使用
First-fit
策略,其操作仍是一个O(n)
的操作,最坏情况是每次都要遍历到最后,同时因为碎片化, 大对象的分配效率 会更慢
标记整理(Mark-Compact)
改善 标记清除清除之后 剩余的对象位置不变 而导致的 空闲内存不连续
标记结束后,标记整理算法会将活着的对象(即不需要清理的对象)向内存的一端 移动 ,最后 清理掉边界 的内存
引用计数 (Reference Counting), 早 的一种垃圾回收算法
它把
对象是否不再需要
简化定义为 没有引用 指向该对象( 零引用 ),对象将被垃圾回收机制回收,目前很少使用这种算法了,因为它的问题很多跟踪记录每个变量值被使用的次数
- 当声明了一个变量并且将一个引用类型赋值给该变量的时候这个值的引用次数就为 1
- 如果同一个值又被赋给另一个变量,那么引用数加 1
- 如果该变量的值被其他的值覆盖了,则引用次数减 1
- 当这个值的引用次数变为 0 的时候,说明没有变量在使用,这个值没法被访问了,回收空间,垃圾回收器会在运行的时候清理掉引用次数为 0 的值占用的内存
优点
引用值为 0 时,可以 立即回收 垃圾
缺点
计数器 需要 占内存
不知道被引用数量的 上限
无法解决 循环引用 无法回收的问题,这也是最严重的
内存泄漏
如果 那些不再使用的变量,它们所占用的内存 不去清除的话就会造成内存泄漏
造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果 。
比如说:
1、闭包:在闭包中引入 闭包外部的变量 时,当闭包结束时此对象无法被垃圾回收(GC)。
2、DOM:当原有的DOM被移除时, 子结点引用 没有被移除则无法回收
JS中拥有自动的垃圾回收机制,
js引擎 会优先执行微任务 ,例如:网页加载完毕,但是图片没加载出来
- 微任务 microtask(异步):可以理解为 task 执行完后立刻执行,Promise async/await。
- 宏任务 macrotask: setTimeout,setInterval一类的 定时 事件,Ajax,DOM事件,script 脚本的执行、 I/O 操作、UI 渲 染 等。
例如:new Promise
实例化是同步
,而
then
中
注册的回调
才是
异步
执行的。
例如:等待的客户为宏任务,他的每个业务为微任务
每办理完一个业务,柜员就会问当前的客户,是否还有其他需要办理的业务。 (检查还有没有微任务需要处理)
而客户明确告知说没有事情以后,柜员就去查看后边还有没有等着办理业务的人。 (结束本次宏任务、检查还有没有宏任务需要处理)
这个检查的过程是持续进行的,
每完成一个任务都会进行一次
,而这样的
操作就被称为
Event Loop
setImmediate与setTimeout的区别
setImmediate
为一次
Event Loop
执行完毕后调用。
setTimeout
则是通过计算一个延迟时间后进行执行。
如果在主进程中直接执行这两个操作,很难保证哪个会先触发。
当注册这两个任务耗时超过
delay(s)
,定时器处于可执行回调的状态,会先执行定时器,
执行完定时器以后才是结束了一次
Event Loop
,这时才会执行
setImmediate
。
JS延迟加载的方式
JavaScript 是单线程(js不走完下面不会走是因为 同步 )会 阻塞DOM的解析 ,因此也就会阻塞DOM的加载。所以有时候我们希望延迟JS的加载来提高页面的加载速度。
1.把 JS 放在页面的 最底部( css放顶部,js放底部是框架常见优化 )
script标签的defer属性 :脚本会 立即下载 但延迟到 整个页面加载完毕再执行 。该属性对于 内联 脚本无作用 (即没有 「src」 属性的脚本)。
3.是在 外部JS 加载完成后,浏览器 空闲 时, Load 事件触发前执行,标记为 async 的脚本并不保证按照指定他们的先后顺序执行, 该属性对于 内联 脚本无作用 (即没有 「src」 属性的脚本)。
动态创建script标签 ,监听dom加载完毕再引入js文件
基本概念
SSR (server side rende r)服务端渲染,是指由服务侧(server side)完成 页面的DOM结构拼接 ,然后发送到浏览器,为其绑定状态与事件,成为完全可交互页面的过程。
CSR(client side rend er)客户端渲染,是指由客户端(client side)JS完成页面和数据的拼接,生成DOM结构再交由浏览器渲染成页面的过程。
SPA(single page application) 单页面应用,只是局部更新内容。SPA实现的原理就采用了CSR,页面中所有内容由JS控制,需要浏览器进行JS解析才能显示出来。
SEO(search engine optimization)搜索引擎优化 ,利用搜索 引擎的规则 提高网站在有关搜索引擎内的自然排名。
服务器端渲染SSR
前端耗时少 。因为 后端拼接了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 的值,根据不同的资源类型来用不同的解析方式
渲染详情可见
- 断开 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 的安全传输机制工作在
传输层
常见的请求方式
- POST:用于传输信息给服务器,功能与 GET 类似,但一般推荐使用 POST 方式;
- GET: 用于请求访问已经被 URI(统一资源标识符)识别的资源,可以通过 URL 传参给服务器;
- HEAD:,类似 GET 获得报文首部 ,只是 不返回报文主体 ,一般用于 验证 URI 是否有效;
- PUT: 传输文件 ,报文主体中包含文件内容,保存到对应 URI 位置;
- DELETE:与 PUT 相反, 删除文件 ,删除对应 URI 位置的文件;
- OPTIONS: 查询 相应 URI 支持的 HTTP 方法。
GET和POST发送请求 ⭐⭐
HTTP协议中的两种发送请求的方法。
异同
同 :GET和POST本质上就是 TCP链接
异 :
数据包数量 :GET产生 一个TCP数据包 ;POST产生 两个TCP数据包 。
(并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。)
过程 :
对于GET方式的请求,浏览器会把 http header和data一并发送出去 ,服务器响应 20 0(返回数据);
对于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默认为"F orm “类型。
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 最常见的用法是作为 用户登录凭证 ,赋予原本无状态HTTP 协议以一种状态
:
必选属性:Name Value
可选属性: 有效期、使用范围、安全性 的可选属性组成
Cookie 的作用域是和 domain(域名或 ip)绑定的,端口无关 。
cookie 是不可跨域的: 每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用, 一级域名和二级域名之间是允许共享使用的 ( 靠的是 domain) 。
- Expires/Max-Age属性:有两种存储类型的Cookie: 会话性(默认) 与 持久性 。
会话性 cookie仅保存在 内存中,并在用户 关闭 整个 浏览器时失效 ;
持久性 Cookie会保存在用户的 硬盘 中,直至 生存期 到或用户直接在网页中单击“ 注销 ”等按钮结束会话时才会失效
Max-Age:失效时间(单位秒)
为 负数 ,该 cookie 为 临时 cookie , 关闭浏览器即失效,
如果为 0 ,表示 删除 该 cookie 。 默认为 -1 。
如果 Max-Age 和 Expires 同时存在,以 Max-Age 为准 。
-
Path属性: 指定 cookie 在哪个路径(路由)下生效,默认是 ‘/’ 。
如果设置为
/abc
,则只有/abc
下的路由可以访问到该 cookie,如:/abc/read
-
Domain属性:指定了可以访问该 Cookie 的 Web 域,默认 是 当前域名
-
Secure属性:指定是否使用 安全协议发送Cookie。默认为false。 ( 安全协议有 HTTPS,SSL等,在网络上传输数据之前先将数据加密。 )
-
httpOnly 属性: 无法通过 JS 脚本 读取到该 cookie 的信息,但还是能通过 Application 中手动修改 cookie,所以只是在一定程度上可以防止 XSS 攻击,不是绝对的安全
HTTP 响应报文 通过 Set-Cookie 头字段 给浏览器给当前网站设置 cookie:
HTTP/1.1 200 OK
Set-Cookie: token=abcd; Max-Age=6000; Path=/; Expires=Thu, 28 Apr 2022 16:31:36 GMT; HttpOnly
name=value; (s); ; <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
Cookie的 安全规范 :
- 不放重要数据 ,重要数据放Session。
- Cookie 数据加 签名 。对 Cookie 数据添加签名,这样 Cookie 如果被篡改了,服务端使用的时候通过校验签名就能发现了。
- Cookie数据 加密 。加密后数据就很难篡改了,但是加解密过程会带来 性能损耗 ,
session
浏览器端第一次发送请求到服务器端,服务器端创建一个Session,同时会创建一个特殊的Cookie(name为 JSESSIONID 的 固定值 ,value为session对象的ID)( JSESSIONID: j session id)
服务器端根据该Cookie查询Session对象,从而区分不同用户。
session 是基于 cookie 实现的,
session 存储在服务器端,
sessionId 会被存储到客户端的cookie 中,是连接 Cookie 和 Session 的一道桥梁
共同点
cookie和 都是用来跟踪浏览器 用户身份 的 会话方式 。
用于浏览器中存储数据的
浏览器的本地存储主要分为
Cookie、WebStorage和IndexDB
(
运行在浏览器的
非关系型数据库)
其中
WebStorage
又可以分为
localStorage和sessionStorage
。
Cookie、
localStorage和sessionStorage
同:都是保存在浏览器端、且同源的
异:
cookie
在浏览器和服务器间
来回传递
存储地
- cookie数据保存在 客户端 ,session数据保存在 服务端,session更安全
- sessionStorage和localStorage不会把数据发给服务器,仅在本地保存
存取值的类型
- Cookie 只支持存字符串数据,想要设置其他类型的数据,需要将其转换成字符串,
- Session 可以存任意数据类型。
存储大小
- 单个 Cookie 保存的数据不能超过 4K,
- Session 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。
有效期
localStorage
: 始终有效 ,窗口或浏览器关闭也一直保存,本地存储,因此用作持久数据;- cookie的有效期是可以设置的,默认情况下是 关闭浏览器 后失效。
- sessionStorage的有效期是仅存在于当前会话, 关闭当前会话或者浏览器 后就会失效。
由于关闭浏览器不会导致 session 被删除,迫使服务器为 session 设置了一个失效时间,
当距离客户端上一次使用 session 的时间超过这个失效时间时,
服务器就认为客户端已经停止了活动,才会把 session 删除以节省存储空间
作用域不同
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)。
认证(Authentication)
针对用户,验证当前用户的身份
互联网中的认证:
- 用户名密码登录
- 手机号接收验证码
授权(Authorization)
针对第三方应用,用户授予第三方应用访问该用户某些资源的权限
- 你在安装手机应用的时候,APP 会询问是否允许授予权限(访问相册、地理位置等权限)
- 你在访问微信小程序时,当登录时,小程序会询问是否允许授予权限(获取昵称、头像、地区、性别等个人信息)
实现授权的方式有:cookie、session、token、OAuth
凭证(Credentials)
实现认证和授权的前提 是需要一种 媒介(证书) 来标记访问者的身份
当用户登录成功后,服务器会给该用户使用的浏览器颁发一个令牌(token),这个令牌用来表明你的身份,每次浏览器发送请求时会带上这个令牌
Token(令牌)
Acesss Token
- 访问资源接口(API)时所需要的资源凭证
- 简单 token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)
- 特点:
- 服务端无状态化、可扩展性好
- 支持移动端设备
- 安全
- 支持跨程序调用
- token 的身份验证流程:
基于token的登录流程
-
客户端使用用户名跟密码 请求 登录
-
服务端收到请求,去 验证 用户名与密码
-
验证成功后,服务端会签发一个 Token ,再把这个 Token 发送给客户端
-
客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage
里
-
客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
-
服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
- 每一次请求都需要携带 token, 需要把 token 放到 HTTP 的 Header 里
- 基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库
- token 完全由应用管理,所以它可以避开同源策略
Refresh Token
refresh token 是专用于 刷新 access token 的 token。
如果没有 refresh token,也可以刷新 access token,但每次刷新都要用户输入登录用户名与密码,会很麻烦。
有了 refresh token,可以减少这个麻烦,客户端直接用 refresh token 去更新 access token,无需用户进行额外的操作。
- Access Token 的有效期比较短,当 Acesss Token 由于过期而失效时,使用 Refresh Token 就可以获取到新的 Token,如果 Refresh Token 也失效了,用户就只能 重新登录 了。
- Refresh Token 及 过期时间 是存储在 服务器的数据库 中,只有在 申请新的 Acesss Token 时才会验证,不会对业务接口响应时间造成影响,也不需要向 Session 一样一直保持在内存中以应对大量的请求。
Token 和 Session 的区别
- Session 是一种 记录服务器和客户端会话状态的机制,使服务端有状态化,可以记录会话信息 。
- Token 是 令牌 , 访问资源接口(API)时所需要的资源凭证 。Token 使服务端无状态化,不会存储会话信息。
- 作为身份认证 Token 安全性比 Session 好,因为每一个请求都有 签名 还能 防止监听 以及 重放攻击 ,
- Session 就必须依赖链路层来保障通讯安全了。 如果你需要实现有状态的会话,仍然可以增加 Session 来在服务器端保存一些状态。
- Session 认证只是简单的把 User 信息存储到 Session 里,因为 SessionID 的不可预测性,暂且认为是安全的。
- Token ,如果指的是 OAuth Token 或类似的机制的话,提供的是 认证 和 授权 ,认证是针对用户,授权是针对 App 。其目的是让某 App 有权利访问某用户的信息。这里的 Token 是唯一的。不可以转移到其它 App上,也不可以转到其它用户上。
- Session 只提供一种简单的认证,即只要有此 SessionID ,即认为有此 User 的全部权利。是需要严格保密的,这个数据应该只保存在站方, 不应该共享给其它网站或者第三方 App 。所以简单来说: 如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token 。如果永远只是自己的网站,自己的 App,用什么就无所谓了。
- 移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token
⭐
跨域认证 解决方案。是一种 认证授权机制 。
因为 JWT 通常使用 localstorage (也可以使用 Cookie),所以你可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS)
JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
内容
1.Header
JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存
{
“alg”: “HS256”,
“typ”: “JWT”
}
2.Payload
有效载荷部分,是JWT的 主体 内容部分,也是一个 JSON 对象,包含需要传递的数据。 JWT指定七个默认字段供选择
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
方式
当用户希望访问一个受保护的路由或者资源的时候,可以把它放在 Cookie 里面自动发送,但是这样不能跨域,
所以更好的做法是放在 HTTP 请求头信息的 Authorization 字段里,使用 Bearer 模式添加 JWT 。
GET /calendar/v1/events
Host: api.example.com
Authorization: Bearer <token>
服务端的保护路由将会检查请求头 Authorization 中的 JWT 信息,如果合法,则允许用户的行为。
由于 JWT 是自包含的,因此减少了需要查询数据库的需要
JWT 的这些特性使得我们可以完全依赖其无状态的特性提供数据 API 服务,甚至是创建一个下载流服务。
- 跨域 方式
JWT 放在 POST 请求的数据体 里。
- 通过 URL 传输方式
http://www.example.com/user?token=xxx
优缺点
- 优点:
基于 JSON,方便解析 ,可以在令牌中自定义丰富内容,易扩展。
通过 非对称加密及数字签名 技术,可以防止篡改、安全性高。
可以 不依赖认证服务 就可以完成授权。
- 缺点:
JWT 令牌较长 ,占存储空间比较大。
由于服务器不需要存储 Session 状态,因此使用过程中 无法废弃 某个 Token 或者更改 Token 的权限。也就是说一旦 JWT 签发了,到期之前就会始终有效,除非服务器部署额外的逻辑。
Token 和 JWT 的区别
相同:
- 都是访问资源的令牌
- 都可以 记录用户 的信息
- 都是使 服务端无状态化
- 都是只有验证成功后,客户端才能访问服务端上受保护的资源
区别:
- Token:服务端验证客户端发送过来的 Token 时,还需要 查询数据库获取用户信息 ,然后验证 Token 是否有效。
- JWT: 将 Token 和 Payload 加密后存储于客户端 ,服务端只需要使用 密钥解密 进行校验( 校验也是 JWT 自己实现的 )即可,不需要查询或者减少查询数据库,因为 JWT 自包含了用户信息和加密的数据。
常见的加密算法
哈希算法(Hash Algorithm)又称散列算法
数据交换格式
服务器端与客户端之间进行数据传输与交换的格式。
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对象)
//Object { name: "参宿", id: 7 }
var jsonstr = JSON.stringify(obj); //JSON.stringify(obj)
console.log(jsonstr); //JSON的生成(JS对象转换为JSON字符串)
JSON.parse(text[, reviver])//reviver函数参数,修改解析生成的原始值,调用时机在 parse 函数返回之前。
//k:key,v:value
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
// ""
异步⭐⭐⭐
- 异步 是通过一次次的 循环事件队列 来实现的.例如 setTimeout setInterval xmlHttprequest 等
- 同步 是 阻塞式的IO ,在 高并发 环境会是一个很大的性能问题,
所以同步一般只在基础框架的 启动 时使用,用来 加载配置 文件, 初始化 程序什么的.
node 是 单线程 的, 网络请求,浏览器 事件等操作都需要使用 异步 的方法。
webworker(创建分线程)⭐
当在 HTML 页面中 执行脚本 时, 页面的状态是不可响应的 ,直到脚本已完成。
web worker 是 运行在后台 的 JavaScript,独立于其他脚本, 不会影响页面的性能 。
index.js为加载到html页面中的主线程(js文件)
work.js为在index中创建的分线程
werbwork的实现方法
在index.js中 创建分线程 var w =new webwork('work.js')//创建 Web Worker 对象
在index.js中 通过 w.postmesage('数据') 向子线程发送数据
在work.js中 通过
onmessage = function(ev) {
console.log(ev);//接受主线程发送过来的ev.data数据
this.postMessage('数据')//通过 postmesage('数据') 向主线程发送数据
}
在index.js中 通过w.onmessage=function(ev){ev.data} ev.data 接受 a 的值.
在work.js中 w.terminate();//终止 Web Worker
AJAX
- ajax 全名 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请求,必须设置请求头。
//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();
}
}
*jQuery ajax
$(function(){
// jQuery里使用ajax
$.ajax({
url:"/js/data.json",
type:'GET',
dataType:'json',
data:{'aa':1},
})
//设置请求成功后的回调函数
.done(function(dat){
console.log(dat);
})
//设置请求失败后的回调函数和
.fail(function(dat){
console.log('no!');
})
})
是一个 JavaScript 工具库
⭐⭐
axios 是一个基于 的 ajax的封装 ,用于 浏览器和 nodejs 的 HTTP 客户端 。
特征:
- 从浏览器中创建 XMLHttpRequest
- 从 node.js 发出 http 请求
- 拦截 请求和响应
- 转换 请求和响应数据
- 取消 请求
- 自动转换JSON数据
- 客户端支持防止 CSRF
import axios from "axios";
axios.get('/users')
.then(res => {
console.log(res.data);
});
// 向给定ID的用户发起请求
axios.get('/user?ID=12345')
.then(function (response) {
// 处理成功情况
console.log(response);
})
.catch(function (error) {
// 处理错误情况
console.log(error);
})
.then(function () {
// 总是会执行
});
axios.get('/user?ID=12345')===
axios.get('/user', {
params: {
ID: 12345
}
})
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
Axios API
1.axios(config)
// 发送 POST 请求
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});
2.axios(url[, config])
// 发送 GET 请求(默认的方法)
axios('/user/12345');
// 在 node.js 用GET请求获取远程图片
axios({
method: 'get',
url: 'http://bit.ly/2mTM3nY',
responseType: 'stream'
})
.then(function (response) {
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
});
axios.get(http://localhost:3000/search/users?q=${keyWord}
)
get(’’) (自动默认) 和get(``)(反单引号)均可
setTimeout()(倒)计时(手写)⭐
setInterval()
方法重复调用一个函数或执行一个代码段,在每次调用之间具有固定的时间延迟。
//返回值timeoutID是一个正整数,表示定时器的编号。
let timeoutID = scope.setTimeout(function[, delay]),//delay表示最小等待时间,真正等待时间取决于前面排队的消息
clearTimeout(timeoutID) //取消该定时器。
<body>
<button onclick="startCount()">开始计数!</button> <input type="text" id="txt">
<button onclick="stopCount()">停止计数!</button>
</body>
<script type="text/javascript">
var c = 0;
var t;
var timer = 0;
function timedCount() {
document.getElementById("txt").value = c;
c = c + 1;
t = setTimeout(function() {
timedCount()
}, 1000);
}
function startCount() {
if (!timer) {
timer = 1;
timedCount();
}
}
function stopCount() {
clearTimeout(t);
timer = 0;
}
</script>
<body>
<button onclick="startCount()">开始倒计时</button> <input type="text" id="txt">
</body>
<script type="text/javascript">
var c = 0;
var t;
function timedCount() {
c -=1;
document.getElementById("txt").value=c;
if(c===0){
clearTimeout(t);
return;
}
t = setTimeout(function() {
timedCount()
}, 1000);
}
function startCount() {
c=document.getElementById("txt").value;
timedCount();
}
</script>
setTimeout()和setInterval()⭐
在 同一个对象 上(一个 window 或者 worker ),
setTimeout()
或者setInterval()
在后续的调用 不会重用同一个定时器编号 。如果省略delay, 取默认值 0,意味着“马上”执行,或者尽快执行。
不同的对象 使用 独立的编号池
浏览器会控制 最小的超时时间 固定在 4ms到15ms 之间。
setTimeout
:延时delay毫秒之后,直接将 回调函数 加入 事件队列 。
setInterval
:延时delay毫秒之后,先看看事件队列中是否存在还没有执行setInterval的回调函数,如果存在,就不要再往事件队列里加入回调函数了。
setTimeout
保证 调用 的时间间隔是一致的,
setInterval
的设定的间隔时间包括了 执行回调 的时间。
setInterval的回调函数 不报错
Promise(ES6 解决地狱回调)⭐⭐⭐
回调函数 :
当一个函数作为参数传入另一个函数中,并且它不会立即执行,只有当满足一定条件后该函数才可以执行,这种函数就称为回调函数,例如setTimeout
回调地狱:回调函数套回调函数, 回调的多重嵌套,会导致代码可读低、编写费劲、容易出错,故而被称为 callback hell
Promise:ES6异步操作的一种解决方案
Promise 构造函数 接受一个 函数 作为 参数 ,该函数是 同步 的并且会被 立即执行 ,称为 起始函数 。
起始函数 包含两个 函数参数 resolve 和 reject ,分别表示 Promise 成功和失败的状态。
起始函数执行成功时,它应该调用 resolve 函数并传递成功的结果。
起始函数执行失败时,它应该调用 reject 函数并传递失败的原因。
promise 有三个状态:
pending[待定]初始状态,fulfilled[实现]操作成功,rejected[被否决]操作失败
如果不使用 resove 和 reject 两个函数 状态为pendding
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");
});
promise调用then,
如果非promise,会将值包装成promise
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);
});
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(resolveFn, rejectFn) {
//return一个新的promise
return new MyPromise((resolve, reject) => {
//把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
const fulfilledFn = value => {
try {
//执行第一个(当前的)Promise的成功回调,并获取返回值
let x = resolveFn(value)
//分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
//这里resolve之后,就能被下一个.then()的回调获取到返回值,从而实现链式调用
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
//把后续then收集的依赖都push进当前Promise的成功回调队列中(_resolveQueue), 这是为了保证顺序调用
this._resolveQueue.push(fulfilledFn)
//reject同理
const rejectedFn = error => {
try {
let x = rejectFn(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
this._rejectQueue.push(rejectedFn)
})
}
- 值穿透 :根据规范,如果 then() 接收的参数不是function,那么我们应该忽略它。
如果没有忽略,当then()回调不为function时将会抛出异常,导致链式调用中断
- 处理状态为resolve/reject的情况
:其实我们上边 then() 的写法是对应状态为pending的情况,但是有些时候,resolve/reject 在 then() 之前就被执行(比如
Promise.resolve().then()
),如果这个时候还把then()回调push进resolve/reject的执行队列里,那么回调将不会被执行,因此对于状态已经变为fulfilled
或rejected
的情况,我们直接执行then回调
// then方法,接收一个成功的回调和一个失败的回调
then(resolveFn, rejectFn) {
// 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
typeof resolveFn !== 'function' ? resolveFn = value => value : null
typeof rejectFn !== 'function' ? rejectFn = reason => {
throw new Error(reason instanceof Error? reason.message:reason);
} : null
// return一个新的promise
return new MyPromise((resolve, reject) => {
// 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
const fulfilledFn = value => {
try {
// 执行第一个(当前的)Promise的成功回调,并获取返回值
let x = resolveFn(value)
// 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
// reject同理
const rejectedFn = error => {
try {
let x = rejectFn(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
switch (this._status) {
// 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
case PENDING:
this._resolveQueue.push(fulfilledFn)
this._rejectQueue.push(rejectedFn)
break;
// 当状态已经变为resolve/reject时,直接执行then回调
case FULFILLED:
fulfilledFn(this._value) // this._value是上一个then回调return的值(见完整版代码)
break;
case REJECTED:
rejectedFn(this._value)
break;
}
})
}
兼容同步任务
executor是异步任务:
Promise的执行顺序是
new Promise -> then()收集回调 -> resolve/reject执行回调
executor是同步任务:
new Promise -> resolve/reject执行回调 -> then()收集回调
,resolve的执行跑到then之前去了,
为了兼容这种情况,给
resolve/reject
执行回调的操作包一个setTimeout,让它异步执行。
//Promise/A+规定的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
// 构造方法接收一个回调
constructor(executor) {
this._status = PENDING // Promise状态
this._value = undefined // 储存then回调return的值
this._resolveQueue = [] // 成功队列, resolve时触发
this._rejectQueue = [] // 失败队列, reject时触发
// 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
let _resolve = (val) => {
//把resolve执行回调的操作封装成一个函数,放进setTimeout里,以兼容executor是同步代码的情况
const run = () => {
if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = FULFILLED // 变更状态
this._value = val // 储存当前value
// 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
// 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
while(this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(val)
}
}
setTimeout(run)
}
// 实现同resolve
let _reject = (val) => {
const run = () => {
if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = REJECTED // 变更状态
this._value = val // 储存当前value
while(this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(val)
}
}
setTimeout(run)
}
// new Promise()时立即执行executor,并传入resolve和reject
executor(_resolve, _reject)
}
// then方法,接收一个成功的回调和一个失败的回调
then(resolveFn, rejectFn) {
// 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
typeof resolveFn !== 'function' ? resolveFn = value => value : null
typeof rejectFn !== 'function' ? rejectFn = reason => {
throw new Error(reason instanceof Error? reason.message:reason);
} : null
// return一个新的promise
return new MyPromise((resolve, reject) => {
// 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
const fulfilledFn = value => {
try {
// 执行第一个(当前的)Promise的成功回调,并获取返回值
let x = resolveFn(value)
// 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
// reject同理
const rejectedFn = error => {
try {
let x = rejectFn(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
switch (this._status) {
// 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
case PENDING:
this._resolveQueue.push(fulfilledFn)
this._rejectQueue.push(rejectedFn)
break;
// 当状态已经变为resolve/reject时,直接执行then回调
case FULFILLED:
fulfilledFn(this._value) // this._value是上一个then回调return的值(见完整版代码)
break;
case REJECTED:
rejectedFn(this._value)
break;
}
})
}
}
Promise.prototype.catch()
catch()方法
返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。
//catch方法其实就是执行一下then的第二个回调
catch(rejectFn) {
return this.then(undefined, rejectFn)
}
Promise.prototype.finally()
finally()方法
返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。在finally之后,还可以继续then。并且会将值原封不动的传递给后面的then
//finally方法
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value), // MyPromise.resolve执行回调,并在then中return结果传递给后面的Promise
reason => MyPromise.resolve(callback()).then(() => { throw reason }) // reject同理
)
}
复制代码
MyPromise.resolve(callback())
的意义:这个写法其实涉及到一个
finally()
的使用细节,
finally()如果return了一个reject状态的Promise,将会改变当前Promise的状态
,这个
MyPromise.resolve
就用于改变Promise状态,在finally()没有返回reject态Promise或throw错误的情况下,去掉
MyPromise.resolve
也是一样的
Promise.resolve()
Promise.resolve(value)
方法返回一个以给定值解析后的Promise 对象。如果该值为promise,返回这个promise;如果这个值是thenable(即带有"then” 方法)),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。
//静态的resolve方法
static resolve(value) {
if(value instanceof MyPromise) return value // 根据规范, 如果参数是Promise实例, 直接return这个实例
return new MyPromise(resolve => resolve(value))
}
复制代码
Promise.reject()
Promise.reject()
方法返回一个带有拒绝原因的Promise对象。
//静态的reject方法
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason))
}
Promise.all()
Promise.all(iterable)
方法 返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve) ;如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是 第一个失败 promise 的结果 。
//静态的all方法
static all(promiseArr) {
let index = 0
let result = []
return new MyPromise((resolve, reject) => {
promiseArr.forEach((p, i) => {
//Promise.resolve(p)用于处理传入值不为Promise的情况
MyPromise.resolve(p).then(
val => {
index++
result[i] = val
//所有then执行后, resolve结果
if(index === promiseArr.length) {
resolve(result)
}
},
err => {
//有一个Promise被reject时,MyPromise的状态变为reject
reject(err)
}
)
})
})
}
复制代码
Promise.race()
Promise.race(iterable)
方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
static race(promiseArr) {
return new MyPromise((resolve, reject) => {
//同时执行Promise,如果有一个Promise的状态发生改变,就变更新MyPromise的状态
for (let p of promiseArr) {
MyPromise.resolve(p).then( //Promise.resolve(p)用于处理传入值不为Promise的情况
value => {
resolve(value) //注意这个resolve是上边new MyPromise的
},
err => {
reject(err)
}
)
}
})
}
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');//setTimeout(function[, delay, arg1, arg2, ...]);
});
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));
Mypromise
//Promise/A+规范的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
// 构造方法接收一个回调
constructor(executor) {
this._status = PENDING // Promise状态
this._resolveQueue = [] // 成功队列, resolve时触发
this._rejectQueue = [] // 失败队列, reject时触发
// 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
let _resolve = (val) => {
if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = FULFILLED // 变更状态
// 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
// 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
while(this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(val)
}
}
// 实现同resolve
let _reject = (val) => {
if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = REJECTED // 变更状态
while(this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(val)
}
}
// new Promise()时立即执行executor,并传入resolve和reject
executor(_resolve, _reject)
}
// then方法,接收一个成功的回调和一个失败的回调
then(resolveFn, rejectFn) {
this._resolveQueue.push(resolveFn)
this._rejectQueue.push(rejectFn)
}
}
fetch(ES6 拉取网络资源)
以前 发送请求 ,使用ajax或者axios,现在还可以使用fetch。这个是 window自带 的方法,和 xhr 是一个级别的。(xhr=new XMLHttpRequest())
fetch(input[, init]);
// url (必须), options (可选)
fetch('/some/url', {method: 'get'})
.then(function(response) {
})
.catch(function(err) {
// 出错了;等价于 then 的第二个参数,但这样更好用更直观 :(
});
第二个 then 接收的才是后台传过来的真正的数据
window.fetch(url, { method: 'get'}) //fetch() 返回响应的promise
// 第一个then 设置请求的格式
.then(res => return res.json()) // 第一层then返回的是:响应的报文对象
.then((data) => { //第二层:如果想要使用第一层的数据,必须把数据序列化
res.json() 返回的对象是一个 Promise对象再进行then()
<!-- data为真正数据 -->
}).catch(e => console.log("Oops, error", e))
*Generator
ES6 引入Generator 函数是一个 状态机 ,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
yield
表达式就是暂停标志,把函数的
执行流挂起
,通过
next()
方法可以
切换到下一个状态
,为改变执行流程提供了可能,从而为异步编程提供解决方案。
function* myGenerator() {
yield '1'
yield '2'
return '3'
}
const gen = myGenerator(); // 获取迭代器
gen.next() //{value: "1", done: false}
gen.next() //{value: "2", done: false}
gen.next() //{value: "3", done: true}
-
形式上,Generator是一个普通函数。
区别一是 function命令和函数名之间有一个星号 *
区别二是 函数体内部使用yield定义不同的状态 。
-
调用后函数并不执行,返回的是 一个指向内部状态的指针对象 Iterator。
function* gen(x){
console.log('x='+x)
var y = yield x + 2;
return y;
}
//调用Generator 函数
var g = gen(1);
g.next();
// x=1
// {value: 3, done: false}
async/await函数
异步函数实际上原理与 Promise 原生 API 的机制是一模一样的,只不过更 便于阅读。
相当于promise用法的 语法糖,async/await实际上是对Generator(生成器)的封装 。
ES6 引入了 Generator 函数,被ES7 提出的async/await取代了,
将Generator函数的星号 * 替换成async,将yield替换成await。
相对于Generator函数的改进: 自带执行器 ,会自动执行。
await
规定了异步操作只能一个一个排队执行,从而达到
用同步方式,执行异步操作
的效果
Promise.resolve(a)
.then(b => {
// do something
})
.then(c => {
// do something
})
//等价于
async () => {
const a = await Promise.resolve(a);
const b = await Promise.resolve(b);
const c = await Promise.resolve(c);
}
async
关键字+函数
,表明该函数内部有异步
操作
。
函数
返回
的是一个状态为
fulfilled
的
Promise对象
,
如果结果是 值 ,会经过 Promise包装 返回。
如果是 promise则会等待promaise 返回结果,否则, 就直接 返回对应的值,
await 操作符 + promise对象 , 用于组成 表达式
awai+
值
,就会转到一个立即
resolve
的Promise对象。
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();
SPA和MPA
- 爬虫在爬取的过程中,不会去执行js,所以隐藏在js中的跳转也不会获取到
单页Web应用(single page web application,SPA)。
整个应用只有一个完整的页面。
点击页面中的链接不会刷新页面,只会做页面的 局部更新 。
数据都需要通过 ajax请求 获取,并在前端 异步 展现
字符串 | 说明 |
---|---|
:// | 协议符号 |
/ | 分隔目录和子目录 |
测试 | 代表需要编译处理了的路径 |
? | 分隔实际的URL和参数 |
& | URL中指定的参数间的 分隔符 |
= | 左边为参数名、右边参数值 |
搜&索 | 搜索词含有中文,含有保留字段,需要编译 |
#
符号的url就是一个 Fragment URL。
#
指定了网页中的一个位置
浏览器就会查询网页中
name
属性值匹配
print
的
<a>
标签。即:
<a name="print"></a>
,
或者是
id
属性匹配
print
的
<a>
标签。即
<a id="print"></a>
匹配后,浏览器会将该部分滚动到可视区域的顶部。
#
仅
仅作用于浏览器
,它不会影响服务器端。所以http请求中不会包括
#
。
URL (Uniform Resource Locator ),统一资源定位符,对可以从互联网上得到的资源的 位置 和 访问方法 的一种简洁的表示,是互联网上标准 资源唯一 的地址 。
URI(Uniform Resource Identifier ),统一资源标识符,结构如下
foo://example.com:8042/over/there?name=ferret#nose
_/ ______________/ ________/_________/ __/
| | | | |
scheme authority path query fragment
URL 是 URI 的一个 子集 , URL 同时说明 访问方式
⭐⭐⭐
所谓同源(域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能
如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。(跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。)
同源策略会阻止一个域的 脚本和另外一个域的内容进行交互。
( 服务端能收到请求并正常返回结果,只是结果被浏览器拦截了 。)
服务器与服务器之间是可以通信的不受同源策略的影响:Nginx反向代理,proxy代理
同源策略限制内容有:
- Cookie、LocalStorage 、IndexedDB 等 存储 性内容
- DOM 节点
- AJAX 请求 发送后,结果被浏览器拦截了
允许跨域加载资源的标签:
- script标签的跨域
功能:
<img src=XXX><script src=XXX>
- 规定
外部脚本文件的 URL:
<link href=XXX>
跨域的解决方案思路两种,绕过去和cors;
- iframe 方式 可传递数据 ,但组织和控制代码逻辑太 复杂 ,鸡肋;
- jsonp 适合加载不同域名的 js、css,img 等 静态资源 ,现在浏览器 兼容性高 了,以及受限于仅get方式, 逐步淘汰 了;
- Nginx反向代理和nodejs中间件跨域 原理都相似,是 绕过去 的方式,是从古至今 通用 的没完解决方案,适合 前后端分离 的前端项目调后端接口。都是搭建一个服务器,直接在服务器端请求HTTP接口,缺点也许是 服务器压力大一点 ,实际中那点压力根本不是大问题;同时反向代理更适合 内部 应用间访问和共享;
- cors 才是 真正 的称得上跨域请求解决方案(支持 所有类型的HTTP请求 ,但浏览器 IE10以下不支持 )适合做ajax各种跨域请求;
- websocket都是 HTML5新特性 , 兼容性不是很好 ,只适用于主流浏览器和IE10+。
JSONP跨域 ( 手写)
服务器与客户端跨源通信 的 常用 方法。
JSONP(JSON With
Padding
)是利用
<script src=XXX>跨域
因为是 动态创建script标签 ,所以它 只支持get请求 , 不支持post请求 。
- 优点 :
- 简单 适用,
- 兼容低版本IE ,可以向 不支持CORS 的网站请求数据( IE<=9, Opera<12, or Firefox<3.5 )
- 不足 :
- 只支持get请求,
- 只支持跨域 HTTP 请求 不安全,可能遇到 XSS攻击 ,
- 不能解决 不同域 的两个页面之间如何进行 Javascript 调用
原生实现
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);
// 回调执行函数
function handleCallback(res) {
alert(JSON.stringify(res));
}
</script>
- src=跨域的API数据接口地址+向服务器传递该函数名(?问号传参
?user=admin&callback=handleCallback)
- 客户端声明一个回调函数,其 函数名 当做 参数值 ,要 传递 给跨域请求数据的 服务器 ,函数 形参 为要获取目标数据( 服务器返回的data )。
- 服务器接收到请求后,查找数据库,把 返回的data 和传递进来的函数名拼接成一个字符串,
- 例如:传递进去的函数名是handleCallback,它准备好的数据是handleCallback
('res')
。 - 服务器把准备的数据通过 HTTP 协议返回给客户端,客户端再调用执行之前声明的回调函数handleCallback,对返回的数据进行操作。
封装JSONP
在开发中可能会遇到多个 JSONP 请求的回调函数名是相同的,这时需封装一个 JSONP函数。
// index.html
function jsonp({ url, params, callback }) {
return new Promise((resolve, reject) => {
let script = document.createElement('script')
window[callback] = function(data) {
resolve(data)
document.body.removeChild(script)
}
//抽出参数,新建对象
params = { ...params, callback } // wd=b&callback=show
let arrs = []
for (let key in params) {
arrs.push(`${key}=${params[key]}`)
}
script.src = `${url}?${arrs.join('&')}`//拼接
document.body.appendChild(script)
})
}
jsonp({
url: 'http://localhost:3000/say',
params: { wd: 'Iloveyou' },
callback: 'show'
}).then(data => {
console.log(data)
})
向
http://localhost:3000/say?wd=Iloveyou&callback=show
这个地址请求数据,然后后台返回
show('我不爱你')
,最后会运行show()这个函数,打印出’我不爱你’
// server.js
let express = require('express')
let app = express()
app.get('/say', function(req, res) {
let { wd, callback } = req.query
console.log(wd) // Iloveyou
console.log(callback) // show
res.end(`${callback}('我不爱你')`)
})
app.listen(3000)
jQuery实现
JSONP都是 GET和异步 请求的,不存在其他的请求方式和同步请求,
且jQuery 默认 就会给JSONP的请求 清除缓存 。
$.ajax({
url:"http://crossdomain.com/jsonServerResponse",
dataType:"jsonp",
type:"get",//可以省略
jsonpCallback:"show",//->自定义传递给服务器的函数名,而不是使用jQuery自动生成的,可省略
jsonp:"callback",//->把传递函数名的那个形参callback,可省略
success:function (data){
console.log(data);}
});
( 手写)
Cross-Origin Resource Sharing W3C 标准 CORS( 主流 的解决方案,推荐使用)
允许浏览器
向跨源服务器发送
XMLHttpRequest
请求
,从而
克服
了
AJAX 只能同源
使用的限制
属于 跨源 AJAX 请求 的 根本解决 方法, 最常用 的一种解决办法
目前, 所有浏览器 都支持该功能, IE浏览器不能低于IE10
CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现 。
服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
CORS分为简单请求,非简单/复杂请求
简单请求
只要同时满足以下两大条件,就属于简单请求
条件1:使用下列方法之一:
- GET
- HEAD
- POST
条件2:Content-Type 的值仅限于下列三者之一:
- text/xxx
- multipart/form-data( 键值对 型数据)
- application/x-www-form-urlencoded( URL encoded)( 默认 )
application/xml 、 text/xml、text/html、text/plain的区别
1、text/html是 html格式 的正文
2、text/plain是 无格式 正文(可以有效避免 XSS 漏洞)
3、text/xml 忽略 xml头所指定编码格式而默认采用us- ascii 编码
4、application/xml会根据xml头指定的编码格式来编码:
请求中的任意 XMLHttpRequestUpload 对象 均 没有注册 任何 事件监听器 ;
XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问 。
GET /cors? HTTP/1.1
Host: localhost:2333
Connection: keep-alive
Origin: http://localhost:2332
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Accept: */*
Referer: http://localhost:2332/CORS.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
If-None-Match: W/"1-NWoZK3kTsExUV00Ywo1G5jlUKKs"
普通跨域请求
只需服务器端设置Access-Control-Allow-Origin( 表示接受那些域名的请求(*为所有) )
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
( 手写)
前后端都需要进行设置
Access-Control-Allow-Credentials 值为true时,Access-Control-Allow-Origin必须有明确的值,不能是通配符(*)
withCredentials 表示 跨域请求是否提供凭据信息 (cookie、HTTP认证及客户端SSL证明等)
cors中间件
cors是Express的一个第三方中间件
使用的步骤分为如下三步:
1.运行 npm install cors 安装中间件
2.使用const cores = require(‘cors’) 导入中间件
3.在路由之前调用app.use(cors())配置中间件
复杂请求
在正式通信之前,增加一次HTTP 查询 请求,称为"预检"请求,该请求是 option 方法的,通过该请求来知道 服务端是否允许 跨域请求。
用
PUT
向后台请求时,后台需做如下配置:
// 允许哪个方法访问我
res.setHeader('Access-Control-Allow-Methods', 'PUT')
// 预检的存活时间
res.setHeader('Access-Control-Max-Age', 6)
// OPTIONS请求不做任何处理
if (req.method === 'OPTIONS') {
res.end()
}
// 定义后台返回的内容
app.put('/getData', function(req, res) {
console.log(req.headers)
res.end('我不爱你')
})
完整复杂请求的例子,并且介绍下CORS请求相关的字段:
// index.html
let xhr = new XMLHttpRequest()
document.cookie = 'name=xiamen' // cookie不能跨域
xhr.withCredentials = true // 前端设置是否带cookie
xhr.open('PUT', 'http://localhost:4000/getData', true)
xhr.setRequestHeader('name', 'xiamen')
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
console.log(xhr.response)
//得到响应头,后台需设置Access-Control-Expose-Headers
console.log(xhr.getResponseHeader('name'))
}
}
}
xhr.send()
//server1.js
let express = require('express');
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
//server2.js
let express = require('express')
let app = express()
let whitList = ['http://localhost:3000'] //设置白名单
app.use(function(req, res, next) {
let origin = req.headers.origin
if (whitList.includes(origin)) {
// 设置哪个源可以访问我
res.setHeader('Access-Control-Allow-Origin', origin)
// 允许携带哪个头访问我
res.setHeader('Access-Control-Allow-Headers', 'name')
// 允许哪个方法访问我
res.setHeader('Access-Control-Allow-Methods', 'PUT')
// 允许携带cookie
res.setHeader('Access-Control-Allow-Credentials', true)
// 预检的存活时间
res.setHeader('Access-Control-Max-Age', 6)
// 允许返回的头
res.setHeader('Access-Control-Expose-Headers', 'name')
if (req.method === 'OPTIONS') {
res.end() // OPTIONS请求不做任何处理
}
}
next()
})
app.put('/getData', function(req, res) {
console.log(req.headers)
res.setHeader('name', 'jw') //返回一个响应头,后台需设置
res.end('我不爱你')
})
app.get('/getData', function(req, res) {
console.log(req.headers)
res.end('我不爱你')
})
app.use(express.static(__dirname))
app.listen(4000)
上述代码由
http://localhost:3000/index.html
向
http://localhost:4000/
跨域请求,
后端是实现 CORS 通信的关键
。
postMessage
HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以 跨域操作的window属 性之一
- 页面 和其打开的 新窗口 的数据传递
- 多窗口 之间消息传递
- 页面与嵌套的iframe消息传递
- 上面三个场景的跨域数据传递
postMessage()方法允许来自 不同源的脚本 采用 异步 方式进行 有限 的通信,
可以实现 跨文本档、多窗口、跨域 消息传递。
otherWindow.postMessage(message, targetOrigin, [transfer]);
- message: 将要发送到其他 window的数据。
- targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串”*"(表示无限制)或者一个URI。
- transfer(可选):是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。
http://localhost:3000/a.html
页面向
http://localhost:4000/b.html
传递“我爱你”,然后后者传回"我不爱你”。
// a.html
//内联框架元素 (<iframe>) 表示嵌套的browsing context。将另一个 HTML 页面嵌入到当前页面中。
<iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe> //等它加载完触发一个事件
//内嵌在http://localhost:3000/a.html
<script>
function load() {
let frame = document.getElementById('frame')
frame.contentWindow.postMessage('我爱你', 'http://localhost:4000') //发送数据
window.onmessage = function(e) { //接受返回数据
console.log(e.data) //我不爱你
}
}
</script>
// b.html
window.onmessage = function(e) {
console.log(e.data) //我爱你
e.source.postMessage('我不爱你', e.origin)
}
代理服务器
前端配置一个代理服务器代替浏览器去发送请求:
因为服务器与服务器之间是可以通信的不受同源策略的影响。
target
表示所要拦截的目标对象(
任何类型的对象
,包括原生数组,函数,甚至另一个
代理
)
handler
通常以
函数
作为属性的对象,各属性中的函数分别定义了在执行各种操作时的代理行为
const p = new Proxy(target, handler)
Reflect(ES6)
ES6
中操作对象而提供的新
API
,若需要在
Proxy
内部调用对象
的默认行为,建议使用
Reflect
- 只要
Proxy
对象具有的代理方法,Reflect
对象全部具有,以 静态方法 的形式存在 - 修改某些
Object
方法的返回结果,让其变得更合理(定义 不存在 属性行为的时候不报错而是返回false
) - 让
Object
操作 都变成 函数 行为
handler
拦截属性
target,propKey,value,receiver:目标对象、属性名、属性值
proxy
实例本身
以下为关键属性
get(target,propKey,receiver)
拦截对象属性的读取
let person = {
name: "Guest"
};
let proxy = new Proxy(person, {
get: function(target, propKey, receiver) {
return Reflect.get(target, propKey, receiver)
// or
// return target[propKey]
}
});
proxy.name // "Guest"
get
能够对数组
增删改查进行拦截
,将读取数组负数的索引
function createArray(...elements) {
//handler对象
let handler = {
//get函数属性
get(target, propKey, receiver) {
let index = Number(propKey);
//实现循环索引
if (index < 0) {
propKey = String(target.length + index);
}
return Reflect.get(target, propKey, receiver);
}
};
let target = [];
target.push(...elements);
return new Proxy(target, handler);
}
let arr = createArray('a', 'b', 'c');
arr[-1] // c
set(target,propKey,value,receiver)
拦截对象属性的设置
现定义一个对象 规定 年龄输入整数时才被赋值,访问无效属性时控制台提醒
const obj = { name: "张三", age: 18 };
const proxy = new Proxy(obj, {
get(target, prop) {
if (prop in target) {
return Reflect.get(target, prop);
} else {
console.error("字段不存在")
return undefined;
}
},
set(target, propKey, value, receiver) {
if (propKey === "age") {
if (typeof value === "number") {
return Reflect.set(target, propKey, value, receiver);
// or
// target[propKey] = value
// return true
} else {
console.error("年龄只能输入正整数");
return false;
}
} else {
return false;
}
}
});
proxy.age = 20;
console.log(proxy.age); // 20
proxy.age = "22";
console.log(proxy.age); // 20
console.log(proxy.test); // undefined
deleteProperty(target,propKey)
拦截
delete proxy[propKey]
的操作,返回一个布尔值
如果这个方法抛出错误或者返回
false
,当前属性就无法被
delete
命令删除
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(`无法删除私有属性`);
}
}
var handler = {
deleteProperty (target, key) {
invariant(key, 'delete');
Reflect.deleteProperty(target,key)
return true;
}
};
var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: 无法删除私有属性
取消代理Proxy.revocable(target, handler)
Proxy.revocable(target, handler);
应用
类似于设计模式中的 代理 模式,常用功能如下:
- 拦截和监视外部对对象的访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行 校验 或对所需资源进行管理
使用
Proxy
保障数据类型的准确性
let data = { num: 0 };
data = new Proxy(data, {
set(target, key, value, proxy) {
if (typeof value !== 'number') {
throw Error("属性只能是number类型");
}
return Reflect.set(target, key, value, proxy);
}
});
data.num = "foo"
// Error: 属性只能是number类型
data.num = 1
// 赋值成功
声明一个私有的
apiKey
,便于
api
这个对象内部的方法调用
let api = {
_apiKey: 'kafakakafaka',
};
//私有属性名 常量数组
const RESTRICTED = ['_apiKey'];
api = new Proxy(api, {
get(target, key, receiver) {
if(RESTRICTED.indexOf(key) > -1) {
throw Error(`${key} 不可访问.`);
}
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
if(RESTRICTED.indexOf(key) > -1) {
throw Error(`${key} 不可修改`);
}
return Reflect.set(target, key, value, receiver);
}
});
console.log(api._apiKey)
api._apiKey = '987654321'
// 上述都抛出错误
观察者模式(Observer mode):函数 自动观察 数据对象,一旦对象有变化,函数就会自动执行
observable
函数返回一个原始对象的
Proxy
代理,拦截赋值操作,触发充当观察者的各个函数
//观察者函数都放进Set集合
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});
//当修改obj的值,在会set函数中拦截
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
//自动执行Set所有的观察者
queuedObservers.forEach(observer => observer());
return result;
}
Nginx反向代理
nginx代理跨域,实质和 CORS跨域原理 一样,通过配置文件设置 请求响应头Access-Control-Allow-Origin. ..等字段。
是高性能的 HTTP 和 的web服务器
是最简单的跨域方式,只需要修改 nginx 的配置 即可解决跨域问题
- node中间件和nginx反向代理,都是搭建一个中转 nginx 服务器,用于 转发请求 。
- 请求发给代理服务器, 静态页面 和 代理服务器是同源 的,
- 代理服务器再向后端服务器发请求, 服务器和服务器 之间 不存在同源限制 。
正向代理和反向代理
正向代理是 代理用户客户端 ,为客户端发送请求,对服务器 隐藏真实客户端 。
反向代理以 代理服务器 来接收客户端的请求,然后将请求转发给内部网络上的服务器,将从服务器上得到的结果返回给客户端。
正向代理主要是用来解决 访问限制 问题;
反向代理则是提供 负载均衡、安全防护 等作用。
websocket协议
HTML5 的一个 持久化 的协议,它实现了 浏览器与服务器 的 全双工通信
WebSocket 和 HTTP 都是 应用层协议 ,都基于 TCP 协议 。
WebSocket 在建 立连接时需要借助 HTTP 协议 ,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了
原生WebSocket API使用起来不太方便
Socket.io
,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了
向下兼容
。
本地文件socket.html向
localhost:3000
发生数据和接受数据:
// socket.html
<script>
let socket = new WebSocket('ws://localhost:3000');
socket.onopen = function () {
socket.send('我爱你');//向服务器发送数据
}
socket.onmessage = function (e) {
console.log(e.data);//接收服务器返回的数据
}
</script>
// server.js
let express = require('express');
let app = express();
let WebSocket = require('ws');//记得安装ws
let wss = new WebSocket.Server({port:3000});
wss.on('connection',function(ws) {
ws.on('message', function (data) {
console.log(data);
ws.send('我不爱你')
});
})
web安全及防护
XSS攻击
跨站脚本攻击
Cross-Site Scripting,
代码注入攻击。
当被攻击者 登陆网站时 就会执行这些恶意代码,这些脚本可以 读取 cookie,session tokens ,或者其它敏感的网站信息,对用户进行钓鱼欺诈,甚至发起蠕虫攻击等。
- 解决:
url参数使用encodeURIComponent方法 转义
尽量 不用InnerHtml插入HTML内容
使用特殊符号、标签转义符。
跨站请求伪造 Cross-site request forgery,在第三方网站中,向被攻击网站发送跨站请求。
利用用户在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
- 解决:添加 验证码 、使用 token
SQL注入攻击
SQL命令插入到Web表单递交或输入域名,最终达到欺骗服务器执行恶意的SQL命令。
解决:表单输入时通过正则表达式将一些 特殊字符进行转换
DDoS攻击
分布式拒绝服务,全称
Distributed Denial of Service
,
其原理就是利用
大量的请求
造成
资源过载
,导致服务不可用。
解决:
- 限制 单IP 请求频率。
- 防火墙
等防护设置禁止
ICMP
包等 - 检查 特权端口 的开放
考试时允许使用草稿纸,请提前准备纸笔。考试过程中允许上厕所等短暂离开,但请控制离开时间
笔试得分60%一般通过,面试答对80%才能通过
考试范围收录
选择题总集合={前端,计算机基础(数据库,操作系统,数据结构与算法,计算机网络),行测};
编程题总集合={常规算法(到具体情景),js手写,Dom操作}
例如:
- 美团:前端,计算机基础,行测,常规算法(前端:计算机基础=1:1)
- 小红书:前端,计算机基础,常规算法(前端:计算机基础=3:1)
- SHINE:前端,js手写
- 携程,京东:是先行测
- 百度:前端,计算机基础,常规算法,Dom操作
选择题
是常考考点,其他是作为理解原理的补充,原理部分在大厂笔面试中会考到
原则
- S – Single Responsibility Principle 单一职责原则
- 一个程序只做好一件事
- 如果功能过于复杂就拆分开,每个部分保持独立
- 例如:Promise每个then中的逻辑只做好一件事
- O – OpenClosed Principle 开放/封闭原则
- 对扩展开放,对修改封闭
- 增加需求时,扩展新代码,而非修改已有代码
- 例如:Promise如果新增需求,扩展then
- L – Liskov Substitution Principle 里氏替换原则
- 子类能覆盖父类
- 父类能出现 的地方子类就能出现
- I – Interface Segregation Principle
接口隔离
原则
- 保持 接口的单一独立
- 类似单一职责原则,这里更关注接口
- D – Dependency Inversion Principle 依赖倒转原则
- 面向接口 编程,依赖于抽象而不依赖于具体
- 使用方只关注接口而不关注具体类的实现
//checkType('165226226326','mobile')
//result:false
let checkType=function(str, type) {
switch (type) {
case 'email':
return /^[\w-]+([\w-]+)*@[\w-]+([\w-]+)+$/.test(str)
case 'mobile':
return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
case 'tel':
return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
default:
return true;
}
}
想添加其他规则就得在函数里面增加 case 。违反了开放-封闭原则(对扩展开放,对修改关闭)
给 API 增加一个扩展的接口:
let checkType=(function(){
let rules={
email(str){
return /^[\w-]+([\w-]+)*@[\w-]+([\w-]+)+$/.test(str);
},
mobile(str){
return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
}
};
//暴露接口
return {
//校验
check(str, type){
return rules[type]?rules[type](str):false;
},
//添加规则
addRule(type,fn){
rules[type]=fn;
}
}
})();
//调用方式
//使用mobile校验规则
console.log(checkType.check('188170239','mobile'));
//添加金额校验规则
checkType.addRule('money',function (str) {
return /^[0-9]+(.[0-9]{2})?$/.test(str)
});
//使用金额校验规则
console.log(checkType.check('18.36','money'));
创建型
单例模式
一个类只有一个实例,并提供一个访问它的全局访问点。
class LoginForm {
constructor() {
this.state = 'hide'
}
show() {
if (this.state === 'show') {
alert('已经显示')
return
}
this.state = 'show'
console.log('登录框显示成功')
}
hide() {
if (this.state === 'hide') {
alert('已经隐藏')
return
}
this.state = 'hide'
console.log('登录框隐藏成功')
}
}
LoginForm.getInstance = (function () {
let instance
return function () {
if (!instance) {
instance = new LoginForm()
}
return instance
}
})()
let obj1 = LoginForm.getInstance()
obj1.show()
let obj2 = LoginForm.getInstance()
obj2.hide()
console.log(obj1 === obj2)
优点:
- 单例模式可以保证内存里只有一个实例,减少了 内存 的开销。
- 单例模式设置全局访问点,可以优化和 共享资源 的访问。
- 只会实例化一次。简化了代码的调试和 维护
缺点:
- 单例模式一般没有接口,扩展困难
- 有可能导致模块间的强耦合 从而不利于单元测试。
应用:登录框
工厂模式
工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。
该模式使一个 类的实例化延迟到了子类 。
而子类可以 重写接口 方法以便创建的时候 指定自己的对象类型 。
class Product1 {
product() {
console.log("生产一线");
}
}
class Product2 {
product() {
console.log("生产二线");
}
}
class Factory {
constructor() {
this.Product1 = Product1;
this.Product2 = Product2;
}
create(name, callBack) {
const product = new this[name]();
product.product();
return callBack("susess");
}
}
let p = new Factory();
p.create("Product1", (res) => {
console.log(res);
});
优点:
- 工厂职责单一化易于维护
- 有利于消除对象间的耦合,提供更大的灵活性
缺点:添加新产品时,需要编写新的具体产品类,一定程度上增加了系统的复杂度
结构型
- 装饰者模式: 扩展功能,原有功能不变且可直接使用
- 代理模式: 显示原有功能,但是经过限制之后的
代理模式
是为一个对象提供一个代用品或占位符,以便控制对它的访问
应用:
- ES6 的 proxy
- HTML元 素事件代理
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>
装饰器模式
动态地给某个对象添加一些额外的职责,是一种实现 继承的替代 方案
class Cellphone {
create() {
console.log('生成一个手机')
}
}
class Decorator {
constructor(cellphone) {
this.cellphone = cellphone
}
create() {
this.cellphone.create()
this.createShell(cellphone)
}
createShell() {
console.log('生成手机壳')
}
}
// 测试代码
let cellphone = new Cellphone()
cellphone.create()
console.log('------------')
let dec = new Decorator(cellphone)
dec.create()
优点:
- 方便动态的扩展功能,且提供了比继承更多的灵活性。
缺点:
- 多层装饰比较复杂。
应用:
- ES7 Decorator
- 比如现在有4 种型号的自行车,我们为每种自行车都定义了一个单 独的类。现在要给每种自行车都装上前灯、尾 灯和铃铛这3 种配件。如果使用继承的方式来给 每种自行车创建子类,则需要 4×3 = 12 个子类。 但是如果把前灯、尾灯、铃铛这些对象动态组 合到自行车上面,则只需要额外增加3 个类
行为型
职责链模式
使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象连成一条链,并 沿着这条链传递该请求 , 直到有一个对象处理它为止
// 请假审批,需要组长审批、经理审批、总监审批
class Action {
constructor(name) {
this.name = name
this.nextAction = null
}
setNextAction(action) {
this.nextAction = action
}
handle() {
console.log( `${this.name} 审批`)
if (this.nextAction != null) {
this.nextAction.handle()
}
}
}
let a1 = new Action("组长")
let a2 = new Action("经理")
let a3 = new Action("总监")
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()
优点:
- 简化了对象。使得对象不需要知道链的结构
缺点:
- 不能保证某个请求一定会被链中的节点处理,这种情况可以在链尾增加一个 保底的接受 者节点来处理这种即将离开链尾的请求。
- 使程序中多了很多节点对象,可能再一次请求的过程中,大部分的节点并没有起到实质性的作用。他们的作用仅仅是让请求传递下去,从 性能 当面考虑,要避免过长的职责链到来的性能损耗。
应用:
- JS 中的事件冒泡
- 作用域链
- 原型链
观察者模式
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('result')
},
1000);
})
p1.then(res => console.log(res), err => console.log(err))
分析Promise的调用流程:
Promise
的构造方法接收一个executor()
,在new Promise()
时就立刻执行这个executor回调executor()
内部的异步任务被放入宏/微任务队列,等待执行then()
被执行,收集成功/失败回调,放入成功/失败队列executor()
的异步任务被执行,触发resolve/reject
,从成功/失败队列中取出回调依次执行
观察者模式:
收集依赖 -> 触发通知 -> 取出依赖执行
在Promise里,执行顺序是
then收集依赖 -> 异步触发resolve -> resolve执行依赖
。
class MyPromise {
// 构造方法接收一个回调
constructor(executor) {
this._resolveQueue = [] // then收集的执行成功的回调队列
this._rejectQueue = [] // then收集的执行失败的回调队列
/*由于resolve/reject是在executor内部被调用,
因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue*/
let _resolve = (val) => {
// 从成功队列里取出回调依次执行
while(this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(val)
}
}
// 实现同resolve
let _reject = (val) => {
while(this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(val)
}
}
// new Promise()时立即执行executor,并传入resolve和reject
executor(_resolve, _reject)
}
// then方法,接收一个成功的回调和一个失败的回调,并push进对应队列
then(resolveFn, rejectFn) {
this._resolveQueue.push(resolveFn)
this._rejectQueue.push(rejectFn)
}
}
操作系统
进程
程序、进程、线程
程序:( 静态 )以 文件形式 存于 硬盘
进程:( 传统OS ) 资源分配 和 独立调度 的 基本单位,进程 实体 的 运行过程
线程:(引入线程的OS) 独立调度 的 基本单位
进程 的状态 和 转换
- 运行
- 就绪:仅缺处理机
- 阻塞/等待 : 等待资源 (除处理机)可用 或 输入/输出完成
- 创建:正创建;
- 结束:正消失;
<– 时间片/优先级
新建 —创建—> 就绪 —调度—> 运行 —-退出—> 终止
事件发生↖ 阻塞 ↙等待事件
死锁
常考类型 :进程Pi各需资源S Xi个,则Smin/Nmax不死锁条件:S=∑(Xi-1)+1
- 定义:多进程 ∵ 资源竞争 而造成 相互等待 的僵局,无外力作用下 都 无法 继续 推进
- 原因: 非剥夺资源 的 竞争 + 进程的 非法推进顺序 (含 信号量 使用不当 )
- 充要 :
等待循环+ Pi的资源 必须 由 Pi+1 满足 (当各类资源=1,则循环等待=死锁)
-
必要 条件:b->a、c->d
-
互斥 访问
-
非剥夺 资源
-
请求 和保持
-
循环 等待
处理 | 预防 | 避免 | 检测 |
分配 | 严格 , 宁愿 闲置 资源 | 折中 , 运行时判断是否 可能 死锁 | 宽松 , 并发性最强 只要 允许 就分配 |
操作 | 破坏必要条件之一: 一次请求all;剥夺;按资源 序 分配 | 算法通过 是否安全状态,找 可能的 安全序列 | 定期检查 是否死锁 |
优点 | 适用 突发 处理, 不必剥夺 | 不必剥夺 ,限制条件弱,系统性能较好 | 不延长进程 初始化时间 , 允许 对 死锁 现场处理 |
缺点 | 效率低,进程 初始化时间长 ; 饥饿 剥夺 次数过多; 不便灵活申请 资源 | 需知 将来 需求 资源; 可能 长时阻塞 进程 | 剥夺 解除 死锁,造成 损失 |
- 预防 :
破坏 必要 条件:
- 互斥访问:某些场合 必须保证 互斥,∴实现 可能性小:
- 非 剥夺 资源:释放已获得,造成前段工作失效;反复申请+释放,增加开销,降低吞吐
(常用于 易于 保存和恢复的资源,eg:CPU的寄存器+内存资源;而 非打印机 etc)
- 请求 和保持: 预先静态 分配(一次申请完); 饥饿
- 循环 等待:资源 编号 ,只能 按 递增 申请, 同类 资源 一次申请完
(需编号稳定,限制了设备增加;可能 用资源和规定顺序不同,造成浪费资源;编程麻烦)
- 避免 :(死锁 =>不安全状态 )
银行家算法:Max,Need,Allocation,Available
-
检测 :
-
死锁定理 :S状态的 资源分配图 不可完全简化
-
解除 :
-
剥夺(暂停) : 挂起 某些进程,并抢夺资源(应防止 被挂起的进程 长期得不到资源)
-
撤销(关闭) :(按 进程 优先级 + 撤销代价 )撤销 部分甚至全部 进程,并抢夺资源
-
回退(回放) :一/多个进程 回退到 足以避免死锁 , 自愿 释放 资源(要求 系统 保持进程 的历史信息,设置 还原点 )
并发和并行
并发:逻辑上的同时发生(simultaneous)一个处理器同时处理多个任务。
并行:物理上的同时发生,是指多个处理器或者是 的处理器同时处理多个不同的任务。
处理机调度
调度层次
多道批处理系统 大多有 作业调度 , 其他系统 则不需要
- 低级/ 作业 调度:外/辅存 后备—>入内存, 建进程,分资源 , 获 竞争处理机 权利
- 中级/ 内存 调度: 暂不能运行 —> 外存 挂起 (提高内存利用率+系统吞吐量)
- 高级/ 进程 调度:按某种方法/ 策略 分配
调度基本准则
- CPU利用率
- 系统 吞吐量 : 单位时间 内 CPU 完成的作业量
- 周转 时间=作业 完成 时间- 提交 时间=t总;
- 带权
周转时间=
- 等待 时间=∑等待 处理机 时间;(判断 效率)
- 响应 时间= 首次响应 时刻-提交时刻;
调度方式
- 非剥夺/抢占式:适用 多数 批处理系统
- 剥夺/抢占式:提高 系统吞吐率+响应率
调度算法
(含 作业/进程 )
平均等待时间 :时间片轮转 较长(上下文 切换 费时 ); 短作业优先 最短
FCFS | 短作业优先 | 高响应比 | RR | 多级反馈队列 | |
抢占 | x | √ | √ | √ | 对内算法? |
非抢占 | √ | √(默认) | √(默认) | x | 对内算法? |
适用 | 无 | 批处理OS | 无 | 分时 | 通用 |
- FCFS 先来先服务(First Come First Serve) : 利于长 作业, CPU繁忙型 作业
- SJF短作业优先 : 一个/若干 估计运行时间 最短 作业 入内存
- SPF短进程优先 :一个 最 短 进程 调度,分配处理机
- 优先级:静 优先级 取决 进程类型(系统>用户),要求资源( I/O>计算 ), 用户要求
动态priority= nice +k1cpuTime-k2waitTime(k1,k2>0调整所占比例)
- 高响应比 优先(主要 作业 )=FCFS+SJF; 无 饥饿
t总/t实=响应比Rp=
-
时间片轮转RR(主要 分时 ):长短 取决 系统响应时间,就绪进程数,系统处理能力
-
多级反馈队列= 时间片 + 优先 级:
-
特点:
- 级↓ 的就绪队列, 优先级↑ , 时间片↑
- 新进程入内存, 先1级 , 时间片用完则降级 ;第n级队列 时间片轮转
- i级队列空 , 才执行i+1级 队列
- 若执行j级队列时,k级队列入进程(k<j),则 抢占 , 当前进程回j队列末尾
- 优势:
- 终端型 作业用户: 短作业优先 (大多 交互型 , 常短小 )
- 短 批处理作业用户: 周转时间短
- 长 批处理作业用户:经过前几个队列的部分执行, 不会长时间无响应
内存管理
内存空间的扩充 :从逻辑上扩充, 虚拟存储/自动覆盖 技术
- 源程序-> 可在 内存中 执行的程序:
- 编译 :编译程序 编译 源代码 成 若干个 目标模块
- 链接 :链接程序 链接 目标模块 + 所需 库函数 ,形成 一个 完整 的 装入模块 (形成逻辑地址)
- 装入 :装入程序 将 装入模块 装入 内存 (形成绝对地址)
- 相对/逻辑地址 :编译后,每个目标模块 都从0号单元开始编址
- 逻辑地址空间 :各个目标模块 的 相对地址 构成 的统一 从0号单元开始编址的 集合
- (内存管理的具体机制 完全透明,只有系统编程人员 才涉及,用户程序/程序员只需 知道逻辑地址)
- 物理地址空间 :内存 中 物理单元的集合
- 地址重定位 : 逻辑 地址-> 物理 地址
连续空间分配策略算法
分配策略算法 | 首次适应 FF | 最佳适应 BF | 最坏适应 WF | 邻近适用NF |
空闲分区链接 | 地址递增 | 容量递增 | 容量递减 | 循环 首次 适应 |
性能 | 最简单、快、好 | 最多 外部碎片 | 很快 没大内存块 | 内存末尾 碎片 |
比较 | (∵留下了 高地址的大空闲区,∴ 更可能满足进程 ) 优于顺序:FF 可能> BF >WF , FF 通常>NF |
页面置换算法
- 最佳(OPT) 置换算法:替换 最长 时间内/永久 不再被访问
- ∵最低缺页率,不可实现,∴只拿来 评价 其他算法
- 先进先出(FIFO) 置换算法( 队列 ): Belady异常 (分配物理块数↑,页故障数↑)
- 最近 最久 未使用(LRU) 置换算法( 堆栈 ): 性能接近OPT ,但需 寄存器+栈 ;困难,开销大
- 理论可证明, 堆栈类 算法 不可能出现 Belady异常
- 时钟(CLOCK) / 最近未使用(NRU) 置换算法: 循环 扫描 缓冲区
- 简单CLOCK 算法:
使用位 : 每一帧 关联一个 附加位/访问位
使用位 置 1 :首次 装入 /再被 访问
候选帧 集合:看做 循环缓冲区 ,有一个指针与之关联
替换:按 装入顺序 扫描/ 上次 扫描 位置 ,扫描查找到 0的帧 , 之前 的1帧 置0
- 改进型 CLOCK算法:+ 修改位m ( 修改过 的页,被 替换前 ,需 写回外存 )
替换 第一个 帧( u =0,m=0 )
重新扫描,替换 第一个帧( u=0,m=1 ), 跳过的帧u 置 0
指针回到 最初位置 , 所有帧 u置0 , 重复①
数据结构
链表
- 指针 :是结点的 相对地址 ,即 数组下标 ,即 游标
- 分配 : 预先 分配 连续 的 内存空间
- 结束标志 : next=-1
a | –> | b | –> | c | ^ |
下标 | data | next |
0 | 2 | |
1 | b | 4 |
2 | a | 1 |
3 | ||
4 | c | -1 |
栈
压栈 的 出入序列
(以 入栈 1 2 3 4 5 为例)
(1)出栈p首时,p前 的序列A,只能逆序出栈,且插在A中每个元素后面
eg:4****; 4_3_2_1_
(2)p 出栈序列 的前一个元素p1,可能为p的 前面的 或 后一个结点
eg:出栈 p1,3 则p1可能=1,2;4
合法的出栈序列的数量=出栈序列的总数-非法序列的数量
卡特兰数Catalan
栈指针
操作 | 初始S.top= -1 ,即top指向 栈顶 | S.top= 0 | 共享栈 ,top指向 栈顶 |
栈顶元素 | S.data[S.top] | S.data[S.top-1] | S.data[S.top] |
进栈 | S.data[++top]=x; | S.data[top++]=x; | S.data[–top1]=x; |
出栈 | x=S.data[top–]; | x=S.data[–top]; | x=S.data[top1++]; |
栈空 | S.top==-1; | S.top=0; | top1==MaxSize; top0=0; |
栈满 | S.top==MaxSize-1; | S.top==MaxSize; | top 1 -top 0 =1; |
- 缺点 :数组上界 约束 入栈,对 最大空间 估计不足时,可能 上溢( 整个 存储空间满时 )
- 共享栈 :栈顶向 共享空间 延伸,栈底 在两端, 优点 :更有效 利用 存储空间
表达式求值
将表达式构建成中序二叉树,然后先序求值
前,中,后缀 指 op在 两 操作数 中的位置
- 中缀 表达式:A+Bx(C-D)-E/F依赖 运算符 的 优先级;处理括号
- 后缀 表达式:ABCD-x+EF/- 已考虑 运算符 优先级 , 无括号
后缀表达式
- 组成 :只有 操作数 + 运算符 ;
- 表达 :原运算式 对应 的 表达式树 的 后续遍历
- 计算 表达式值:
- 初始设置一个 空栈 , 顺序扫描 后缀表达式
- 若为 操作数 ,则压 入栈
- 若为 操作 符
,则 连续 从 栈中 退出 两个操作数Y 和 X ,形成 运算指令X Y ,计算 结果 重新 压 入栈- 所有表达式 项 都扫描完, 栈顶 即为 结果
中 缀 转换为 前/后 缀:( 手工 )
- 按 运算符 优先级 ,对所有 运算单位 加()
- 运算符 移到 相应 的 ()前/后 面
- 去掉括号
中 缀 转换为 后 缀:以a*b+(-c)为例
-
根本 : 栈 存放 暂时 不能确定 运算 次序 的 操作符
-
算法思想 :
-
从 左到右 扫描 中缀 表达式
-
扫到 数 , 加入后缀
-
扫到 符 :
-
‘ ( ’: 入栈
-
‘ ) ’:栈内 运算符 依次 出栈 , 直至栈内遇 到‘ ( ’,然后 直接删除 ‘ ( ’
-
其他 运算符 :优先级 > 栈顶的非‘(’运算符时or栈空or栈顶‘(’,直接入栈
否则,依次弹出 当前处理 符 ≤ 栈内优先级 的 运算符,
直到遇‘(’or 优先级<当前处理 符
待处理序列 | 栈 | 后缀表达式 | 当前扫描元素 | 动作 |
a*b+(-c) | a | a加入后缀表达式 | ||
*b+(-c) | a | * | *入栈 | |
b+(-c) | * | a | b | b加入后缀表达式 |
+(-c) | * | ab | + | +<栈顶,弹出** |
+(-c) | ab | + | +入栈 | |
(-c) | + | ab* | ( | (入栈 |
-c) | +( | ab* | - | 栈顶 为 (,-入栈 |
c) | +(- | ab* | c | c加入后缀表达式 |
) | +(- | ab*c | ) | 把 栈 中 (之后 的符号 入后缀,并删( |
ab*c- | 扫描完毕, 运算符 依次退栈,入后缀 | |||
ab*c-+ | 完成 |
- 具体 转换 过程 (在 中缀表达式后+‘ # ’表示 表达式结束 ,题中 不算 操作符 )
操作符 | # | ( | *,/ | +,- | ) |
isp栈内优先 | 0 | 1 | 5 | 3 | 6 |
icp栈外优先 | 0 | 6 | 4 | 2 | 1 |
步骤 | 扫描项 | 项类型 | 动作 | 栈内 | 输出 |
0 | ‘#’进栈,读下一符号 | # | |||
1 | a | 操作数 | 直接输出 | # | a |
2 | * | 操作符 | isp (‘#’) <icp (‘*’), 进栈 | #* | |
3 | b | 操作数 | 直接输出 | #* | b |
4 | + | 操作符 | isp (‘*’) >icp (‘+’) ,退栈并输出 | # | * |
5 | isp(‘#’)<icp(‘+’),进栈 | #+ | |||
6 | ( | 操作符 | isp(‘-’)<icp(‘(’),进栈 | #+( | |
7 | - | 操作符 | isp(‘(’)<icp(‘-’),进栈 | #+(- | |
8 | c | 操作数 | 直接输出 | #+(- | c |
9 | ) | 操作符 | isp(‘-’)>icp(‘)’),退栈并输出 | #+( | - |
10 | isp (‘(’) ==icp (‘)’) ,直接退栈 | #+ | |||
11 | # | 操作符 | isp(‘+’)>icp(‘#’),退栈并输出 | # | + |
12 | isp(‘#’)==icp(‘#’),退栈, 结束 |
队列
顺序存储
队非空时,
Q.front指向队头元素的 上一个 元素,Q.rear指向队尾元素
Q.front指向队头元素,Q.rear指向队尾元素的 下一个 位置
∵ 假溢出 : 初始Q.front==Q.rear =0; “队满”Q.front==Q.rear
∴ 循环队列
- 初始 Q.front=Q.rear=0;
- 队长 : (Q.rear-Q.front+MaxSize)%MaxSize
- 出 队/ 入 队时,Q.front或Q.rear 顺时针+1 :
x=Q.data[Q.front];
Q.front=(Q.front+1)%MaxSize
链式存储
链队列
-
优点 :不存在 队满 甚至 溢出
-
适用 :数据 元素波动大 ,或者 多个队列
-
队空 :
-
无头 结点: Q.rear=NULL,Q.front=NULL
-
带头 结点: Q.front==Q.rear
(带头结点, 增删 操作 统一 )
- 入队 :…s->next=NULL…
- 出队 :…Q.front->next=p->next,if(Q.rear==p){ Q.rear=Q.front;}…
树
二叉树
N0=1+N2
满二叉树
- 结点数:2^h-1
结点i的
- 父 结点:└i/2┘
- 左 孩子结点:2i
- 右 孩子结点:2i+1
哈夫曼树 (最优二叉树) Huffman
目的:找出存放一串字符所需的最少的二进制编码
最小的两个合成组成二叉树。在 频率表 中删去他俩,并加入新的根结点。重复该步骤
默认是小在左,大在右,,所以哈弗曼 编码不唯一
例如:频率表 B:45, C:13 D:69 E:14 F:5 G:3
度m的哈夫曼树 只有度为0和m的结点∴Nm=(n-1)/(m-1)
- 固定 长度编码:待处理的字符串序列中,每个字符用 同样长度 的 二进制位
- 可变 长度编码: 频率 高的字符短编码;平均编码长度 减短,压缩数据
- 前缀 编码: 没有 一个 编码 是 另外 一个编码的 前缀
- 哈夫曼是前缀,可变长度编码
二叉排序树/二叉查找树BST(Binary Search/Sort Tree)
左 子树的关键字 < 根 结点 < 右 子树的关键字
判断是否为BST :中序序列递增=>B为BST,即pre < bt->data
平衡二叉树AVL
任一 结点的 左子树 和 右子树 的 深度之差≤1
插入 :若需调整,则每次 调整对象 必为 最小 不平衡二叉树
查找 :Nh表示深度为h 最少结点数 ,则N0=0,N1=1,N2=2,Nh=Nh-1+Nh-2+1
大根堆
左/右 子树的关键字 ≤根 结点, 完全 二叉树
最小生成树
- 定义: 连通 , 无向带权 图的生成树, 权值之和最小 的
- 唯一 :当任意环中边的 权值相异 ,则最小生成树 唯一
普里姆Prim算法 | 克鲁斯卡Kruskal算法 | |
共同 | 基于 贪心 算法 | |
特点 | 从 顶点 开始扩展最小生成树 | 按 权 递 增 次 序 ,选择 不构成环 的 边 |
T(n) | O( | V |
适用 | 稠密 图 | 稀疏 图 |
森林
对应树 | 森林1次 | 对应二叉树 |
先 根 遍历 | 先序遍历 | 先序遍历 |
后 根 遍历 | 中序遍历 | 中序遍历 |
先序 确定的 二叉树个数
∵ 先序+中序 可 唯一 确定 一棵二叉树
其关系 就如 入栈序列+出栈序列 可 唯一 确定 一个 栈
∴先序 确定 二叉树个数,即先序 确定 中序个数,
NL R确定 LN R,LN、NL相当于压栈,R相当于进了立即出
∴h(n)=Catalan卡特兰数=
带权路径长度WPL
WPL=∑(weight*路径 边 数)=(16+21+30)*2+(10+12)*3=200
查找次数 =路径上的 结点数 , 路径长度 =路径上的 边数
图
完全 图
- 无向 :任意两个顶点间, 只有 一条边, n(n-1)/2 条边
- 有向 :任意两个顶点间, 只有 方向相反 的两条弧, n(n-1) 条弧
最短路径
Dijkstra算法 | Floyd算法 | |
问题 | 单源 最短路径(单起源到各个顶点的最短距离,从源点的临近点开始) | 各个顶点 之间的最短路径 |
拓扑排序
-
DAG : 有向无环 图Directed Acycline Graph
-
拓扑排序:DAG中,每个顶点只出现一次,对每个 <u,v> , 序列 中, u在v前
-
唯一:图为 线性 有序序列时,唯一;若存在顶点 有多个后继则不唯一
-
邻接矩阵:
-
算法:
-
输出 并 删除 一个 没有前驱 的 结点
-
删除 以该结点 为 弧头 的 边
-
重复(1)(2),直到 DAG为空 或者 不存在 无前驱的 结点(环)
关键路径
-
关键路径: 最长 路径, 工程所需时间
-
关键活动:最长路径上的边
-
ve(k):事件k最早发生时间ve(k)=0(源点),ve(k)= Max {ve(j)+Weight(j,k)}
-
vl(k):事件k最迟发生时间vl(k)=ve(k)(汇点),vl(k)= Min {vl(j)-Weight(j,k)}
-
e(i):活动ai最早开始时间<vk,vj>,e(i)=vl(k)
-
l(i):活动ai最迟开始时间<vk,vj>,l(i)=vl(j)-Weight(k,j)
-
d(i):l(i)-e(i),为0的即 关键 活动
- 适度缩短 关键活动 ,可以缩短工期, 过度 时,关键活动可能变成 非关键活动
- 多关键路径 时, 缩短 所有 关键路径 才 缩短工期,除非有“ 桥 ”—所有关键路径的 共有活动
模式匹配
主串S,长n,模式串T,长m。T在S中 首次出现 的 位置
BF模式匹配
最坏T(n)=O(m
n)
KMP模式匹配
- next[j]: T 的 第j个 字符 失配 于 S 中的 第i个 字符,需要用 T 的 第next[j]个 字符与 S 中的 第i个 字符 比较
abcdeab f (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才明显快
内部排序算法
T(n)和S(n)
- 任何 基于 比较 的算法,都可用 二叉树 描述判定 过程,∴T(n)至少=O(nlog2n)
操作 | 内部排序 | 思想 | 稳定 | 平均 S(n) | T(n) | ||
平均 | 最坏 | 最好 | |||||
插 入 | 直接 | 查找;elem插入到有序,顺序找位 | √ | 1 | n 2 | n 2 顺序 | n逆序 |
折半 | 查找;直接插入的优化,折半找位 | x | 1 | n2与初始序列无关 | |||
希尔 | 分治;分组直接插入排序d个组L[i,i+d,…,i+kd] | x | 1 | n1.3 | n2 | 依赖f(d) | |
交换 | 冒泡 | 擂台;无序中两两比较,不等则交换,至最值 | √ | 1 | n 2 | n 2 逆序 | n顺序 |
快速 | 分治;取pivot,调整至L[1…k-1]<L(k)≤L[k+1…n] | x | log 2 n | nlog2n | n2最不平衡 | nlog2n最平衡 | |
选择 | 简单 | 擂台;第i趟L[i…n],初始min=i,swap(L(min),L(i)) | x | 1 | n2 | n2逆序 | n2顺序 |
堆 | 擂台;完全二叉树,根>子结点(大根堆) | x | 1 | nlog 2 n | nlog 2 n逆序 | nlog 2 n顺序 | |
2-路归并 | 分治;分组排序,两两合并 相邻 有序序列 | √ | n | nlog 2 n | nlog 2 n逆序 | nlog 2 n顺序 | |
基数 | 多key;从优先级min的开始入队分配,收集 | √ | r | d(n+r)与初始序列无关 |
-
顺序/链式 存储 均可:(与 下标 无关 ) 直接插入 , 冒泡 , 快速 , 简单选择 , 2-路归并
-
顺序 存储:(数组) 折半 , 希尔 , 堆 , 基数
-
比较 次数与 初始状态无关 : 简单选择 , 基数
-
T(n) 与 初始序列 无关: 折半 , 堆 , 多路归并 , 基数
-
过程特征 :( 每一趟 确定 一个 elem 最终位置 )
-
第 k 趟确定第 k 小/大值: 冒泡 , 堆 , 简单选择
-
第 k 趟确定第 i 小/大值: 快速
应用
-
考虑因素 :
-
n 待排序数目
-
elem本身 信息量 大小
-
key 的 结构
-
稳定性 要求
-
语言工具 条件
-
存储结构
-
辅助空间大小
-
情况 :
-
n≤50 ? 100 : n 2
-
链 式存储/ n≤50 : 直接插入
-
顺 序存储: 折半插入
-
elem本身 信息量 大 : 简单选择 ( 移动少 )
-
1000≥n>50?100 : 希尔
-
n>1000 : nlog 2 n
-
key随机 分布: 快速 排序
-
key位数少 且可 分解 : 基数
-
T(n) 与 初始序列 无关:
- 辅助空间小 : 堆
- 稳定 : 归并
-
key基本有序 :( 比较 : 直接插入 < 冒泡 , 移动 : 直接插入
冒泡 )
-
基本 逆序 : 直接插入
-
基本 顺序 : 冒泡
-
elem本身 信息量 大 : 链 式存储;避免耗费 大量移动记录
-
总体 信息量 大 : 归并排序 ; 内存 一次 放不下 ,需要 外部介质
平均查找长度ASL
顺序 / 线性查找
ASL | 无序线性表 | 有序表 |
succ | (n+1)/2 | |
fail | n+1 |
折半 / 二分查找
判定树 :描述 折半查找 过程
ASLsucc≈log2(n+1)-1
分块 / 索引顺序查找
-
优点 : 动态 结构, 快速 查找
-
基本思想 :
-
块 间 : 第i块max key<第i+1块 all key
-
块 内 : 无序
-
索引表 :含 各块 的 max key 和 各块 第一个元素 地址
-
ASLSucc=LI+LS
-
LI:ASL 索引 查找
-
LS:ASL 块内 查找
-
分 b块 , 每块s个记录 :(b+1)/2 + (s+1)/2 =
(
索引表
,
顺序表
,均
顺序查找
)当s=
时ASLmin=
+1
- 若对 索引表 折半 查找:┌log2(b+1)┐ +(s+1)/2
散列(Hash)表
- 散列表 :根据 关键字 直接访问 的 数据结构
- 对比 :散列表中 key 和 存储地址 有 直接映射 关系,而线性表,树表等无确定映射关系
- 散列函数 : Hash(key)=Addr ( 数组下标 , 索引 , 内存地址 )
- 冲突 : 不同关键字 映射 到 同一地址
- 同义词 :映射到同一地址的不同关键字
- T(n) : 无冲突时 , O(1)
散列函数 构造
要求
- 定义域 : 全部 关键字
- 值域 :依赖于 散列表大小 / 地址范围
- 地址 :计算出来的地址 应 等概率 , 均匀 分布 整个地址空间
- 计算: 简单 ,以在 短时 间计算出
方法 | Hash(key) | 适用 | 不适/特点 |
直接 定址 | a*key+b | key 分布连续 | key分布不连续,空位多,则空间浪费 |
除留 取余 | key MOD p | 质数p 左接近 表长 m ;简单,常用 | |
平方取中 | key 2 的 中间几位 | 每一位取值 都 不够均匀 / 均<Addr所需位数 | Addr与key的每一位都有关系, 使Addr分布均匀 |
数字分析 | 数码分布均匀 的 几位 | 已知Key 的 集合 ( r进制 , r个数码 ) | 某些位分布不均匀 , 只有 某几种数码经常出现 |
折叠 | 分割成 位数相同 的几部分, 取这几部分的 叠加和 | key 位数多 ,且 每一位 数字 上 分布大致均匀 | key分成 位数相同的几部分 ( 最后一部分 , 可以短些 ) |
处理冲突
- 定义 : 同义词 找下一个“ 空”Hash地址
- 方法 :
Hi 表示冲突后的 第i次探测 的 散列地址 , m 散列表 表长 , di增量序列 ,i∈[1,m-1]
- 开放地址 :Hi=(Hash(key)+ di )%m空闲地址 还对 同义词表项开放
- 线性探测 :di=1…di++..di=m-1 顺序 查看 下一单元 ,直到找到 空闲 单元/ 查遍 全表
(检测到表尾地址m-1时,下一地址为表首地址0)
可能 大量元素 在 相邻 散列地址 堆积 ,大大降低了查找效率
- 平方/二次探测
:di=
( k≤m/2 , m 为 4k+3 的 质数 );较好,可 避免堆积 ;
不能 检测所有 单元,但 至少 能检测 一半 单元
-
再/双散列 :di=Hash 2 (key); 最多m-1次 探测 遍历全表, 回到H0位置
-
伪随机 序列:di=伪随机序列
-
拉链/链式地址 : 同义词 链 由 散列地址 唯一标识 ;适用于 经常增删
ps:
∵ 查找 时,碰到 空 指针 就认为 查找失败
∴ 开放地址 不能物理删除 元素,否则会 截断 其他 具有相同 散列地址 的 查找地址 ;
∴只能做删除 标记 ∴ 多次删除后,散列表看起来很满,其实许多位置没用
∴要 定期维护 散列表,把 删除标记 的 元素 物理删除
性能分析
-
决定 因素: 散列函数;处理冲突 的方法; 装满因子α
n/m=记录数/表长 - ASL : 与α有关
递归
- 代码简单,容易理解
- 缺点 :通常 包含很多 重复计算 ,效率不高
- 精髓 :将 原始问题 转换为 属性相似 的 小规模 问题
- 递归工作栈 :容量与 递归 调用最大深度一致
递归和递推的区别
- 递归:
- 设置递归边界
- 判断已经计算过,直接返回结果
- 返回关系式
- 递推:
- 初始化边界
- 根据初始化边界 开始 递推
- 循环递推
十进制转换为二进制
余数入栈
迷宫求解
- 已走过 的 0 改 2 ,且 入栈 ,
- 坐标周围无0 时, 出栈 直到 遇到周围有0
算法(编程题)
场景题千千万,但都是由经典算法变换而来。
优化一般靠牺牲空间换时间
经验
一般过了3道编程,过了1.5就差不多,2就稳了。但是不绝对,有的一道题也会让你面试,有的a了2,也不一定有面试机会
有没有面试机会更多看的是卷的程度,名额多少,简历(例如学历高低)
- 运用 示例 ,摸清 规律 ,弄懂 整个逻辑 后,再动手
- 10min没有完整思路 的 先跳过 ,有时候局限了, 回过头可能 想得出来
- 随手保存
- 不要追求AC率 ,后面有空再返回完善,
- 注意题目中说明 输入的上限 ,如下
read_line()//将读取至多1024个字符,一定注意看题目字符上限
gets(n)//将读取至多n个字符,当还未达到n个时如果遇到回车或结束符
常用输出
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 展开运算符...
考核方式
ACM模式
自己构造输入格式,控制返回格式,OJ不会给任何代码,不同的语言有不同的输入输出规范。
JavaScript(V8)
readline()获取单行 字符串
key:
read_line()//将读取至多1024个字符,一定注意看题目字符上限
gets(n)//将读取至多n个字符,当还未达到n个时如果遇到回车或结束符
printsth(sth, ...)//多个参数时,空格分隔;最后不加回车。
console.log(sth, ...)、print(sth, ...)//多个参数时,空格分隔;最后加回车
line.split(' ').map(e=>Number(e));//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);
}
}()
核心代码模式
只需要实现函数核心功能并返回结果,无须处理输入输出
例如力扣上是核心代码模式,就是把要处理的数据都已经放入容器里,可以直接写逻辑
链表
判断链表是否有环
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()
};
层序遍历
树 的 层序遍历 ,相似 图 的 广度优先搜索
- 初始设置一个 空队 , 根结点入队
- 队首 结点 出队 ,其 左右孩子 依次 入队
- 若 队空 ,说明 所有结点 已处理完 ,结束遍历;否则(2)
/*
* 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
};
最近公共祖先
如果从两个节点往上找,每个节点都往上走,一直走到根节点,那么根节点到这两个节点的连线肯定有相交的地方,
如果从上往下走,那么最后一次相交的节点就是他们的最近公共祖先节点。
/*
* function TreeNode(x) {
* this.val = x;
* this.left = null;
* this.right = null;
* }
*/
/**
*
* @param root TreeNode类
* @param o1 int整型
* @param o2 int整型
* @return int整型
*/
function dfs(root, o1, o2) {
if (root == null || root.val == o1 || root.val == o2) {
return root;
}
//递归遍历左子树
let left = dfs(root.left, o1, o2);
//递归遍历右子树
let right = dfs(root.right, o1, o2);
//如果left、right都不为空,那么代表o1、o2在root的两侧,所以root为他们的公共祖先
if (left && right) return root;
//如果left、right有一个为空,那么就返回不为空的那一个
return left != null ? left : right;
}
数组和树
扁平结构(一维数组)转树
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));
//join() + split() 实现
return arr.join(',').split(',').map(item => Number(item));
//reduce 实现
return arr.reduce((target, item) => {
return target.concat(Array.isArray(item) ? flatten(item) : 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)
常用方法
异或运算^
按位异或,相同为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及AF),字母大小写都可以,超出范围会 报错
特殊值 :
- 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"
set判断值相等的机制
//Set用===判断是否相等
const set= new Set();
const obj1={ x: 10, y: 20 },obj2={ x: 10, y: 20 }
set.add(obj1).add(obj2);
console.log(obj1===obj2);//false
console.log(set.size);// 2
set.add(obj1);
console.log(obj1===obj1);//true
console.log(set.size);//2
数组去重 ( 手写)
// Use to remove duplicate elements from the array
const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]
console.log([...new Set(numbers)])
// [2, 3, 4, 5, 6, 7, 32]
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() //从数组中删除第一个元素,并返回该元素的值。
array.pop() //从数组中删除最后一个元素,并返回该元素的值。此方法会更改数组的长度。
array.push() //将一个或多个元素添加到数组的末尾,并返回该数组的新长度
//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 将使用数组第二个元素。
const array1 = [1, 2, 3, 4];
// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
(accumulator, currentValue) => accumulator + currentValue,
initialValue
);
console.log(sumWithInitial);
// Expected output: 10
String
str.charAt(index)//获取第n位字符
str.charCodeAt(n)//获取第n位UTF-16字符编码 (Unicode)A是65,a是97
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.concat(str2, [, ...strN])
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)//找到一个或多个正则表达式的匹配。
const paragraph = 'The quick brown fox jumps over the lazy dog. It barked.';
let regex = /[A-Z]/g;
let found = paragraph.match(regex);
console.log(found);
// Expected output: Array ["T", "I"]
regex = /[A-Z]/;
found = paragraph.match(regex);
console.log(found);
// Expected output: Array ["T"]
//match类似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置。
var str = '123123000'
str.match(/\w{3}/g).join(',') // 123,123,000
str.search(regexp)//如果匹配成功,则 search() 返回正则表达式在字符串中首次匹配项的索引;否则,返回 -1
const paragraph = '? The quick';
// Any character that is not a word character or whitespace
const regex = /[^\w\s]/g;
console.log(paragraph.search(regex));
// Expected output: 0
str.repeat(count)//返回副本
str.replace(regexp|substr, newSubStr|function)//返回一个由替换值(replacement)替换部分或所有的模式(pattern)匹配项后的新字符串。
const p = 'lazy dog.Dog lazy';//如果pattern是字符串,则仅替换第一个匹配项。
console.log(p.replace('dog', 'monkey'));
// "lazy monkey.Dog lazy"
let regex = /dog/i;//如果非全局匹配,则仅替换第一个匹配项
console.log(p.replace(regex, 'ferret'));
//"lazy ferret.Dog lazy"
regex = /d|Dog/g;
console.log(p.replace(regex, 'ferret'));
//"lazy ferretog.ferret lazy"
//当使用一个 regex 时,您必须设置全局(“g”)标志, 否则,它将引发 TypeError:“必须使用全局 RegExp 调用 replaceAll”。
const p = 'lazy dog.dog lazy';//如果pattern是字符串,则仅替换第一个匹配项。
console.log(p.replaceAll('dog', 'monkey'));
// "lazy monkey.monkey lazy"
let regex = /dog/g;//如果非全局匹配,则仅替换第一个匹配项
console.log(p.replaceAll(regex, 'ferret'));
//"lazy ferret.ferret lazy"
正则表达式Regular Expression(RegExp)
RegExp 对象是一个 预定义了属性和方法 的正则表达式对象
字面量和字符串
//以下三种表达式都会创建相同的正则表达式:
/ab+c/i; //字面量形式 /正则表达式主体/修饰符(可选)
new RegExp('ab+c', 'i'); // 首个参数为字符串模式的构造函数
new RegExp(/ab+c/, 'i'); // 首个参数为常规字面量的构造函数
//防止在字符串中被解译成一个转义字符
var re = new RegExp("\\w+");//需要常规的字符转义规则(在前面加反斜杠 \)
var re = /\w+/;
当表达式被 赋值 时,字面量形式提供正则表达式的 编译 (compilation)状态,
当正则表达式保持为 常量 时使用字面量。
例如在循环中使用字面量构造一个正则表达式时,正则表达式不会在每一次迭代中都被重新编译(recompiled)。
正则表达式对象的构造函数,如
new RegExp('ab+c')
提供了正则表达式 运行时编 译(runtime compilation)。如果你知道正则表达式模式为 变量 ,如用户输入,这些情况都可以使用构造函数。
regexp.test和regexp.exec
regexp.test(str)返回Bool
regexp.exec(str)返回匹配的子串 或者 null
常用修饰符
- i ignoreCase 执行对 大小写不敏感 的匹配。
- g global 执行 全局匹配 (查找所有匹配而非在找到第一个匹配后停止)。
- y sticky 粘性匹配 从源字符串的 位置开始匹配,
lastIndex
只有 "
g
" 或”
y
" 标志时,lastIndex才会起作用。
y
:下一次匹配一定在
lastIndex
位置开始;
g
:下一次匹配
可能
在
lastIndex
位置开始,也可能在这个位置的
后面
开始。
lastIndex
str.length,则匹配失败,
匹配
失败
,则
lastIndex
被设置为
0
。
let str = '#foo#'
let regex = /foo/y
regex.lastIndex = 1
regex.test(str) // true
regex.lastIndex = 5
regex.test(str) // false (lastIndex is taken into account with sticky flag)
regex.lastIndex // 0 (reset after match failure)
分组
‘(
正则表达式
)’
每一个分组都是一个
子表达式
回溯引用
(backreference)指的是 模式的后面 部分 引用 前面 已经匹配到 的子字符串。
回溯引用的语法像
\1
,
\2
,….,其中
\1
表示引用的第一个子表达式,
\2
表示引用的第二个子表达式,以此类推。而
\0
则表示
整个表达式
。
匹配两个连续相同的单词:\b(\w+)\s\1
Hello what what is the first thing, and I am am scq000.
回溯引用在
替换
字符串中十分常用,语法上有些许区别,用
$1
,
$2
…来引用要被替换的字符
var str = 'abc abc 123';
str.replace(/(ab)c/g,'$1g');
// 得到结果 'abg abg 123'
匹配
选择匹配:(子模式)|(子模式)
多重选择模式:在多个子模式之间加入选择操作符。
为了避免 歧义 :(子模式)。
var r = /(abc)|(efg)|(123)|(456)/;
惰性匹配: 最小化 匹配
重复类量词 都具有贪婪性,在条件允许的前提下, 会匹配尽可能多 的字符。
- ?、{n} 和 {n,m} 重复类具有 弱 贪婪性,表现为贪婪的 有限性 。
- *、+ 和 {n,} 重复类具有 强 贪婪性,表现为贪婪的 无限性 。
越 左 的 重复类量词 优先级越 高 ,会 在保证右侧 重复类量词 最低 匹配次数基础上,使 最左侧 的重复类量词 尽可能占有所有字符 。
var s = "<html><head><title></title></head><body></body></html>";
var r = /(<.*>)(<.*>)/
var a = s.match(r);
console.log(a[0])//整个表达式匹配'<html><head><title></title></head><body></body></html>'
console.log(a[1]);//左侧表达式匹配"<html><head><title></title></head><body></body></html>"
console.log(a[2]);//右侧表达式匹配“</html>”
定义: 在满足条件的前提 下,尽可能少的匹配字符。
方法:在重复类量词后面添加问号 ? 限制词。
贪婪匹配体现了 最大化 匹配原则,惰性匹配则体现 最小化 匹配原则。
var s = "<html><p><title></title></head><body></body></html>";
var r = /<.*?>/
var a = s.match(r); //返回单个元素数组["<html>"]而不是最短的<p>
针对 6 种重复类惰性匹配的简单描述如下:
- {n,m}?:尽量匹配 n 次,但是为了满足限定条件也可能最多重复 m 次。
- {n}?:尽量匹配 n 次。
- {n,}?:尽量匹配 n 次,但是为了满足限定条件也可能匹配任意次。
- ??:尽量匹配,但是为了满足限定条件也可能最多匹配 1 次,相当于 {0,1}?。
- +? :尽量匹配 1 次,但是为了满足限定条件也可能匹配任意次,相当于 {1,}?。
- *? :尽量不匹配,但是为了满足限定条件也可能匹配任意次,相当于 {0,}?。
前/后向查找:匹配括号中的内容(不包含括号)
包括括号:[\S+?]
不包括括号:(?<=[)\S+?(?=])
后向 查找: (?<=exp) 是以exp开头的字符串, 但 不包含本身 .
负后向查找 : (?<!exp) ,被指定的子表达式 不能被匹配到 。
前向 查找: (?=exp) 就匹配为 exp结尾 的字符串, 但 不包含本身 .
负前向 查找 :: (?!exp), 被指定的子表达式 不能被匹配到 。
\S 匹配任何非空白字符。等价于[^\f\n\r\t\v]。
如果不支持后向查找:将字符串进行 翻转 ,然后再使用 前向 查找,作完处理后再 翻转 回来
技巧
反义字符
可以匹配很多无法直接描述的字符,达到 以少应多 的目的。
var r = /[^0123456789]/g;
边界量词
边界就是确定匹配模式的 位置 ,如字符串的头部或尾部,具体说明如表所示。
JavaScript 正则表达式支持的边界量词
量词 | 说明 |
---|---|
^ | 匹配开头,在多行检测中,会匹配一行的开头 |
$ | 匹配结尾,在多行检测中,会匹配一行的结尾 |
- var s = “how are you”
1) 匹配最后一个单词
var r = /\w+$/;
var a = s.match(r); //返回数组["you"]
2) 匹配第一个单词
var r = /^\w+/;
var a = s.match(r); //返回数组["how"]
3) 匹配每一个单词
var r = /\w+/g;
var a = s.match(r); //返回数组["how","are","you"]
应用
str.split()
//对于不同的平台(Unix,Windows 等等),其默认的行结束符是不一样的。而下面的划分方式适用于所有平台。
let text = 'Some text\nAnd some more\r\nAnd yet\rThis is the end'
let lines = text.split(/\r\n|\r|\n/)
console.log(lines) // logs [ 'Some text', 'And some more', 'And yet', 'This is the end' ]
str.match()
在 字符范围 内可以 混用 各种字符模式。
var s = "abcdez"; //字符串直接量
var r = /[abce-z]/g; //字符a、b、c,以及从e~z之间的任意字符
var a = s.match(r); //返回数组["a","b","c","e","z"]
str.match(regexp)//找到一个或多个正则表达式的匹配。
const paragraph = 'The quick brown fox jumps over the lazy dog. It barked.';
let regex = /[A-Z]/g;
let found = paragraph.match(regex);
console.log(found);
// Expected output: Array ["T", "I"]
regex = /[A-Z]/;
found = paragraph.match(regex);
console.log(found);
// Expected output: Array ["T"]
//match类似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置。
var str = '123123000'
str.match(/\w{3}/g).join(',') // 123,123,000
str.replace()
let re = /(\w+)\s(\w+)/;//匹配姓名 first last 输出新的格式 last, first。
let str = "John Smith";
let newstr = str.replace(re, "$2, $1");//$1 和 $2 指明括号里先前的匹配
console.log(newstr);//"Smith, John".
str.replace(regexp|substr, newSubStr|function)//返回一个由替换值(replacement)替换部分或所有的模式(pattern)匹配项后的新字符串。
const p = 'lazy dog.Dog lazy';//如果pattern是字符串,则仅替换第一个匹配项。
console.log(p.replace('dog', 'monkey'));
// "lazy monkey.Dog lazy"
let regex = /dog/i;//如果非全局匹配,则仅替换第一个匹配项
console.log(p.replace(regex, 'ferret'));
//"lazy ferret.Dog lazy"
regex = /d|Dog/g;
console.log(p.replace(regex, 'ferret'));
//"lazy ferretog.ferret lazy"
//当使用一个 regex 时,您必须设置全局(“g”)标志, 否则,它将引发 TypeError:“必须使用全局 RegExp 调用 replaceAll”。
const p = 'lazy dog.dog lazy';//如果pattern是字符串,则仅替换第一个匹配项。
console.log(p.replaceAll('dog', 'monkey'));
// "lazy monkey.monkey lazy"
let regex = /dog/g;//如果非全局匹配,则仅替换第一个匹配项
console.log(p.replaceAll(regex, 'ferret'));
//"lazy ferret.ferret lazy"
设计对提交的表单字符串进行敏感词过滤。先设计一个敏感词列表,然后使用竖线把它们连接在一起,定义选择匹配模式,最后使用字符串的 replace() 方法把所有敏感字符替换为可以显示的编码格式。
var s = '<meta charset="utf-8">'; //待过滤的表单提交信息
var r = /\'|\"|\<|\>/gi; //过滤敏感字符的正则表达式
function f() { //替换函数
把敏感字符替换为对应的网页显示的编码格式
return "&#" + arguments[0].charCodeAt(0) + ";";
}
var a =s.replace(r,f); //执行过滤替换
document.write(a); //在网页中显示正常的字符信息
console.log(a);
str.serach()
str.search(regexp)//如果匹配成功,则 search() 返回正则表达式在字符串中首次匹配项的索引;否则,返回 -1
const paragraph = '? The quick';
// Any character that is not a word character or whitespace
const regex = /[^\w\s]/g;
console.log(paragraph.search(regex));
// Expected output: 0
合法的URL
URL结构一般包括协议、主机名、主机端口、路径、请求信息、哈希
- 首先必须是以http(s)开头并且可以不包含协议头部信息
- 主机名可以使用”-“符号,所以两种情况都要判断,包含”-“或不包含”-”
- 顶级域名很多,直接判断”.“之后是否为字母即可
- 最后判断端口、路径和哈希,这些参数可有可无
域名中只能包含以下字符
-
26个英文字母
-
“0,1,2,3,4,5,6,7,8,9"十个数字
-
“-"(英文中的连词号,但不能是第一个字符)
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-zA-Z]{2,6}
//端口 数字: (\d+)?
//路径 任意字符 如 /login: (\/.+)?
//哈希 ? 和 # ,如?age=1: (\?.+)?(#.+)?
//结束: $
// https:// www.bilibili com /video/BV1F54y1N74E ?spm..
/^(http(s)?:\/\/)?(([a-zA-Z0-9]+-[a-zA-Z0-9]+|[a-zA-Z0-9]+))+([a-zA-Z]{2,6})(:\d+)?(\/.+)?(\?.+)?(#.+)?$/.test(url)
常用字符
\标记下一个字符是特殊字符或文字。例如,“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_]”等效。
元字符表
元字符 | 描述 |
---|---|
. | 查找单个字符,除了换行和行结束符 |
\w | 查找单词字符 |
\W | 查找非单词字符 |
\d | 查找数字 |
\D | 查找非数字字符 |
\s | 查找空白字符 |
\S | 查找非空白字符 |
\b | 匹配单词边界 |
\B | 匹配非单词边界 |
\0 | 查找 NUL字符 |
\n | 查找换行符 |
\f | 查找换页符 |
\r | 查找回车符 |
\t | 查找制表符 |
\v | 查找垂直制表符 |
\xxx | 查找以八进制数 xxxx 规定的字符 |
\xdd | 查找以十六进制数 dd 规定的字符 |
\uxxxx | 查找以十六进制 xxxx规定的 Unicode 字符 |
[A-z]和[a-zA-Z]
[A-z]
将在范围匹配的ASCII字符从
A
到
z
,
[a-zA-Z]
将在范围中的范围匹配的ASCII字符从
A
到
Z
和
从
a
到
z
。
查看ASCII字符的
,则会看到
A-z
包含
[
,
\
,
]
,
^
,
_,
## 规范
### *命名规范
建议养成每句后加;的好习惯
1. Pascal Case 大驼峰式命名法:首字母大写。eg:StudentInfo、UserInfo、ProductInfo
2. Camel Case 小驼峰式命名法:首字母小写。eg:studentInfo、userInfo、productInfo
#### 常量
命名方法:名词全部大写
命名规范:使用大写字母和下划线来组合命名,下划线用来分割单词
```javascript
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
*注释
HTML
CSS
p{
color: #ff7000; /*字体颜色设置*/
height:30px; /*段落高度设置*/
}
/注释/
JS
//注释
/注释/
项目为二面三面,面试官基本就是照着简历里面的项目技术点切入然后 深入 展开。
为了简洁,相关文章参考链接在标题里
目录
模块化规范
一个模块是实现一个 特定功能 的 一组方法 。
- 由于函数具有 独立作用域 的特点,最原始的写法是使用函数来作为模块, 几个函数作为一个模块 ,但是这种方式容易造成 全局变量的污染 ,并且 模块间没有联系 。
- 后面提出了对象,通过 将函数作为一个对象的方法 来实现,但是这种办法会 暴露 所 有的所有的模块成员,外部代码可以修改 内部属性的值 。
- 现在最常用的是 立即执行函数 的写法,通过利用 闭包 来实现 模块私有作用域 的建立,同时 不会对全局作用域造成污染 。
- 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)延迟加载、按需加载
可视化区域之外 的图片不会进行加载,在 滚动 屏幕时才加载。这样使得网页的加载速度更快,减少了服务器的负载。懒加载适用于图片较多,页面列表较长(长列表)的场景中。
scroll版
- 图片的 src 属性设为默认图片
- 图片的真实路径则设置在data-src属性中,
- 绑定 window 的
scroll
事件,对其进行事件监听。 -
//节流 window.addEventListener('scroll', throttle(lazyload, 200))
- 在scroll事件的回调中,判断我们的懒加载的图片是否进入可视区域,
- 如果图片在可视区内将图片的 src 属性设置为data-src的值
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lazyload</title>
<style>
.image-item {
display: block;
margin-bottom: 50px;
height: 200px;//一定记得设置图片高度
}
</style>
</head>
<body>
<img src="./img/default.png" data-src="./img/1.jpg" />
...
<img src="./img/default.png" data-src="./img/10.jpg" />
<script>
function lazyload() {
let viewHeight = document.body.clientHeight //获取可视区高度
//用属性选择器返回属性名为data-src的img元素列表
let imgs = document.querySelectorAll('img[data-src]')
imgs.forEach((item, index) => {
if (item.dataset.src === '') return
// 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置
let rect = item.getBoundingClientRect()
if (rect.bottom >= 0 && rect.top < viewHeight) {
item.src = item.dataset.src
item.removeAttribute('data-src')//移除属性,下次不再遍历
}
})
}
lazyload()//刚开始还没滚动屏幕时,要先触发一次函数,初始化首页的页面图片
document.addEventListener("scroll",lazyload)
</script>
</body>
</html>
IntersectionObserver
版
IntersectionObserver
“交叉观察器”。可见(visible)本质是,目标元素与视口产生一个交叉区
callback 一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)
//callback 是可见性变化时的回调函数,option 是配置对象(该参数可选)
var io = new IntersectionObserver(callback, option)
// 开始观察
io.observe(document.getElementById('example'))
// 停止观察
io.unobserve(element)
// 关闭观察器
io.disconnect()
callback 函数的参数
(entries)
是一个数组,每个成员都是一个
IntersectionObserverEntry
对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,
entries
数组就会有两个成员。
- time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
- target:被观察的目标元素,是一个 DOM 节点对象
- isIntersecting: 目标是否可见
- rootBounds:根元素的矩形区域的信息,
getBoundingClientRect()
方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回 null - boundingClientRect:目标元素的矩形区域的信息
- intersectionRect:目标元素与视口(或根元素)的交叉区域的信息
- intersectionRatio:目标元素的可见比例,即
intersectionRect
占boundingClientRect
的比例,完全可见时为 1,完全不可见时小于等于 0
const config = {
rootMargin: '0px',
threshold: 0,
}
let observer = new IntersectionObserver((entries, self) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
let img = entry.target
let src = img.dataset.src
if (src) {
img.src = src
img.removeAttribute('data-src')
}
// 解除观察
self.unobserve(entry.target)
}
})
}, config)
imgs.forEach((image) => {
observer.observe(image)
})
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)
其他规范在项目中根据实际情况决定和编写
命令(创建运行)
npm run start 来运行启动项目并打开页面
npx create-react-app my-app //创建项目
cd my-app
npm start
安装命令:
npx create-react-app 项目名称 –template typescript
主要开发代码在src目录下。App.js为 根 组件,index.js为 入口模块 ,index.css为 全局样式 文件。
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
是安装 后用来存放用包管理工具下载安装的 包 的文件夹。比如 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分包,减少重绘回流
它将根据 模块的依赖关 系进行 静态分析 ,然后将这些模块( 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 回调触发模块 热替换逻辑 。
一般面试官不会考察,但是你可以写在简历项目或者技能中,面试时主动提出使用了ts,解释下为什么要使用ts及ts与js,java(类成员可见性,多的类型(any,void,泛型,断言等))的区别,然后引导面试官提问,回答完其问题再自己拓展一些相关,是不错的加分项。
但既然是加分项,一般不会问太深,毕竟还有更关键的要问你,所以会试探一下你是否真的用过,还是纯背。
面试官一般是看你简历问的,没有问的才会问八股文之类。
面试一定要把握主动权,不要干等着面试官问,尽量展示自己所会的,并且回答不必太急,最后能条理清晰。
区别
为什么推荐使用TypeScript?
- JS的 超集 ,扩展新功能
- 强制类型 ,防止报错
- 自动类型推断, 保证变量的 类型稳定
// let a: string -> 类型推断
let a = 'hello'
a = 123; // error
- 强大的类型系统,包括 泛型 。
- 支持 静态类型 。(主要改进)更有利于构建大型应用
TypeScript和JavaScript的区别
- TypeScript是JavaScript 的 超集 ,可以被编译成JS。用JS编写的代码,在TS中依然有效。
- TypeScript通过 TypeScript编译器 或 Babel 转译为 JavaScript 代码
- TypeScript 是 纯面向对象 的编程语言,包含 类和接口 的概念。
- JavaScript 弱类型 语言, 动态类型
类型强弱 是针对类型 转换是否显示 来区分, 静态和动态类型 是针对 类型检查的时机 来区分。
- 在JS中只有
变量声明空间
,比如
let a = 123
。但在TS中还存在 类型声明空间, 可通过 类型注解结合这两个空间
可以通过
type
关键字来定义类型声明空间,
type
在TS中叫做
类型别名
。为了能够更好的区分两个空间,所以人为规定类型空间定义的名字
首字母要大写
,例如:
type A = number
控制类成员可见性的访问关键字有哪些?
- 公共(public),类的所有 成员 ,其 子类 以及该类的 实例 都可以访问。
- 受保护(protected),该类及其子类都可以访问它们。 但是该类的实例无法访问。
- 私有(private),只有类的成员可以访问它们。
TS与java不同,TS默认隐式 公共 的,TS符合 JS 的便利性,java默认private,符合安全性
接口interface与类型别名type
接口是一系列抽象方法的声明,是一些方法特征的集合。
接口 跟 类型别名 类似都是用来定义 类型注解 的,但接口 只能操作对象 ,且有 继承,合并
interface A {
username: string;
age: number;
}
let a: A = {
username: 'xiaoming',
age: 20
}
接口可以进行 合并 操作。
interface A {
username: string;
}
interface A {
age: number;
}
let a: A = {
username: 'xiaoming',
age: 20
}
接口具备 继承 能力。
interface A {
username: string
}
interface B extends A {
age: number
}
let b: B = {
username: 'xiaoming',
age: 20
}
类型
never,any,unknown
never类型表示永不存在的值的类型,当一个值 不存在 的时候就会被自动类型推断成never类型。
在 出问题 的时候会被自动转成never。
any类型表示 任意 类型,而unknown类型表示为 未知 类型,是any类型对应的 安全 类型。
类型断言as与非空断言!
当 TypeScript 推断 出来类型并不满足需求,需要 手动指定 一个类型,强制说明类型,让TS编译不报错
let a: unknown = 'hello';
a = 123;
(a as []).map(()=>{}) // success
因为b可能是字符串也可能是undefined,所以
b.length
的时候就会报错,这样我们可以采用非空断言来告诉TS,这个b肯定不是undefined,所以b只能是字符串,那么
b.length
就不会报错了。
let b: string|undefined = undefined;
b!.length // success
函数类型和void
function foo(n: number, m?: string): number{
return 123;
}
foo(123, 'hello');
foo(123); // success
当函数 没有return 和 return undefined 的时候返回void类型。
let foo = function(){ // void
}let foo = function(): undefined{ // undefined 不能不写return的
} // error
泛型
泛型是指在定义 函数、接口、类 时,未指定其参数类型,只有在 运行 时传入才能确定。泛型简单来说就是对类型进行 传参 处理。
应用
变量
*类型注解
类型注解:把变量空间与类型空间结合在一起
type关键字+ 类型别名
let a: string = 'hello'
//也可以通过类型别名进行连接
type A = string
let a: A = 'hello'
数组
let arr: Array<number> = [1, 2, 3];
//自定义MyArray实现
type MyArray<T> = T[];
let arr2: MyArray<number> = [1, 2, 3];
函数
function foo(n: number, m?: string): number{
return 123;
}
foo(123, 'hello');
foo(123); // success
function foo<T>(n: T){
}
foo<string>('hello');
foo(123); // 泛型会自动类型推断
类
class Foo {
//第一种写法
//username: string = 'xiaoming';
//第二种写法
// username: string;
// constructor(){
// this.username = 'xiaoming';
// }
//第三种写法
username: string;
constructor(username: string){
this.username = username;
}
}
interface A {
username: string
age: number
showName(n: string): string
}
class Foo implements A {
username: string = 'xiaoming'
age: number = 20
gender: string = 'male'
showName = (n: string): string => {
return n
}
}
class Foo {
...
showAge = (n: number): number => {
return n;
}
}
class Foo<T> {
username!: T; //不给初始值可通过非空断言
}
let f = new Foo<string>();
f.username = 'hello';
class Foo<T> {
username!: T
}
class Baz extends Foo<string> {}
let f = new Baz()
f.username = 'hello'
is
is
让 ts 分辨类型
function toUpperCase(x: unknown) {
if(isString(x)) {
x.toUpperCase(); // ⚡️ x is still of type unknown
}
}
- 但是由于这个检验函数(isString)被包裹在 toUpperCase()函数中,ts 在判断的时候还是抛出了错误提示
- 使用
is
,这里让我们主动明确的告诉 ts ,在 isString() 这个函数的参数是一个 string。
// !!! 使用 is 来确认参数 s 是一个 string 类型
function isString(s): s is string {
return typeof s === 'string';
}
Narrowing down sets
function pipsAreValid(pips: number) {
// we check for every discrete value, as number can
// be something between 1 and 2 as well.
return pips === 1 || pips === 2 || pips === 3 ||
pips === 4 || pips === 5 || pips === 6;
}
function evalThrow(count: number) {
if (pipsAreValid(count)) {
// my types are lying 😢
switch (count) {
case 1:
case 2:
case 3:
case 4:
case 5:
console.log('Not today');
break;
case 6:
console.log('Won!');
break;
case 7:
// TypeScript does not complain here, even though
// it's impossible for count to be 7
console.log('This does not work!');
break;
}
}
}
count
是一个
number
类型,输入的参数是没有问题的。
在我们校验它的时候,
count
不再是一个
number
类型,而是变成了一个 1-6 number 的特殊类型。
ts 为了防止类型溢出,使用了联合类型把原来
number
类型变成 1-6的数字(缩小了范围)
改变处
// 主动使用联合类型确保我们的输入是 1-6的数字
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
function pipsAreValid(pips: number): pips is Dice {
改变处
return pips === 1 || pips === 2 || pips === 3 ||
pips === 4 || pips === 5 || pips === 6;
}
function evalThrow(count: number) {
if (pipsAreValid(count)) {
// count is now of type Dice 😎
switch (count) {
case 1:
case 2:
case 3:
case 4:
case 5:
console.log('Not today');
break;
case 6:
console.log('Won!');
break;
case 7:
// TypeScript errors here. 7 is not in the union type of
// Dice
console.log('This does not work!');
break;
}
}
}
样式
less, sass, scss都是 css预处理语言 (也是对应的文件后缀名)。
CSS 预处理器为 CSS 增加一些 编程 的特性,无需考虑浏览器的 兼容性 问题,
可以在 CSS 中使用 变量 、简单的 逻辑 程序、 函数 (如变量$main-color)等等在 编程 语言中的一些 基本特性 ,可以让 CSS 更加简洁、 适应性更强 、可读性更佳,更易于代码的 维护 等诸多好处。
开发 时用 预处理语言 ,在 打包上线 时,用webpack再配合loader工具给 转成css 给浏览器使用。
在实际开发过程中,scss是常用写法, scss还是越 直观 越好,这种 运算类型 的特别是map类型的,尽量 不要在实际项目中使用 ,后续维护成本很高的。
Sass 和 SCSS 其实是同一种东西,我们平时都称之为 Sass,
后缀扩展名:
- Sass :“.sass”,
- SCSS :“.scss”
语法书写方式:
- Sass 是以严格的 缩进式 语法规则来书写, 不带 大括号 ({}) 和分号 (;) ,
- SCSS 的语法书写和我们的 CSS 语法书写方式非常类似
嵌套
选择器嵌套
#css
nav a {
color:red;
}
header nav a {
color:green;
}
#scss
nav {
a {
color: red;
header & {
color:green;
}
}
}
复制代码
属性嵌套
#css
.box {
border-top: 1px solid red;
border-bottom: 1px solid green;
}
#scss
.box {
border: {
top: 1px solid red;
bottom: 1px solid green;
}
}
伪类嵌套
.clearfix{
&:after {
clear:both;
overflow: hidden;
}
}
变量
全局变量
局部变量
在选择器、函数、混合宏内定义的变量
em {
$color: red;//定义局部变量
a {
color: $color;//调用局部变量
}
}
默认值
$btn-primary-color : #fff !default;
混入指令/混合指令/宏指令 @mixin
Vue中叫它混入指令, 可以 设置参数,复用重复 代码块。但会生成 冗余 的代码块。比如在不同的地方调用一个相同的混合宏时,不能将两个 合成并集 形式。
不带参数混合宏
@mixin border-radius{
border-radius: 5px;
}
带参数混合宏
# 带值参数
@mixin border-radius($radius:5px){
border-radius: $radius;
}
# 不带值参数
@mixin border-radius($radius){
border-radius: $radius;
}
# 带多个参数
@mixin center($width,$height){
width: $width;
height: $height;
margin-top: -($height) / 2;
margin-left: -($width) / 2;
}
带特别多参数混合宏
当混合宏传的参数过多之时,可以使用“…”来替代
# 带特别多参数
@mixin box-shadow($shadows...){
@if length($shadows) >= 1 {
-webkit-box-shadow: $shadows;
box-shadow: $shadows;
} @else {
$shadows: 0 0 2px rgba(#000,.25);
-webkit-box-shadow: $shadow;
box-shadow: $shadow;
}
}
调用混合宏 @include
关键词“
@include
”来调用声明好的混合宏
button {
@include border-radius;
}
.box {
@include border-radius(3px);
}
.box-center {
@include center(500px,300px);
}
.box {
@include box-shadow(0 0 1px rgba(#000,.5),0 0 2px rgba(#000,.2));
}
继承@extend
继承已存在的类样式块,从而实现代码的继承
.btn {
border: 1px solid #ccc;
padding: 6px 10px;
font-size: 14px;
}
.btn, .btn-primary, .btn-second {
border: 1px solid #ccc;
padding: 6px 10px;
font-size: 14px;
}
.btn-primary {
background-color: #f36;
color: #fff;
}
.btn-second {
background-clor: orange;
color: #fff;
}
写成继承的形式,而且编译出来的 CSS 会将选择器合并在一起,形成组合选择器 .
.btn-primary {
background-color: #f36;
color: #fff;
@extend .btn;
}
.btn-second {
background-color: orange;
color: #fff;
@extend .btn;
}
占位符 %placeholder
%声明的代码,如果不被 @extend 调用的话,不会产生任何代码
%mt5 {
margin-top: 5px;
}
.btn {
@extend %mt5;
}
.block {
@extend %mt5;
}
通过 @extend 调用的占位符,编译出来的代码会将相同的代码合并在一起.
.btn, .block {
margin-top: 5px;
}
混合宏VS继承VS占位符
基础语法
插值#
(1)构建一个选择器
@mixin generate-sizes($class, $small, $medium, $big) {
.#{$class}-small { font-size: $small; }
.#{$class}-medium { font-size: $medium; }
.#{$class}-big { font-size: $big; }
}
@include generate-sizes("header-text", 12px, 20px, 40px);
(2) 属性变量
$properties: (margin, padding);
@mixin set-value($side, $value) {
@each $prop in $properties {
#{$prop}-#{$side}: $value;
}
}
.login-box {
@include set-value(top, 14px);
}
@mixin中插值不能作为赋值语句的值部分,只能用做属性名定义或者选择器构建时@include中不能使用插值
注释
1、类似 CSS 的注释方式,使用 ”/* ”开头,结属使用 ”*/ ”
2、类似 JS的注释方式,使用“//” 两者区别,
前者会在编译出来的 CSS
显示
,
后者在编译出来的 CSS 中不会显示
加减乘除 连带单位一起计算
加减法
在变量或属性中都可以做加法运算,但对于携带 不同类型的单位 时,在 Sass 中计算会报错
.content {
width: $full-width - $sidebar-width;
}
字符串拼接
div {
cursor: e + -resize;
}
编译后
div {cursor: e-resize;}
乘法
当一个单位同时声明两个值时会有问题
只能有一个值带单位
(比如 em ,px , %)
# 编译的时候报“20px*px isn't a valid CSS value.”错误信息。
.box {
width:10px * 2px;
}
# 正确的写法
.box {
width: 10px * 2;
}
除法
如果数值或它的任意部分是存储在一个变量中或是函数的返回值。
• 如果数值被 圆括号 包围。
• 如果数值是另一个数学表达式的一部分
在除法运算时,如果两个值带有相同的单位值时,除法运算之后会得到一个 不带单位 的数值
.box {
width: (1000px / 100px);
}
编译后
.box {
width: 10;
}
@if, @else if
, @else
条件
@mixin blockOrHidden($boolean:true) {
@if $boolean {
display: block;
}
@else {
display: none;
}
}
.block {
@include blockOrHidden;
}
.hidden{
@include blockOrHidden(false);
}
@for
@for $i from <start> through <end>
@for $i from <start> to <end>
关键字
through
表示包括 end
,而
to
则 不包括 end
@for $i from 1 through 3 {
.item-#{$i} { width: 2em * $i; }
}
@while循环
@while $types > 0 {
.while-#{$types} {
width: $type-width + $types;
}
$types: $types - 1;
}
@each循环
@each
循环就是去遍历一个列表,然后从列表中取出对应的值
@each $var in <list>
$list: adam john wynn mason kuroir;
@mixin author-images {
@each $author in $list {
.photo-#{$author} {
background: url("/images/avatars/#{$author}.png") no-repeat;
}
}
}
.author-bio {
@include author-images;
}
@import引入 SCSS 和 Sass
Sass 扩展了 CSS 的 @import 规则,让它能够引入 SCSS 和 Sass 文件。 所有引入的 SCSS 和 Sass 文件都会被合并并输出一个单一
描述:页面元素的 宽度 按照 屏幕分辨率进行适配调整 ,但 整体布局不变 。主要特征是像瀑布一样往下流, 有规律的无限遍历模块 。(阿里、字节考过)
css
给图片 添加样式 让图片 等宽并同行 显示。
1、首先我们定义一个 container 容器来装 所有图片 ,在这个容器中用 box 容器装 box-img 容器再装入 每张图片 ,这样方便之后样式的编写。
2、使图片 同行 显示–给box容器使用 float:left;属性 。
3、让图片 等宽 显示–给box-img容器设置 width:150px; ,img标签设置 width:100%;继承父容器box-img高度的100%
<!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>waterFall</title>
<script src="./index.js"></script>
</head>
<style>
<!--清除浏览器自带样式-->
*{
margin: 0;
padding: 0;
}
<!--overflow: hidden触发BFC-->
.container {
overflow: hidden;
position: relative;
}
<!--float: left同行显示-->
.box{
float: left;
padding: 5px;
}
<!--图片等宽-->
.box-img{
width: 150px;
padding: 5px;
border: 1px solid #484848;
box-shadow: 0 0 5px #484848;
}
<!--图片占满父容器-->
.box-img img{
width: 100%;
}
</style>
<body>
<div id="container">
<div class="box">
<div class="box-img">
<img src="./img/1.jpg" alt="">
</div>
</div>
/*.......后面接39个box,此处省略*/
</div>
</body>
</html>
js
1、首先用window.οnlοad=function(){}来实现 页面加载完毕后立即执行 的功能
调用 imgLocation(‘container’,‘box’)函数 来呈现最终效果,
传入的实参是 父容器 ‘container’,装图片的 子容器 ‘box’。
window.onload=function() {
imgLocation('container','box')
}
2、实现 imgLocation() 函数功能
1)首先我们得
获取所有要摆放的图片
,并将其存入一个
数组
中
function imgLocation(parent,content){
//得到父容器
var cparent=document.getElementById(parent)
//cparent下的所有的第一层的子容器 box
var ccontent=getChildElement(cparent,content)//数组,40个div
}
//取到父容器中的某一层子容器
function getChildElement(parent,content){
var contentArr=[]
var allContent=parent.getElementsByTagName('*')//通过标签来选中得到一个数组
//遍历allContent把其中类名为content的容器都存到contentArr数组中
for(var i=0;i<allContent.length;i++){
if(allContent[i].className==content){ //当前这个容器的类名是否为content
contentArr.push(allContent[i])
}
}
return contentArr;
}
2)得到这个
数组
后,找出
从谁开始
是需要
被摆放
位置的
首先获取 视窗的宽度 和 每张图片的宽度 ,
将两者 相除 并 向下取整 可得到 第一行 可以放置图片的数量,
自然也就知道了我们需要 操作 的那张图片的 序号 。
//从谁开始是需要被摆放位置的
var winWidth=document.documentElement.clientWidth;//视窗宽度
var imgWidth=ccontent[0].offsetWidth;//图片宽度
var num=Math.floor(winWidth/imgWidth)//第一行能放几张图
3)得到需要
被摆放
位置的图片序号后,确定其
摆放位置
定义一个 存储高度的数组 ,对前一行元素的 高度 进行 遍历 并 存入 数组,
当遍历到需要 被摆放 位置的图片时,
用 Math.min()方法 获取前一行高度 最矮 的元素高度,并用 indexOf()方法 获取到其 下标 。
再对我们 所操作 的这个图片容器的 样式调整 :
position:absolute; 绝对定位 ,
top值 设置为前一行高度最矮的图片高度 minHeight ,
left值 设置为单张图片宽度 乘 这张图片的下标 minIndex 。
最后,摆放好图片后,还要 更新 摆放的那一列的 高度
//操作num+1张图
var BoxHeightArr=[]
for(var i=0;i<ccontent.length;i++){
//前num张只要计算高度
if(i<num){
BoxHeightArr[i]=ccontent[i].offsetHeight
}
else{
//我们要操作的box :ccontent[i]
var minHeight=Math.min.apply(null,BoxHeightArr)//apply:把最小值这个方法借给它用
var minIndex=BoxHeightArr.indexOf(minHeight)//返回数组下标
ccontent[i].style.position='absolute'//style设置样式
ccontent[i].style.top=minHeight+'px'
ccontent[i].style.left=imgWidth*minIndex+'px'
//更新最矮的那一列的高度
BoxHeightArr[minIndex]+=ccontent[i].offsetHeight
}
}
共性
问项目中有哪些难点,怎么解决的
代码遇到冲突怎么办
业务系统的搭建过程
复盘过程
拆分组件
复用和业务逻辑拆分
如何实现自适应页面
说到媒体查询和多个样式表
React如何实现动画的(数字不断变大的动画)
最后是用库实现的
介绍
打卡考勤项目
实现功能
- Navicat创建 数据库和空表
- 加载 MySQL等模块
- 创建 MySQL连接池(设置服务器 地址host ,服务器 端口号port ) 和 服务器 对象server
-
3306是MySQL的默认 端口
-
默认的 服务端口 就是 8080 。
-
跨源资源共享CORS
-
Origin
字段用来说名 本次请求来自哪个源 ,服务器根据这个值,决定是否同意这次请求。如果
Origin
指定的源不在允许范围之内,服务器就会返回一个正常的HTTP
回应,然后浏览器发现头信息中 没有包含Access-Control-Allow-Origin
字段,就知道出错啦,然后抛出错误
// 加载Express模块
const express = require('express');
// 加载MySQL模块
const mysql = require('mysql');
...
// 创建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']
}));
*0.0.0.0,localhost,127.0.0.1
网络需求 | 数据传输 | 访问 | 性质 |
---|---|---|---|
localhost | 不联网 | 不使用网卡,不受防火墙和网卡限制 | 本机访问 |
127.0.0.1 | 不联网 | 网卡传输,受防火墙和网卡限制 | 本机访问 |
本机IP | 联网 | 网卡传输 ,受防火墙和网卡限制 | 本机or外部访问 |
0.0.0.0
-
在 服务器 中:0.0.0.0表示 本机上的任意ip地址 ,
-
在 路由 中:0.0.0.0表示的是 默认路由 ,即当路由表中 没有找到完全匹配 的路由的时候所对应的路由。表示”任意IPV4主机”。
-
当一台 主机还没有被分配一个IP地址 的时候,用于表示 主机本身 。
localhost
localhost是个 域名 , 而不是一个ip地址 。可修改。
用于指代 this computer 或者 this host,可以用它来获取运行在 本机上的网络服务 。
在大多数系统中,localhost被指向了 IPV4 的 127.0.0.1 和 IPV6 的 ::1,这就是把localhost与127.0.0.1等同的原因。
127.0.0.1
本机地址 ,主要用于 测试
(127.x.x.x)是 本机回送 地址(Loopback Address),即主机 堆栈内部的 ,主要用于网络软件测试以及本地机 ,无论什么程序,一旦使用回送地址发送数据,协议软件立即返回,不进行任何 。
示例都使用端口 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;
//以username为条件进行查找操作,以保证用户名的唯一性
let sql = 'SELECT COUNT(id) AS count FROM reg_log WHERE username=?';
pool.query(sql, [username], (error, results) => {
if (error) throw error;
let count = results[0].count;
if (count == 0) {
// 将用户的相关信息插入到数据表
sql = 'INSERT reg_log(username,password) VALUES(?,MD5(?))';
pool.query(sql, [username, password], (error, results) => {
if (error) throw error;
res.send({
message: 'ok',
code: 200
});
})
} else {
res.send({
message: 'user exists',
code: 201
});
}
});
});
// 用户登录接口
server.post('/login', (req, res) => {
//获取用户名和密码信息
let username = req.body.username;
let password = req.body.password;
// SQL语句
let sql = 'SELECT id,username FROM reg_log WHERE username=? AND password=MD5(?)';
pool.query(sql, [username, password], (error, results) => {
if (error) throw error;
if (results.length == 0) { //登录失败
res.send({
message: 'login failed',
code: 201
});
} else { //登录成功
res.send({
message: 'ok',
code: 200,
result: results[0]
});
}
});
// 指定服务器对象监听的端口号
server.listen(3000, () => {
console.log('server is running...');
})
注册页,使用token,能避免CSRF攻击Cross-site request forgery。
//注册
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) {
...
}
});
}
//登陆
checkForm() {
// 点击登录按钮后调用此方法,同时验证用户名和密码
if (this.checkName() && this.checkPwd()) {
// 发送登录(post)请求
this.axios
.post("/login", `username=${this.name}&password=${this.pwd}`)
.then((result) => {
console.log(result);
if (result.data.code == 200) {
// 弹窗提示登录成功
this.$toast({
message: `欢迎您 ${this.name}`,
position: "bottom",
duration: 3000,
});
...
} else {
this.$toast({
message: "登录失败,请检查您的用户名和密码",
position: "bottom",
duration: 3000,
});
}
});
}
currentPage(当前页码)、total(总条数)、pageSize(每页展示的数据量)
把 所有的数据请求回 后,通过 arr.slice(开始索引,结束索引) 来进行截取每一页的数据;
假设当前页是currentPage = 1,pageSize = 5,那么应该从(currentPage-1)pageSize开始截取,到currentPagepageSize结束
弹窗组件modal
.modal {
display: none; /* 默认隐藏 */
position: fixed; /* 固定定位 */
z-index: 1; /* 设置在顶层 */
...
overflow: auto;
}
// 点击按钮打开弹窗
btn.onclick = function() {
modal.style.display = "block";
}
如何实现点击弹窗外面区域,弹窗消失
事件委托,往上遍历target的父元素如果有弹窗的id就不做反应,如果没有就触发消失
相关技术
主要目的是为了巩固基础知识,和适应大型项目的需求
- 框架,选择了 React ,说说其不同…., Router,Redux 基本使用
- 保持 TS 限定类型的习惯,减少报错,说下区别和原因及其常用,
- 使用 懒加载 ,组件库按需加载,优化性能,
- 使用 ant-design 做ui,学会利用API,省去设计样式的时间,实现样式和行为分离
- webpack打包
框架为一二面,面试官尤其喜欢问为什么要用+怎么用
为了简洁,相关文章参考链接在标题里
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,随后渲染到页面上
- key 用于识别唯一的 Virtual DOM 元素及其驱动 UI 的相应数据。
- 它们通过回收 DOM 中当前所有的元素来帮助 React 优化渲染。这些 key 必须是唯一的数字或字符串,
- React 只是 重新排序元素 而不是重新渲染它们。这可以提高应用程序的性能
渲染原理⭐⭐
- 单向数据流 。React 是一个 MVVM 框架,简单来说是在 MVC 的模式下在 前端部分拆分出数据层和视图层 。单向数据流指的是只能由 数据层的变化去影响视图层 的变化,而不能反过来(除非像Vue双向绑定)
- 数据驱动视图 。我们无需关注页面的 DOM,只需要关注数据即可
- 渲染过程:生命周期
- setState() 大部分时候是 异步执行 的, 提升性能 。
组件属性
每个 React 组件 强制要求必须有一个 render() 。它返回一个 React 元素 ,是 原生 DOM 组件 的表示。
构建组件的方式⭐⭐
React.createClass()、ES6 class 、无状态函数 。
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
}
...
}
状态提升⭐⭐
将 共享状态 提升到 最近的共同父组件 中去, 在父组件上改变 状态,然后通过 props分发 给子组件。
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 组件中唯一必须实现的方法。一旦组件被添加到 DOM,它只有在 prop 或状态 发生变化时才可能 更新和重新渲染 。
- 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>
);
}
}
状态提升⭐⭐
状态是 React 组件的 核心 ,是 数据的来源 ,必须尽可能简单。
基本上状态是确定组件呈现和行为的对象。
与 Props 不同,它们是可变的,并创建动态和交互式组件。可以通过this.state() 访问它们。
多个组件 需要 共享 的 状态提升 到它们最近的 父组件 上,在父组件上改变这个状态然后通过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 特性 ( 即 组件化模块 的特性 ) 。
⭐⭐⭐
以下帮助了解Hooks
Hooks(计算机术语):运行到某时机会调用某函数,例
onload
,addEventListener
Hooks(React):一系列以
“ use ”
作为开头的方法,可以完全避开class式 写法
,在 函数式组件 中完成 生命周期、状态管理、逻辑复用 等几乎 全部组件 开发工作的能力。
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>
);
}
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 ++;
…
}
倒计时( 手写)
//利用setTimeOut,每秒将数值减一
//利用useRef设置定时器,方便清楚
const [time,setTime]=useState(null)//倒计时时间
const timeRef=useRef()//设置延时器
//倒计时
useEffect(()=>{
//如果设置倒计时且倒计时不为0
if(time&&time!==0)
timeRef.current=setTimeout(()=>{
setTime(time=>time-1)
},1000)
//清楚延时器
return ()=>{
clearTimeout(timeRef.current)
}
},[time])
<button onClick={()=>{settime(60)}} >开始计时</button>
useCallback和useMemo
React 使用 来比较 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>
)
})
当点击按钮的时候,
组件会进行重新渲染,因为每次重新触发通过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(单页应用)的 路径管理器 ,
用于开发 单页 Web 应用
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 />,
}
]
}
]
}
];
接下来就是显示路由区域,利用
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;
可以看到 组件用于
声明式
路由切换使用。同样
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上,从而实现重定向的需求。
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
}
]
动态路由
根据 不同的UR L,可以访问 同一个组件 。在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相当于在 顶层组件 之上又加了一个 组件
单一事实来源⭐
Redux: 所有组件的状态 都存储在store 中,并且它们从 store 本身接收更新。
单一状态树 可 以更容易地跟踪随 时间的变化,并调试或检查程序。
Redux 的组件⭐⭐⭐
- Action 这是一个用来描述 发生 了什么事情的 对象
- Reducer 这是一个 确定状态将如何变化 的地方
- Store 整个程序的状态/对象树 保存在 Store 中
- View 查只 显示 Store 提供的数据
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对象就可以在其他组件中进行使用了,例如在
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将多个状态更新分组到单个重新渲染中以获得更好的性能。
function App () {
const [ count , setCount ] = useState ( 0 ) ;
const [ flag , setFlag ] = useState ( false ) ;
function handleClick ( ) {
setCount ( c => c + 1 ) ; // 还没有重新渲染
setFlag ( f => ! f ) ; // 还没有重新渲染
// React 只会在最后重新渲染一次(这是批处理!)
}
return (
< div >
< button onClick = { handleClick } > Next < / button >
< h1 style = { { color : flag ? "blue" : "black" } } > { count } < / h1 >
< / div >
) ;
}
flushSync清洗同步
与组件中的state一样自动批处理(即合并修改,每次set只渲染一次),可用
flushSync
方法消除
import { flushSync } from 'react-dom'; // Note: react-dom, not react
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
// React has updated the DOM by now
flushSync(() => {
setFlag(f => !f);
});
// React has updated the DOM by now
}
< Suspense >组件 的 SSR 支持
这基本上是服务器端渲染 (SSR) 逻辑的扩展 。
在典型的 React SSR 应用程序中,会发生以下步骤:
- 服务器获取需要在 UI 上显示的相关数据
- 服务器将 整个应用程序呈现为 HTML 并将其发送给客户端作为响应
- 客户端下载 JavaScript 包 (除了 HTML)
- 在最后一步,客户端将 javascript 逻辑连接到 HTML (称为 hydration )
在下一步可以开始之前,必须 立即 完成 整个应用程序 的 每个步骤 。在 初始加载时变慢且无响应 。
startTransition
状态更新 分类
- 紧急更新 反映 直接交互 ,如 打字、悬停、拖动 等。
- 过渡更新 将 UI 从一个 视图 过渡到另一个视图。
使用
默认 情况下,React 18 仍然将更新处理为 紧急更新 ,您可以通过将 更新包装到startTransition .
import { startTransition } from 'react' ;
// 紧急:显示输入的内容
setInputValue ( input ) ;
// 将内部的任何状态更新标记为转换
startTransition ( ( ) => {
// Transition: 显示结果
setSearchQuery ( input ) ;
} ) ;
包装在其中的更新startTransition被视为非紧急处理,如果出现更紧急的更新(如点击或按键),则会中断。
// 显示你输入的内容
setInputValue ( input ) ;
// 显示结果
setTimeout ( ( ) => {
setSearchQuery ( input ) ;
} , 0 ) ;
应用场景
startTransition来包装要 移动到后台的任何更新
- 缓慢渲染 :React 需要执行大量工作才能转换 UI 以显示结果。
- 慢速网络 :React 正在等待来自网络的一些数据。此用例与 Suspense 紧密集成。
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(控制器): 用户交互
【优点】
耦合性低,方便维护 ,可以利于分工协作 重用性高
【缺点】
使得项目架构变得 复杂 ,对开发人员要求高
- key 对 DOM 操作的代价非常高
- 程序运行缓慢且效率低下
- 内存浪费严重
- 由于循环依赖性,组件模型需要围绕 models 和 views 进行创建
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双向数据绑定
68747470733a2f2f62:6c6f672e6373646e2e6e65742f71715f32383833383839312f:61727469636c652f64657461696c732f313234363338343536