、、
```
**行内元素的特点:**
1、相邻行内元素在一行上,一行可以显示多个。
2、高、宽直接设置是无效的。
3、默认宽度就是它本身内容的宽度。
4、行内元素只能容纳文本或其他行内元素。
##### 3、行内块元素
```
、、
```
**行内块元素的特点**:
1、和相邻行内元素(行内块)在一行上,但是他们之间会有空白缝隙。
2、一行可以显示多个(行内元素特点)。
3、默认宽度就是它本身内容的宽度(行内元素特点)。
4、高度,行高、外边距以及内边距都可以控制(块级元素特点)。
##### 4、元素显示模式的转换
**转换方式**
- 转换为块元素:display:block;
- 转换为行内元素:display:inline;
- 转换为行内块:display: inline-block;
#### 背景
| 描述 | 语法 |
| ------------ | ------------------------------------------------------------ |
| 背景颜色 | background-color:颜色值; |
| 背景图片 | background-image:url(路径); |
| 背景平铺 | background-repeat:值;-->repeat平铺;no-repeat不平铺;repeat-x水平平铺;repeat-y垂直平铺; |
| 背景图片固定 | background-attachment:值;-->scoll滚动;fiexd固定; |
| 背景图片位置 | background-position:X Y; |
| 背景色半透明 | background: rgba(0, 0, 0, .3); |
| 背景图片大小 | background-size:宽度 高度;-->cover两边都铺满;contain铺满一边即可; |
综合写法:
background: 背景颜色 背景图片地址 背景平铺 背景图像滚动 背景图片位置;
```
background : [background-color] | [background-image]|[background-repeat] | [background-position][/background-size] |
```
扩展:
opacity:0~1; -->透明度
------
### 边框
| 描述 | 语法 |
| -------- | ------------------------------------------------------------ |
| 边框粗细 | border-width:10px; |
| 边框样式 | border-style:值; -->solid边框为单实线(最为常用的);dashed边框为虚线; dotted边框为点线; |
| 边框颜色 | border-color:值; |
综合写法:
border : border-width border-style border-color;
#### 圆角边框
语法:
```
border-radius:length;
```
- 参数值可以为数值或百分比的形式
- 如果是正方形,想要设置为一个圆,把数值修改为高度或者宽度的一半即可,或者直接写为 50%
- 该属性是一个简写属性,可以跟四个值,分别代表左上角、右上角、右下角、左下角
- 分开写:border-top-left-radius、border-top-right-radius、border-bottom-right-radius 和border-bottom-left-radius
- 兼容性 ie9+ 浏览器支持, 但是不会影响页面布局,可以放心使用
#### 表格的细线边框
1、border-collapse 属性控制浏览器绘制表格边框的方式。它控制相邻单元格的边框。
2、语法:
```
border-collapse:collapse;
```
------
### 内外边距
#### 1、内边距
定义:padding 属性用于设置内边距,即边框与内容之间的距离 。

内边距会影响盒子实际大小:
1、当我们给盒子指定 padding 值之后,发生了 2 件事情:
内容和边框有了距离,添加了内边距。
padding影响了盒子实际大小。
2、内边距对盒子大小的影响:
如果盒子已经有了宽度和高度,此时再指定内边框,会撑大盒子。
如何盒子本身没有指定width/height属性, 则此时padding不会撑开盒子大小。
3、解决方案:
如果保证盒子跟效果图大小保持一致,则让 width/height 减去多出来的内边距大小即可。
#### 2、外边距
定义:margin 属性用于设置外边距,即控制盒子和盒子之间的距离。

**问题:嵌套块元素垂直外边距的塌陷**
对于两个嵌套关系(父子关系)的块元素,父元素有上外边距同时子元素也有上外边距,此时父元素会塌陷较大的外边距值。
解决方案:
1、可以为父元素定义上边框。
2、可以为父元素定义上内边距。
3、可以为父元素添加 overflow:hidden。
------
### 浮动
语法:
```
选择器 { float: 属性值; }
```

#### 清除浮动
原因:由于父级盒子很多情况下,不方便给高度,但是子盒子浮动又不占有位置,最后父级盒子高度为 0 时,就会影响下面的标准流盒子。
##### 1、额外标签法
额外标签法会在浮动元素末尾添加一个空的标签。
```
例如 ,或者其他标签(如 等)。
```
##### 2、父级添加 overflow 属性
可以给父级添加 overflow 属性,将其属性值设置为 hidden、 auto 或 scroll 。
```
overflow:hidden | auto | scroll;
```
##### 3、父级添加after伪元素
```
.clearfix:after {
content: "";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
.clearfix { /* IE6、7 专有 */
*zoom: 1;
}
```
##### 4、父级添加双伪元素
```
.clearfix:before,
.clearfix:after {
content:"";
display:table;
}
.clearfix:after {
clear:both;
}
.clearfix {
*zoom:1;
}
```
------
### 元素的显示与隐藏
#### 1、display 显示
```
display: none 隐藏对象
display:block 除了转换为块级元素之外,同时还有显示元素的意思。
```
特点: display 隐藏元素后,**不再占**有原来的位置。
#### 2、visibility 可见性
```
visibility:visible ; 元素可视
visibility:hidden; 元素隐藏
```
特点:visibility 隐藏元素后,继续占有原来的位置。(停职留薪)
#### 3、overflow 溢出
| 属性值 | 描述 |
| ----------- | ------------------------------------------ |
| **visible** | 不剪切内容也不添加滚动条 |
| **hidden** | 不显示超过对象尺寸的内容,超出的部分隐藏掉 |
| **scroll** | 不管超出内容否,总是显示滚动条 |
| **auto** | 超出自动显示滚动条,不超出不显示滚动条 |
实际开发场景:
1、清除浮动;
2、隐藏超出内容,隐藏掉, 不允许内容超过父盒子。
------
### CSS 用户界面样式
#### 1、 鼠标样式 cursor
语法: cursor: 属性值;
| 属性值 | 描述 |
| ----------- | ------------ |
| default | 小白(默认) |
| pointer | 小手 |
| move | 移动 |
| text | 文本 |
| not-allowed | 禁止 |
#### 2、轮廓线 outline
给表单添加 outline: 0; 或者 outline: none; 样式之后,就可以去掉默认的蓝色边框。
#### 3、防止拖拽文本域 resize
语法:resize: none;
------
### vertical-align 属性应用(垂直对齐)
CSS 的 **vertical-align** 属性使用场景: 经常用于设置图片或者表单(行内块元素)和文字垂直对齐。
官方解释: 用于设置一个元素的**垂直对齐方式**,但是它只针对于行内元素或者行内块元素有效。
语法:
```
vertical-align : baseline | top | middle | bottom
```

应用:
1、图片、表单和文字对齐
给图片、表单这些行内块元素的 **vertical-align 属性设置为 middle** 就可以让文字和图片垂直居中对齐了。
2、解决图片底部默认空白缝隙问题
主要解决方法有两种:
1.**给图片**添加 **vertical-align:middle | top| bottom** 等。 (提倡使用的)
2.把图片转换为块级元素 **display: block**;
------
### 溢出的文字省略号显示
#### 单行
```
/*1. 先强制一行内显示文本*/
white-space: nowrap; ( 默认 normal 自动换行)
/*2. 超出的部分隐藏*/
overflow: hidden;
/*3. 文字用省略号替代超出的部分*/
text-overflow: ellipsis;
```
#### 多行
```
overflow: hidden;
text-overflow: ellipsis;
/* 弹性伸缩盒子模型显示 */
display: -webkit-box;
/* 限制在一个块元素显示的文本的行数 */
-webkit-line-clamp: 2;
/* 设置或检索伸缩盒对象的子元素的排列方式 */
-webkit-box-orient: vertical;
```
------
### 网站优化三大标签TDK及favicon图标
```
品优购
```
#### favicon图标的制作
网站:https://www.bitbug.net/

------
### 字体图标
网站:https://www.iconfont.cn/
#### 使用:
##### 第一种:Unicode编码
步骤:
引入样式表:iconfont.css
复制粘贴图标对应的Unicode编码
设置文字字体
```
例子
引入:
使用:
.side-nav .bd li::after {
content: "\e6fc";
font-family: "iconfont";
}
```
##### 第二种:类名
步骤:
引入样式表:iconfont.css
调用图标对应的类名,必须调用2个类名
```
例子
引入:
使用:
l iconfont类:基本样式,包含字体的使用等
2 icon-xxx:图标对应的类名
```
#### 上传:
需要将svg格式图标上传到iconfont得网,站然后下载使用;
------
### 转换transform
#### 平面转换(2D转换)
###### 1、位移 translate
语法:transform: translate(X,Y);
单独设置某个方向的移动距离:translateX() & translateY();
单位:
像素单位数值;
百分比(参照物为盒子自身尺寸;
###### 2、旋转 rotate
语法:transform: rotate(数字deg);
正值:顺时针旋转 ;负值:逆时针旋转 ;
单位:
deg;
###### 3、缩放 scale
语法:transform: scale(x,y);
实际的用法:transform: scale(1);
实际开发中取值可以是1个,表示等比缩放,如果取值为1表示原始大小不缩放,如果取值大于1表示放大的倍数,如果取值小于1表示缩小的倍数;
###### 4、中心点(原点)设置 transform-origin
语法:transform-origin: center center;(默认原点为中心点);
###### 5、复合写法
语法:transform: 位移 旋转 缩放;
注意:translate位移必须要写在最前面,如果旋转在最前面会导致坐标轴的位置偏移,导致问题;
#### 空间转换(3D转换)
###### 1、位移 translate
复合书写:translate3d(x,y,z);
单独设置某个方向的移动距离:translateX() & translateY() & translateZ();
单位:
像素单位数值;
百分比(参照物为盒子自身尺寸;
###### 2、旋转 rotate
复合书写:transform: rotate3d(x,y,z,angle); (用来设置自定义旋转的位置及旋转的角度,x,y,z的取值是0或者1,0表示没有,1表示有;)
单独设置某个方向的移动距离:rotateX() &rotateY() & rotateZ();
正值:顺时针旋转 ;负值:逆时针旋转 ;
单位:
deg;
###### 3、透视 perspective
语法:perspective: 800~1200;
注意:属性添加给父级;近大远小、近实远虚;
###### 4、3D呈现 preserve-3d
语法:transform-style: preserve-3d;
注意:属性添加给父级;
###### 5、缩放 scale(了解)
语法:transform: scale3d(x, y, z);
实际的用法:transform: scale(1);
实际开发中取值可以是1个,表示等比缩放,如果取值为1表示原始大小不缩放,如果取值大于1表示放大的倍数,如果取值小于1表示缩小的倍数;
------
### 线性渐变
用法:linear-gradient(颜色1, 颜色2,颜色3,...)
(设置正常从上到下渐变)

设置渐变的角度:linear-gradient(60deg, red, green) 60度可以换成方位名词(top left, red, blue)

透明渐变:linear-gradient(transparent, rgba(0, 0, 0, .3))

------
### 过渡动画
语法:transition: 属性 动画时间 动画形式 延时;
常用用法:transition: all 动画时间 ;
动画形式:
| 默认 ease |
| ----------------------------- |
| 匀速 linear |
| ease-in以慢速开始 ; |
| ease-out以慢速结束 ; |
| ease-in-out以慢速开始和结束; |
------
### 关键帧动画
#### 实现步骤
第一步:定义关键帧动画 @keyframes
```
@keyframes 动画名称 {
0% {}
...% {}
100% {}
}
或者
@keyframes 动画名称 {
from {}
to {}
}
```
第二步:调用动画
animation: name duration timing-function delay iteration-count direction fill-mode;
animation: 动画名称 动画时长 速度曲线 延迟时间 重复次数 动画方向 执行完毕时的状态;
| animation-name | 规定 @keyframes 动画的名称。(必须有) |
| ----------------------------- | ------------------------------------------------------------ |
| **animation-duration** | 规定动画完成一个周期所花费的秒或毫秒。默认是 0。(必须有) |
| animation-timing-function | 规定动画的速度曲线。默认是 "ease"。steps(步数) 逐帧(步长)动画设置动画完成需要多少帧(步数);linear 动画从头到尾的速度是相同的。 |
| animation-delay | 规定动画何时开始。默认是 0。必须写单位s。 |
| **animation-iteration-count** | 规定动画被播放的次数。默认是 1。循环是infinite; |
| **animation-direction** | 规定动画是否在下一周期逆向地播放。默认是 “normal”。 alternate逆向 |
| animation-play-state | 规定动画是否正在运行或暂停。默认是 "running“,暂停是paused。 |
| **animation-fill-mode** | 规定对象动画时间之外的状态。保持现状forwards,回到起始backwards。 |
***注意***:
1、animation-iteration-count和animation-fill-mode不能同时出现,同时出现时animation-fill-mode不生效;
2、实际开发中一个元素可以同时调用多组动画,animation在调用动画的时候用英文逗号隔开即可;
------
### 动画库
网站:http://www.dowebok.com/demo/2014/98/
使用步骤:
1、下载并引入.css文件;
2、调用(效果看官网)
```
//animated:表示调用了动画库;
//类名:从动画库查找
```
实现页面滚动到某一个位置再去加载动画,需要配合一个wow.js插件
使用步骤:
1、将代码复制到自己的html结构的最后面 ;
```
```
2、调用;(在class调用wow类名)
```
44
```
------
### CSS3滤镜filter
定义:图标模糊处理。
filter: 函数();
例如: filter: blur(5px); --> blur模糊处理 数值越大越模糊
------
### calc 函数
含义:calc() 此CSS函数让你在声明CSS属性值时执行一些计算
语法:width: calc(100% - 80px); -->括号里面可以使用 + - * / 来进行计算
------
### 盒子模型
可以分成两种情况:
1、box-sizing: content-box 盒子大小为 width + padding + border (以前默认的)
2、box-sizing: border-box 盒子大小为 width
如果盒子模型我们改为了box-sizing: border-box , 那padding和border就不会撑大盒子了(前提padding和border不会超过width宽度)
------
### 阴影
#### 1、盒子阴影
```
box-shadow: h-shadow v-shadow blur spread color inset;
```

#### 2、文字阴影
```
text-shadow: h-shadow v-shadow blur color;
```

------
### 定位
定位 = 定位模式 + 边偏移
#### 1、静态定位(static)
静态定位 按照标准流特性摆放位置,它没有边偏移。
静态定位在布局时我们几乎不用的
#### 2、相对定位(relative)
**相对定位**是元素在移动位置的时候,是相对于它自己**原来的位置**来说的(自恋型)。
语法:position: relative;
相对定位的特点:(务必记住)
1.它是相对于自己原来的位置来移动的(移动位置的时候参照点是自己原来的位置)。
2.**原来**在标准流的**位置**继续占有,后面的盒子仍然以标准流的方式对待它。
因此,**相对定位并没有脱标**。它最典型的应用是给绝对定位当爹的。
#### 3、绝对定位(absolute)
**绝对定位**是元素在移动位置的时候,是相对于它**祖先元素**来说的(拼爹型)。
特点:
**完全脱标** —— 完全不占位置;
**父元素没有定位**,则以**浏览器**为准定位(Document 文档)。
**父元素要有定位**元素将依据最近的已经定位(绝对、固定或相对定位)的父元素(祖先)进行定位。
#### 4、固定定位(fixed)
**固定定位**是元素**固定于浏览器可视区的位置**。(认死理型) 主要使用场景: 可以在浏览器页面滚动时元素的位置不会改变。
特点:
- **完全脱标**—— 完全不占位置;
- 只认**浏览器的可视窗口** —— `浏览器可视窗口 + 边偏移属性` 来设置元素的位置;
- 跟父元素没有任何关系;单独使用的
- 不随滚动条滚动。
------
### CSS属性书写顺序
建议遵循以下顺序:
1. **布局定位属性**:display / position / float / clear / visibility / overflow(建议 display 第一个写,毕竟关系到模式)
2. **自身属性**:width / height / margin / padding / border / background
3. **文本属性**:color / font / text-decoration / text-align / vertical-align / white- space / break-word
4. **其他属性(CSS3)**:content / cursor / border-radius / box-shadow / text-shadow / background:linear-gradient …
------
# 移动Web开发
------
### 视口
分类:
1、布局视口 layout viewport;
2、视觉视口 visual viewport;
3、理想视口 ideal viewport。
**总结:我们开发最终会用理想视口,而理想视口就是将布局视口的宽度修改为视觉视口**
#### meta标签
```
//width=device-width ---- 视口的宽度是设备屏幕的宽度
//initial-scale:初始比例,页面加载时的默认比例1.0
//user-scalable:指定用户是否可以对页面进行缩放 yes或者1允许/no或者0不允许
//minimum-scale:最小允许缩放的比率1.0
//maximum-scale:最大允许缩放的比率1.0
```
#### 查看技术是否兼容浏览器版本:https://caniuse.com/
------
### 移动开发选择和技术解决方案
#### 移动端技术解决方案
1.移动端浏览器兼容问题
移动端浏览器基本以 webkit 内核为主,因此我们就考虑webkit兼容性问题。
我们可以放心使用 H5 标签和 CSS3 样式。
同时我们浏览器的私有前缀我们只需要考虑添加 webkit 即可
2.移动端公共样式
移动端 CSS 初始化推荐使用 normalize.css/
Normalize.css:保护了有价值的默认值
Normalize.css:修复了浏览器的bug
Normalize.css:是模块化的
Normalize.css:拥有详细的文档
官网地址:
------
### 移动端特殊样式
```
/*CSS3盒子模型*/
box-sizing: border-box;
-webkit-box-sizing: border-box;
/*点击高亮我们需要清除清除 设置为transparent 完成透明*/
-webkit-tap-highlight-color: transparent;
/*在移动端浏览器默认的外观在iOS上加上这个属性才能给按钮和输入框自定义样式*/
-webkit-appearance: none;
/*禁用长按页面时的弹出菜单*/
img,a { -webkit-touch-callout: none; }
```
------
### flex布局
#### 父级盒子属性设置
| 描述 | 属性 | 值 |
| -------------------------------- | --------------- | ------------------------------------------------------------ |
| 设置Flex布局的主轴方向 | flex-direction | **column**:将主轴改为y轴;**row**:将主轴改为x轴;row- reverse:主轴为x轴,并且翻转,从右到左翻转排列;column- reverse:主轴为y轴,并且翻转,从下到上翻转排列 ; |
| 设置主轴子元素排列形式 | justify-content | flex-start;flex-end;**center**;**space-around**所有子元素平分剩余空间;**space-between**所有子元素中,先两边两个元素贴边,剩余的元素平分剩余空间(常用);**space-evenly**所有子元素之间的距离都一致 |
| 子元素是否换行 | flex-wrap | nowrap;wrap |
| 设置侧轴子元素对齐的方式(多行) | align-content | **flex-start**;flex-end;**center**;**space-around**所有子元素平分剩余空间;**space-between**所有子元素中,先两边两个元素贴边,剩余的元素平分剩余空间(常用);**stretch**默认值(拉升),如果项目未设置高度或设为auto,将占满整个容器的高度; |
| 设置侧轴子元素对齐的方式(单行) | align-items | **flex-start**;flex-end;**center**;**stretch**默认值(拉升),如果项目未设置高度或设为auto,将占满整个容器的高度; baseline项目(子元素)的第一行文字的基线对齐; ; |
#### 子级盒子属性设置
| 描述 | 属性 | 值 |
| ------------------------------------------------------------ | ---------- | --------------------------------------------- |
| 将容器(父级盒子)剩余空间按照占比分配给各个子盒子 | flex | 实际的数字1;百分比 |
| 项目(子级盒子)的排列顺序。 | order | 实际的一个数值,数值越小,排列越靠前,默认为0 |
| 单个项目有与其他项目不一样的对齐方式设置,可覆盖align-items属性; | align-self | **flex-start**;flex-end;center; stretch |
------
### rem布局
**安装px to rem 插件和Easy Less插件**
**rem值 = px值 / html文字大小**
**屏幕尺寸 / 划分份数 = html文字大小**
**不同的视口, HTML标签字号不同, 字号是视口宽度的1/10**
rem的基准是相对于html元素的字体大小。
```
/* 根html 为 12px */
html {
font-size: 12px;
}
/* 此时 div 的字体大小就是 24px */
div {
font-size: 2rem;
}
```
#### 媒体查询
##### 语法:
```
最好的写法:
@media (media feature媒体特性) {}
@media mediatype类型 and|not|only关键字 (media feature媒体特性) {}
```
1、mediatype 查询类型

2、关键字
and:可以将多个媒体特性连接到一起,相当于“且”的意思。
not:排除某个媒体类型,相当于“非”的意思,可以省略。
only:指定某个特定的媒体类型,可以省略。
3、 媒体特性

##### 媒体查询link写法
```
```
##### 媒体查询书写规则
注意: 为了防止混乱,媒体查询我们要按照从小到大或者从大到小的顺序来写,但是我们最喜欢的还是从小到大来写,这样代码更简洁。

#### rem适配方案
技术方案:
1.less+rem+媒体查询
2.lflexible.js+rem
#### less
Less中文网址:[http://](http://lesscss.cn/)[less](http://lesscss.cn/)[css.cn/](http://lesscss.cn/)
##### Less安装
①安装nodejs,可选择版本(8.0),网址:
②检查是否安装成功,使用cmd命令(win10是window+r 打开运行输入cmd) ---输入“node –v”查看版本即可
③基于nodejs在线安装Less,使用cmd命令“npm install -g less”即可
④检查是否安装成功,使用cmd命令“ lessc -v ”查看版本即可。
##### 用法:
###### 1、变量
```
@color: pink;
使用:
div {
@color
}
```
###### 2、 嵌套
```
// 将css改为less
#header .logo {
width: 300px;
}
#header {
.logo {
width: 300px;
}
}
```
**如果遇见 (交集|伪类|伪元素选择器) ,利用&进行连接**
```
a:hover{
color:red;
}
a{
&:hover{
color:red;
}
}
```
###### 3、运算
```
/*Less 里面写*/
@witdh: 10px + 5;
div {
border: @witdh solid red;
}
/*生成的css*/
div {
border: 15px solid red;
}
/*Less 甚至还可以这样 */
width: (@width + 5) * 2;
```
+ 乘号(*)和除号(/)的写法
+ 除号(/)需要加小括号
+ 运算符中间左右有个空格隔开 1px + 5
+ 对于两个不同的单位的值之间的运算,运算结果的值取第一个值的单位
+ 如果两个值之间只有一个值有单位,则运算结果就取该单位
##### 样式导入
```
@import "./common.less";
可以简写为:
@import "./common";
```
**导入语句要书写在导入文件的的最前面;**
##### 导出路径设置
实际开发中我们需要将less文件和css文件分开保存,所以我们需要设置less翻译成css的存储位置
###### 导出的路径是统一
```
设置 → 搜索Easy Less → 在setting.json中编辑 → 在less.compile中添加代码----"out":"您的路径"
```

###### 单独路径导出
语法: // out:您要导出的路径

注意:
1、书写单独输出路径的时候最后面不需要加封号;
2、必须写在less文件的第一行;
###### 禁止导出
语法:// out:false
注意:禁止导出语句一定要写在文件的第一行,后面一定不能写封号

#### **flexible.js适配**
##### 地址:
github地址:https://github.com/amfe/lib-flexible
官方文档地址:https://github.com/amfe/article/issues/17
##### 工作原理:
flexible.js时间设备的视口平均划分为10份,然后再去按照不同的视口大小计算出各自的html根标签的文字大小。
##### 使用方法:
第一步:将flexible.js拷贝到自己的项目中。
第二步:将flexible.js利用script标签引入到自己html页面的最后面。
------
### vw/vh布局
根据视口的宽高进行相对大小计算结果
**vh = px / (1/100*视口高度)**
**vw = px / (1/100*视口宽度)**
> ***不允许vw vh 混合使用***
变量
```
@vw: 3.75vw;
使用:
div {
height: (18 / @vw)
}
```
------
# Bootstrap
#### 概念:
在不同屏幕下,通过媒体查询来改变这个布局容器的大小,再改变里面子元素的排列方式和大小,从而实现不同屏幕下,看到不同的页面布局和样式变化。
#### 常见屏幕尺寸下布局容器大小设置
超小屏幕(手机、小于768px):设置宽度为100%;
小屏幕(平板,大于等于768px):设置宽度为750px;
中等屏幕(桌面显示器,大于等于992px):设置宽度为970px;
大屏幕(大桌面显示器,大于等于1200px):设置宽度为1170px;
#### 中文网站:
http://www.bootcss.com/
#### 使用
01、将下载的生产环境文件夹直接复制到自己的项目中,并且引入css文件
02、布局容器 --- container 类
03、栅格系统
#### bootstrap布局容器
Bootstrap 需要为页面内容和栅格系统包裹一个 .container 或者.container-fluid 容器,它提供了两个作此用处的类。
.container
+ 响应式布局的容器 固定宽度
+ 大屏 ( >=1200px) 宽度定为 1170px
+ 中屏 ( >=992px) 宽度定为 970px
+ 小屏 ( >=768px) 宽度定为 750px
+ 超小屏 (100%)
.container-fluid
+ 流式布局容器 百分百宽度
+ 占据全部视口(viewport)的容器。
#### bootstrap栅格系统
+ 按照不同屏幕划分为1~12 等份
+ 行(row) 可以去除父容器作用15px的边距
+ xs-extra small:超小; sm-small:小; md-medium:中等; lg-large:大;
+ 列(column)大于 12,多余的“列(column)”所在的元素将被作为一个整体另起一行排列
+ 每一列默认有左右15像素的 padding
+ 可以同时为一列指定多个设备的类名,以便划分不同份数 例如 class="col-md-4 col-sm-6"
#### 列偏移
使用 .col-md-offset-* 类可以将列向右侧偏移。这些类实际是通过使用 * 选择器为当前元素增加了左侧的边距(margin)。
col-md-offset- 数字 = 12 - 已有的盒子份数;
盒子居中:(12 - 已有的份数) / 2
```
```
#### 列排序
通过使用 .col-md-push-* 和 .col-md-pull-* 类就可以很容易的改变列(column)的顺序。
push : 推;
pull : 拉;
```
```
#### 响应式工具

------
# JavaScript
## 基础
### 书写格式
1、行内式
```
```
2、内嵌式
```
```
3、外部JS文件
```
```
### JavaScript输入输出语句
| 方法 | 说明 | 归属 |
| ------------------ | ------------------------------ | ------ |
| alert("msg") | 浏览器弹出警示框 | 浏览器 |
| console.log("msg") | 浏览器控制台打印输出信息 | 浏览器 |
| prompt("info") | 浏览器弹出输入框,用户可以输入 | 浏览器 |
### 变量
#### 使用
1、变量的声明
```
// 声明变量
var age; // 声明一个 名称为age 的变量
```
2、变量的赋值
```
age = 10; // 给 age 这个变量赋值为 10
```
#### 变量的初始化
```
var age = 18; // 声明变量同时赋值为 18
// 声明一个变量并赋值, 我们称之为变量的初始化。
```
#### 变量语法扩展
- 更新变量
一个变量被重新复赋值后,它原有的值就会被覆盖,变量值将以最后一次赋的值为准。
```js
var age = 18;
age = 81; // 最后的结果就是81因为18 被覆盖掉了
```
- 同时声明多个变量
同时声明多个变量时,只需要写一个 var, 多个变量名之间使用英文逗号隔开。
```js
var age = 10, name = 'zs', sex = 2;
```
- 声明变量特殊情况
| 情况 | 说明 | 结果 |
| ------------------------------ | ----------------------- | --------- |
| var age ; console.log (age); | 只声明 不赋值 | undefined |
| console.log(age) | 不声明 不赋值 直接使用 | 报错 |
| age = 10; console.log (age); | 不声明 只赋值 | 10 |
#### 变量命名规范
规则:
- 由字母(A-Za-z)、数字(0-9)、下划线(_)、美元符号( $ )组成,如:usrAge, num01, _name
- 严格区分大小写。var app; 和 var App; 是两个变量
- 不能 以数字开头。 18age 是错误的
- 不能 是关键字、保留字。例如:var、for、while
- 变量名必须有意义。 MMD BBD nl → age
- 遵守驼峰命名法。首字母小写,后面单词的首字母需要大写。
- myFirstName\4-笔记\images\图片15.png)
推荐翻译网站: 有道 爱词霸
### 数据类型
分类:1、简单数据类型 (Number,String,Boolean,Undefined,Null)
2、复杂数据类型 (object)
#### 简单数据类型

##### 1、数字型 Number
JavaScript中数值的最大和最小值
- 最大值:Number.MAX_VALUE,这个值为: 1.7976931348623157e+308
- 最小值:Number.MIN_VALUE,这个值为:5e-32
数字型三个特殊值
- Infinity ,代表无穷大,大于任何数值
- -Infinity ,代表无穷小,小于任何数值
- NaN ,Not a number,代表一个非数值
isNaN():
- 用来判断一个变量是否为非数字的类型,返回 true 或者 false
**toFixed()** 保留小数
方法可把 Number 四舍五入为指定小数位数的数字。`NumberObject.toFixed(num)`
##### 2、字符串型 String
字符串型可以是引号中的任意文本,其语法为 双引号 "" 和 单引号''
###### 字符串转义符
| 转义符 | 解释说明 |
| ------ | --------------------------------- |
| \n | 换行符,n 是 newline 的意思 |
| \ \ | 斜杠 \ |
| \' | ' 单引号 |
| \" | ”双引号 |
| \t | tab 缩进 |
| \b | 空格 ,b 是 blank 的意思 |
###### 检测字符串长度
String.length
###### 字符串拼接
```
//1.1 字符串 "相加"
alert('hello' + ' ' + 'world'); // hello world
//1.2 数值字符串 "相加"
alert('100' + '100'); // 100100
//1.3 数值字符串 + 数值
alert('11' + 12); // 1112
```
- 经常会将字符串和变量来拼接,变量可以很方便地修改里面的值
- 变量是不能添加引号的,因为加引号的变量会变成字符串
- 如果变量两侧都有字符串拼接,口诀“引引加加 ”,删掉数字,变量写加中间
###### 字符串补全
padStart(目标长度,'填充的字符串')
```
console.log("h".padStart(5,"o")); // "ooooh"
```
##### 3、布尔型Boolean
布尔类型有两个值:true 和 false ,其中 true 表示真(对),而 false 表示假(错)。
布尔型和数字型相加的时候, true 的值为 1 ,false 的值为 0。
##### 4、未定义Undefined
##### 5、空值Null
#### 获取变量数据类型
typeof 可用来获取检测变量的数据类型
```
var num = 18;
console.log(typeof num) // 结果 number
```

Object.prototype.toString(更好的方法)
```
Object.prototype.toString.call(undefined)// "Undefined" Object.prototype.toString.call(null) // "Null" Object.prototype.toString.call(3) // "Number" Object.prototype.toString.call(new Number(3)) // "Number" Object.prototype.toString.call(true) // "Boolean" Object.prototype.toString.call('3') // "String" Object.prototype.toString.call(Symbol())// "Symbol
```
#### 数据类型转换
##### 1、转换为字符串

- 变量.toString() 和 String(变量) 使用方式不一样。
- 三种转换方式,更多第三种加号拼接字符串转换方式, 这一种方式也称之为隐式转换。
##### 2、转换为数字型

- 注意 parseInt 和 parseFloat 单词的大小写,这2个是重点
- 隐式转换是我们在进行算数运算的时候,JS 自动转换了数据类型
##### 3、转换为布尔型

- 代表空、否定的值会被转换为 false ,如 ''、0、NaN、null、undefined
- 其余值都会被转换为 true
### 运算符
#### 运算符优先级

#### 1、算数运算符

#### 2、递增和递减运算符
前置递增运算符
```
++num 前置递增,就是自加1,类似于 num = num + 1,但是 ++num 写起来更简单。
使用口诀:先自加,后返回值
```
后置递增运算符
```
num++ 后置递增,就是自加1,类似于 num = num + 1 ,但是 num++ 写起来更简单。
使用口诀:先返回原值,后自加
```
#### 3、比较运算符

#### 4、逻辑运算符

逻辑与&&:两边都是 true才返回 true,否则返回 false;
逻辑或 ||:一边是 true则返回 true,否则返回 false;
逻辑非 !:逻辑非(!)也叫作取反符,用来取一个布尔值相反的值,如 true 的相反值是 false;
短路运算(逻辑中断):
```
逻辑与:
- 如果第一个表达式的值为真,则返回表达式2
- 如果第一个表达式的值为假,则返回表达式1
```
```
逻辑或
- 如果第一个表达式的值为真,则返回表达式1
- 如果第一个表达式的值为假,则返回表达式2
```
#### 5、赋值运算符

```
var age = 10;
age += 5; // 相当于 age = age + 5;
age -= 5; // 相当于 age = age - 5;
age *= 10; // 相当于 age = age * 10;
```
### 流程控制
#### 分支流程控制
##### 1、if 语句
```
// 条件成立执行代码,否则什么也不做
if (条件表达式) {
// 条件成立执行的代码语句
}
// 条件成立 执行 if 里面代码,否则执行else 里面的代码
if (条件表达式) {
// [如果] 条件成立执行的代码
} else {
// [否则] 执行的代码
}
// 适合于检查多重条件。
if (条件表达式1) {
语句1;
} else if (条件表达式2) {
语句2;
} else if (条件表达式3) {
语句3;
....
} else {
// 上述条件都不成立执行此处代码
}
```
##### 2、三元表达式
- 语法结构
```js
表达式1 ? 表达式2 : 表达式3;
```
- 执行思路
- 如果表达式1为 true ,则返回表达式2的值,如果表达式1为 false,则返回表达式3的值
- 简单理解: 就类似于 if else (双分支) 的简写
##### 3、switch 语句
- 语法结构
```
switch( 表达式 ){
case value1:
// 表达式 等于 value1 时要执行的代码
break;
case value2:
// 表达式 等于 value2 时要执行的代码
break;
default:
// 表达式 不等于任何一个 value 时要执行的代码
}
```
**注意:**
1、执行case 里面的语句时,如果没有break,则继续执行下一个case里面的语句。
2、表达式为固定值,变量时,必须时全等。
#### 循环
##### 1、for循环
- 语法结构
```
for(初始化变量; 条件表达式; 操作表达式 ){
//循环体
}
```
| 名称 | 作用 |
| ---------- | ------------------------------------------------------------ |
| 初始化变量 | 通常被用于初始化一个计数器,该表达式可以使用 var 关键字声明新的变量,这个变量帮我们来记录次数。 |
| 条件表达式 | 用于确定每一次循环是否能被执行。如果结果是 true 就继续循环,否则退出循环。 |
| 操作表达式 | 用于确定每一次循环是否能被执行。如果结果是 true 就继续循环,否则退出循环。 |
- for 循环小结
for 循环可以重复执行某些相同代码
for 循环可以重复执行些许不同的代码,因为我们有计数器
for 循环可以重复执行某些操作,比如算术运算符加法操作
随着需求增加,双重for循环可以做更多、更好看的效果
双重 for 循环,外层循环一次,内层 for 循环全部执行
for 循环是循环条件和数字直接相关的循环
##### 2、while循环
- 语法结构
```
while (条件表达式) {
// 循环体代码
}
```
**注意:**
使用 while 循环时一定要注意,它必须要有退出条件,否则会成为死循环
##### 3、do-while循环
- 语法结构
```
do {
// 循环体代码 - 条件表达式为 true 时重复执行循环体代码
} while(条件表达式);
```
**注意:**
先再执行循环体,再判断,do…while循环语句至少会执行一次循环体代码
##### 4、for...in 语句
用于对数组或者对象的属性进行循环操作。
```
for (var k in obj) {
console.log(k); // 这里的 k 是属性名
console.log(obj[k]); // 这里的 obj[k] 是属性值
}
```
##### 5、关键字
continue:continue 关键字用于立即跳出本次循环,继续下一次循环(本次循环体中 continue 之后的代码就会少执行一次)。
break:直接退出整个for 循环,跳到整个for下面的语句。
return :不仅可以退出循环,还能够返回 return 语句中的值,同时还可以结束当前的函数体内的代码
### 函数
函数:就是**封装了一段可被重复调用执行的代码块**。通过此代码块可以**实现大量代码的重复使用**。
#### 函数的使用
##### 1、函数声明(命名函数)
```
// 声明函数
function 函数名(形参...) {
//函数体代码
}
//调用
函数名(实参);
```
##### 2、函数表达式(匿名函数)
```
// 声明函数
var 变量名 = function(形参...) {
//函数体代码
}
//调用
变量名(实参);
```
##### 3、函数形参和实参

注意:在JavaScript中,形参的默认值是undefined。
小结:
- 函数可以带参数也可以不带参数
- 声明函数的时候,函数名括号里面的是形参,形参的默认值为 undefined
- 调用函数的时候,函数名括号里面的是实参
- 多个参数中间用逗号分隔
- 形参的个数可以和实参个数不匹配,但是结果不可预计,我们尽量要匹配
#### 函数返回值
返回值:函数调用整体代表的数据;函数执行完成后可以通过return语句将指定数据返回 。
```
// 声明函数
function 函数名(){
...
return 需要返回的值;
}
// 调用函数
函数名(); // 此时调用函数就可以得到函数体内return 后面的值
```
- 在使用 return 语句时,函数会停止执行,并返回指定的值
- 如果函数没有 return ,返回的值是 undefined
#### arguments的使用
arguments 对象中存储了传递的所有实参。
具有以下特点:
- 具有 length 属性
- 按索引方式储存数据
- 不具有数组的 push , pop 等方法
注意:在函数内部使用该对象,用此对象获取函数调用时传的实参。
#### 立即执行函数
含义: 不需要调用,立马执行的函数。
1、 ( function () {} )() //常用,多个时要用“;”隔开。
2、 ( function () {} ())
*立即执行函数最大的作用就是 独立创建了一个作用域, 里面所有的变量都是局部变量 不会有命名冲突的情况*
### 对象
#### 创建对象
##### 1、使用对象字面量创建
```
//创建对象
var 对象名 = {}; // 键值对的形式,多个属性用“ ,”隔开
var star = {
name : 'pink',
age : 18,
sex : '男',
sayHi : function(){ //sayHi属于对象的方法
alert('大家好啊~');
}
};
//调用
对象里面的属性调用:对象.属性名;
或者
对象[‘属性名’]//也可以判断对象中是否有这个属性;
对象里面的方法调用: 对象.方法名();
```
##### 2、利用 new Object 创建
```
var andy = new Object();
andy.属性名 = 'pink';
andy.属性名 = 18;
andy.属性名 = '男';
andy.方法名 = function(){
alert('大家好啊~');
}
```
##### 3、利用构造函数创建
构造函数:把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
```
//创建
function 构造函数名(形参1,形参2,形参3) {
this.属性名1 = 参数1;
this.属性名2 = 参数2;
this.属性名3 = 参数3;
this.方法名 = 函数体;
}
//调用
var obj = new 构造函数名(实参1,实参2,实参3)
```
**注意事项**:
1. 构造函数约定**首字母大写**。
2. 函数内的属性和方法前面需要添加 **this** ,表示当前对象的属性和方法。
3. 构造函数中**不需要 return 返回结果**。
4. 当我们创建对象的时候,**必须用 new 来调用构造函数**。
#### 遍历对象
for...in 语句
用于对数组或者对象的属性进行循环操作。
```
for (var k in obj) {
console.log(k); // 这里的 k 是属性名
console.log(obj[k]); // 这里的 obj[k] 是属性值
}
```
#### 内置对象
JavaScript 中的对象分为3种:**自定义对象 、内置对象、 浏览器对象**
MDN:https://developer.mozilla.org/zh-CN/
##### 1、Math对象
Math 对象不是构造函数。
| 属性、方法名 | 功能 |
| --------------------- | -------------------------------------------- |
| Math.PI | 圆周率 |
| Math.floor() | 向下取整 |
| Math.ceil() | 向上取整 |
| Math.round() | 四舍五入版 就近取整 注意 -3.5 结果是 -3 |
| Math.abs() | 绝对值 |
| Math.max()/Math.min() | 求最大和最小值 |
| Math.random() | 获取范围在[0,1)内的随机值 |
**获取指定范围内的随机整数**:
```
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
```
##### 2、Date对象(日期)
Date是一个构造函数。
###### 使用Date实例化日期对象:
```
获取当前时间必须实例化
var now = new Date();
获取指定时间的日期对象
var future = new Date('2019-10-1');
//参数常用的写法 数字型 2019, 10, 01 或者是 字符串型 '2019-10-1 8:8:8'
注意:如果创建实例时并未传入参数,则得到的日期对象是当前时间对应的日期对象
```
###### Date实例的方法和属性
| 代码 | 说明 |
| ------------------- | ---------------------------------------- |
| date.getFullYear(); | 返回当前年 |
| date.getMonth() +1; | 返回当前月,要+1 |
| date.getDate(); | 返回当前几号 |
| date.getDay(); | 返回当前周几,周一为1,周六为6, 周日为0 |
| date.getHours(); | 返回当前小时 |
| date.getMinutes(); | 返回当前分钟 |
| date.getSeconds(); | 返回当前秒钟 |
###### 获取总毫秒数
总毫秒数的含义:基于1970年1月1日(世界标准时间)起的毫秒数。
```
// 实例化Date对象
var date = new Date();
// 1. 用于获取对象的原始值
console.log(date.valueOf())
console.log(date.getTime())
// 2. 简单写可以这么做 // 常用
var date = +new Date();
// 3. HTML5中提供的方法,有兼容性问题
var date = Date.now();
```
###### 毫秒数转为天、时、时、秒
毫秒数 / 1000 = 秒数
天:parseInt(秒数 / 60 / 60 / 24);
时:parseInt(秒数 / 60 / 60 % 24);
时:parseInt(秒数 / 60 % 60);
秒:parseInt(秒数 %60);
##### 3、数组对象
###### 创建数组
1、利用 new 创建数组
```
var 数组名 = new Array() ;
var arr = new Array(); // 创建一个新的空数组
```
2、利用数组字面量创建数组
```
//1. 使用数组字面量方式创建空的数组
var 数组名 = [];
//2. 使用数组字面量方式创建带初始值的数组
var 数组名 = ['小白','小黑','大黄','瑞奇'];
```
###### 获取数组中的元素
数组可以通过索引来访问、设置、修改对应的数组元素,可以通过“数组名[索引]”的形式来获取数组中的元素。

```
// 定义数组
var arrStus = [1,2,3];
// 获取数组中的第2个元素
alert(arrStus[1]);
```
**注意:**
如果访问时数组没有和索引值对应的元素,则得到的值是undefined。
###### 遍历数组
```
var arr = ['red','green', 'blue'];
for(var i = 0; i < arr.length; i++){
console.log(arrStus[i]);
}
```
###### 数组的长度
```
var arrStus = [1,2,3];
alert(arrStus.length); // 3
```
###### 新增数组元素
数组中可以通过以下方式在数组的末尾插入新元素
```
数组[ 数组.length ] = 新数据;
```
###### 检测是否为数组
```
1、数组名 instanceof Array;
2、Array.isArray(数组名); // 常用
```
###### 添加删除数组元素的方法


```
var arr1 = [1, 2, 3],
arr2 = [4, 5, 6];
console.log(arr1.concat(arr2));
var arr3 = [1, 2, 3, 4, 5, 6];
//不包括end;当只有一个数字时,从start到结尾全部
console.log(arr3.slice(2, 5));
var arr4 = [1, 2, 3, 4, 5, 6];
//删除(索引号,删除个数)
//添加 (索引号,0,添加的值)
//替换 (索引号, 替换几个,替换的值)
arr4.splice(2, 1);
console.log(arr4);
```
###### 数组排序

注意:sort方法需要传入参数来设置升序、降序排序
- 如果传入“function(a,b){ return a-b;}”,则为升序
- 常规数组:如果传入“function(a,b){ return b-a;}”,则为降序
- 对象数组:
```
this.list.sort((a, b) => {
return a.id > b.id ? 1 : -1
或者: a.id - b.id
})升序
```
###### 获取数组索引方法
```
1、数组名.indexOf(数组元素) //从前往后找
2、数组名.lastIndexOf(数组元素) //从后往前找
```
**注意:**
只返回第一个满足的元素的索引号;找不到则返回“-1”;
###### 数组转换为字符串

**注意:**
join方法如果不传入参数,则按照 “ , ”拼接元素。常用//
##### 4、字符串对象
基本数据类型包装为复杂数据类型,其执行过程如下(浏览器自动执行) :
```
// 1. 生成临时变量,把简单类型包装为复杂数据类型
var temp = new String('andy');
// 2. 赋值给我们声明的字符变量
str = temp;
// 3. 销毁临时变量
temp = null;
```
###### 根据字符返回位置
```
1、变量名.indexOf(‘查的字符串’,[起始位置]) //从前往后找
2、变量名.lastIndexOf(‘查的字符串’,[起始位置]) //从后往前找
```
**注意:**
只返回第一个满足的元素的索引号;找不到则返回“-1”;
###### 根据位置返回字符

**注意:**
常用第三种;
###### 字符串操作方法

```
//替换字符 注意:只会替换第一个字符串
字符串.replace("被替换的字符串", "要替换为的字符串");
//转为数组
字符串.split("分割字符")
//去除左右两边空白+
字符串.trim()
//把字符串转换为大写。
字符串.toUpperCase()
//补零
padStart(补零后几位数,'0')
例如:var str ='8'
str.padStart(2,'0')
输出:08
```
## 高级
### 面向对象
面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
| | 面向过程 | 面向对象 |
| ---- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 优点 | 性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。 | 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护 |
| 缺点 | 不易维护、不易复用、不易扩展 | 性能比面向过程低 |
### ES6中的对象与类
#### 创建类
```
// 1. 创建类 class 创建一个 明星类
class 类名 {
// 类的共有属性放到 constructor 里面
constructor(name, age) {
this.name = name;
this.age = age;
}
}
// 2. 利用类创建对象 new
var 变量 = new 类名('刘德华', 18);
```
#### 类创建添加属性和方法
```
// 1. 创建类 class 创建一个类
class Star {
// 类的共有属性放到 constructor 里面 constructor是 构造器或者构造函数
constructor(uname, age) {
this.uname = uname;
this.age = age;
}//------------------------------------------->注意,方法与方法之间不需要添加逗号
sing(song) {
console.log(this.uname + '唱' + song);
}
}
// 2. 利用类创建对象 new
var ldh = new Star('刘德华', 18);
console.log(ldh); // Star {uname: "刘德华", age: 18}
ldh.sing('冰雨'); // 刘德华唱冰雨
```
注意哟:**
1. 通过class 关键字创建类, 类名我们还是习惯性定义首字母大写
2. 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
3. constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果我们不写这个函数,类也会自动生成这个函数
4. 多个函数方法之间不需要添加逗号分隔
5. 生成实例 new 不能省略
6. 语法规范, 创建类 类名后面不要加小括号,生成实例 类名后面加小括号, 构造函数不需要加function
### 类的继承
```
// 父类
class Father{
}
// 子类继承父类
class Son extends Father {
}
```
子类使用super关键字访问父类的方法
**super 必须在子类this之前调用**
```
//定义了父类
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
//子元素继承父类
class Son extends Father {
constructor(x, y) {
super(x, y); //使用super调用了父类中的构造函数
}
}
var son = new Son(1, 2);
son.sum(); //结果为3
```
时刻注意this的指向问题,类里面的共有的属性和方法一定要加this使用.
1. constructor中的this指向的是new出来的实例对象
2. 自定义的方法,一般也指向的new出来的实例对象
3. 绑定事件之后this指向的就是触发事件的事件源
在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象
### 构造函数和原型
#### 定义:
1、实例成员就是构造函数内部通过this添加的成员;
```
uname age sing 就是实例成员,实例成员只能通过实例化的对象来访问
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}
var ldh = new Star('刘德华', 18);
console.log(ldh.uname);//实例成员只能通过实例化的对象来访问
```
2、静态成员 在构造函数本身上添加的成员
```
如下列代码中 sex 就是静态成员,静态成员只能通过构造函数来访问
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}
Star.sex = '男';
var ldh = new Star('刘德华', 18);
console.log(Star.sex);//静态成员只能通过构造函数来访问
```
#### 构造函数原型(原型对象)prototype
JavaScript 规定,每一个构造函数都有一个prototype 属性,指向另一个对象。注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
我们可以把那些不变的方法,直接**定义在 prototype 对象上**,这样所有对象的实例就可以**共享这些方法。**
```
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(ldh.sing === zxy.sing)//true
ldh.sing();//我会唱歌
zxy.sing();//我会唱歌
```
#### 对象原型
```
对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象。
__proto__对象原型和原型对象 prototype 是等价的
__proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
```

#### 构造函数constructor
指回构造函数本身
```
对象原型( __proto__)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。
constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
```
如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数如:
```
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
constructor: Star, // 手动设置指回原来的构造函数
sing: function() {
console.log('我会唱歌');
},
movie: function() {
console.log('我会演电影');
}
}
var zxy = new Star('张学友', 19);
console.log(zxy)
```
#### 原型链
每一个实例对象又有一个__proto__属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有__proto__属性,这样一层一层往上找就形成了原型链。
```
当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
如果还没有就查找原型对象的原型(Object的原型对象)。
依此类推一直找到 Object 为止(null)。
__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
```

#### 构造函数实例和原型对象三角关系
```
1.构造函数的prototype属性指向了构造函数原型对象
2.实例对象是由构造函数创建的,实例对象的__proto__属性指向了构造函数的原型对象
3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数
```

#### 原型对象中this指向
```
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
var that;
Star.prototype.sing = function() {
console.log('我会唱歌');
that = this;
}
var ldh = new Star('刘德华', 18);
// 1. 在构造函数中,里面this指向的是对象实例 ldh
console.log(that === ldh);//true
// 2.原型对象函数里面的this 指向的是 实例对象 ldh
```
#### 通过原型为数组扩展内置方法
```
Array.prototype.sum = function() {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
};
//此时数组对象中已经存在sum()方法了 可以始终 数组.sum()进行数据的求
```
### 继承
#### 1、call()
- 函数名.call()---> 可以调用函数
- 函数名.call(thisAry,arg1.....)--->可以修改this的指向,使用call()的时候 参数一是修改后的this指向,参数2,参数3..使用逗号隔开连接
- thisAry:修改当前this指向为这个
- arg1:传递参数
```
function fn(x, y) {
console.log(this);
console.log(x + y);
}
var o = {
name: 'andy'
};
fn.call(o, 1, 2);//调用了函数此时的this指向了对象o,
```

#### 2、子构造函数继承父构造函数中的属性
1. 先定义一个父构造函数
2. 再定义一个子构造函数
3. 子构造函数继承父构造函数的属性(使用call方法)
```
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
3.使用call方式实现子继承父的属性
Father.call(this, uname, age);
this.score = score;
}
var son = new Son('刘德华', 18, 100);
console.log(son);
```

#### 3、借用原型对象继承方法
1. 先定义一个父构造函数
2. 再定义一个子构造函数子构造函数继承父构造函数的属性(使用call方法)
```
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
Father.prototype.money = function() {
console.log(100000);
};
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
-------------------------------------------------
// Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
-------------------------------------------------
Son.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
Son.prototype.constructor = Son;
// 这个是子构造函数专门的方法
Son.prototype.exam = function() {
console.log('孩子要考试');
}
var son = new Son('刘德华', 18, 100);
console.log(son);
```
如上代码结果如图:

### ES5新增方法
#### 数组方法
##### 1、forEach遍历数组
主要用于遍历数组,没有返回值
```js
数组.forEach(function(value, index, array) {
//参数一是:数组元素
//参数二是:数组元素的索引
//参数三是:当前的数组
})
//相当于数组遍历的 for循环 没有返回值
```
##### 2、filter过滤数组
主要用于筛选数组,返回的是一个新数组;
满足条件的元素都返回来,需要申明一个变量来接收
```js
var arr = [12, 66, 4, 88, 3, 7];
var newArr = arr.filter(function(value, index,array) {
//参数一是:数组元素
//参数二是:数组元素的索引
//参数三是:当前的数组
return value >= 20;
});
console.log(newArr);//[66,88] //返回值是一个新数组
```
##### 3、some
查找数组中是否有满足条件的元素
返回值是布尔值,只要查找到满足条件的一个元素就立马终止循环
```
some 查找数组中是否有满足条件的元素
var arr = [10, 30, 4];
var flag = arr.some(function(value,index,array) {
//参数一是:数组元素
//参数二是:数组元素的索引
//参数三是:当前的数组
return value < 3;
});
console.log(flag);//false返回值是布尔值,只要查找到满足条件的一个元素就立马终止循环
```
##### 4、map
对数组的每一项做同样的处理
map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
```
var numbers = [4, 9, 16, 25];
function myFunction() {
x = document.getElementById("demo")
x.innerHTML = numbers.map(Math.sqrt);
}
//2,3,4,5
```
##### 扩展:
```
return true;
1、在forEach中不会终止遍历
2、在some中会终止遍历,效率更高
```
#### 字符串方法trim
返回的是新字符串,需要用一个变量接收
```
var str = ' hello '
console.log(str.trim()) //hello 去除两端空格
var str1 = ' he l l o '
console.log(str.trim()) //he l l o 去除两端空格
```
#### 对象方法
##### 1、获取对象的属性名
Object.keys(对象) //获取到当前对象中的属性名 ,返回值是一个**数组**
```
var obj = {
id: 1,
pname: '小米',
price: 1999,
num: 2000
};
var result = Object.keys(obj)
console.log(result)//[id,pname,price,num]
```
##### 2、设置或修改对象中的属性
**三个参数必须写**
```js
Object.defineProperty(对象,修改或新增的属性名,{
value:修改或新增的属性的值,
writable:true/false,//如果值为false 不允许修改这个属性值
enumerable: false,//enumerable 如果值为false 则不允许遍历
configurable: false //configurable 如果为false 则不允许删除这个属性 属性是否可以被删除或是否可以再次修改特性
})
```
### 函数的定义和调用
#### 1、定义
1. 方式1: 函数声明方式 function 关键字 (命名函数)
```js
function fn(){}
```
2. 方式2: 函数表达式(匿名函数)
```js
var fn = function(){}
```
3. 方式3 :new Function()
```js
var f = new Function('a', 'b', 'console.log(a + b)');
f(1, 2);
var fn = new Function('参数1','参数2'..., '函数体')
注意
/*Function 里面参数都必须是字符串格式
第三种方式执行效率低,也不方便书写,因此较少使用
所有函数都是 Function 的实例(对象)
函数也属于对象
*/
```
#### 2、函数的调用
```js
/* 1. 普通函数 */
function fn() {
console.log('人生的巅峰');
}
fn();
/* 2. 对象的方法 */
var o = {
sayHi: function() {
console.log('人生的巅峰');
}
}
o.sayHi();
/* 3. 构造函数*/
function Star() {};
new Star();
/* 4. 绑定事件函数*/
btn.onclick = function() {}; // 点击了按钮就可以调用这个函数
/* 5. 定时器函数*/
setInterval(function() {}, 1000); 这个函数是定时器自动1秒钟调用一次
/* 6. 立即执行函数(自调用函数)*/
(function() {
console.log('人生的巅峰');
})();
```
### this指向
#### 1、函数内部的this指向
这些 this 的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this 的指向不同,一般指向我们的调用者.

#### 2、改变函数内部 this 指向
##### 2.1、call方法
call()方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向;
应用场景: 经常做继承.
```js
var o = {
name: 'andy'
}
function fn(a, b) {
console.log(this);
console.log(a+b)
};
fn(1,2)// 此时的this指向的是window 运行结果为3
fn.call(o,1,2)//此时的this指向的是对象o,参数使用逗号隔开,运行结果为3
```
以上代码运行结果为:

##### 2.2、apply方法
apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
应用场景: 经常跟数组有关系
```js
var o = {
name: 'andy'
}
function fn(a, b) {
console.log(this);
console.log(a+b)
};
fn()// 此时的this指向的是window 运行结果为3
fn.apply(o,[1,2])//此时的this指向的是对象o,参数使用数组传递 运行结果为3
```

##### 2.3 、bind方法(常用)
bind() 方法不会调用函数,但是能改变函数内部this 指向,返回的是原函数改变this之后产生的新函数,需用一个变量来接收
如果只是想改变 this 指向,并且不想调用这个函数的时候,可以使用bind
应用场景:不调用函数,但是还想改变this指向
```js
var o = {
name: 'andy'
};
function fn(a, b) {
console.log(this);
console.log(a + b);
};
var f = fn.bind(o, 1, 2); //此处的f是bind返回的新函数
f();//调用新函数 this指向的是对象o 参数使用逗号隔开
```

### 严格模式
#### 1、开启严格模式
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。
- 情况一 :为脚本开启严格模式
- 有的 script 脚本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他
script 脚本文件。
```js
(function (){
//在当前的这个自调用函数中有开启严格模式,当前函数之外还是普通模式
"use strict";
var num = 10;
function fn() {}
})();
//或者
//当前script标签开启了严格模式,第一行
```
- 情况二: 为函数开启严格模式
- 要给某个函数开启严格模式,需要把“use strict”; (或 'use strict'; ) 声明放在函数体所有语句之前。
```js
function fn(){
"use strict";
return "123";
}
//当前fn函数开启了严格模式
```
#### 2、严格模式中的变化
```js
'use strict'
num = 10
console.log(num)//严格模式后使用未声明的变量
--------------------------------------------------------------------------------
var num2 = 1;
delete num2;//严格模式不允许删除变量
--------------------------------------------------------------------------------
function fn() {
console.log(this); // 严格模式下全局作用域中函数中的 this 是 undefined
}
fn();
---------------------------------------------------------------------------------
function Star() {
this.sex = '男';
}
// Star();严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果给他赋值则 会报错.
var ldh = new Star();
console.log(ldh.sex);
----------------------------------------------------------------------------------
setTimeout(function() {
console.log(this); //严格模式下,定时器 this 还是指向 window
}, 2000);
```
### 高阶函数
1、接收函数作为参数;
2、将函数作为返回值输出。
### 闭包
闭包(closure)指有权访问另一个函数作用域中变量的函数。简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。

#### 闭包的作用
作用:延伸变量的作用范围。
```js
function fn() {
var num = 10;
function fun() {
console.log(num);
}
return fun;
}
var f = fn();
f();
```
### 递归
**递归:**如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。简单理解:函数内部自己调用自己, 这个函数就是递归函数
**注意:**递归函数的作用和循环效果一样,由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件return。
```js
//利用递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..n
function fn(n) {
if (n == 1) { //结束条件
return 1;
}
return n * fn(n - 1);
}
console.log(fn(3));
```
### 拷贝
#### 1、浅拷贝
方法一:
```
var obj1 = {...obj}
```
方法二:
Object.assign(target , sources)
- target: 拷贝给谁
- sources: 拷贝谁
```
var obj1 = Object.assign({}, obj)
```
#### 2、深拷贝
方法一(推荐):
利用递归函数来深拷贝。//需要判断值为什么类型
```
function deepClone(obj) {
var obj1 = Array.isArray(obj) ? [] : {}
for(var k in obj) {
if(typeof obj[k] == 'object') {
obj1[k] = deepClone(obj[k])
} else {
obj1[k] = obj[k]
}
}
}
```
方法二:
json
### 正则表达式
正则表达式( Regular Expression )是用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象。
#### 1、正则表达式在js中的使用
##### 正则表达式的创建
在 JavaScript 中,可以通过两种方式创建一个正则表达式。
方式一:通过调用RegExp对象的构造函数创建
```js
var regexp = new RegExp(/123/);
console.log(regexp);
```
方式二:利用字面量创建 正则表达式(常用)
```js
var rg = /123/;
```
##### 测试正则表达式
test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串。
```js
var rg = /123/;
console.log(rg.test(123));//匹配字符中是否出现123 出现结果为true
console.log(rg.test('abc'));//匹配字符中是否出现123 未出现结果为false
```

#### 2、正则表达式中的特殊字符
##### 2.1、边界符
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符
| 边界符 | 说明 |
| ------ | ------------------------------ |
| ^ | 表示匹配行首的文本(以谁开始) |
| $ | 表示匹配行尾的文本(以谁结束) |
如果 ^和 $ 在一起,表示必须是精确匹配。
```js
var rg = /abc/; // 正则表达式里面不需要加引号 不管是数字型还是字符串型
// /abc/ 只要包含有abc这个字符串返回的都是true
console.log(rg.test('abc'));
console.log(rg.test('abcd'));
console.log(rg.test('aabcd'));
console.log('---------------------------');
var reg = /^abc/;
console.log(reg.test('abc')); // true
console.log(reg.test('abcd')); // true
console.log(reg.test('aabcd')); // false
console.log('---------------------------');
var reg1 = /^abc$/; // 精确匹配 要求必须是 abc字符串才符合规范
console.log(reg1.test('abc')); // true
console.log(reg1.test('abcd')); // false
console.log(reg1.test('aabcd')); // false
console.log(reg1.test('abcabc')); // false
```
##### 2.2、字符类
字符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。
表示有一系列字符可供选择,只要匹配其中一个就可以了
```js
var rg = /[abc]/; // 只要包含有a 或者 包含有b 或者包含有c 都返回为true
console.log(rg.test('andy'));//true
console.log(rg.test('baby'));//true
console.log(rg.test('color'));//true
console.log(rg.test('red'));//false
var rg1 = /^[abc]$/; // 三选一 只有是a 或者是 b 或者是c 这三个字母才返回 true
console.log(rg1.test('aa'));//false
console.log(rg1.test('a'));//true
console.log(rg1.test('b'));//true
console.log(rg1.test('c'));//true
console.log(rg1.test('abc'));//true
----------------------------------------------------------------------------------
var reg = /^[a-z]$/ //26个英文字母任何一个字母返回 true - 表示的是a 到z 的范围
console.log(reg.test('a'));//true
console.log(reg.test('z'));//true
console.log(reg.test('A'));//false
-----------------------------------------------------------------------------------
//字符组合
var reg1 = /^[a-zA-Z0-9]$/; // 26个英文字母(大写和小写都可以)任何一个字母返回 true
------------------------------------------------------------------------------------
//取反 方括号内部加上 ^ 表示取反,只要包含方括号内的字符,都返回 false 。
var reg2 = /^[^a-zA-Z0-9]$/;
console.log(reg2.test('a'));//false
console.log(reg2.test('B'));//false
console.log(reg2.test(8));//false
console.log(reg2.test('!'));//true
```
##### 2.3、量词符
量词符用来设定某个模式出现的次数。
| 量词 | 说明 |
| ----- | --------------- |
| * | 重复0次或更多次 |
| + | 重复1次或更多次 |
| ? | 重复0次或1次 |
| {n} | 重复n次 |
| {n,} | 重复n次或更多次 |
| {n,m} | 重复n到m次 |
##### 2.4、预定义类
预定义类指的是某些常见模式的简写方式.

#### 括号总结
1.大括号 量词符. 里面表示重复次数
2.中括号 字符集合。匹配方括号中的任意字符.
3.小括号表示优先级
#### 正则替换replace
replace() 方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。
()里的正则: /表达式/[swith]
1、g: 全局匹配
2、i:忽略大小写
3、gi:全局匹配,忽略大小写
```js
var str = 'andy和red';
var newStr = str.replace('andy', 'baby');
console.log(newStr)//baby和red
//等同于 此处的andy可以写在正则表达式内
var newStr2 = str.replace(/andy/, 'baby');
console.log(newStr2)//baby和red
//全部替换
var str = 'abcabc'
var nStr = str.replace(/a/,'哈哈')
console.log(nStr) //哈哈bcabc
//全部替换g
var nStr = str.replace(/a/a,'哈哈')
console.log(nStr) //哈哈bc哈哈bc
//忽略大小写i
var str = 'aAbcAba';
var newStr = str.replace(/a/gi,'哈哈')//"哈哈哈哈bc哈哈b哈哈"
```
**案例:过滤敏感词汇**
```js
```
### ES6语法
#### let
##### 块级作用域
et声明的变量只在所处于的块级有效
```
if (true) {
let a = 10;
}
console.log(a) // a is not defined
```
**注意:**使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性。
##### 不存在变量提升
```
console.log(a); // a is not defined
let a = 20;
```
##### 暂时性死区
利用let声明的变量会绑定在这个块级作用域,不会受外界的影响
```javascript
var tmp = 123;
if (true) {
tmp = 'abc';
let tmp;
}
```
##### 经典面试题
```javascript
var arr = [];
for (var i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[0]();
arr[1]();
```

**经典面试题图解:**此题的关键点在于变量i是全局的,函数执行时输出的都是全局作用域下的i值。
```javascript
let arr = [];
for (let i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[0]();
arr[1]();
```

**经典面试题图解:**此题的关键点在于每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的i值.
##### 小结
- let关键字就是用来声明变量的
- 使用let关键字声明的变量具有块级作用域
- 在一个大括号中 使用let关键字声明的变量才具有块级作用域 var关键字是不具备这个特点的
- 防止循环变量变成全局变量
- 使用let关键字声明的变量没有变量提升
- 使用let关键字声明的变量具有暂时性死区特性
#### const
声明常量,常量就是值(内存地址)不能变化的量
##### 具有块级作用域
```javascript
if (true) {
const a = 10;
}
console.log(a) // a is not defined
```
##### 声明常量时必须赋值
```javascript
const PI; // Missing initializer in const declaration
```
##### 常量赋值后,值不能修改
```javascript
const PI = 3.14;
PI = 100; // Assignment to constant variable.
const ary = [100, 200];
ary[0] = 'a';
ary[1] = 'b';
console.log(ary); // ['a', 'b'];
ary = ['a', 'b']; // Assignment to constant variable.
```
##### 小结
- const声明的变量是一个常量
- 既然是常量不能重新进行赋值,如果是基本数据类型,不能更改值,如果是复杂数据类型,不能更改地址值
- 声明 const时候必须要给定值
#### let、const、var 的区别
- 使用 var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象
- 使用 let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升
- 使用 const 声明的是常量,在后面出现的代码中不能再修改该常量的值

#### 解构赋值
##### 数组解构
```javascript
let [a, b, c] = [1, 2, 3];
console.log(a)//1
console.log(b)//2
console.log(c)//3
//如果解构不成功,变量的值为undefined
```
##### 对象解构
```javascript
let person = { name: 'zhangsan', age: 20 };
let { name, age } = person;
console.log(name); // 'zhangsan'
console.log(age); // 20
let {name: myName, age: myAge} = person; // myName myAge 属于别名
console.log(myName); // 'zhangsan'
console.log(myAge); // 20
```
##### 小结
- 解构赋值就是把数据结构分解,然后给变量进行赋值
- 如果结构不成功,变量跟数值个数不匹配的时候,变量的值为undefined
- 数组解构用中括号包裹,多个变量用逗号隔开,对象解构用花括号包裹,多个变量用逗号隔开
- 利用解构赋值能够让我们方便的去取对象中的属性跟方法
#### 箭头函数
ES6中新增的定义函数的方式。
```javascript
() => {} //():代表是函数; =>:必须要的符号,指向哪一个代码块;{}:函数体
const fn = () => {}//代表把一个函数赋值给fn
```
1、函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号
```javascript
function sum(num1, num2) {
return num1 + num2;
}
//es6写法
const sum = (num1, num2) => num1 + num2;
```
2、如果形参只有一个,可以省略小括号
```javascript
function fn (v) {
return v;
}
//es6写法
const fn = v => v;
```
3、箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this
```javascript
const obj = { name: '张三'}
function fn () {
console.log(this);//this 指向 是obj对象
return () => {
console.log(this);//this 指向 的是箭头函数定义的位置,那么这个箭头函数定义在fn里面,而这个fn指向是的obj对象,所以这个this也指向是obj对象
}
}
const resFn = fn.call(obj);
resFn();
```
##### 小结
- 箭头函数中不绑定this,箭头函数中的this指向是它所定义的位置,可以简单理解成,定义箭头函数中的作用域的this指向谁,它就指向谁
- 箭头函数的优点在于解决了this执行环境所造成的一些问题。比如:解决了匿名函数this指向的问题(匿名函数的执行环境具有全局性),包括setTimeout和setInterval中使用this所造成的问题
##### 面试题
```javascript
var age = 100;
var obj = {
age: 20,
say: () => {
alert(this.age)
}
}
obj.say();//箭头函数this指向的是被声明的作用域里面,而对象没有作用域的,所以箭头函数虽然在对象中被定义,但是this指向的是全局作用域
```
#### 剩余参数
剩余参数语法允许我们将一个不定数量的参数表示为一个数组,不定参数定义方式,这种方式很方便的去声明不知道参数情况下的一个函数
```javascript
function sum (first, ...args) {
console.log(first); // 10
console.log(args); // [20, 30]
}
sum(10, 20, 30)
```
##### 剩余参数和解构配合使用
```javascript
let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu'
console.log(s2); // ['zhangsan', 'lisi']
```
#### ES6 的内置对象扩展
##### Array 的扩展方法
###### 1、扩展运算符(展开语法)
可以将数组或者对象转为用逗号分隔的参数序列
```js
let ary = [1, 2, 3];
...ary // 1, 2, 3
console.log(...ary); // 1 2 3,相当于下面的代码
console.log(1,2,3);
obj = {a: 2,b:3}
{...obj} //{a: 2,b:3}
{...obj, b: 4}//{a: 2,b:4}
```
扩展运算符可以应用于合并数组
```js
// 方法一
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2];
// 方法二
ary1.push(...ary2);
```
将类数组或可遍历对象转换为真正的数组
```javascript
let oDivs = document.getElementsByTagName('div');
oDivs = [...oDivs];
```
###### 2、构造函数方法:Array.from()
将伪数组或可遍历对象转换为真正的数组
```javascript
//定义一个集合
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
//转成数组
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
```
方法还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组
```javascript
let arrayLike = {
"0": 1,
"1": 2,
"length": 2
}
let newAry = Array.from(arrayLike, item => item *2)//[2,4]
```
注意:如果是对象,那么属性需要写对应的索引
###### 实例方法:find()
用于找出**第一个符合条件的数组成员**,如果没有找到返回undefined
```javascript
let ary = [{
id: 1,
name: '张三'
}, {
id: 2,
name: '李四'
}];
let target = ary.find((item, index) => item.id == 2);//找数组里面符合条件的值,当数组中元素id等于2的查找出来,注意,只会匹配第一个
```
###### 实例方法:findIndex()
用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1
```javascript
let ary = [1, 5, 10, 15];
let index = ary.findIndex((value, index) => value > 9);
console.log(index); // 2
```
###### 实例方法:includes()
判断某个数组是否包含给定的值,返回布尔值。
```javascript
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
```
##### String 的扩展方法
###### 模板字符串
ES6新增的创建字符串的方式,使用反引号定义
```javascript
let name = `zhangsan`;
```
模板字符串中可以解析变量
```javascript
let name = '张三';
let sayHello = `hello,my name is ${name}`; // hello, my name is zhangsan
```
模板字符串中可以换行
```javascript
let result = {
name: 'zhangsan',
age: 20,
sex: '男'
}
let html = `
${result.name}
${result.age}
${result.sex}
`;
```
在模板字符串中可以调用函数
```javascript
const sayHello = function () {
return '哈哈哈哈 追不到我吧 我就是这么强大';
};
let greet = `${sayHello()} 哈哈哈哈`;
console.log(greet); // 哈哈哈哈 追不到我吧 我就是这么强大 哈哈哈哈
```
###### 实例方法:startsWith() 和 endsWith()
- startsWith():表示参数字符串是否在原字符串的头部,返回布尔值
- endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值
```javascript
let str = 'Hello world!';
str.startsWith('Hello') // true
str.endsWith('!') // true
```
###### 实例方法:repeat()
repeat方法表示将原字符串重复n次,返回一个新字符串
```javascript
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
```
##### Set 数据结构
ES6 提供了新的数据结构 Set。它类似于数组,但是**成员的值都是唯一的,没有重复的值**。
Set本身是一个构造函数,用来生成 Set 数据结构
```javascript
const s = new Set();
```
Set函数可以接受一个数组作为参数,用来初始化。
```javascript
const set = new Set([1, 2, 3, 4, 4]);//{1, 2, 3, 4}
```
###### 实例方法
- add(value):添加某个值,返回 Set 结构本身
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功
- has(value):返回一个布尔值,表示该值是否为 Set 的成员
- clear():清除所有成员,没有返回值
```javascript
const s = new Set();
s.add(1).add(2).add(3); // 向 set 结构中添加值
s.delete(2) // 删除 set 结构中的2值
s.has(1) // 表示 set 结构中是否有1这个值 返回布尔值
s.clear() // 清除 set 结构中的所有值
//注意:删除的是元素的值,不是代表的索引
```
###### 遍历
Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
```javascript
s.forEach(value => console.log(value))
```
------
# TypeScript
------
1、TS代码需要通过编译器编译为JS,然后再交由JS解析器执行。
2、TS完全兼容JS,换言之,任何的JS代码都可以直接当成JS使用。
3、**`TypeScript` 简称:TS,是 JavaScript 的超集**,简单来说就是:JS 有的 TS 都有
4、TypeScript 是*微软*开发的开源编程语言,可以在任何运行 JavaScript 的地方运行
## TypeScript 相比 JS 的优势
1. **更早(写代码的同时)发现错误**,减少找 Bug、改 Bug 时间,提升开发效率
2. 程序中任何位置的代码都有**代码提示**,随时随地的安全感,增强了开发体验
3. 强大的类型系统提升了代码的可维护性,使得**重构代码更加容易**
4. 支持最新的 ECMAScript 语法,**优先体验最新的语法,让你走在前端技术的最前沿**
5. TS 类型推断机制,不需要在代码中的每个地方都显示标注类型,让你在享受优势的同时,尽量降低了学习负担
除此之外,Vue 3 源码使用 TS 重写、Angular 默认支持 TS、React 与 TS 完美配合,TypeScript 已成为大中型前端 项目的首选编程语言
## 开发环境搭建
1. 下载Node.js
- 64位:https://nodejs.org/dist/v14.15.1/node-v14.15.1-x64.msi
- 32位:https://nodejs.org/dist/v14.15.1/node-v14.15.1-x86.msi
2. 安装Node.js
3. 使用npm全局安装typescript
- 进入命令行
- 输入:npm i -g typescript
4. 创建一个ts文件
5. 使用tsc对ts文件进行编译
- 进入命令行
- 进入ts文件所在目录
- 执行命令:tsc xxx.ts
优化:使用 `ts-node` 包,直接在 Node.js 中执行 TS 代码
- 安装命令:`npm i -g ts-node`
- 使用方式:`ts-node hello.ts` 相当于:1 tsc 命令 2 node(注意:ts-node 不会生成 js 文件)
- 解释:ts-node 命令在内部偷偷的将 TS -> JS,然后,再运行 JS 代码
- npm i @types/node -g
## 基本类型
### 类型声明
- 类型声明是TS非常重要的一个特点
- 通过类型声明可以指定TS中变量(参数、形参)的类型
- 指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,**符合则赋值,否则报错**
- 简而言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值
- 语法:
- ```typescript
let 变量: 类型;
let 变量: 类型 = 值;
第三个类型指的是返回值的类型
function fn(参数: 类型, 参数: 类型): 类型{
...
}
```
### 自动类型判断
- TS拥有自动的类型判断机制
- **当对变量的声明和赋值是同时进行的**,**TS编译器会自动判断变量的类型**
- **所以如果你的变量的声明和赋值时同时进行的,可以省略掉类型声明**
### 类型:
| 类型 | 例子 | 描述 |
| :-------------------------: | :---------------: | :----------------------------: |
| number | 1, -33, 2.5 | 任意数字 |
| string | 'hi', "hi", `hi` | 任意字符串 |
| boolean | true、false | 布尔值true或false |
| 字面量 | 其本身 | 限制变量的值就是该字面量的值 |
| any(声明不赋值默认,不推荐) | * | 任意类型 |
| unknown | * | 类型安全的any |
| void | 空值(undefined) | 没有值(或undefined) |
| never | 没有值 | 不能是任何值 |
| object | {name:'孙悟空'} | 任意的JS对象 |
| array | [1,2,3] | 任意JS数组 |
| tuple | [4,5] | 元素,TS新增类型,固定长度数组 |
| enum | enum{A, B} | 枚举,TS中新增类型 |
#### 原始类型
- 原始类型:number/string/boolean/null/undefined/symbol
- 特点:简单,这些类型,完全按照 JS 中类型的名称来书写
```ts
let age: number = 18
let myName: string = '老师'
let isLoading: boolean = false
// 等等...
```
#### 数组类型Array
- 数组类型的两种写法:
- 推荐使用 `number[]` 写法
```ts
// 写法一:
let numbers: number[] = [1, 3, 5]
// 写法二:泛型
let strings: Array = ['a', 'b', 'c']
```
#### 联合类型|
需求:数组中既有 number 类型,又有 string 类型,这个数组的类型应该如何写?
```ts
let arr: (number | string)[] = [1, 'a', 3, 'b']
```
- 解释:`|`(竖线)在 TS 中叫做**联合类型**,即:由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种
- 注意:这是 TS 中联合类型的语法,只有一根竖线,不要与 JS 中的或(|| 或)混淆了
#### 类型别名type
- `类型别名(自定义类型)`:为任意类型起别名
- 使用场景:当同一类型(复杂)被多次使用时,可以通过类型别名,**简化该类型的使用**
```ts
type CustomArray = (number | string)[]
let arr1: CustomArray = [1, 'a', 3, 'b']
let arr2: CustomArray = ['x', 'y', 6, 7]
```
- 解释:
1. 使用 `type` 关键字来创建自定义类型
2. 类型别名(比如,此处的 *CustomArray*)可以是任意合法的变量名称
3. 推荐使用大写字母开头
4. 创建类型别名后,直接使用该类型别名作为变量的类型注解即可
#### 函数类型(包含void)
- 函数的类型实际上指的是:`函数参数`和`返回值`的类型
- 为函数指定类型的两种方式:
1. 单独指定参数、返回值的类型
2. 同时指定参数、返回值的类型
1. 单独指定参数、返回值的类型:
```ts
// 函数声明
function add(num1: number, num2: number): number {
return num1 + num2
}
// 箭头函数
const add = (num1: number, num2: number): number => {
return num1 + num2
}
```
2. 同时指定参数、返回值的类型:
```ts
type AddFn = (num1: number, num2: number) => number
const add: AddFn = (num1, num2) => {
return num1 + num2
}
```
- 解释:当函数作为表达式时,可以通过类似箭头函数形式的语法来为函数添加类型
- 注意:这种形式只适用于函数表达式
##### void 类型
- 用来表示空
- 如果函数没有返回值,那么,函数返回值类型为:`void`
```ts
function greet(name: string): void {
console.log('Hello', name)
}
```
- 注意:
- 如果一个函数没有返回值,此时,在 TS 的类型中,应该使用 `void` 类型
```ts
// 如果什么都不写,此时,add 函数的返回值类型为: void
const add = () => {}
// 这种写法是明确指定函数返回值类型为 void,与上面不指定返回值类型相同
const add = (): void => {}
// 但,如果指定 返回值类型为 undefined,此时,函数体中必须显示的 return undefined 才可以
const add = (): undefined => {
// 此处,返回的 undefined 是 JS 中的一个值
return undefined
}
```
##### 函数可选参数
- 使用函数实现某个功能时,参数可以传也可以不传。这种情况下,在给函数参数指定类型时,就用到**可选参数**了
- 比如,数组的 slice 方法,可以 `slice()` 也可以 `slice(1)` 还可以 `slice(1, 3)`
```ts
function mySlice(start?: number, end?: number): void {
console.log('起始索引:', start, '结束索引:', end)
}
```
- 可选参数:在可传可不传的参数名称后面添加 `?`(问号)
- 注意:**可选参数只能出现在参数列表的最后**,也就是说可选参数后面不能再出现必选参数
##### 函数默认参数
```
function mySlice(start: number = 1, end: number = 2): void {
console.log('起始索引:', start, '结束索引:', end) //起始索引 1, 结束索引:2
}
mySlice() //不传的话会走默认值1和2
```
#### 对象类型
- JS 中的对象是由属性和方法构成的,而 **TS 对象的类型就是在描述对象的结构**(有什么类型的属性和方法)
- 对象类型的写法:
```ts
// 空对象
let person: {} = {}
// 有属性的对象
let person: { name: string } = {
name: '同学'
}
// 既有属性又有方法的对象
// 在一行代码中指定对象的多个属性类型时,使用 `;`(分号)来分隔
let person: { name: string; sayHi(): void } = {
name: 'jack',
sayHi() {}
}
// 对象中如果有多个类型,可以换行写:
// 通过换行来分隔多个属性类型,可以去掉 `;`
let person: {
name: string
sayHi(): void
} = {
name: 'jack',
sayHi() {}
}
```
- 解释:
1. 使用 `{}` 来描述对象结构
2. 属性采用`属性名: 类型`的形式
3. 方法采用`方法名(): 返回值类型`的形式
##### 使用类型别名
- 注意:直接使用 `{}` 形式为对象添加类型,会降低代码的可读性(不好辨识类型和值)
- 推荐:**使用类型别名为对象添加类型**
```ts
// 创建类型别名
type Person = {
name: string
sayHi(): void
}
// 使用类型别名作为对象的类型:
let person: Person = {
name: 'jack',
sayHi() {}
}
```
##### 带有参数的方法类型
- 如果方法有参数,就在方法名后面的小括号中指定参数类型
```ts
type Person = {
greet(name: string): void
}
let person: Person = {
greet(name) {
console.log(name)
}
}
```
##### 箭头函数形式的方法类型
- 方法的类型也可以使用箭头函数形式
```ts
type Person = {
greet: (name: string) => void
}
let person: Person = {
greet(name) {
console.log(name)
}
}
```
##### 对象可选属性
- 对象的属性或方法,也可以是可选的,此时就用到**可选属性**了
- 比如,我们在使用 `axios({ ... })` 时,如果发送 GET 请求,method 属性就可以省略
- 可选属性的语法与函数可选参数的语法一致,都使用 `?` 来表示
```ts
type Config = {
url: string
method?: string
}
function myAxios(config: Config) {
console.log(config)
}
```
#### 接口interface
当一个对象类型被多次使用时,一般会使用接口(`interface`)来描述对象的类型,达到复用的目的
- 解释:
1. 使用 `interface` 关键字来声明接口
2. 接口名称(比如,此处的 IPerson),可以是任意合法的变量名称,推荐以 `I` 开头
3. 声明接口后,直接使用接口名称作为变量的类型
4. 因为每一行只有一个属性类型,因此,属性类型后没有 ;(分号)
```ts
interface IPerson {
name: string
age: number
sayHi(): void
}
let person: IPerson = {
name: 'jack',
age: 19,
sayHi() {}
}
```
##### interface vs type
- interface(接口)和 type(类型别名)的对比:
- 相同点:都可以给对象指定类型
- 不同点:
- 接口,只能为对象指定类型
- 类型别名,不仅可以为对象指定类型,实际上可以为任意类型指定别名
- 推荐:**能使用 type 就是用 type**
```ts
interface IPerson {
name: string
age: number
sayHi(): void
}
// 为对象类型创建类型别名
type IPerson = {
name: string
age: number
sayHi(): void
}
// 为联合类型创建类型别名
type NumStr = number | string
```
##### 接口继承
- 如果两个接口之间有相同的属性或方法,可以将**公共的属性或方法抽离出来,通过继承来实现复用**
- 比如,这两个接口都有 x、y 两个属性,重复写两次,可以,但很繁琐
```ts
interface Point2D { x: number; y: number }
interface Point3D { x: number; y: number; z: number }
```
- 更好的方式:
```ts
interface Point2D { x: number; y: number }
// 继承 Point2D
interface Point3D extends Point2D {
z: number
}
```
- 解释:
1. 使用 `extends`(继承)关键字实现了接口 Point3D 继承 Point2D
2. 继承后,Point3D 就有了 Point2D 的所有属性和方法(此时,Point3D 同时有 x、y、z 三个属性)
#### 元组Tuple
- 场景:在地图中,使用经纬度坐标来标记位置信息
- 可以使用数组来记录坐标,那么,该数组中只有两个元素,并且这两个元素都是数值类型 number[]
```ts
let position: number[] = [116.2317, 39.5427]
```
- 使用 number[] 的缺点:不严谨,因为该类型的数组中可以出现任意多个数字
- 更好的方式:`元组 Tuple`
- 元组类型是另一种类型的数组,它确切地知道包含多少个元素,**以及特定索引对应的类型**
```ts
let position: [number, number] = [39.5427, 116.2317]
```
- 解释:
1. 元组类型可以确切地标记出有多少个元素,以及每个元素的类型
2. 该示例中,元素有两个元素,每个元素的类型都是 number
#### 字面量:
- 以使用 | 来连接多个类型(联合类型)代表只能在这几个中取值
- 也可以使用字面量去指定变量的类型,通过字面量可以确定变量的取值范围
- ```typescript
let color: 'red' | 'blue' | 'black';
let num: 1 | 2 | 3 | 4 | 5;
```
#### 枚举enum
- ```typescript
enum Gender{
Male,
Female
}
let i: {name: string, gender: Gender};
i = {
name: '孙悟空',
gender: Gender.Male // 'male'
}
// console.log(i.gender === Gender.Male);// true
// &表示同时
let j: { name: string } & { age: number };
// j = {name: '孙悟空', age: 18};
// 类型的别名
type myType = 1 | 2 | 3 | 4 | 5;
let k: myType;
let l: myType;
let m: myType;
k = 2;
```
- 枚举的功能类似于**字面量类型+联合类型组合**的功能,也可以表示一组明确的可选值
- 枚举:定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个
```ts
// 创建枚举
enum Direction { Up, Down, Left, Right }
// 使用枚举类型
function changeDirection(direction: Direction) {
console.log(direction)
}
// 调用函数时,需要应该传入:枚举 Direction 成员的任意一个
// 类似于 JS 中的对象,直接通过 点(.)语法 访问枚举的成员
changeDirection(Direction.Up)
```
- 解释:
1. 使用 `enum` 关键字定义枚举
2. 约定枚举名称以大写字母开头
3. 枚举中的多个值之间通过 `,`(逗号)分隔
4. 定义好枚举后,直接使用枚举名称作为类型注解
##### 数字枚举
- 问题:我们把枚举成员作为了函数的实参,它的值是什么呢?
- 解释:通过将鼠标移入 Direction.Up,可以看到枚举成员 Up 的值为 0
- 注意:枚举成员是有值的,默认为:从 0 开始自增的数值
- 我们把,枚举成员的值为数字的枚举,称为:`数字枚举`
- 当然,也可以给枚举中的成员初始化值
```ts
// Down -> 11、Left -> 12、Right -> 13
enum Direction { Up = 10, Down, Left, Right }
enum Direction { Up = 2, Down = 4, Left = 8, Right = 16 }
```
##### 字符串枚举
- 字符串枚举:枚举成员的值是字符串
- 注意:字符串枚举没有自增长行为,因此,**字符串枚举的每个成员必须有初始值**
```ts
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
```
##### 枚举实现原理
- 枚举是 TS 为数不多的非 JavaScript 类型级扩展(不仅仅是类型)的特性之一
- 因为:其他类型仅仅被当做类型,而枚举不仅用作类型,还提供值(枚举成员都是有值的)
- 也就是说,其他的类型会在编译为 JS 代码时自动移除。但是,**枚举类型会被编译为 JS 代码**
```ts
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
// 会被编译为以下 JS 代码:
var Direction;
(function (Direction) {
Direction['Up'] = 'UP'
Direction['Down'] = 'DOWN'
Direction['Left'] = 'LEFT'
Direction['Right'] = 'RIGHT'
})(Direction || Direction = {})
```
- 说明:枚举与前面讲到的字面量类型+联合类型组合的功能类似,都用来表示一组明确的可选值列表
- 一般情况下,**推荐使用字面量类型+联合类型组合的方式**,因为相比枚举,这种方式更加直观、简洁、高效
#### any 类型
- **原则:不推荐使用 any**!这会让 TypeScript 变为 “AnyScript”(失去 TS 类型保护的优势)
- 因为当值的类型为 any 时,可以对该值进行任意操作,并且不会有代码提示
```ts
let obj: any = { x: 0 }
obj.bar = 100
obj()
const n: number = obj
```
- 解释:以上操作都不会有任何类型错误提示,即使可能存在错误
- 尽可能的避免使用 any 类型,除非临时使用 any 来“避免”书写很长、很复杂的类型
- 其他隐式具有 any 类型的情况
1. 声明变量不提供类型也不提供默认值
2. 函数参数不加类型
- 注意:因为不推荐使用 any,所以,这两种情况下都应该提供类型
在项目开发中,尽量少用any类型
#### 类型断言
- 有些情况下,变量的类型对于我们来说是很明确,但是TS编译器却并不清楚,此时,可以通过类型断言来告诉编译器变量的类型,断言有两种形式:
- ```
// 类型断言,可以用来告诉解析器变量的实际类型
/*
* 语法:
* 变量 as 类型
* <类型>变量
*
* */
s = e as string;
s = e;
```
- 第一种
- ```typescript
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;
```
- 第二种
- ```typescript
let someValue: unknown = "this is a string";
let strLength: number = (someValue).length;
```
#### typeof
- 众所周知,JS 中提供了 typeof 操作符,用来在 JS 中获取数据的类型
```js
console.log(typeof 'Hello world') // ?
```
- 实际上,TS 也提供了 typeof 操作符:可以在*类型上下文*中引用变量或属性的类型(类型查询)
- 使用场景:根据已有变量的值,获取该值的类型,来简化类型书写
```ts
let p = { x: 1, y: 2 }
function formatPoint(point: { x: number; y: number }) {}
formatPoint(p)
function formatPoint(point: typeof p) {}
```
- 解释:
1. 使用 `typeof` 操作符来获取变量 p 的类型,结果与第一种(对象字面量形式的类型)相同
2. typeof 出现在**类型注解的位置(参数名称的冒号后面)所处的环境就在类型上下文**(区别于 JS 代码)
3. 注意:typeof 只能用来查询变量或属性的类型,无法查询其他形式的类型(比如,函数调用的类型)
## 编译选项
### 自动编译文件
- 编译文件时,使用 -w 指令后,TS编译器会自动监视文件的变化,并在文件发生变化时对文件进行重新编译。
- 示例:
- ```powershell
tsc xxx.ts -w
```
### 自动编译整个项目(重点)
如果直接使用tsc指令,则可以自动将当前项目下的所有ts文件编译为js文件。
但是能直接使用tsc命令的前提时,要先在项目根目录下创建一个ts的配置文件 tsconfig.json
tsconfig.json是一个JSON文件,添加配置文件后,只需 tsc 命令即可完成对整个项目的编译
- ```
tsc -w 监听全部文件
```
#### 配置选项:
##### include
- 定义:*用来指定哪些ts文件需要被编译*
- 默认值:["\*\*/\*"]
- ```
路径:** 表示任意目录
* 表示任意文件
```
- 示例:
- ```json
"include":["src/**/*", "tests/**/*"]
```
- 上述示例中,所有src目录和tests目录下的文件都会被编译
##### exclude
- 定义:*不需要被编译的文件目录*
- 默认值:["node_modules", "bower_components", "jspm_packages"]
- 示例:
- ```json
"exclude": ["./src/hello/**/*"]
```
- 上述示例中,src下hello目录下的文件都不会被编译
##### extends
- 定义被继承的配置文件
- 示例:
- ```json
"extends": "./configs/base"
```
- 上述示例中,当前配置文件中会自动包含config目录下base.json中的所有配置信息
##### files(不常用)
- 指定被编译文件的列表,只有需要编译的文件少时才会用到
- 示例:
- ```json
"files": [
"core.ts",
"sys.ts",
"types.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
"binder.ts",
"checker.ts",
"tsc.ts"
]
```
- 列表中的文件都会被TS编译器所编译
##### compilerOptions(重点)
编译选项是配置文件中非常重要也比较复杂的配置选项
在compilerOptions中包含多个子选项,用来完成对编译的配置
项目选项
###### target
- 设置ts代码编译的目标版本
- 可选值:
- ES3(默认)、ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext(最新版本的)
- 示例:
- ```json
"compilerOptions": {
"target": "ES6"
}
```
- 如上设置,我们所编写的ts代码将会被编译为ES6版本的js代码
###### lib(一般情况下不需要设置)
- 指定代码运行时所包含的库(宿主环境)
- 可选值:
- ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext、DOM、WebWorker、ScriptHost ......
- 示例:
- ```json
"compilerOptions": {
"target": "ES6",
"lib": ["ES6", "DOM"],
"outDir": "dist",
"outFile": "dist/aa.js"
}
```
###### module
- 设置编译后代码使用的模块化系统
- 可选值:
- *'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'esnext'*
- 示例:
- ```typescript
"compilerOptions": {
"module": "es2015"
}
```
###### outDir
- 编译后文件的所在目录
- 默认情况下,编译后的js文件会和ts文件位于相同的目录,设置outDir后可以改变编译后文件的位置
- 示例:
- ```json
"compilerOptions": {
"outDir": "./dist"
}
```
- 设置后编译后的js文件将会生成到dist目录
###### outFile(了解)
- 将所有的文件编译为一个js文件(合并)
- 默认会将所有的编写在全局作用域中的代码合并为一个js文件,如果module制定了None、System或AMD则会将模块一起合并到文件之中
- 示例:
- ```json
"compilerOptions": {
"outFile": "./dist/app.js"
}
```
###### rootDir
- 指定代码的根目录,默认情况下编译后文件的目录结构会以最长的公共目录为根目录,通过rootDir可以手动指定根目录
- 示例:
- ```json
"compilerOptions": {
"rootDir": "./src"
}
```
###### allowJs
- *是否对js文件进行编译,默认是false*
###### checkJs
- 是否对js文件进行检查*是否符合语法规范*,默认是false
- 示例:
- ```json
"compilerOptions": {
"allowJs": true,
"checkJs": true
}
```
###### removeComments
- 是否删除注释
- 默认值:false
###### noEmit
- *不生成编译后的文件*
- 默认值:false
###### sourceMap
- 是否生成sourceMap
- 默认值:false
###### 严格检查
- strict
- 启用所有的严格检查,默认值为true,设置后相当于开启了所有的严格检查
- 建议开启
- 设置了true,则下面的所有都默认为true,要关闭的话单独设置
- alwaysStrict
- 总是以严格模式对代码进行编译
- *用来设置编译后的文件是否使用严格模式,默认false*
- "alwaysStrict": true,
- noImplicitAny
- 禁止隐式的any类型
- 默认false
- "noImplicitAny": true,
- noImplicitThis
- 禁止类型不明确的this
- 默认false
- "noImplicitThis": true,
- strictBindCallApply
- 严格检查bind、call和apply的参数列表
- strictFunctionTypes
- 严格检查函数的类型
- strictNullChecks
- 严格的空值检查
- 默认false
- "strictNullChecks": true
- strictPropertyInitialization
- 严格检查属性是否初始化
###### 额外检查
- noFallthroughCasesInSwitch
- 检查switch语句包含正确的break
- noImplicitReturns
- 检查函数没有隐式的返回值
- noUnusedLocals
- 检查未使用的局部变量
- noUnusedParameters
- 检查未使用的参数
###### 高级
- allowUnreachableCode
- 检查不可达代码
- 可选值:
- true,忽略不可达代码
- false,不可达代码将引起错误
- noEmitOnError
- 有错误的情况下不进行编译
- 默认值:false
- "noEmitOnError": true,
## webpack
通常情况下,实际开发中我们都需要使用构建工具对代码进行打包,TS同样也可以结合构建工具一起使用,下边以webpack为例介绍一下如何结合构建工具使用TS。
```
下载:配置less
npm i -D postcss postcss-loader postcss-preset-env
npm i -D less less-loader css-loader style-loader
```
### 步骤:
1. 初始化项目
- 进入项目根目录,执行命令 ``` npm init -y```
- 主要作用:创建package.json文件
2. 下载构建工具
- ```npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader clean-webpack-plugin```
- 共安装了7个包
- webpack
- 构建工具webpack
- webpack-cli
- webpack的命令行工具
- webpack-dev-server
- webpack的开发服务器
- typescript
- ts编译器
- ts-loader
- ts加载器,用于在webpack中编译ts文件
- html-webpack-plugin
- webpack中html插件,用来自动创建html文件
- clean-webpack-plugin
- webpack中的清除插件,每次构建都会先清除目录
3. 根目录下创建webpack的配置文件webpack.config.js
- ```javascript
// 引入一个包
const path = require('path');
// 引入html插件
const HTMLWebpackPlugin = require('html-webpack-plugin');
// 引入clean插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// webpack中的所有的配置信息都应该写在module.exports中
module.exports = {
// 指定入口文件
entry: "./src/index.ts",
// 指定打包文件所在目录
output: {
// 指定打包文件的目录
path: path.resolve(__dirname, 'dist'),
// 打包后文件的文件
filename: "bundle.js",
// 告诉webpack不使用箭头
environment:{
arrowFunction: false
}
},
// 指定webpack打包时要使用模块
module: {
// 指定要加载的规则
rules: [
{
// test指定的是规则生效的文件
test: /\.ts$/,
// 要使用的loader
use: [
// 配置babel
{
// 指定加载器
loader:"babel-loader",
// 设置babel
options: {
// 设置预定义的环境
presets:[
[
// 指定环境的插件
"@babel/preset-env",
// 配置信息
{
// 要兼容的目标浏览器
targets:{
"chrome":"58",
"ie":"11"
},
// 指定corejs的版本
"corejs":"3",
// 使用corejs的方式 "usage" 表示按需加载
"useBuiltIns":"usage"
}
]
]
}
},
'ts-loader'
],
// 要排除的文件
exclude: /node-modules/
}
]
},
// 配置Webpack插件
plugins: [
new CleanWebpackPlugin(),
new HTMLWebpackPlugin({
// title: "这是一个自定义的title"
template: "./src/index.html"
}),
],
// 用来设置引用模块
resolve: {
extensions: ['.ts', '.js']
}
};
```
4. 根目录下创建tsconfig.json,配置可以根据自己需要
- ```json
{
"compilerOptions": {
"module": "ES2015",
"target": "ES2015",
"strict": true
}
}
```
5. 修改package.json添加如下配置
- ```json
{
...略...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack serve --open chrome.exe"
},
...略...
}
```
6. 在src下创建ts文件,并在并命令行执行```npm run build```对代码进行编译,或者执行```npm start```来启动开发服务器
## Babel
经过一系列的配置,使得TS和webpack已经结合到了一起,除了webpack,开发中还经常需要结合babel来对代码进行转换以使其可以兼容到更多的浏览器,在上述步骤的基础上,通过以下步骤再将babel引入到项目中。
1. 安装依赖包:
- ```npm i -D @babel/core @babel/preset-env babel-loader core-js```
- 共安装了4个包,分别是:
- @babel/core
- babel的核心工具
- @babel/preset-env
- babel的预定义环境
- @babel-loader
- babel在webpack中的加载器
- core-js
- core-js用来使老版本的浏览器支持新版ES语法
2. 修改webpack.config.js配置文件
- ```javascript
...略...
module: {
rules: [
{
// test指定的是规则生效的文件
test: /\.ts$/,
// 要使用的loader
use: [
// 配置babel
{
// 指定加载器
loader:"babel-loader",
// 设置babel
options: {
// 设置预定义的环境
presets:[
[
// 指定环境的插件
"@babel/preset-env",
// 配置信息
{
// 要兼容的目标浏览器
targets:{
"chrome":"58",
"ie":"11"
},
// 指定corejs的版本
"corejs":"3",
// 使用corejs的方式 "usage" 表示按需加载
"useBuiltIns":"usage"
}
]
]
}
},
'ts-loader'
],
// 要排除的文件
exclude: /node-modules/
}
]
}
...略...
```
- 如此一来,使用ts编译后的文件将会再次被babel处理,使得代码可以在大部分浏览器中直接使用,可以在配置选项的targets中指定要兼容的浏览器版本。
## 面向对象
### 类(class)
- 定义类
- ```
class 类名 {
属性名: 类型;
constructor(参数: 类型){
this.属性名 = 参数;
}
方法名(){
....
}
}
```
- 使用类
- ```
示例:
class Person{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
sayHello(){
console.log(`大家好,我是${this.name}`);
}
}
const p = new Person('孙悟空', 18);
p.sayHello();
```
- 注意点:
- ```
/*
* 直接定义的属性是实例属性,需要通过对象的实例去访问:
* const per = new Person();
* per.name
*
* 使用static开头的属性是静态属性(类属性),可以直接通过类去访问
* Person.age
*
* readonly开头的属性表示一个只读的属性无法修改
* */
```
### 面向对象的特点
- 只读属性(readonly):
- 如果在声明属性时添加一个readonly,则属性便成了只读属性无法修改
- TS中属性具有三种修饰符:
- public(默认值),可以在类、子类和对象中修改
- protected ,可以在类、子类中修改
- private ,可以在类中修改
- public
- ```typescript
class Person{
public name: string; // 写或什么都不写都是public
public age: number;
constructor(name: string, age: number){
this.name = name; // 可以在类中修改
this.age = age;
}
sayHello(){
console.log(`大家好,我是${this.name}`);
}
}
class Employee extends Person{
constructor(name: string, age: number){
super(name, age);
this.name = name; //子类中可以修改
}
}
const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 可以通过对象修改
```
- protected
- ```typescript
class Person{
protected name: string;
protected age: number;
constructor(name: string, age: number){
this.name = name; // 可以修改
this.age = age;
}
sayHello(){
console.log(`大家好,我是${this.name}`);
}
}
class Employee extends Person{
constructor(name: string, age: number){
super(name, age);
this.name = name; //子类中可以修改
}
}
const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 不能修改
```
- private
- ```typescript
class Person{
private name: string;
private age: number;
constructor(name: string, age: number){
this.name = name; // 可以修改
this.age = age;
}
sayHello(){
console.log(`大家好,我是${this.name}`);
}
}
class Employee extends Person{
constructor(name: string, age: number){
super(name, age);
this.name = name; //子类中不能修改
}
}
const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 不能修改
```
- 属性存取器
对于一些不希望被任意修改的属性,可以将其设置为private
直接将其设置为private将导致无法再通过对象修改其中的属性
我们可以在类中定义一组读取、设置属性的方法,这种对属性读取或设置的属性被称为属性的存取器
读取属性的方法叫做setter方法,设置属性的方法叫做getter方法
示例:
```typescript
class Person{
private _name: string;
constructor(name: string){
this._name = name;
}
get name(){
return this._name;
}
set name(name: string){
this._name = name;
}
}
const p1 = new Person('孙悟空');
console.log(p1.name); // 通过getter读取name属性
p1.name = '猪八戒'; // 通过setter修改name属性
```
- 静态属性
- 静态属性(方法),也称为类属性。使用静态属性无需创建实例,通过类即可直接使用
- 静态属性(方法)使用static开头
- 示例:
- ```typescript
class Tools{
static PI = 3.1415926;
static sum(num1: number, num2: number){
return num1 + num2
}
}
console.log(Tools.PI);
console.log(Tools.sum(123, 456));
```
- this
- 在类中,使用this表示当前对象
### 继承
- 继承时面向对象中的又一个特性
- *通过继承可以将多个类中共有的代码写在一个父类中*,*这样只需要写一次即可让所有的子类都同时拥有父类中的属性和方法*,*如果希望在子类中添加一些父类中没有的属性或方法直接加就行*中
- 示例:
- ```typescript
class Animal{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
}
class Dog extends Animal{
bark(){
console.log(`${this.name}在汪汪叫!`);
}
}
const dog = new Dog('旺财', 4);
dog.bark();
```
- 通过继承可以在不修改类的情况下完成对类的扩展
- 重写
- *如果在子类中添加了和父类相同的方法,则子类方法会覆盖掉父类的方法*
** 这种子类覆盖掉父类方法的形式,我们称为方法重写*
- 示例:
- ```typescript
class Animal{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
run(){
console.log(`父类中的run方法!`);
}
}
class Dog extends Animal{
bark(){
console.log(`${this.name}在汪汪叫!`);
}
run(){
console.log(`子类中的run方法,会重写父类中的run方法!`);
}
}
const dog = new Dog('旺财', 4);
dog.bark();
```
- 在子类中可以使用super来完成对父类的引用
- super
- *如果在子类中写了构造函数,在子类构造函数中必须对父类的构造函数进行调用*
- *// 在类的方法中 super就表示当前类的父类*
- ```
(function () {
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHello() {
console.log('动物在叫~');
}
}
class Dog extends Animal{
age: number;
constructor(name: string, age: number) {
// 如果在子类中写了构造函数,在子类构造函数中必须对父类的构造函数进行调用
super(name); // 调用父类的构造函数
this.age = age;
}
sayHello() {
// 在类的方法中 super就表示当前类的父类
//super.sayHello();
console.log('汪汪汪汪!');
}
}
const dog = new Dog('旺财', 3);
dog.sayHello();
})();
```
- 抽象类(abstract class)
- 抽象类是专门用来被其他类所继承的类,它只能被其他类所继承**不能用来创建实例**
- ```typescript
abstract class Animal{
abstract run(): void;
bark(){
console.log('动物在叫~');
}
}
class Dog extends Animals{
run(){
console.log('狗在跑~');
}
}
```
- 使用abstract开头的方法叫做抽象方法,抽象方法没有方法体只能定义在抽象类中,**继承抽象类时抽象方法必须要实现**
## 接口(Interface)
接口的作用类似于抽象类,不同点在于接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法。接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性。
接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法,同时接口也可以当成类型声明去使用
```
* 接口可以在定义类的时候去限制类的结构,
* 接口中的所有的属性都不能有实际的值
* 接口只定义对象的结构,而不考虑实际值
* 在接口中所有的方法都是抽象方法
*
* */
interface myInter{
name: string;
sayHello():void;
}
```
- 示例(检查对象类型):
- ```typescript
interface Person{
name: string;
sayHello():void;
}
function fn(per: Person){
per.sayHello();
}
fn({name:'孙悟空', sayHello() {console.log(`Hello, 我是 ${this.name}`)}});
```
- 示例(实现)
- ```typescript
* 定义类时,可以使类去实现一个接口,
* 实现接口就是使类满足接口的要求
* */
interface Person{
name: string;
sayHello():void;
}
class Student implements Person{
constructor(public name: string) {
}
sayHello() {
console.log('大家好,我是'+this.name);
}
}
```
## 属性的封装
```
// TS可以在属性前添加属性的修饰符
/*
* public 修饰的属性可以在任意位置访问(修改) 默认值
* private 私有属性,私有属性只能在类内部进行访问(修改)
* - 通过在类中添加方法使得私有属性可以被外部访问
* protected 受包含的属性,只能在当前类和当前类的子类中访问(修改)
*
* */
private _name: string;
private _age: number;
constructor(name: string, age: number) {
this._name = name;
this._age = age;
}
通过在类中添加方法使得私有属性可以被外部访问
/*
* getter方法用来读取属性
* setter方法用来设置属性
* - 它们被称为属性的存取器
* */
// 定义方法,用来获取name属性
// getName(){
// return this._name;
// }
//
// // 定义方法,用来设置name属性
// setName(value: string){
// this._name = value;
// }
//
// getAge(){
// return this._age;
// }
//
// TS中设置getter方法的方式,使用:实例.name
get name(){
// console.log('get name()执行了!!');
return this._name;
}
set name(value){
this._name = value;
}
get age(){
return this._age;
}
set age(value){
if(value >= 0){
this._age = value
}
}
/* class C{
name: string;
age: number
// 可以直接将属性定义在构造函数中
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}*/
class C{
// 可以直接将属性定义在构造函数中
constructor(public name: string, public age: number) {
}
}
```
## 泛型(Generic)
- **泛型是可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用**,常用于:函数、接口、class 中
- 需求:创建一个 id 函数,传入什么数据就返回该数据本身(也就是说,参数和返回值类型相同)
```ts
function id(value: number): number { return value }
```
- 比如,id(10) 调用以上函数就会直接返回 10 本身。但是,该函数只接收数值类型,无法用于其他类型
- 为了能让函数能够接受任意类型,可以将参数类型修改为 any。但是,这样就失去了 TS 的类型保护,类型不安全
```ts
function id(value: any): any { return value }
```
- **泛型在保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同的类型一起工作,灵活可复用**
- 实际上,在 C# 和 Java 等编程语言中,泛型都是用来实现可复用组件功能的主要工具之一
### 创建泛型函数
```ts
function id(value: Type): Type { return value }
function id(value: T): T { return value }
```
- 解释:
1. 语法:在函数名称的后面添加 `<>`(尖括号),**尖括号中添加类型变量**,比如此处的 Type
2. **类型变量 Type,是一种特殊类型的变量,它处理类型而不是值**
3. **该类型变量相当于一个类型容器**,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)
4. 因为 Type 是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型
5. 类型变量 Type,可以是任意合法的变量名称
### 调用泛型函数
```ts
const num = id(10)
const str = id('a')
```
- 解释:
1. 语法:在函数名称的后面添加 `<>`(尖括号),**尖括号中指定具体的类型**,比如,此处的 number
2. 当传入类型 number 后,这个类型就会被函数声明时指定的类型变量 Type 捕获到
3. 此时,Type 的类型就是 number,所以,函数 id 参数和返回值的类型也都是 number
- 同样,如果传入类型 string,函数 id 参数和返回值的类型就都是 string
- 这样,通过泛型就做到了让 id 函数与多种不同的类型一起工作,**实现了复用的同时保证了类型安全**
### 简化泛型函数调用
```ts
// 省略 调用函数
let num = id(10)
let str = id('a')
```
- 解释:
1. 在调用泛型函数时,**可以省略 `<类型>` 来简化泛型函数的调用**
2. 此时,TS 内部会采用一种叫做**类型参数推断**的机制,来根据传入的实参自动推断出类型变量 Type 的类型
3. 比如,传入实参 10,TS 会自动推断出变量 num 的类型 number,并作为 Type 的类型
- 推荐:使用这种简化的方式调用泛型函数,使代码更短,更易于阅读
- 说明:**当编译器无法推断类型或者推断的类型不准确时,就需要显式地传入类型参数**
### 泛型约束
- 默认情况下,泛型函数的类型变量 Type 可以代表多个类型,这导致无法访问任何属性
- 比如,id('a') 调用函数时获取参数的长度:
```ts
function id(value: Type): Type {
console.log(value.length)
return value
}
id('a')
```
- 解释:Type 可以代表任意类型,无法保证一定存在 length 属性,比如 number 类型就没有 length
- 此时,就需要**为泛型添加约束来`收缩类型`(缩窄类型取值范围)**
- 添加泛型约束收缩类型,主要有以下两种方式:1 指定更加具体的类型 2 添加约束
#### 指定更加具体的类型
比如,将类型修改为 `Type[]`(Type 类型的数组),因为只要是数组就一定存在 length 属性,因此就可以访问了
```ts
function id(value: Type[]): Type[] {
console.log(value.length)
return value
}
```
#### 添加约束
```ts
// 创建一个接口
interface ILength { length: number }
// Type extends ILength 添加泛型约束
// 解释:表示传入的 类型 必须满足 ILength 接口的要求才行,也就是得有一个 number 类型的 length 属性
function id(value: Type): Type {
console.log(value.length)
return value
}
```
- 解释:
1. 创建描述约束的接口 ILength,该接口要求提供 length 属性
2. 通过 `extends` 关键字使用该接口,为泛型(类型变量)添加约束
3. 该约束表示:**传入的类型必须具有 length 属性**
- 注意:传入的实参(比如,数组)只要有 length 属性即可(类型兼容性)
### 多个类型变量
泛型的类型变量可以有多个,并且**类型变量之间还可以约束**(比如,第二个类型变量受第一个类型变量约束)
比如,创建一个函数来获取对象中属性的值:
```ts
function getProp(obj: Type, key: Key) {
return obj[key]
}
let person = { name: 'jack', age: 18 }
getProp(person, 'name')
```
- 解释:
1. 添加了第二个类型变量 Key,两个类型变量之间使用 `,` 逗号分隔。
2. **keyof 关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型**。
3. 本示例中 `keyof Type` 实际上获取的是 person 对象所有键的联合类型,也就是:`'name' | 'age'`
4. 类型变量 Key 受 Type 约束,可以理解为:Key 只能是 Type 所有键中的任意一个,或者说只能访问对象中存在的属性
```ts
// Type extends object 表示: Type 应该是一个对象类型,如果不是 对象 类型,就会报错
// 如果要用到 对象 类型,应该用 object ,而不是 Object
function getProperty(obj: Type, key: Key) {
return obj[key]
}
```
---
### 泛型接口
泛型接口:接口也可以配合泛型来使用,以增加其灵活性,增强其复用性
```ts
interface IdFunc {
id: (value: Type) => Type
ids: () => Type[]
}
let obj: IdFunc = {
id(value) { return value },
ids() { return [1, 3, 5] }
}
```
- 解释:
1. 在接口名称的后面添加 `<类型变量>`,那么,这个接口就变成了泛型接口。
2. 接口的类型变量,对接口中所有其他成员可见,也就是**接口中所有成员都可以使用类型变量**。
3. 使用泛型接口时,**需要显式指定具体的类型**(比如,此处的 IdFunc)。
4. 此时,id 方法的参数和返回值类型都是 number;ids 方法的返回值类型是 number[]。
#### JS 中的泛型接口
实际上,JS 中的数组在 TS 中就是一个泛型接口。
```ts
const strs = ['a', 'b', 'c']
// 鼠标放在 forEach 上查看类型
strs.forEach
const nums = [1, 3, 5]
// 鼠标放在 forEach 上查看类型
nums.forEach
```

- 解释:当我们在使用数组时,TS 会根据数组的不同类型,来自动将类型变量设置为相应的类型
- 技巧:可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键)来查看具体的类型信息
### 泛型工具类型
- 泛型工具类型:TS 内置了一些常用的工具类型,来简化 TS 中的一些常见操作
- 说明:它们都是基于泛型实现的(泛型适用于多种类型,更加通用),并且是内置的,可以直接在代码中使用。 这些工具类型有很多,主要学习以下几个:
1. `Partial`
2. `Readonly`
3. `Pick`
4. `Omit`
#### Partial
- Partial 用来构造(创建)一个类型,将 Type 的所有属性设置为可选。
```ts
type Props = {
id: string
children: number[]
}
type PartialProps = Partial
```
- 解释:构造出来的新类型 PartialProps 结构和 Props 相同,但所有属性都变为可选的。
#### Readonly
- Readonly 用来构造一个类型,将 Type 的所有属性都设置为 readonly(只读)。
```ts
type Props = {
id: string
children: number[]
}
type ReadonlyProps = Readonly
```
- 解释:构造出来的新类型 ReadonlyProps 结构和 Props 相同,但所有属性都变为只读的。
```ts
let props: ReadonlyProps = { id: '1', children: [] }
// 错误演示
props.id = '2'
```
- 当我们想重新给 id 属性赋值时,就会报错:无法分配到 "id" ,因为它是只读属性。
#### Pick
- Pick 从 Type 中选择一组属性来构造新类型。
```ts
interface Props {
id: string
title: string
children: number[]
}
type PickProps = Pick
```
- 解释:
1. Pick 工具类型有两个类型变量:1 表示选择谁的属性 2 表示选择哪几个属性。 2. 其中第二个类型变量,如果只选择一个则只传入该属性名即可。
2. 第二个类型变量传入的属性只能是第一个类型变量中存在的属性。
3. 构造出来的新类型 PickProps,只有 id 和 title 两个属性类型。
#### Omit
Omit类型让我们可以从另一个对象类型中剔除某些属性,并创建一个新的对象类型:
K:是对象类型名称,T:是剔除K类型中的属性名称

## 项目中使用
创建项目: npx create-react-app 项目名 --template typescript
项目目录的变化:
1. 在项目根目录中多了一个文件:`tsconfig.json`
- TS 的配置文件
2. 在 src 目录中,文件的后缀有变化,由原来的 .js 变为 `.ts` 或 `.tsx`
- `.ts` ts 文件的后缀名
- `.tsx` 是在 TS 中使用 React 组件时,需要使用该后缀
3. 在 src 目录中,多了 `react-app-env.d.ts` 文件
- `.d.ts` 类型声明文件,用来指定类型
### tsconfig的介绍
+ tsconfig.json是typescript项目的配置文件,用于配置typescript
+ tsconfig.json配置文件可以通过 `tsc --init` 生成
- 说明:所有的配置项都可以通过鼠标移入的方式,来查看配置项的解释说明。
- [tsconfig 文档链接](https://www.typescriptlang.org/tsconfig)
```json
{
// 编译选项
"compilerOptions": {
// 生成代码的语言版本:将我们写的 TS 代码编译成哪个版本的 JS 代码
// 命令行: tsc --target es5 11-测试TS配置文件.ts
"target": "es5",
// 指定要包含在编译中的 library
"lib": ["dom", "dom.iterable", "esnext"],
// 允许 ts 编译器编译 js 文件
"allowJs": true,
// 跳过类型声明文件的类型检查
"skipLibCheck": true,
// es 模块 互操作,屏蔽 ESModule 和 CommonJS 之间的差异
"esModuleInterop": true,
// 允许通过 import x from 'y' 即使模块没有显式指定 default 导出
"allowSyntheticDefaultImports": true,
// 开启严格模式
"strict": true,
// 对文件名称强制区分大小写
"forceConsistentCasingInFileNames": true,
// 为 switch 语句启用错误报告
"noFallthroughCasesInSwitch": true,
// 生成代码的模块化标准
"module": "esnext",
// 模块解析(查找)策略
"moduleResolution": "node",
// 允许导入扩展名为.json的模块
"resolveJsonModule": true,
// 是否将没有 import/export 的文件视为旧(全局而非模块化)脚本文件
"isolatedModules": true,
// 编译时不生成任何文件(只进行类型检查)
"noEmit": true,
// 指定将 JSX 编译成什么形式
"jsx": "react-jsx"
},
// 指定允许 ts 处理的目录
"include": ["src"]
}
```
### typescript声明文件
今天几乎所有的 JavaScript 应用都会引入许多第三方库来完成任务需求。
这些第三方库不管是否是用 TS 编写的,最终都要编译成 JS 代码,才能发布给开发者使用。
我们知道是 TS 提供了类型,才有了代码提示和类型保护等机制。
但在项目开发中使用第三方库时,你会发现它们几乎都有相应的 TS 类型,这些类型是怎么来的呢? `类型声明文件`
- **类型声明文件:用来为已存在的 JS 库提供类型信息**
这样在 TS 项目中使用这些库时,就像用 TS 一样,都会有代码提示、类型保护等机制了。
1. TS 的两种文件类型
2. 类型声明文件的使用说明
#### TS 中的两种文件类型
- TS 中有两种文件类型:1 `.ts` 文件 2 `.d.ts` 文件
- .ts 文件:
1. `既包含类型信息又可执行代码`
2. 可以被编译为 .js 文件,然后,执行代码
3. 用途:编写程序代码的地方
- .d.ts 文件:
1. `只包含类型信息`的类型声明文件
2. **不会生成 .js 文件,仅用于提供类型信息,在.d.ts文件中不允许出现可执行的代码,只用于提供类型**
3. 用途:为 JS 提供类型信息
- 总结:.ts 是 `implementation`(代码实现文件);**.d.ts 是 declaration(类型声明文件)**
- 如果要为 JS 库提供类型信息,要使用 `.d.ts` 文件
#### 类型声明文件的使用说明
- 在使用 TS 开发项目时,类型声明文件的使用包括以下两种方式:
1. 使用已有的类型声明文件
2. 创建自己的类型声明文件
使用已有的类型声明文件
1. 内置类型声明文件
2. 第三方库的类型声明文件
3. 自己提供的
#### 内置类型声明文件
- TS 为 JS 运行时可用的所有标准化内置 API 都提供了声明文件
- 比如,在使用数组时,数组所有方法都会有相应的代码提示以及类型信息:
```ts
const strs = ['a', 'b', 'c']
// 鼠标放在 forEach 上查看类型
strs.forEach
```
- 实际上这都是 TS 提供的内置类型声明文件
- 可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键)来查看内置类型声明文件内容
- 比如,查看 forEach 方法的类型声明,在 VSCode 中会自动跳转到 `lib.es5.d.ts` 类型声明文件中
- 当然,像 window、document 等 BOM、DOM API 也都有相应的类型声明(`lib.dom.d.ts`)
#### 第三方库的类型声明文件
- 目前,几乎所有常用的第三方库都有相应的类型声明文件
- 第三方库的类型声明文件有两种存在形式:1 库自带类型声明文件 2 由 DefinitelyTyped 提供。
1. 库自带类型声明文件:比如,axios
- 查看 `node_modules/axios` 目录
解释:这种情况下,正常导入该库,**TS 就会自动加载库自己的类型声明文件**,以提供该库的类型声明。
2. 由 DefinitelyTyped 提供
- DefinitelyTyped 是一个 github 仓库,用来提供高质量 TypeScript 类型声明
- [DefinitelyTyped 链接](https://github.com/DefinitelyTyped/DefinitelyTyped/)
- 可以通过 npm/yarn 来下载该仓库提供的 TS 类型声明包,这些包的名称格式为:`@types/*`
- 比如,@types/react、@types/lodash 等
- 说明:在实际项目开发时,如果你使用的第三方库没有自带的声明文件,VSCode 会给出明确的提示
```ts
import _ from 'lodash'
// 在 VSCode 中,查看 'lodash' 前面的提示
```
- 解释:当安装 `@types/*` 类型声明包后,**TS 也会自动加载该类声明包**,以提供该库的类型声明
- 补充:TS 官方文档提供了一个页面,可以来查询 @types/* 库
- [@types/* 库](https://www.typescriptlang.org/dt)
#### 创建自己的类型声明文件
1. 项目内共享类型
2. 为已有 JS 文件提供类型声明
##### 项目内共享类型
- 如果多个 .ts 文件中都用到同一个类型,此时可以创建 .d.ts 文件提供该类型,实现类型共享。
- 操作步骤:
1. 创建 index.d.ts 类型声明文件。
2. 创建需要共享的类型,并使用 export 导出(TS 中的类型也可以使用 import/export 实现模块化功能)。
3. 在需要使用共享类型的 .ts 文件中,通过 import 导入即可(.d.ts 后缀导入时,直接省略)。
##### 为已有 JS 文件提供类型声明
1. 在将 JS 项目迁移到 TS 项目时,为了让已有的 .js 文件有类型声明。
2. 成为库作者,创建库给其他人使用。
- 注意:类型声明文件的编写与模块化方式相关,不同的模块化方式有不同的写法。但由于历史原因,JS 模块化的发展 经历过多种变化(AMD、CommonJS、UMD、ESModule 等),而 TS 支持各种模块化形式的类型声明。这就导致 ,类型声明文件相关内容又多又杂。
- 演示:基于最新的 ESModule(import/export)来为已有 .js 文件,创建类型声明文件。
##### 类型声明文件的使用说明
- 说明:TS 项目中也可以使用 .js 文件。
- 说明:在导入 .js 文件时,TS 会自动加载与 .js 同名的 .d.ts 文件,以提供类型声明。
- declare 关键字:用于类型声明,为其他地方(比如,.js 文件)已存在的变量声明类型,而不是创建一个新的变量。
1. 对于 type、interface 等这些明确就是 TS 类型的(只能在 TS 中使用的),可以省略 declare 关键字。
2. 对于 let、function 等具有双重含义(在 JS、TS 中都能用),应该使用 declare 关键字,明确指定此处用于类型声明。
```jsx
let count = 10
let songName = '痴心绝对'
let position = {
x: 0,
y: 0
}
function add(x, y) {
return x + y
}
function changeDirection(direction) {
console.log(direction)
}
const fomartPoint = point => {
console.log('当前坐标:', point)
}
export { count, songName, position, add, changeDirection, fomartPoint }
```
定义类型声明文件
```jsx
declare let count:number
declare let songName: string
interface Position {
x: number,
y: number
}
declare let position: Position
declare function add (x :number, y: number) : number
type Direction = 'left' | 'right' | 'top' | 'bottom'
declare function changeDirection (direction: Direction): void
type FomartPoint = (point: Position) => void
declare const fomartPoint: FomartPoint
export {
count, songName, position, add, changeDirection, FomartPoint, fomartPoint
}
```
### 在现有项目中添加 TS
- [CRA 添加 ts 文档](https://create-react-app.dev/docs/adding-typescript)
- 如果要在现有的 JS 项目中,添加 TS,需要以下操作:
1. 安装包:`yarn add typescript @types/node @types/react @types/react-dom @types/jest`
2. 把 `jsconfig.json`改成 path.tsconfig.json
3. 将原来通过 React 脚手架创建的 TS 项目中的 tsconfig.json 中的配置,拷贝到咱们自己的项目中
4. 创建 `path.tsconfig.json` 文件,将原来 `jsconfig.json` 文件中的内容拿过来
```json
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"],
"@scss/*": ["src/assets/styles/*"]
}
}
}
```
5. 在 `tsconfig.json` 中,添加以下配置:
```json
{
// 添加这一句
"extends": "./path.tsconfig.json",
"compilerOptions": {
...
}
}
```
6. 将通过 React 脚手架创建的 TS 项目中的 `src/react-app-env.d.ts` 拷贝到咱们自己项目的 src 目录下
7. 重启项目
#### 说明
1. 项目中使用 TS 时,既可以包含 js 文件,又可以包含 ts 文件
- `.js`、`.jsx`(使用 JS 时,React 组件对应的文件后缀)
- `.ts`、`.tsx`(使用 TS 时,React 组件对应的文件后缀)、`.d.ts`
2. 在已有项目中,添加 TS 时的推荐模式
- 新的功能用 TS
- 已实现的功能,可以继续保持 JS 文件,慢慢修改为 TS 即可
3. React 组件对应的文件后缀,修改为:`.tsx`
4. 工具函数对应的文件后缀,修改为:`.ts` 或者为其添加类型声明文件 `.d.ts`
## React与typescript
### useState的使用
**目标:**掌握useState hooks配合typescript使用
**内容:**
+ `useState`接收一个泛型参数,用于指定初始值的类型
+ `useState`的源码如下
```jsx
/**
* Returns a stateful value, and a function to update it.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usestate
*/
function useState(initialState: S | (() => S)): [S, Dispatch>];
```
+ `useState`的使用
```jsx
const [name, setName] = useState('张三')
const [age, setAge] = useState(28)
const [isProgrammer, setIsProgrammer] = useState(true)
// 如果你在set函数中的参数不符合声明的变量类型,程序会报错
// 报错
```
+ `useState`的类型推断,在使用useState的时候,只要提供了初始值,typescript会自动根据初始值进行类型推断,因此`useState`的泛型参数可以省略
```jsx
export default function App() {
const [name, setName] = useState('张三')
const [age, setAge] = useState(28)
const [isProgrammer, setIsProgrammer] = useState(true)
return (
)
}
```
### useEffect的使用
**目标:**掌握useEffect hook在typescript中的使用
**内容**
+ `useEffect`是用于我们管理副作用(例如 API 调用)并在组件中使用 React 生命周期的
+ `useEffect`的源码
```jsx
/**
* Accepts a function that contains imperative, possibly effectful code.
*
* @param effect Imperative function that can return a cleanup function
* @param deps If present, effect will only activate if the values in the list change.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#useeffect
*/
function useEffect(effect: EffectCallback, deps?: DependencyList): void;
```
+ `useEffect`函数不涉及到任何泛型参数,在typescript中使用和javascript中使用完全一致。
```jsx
useEffect(() => {
// 给 window 绑定点击事件
const handleClick = () => {
console.log('哈哈哈')
}
window.addEventListener('click', handleClick)
return () => {
// 给 window 移除点击事件
window.addEventListener('click', handleClick)
}
}, [])
```
### useState 进阶用法
**目标:**能够使用useEffect发送请求并且配合useState进行渲染
**内容:**
+ 频道列表接口:`http://geek.itheima.net/v1_0/channels`
+ 需求,发送请求获取频道列表数据,并且渲染
+ **注意:**useState如果没有提供具体类型的初始值,是需要使用泛型参数指定类型的。
```jsx
// 存放频道列表数据
// 如果给useState的泛型参数直接指定为一个[],那将会得到一个never类型的数据,渲染的时候会出问题
const [list, setList] = useState([])
```

+ 如果useState的初始值是一个复杂的数据类型,需要给useState指定泛型参数
```jsx
import { useEffect, useState } from 'react'
import axios from 'axios'
type Res = {
id: number
name: string
}[]
export default function App() {
// 存放频道列表数据
const [list, setList] = useState([])
useEffect(() => {
const fetchData = async () => {
const res = await axios.get('http://geek.itheima.net/v1_0/channels')
setList(res.data.data.channels)
}
fetchData()
}, [])
return (
{list.map((item) => {
return - {item.name}
})}
)
}
```
### useRef的使用
**目标:**能够使用useRef配合ts操作DOM
**内容:**
+ `useRef` 接收一个泛型参数,源码如下
```jsx
/**
* `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
* (`initialValue`). The returned object will persist for the full lifetime of the component.
*
* Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
* value around similar to how you’d use instance fields in classes.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#useref
*/
function useRef(initialValue: T): MutableRefObject;
interface MutableRefObject {
current: T;
}
```
+ `useRef`的泛型参数用于指定current属性的值的类型
+ 如果使用useRef操作DOM,需要明确指定所操作的DOM的具体的类型,否则current属性会是null

+ 正确语法:
```jsx
const inputRef = useRef(null)
const get = () => {
console.log(inputRef.current?.value)
}
```
+ **技巧:**如何获取一个DOM对象的类型,鼠标直接移动到该元素上,就会显示出来该元素的类型

### 可选链操作符
**目标:**掌握js中的提供的可选链操作符语法
**内容**
+ **可选链**操作符( **`?.`** )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。
+ 参考文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Optional_chaining
```jsx
let nestedProp = obj.first?.second;
console.log(res.data?.data)
obj.fn?.()
if (obj.fn) {
obj.fn()
}
obj.fn && obj.fn()
// 等价于
let temp = obj.first;
let nestedProp = ((temp === null || temp === undefined) ? undefined : temp.second);
```
### 非空断言
**目标:**掌握ts中的非空断言的使用语法
**内容:**
+ 如果我们明确的知道对象的属性一定不会为空,那么可以使用非空断言 `!`
```ts
const obj: {
data: { name: string } | null
} = {
data: null
}
useEffect(() => {
obj.data = {
name: 'zs'
}
})
// 告诉typescript, 明确的指定obj不可能为空
let nestedProp = obj.data!.name
```
+ 注意:非空断言一定要确保有该属性才能使用,不然使用非空断言会导致bug
### react路由的使用
**目标:**能够在typescript中使用react路由
**内容:**
- 先装npm i react-router-dom@5.3.0
+ 再安装react-router-dom的类型声明文件`yarn add @types/react-router-dom`
+ 新建组件`Home.tsx`和`Login.tsx`
+ 配置路由
```jsx
import { BrowserRouter as Router, Link, Route } from 'react-router-dom'
import Home from './pages/Home'
import Login from './pages/Login'
export default function App() {
return (
)
}
```
+ **注意:**有了ts的支持后,代码提示变得非常的精确
### useHistory的使用
**目标:**掌握useHistory在typescript中的使用
**内容:**
+ useHistory可以实现路由之间的跳转,并且在跳转时可以指定跳转参数state的类型
+ useHistory的源码如下
```jsx
export function useHistory(): H.History;
```
+ useHistory如果仅仅实现跳转功能,和js中使用语法一致
```jsx
const history = useHistory()
const login = () => {
history.push('/login')
}
```
+ useHistory可以通过泛型参数来指定state的类型
```tsx
const history = useHistory<{
aa: string
}>()
const login = () => {
history.push({
pathname: '/login',
state: {
aa: 'cc',
},
})
}
```
### useLocation的使用
**目标:**掌握useLocation在typescript中的使用
**内容:**
+ useLocation接收一个泛型参数,用于指定接收的state类型,与useHistory的泛型参数对应
+ useLocation的源码
```jsx
export function useLocation(): H.Location;
```
+ 基本使用
```jsx
import { useLocation } from 'react-router'
export default function Home() {
const location = useLocation<{ aa: string } | null>()
const aa = location.state?.aa
return Home组件---{aa}
}
```
**注意:**因为useLocation和useHistory都需要指定Location类型,因此可以将类型存放到通用的类型声明文件中
```tsx
// types.d.ts
export type LoginState = {
aa: string
} | null
```
### useParams的使用
**目标**:能够掌握useParams在typescript中的使用
**内容:**
+ useParams接收一个泛型参数,用于指定params对象的类型
+ 基本使用
```jsx
import { useParams } from 'react-router'
export default function Article() {
const params = useParams<{ id: string }>()
console.log(params.id)
return (
)
}
```
### unkonw类型
**目标:**了解什么是TS中的unknown类型
**内容:**
+ unknown是更加安全的any类型。
+ 我们可以对 any 进行任何操作,不需要检查类型。
```jsx
// 没有类型检查就没有意义了,跟写JS一样。很不安全。
let value:any
value = true
value = 1
value.length
```
+ 也可以把任何值赋值给 unknown,但是不能调用属性和方法,除非使用类型断言或者类型收窄
```tsx
let value:unknown
value = 'abc'
(value as string).length
if (typeof value === 'string') {
value.length
}
```
### redux**基本使用**
**目标:**掌握在ts项目中如何初始化redux
**内容:**
+ 安装依赖包
```jsx
yarn add redux react-redux redux-devtools-extension
```
+ 新建文件 store/index.ts
```jsx
import { createStore } from 'redux'
import reducer from './reducers'
import { composeWithDevTools } from 'redux-devtools-extension'
const store = createStore(reducer, composeWithDevTools())
export default store
```
+ 新建文件 store/reducers/index.ts
```ts
import { combineReducers } from 'redux'
import todos from './todos'
const rootReducer = combineReducers({
todos,
})
export default rootReducer
```
+ 新建文件 store/reducers/todos.ts
```ts
const initValue = [
{
id: 1,
name: '吃饭',
done: false,
},
{
id: 2,
name: '睡觉',
done: true,
},
{
id: 3,
name: '打豆豆',
done: false,
},
]
export default function todos(state = initValue, action: any) {
return state
}
```
+ index.tsx中
```tsx
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import store from './store'
import { Provider } from 'react-redux'
ReactDOM.render(
,
document.getElementById('root')
)
```
### useSelector的使用
**目标:**掌握useSelector在ts中的使用
**内容**
+ useSelector接收两个泛型参数
+ 类型变量TState用于指定state的类型
+ TSelected用于指定返回值的类型
```tsx
export function useSelector(
selector: (state: TState) => TSelected,
equalityFn?: (left: TSelected, right: TSelected) => boolean
): TSelected;
```
+ useSelector的基本使用
```jsx
// 获取todos数据
const todos = useSelector<{ name: string }, string>((state) => state.name)
```
+ useSelector使用方式2,不指定泛型参数,直接指定state的类型
+ 参考文档:https://react-redux.js.org/using-react-redux/usage-with-typescript#typing-the-useselector-hook
```jsx
const todos = useSelector((state: { name: string }) => state.name)
```
**问题:如何准确的获取到store的类型??**
最佳使用方法:
```
//利用ReturnType获取,
type ReturnStateType = ReturnType
const count = useSelector((state) => state.count)
console.log(count)
```
### ReturnType获取
**目标:**能够掌握如何获取redux的rootState
**内容:**
+ 参考文档:https://react-redux.js.org/using-react-redux/usage-with-typescript
+ `typeof`可以获取某个数据的类型
```ts
function fn(n1: number, n2:number):number {
return n1 + n2
}
// 获取fn函数的类型
type Fn = typeof fn
```
+ `ReturnType`是一个泛型工具类型,可以获取一个函数类型的返回值类型
```tsx
function fn(n1: number, n2:number):number {
return n1 + n2
}
// 获取fn函数的类型
type Fn = typeof fn
// 获取Fn函数的返回值类型
type Res = ReturnType
```
+ 获取RootState的操作 `store/index.tx`
```tsx
export type RootState = ReturnType
```
+ **重要:**useSelector的正确用法
```tsx
import { RootState } from '../store'
// 获取todos数据
const todos = useSelector((state: RootState) => state.todos)
```
+ 渲染数据
```tsx
{todos.map(item => (
- {item.name}
))}
```
### useDispatch的使用
**目标:**掌握useDispatch在ts中的使用
**内容:**
+ 直接使用
```tsx
const dispatch = useDispatch()
```
+ 了解:useDispatch接收一个泛型参数用于指定Action的类型
```tsx
const dispatch = useDispatch<
Dispatch<{ type: 'ADD'; payload: number }>
>()
dispatch({
type: 'ADD',
payload: 1
})
```
### action和reducer的使用
**目标:**掌握reducers在TS中的写法
**内容**:
+ 准备Action
```tsx
export function addTodo(name: string) {
return {
type: 'ADD_TODO',
name,
}
}
export function delTodo(id: number) {
return {
type: 'DEL_TODO',
id,
}
}
```
+ 需要给action提供类型
```jsx
export type delAction = {
type: 'DEL_TODO'
payload: string
}
export type addAction = {
type: 'ADD_TODO'
payload: number
}
// 导出action联合类型
export type todoAction = delAction | addAction
export const delTodo = (payload: string): delAction => {
return {
type: 'DEL_TODO',
payload
}
}
export const addTodo = (payload: number): addAction => {
return {
type: 'ADD_TODO',
payload
}
}
```
+ 在reducer中指定初始值的类型
```tsx
import { todoAction } from '../actions/todos'
type TodosList = {
id: number
name: string
done: boolean
}[]
const initValue: TodosList = []
export default function todos(
state = initValue,
action: todoAction
): TodosList {
if (action.type === 'ADD_TODO') {
}
return state
}
```
+ 编写reducer
```jsx
import { TodoAction } from '../types'
export default function todos(
state = initValue,
action: TodoAction
): TodosList {
if (action.type === 'ADD_TODO') {
return [
{
id: Date.now(),
name: action.name,
done: false,
},
...state,
]
}
if (action.type === 'DEL_TODO') {
return state.filter((item) => item.id !== action.id)
}
return state
}
```
### 实现添加
**目标:**实现添加并掌握事件对象在TS中如何指定类型
**内容:**
+ 在使用事件对象时,需要指定事件对象的类型
```tsx
// 添加功能
const [todoName, setTodoName] = useState('')
const onChange = (e: React.ChangeEvent) => {
setTodoName(e.target.value)
}
const onAdd = (e: React.KeyboardEvent) => {
console.log(123)
if (e.code === 'Enter') {
dispatch(addTodo(todoName))
setTodoName('')
}
}
return (
)
```
+ 技巧:在行内事件中,鼠标移动到e上面可以看到具体的事件对象类型

### 实现删除
```tsx
const onDel = (id: number) => {
dispatch(delTodo(id))
}
return (
{todos.map(item => (
-
{item.name}{' '}
))}
)
```
### 实现修改
+ 控制类名
```tsx
{item.name}{' '}
```
+ 准备样式
```css
.completed {
color: #ccc;
text-decoration-line: line-through;
}
```
+ 定义actions
```ts
export type updateDoneAction = {
type: 'UPDATE_TODO'
payload: number
}
export type todoAction = delAction | addAction | updateDoneAction
export const updateDone = (payload: number): updateDoneAction => {
return {
type: 'UPDATE_TODO',
payload
}
}
```
+ 定义reducers
```ts
if (action.type === 'UPDATE_TODO') {
return state.map(item => {
if (item.id === action.payload) {
return {
...item,
done: !item.done
}
}
return item
})
}
```
+ 绑定点击事件
```tsx
dispatch(updateDone(item.id))}
>
{item.name}{' '}
```
### redux thunk的使用
**目标:**掌握redux thunk在typescript中的使用
**内容:**
+ 引入redux-thunk
```ts
yarn add redux-thunk
import thunk from 'redux-thunk'
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))
```
+ thunk类型的变更,使用了thunk之后,返回的Action类型不再是对象,而是函数类型的Action,因此需要修改Action的类型。 
+ ThunkAction类型的使用
+ 参考文档:https://redux.js.org/usage/usage-with-typescript#type-checking-redux-thunks
```tsx
// 在store中定义复用的RootThunkAction
export type RootThunkAction = ThunkAction
// 修改删除Action
export function delTodo(id: number): RootThunkAction {
return (dispatch) => {
setTimeout(() => {
dispatch({
type: 'DEL_TODO',
id,
})
}, 1000)
}
}
```
### redux-thunk版本bug
+ 在redux-thunk@2.4.0新版中,使用dispatch的时候,会丢失提示,需要降级到2.3.0版本
+ https://github.com/reduxjs/redux-thunk/issues/326
+ `yarn add redux-thunk@2.3.0`
------
# Web API
------
## Web API接口参考
### 1、URL.createObjectURL()
静态方法会创建一个 [`DOMString`](https://developer.mozilla.org/zh-CN/docs/Web/API/DOMString),其中包含一个表示参数中指定对象的 URL。这个 URL 的生命周期和创建它的窗口中的 [`document`](https://developer.mozilla.org/zh-CN/docs/Web/API/Document)绑定。这个新的 URL 对象表示指定的 [`File`](https://developer.mozilla.org/zh-CN/docs/Web/API/File)对象[`Blob`](https://developer.mozilla.org/zh-CN/docs/Web/API/Blob)对象。
示例:
```
// 根据文件,创建对应的 URL 地址
var newImgURL = URL.createObjectURL(files[0])
// 为裁剪区域重新设置图片
$image
.cropper('destroy') // 销毁旧的裁剪区域
.attr('src', newImgURL) // 重新设置图片路径
.cropper(options) // 重新初始化裁剪区域
```
## DOM
文档对象模型 编程接口
```
console.dir 打印我们返回的元素对象 更好的查看里面的属性和方法
```
### 获取元素
#### 1、根据ID获取
```
语法:document.getElementById("id")
作用:根据ID获取元素对象
参数:id值,区分大小写的字符串
返回值:元素对象 或 null
```
#### 2、根据标签名获取元素
```
语法:document.getElementsByTagName('标签名')
作用:根据标签名获取元素对象
参数:标签名
返回值:元素对象集合(伪数组,数组元素是元素对象)
//element.getElementsByTagName() 可以得到这个元素里面的某些标签
var nav = document.getElementById('nav'); // 这个获得nav 元素
var navLis = nav.getElementsByTagName('li');
```
#### 3、H5新增获取元素方式

```
document.getElementsByClassName('box');
document.querySelector('.box');
document.querySelectorAll('.box'); 返回一个集合
```
### 获取特殊元素(body,html)

### 事件
#### 事件三要素
- 事件源(谁):触发事件的元素
- 事件类型(什么事件): 例如 click 点击事件
- 事件处理程序(做啥):事件触发后要执行的代码(函数形式),事件处理函数
```
```
#### 常见的鼠标事件

mouseenter 鼠标经过,不会冒泡,只有经过自身才会触发事件;
mouseleave 鼠标离开,不会冒泡,只有经过自身才会触发事件;
#### 高级事件
##### 注册事件

###### 1、addEventListener()事件监听(IE9以后支持)


```
btns[1].addEventListener('click', function() {
alert(22);
})
```
###### 2、attacheEvent()事件监听(IE678支持)

eventTarget.attachEvent()方法将指定的监听器注册到 eventTarget(目标对象) 上,当该对象触发指定的事件时,指定的回调函数就会被执行。

###### 事件监听兼容性解决方案
封装一个函数,函数中判断浏览器的类型:

##### 删除事件(解绑事件)

```
// 1. 传统方式删除事件
divs[0].onclick = null;
// 2. removeEventListener 删除事件
divs[1].removeEventListener('click', fn) // 里面的fn 不需要调用加小括号
```
###### 删除事件兼容性解决方案

#### DOM 事件流
DOM 事件流会经历3个阶段:
捕获阶段
当前目标阶段
冒泡阶段


#### 事件对象

###### 事件对象的兼容性处理

```
只要“||”前面为false, 不管“||”后面是true 还是 false,都返回 “||” 后面的值。
只要“||”前面为true, 不管“||”后面是true 还是 false,都返回 “||” 前面的值。
123
```
#### 事件对象的属性和方法

1\3\6\7
#### 禁止选中文字和禁止右键菜单
```
// 1. contextmenu 我们可以禁用右键菜单 document.addEventListener('contextmenu', function(e) {
e.preventDefault();
})
// 2. 禁止选中文字 selectstart document.addEventListener('selectstart', function(e) {
e.preventDefault();
})
```
#### 鼠标事件对象
e.page

#### 常用的键盘事件


#### 键盘事件对象


```
document.addEventListener('keyup', function(e) {
console.log('up:' + e.keyCode);
// 我们可以利用keycode返回的ASCII码值来判断用户按下了那个键
}
```
### 操作元素
#### 改变元素内容
常用:innerHTML

**innerText和innerHTML的区别**
- 获取内容时的区别:
innerText会去除空格和换行,而innerHTML会保留空格和换行
- 设置内容时的区别:
innerText不会识别html,而innerHTML会识别
#### 表单元素的属性操作
**获取属性的值**
> 元素对象.属性名
**设置属性的值**
> 元素对象.属性名 = 值
>
> 表单元素中有一些属性如:disabled、checked、selected,元素对象的这些属性的值是布尔型。
```
```
#### 样式属性操作

##### 方式1:通过操作style属性
> 元素对象的style属性也是一个对象!
>
> 元素对象.style.样式属性 = '值';
>
> 
##### 方式2:通过操作className属性
> 元素对象.className = '值';
>
> 因为class是关键字,所有使用className。
>
> 
>
> ```
> //如果想要保留原先的类名,我们可以这么做 多类名选择器
> // this.className = 'change';
> this.className = 'first change';
> ```
### 自定义属性操作
#### 获取属性值

```
```
#### 设置属性值

```
// 2. 设置元素属性值
// (1) element.属性= '值'
div.id = 'test';
div.className = 'navs';
// (2) element.setAttribute('属性', '值'); 主要针对于自定义属性
div.setAttribute('index', 2);
div.setAttribute('class', 'footer'); // class 特殊 这里面写的就是
```
#### 移出属性

```
// class 不是className
// 3 移除属性 removeAttribute(属性)
div.removeAttribute('index');
```
#### H5自定义属性


### 节点操作
节点至少拥有nodeType(节点类型)、nodeName(节点名称)和nodeValue(节点值)这三个基本属性。
1. nodeType
**1.1 元素节点,nodeType为1**
1.2 属性节点,nodeType为2
1.3 文本节点,nodeType为3(文本节点包含文字、空格、换行等)
#### 父级节点

#### 子节点


##### **第1个子节点**

##### **最后1个子节点**

##### **第1个子元素节点**

##### **最后1个子元素节点**

**实际开发中,firstChild 和 lastChild 包含其他节点,操作不方便,而 firstElementChild 和 lastElementChild 又有兼容性问题,那么我们如何获取第一个子元素节点或最后一个子元素节点呢?**

#### 兄弟节点
```
//包括元素和文本节点
node.nextSibling //下一个
node.previousSibling //上一个
//元素节点 常用
node.nextElementSibling //下一个兄弟元素节点
node.previousElementSibling //上一个兄弟元素节点
```
#### 创建节点

#### 添加节点

常用第一种;
#### 删除节点
```
node.removeChild(child)
```
#### 复制(克隆)节点

## BOM
BOM(Browser Object Model)即浏览器对象模型,它提供了独立于内容而与浏览器窗口进行交互的对象,其核心对象是 window。
### window对象的常见事件
#### 页面(窗口)加载事件
1、**当文档内容完全加载完成**
会触发该事件(包括图像、脚本文件、CSS 文件等), 就调用的处理函数。


2、仅当DOM加载完成,不包括样式表,图片,flash等等。

注意:
IE9以上才支持!!!
如果页面的图片很多的话, 从用户访问到onload触发可能需要较长的时间, 交互效果就不能实现,必然影响用户的体验,此时用 DOMContentLoaded 事件比较合适。
#### 调整窗口大小事件

window.onresize 是调整窗口大小加载事件, 当触发时就调用的处理函数。
注意:
1. 只要窗口大小发生像素变化,就会触发这个事件。
2. 我们经常利用这个事件完成响应式布局。 window.innerWidth 当前屏幕的宽度
### 定时器
**重点:**
**防止刷新时有空白,需先调用一次定时器;**
#### setTimeout() 炸弹定时器


###### 使用方式:
```
1、
setTimeout(function() {}, 延时时长)
2、常用
callback(); //防止刷新时有空白,需先调用一次定时器;
function callback() {};
//调用
setTimeout( callback, 延时时长)
```
###### 清除定时器
```
clearTimeout(定时器名);
```
#### setInterval() 闹钟定时器

```
```
###### 使用方式:
```
1、
setInerval(function() {}, 延时时长)
2、常用
callback(); //防止刷新时有空白,需先调用一次定时器;
function callback() {};
//调用
setInerval( callback, 延时时长)
```
###### 清除定时器
```
clearInterval(定时器名);
```
### location对象


#### location 对象的属性
#### location 对象的属性

#### location对象的常见方法

### history对象
window对象给我们提供了一个 history对象,与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中)访问过的URL。

## PC端网页特效
### 元素偏移量 (offset)
1. 获得元素距离带有定位父元素的位置
2. 获得元素自身的大小(宽度高度)
3. 注意:返回的数值都不带单位
4. 注意:offsetWidth 包含padding+border+width;
5. 注意:offsetParent如果父亲没有定位,则返回body;

### 元素可视区 (client )
获取该元素的边框大小、元素大小等。

### 元素滚动 (scroll )
获取滚动距离


页面被卷去的头部:window.pageYoffset
元素被卷去的头部:element.scrollTop
页面滚动:window.scroll(0,0) //不跟单位
### 动画函数封装
核心原理:通过定时器 setInterval() 不断移动盒子位置。
实现步骤:
1. 获得盒子当前位置
2. 让盒子在当前位置加上1个移动距离
3. 利用定时器不断重复这个操作
4. 加一个结束定时器的条件
5. 注意此元素需要添加定位,才能使用element.style.left
#### 动画函数给不同元素记录不同定时器
```
function animate(obj, target) {
// 当我们不断的点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
// 解决方案就是 让我们元素只有一个定时器执行
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer);
obj.timer = setInterval(function() {
if (obj.offsetLeft >= target) {
obj.style.left = target + "px";
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
}
obj.style.left = obj.offsetLeft + 1 + 'px';
}, 30);
}
```
#### 缓动效果原理
核心算法: (目标值 - 现在的位置) / 10 = 做为每次移动的距离步长。
取整:step = step > 0 ? Math.ceil(step) : Math.floor(step);
#### 动函数添加回调函数
回调函数原理:函数可以作为一个参数。将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数,这个过程就叫做回调。
回调函数写的位置:定时器结束的位置。
#### 动画完整版代码:
```
1、左右运动
function animate(obj, target, callback) {
// console.log(callback); callback = function() {} 调用的时候 callback()
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer);
obj.timer = setInterval(function() {
// 步长值写到定时器的里面
// 把我们步长值改为整数 不要出现小数的问题
// var step = Math.ceil((target - obj.offsetLeft) / 10);
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
// 回调函数写到定时器结束里面
// if (callback) {
// // 调用函数
// callback();
// }
callback && callback();
}
// 把每次加1 这个步长值改为一个慢慢变小的值 步长公式:(目标值 - 现在的位置) / 10
obj.style.left = obj.offsetLeft + step + 'px';
}, 15);
}
-------------------------------------------------------------------------------------------------
2、页面回到顶部
function animateGoTop(target, callback) {
clearInterval(window.timer);
window.timer = setInterval(function() {
// 步长值
// var step = Math.ceil((target - obj.offsetLeft) / 10);
var step = (target - window.pageYOffset) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (window.pageYOffset == target) {
// 停止动画 本质是停止定时器
clearInterval(window.timer);
callback && callback();
}
// 设置页面滚动到的位置
window.scroll(0, window.pageYOffset + step);
}, 15);
}
```
### 节流阀
节流阀目的:当上一个函数动画内容执行完毕,再去执行下一个函数动画,让事件无法连续触发。
核心实现思路:利用回调函数,添加一个变量来控制,锁住函数和解锁函数。
```
var flag = true;
if (flag) {
flag = false; //先关闭节流阀
.
.
.
animate(obj,target , function() {
flag = true; //打开节流阀
})
}
```
### 网页轮播图
轮播图也称为焦点图,是网页中比较常见的网页特效。
功能需求:
1.鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。
2.点击右侧按钮一次,图片往左播放一张,以此类推,左侧按钮同理。
3.图片播放的同时,下面小圆圈模块跟随一起变化。
4.点击小圆圈,可以播放相应图片。
5.鼠标不经过轮播图,轮播图也会自动播放图片。
6.鼠标经过,轮播图模块, 自动播放停止。
```
window.addEventListener('load', function() {
// 1. 获取元素
var arrow_l = document.querySelector('.arrow-l');
var arrow_r = document.querySelector('.arrow-r');
var focus = document.querySelector('.focus');
var focusWidth = focus.offsetWidth;
// 2. 鼠标经过focus 就显示隐藏左右按钮
focus.addEventListener('mouseenter', function() {
arrow_l.style.display = 'block';
arrow_r.style.display = 'block';
clearInterval(timer);
timer = null; // 清除定时器变量
});
focus.addEventListener('mouseleave', function() {
arrow_l.style.display = 'none';
arrow_r.style.display = 'none';
timer = setInterval(function() {
//手动调用点击事件
arrow_r.click();
}, 2000);
});
// 3. 动态生成小圆圈 有几张图片,我就生成几个小圆圈
var ul = focus.querySelector('ul');
var ol = focus.querySelector('.circle');
// console.log(ul.children.length);
for (var i = 0; i < ul.children.length; i++) {
// 创建一个小li
var li = document.createElement('li');
// 记录当前小圆圈的索引号 通过自定义属性来做
li.setAttribute('index', i);
// 把小li插入到ol 里面
ol.appendChild(li);
// 4. 小圆圈的排他思想 我们可以直接在生成小圆圈的同时直接绑定点击事件
li.addEventListener('click', function() {
// 干掉所有人 把所有的小li 清除 current 类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下我自己 当前的小li 设置current 类名
this.className = 'current';
// 5. 点击小圆圈,移动图片 当然移动的是 ul
// ul 的移动距离 小圆圈的索引号 乘以 图片的宽度 注意是负值
// 当我们点击了某个小li 就拿到当前小li 的索引号
var index = this.getAttribute('index');
// 当我们点击了某个小li 就要把这个li 的索引号给 num
num = index;
// 当我们点击了某个小li 就要把这个li 的索引号给 circle
circle = index;
// num = circle = index;
console.log(focusWidth);
console.log(index);
animate(ul, -index * focusWidth);
})
}
// 把ol里面的第一个小li设置类名为 current
ol.children[0].className = 'current';
// 6. 克隆第一张图片(li)放到ul 最后面
var first = ul.children[0].cloneNode(true);
ul.appendChild(first);
// 7. 点击右侧按钮, 图片滚动一张
var num = 0;
// circle 控制小圆圈的播放
var circle = 0;
// flag 节流阀
var flag = true;
arrow_r.addEventListener('click', function() {
if (flag) {
flag = false; // 关闭节流阀
// 如果走到了最后复制的一张图片,此时 我们的ul 要快速复原 left 改为 0
if (num == ul.children.length - 1) {
ul.style.left = 0;
num = 0;
}
num++;
animate(ul, -num * focusWidth, function() {
flag = true; // 打开节流阀
});
// 8. 点击右侧按钮,小圆圈跟随一起变化 可以再声明一个变量控制小圆圈的播放
circle++;
// 如果circle == 4 说明走到最后我们克隆的这张图片了 我们就复原
if (circle == ol.children.length) {
circle = 0;
}
// 调用函数
circleChange();
}
});
// 9. 左侧按钮做法
arrow_l.addEventListener('click', function() {
if (flag) {
flag = false;
if (num == 0) {
num = ul.children.length - 1;
ul.style.left = -num * focusWidth + 'px';
}
num--;
animate(ul, -num * focusWidth, function() {
flag = true;
});
// 点击左侧按钮,小圆圈跟随一起变化 可以再声明一个变量控制小圆圈的播放
circle--;
// 如果circle < 0 说明第一张图片,则小圆圈要改为第4个小圆圈(3)
// if (circle < 0) {
// circle = ol.children.length - 1;
// }
circle = circle < 0 ? ol.children.length - 1 : circle;
// 调用函数
circleChange();
}
});
function circleChange() {
// 先清除其余小圆圈的current类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下当前的小圆圈的current类名
ol.children[circle].className = 'current';
}
// 10. 自动播放轮播图
var timer = setInterval(function() {
//手动调用点击事件
arrow_r.click();
}, 2000);
})
```
### 返回顶部
1. 带有动画的返回顶部
2. 此时可以继续使用我们封装的动画函数
3. 只需要把所有的left 相关的值改为 跟 页面垂直滚动距离相关就可以了
4. 页面滚动了多少,可以通过 window.pageYOffset 得到
5. 最后是页面滚动,使用 window.scroll(x,y)
```
//1. 获取元素
var sliderbar = document.querySelector('.slider-bar');
var banner = document.querySelector('.banner');
// banner.offestTop 就是被卷去头部的大小 一定要写到滚动的外面
var bannerTop = banner.offsetTop
// 当我们侧边栏固定定位之后应该变化的数值
var sliderbarTop = sliderbar.offsetTop - bannerTop;
// 获取main 主体元素
var main = document.querySelector('.main');
var goBack = document.querySelector('.goBack');
var mainTop = main.offsetTop;
// 2. 页面滚动事件 scroll
document.addEventListener('scroll', function() {
// console.log(11);
// window.pageYOffset 页面被卷去的头部
// console.log(window.pageYOffset);
// 3 .当我们页面被卷去的头部大于等于了 172 此时 侧边栏就要改为固定定位
if (window.pageYOffset >= bannerTop) {
sliderbar.style.position = 'fixed';
sliderbar.style.top = sliderbarTop + 'px';
} else {
sliderbar.style.position = 'absolute';
sliderbar.style.top = '300px';
}
// 4. 当我们页面滚动到main盒子,就显示 goback模块
if (window.pageYOffset >= mainTop) {
goBack.style.display = 'block';
} else {
goBack.style.display = 'none';
}
})
// 3. 当我们点击了返回顶部模块,就让窗口滚动的页面的最上方
goBack.addEventListener('click', function() {
// 里面的x和y 不跟单位的 直接写数字即可
// window.scroll(0, 0);
// 因为是窗口滚动 所以对象是window
animate(window, 0);
});
```
### 筋头云案例
1. 利用动画函数做动画效果
2. 原先筋斗云的起始位置是0
3. 鼠标经过某个小li,把当前小li的offsetLeft 位置做为目标值即可
4. 鼠标离开某个小li,就把目标值设为 0
5. 如果点击了某个小li, 就把li当前的位置存储起来,做为筋斗云的起始位置
```
window.addEventListener('load', function() {
// 1. 获取元素
var cloud = document.querySelector('.cloud');
var c_nav = document.querySelector('.c-nav');
var lis = c_nav.querySelectorAll('li');
// 2. 给所有的小li绑定事件
// 这个current 做为筋斗云的起始位置
var current = 0;
for (var i = 0; i < lis.length; i++) {
// (1) 鼠标经过把当前小li 的位置做为目标值
lis[i].addEventListener('mouseenter', function() {
animate(cloud, this.offsetLeft);
});
// (2) 鼠标离开就回到起始的位置
lis[i].addEventListener('mouseleave', function() {
animate(cloud, current);
});
// (3) 当我们鼠标点击,就把当前位置做为目标值
lis[i].addEventListener('click', function() {
current = this.offsetLeft;
});
}
})
```
## 移动端网页特效
### 触屏事件

### 触摸事件对象

**因为平时我们都是给元素注册触摸事件,所以重点记住 targetTocuhes**
### 拖动元素
拖动元素三步曲:
(1) 触摸元素 touchstart: 获取手指初始坐标,同时获得盒子原来的位置
(2) 移动手指 touchmove: 计算手指的滑动距离,并且移动盒子
(3) 离开手指 touchend:
**注意: 手指移动也会触发滚动屏幕所以这里要阻止默认的屏幕滚动 e.preventDefault();**
### classList 属性
classList属性是HTML5新增的一个属性,返回元素的类名。但是ie10以上版本支持。
该属性用于在元素中添加,移除及切换 CSS 类。有以下方法
**获取元素类名:**
element.classList [ 索引号 ];
```javascript
focus.classList[ 索引号 ];
```
**添加类:**
element.classList.add(’类名’);
```javascript
focus.classList.add('current');
```
**移除类:**
element.classList.remove(’类名’);
```javascript
focus.classList.remove('current');
```
**切换类:**
element.classList.toggle(’类名’);
```javascript
focus.classList.toggle('current');
```
`注意:以上方法里面,所有类名都不带点`
### click 延时解决方案(常用第三种方法)
移动端 click 事件会有 300ms 的延时,原因是移动端屏幕双击会缩放(double tap to zoom) 页面。
解决方案:
1. 禁用缩放。 浏览器禁用默认的双击缩放行为并且去掉300ms 的点击延迟。
```html
```
2.利用touch事件自己封装这个事件解决300ms 延迟。
原理就是:
1、当我们手指触摸屏幕,记录当前触摸时间
2、当我们手指离开屏幕, 用离开的时间减去触摸的时间
3、如果时间小于150ms,并且没有滑动过屏幕, 那么我们就定义为点击
代码如下:
```javascript
//封装tap,解决click 300ms 延时
function tap (obj, callback) {
var isMove = false;
var startTime = 0; // 记录触摸时候的时间变量
obj.addEventListener('touchstart', function (e) {
startTime = Date.now(); // 记录触摸时间
});
obj.addEventListener('touchmove', function (e) {
isMove = true; // 看看是否有滑动,有滑动算拖拽,不算点击
});
obj.addEventListener('touchend', function (e) {
if (!isMove && (Date.now() - startTime) < 150) { // 如果手指触摸和离开时间小于150ms 算点击
callback && callback(); // 执行回调函数
}
isMove = false; // 取反 重置
startTime = 0;
});
}
//调用
tap(div, function(){ // 执行代码 });
```
3. 使用插件。fastclick 插件解决300ms 延迟。
GitHub官网地址: [https://](https://github.com/ftlabs/fastclick)[github.com/ftlabs/fastclick](https://github.com/ftlabs/fastclick)
插件的使用:
1. 引入 js 插件文件。
2. 按照规定语法使用。
3. fastclick 插件解决 300ms 延迟。 使用延时
```javascript
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
```

## 移动端适配
Vant 中的样式默认使用 `px` 作为单位,如果需要使用 `rem` 单位,推荐使用以下两个工具:
- [postcss-pxtorem](https://github.com/cuth/postcss-pxtorem) 是一款 postcss 插件,用于将单位转化为 rem
- [lib-flexible](https://github.com/amfe/lib-flexible) 用于设置 rem 基准值
下面我们分别将这两个工具配置到项目中完成 REM 适配。
**一、使用 [lib-flexible](https://github.com/amfe/lib-flexible) 动态设置 REM 基准值(html 标签的字体大小)**
1、安装
```shell
# yarn add amfe-flexible
npm i amfe-flexible
```
2、然后在 `main.js` 中加载执行该模块
```javascript
import 'amfe-flexible'
```
**二、使用 [postcss-pxtorem](https://github.com/cuth/postcss-pxtorem) 将 `px` 转为 `rem`**
1、安装
```shell
# yarn add -D postcss-pxtorem
# -D 是 --save-dev 的简写
npm install postcss-pxtorem -D
```
2在**项目根目录**中创建`postcss.config.js`
```js
// postcss.config.js
module.exports = {
plugins: {
'postcss-pxtorem': {
rootValue: 37.5,
propList: ['*'],
},
},
};
```
==以iphone6为基准, 37.5px为基准值换算rem==
## **输入框的防抖**
**防抖策略**(debounce)是当事件被触发后,延迟 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时
用户在输入框中连续输入一串字符时,可以通过防抖策略,只在输入完后,才执行查询的请求,这样可以有效减少请求次数,节约请求资源;
```
var timer = null // 1. 防抖动的 timer
function debounceSearch(keywords) { // 2. 定义防抖的函数
timer = setTimeout(function() {
// 发起 JSONP 请求
getSuggestList(keywords)
}, 500)
}
$('#ipt').on('keyup', function() { // 3. 在触发 keyup 事件时,立即清空 timer
clearTimeout(timer)
// ...省略其他代码
debounceSearch(keywords)
})
```
## 防抖\缓存搜索案例

```
$(function () {
var time = null
function debounceSearch(kw) {
clearTimeout(time)
time = setTimeout(function () {
getSuggestList(kw)
}, 500)
}
var cacheObj = {}
function getSuggestList(kw) {
if (cacheObj[kw]) {
var res = cacheObj[kw]
var str = template('tp1', res)
$('#suggest-list').html(str).show()
} else {
$.ajax({
url: 'https://suggest.taobao.com/sug?code=utf-8&_ksTS=1633923881301_760&k=1&area=c2c&bucketid=18&q=' + kw,
dataType: 'jsonp',
success(res) {
console.log(res)
cacheObj[kw] = res
var str = template('tp1', res)
$('#suggest-list').html(str).show()
}
})
}
}
$('.ipt').on('keyup', function () {
var keywords = $(this).val().trim()
if (keywords == '') {
return $('#suggest-list').empty().hide()
}
// console.log(keywords)s
debounceSearch(keywords)
})
})
```
## 缓存搜索的建议列表
```
// 缓存对象 定义全局缓存对象
var cacheObj = {}
// 渲染建议列表
function renderSuggestList(res) {
// ...省略其他代码
// 将搜索的结果,添加到缓存对象中
var k = $('#ipt').val().trim()
// 将搜索结果保存到缓存对象中
cacheObj[k] = res
}
// 优先从缓存中获取搜索建议
// 监听文本框的 keyup 事件
$('#ipt').on('keyup', function() {
// ...省略其他代码
// 优先从缓存中获取搜索建议
if (cacheObj[keywords]) {
return renderSuggestList(cacheObj[keywords])
}
// 获取搜索建议列表
debounceSearch(keywords)
})
```
## 节流
节流策略(throttle),顾名思义,可以减少一段时间内事件的触发频率。
### 节流的应用场景
- 鼠标连续不断地触发某事件(如点击),只在单位时间内只触发一次;
- 懒加载时要监听计算滚动条的位置,但不必每次滑动都触发,可以降低计算的频率,而不必去浪费 CPU 资源;、
## 本地存储
### 本地存储特性
1、数据存储在用户浏览器中
2、设置、读取方便、甚至页面刷新不丢失数据
3、容量较大,sessionStorage约5M、localStorage约20M
4、只能存储字符串,可以将对象JSON.stringify() 编码后存储
5、获取是要是对象形式: JSON.parse()
### window.sessionStorage
1、生命周期为关闭浏览器窗口
2、在同一个窗口(页面)下数据可以共享
3、以键值对的形式存储使用
存储数据:
```javascript
sessionStorage.setItem('key', value)
```
获取数据:
```javascript
sessionStorage.getItem('key')
```
删除数据:
```javascript
sessionStorage.removeItem('key')
```
清空数据:(所有都清除掉)
```javascript
sessionStorage.clear()
```
### window.localStorage
1、声明周期永久生效,除非手动删除 否则关闭页面也会存在
2、可以多窗口(页面)共享(同一浏览器可以共享)
3. 以键值对的形式存储使用
存储数据:
```javascript
localStorage.setItem('key', value)
```
获取数据:
```javascript
localStorage.getItem('key')
```
删除数据:
```javascript
localStorage.removeItem('key')
```
清空数据:(所有都清除掉)
```javascript
localStorage.clear()
```
------
# jQuery
------
官网地址: https://jquery.com/
步骤:
- 引入jQuery文件。
- 在文档最末尾插入 script 标签,书写体验代码。
- $('div').hide() 可以隐藏盒子。
## jQuery 插件
1. jQuery 插件库 http://www.jq22.com/
2. jQuery 之家 http://www.htmleaf.com/
3. 全屏滚动插件 http://www.dowebok.com/demo/2014/77/
## 入口函数
相当于原生 js 中的 DOMContentLoaded。
```
// 第一种: 简单易用。
$(function () {
... // 此处是页面 DOM 加载完成的入口
}) ;
// 第二种: 繁琐,但是也可以实现
$(document).ready(function(){
... // 此处是页面DOM加载完成的入口
});
```
## jQuery 对象和 DOM 对象转换
jQuery 对象本质是: 利用$对DOM 对象包装后产生的对象(伪数组形式存储)。
```
// 1.DOM对象转换成jQuery对象,方法只有一种
var box = document.getElementById('box'); // 获取DOM对象
var jQueryObject = $(box); // 把DOM对象转换为 jQuery 对象
// 2.jQuery 对象转换为 DOM 对象有两种方法:
// 2.1 jQuery对象[索引值]
var domObject1 = $('div')[0]
// 2.2 jQuery对象.get(索引值)
var domObject2 = $('div').get(0)
```
## jQuery 选择器
### 基础选择器
```
$("选择器") // 里面选择器直接写 CSS 选择器即可,但是要加引号
```

### 层级选择器

### 筛选选择器

## 查元素

## 排他思想
```
// 想要多选一的效果,排他思想:当前元素设置样式,其余的兄弟元素清除样式。
$(this).css(“color”,”red”);
$(this).siblings(). css(“color”,””);
```
## 链式编程
```
// 链式编程是为了节省代码量,看起来更优雅。
$(this).css('color', 'red').sibling().css('color', '');
```
## 样式操作
### 操作 css 方法
```
// 1.参数只写属性名,则是返回属性值
var strColor = $(this).css('color');
// 2. 参数是属性名,属性值,逗号分隔,是设置一组样式,属性必须加引号,值如果是数字可以不用跟单位和引号
$(this).css(''color'', ''red'');
// 3. 参数可以是对象形式,方便设置多组样式。属性名和属性值用冒号隔开, 属性可以不用加引号
$(this).css({ "color":"white","font-size":"20px"});
```
### 设置类样式方法
removeClass
```
// 1.添加类
$("div").addClass("current");
// 2.删除类
$("div").removeClass("current");
// 3.切换类
$("div").toggleClass("current");
```
## jQuery 效果
### 显示隐藏
show
hide
显示隐藏动画,常见有三个方法:show() / hide() / toggle() ;



### 滑入滑出
滑入滑出动画,常见有三个方法:slideDown() / slideUp() / slideToggle() ;



### 淡入淡出
淡入淡出动画,常见有四个方法:fadeIn() / fadeOut() / fadeToggle() / fadeTo() ;




### 自定义动画
animate

### 停止动画排队
动画或者效果一旦触发就会执行,如果多次触发,就造成多个动画或者效果排队执行。
停止动画排队的方法为:stop() ;
- stop() 方法用于停止动画或效果。
- stop() 写到动画或者效果的前面, 相当于停止结束上一次的动画。
总结: 每次使用动画之前,先调用 stop() ,在调用动画。
### 事件切换
jQuery中为我们添加了一个新事件 hover() ; 功能类似 css 中的伪类 :hover 。介绍如下
**语法**
```javascript
hover([over,]out) // 其中over和out为两个函数
```
- over:鼠标移到元素上要触发的函数(相当于mouseenter)
- out:鼠标移出元素要触发的函数(相当于mouseleave)
- 如果只写一个函数,则鼠标经过和离开都会触发它
## jQuery 属性操作
### 元素固有属性值

注意:prop() 除了普通属性操作,更适合操作表单属性:disabled / checked / selected 等。
### 元素自定义属性值

注意:attr() 除了普通属性操作,更适合操作自定义属性。(该方法也可以获取 H5 自定义属性)
### 数据缓存

注意:同时,还可以读取 HTML5 自定义属性 data-index ,得到的是数字型。
## jQuery 文本属性值
jQuery的文本属性值常见操作有三种:html() / text() / val() ; 分别对应JS中的 innerHTML 、innerText 和 value 属性。
()为空为赋值;填写内容是为修改
```
//1. 获取设置元素内容 html()
$("div").html();
// 2. 获取设置元素文本内容 text()
$("div").text();
// 3. 获取设置表单值 val()
$("input").val();
```
## jQuery 元素操作
### 遍历元素each

注意:此方法用于遍历 jQuery 对象中的每一项,回调函数中元素为 DOM 对象,想要使用 jQuery 方法需要转换。

注意:此方法用于遍历 jQuery 对象中的每一项,回调函数中元素为 DOM 对象,想要使用 jQuery 方法需要转换。
### 创建元素

### 添加元素
append
after
before


### 删除元素
remove
empty

注意:以上只是元素的创建、添加、删除方法的常用方法,其他方法请参详API。
## jQuery 尺寸、位置操作
### jQuery 尺寸操作
width

### jQuery 位置操作
offset
position
scroll



## jQuery 事件处理
### 事件注册
单个事件注册:
```
element.事件(function() {});
```
多个事件注册:
```
element.on({
事件1: function() {},
事件2: function() {}
})
//当事件处理相同时,可以简写:
element.on("事件1,事件2",function() {});
```
### 事件解绑

### 自动触发事件


### 事件委托
// click 是绑定在ul 身上的,但是 触发的对象是 ul 里面的小li
```
$("ul").on("click", "li", function() {
alert(11);
});
```
### 给未来动态创建的元素绑定事件
```
$("ol").on("click", "li", function() {
alert(11);
})
var li = $("我是后来创建的");
$("ol").append(li);
```
## jQuery 事件对象

注意:jQuery中的 event 对象使用,可以借鉴 API 和 DOM 中的 event 。
## jQuery 拷贝对象

## jQuery 多库共存

```
```
------
# Echarts
------
ECharts,一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等),底层依赖矢量图形库 [ZRender](https://github.com/ecomfe/zrender),提供直观,交互丰富,可高度个性化定制的数据可视化图表。
官网:**https://www.echartsjs.com/zh/tutorial.html**
## 使用步骤:
- 下载echarts https://github.com/apache/incubator-echarts/tree/4.5.0
- 引入echarts `dist/echarts.min.js`
- 准备一个具备大小的DOM容器
```html
```
- ###### 初始化echarts实例对象
```js
var myChart = echarts.init(document.getElementById('main'));
```
- 指定配置项和数据(option)
```js
var option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [{
data: [820, 932, 901, 934, 1290, 1330, 1320],
type: 'line'
}]
};
```
- 将配置项设置给echarts实例对象
```js
myChart.setOption(option);
```
## 基础配置
- series
通过type:选择类型;data:[ ]写数据
```
bar 柱状图
line 折线/面积图
pie 饼图
```
- 系列列表。每个系列通过 `type` 决定自己的图表类型
- 大白话:图标数据,指定什么类型的图标,可以多个图表重叠。
- xAxis:直角坐标系 grid 中的 x 轴
- boundaryGap: 坐标轴两边留白策略 true,这时候刻度只是作为分隔线,标签和数据点都会在两个刻度之间的带(band)中间。
- yAxis:直角坐标系 grid 中的 y **轴**
- grid:直角坐标系内绘图网格。
- //containLabel 区域是否包含坐标轴的刻度标签。
- title:标题组件
- tooltip:提示框组件(提示)
- legend:图例组件(有多组数据时进行筛选,series中药有name属性)
- color:调色盘颜色列表
数据堆叠,同个类目轴上系列配置相同的`stack`值后 后一个系列的值会在前一个系列的值上相加。
```
var option = {
color: ['pink', 'blue', 'green', 'skyblue', 'red'],
title: {
text: '我的折线图'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['直播营销', '联盟广告', '视频广告', '直接访问']
},
grid: {
left: '3%',
right: '3%',
bottom: '3%',
// 当刻度标签溢出的时候,grid 区域是否包含坐标轴的刻度标签。如果为true,则显示刻度标签
// 如果left right等设置为 0% 刻度标签就溢出了,此时决定是否显示刻度标签
//containLabel 区域是否包含坐标轴的刻度标签。
containLabel: true
},
toolbox: {
feature: {
saveAsImage: {}
}
},
xAxis: {
type: 'category',
// 坐标轴两边留白策略 true,这时候刻度只是作为分隔线,标签和数据点都会在两个刻度之间的带(band)中间。
boundaryGap: false,
data: ['星期一', '星期二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [
{
name: '直播营销',
// 图表类型是线形图
type: 'line',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '联盟广告',
type: 'line',
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '视频广告',
type: 'line',
data: [150, 232, 201, 154, 190, 330, 410]
},
{
name: '直接访问',
type: 'line',
data: [320, 332, 301, 334, 390, 330, 320]
}
]
};
```
## 适配方案
flexible.js + rem + flex布局
基准 = (效果图大小 / 划分份数) //相当于html大小
## 边框图片

组合写法:
```css
border-image: url("images/border.jpg") 167/20px round;
```
拆分写法:
```css
1、路径
border-image-source: url("images/border.jpg");
2、裁剪尺寸(上,右,下,左)
border-image-slice: 167 167 167 167;
3、图片边框的宽度
border-image-width: 20px;
4、平铺repeat 铺满round(常用) 拉伸stretch(默认)
border-image-repeat: round;
```
解释:
- 边框图片资源地址
- 裁剪尺寸(上 右 下 左)单位默认px,可使用百分百。
- 边框图片的宽度,默认边框的宽度。
- 平铺方式:
- stretch 拉伸(默认)
- repeat 平铺,从边框的中心向两侧开始平铺,会出现不完整的图片。
- round 环绕,是完整的使用切割后的图片进行平铺。
## 公用面板样式

------
------
# Ajax
------
### Ajax简介
1、全称为 Asynchronous JavaScript And XML,就是异步的 JS 和 XML。
2、通过 AJAX 可以在浏览器中向服务器发送异步请求,最大的优势:无刷新获取数据。
3、AJAX 不是新的编程语言,而是一种将现有的标准组合在一起使用的新方式。
4、默认同源发请求
#### 优点:
1、可以无需刷新页面而与服务器端进行通信。
2、允许你根据用户事件来更新部分页面内容。
#### 缺点:
1、没有浏览历史,不能回退 。
2、存在跨域问题(同源) 。
3、SEO 不友好 。
#### 典型应用场景
1、页面上拉加载更多数据
2、列表数据无刷新分页
3、表单项离开焦点数据验证
4、搜索框提示文字下拉列表
### Ajax的使用
1、创建 AJAX (XMLHttpRequest) 对象
```js
var xhr = new XMLHttpRequest();
```
2、初始化 设置请求方法和 url
```js
xhr.open('method', 'url');
//可以设置请求头,一般不设
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
```
3、发送请求
```js
//get 请求不传 body 参数,只有 post 请求使用
xhr.send(body)
```
4、 接收响应
```js
1:
xhr.onload = function (){
console.log(xhr.responseText)
}
2:常用
//xhr.responseXML 接收 xml 格式的响应数据
//xhr.responseText 接收文本格式的响应数据
//当 Ajax 状态码发生变化时将自动触发该事件。
xhr.onreadystatechange = function (){
if(xhr.readyState == 4 && xhr.status == 200){
var text = xhr.responseText; console.log(text);
} else{
// console.log(xhr.status);//状态码
// console.log(xhr.statusText);//状态字符串
// console.log(xhr.getAllResponseHeaders());//所有响应头
// console.log(xhr.response);//响应体
//设置 result 的文本
result.innerHTML = xhr.response;
}
}
```
------
### **AJAX** 请求状态
#### 1、状态码
| **值** | **状态** | **描述** |
| ------ | ---------------- | --------------------------------------------------- |
| 0 | UNSENT | XMLHttpRequest 对象已被创建,但尚未调用 open方法。 |
| 1 | OPENED | open() 方法已经被调用。 |
| 2 | HEADERS_RECEIVED | send() 方法已经被调用,响应头也已经被接收。 |
| 3 | LOADING | 数据接收中,此时 response 属性中已经包含部分数据。 |
| 4 | DONE | Ajax 请求完成,这意味着数据传输已经彻底完成或失败。 |
#### 2、获取Ajax状态码
```
xhr.readyState
```
#### 3、当状态码发生改变时触发
```js
//需要放到xhr.send的前面
xhr.onreadystatechange = function() {
// 判断当Ajax状态码为4时
if (xhr.readyState == 4) {
// 获取服务器端的响应数据
console.log(xhr.responseText);
}
}
```
------
### URL编码与解码
URL 地址中,只允许出现英文相关的字母、标点符号、数字,因此,在 URL 地址中不允许出现中文字符。如果 URL 中需要包含中文这样的字符,则必须对中文字符进行编码(转义)。
URL编码的原则:使用安全的字符(没有特殊用途或者特殊意义的可打印字符)去表示那些不安全的字符。
URL编码原则的通俗理解:使用英文字符去表示非英文字符。
浏览器提供了 URL 编码与解码的 API,分别是:
encodeURI() 编码的函数
decodeURI() 解码的函数
```js
encodeURI('黑马程序员')
// 输出字符串 %E9%BB%91%E9%A9%AC%E7%A8%8B%E5%BA%8F%E5%91%98
decodeURI('%E9%BB%91%E9%A9%AC')
// 输出字符串 黑马
```
### 请求参数传递
查询字符串
格式:将英文的 ? 放在URL 的末尾,然后再加上 参数=值 ,想加上多个参数的话,使用 & 符号进行分隔。以这个形式,可以将想要发送给服务器的数据添加到 URL 中
```
// 不带参数的 URL 地址
http://www.liulongbin.top:3006/api/getbooks
// 带一个参数的 URL 地址
http://www.liulongbin.top:3006/api/getbooks?id=1
// 带两个参数的 URL 地址
http://www.liulongbin.top:3006/api/getbooks?id=1&bookname=西游记
```
#### 1、get请求方式
```js
// 配置ajax对象
xhr.open('get', 'http://localhost:3000/get?'+params)
// 发送请求
xhr.send();
// 获取姓名文本框
var username = document.getElementById('username');
// 获取年龄文本框
var age = document.getElementById('age');
// 获取用户在文本框中输入的值
var nameValue = username.value;
var ageValue = age.value;
// 拼接请求参数
var params = 'username='+ nameValue +'&age=' + ageValue;
```
简单写法:
```
xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks?id=1')
```
#### 2、post请求方式
```js
// 配置ajax对象
xhr.open('post', 'http://localhost:3000/post');
// 设置请求头的类型(post请求必须要设置)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
// 发送请求
xhr.send(params);
// 获取姓名文本框
var username = document.getElementById('username');
// 获取年龄文本框
var age = document.getElementById('age');
// 获取用户在文本框中输入的值
var nameValue = username.value;
var ageValue = age.value;
// 拼接请求参数
var params = 'username='+ nameValue +'&age=' + ageValue;
```
简单写法:
```
// 3. 设置 Content-Type 属性
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
// 4. 调用 send 函数
xhr.send('bookname=水浒传&author=施耐庵&publisher=上海图书出版社')
```
#### 3、数据格式
##### 1、响应数据格式
在 http 请求与响应的过程中,无论是请求参数还是响应内容,如果是对象类型,最终都会被转换为**对象字符串进行传输**。
```
// 将 json 字符串转换为json对象,把字符串转换为数据对象的过程,叫做反序列化
JSON.parse()
// 将json对象转换为json字符串,把数据对象转换为字符串的过程,叫做序列化
JSON.stringify()
//当设置了响应体数据的类型,则不需要手动转换了
xhr.responseType = 'json';
```
##### 2、请求数据格式( 请求头类型(Conent-Type))
类型:
1、application/x-www-form-urlencoded
`name=zhangsan&age=20&sex=男`
解析:app.use(bodyParser.urlencoded())
2、application/json
对象结构:key 必须是使用英文的双引号包裹的字符串,value 的数据类型可以是数字、字符串、布尔值、null、数组、对象6种类型
`{"name": "zhangsan", "age": "20", "sex": "男"}`
数组结构:数组结构在 JSON 中表示为 [ ] 括起来的内容。数据结构为 [ "java", "javascript", 30, true … ] 。数组中数据的类型可以是数字、字符串、布尔值、null、数组、对象6种类型。
```
[ "java", "python", "php" ]
[ 100, 200, 300.5 ]
[ true, false, null ]
[ { "name": "zs", "age": 20}, { "name": "ls", "age": 30} ]
[ [ "苹果", "榴莲", "椰子" ], [ 4, 50, 5 ] ]
```
解析:app.use(bodyParser.json())
*在请求头中指定 Content-Type 属性的值是 application/json,告诉服务器端当前请求参数的格式是 json。*
------
### Ajax 错误处理
#### **解决** **IE** 缓存问题
问题:
在一些浏览器中(IE),由于缓存机制的存在,ajax 只会发送的第一次请求,剩 余多次请求不会在发送给浏览器而是直接加载缓存中的数据。
解决方式:
浏览器的缓存是根据 url 地址来记录的,所以我们只需要修改 url 地址 即可避免缓存问题
```
xhr.open("get","'http://localhost:3000/cache?t=" + Date.now());
或者
xhr.open('get', 'http://localhost:3000/cache?t=' + Math.random());
```
#### 网络错误处理
```
1、网络畅通,服务器端能接收到请求,服务器端返回的结果不是预期结果。
可以判断服务器端返回的状态码,分别进行处理。xhr.status 获取http状态码
2、网络畅通,服务器端没有接收到请求,返回404状态码。
检查请求地址是否错误。
3、网络畅通,服务器端能接收到请求,服务器端返回500状态码。
服务器端错误,找后端程序员进行沟通。
4、网络超时。
xhr.ontimeout = function(){
alert("网络异常, 请稍后重试!!");
}
5、网络中断,请求无法发送到服务器端。
会触发xhr对象下面的onerror事件,在onerror事件处理函数中对错误进行处理。
xhr.onerror = function(){
alert("你的网络似乎出了一些问题!");
}
```
#### 取消请求
```
//获取元素对象
const btns = document.querySelectorAll('button');
// abort
btns[1].onclick = function(){
x.abort();
}
```
#### 重复请求问题
```
//获取元素对象
const btns = document.querySelectorAll('button');
let x = null;
//标识变量
// 是否正在发送AJAX请求
let isSending = false;
btns[0].onclick = function(){
//判断标识变量
// 如果正在发送, 则取消该请求, 创建一个新的请求
if(isSending) x.abort();
x = new XMLHttpRequest();
//修改 标识变量的值
isSending = true;
x.open("GET",'http://127.0.0.1:8000/delay');
x.send();
x.onreadystatechange = function(){
if(x.readyState === 4){
//修改标识变量
isSending = false;
}
}
}
// abort
btns[1].onclick = function(){
x.abort();
}
```
------
### 封装Ajax
调用:
```
ajax({
type: 'post',
// 请求地址
url: 'http://localhost:3000/responseData',
success: function (data) {
console.log('这里是success函数');
console.log(data)
}
})
请求参数位置的问题
将请求参数传递到ajax函数内部, 在函数内部根据请求方式的不同将请求参数放置在不同的位置
get 放在请求地址的后面
post 放在send方法中
```
封装:
```
function ajax (options) {
// 默认值
var defaults = {
type: 'get',
url: '',
data: {},
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
success: function () {},
error: function () {}
}
// 使用用户传递的参数替换默认值参数
Object.assign(defaults, options);
// 创建ajax对象
var xhr = new XMLHttpRequest();
// 参数拼接变量
var params = '';
// 循环参数
for (var attr in defaults.data) {
// 参数拼接
params += attr + '=' + defaults.data[attr] + '&';
// 去掉参数中最后一个&
params = params.substr(0, params.length-1)
}
// 如果请求方式为get
if (defaults.type == 'get') {
// 将参数拼接在url地址的后面
defaults.url += '?' + params;
}
// 配置ajax请求
xhr.open(defaults.type, defaults.url);
// 如果请求方式为post
if (defaults.type == 'post') {
// 设置请求头
xhr.setRequestHeader('Content-Type', defaults.header['Content-Type']);
// 如果想服务器端传递的参数类型为json
if (defaults.header['Content-Type'] == 'application/json') {
// 将json对象转换为json字符串
xhr.send(JSON.stringify(defaults.data))
}else {
// 发送请求
xhr.send(params);
}
} else {
xhr.send();
}
// 请求加载完成
xhr.onload = function () {
// 获取服务器端返回数据的类型
var contentType = xhr.getResponseHeader('content-type');
// 获取服务器端返回的响应数据
var responseText = xhr.responseText;
// 如果服务器端返回的数据是json数据类型
if (contentType.includes('application/json')) {
// 将json字符串转换为json对象
responseText = JSON.parse(responseText);
}
// 如果请求成功
if (xhr.status == 200) {
// 调用成功回调函数, 并且将服务器端返回的结果传递给成功回调函数
defaults.success(responseText, xhr);
} else {
// 调用失败回调函数并且将xhr对象传递给回调函数
defaults.error(responseText, xhr);
}
}
// 当网络中断时
xhr.onerror = function () {
// 调用失败回调函数并且将xhr对象传递给回调函数
defaults.error(xhr);
}
}
```
### 模板引擎
作用:使用模板引擎提供的模板语法,可以将数据和 HTML 拼接起来。
官方地址: https://aui.github.io/art-template/zh-cn/index.html
#### 使用步骤
```
1、下载 art-template 模板引擎库文件并在 HTML 页面中引入库文件
2、准备 art-template 模板
3、告诉模板引擎将哪一个模板和哪个数据进行拼接(数据是对象的形式)
var html = template('tpl', {username: 'zhangsan', age: '20'});
4、将拼接好的html字符串添加到页面中document.getElementById('container').innerHTML = html;
5、通过模板语法告诉模板引擎,数据和html字符串要如何拼接
```
#### 输出:
数据可为:属性名,运算, 三元表达式。。。
```
{{value}}
{{data.key}}
{{data['key']}}
{{a ? b : c}}
{{a || b}}
{{a + b}}
```
注意点:如果内含标签,需要解析
```
{{@ 数据}}
```
#### 条件判断:
```html
1、{{if 条件}}... {{/if}}
2、{{if 条件}}... {{else}}... {{/if}}
3、{{if 条件}}... {{else if 条件}}... {{/if}}
```
#### 循环:
```html
{{each 数据}}
{{$index}} //当前循环索引
{{$value}} //当前循环数据
{{/each}}
```
#### 过滤器:
要写再template函数渲染之前
注册过滤器时一定要有return
```
1、注册
template.defaults.imports.dateFormat = function(date, format){/*[code..]*/};
template.defaults.imports.timestamp = function(value){return value * 1000};
过滤器函数第一个参数接受目标值。
2、使用
{{date | timestamp | dateFormat 'yyyy-MM-dd hh:mm:ss'}}
```
#### 模板引擎的实现原理
##### 1、exec()
函数用于检索字符串中的正则表达式的匹配。如果字符串中有匹配的值,则返回该匹配值,否则返回 null。
```
RegExpObject(正则规则).exec(string)
```
示例
```
1、
var str = 'hello'
var pattern = /o/
// 输出的结果["o", index: 4, input: "hello", groups: undefined]
console.log(pattern.exec(str))
2、正则表达式中 ( ) 包起来的内容表示一个分组,可以通过分组来提取自己想要的内容
var obj = {
name: '杨明'
}
var str = '我的名字是{{name}}'
var reg = /{{(\w+)}}/
//2、1------------------------------------
var arr = reg.exec(str)
console.log(arr)
// ["{{name}}", "name", index: 7, input: "我是{{name}} ", groups: undefined]
str = str.replace(arr[0], obj[arr[1]])
console.log(str)
//2、2------------------------------------
var arr
while(arr = reg.exec(str)) {
str.replace(arr[0], obj[arr[1]])
}
console.log(str)
```
### 设置HTTP请求时限
- ***写于xhr.open之前***
有时,Ajax 操作很耗时,而且无法预知要花多少时间。如果网速很慢,用户可能要等很久。新版本的 XMLHttpRequest 对象,增加了 timeout 属性,可以设置 HTTP 请求的时限:
```
xhr.timeout = 3000
```
上面的语句,将最长等待时间设为 3000 毫秒。过了这个时限,就自动停止HTTP请求。与之配套的还有一个 timeout 事件,用来指定回调函数:
```
xhr.ontimeout = function(){
alert('请求超时!')
}
```
### FormData 对象
#### 作用
1、模拟HTML表单,相当于将HTML表单映射成表单对象,自动将表单对象中的数据拼接成请求参数的格式。
2、异步上传二进制文件
#### 基本使用步骤
```
1. 准备 HTML 表单 //要有name属性
2.1. 新建 FormData 对象
var formData = new FormData();
2.2. 向表单对象中追加属性值
formData.append('key', 'value');
3. 创建 XHR 对象
var xhr = new XMLHttpRequest()
4. 指定请求类型与URL地址
xhr.open('POST', 'url')
5 提交表单对象
xhr.send(formData);
注意:
Formdata 对象不能用于 get 请求,因为对象需要被传递到 send 方法中,而 get 请求方式的请求参数只能放在请求地址的后面。
服务器端 bodyParser 模块不能解析 formData 对象表单数据,我们需要使用 formidable 模块进行解析。
```
#### 获取网页表单的值
```js
```
#### 实例方法
```
1. 获取表单对象中属性的值
formData.get('key');
2. 设置表单对象中属性的值
formData.set('key', 'value');
3. 删除表单对象中属性的值
formData.delete('key');
4. 向表单对象中追加属性值
formData.append('key', 'value');
注意:set 方法与 append 方法的区别是,在属性名已存在的情况下,set 会覆盖已有键名的值,append会保留两个值。
```
#### 二进制文件上传
```
// 1. 获取到文件上传按钮
var btnUpload = document.querySelector('#btnUpload')
// 2. 为按钮绑定单击事件处理函数
btnUpload.addEventListener('click', function () {
// 3. 获取到用户选择的文件列表
var files = document.querySelector('#file1').files
if (files.length <= 0) {
return alert('请选择要上传的文件!')
}
var fd = new FormData()
// 将用户选择的文件,添加到 FormData 中
fd.append('avatar', files[0])
var xhr = new XMLHttpRequest()
xhr.open('POST', 'http://www.liulongbin.top:3006/api/upload/avatar')
xhr.send(fd)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var data = JSON.parse(xhr.responseText)
if (data.status === 200) {
// 上传成功
document.querySelector('#img').src = 'http://www.liulongbin.top:3006' + data.url
} else {
// 上传失败
console.log('图片上传失败!' + data.message)
}
}
}
})
```
#### 二进制文件上传进度提示
```
// 文件上传过程中持续触发onprogress事件
xhr.upload.onprogress = function (e) {
// 当前上传文件大小(e.loaded)/文件总大小(e.total) 再将结果转换为百分数
// 将结果赋值给进度条的宽度属性
var result = Math.ceil((e.loaded / e.total) * 100) + '%';
bar.style.width = resule;
//将百分比显示在进度条中
bar.innerHTML = result;
}
// 文件上传完成触发onlload事件
xhr.upload.onload = function () {
$('#percent').removeClass().addClass('progress-bar progress-bar-success')
}
```
#### 使用jQuery实现文件上传
```js
$('#btnUpload').on('click', function () {
var files = $('#file1')[0].files
if (files.length <= 0) {
return alert('请选择文件后再上传!')
}
var fd = new FormData()
fd.append('avatar', files[0])
// 发起 jQuery 的 Ajax 请求,上传文件
$.ajax({
method: 'POST',
url: 'http://www.liulongbin.top:3006/api/upload/avatar',
data: fd,
processData: false,//不要序列化
contentType: false,//不设置请求头
success: function (res) {
console.log(res)
}
})
})
```
#### jQuery实现loading效果
写于Ajax发起请求之前
```
Ajax 请求开始时,执行 ajaxStart 函数。可以在 ajaxStart 的 callback 中显示 loading 效果
// 监听到Ajax请求被发起了
$(document).ajaxStart(function () {
$('#loading').show()
})
注意: $(document).ajaxStart() 函数会监听当前文档内所有的 Ajax 请求。
Ajax 请求结束时,执行 ajaxStop 函数。可以在 ajaxStop 的 callback 中隐藏 loading 效果
// 监听到 Ajax 完成的事件
$(document).ajaxStop(function() {
$('#loading').hide()
})
var files = document.querySelector('#file1').files;
var fd = new FormData()
fd.append('avatar',files[0])
$.ajax({
method:'POST',
url:'',
data:fd,
timeout:200,//设置超时时间 如果超时了不执行success,执行error
processData:false,
contentType:false,
success:function(res){
},
error:function(err){
},
//如果做进度效果在这个属性里面写就可以
xhr:function(){
xhr.upload.onprogress = function(e){
console.log(e.loaded / e.total);
}
}
})
```
#### 文件上传图片即时预览
```
xhr.onload = function () {
//将服务器返回的数据显示在控制台中
var result = JSON.parse(xhr.responseText);
//动态创建img标签
var img = document.createElement('img');
//给img设置src属性
img.src = result.src;
//当图片加载完成后的
img.onload = function () {
//将图片显示在页面中
document.body.appendChild(this);
}
}
```
------
### jQuery中的Ajax
#### 1、$.get()
```
$.get(url, [data], [callback], [type])url:请求的 URL 地址。 data:请求携带的参数。 callback:载入成功时回调函数。 type:设置返回内容格式,xml, html, script, json, text, _default。
```
```
$.get('http://www.liulongbin.top:3006/api/getbooks', function(res) {
console.log(res) // 这里的 res 是服务器返回的数据
})
```
#### 2、$.post()
```
$.post(url, [data], [callback], [type])url:请求的 URL 地址。 data:请求携带的参数。 callback:载入成功时回调函数。 type:设置返回内容格式,xml, html, script, json, text, _default。
```
```
$.post(
'http://www.liulongbin.top:3006/api/addbook', // 请求的URL地址
{ bookname: '水浒传', author: '施耐庵', publisher: '上海图书出版社' }, // 提交的数据
function(res) { // 回调函数
console.log(res)
}
)
```
#### 3、$.ajax()
```
$.ajax({
// 请求的方式,例如 GET 或 POST
type: '',
// 请求的 URL 地址
url: '',
// 这次请求要携带的数据
data: { },
contentType: 'application/x-www-form-urlencoded',
beforeSend: function () {
return false
},
// 请求成功之后的回调函数
success: function(res) {
// response为服务器端返回的数据
// 方法内部会自动将json字符串转换为json对象
console.log(response);
},
// 请求失败以后函数被调用
error: function (xhr) {
console.log(xhr)
},
//超时时间
timeout: 2000,
//失败的回调
error: function(){
console.log('出错啦!!');
},
//头信息
headers: {
c:300,
d:400
},
//用于用户如果没有登录,是否能够允许用户访问后台主页?肯定是不能的,所以我们需要进行权限的校验,可以利用请求后服务器返回的状态来决定
// 不论成功还是失败,最终都会调用 complete 回调函数
complete: function(res) {
// console.log('执行了 complete 回调:')
// console.log(res)
// 在 complete 回调函数中,可以使用 res.responseJSON 拿到服务器响应回来的数据
if (res.responseJSON.status === 1 && res.responseJSON.message === '身份认证失败!') {
// 1. 强制清空 token
localStorage.removeItem('token')
// 2. 强制跳转到登录页面
location.href = '/login.html'
}
}
})
```
#### 4、$.ajax()中的方法
##### 1、beforeSend
```
// 在请求发送之前调用
beforeSend: function () {
alert('请求不会被发送')
// 请求不会被发送
return false;
},
```
##### 2、serialize
作用:将表单中的数据自动拼接成字符串类型的参数
```
var params = $('#form').serialize();
// name=zhangsan&age=30
// 将内容转换为数组、对象类型
var params = obj.serializeArray();
var params = obj.serializeObject();
```
##### 3、$.ajaxPrefilter()
作用:在我们调用 `$.ajax()` 之后,请求真正发给后台之前调用的: `$.ajax() > ajaxPrefilter过滤器 -> 发送请求给服务器`
- 调用 `$.ajaxPrefilter()` 函数,里面传递一个回调函数,回调函数里面有一个形成 `options`,这个形成里面就包含了这一次请求的相关信息
```
// 注意:每次调用 $.get() 或 $.post() 或 $.ajax() 的时候,
// 会先调用 ajaxPrefilter 这个函数
// 在这个函数中,可以拿到我们给Ajax提供的配置对象
$.ajaxPrefilter(function(options) {
// 在发起真正的 Ajax 请求之前,统一拼接请求的根路径
options.url = 'http://ajax.frontend.itheima.net' + options.url
})
```
#### 5、全局事件(NProgress)
官宣:纳米级进度条,使用逼真的涓流动画来告诉用户正在发生的事情
https://github.com/rstacruz/nprogress
```
只要页面中有Ajax请求被发送,对应的全局事件就会被触发
.ajaxStart() // 当请求开始发送时触发
.ajaxComplete() // 当请求完成时触发
//引入文件
NProgress.start(); // 进度条开始运动
NProgress.done(); // 进度条结束运动
```
例子:
```
// 当页面中有ajax请求发送时触发
$(document).on('ajaxStart', function () {
NProgress.start()
})
// 当页面中有ajax请求完成时触发
$(document).on('ajaxComplete', function () {
NProgress.done()
})
```
#### 6、RESTful 风格的 API
传统请求地址回顾
```
GET http://www.example.com/getUsers
// 获取用户列表GET http://www.example.com/getUser?id=1
// 比如获取某一个用户的信息
POST http://www.example.com/modifyUser
// 修改用户信息
GET http://www.example.com/deleteUser?id=1
// 删除用户信息
```
RESTful API 概述
```
GET: 获取数据
POST: 添加数据
PUT: 更新数据
http://www.example.com/users/1 修改用户ID为1的用户信息
DELETE: 删除数据
http://www.example.com/users/1 删除用户ID为1的用户信息
```
------
### Axios
简介:
基于promise用于浏览器和node.js的http客户端
支持浏览器和node.js
支持promise
能拦截请求和响应
自动转换JSON数据
能转换请求和响应数据
官网:
https://github.com/axios/axios
https://www.npmjs.com/package/axios
基本用法:
> ```
> axios.get('http://localhost:3000/adata').then(function(ret){
> // 注意data属性是固定的用法,用于获取后台的实际数据
> // console.log(ret.data)
> console.log(ret)
> })
> ```
发送get 请求:
> ```
> axios.get('http://localhost:3000/adata').then(function(ret){
> # 拿到 ret 是一个对象 所有的对象都存在 ret 的data 属性里面
> // 注意data属性是固定的用法,用于获取后台的实际数据
> // console.log(ret.data)
> console.log(ret)
> })
> ```
#### vue2.0中的使用
```js
常用
下载: npm install axios -S
在src/utils下封装一个request.js模块
import axios from 'axios'
const request = axios.create({
baseURL: '请求根路径'
})
export default request
使用:
(项目中常用)
1、在src/api下封装请求接口文件xxxxAPI.js
import request from '@/utils/request.js'
// 按需导出
export const getArticleAPI = function(_page, _limit) {
return request.get('/articles', {
// 请求参数
params: {
_page,
_limit
}
})
}
2、使用
// 按需导入API接口
import { getArticleAPI } from '@/api/articleAPI.js'
export default {
name: 'Home',
data() {
return {
page: 1,
limit: 10
}
},
created() {
this.initArticleList()
},
methods: {
// 封装获取文章列表数据的方法
async initArticleList() {
const { data: res } = await getArticleAPI(this.page, this.limit)
console.log(res)
}
}
}
----------------------------------------------------------------------------------------------
(常规使用)
// 导入封装axios文件
import request from '@/utils/request.js'
export default {
name: 'Home',
data() {
return {
page: 1,
limit: 10
}
},
created() {
this.initArticleList()
},
methods: {
// 封装获取文章列表数据的方法
async initArticleList() {
const { data: res } = await request.get('/articles', {
// 请求参数
params: {
_page: this.page,
_limit: this.limit
}
})
console.log(res)
}
}
}
```
```
在main.js中(不利于复用)
1、引入
import axios from 'axios'
2、把axios挂载到vue实例对象上:
配置公共的请求根路径
axios.defaults.baseURL = 'http://localhost:3000/';
Vue.protoype.$http = axios
3、其他组件使用
this.$http.get/post()
```
#### 请求传递参数(自服务器)
##### 1、get和 delete请求传递参数(相似)
通过传统的url 以 ? 的形式传递参数 :
> ```
> axios.get('http://localhost:3000/axios?id=123').then(function(ret){ console.log(ret.data)})
> ```
restful 形式传递参数 :
> ```
> axios.get('http://localhost:3000/axios/123').then(function(ret){
> console.log(ret.data)
> })
> ```
通过params 形式传递参数:
> ```
> axios.get('http://localhost:3000/axios', {
> params: {
> id: 789
> }
> }).then(function(ret){
> console.log(ret.data)
> })
> ```
##### 2、post 和 put 请求传递参数(相似)
通过选项传递参数
> ```
> axios.post('http://localhost:3000/axios', {
> uname: 'lisi',
> pwd: 123
> }).then(function(ret){
> console.log(ret.data)
> })
> ```
通过 URLSearchParams 传递参数
> ```
> var params = new URLSearchParams();
> params.append('uname', 'zhangsan');
> params.append('pwd', '111');
> axios.post('http://localhost:3000/axios', params).then(function(ret){
> console.log(ret.data)
> })
> ```
#### 请求传递参数(引入文件)
```
```
```
axios({
//请求方法
method : 'POST',
//url
url: '/axios-server',
params: {
/* GET参数 */
vip:10,
level:30
},
//请求体参数
data: {
/* POST数据 */
username: 'admin',
password: 'admin'
},
//头信息
headers: {
a:100,
b:200
}
}).then(response=>{
//响应状态码
console.log(response.status);
//响应状态字符串
console.log(response.statusText);
//响应头信息
console.log(response.headers);
//响应体
console.log(response.data);
}).catch(err =>{
console.log(err);
})
```
#### Axios响应结果
data 实际响应回来的数据
headers 响应头
status响应状态码
statusText响应状态信息
#### Axios中全局配置
```
配置公共的请求根路径
axios.defaults.baseURL = 'http://localhost:3000/';
配置 超时时间
axios.defaults.timeout
配置公共的请求头
axios.defaults.headers.common['Authorization']
//配置请求头信息
axios.defaults.headers['mytoken'] = 'hello';
总写:
axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
```
#### Axios拦截器用法
##### 1、请求拦截器
> ```
> axios.interceptors.request.use(function(config) {
> console.log(config.url)
> config.headers.mytoken = 'nihao';
> //必须有
> return config;
> }, function(err){
> console.log(err)
> })
> ```
##### 2、响应拦截器
> ```
> axios.interceptors.response.use(function(res) {
> // console.log(res)
> var data = res.data;
> //必须有
> return data;
> }, function(err){
> console.log(err)
> })
> ```
#### async/await用法
async用于函数上,返回值为promise实例对象;
await用于async函数中,可以得到异步结果。
```
async function 函数名() {
var ret = await axios.get('/data');
return ret
}
函数名.then(ret => {console.log(ret)})
```
#### 取消请求
> ```
> axios({
> url: 'http://localhost:4000/products1',
> cancelToken: new axios.CancelToken(function executor(c){ // c是用于取消当前请求的函数
> // 保存取消函数,用于之后可能需要取消当前请求
> cancel = c;
> })
> }).then(
> response => {
> cancel = null
> console.log('请求1成功了', response.data)
> },
> error => {
> cancel = null
> console.log('请求1失败了', error.message, error) // 请求1失败了 强制取消请求 Cancel {message: "强制取消请求"}
> }
> )
> ```
#### 捕获错误
```
try {
//可能错误的代码
} cath(err) {
next(err.message)
}
```
------
### Fetch
简介:
fetch默认是get请求;
返回promise对象。
官网:
https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API
基本用法:
> ```
> fetch('http://localhost:3000/fdata').then(function(data){
> // text()方法属于fetchAPI的一部分,它返回一个Promise实例对象,用于获取后台返回的数据
> return data.text();
> }).then(function(data){
> console.log(data);
> })
> ```
注意事项:
需要在 options 对象中 指定对应的 method method:请求使用的方法
post 和 普通请求的时候 需要在options 中 设置 请求头 headers 和请求体 body
#### 请求传递参数
##### 1、get和 delete请求传递参数(相似)
GET参数传递-传统URL
> ```
> fetch('http://localhost:3000/books?id=123', {
> method: 'get'
> }).then(function(data){
> return data.text();
> }).then(function(data){
> console.log(data)
> });
> ```
GET参数传递-restful形式的URL
> ```
> fetch('http://localhost:3000/books/456', {
> method: 'get'
> }).then(function(data){
> return data.text();
> }).then(function(data){
> console.log(data)
> });
> ```
##### 2、post 和 put 请求传递参数(相似)
post 和 普通请求的时候 需要在options 中 设置 请求头 headers 和请求体 body
> ```
> fetch('http://localhost:3000/books', {
> method: 'post',
> body: 'uname=lisi&pwd=123',
> headers: {
> 'Content-Type': 'application/x-www-form-urlencoded'
> }
> }).then(function(data){
> return data.text();
> }).then(function(data){
> console.log(data)
> });
> ```
#### Fetch响应结果
用fetch来获取数据,如果响应正常返回,我们首先看到的是一个response对象,其中包括返回的一堆原始字节,这些字节需要在收到后,需要我们通过调用方法将其转换为相应格式的数据,比如`JSON`,`BLOB`或者`TEXT`等等。
> ```
> /*
> Fetch响应结果的数据格式
> */
> fetch('http://localhost:3000/json').then(function(data){
> // return data.json(); // 将获取到的数据使用 json 转换对象
> return data.text(); // // 将获取到的数据 转换成字符串
> }).then(function(data){
> // console.log(data.uname)
> // console.log(typeof data)
> var obj = JSON.parse(data);
> console.log(obj.uname,obj.age,obj.gender)
> })
> ```
------
### Promise
注意: new是同步的,then是异步的
作用:
主要解决异步深层嵌套(回调地狱);
语法更加简洁。
官网:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
基本使用:我们使用new来构建一个Promise Promise的构造函数接收一个参数,是函数,并且传入两个参数:resolve,reject, 分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数
> ```
> var p = new Promise(function(resolve, reject){
> // 这里用于实现异步任务
> setTimeout(function(){
> var flag = false;
> if(flag) {
> // 正常情况
> resolve('hello');
> }else{
> // 异常情况
> reject('出错了');
> }
> }, 100);
> });
>
> p.then(function(data){
> console.log(data)
> },function(info){
> console.log(info)
> });
> //或者
> p.then(function(data){
> console.log(data)
> }).catch(function(info){
> console.log(info)
> });
>
> p.then(成功的回调函数,失败的回调函数) p.then(result => { }, error => { })
> 调用 .then() 方法时,成功的回调函数是必选的、失败的回调函数是可选的
> ```
#### 基于Promise发送Ajax请求
```
function queryData(url) {
# 1.1 创建一个Promise实例
var p = new Promise(function(resolve, reject){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState != 4) return;
if(xhr.readyState == 4 && xhr.status == 200) {
# 1.2 处理正常的情况
resolve(xhr.responseText);
}else{
# 1.3 处理异常情况
reject('服务器错误');
}
};
xhr.open('get', url);
xhr.send(null);
});
return p;
}
# 注意: 这里需要开启一个服务
# 在then方法中,你也可以直接return数据而不是Promise对象,在后面的then中就可以接收到数据了
queryData('http://localhost:3000/data')
.then(function(data){
console.log(data)
# 1.4 想要继续链式编程下去 需要 return
return queryData('http://localhost:3000/data1');
})
.then(function(data){
console.log(data);
return queryData('http://localhost:3000/data2');
})
.then(function(data){
console.log(data)
});
```
#### Promise常用API
##### 1、实例方法
1、.then()得到异步任务正确的结果
2、.catch()获取异常信息
3、.finally()成功与否都会执行(不是正式标准)
##### 2、对象方法
1、 .all()
Promise.all() 方法会发起并行的 Promise 异步操作,等所有的异步操作全部结束后才会执行下一步的 .then 操作(等待机制)
2、.race()
Promise.race() 方法会发起并行的 Promise 异步操作,只要任何一个异步操作完成,就立即执行下一步的.then 操作(赛跑机制)。
例子:
> ```
> Promise.all([p1,p2,p3]).then(function(result){
> //all 中的参数 [p1,p2,p3] 和 返回的结果一 一对应["HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]
> console.log(result) //["HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]
> })
> ```
#### **基于 then-fs 读取文件内容**
由于 node.js 官方提供的 fs 模块仅支持以回调函数的方式读取文件,不支持 Promise 的调用方式。因此,需要先运行如下的命令,安装 then-fs 这个第三方包,从而支持我们基于 Promise 的方式读取文件的内容:
安装:npm install then-fs
调用 then-fs 提供的 readFile() 方法,可以异步地读取文件的内容,它的返回值是 Promise 的实例对象。因此可以调用 .then() 方法为每个 Promise 异步操作指定成功和失败之后的回调函数。示例代码如下:

------
### 同源、跨域、**JSONP**
同源:协议、域名、端口号 必须完全相同,违背同源策略就是跨域。
#### **JSONP** 解决跨域问题(不推荐)
1、将不同源的服务器端请求地址写在 script 标签的 src 属性中
> ```
>
>
> ```
2、服务器端响应数据必须是一个函数的调用,真正要发送给客户端的数据需要作为函数调用的参数。
> ```
> const data = 'fn({name: "张三", age: "20"})';res.send(data);
> ```
3、在客户端全局作用域下定义函数 fn。
> ```
> function fn (data) { }
> ```
4、在 fn 函数内部对服务器端返回的数据进行处理。
> ```
> function fn (data) { console.log(data); }
> ```
##### JSONP 的使用
```
链接中的callback=命名函数的名字
```
#### jQuery中的JSONP
默认情况下,使用 jQuery 发起 JSONP 请求,会自动携带一个 callback=jQueryxxx 的参数,jQueryxxx 是随机生成的一个回调函数名称;
只能get请求;
```
$.ajax({
url: 'http://ajax.frontend.itheima.net:3006/api/jsonp?name=zs&age=20',
// 如果要使用 $.ajax() 发起 JSONP 请求,必须指定 datatype 为 jsonp
dataType: 'jsonp',
//想改回调函数的名字(没有意义)
jsonpCallback: 'abc',
//替换callback字段*(没有意义)
jsonp: 'callback',
success: function(res) {
console.log(res)
}
})
```
#### 封装JSONP
调用:
```
jsonp({
// 请求地址
url: 'http://localhost:3001/better',
data:{
source: 'pc',
weather_type: 'forecast_1h',
// weather_type: 'forecast_1h|forecast_24h',province: '黑龙江省',city: '哈尔滨市'
}
success: function (data) {
console.log(456789)
console.log(data)
}
})
```
封装:
```
function jsonp (options) {
// 动态创建script标签
var script = document.createElement('script');
// 拼接字符串的变量
var params = '';
for (var attr in options.data) {
params += '&' + attr + '=' + options.data[attr];
}// myJsonp0124741
var fnName = 'myJsonp' + Math.random().toString().replace('.', '');
// 它已经不是一个全局函数了 // 我们要想办法将它变成全局函数 window[fnName] = options.success;
// 为script标签添加src属性
script.src = options.url + '?callback=' + fnName + params;
// 将script标签追加到页面中
document.body.appendChild(script);
// 为script标签添加onload事件
script.onload = function () { document.body.removeChild(script);
}
}
```
服务器端优化:
```
app.get('/user', (req, res) => {
res.jsonp({name:'lisi', age:'19'})
})
```
#### **CORS** 解决跨域问题(推荐)
简介:
CORS(Cross-Origin Resource Sharing),跨域资源共享。CORS 是官方的跨域解决方 案,它的特点是不需要在客户端做任何特殊的操作,完全在服务器中进行处理,支持 get 和 post 请求。跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些 源站通过浏览器有权限访问哪些资源 。
工作流程:
CORS 是通过设置一个响应头来告诉浏览器,该请求允许跨域,浏览器收到该响应 以后就会对响应放行。
##### CORS中间件的使用:
cors 是 Express 的一个第三方中间件。通过安装和配置 cors 中间件,可以很方便地解决跨域问题。
使用步骤分为如下 3 步:
① 运行 npm install cors 安装中间件
② 使用 const cors = require('cors') 导入中间件
③ 在路由之前调用 app.use(cors()) 配置中间件
##### CORS的使用:
① CORS 主要在服务器端进行配置。客户端浏览器**无须做任何额外的配置**,即可请求开启了 CORS 的接口。
② CORS 在浏览器中有兼容性。只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS 的服务端接口(例如:IE10+、Chrome4+、FireFox3.5+)。
```
app.use((req, res, next) => {
//1.允许哪些客户端访问我、
// * 代表允许所有的客户端访问我 // 注意:如果跨域请求中涉及到cookie信息传递,值不可以为*号 比如是具体的域名信息
res.setHeader('Access-Control-Allow-Origin', '*');
// 2.允许哪些客户端请求头
res.setHeader('Access-Control-Allow-Headers', '*');
// 3.允许客户端使用哪些请求方法访问我
res.setHeader('Access-Control-Allow-Methods', 'GET, POST'或者'*');
// 4.允许客户端发送跨域请求时携带cookie信息
res.setHeader('Access-Control-Allow-Credentials', true); next();
})//setHeader设置请求头
```
#### 访问非同源数据服务器端解决方案
```
//服务器端
app.get('/server', (req, res) => {
request('其它服务器请求地址', (err, response, body) => { res.send(body);
})
});
```
#### withCredentials属性
1、在使用Ajax技术发送跨域请求时,默认情况下不会在请求中携带cookie信息
2、withCredentials:指定在涉及到跨域请求时,是否携带cookie信息,默认值为false
3、Access-Control-Allow-Credentials:true 允许客户端发送请求时携带cookie
### HTTP协议
通信三要素:通信的主体;通信的内容;通信的方式。
#### HTTP请求消息的组成部分
HTTP 请求消息由请求行(request line)、请求头部( header ) 、空行 和 请求体 4 个部分组成。

**注意:只有 POST 请求才有请求体,GET 请求没有请求体!**
#### HTTP响应消息的组成部分
HTTP响应消息由状态行、响应头部、空行 和 响应体 4 个部分组成,如下图所示:

#### HTTP的请求方法

#### HTTP响应状态码
HTTP 状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字用来对状态码进行细分。
HTTP 状态码共分为 5 种类型:





------
# Git
------
## 合作开发
### **问题1:**
开发一开始,你忘了新建分支。但此时,你已经在master分支上开发了半天了,写了好多代码。如果你现在新建分支是不可以了,你得先提交,但那不就提到master分支了吗。。。。
如何解决
```
git statsh //把已经修改的代码暂存到另外一个地方
```
现在你再切换分支就可以了
```
git checkout -b new-feature //切换到new-feature分支
git stash pop //把修改的释放出来
```
然后正常那个提交即可
```
git add .
git commit -m "新增XX"
```
## **安装并配置** Git
https://git-scm.com/downloads
**配置用户信息**
安装完 Git 之后,要做的第一件事就是设置自己的用户名和邮件地址。因为通过 Git 对项目进行版本管理的时候,Git 需要使用这些基本信息,来记录是谁对项目进行了操作:
```
1、git config --global user.name "提交人姓名"
2、git config --global user.email "邮箱"
```
通过 git config --global user.name 和 git config --global user.email 配置的用户名和邮箱地址,会被写入到 C:/Users/用户名文件夹/.gitconfig 文件中。这个文件是 Git 的**全局配置文件**,**配置一次即可永久生效**。
## **检查配置信息**
```
1、查询安装版本
git --version
2、查看配置信息
git config --list --global
# 查看指定的全局配置项
git config user.name
git config user.email
3、获取帮助信息
git help
git help config
-h 选项获得更简明的“help”输出
git config -h
4、更新
git update-git-for-windows
```
## **提交步骤**
```
1、初始化仓库
git init
2、查看文件状态( git status 输出的状态报告很详细,但有些繁琐。如果希望以精简的方式显示文件的状态,可以使用如下两条完全等价的命令,其中 -s 是 --short 的简写形式)
git status 或者 git status -s
3、跟踪新文件,添加到暂存区
git add git add . //添加所有文件
4、向仓库提交代码
git commit -m "提交信息"
5、查看提交信息
git log
```
## **取消暂存的文件**
```
git reset HEAD 要移除的文件名称
```
## 忽略文件
一般我们总会有些文件无需纳入 `Git` 的管理,也不希望它们总出现在未跟踪文件列表。 在这种情况下,我们可以创建一个名为 `.gitignore` 的配置文件,列出要忽略的文件的匹配模式。
文件 `.gitignore` 的格式规范如下:
① 以 **# 开头**的是注释
② 以 **/ 结尾**的是目录
③ 以 **/ 开头**防止递归
④ 以 **! 开头**表示取反
⑤ 可以使用 **glob 模式**进行文件和文件夹的匹配(glob 指简化了的正则表达式)
- **星号 \*** 匹配**零个或多个任意字符**
- **`[abc]`** 匹配**任何一个列在方括号中的字符** (此案例匹配一个 a 或匹配一个 b 或匹配一个 c)
- **问号 ?** 只匹配**一个任意字符**
- **两个星号 \**** 表示匹配**任意中间目录**(比如 a/**/z 可以匹配 a/z 、 a/b/z 或 a/b/c/z 等)
- 在方括号中使用**短划线**分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)
## 跳过使用暂存区域,提交
```
git commit -a -m "提交信息"
```
## **移除文件**
```
1、从 Git 仓库和工作区中同时移除对应的文件
git rm -f 文件名
2、只从 Git 仓库中移除指定的文件,但保留工作区中对应的文件
git rm --cached 文件名
```
## 覆盖文件
```
1、暂存区的目录覆盖工作区文件
git checkedout 文件
2、将git仓库中指定的更新记录恢复出来,并覆盖暂存区和工作目录
git rest --hard commitID(黄色的)
```
## 遇到冲突时的分支合并
如果**在两个不同的分支中**,对**同一个文件**进行了**不同的修改**,Git 就没法干净的合并它们。 此时,我们需要打开
这些包含冲突的文件然后**手动解决冲突**。
```shell
# 假设:在把 reg 分支合并到 master 分支期间
git checkout master
git merge reg
# 打开包含冲突的文件,手动解决冲突之后,再执行如下命令
git add .
git commit -m "解决了分支合并冲突的问题"
```
## 回退到指定的版本
```
查看提交历史
# 按时间先后顺序列出所有的提交历史,最近的提交在最上面
git log
# 只展示最新的两条提交历史,数字可以按需进行填写
git log -2
# 在一行上展示最近两条提交历史的信息
git log -2 --pretty=oneline
# 在一行上展示最近两条提交历史信息,并自定义输出的格式
# &h 提交的简写哈希值 %an 作者名字 %ar 作者修订日志 %s 提交说明
git log -2 --pretty=format:"%h | %an | %ar | %s"
1、从新到旧
git log --pretty=oneline//在一行上展示所有的提交历史
git reset --hard (黄色的)//回退到指定的版本
2、从旧到新
git reflog//提交历史
git reset --hard (黄色的)//回退到指定的版本
```
## Github - 远程仓库的使用
SSH key 由**两部分组成**,分别是:
① id_rsa(私钥文件,存放于客户端的电脑中即可)
② id_rsa.pub(公钥文件,需要配置到 Github 中)
**生成 SSH key**
① 打开 Git Bash
② 粘贴如下的命令,并将 your_email@example.com 替换为注册 Github 账号时填写的邮箱:
⚫ ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
③ 连续敲击 3 次回车,即可在 C:\Users\用户名文件夹\.ssh 目录中生成 id_rsa 和 id_rsa.pub 两个文件
**配置 SSH key**
① 使用记事本打开 id_rsa.pub 文件,复制里面的文本内容
② 在浏览器中登录 Github,点击头像 -> Settings -> SSH and GPG Keys -> New SSH key
③ 将 id_rsa.pub 文件中的内容,粘贴到 Key 对应的文本框中
④ 在 Title 文本框中任意填写一个名称,来标识这个 Key 从何而来
**检测 Github 的 SSH key 是否配置成功**
打开 Git Bash,输入如下的命令并回车执行:
- ```
ssh -T git@github.com
```
上述的命令执行成功后,可能会看到如下的提示消息:

输入 yes 之后,如果能看到类似于下面的提示消息,证明 SSH key 已经配置成功

## 将本地仓库上传到 Github
```
1、 git push 远程地址 分支名称
2、 git push 远程地址别名 分支名称
远程地址别名: git remote add 别名 远程地址
3、 git push -u 远程地址别名 分支名
-u 记住推送的远程地址及分支,下次只需git push即可
```
## 将远程仓库克隆到本地
```
git clone 远程仓库地址
```
## 本地分支操作
- 主分支: master:用来保存和记录整个项目已完成的功能代码
- 功能分支:feature:专门用来开发新功能的分支
- 开发分支:develop
```
1、查看分支
git branch
2、创建新分支
git branch 分支名
3、切换分支
git checkout 分支名
4、分支的快速创建和切换(创建新分支,并立即切换到新分支上)
git checkout -b 分支名
5、合并分支(合并到哪先切换到哪分支)
git merge 来源分支
6、删除分支(分支合并后才允许删除) -D:强制删除
git branch -d 分支名
```
## 远程分支操作
```
1、推送分支
git push -u 远程仓库 本地分支:远程分支名称
2、查看远程仓库所有分支列表
git remote show 远程仓库名称
3、拉取远程库的最新版
git pull 远程地址 分支名
4、删除远程分支
git push 远程仓库名称 --delete 远程分支名称
5、从远程仓库中,把远程分支下载到本地仓库中
git checkout 远程分支
6、从远程仓库中,把远程分支下载到本地仓库中,并把下载的本地分支进行重命名
git checkout -b 本地分支名称 远程仓库名称(origin)/远程分支名称
```
## 为仓库添加详细说明
新建文件: README.m
------
# 模拟接口
大致步骤:
- 全局安装json-server/mock
- 新建 db.json 文件
- 启动接口服务
- 接口地址列表
具体内容:
1. 全局安装json-server
```bash
npm i json-server -g
```
1. 新建 db.json 文件,内容如下
```json
{
"todos": [
{"id":1,"name":"吃饭","done":true},
{"id":2,"name":"睡觉","done":false}
]
}
```
1. 启动服务,在db.json文件目录下执行
```bash
json-server db.json
# 设置端口
# json-server db.json --port 5000
```
1. 接口地址列表
```text
GET /todos 获取列表
POST /todos 添加任务,参数 name 和 done
GET /todos/:id 获取任务,参数 id
DELETE /todos/:id 删除任务,参数 id
PUT /todos/:id 修改任务,参数 name 和 done , 完整更新
PATCH /todos/:id 修改任务,参数 name 或 done , 局部更新
```
# Node.js
------
## 终端中的快捷键
在 Windows 的 powershell 或 cmd 终端中,我们可以通过如下快捷键,来提高终端的操作效率:
1、 使用 ↑ 键,可以快速定位到上一次执行的命令
2、使用 tab 键,能够快速补全路径
3、使用 esc 键,能够快速清空当前已输入的命令
4、输入 cls 命令,可以清空终端
5、cd 切换目录
5.1、cd .. 返回上一级目录
5.2、cd 下一级目录(./文件夹)
## 系统模块
### 1、fs 文件系统模块
fs 模块是 Node.js 官方提供的、用来操作文件的模块。它提供了一系列的方法和属性,用来满足用户对文件的操作需求。
例如:
- fs.readFile() 方法,用来读取指定文件中的内容
- fs.writeFile() 方法,用来向指定的文件中写入内容1、
#### fs.readFile() 方法:
`fs.readFile(path[, options], callback)`
参数解读:
⚫ 参数1:必选参数,字符串,表示文件的路径。
⚫ 参数2:可选参数,表示以什么编码格式来读取文件。
⚫ 参数3:必选参数,文件读取完成后,通过回调函数拿到读取的结果
```
// 1. 导入 fs 模块,来操作文件
const fs = require('fs')
// 2. 调用 fs.readFile() 方法读取文件
// 参数1:读取文件的存放路径
// 参数2:读取文件时候采用的编码格式,一般默认指定 utf8
// 参数3:回调函数,拿到读取失败和成功的结果 err dataStr
fs.readFile('./files/11.txt', 'utf8', function(err, dataStr) {
// 2.1 打印失败的结果
// 如果读取成功,则 err 的值为 null
// 如果读取失败,则 err 的值为 错误对象,dataStr 的值为 undefined
console.log(err)
console.log('-------')
// 2.2 打印成功的结果
console.log(dataStr)
if (err) {
return console.log('读取文件失败!' + err.message)
}
console.log('读取文件成功!' + dataStr)
})
```
#### fs.writeFile() 方法:
`fs.writeFile(file, data[, options], callback)`
参数解读:
⚫ 参数1:必选参数,需要指定一个文件路径的字符串,表示文件的存放路径。
⚫ 参数2:必选参数,表示要写入的内容。
⚫ 参数3:可选参数,表示以什么格式写入文件内容,默认值是 utf8。
⚫ 参数4:必选参数,文件写入完成后的回调函数
```
// 1. 导入 fs 文件系统模块
const fs = require('fs')
// 2. 调用 fs.writeFile() 方法,写入文件的内容
// 参数1:表示文件的存放路径
// 参数2:表示要写入的内容
// 参数3:回调函数
fs.writeFile('./files/3.txt', 'ok123', function(err) {
// 2.1 如果文件写入成功,则 err 的值等于 null
// 2.2 如果文件写入失败,则 err 的值等于一个 错误对象
// console.log(err)
if (err) {
return console.log('文件写入失败!' + err.message)
}
console.log('文件写入成功!')
})
```
### 2、path 路径模块
#### 路径拼接path.join()
```
const path = require('path')
// 注意: ../ 会抵消前面的路径
const pathStr = path.join('/a', '/b/c', '../../', './d', 'e')
console.log(pathStr) // \a\b\d\e
```
```
const path = require('path')
//绝对路径
const pathStr = path.join(__dirname,'./files/1.txt')
console.log(pathStr) // 输出当前文件所处目录\files\1.txt
```
案例:
```
const fs = require('fs')
const path = require('path')
fs.readFile(path.join(__dirname, './files/1.txt'), 'utf-8', function (err, dataStr) {
if (err) {
return console.log(err.message)
}
console.log(dataStr)
})
```
### 3、http 模块
#### 创建最基本的 web 服务器
① 导入 http 模块
```
const http = require('http')·
```
② 创建 web 服务器实例
```
const app = http.createServer()
```
③ 为服务器实例绑定 **request** 事件,监听客户端的请求
```
app.on('request', function (req, res) {
console.log('Someone visit our web server.')
res.end('')//响应
req.method//请求方式
req.url//请求地址
req.headers//请求方法/报文信息
})
```
④ 启动服务器
```
app.listen(8080, function () {
console.log('server running at http://127.0.0.1:8080')
})
```
#### 解决中文乱码问题
当调用 res.end() 方法,向客户端发送中文内容的时候,会出现乱码问题,此时,需要手动设置内容的编码格式:
```
app.on('request', (req, res) => {
// 定义一个字符串,包含中文的内容
const str = `您请求的 URL 地址是 ${req.url},请求的 method 类型为 ${req.method}`
// 调用 res.setHeader() 方法,设置 Content-Type 响应头,解决中文乱码的问题
res.setHeader('Content-Type', 'text/html; charset=utf-8')
// res.end() 将内容响应给客户端
res.end(str)
})
```
#### 根据不同的 url 响应不同的 html 内容
**1. 核心实现步骤**
① 获取请求的 url 地址
② 设置默认的响应内容为 404 Not found
③ 判断用户请求的是否为 / 或 /index.html 首页
④ 判断用户请求的是否为 /about.html 关于页面
⑤ 设置 Content-Type 响应头,防止中文乱码
⑥ 使用 res.end() 把内容响应给客户端
```
app.on('request', (req, res) => {
// 1. 获取请求的 url 地址
const url = req.url
// 2. 设置默认的响应内容为 404 Not found
let content = '404 Not found!'
// 3. 判断用户请求的是否为 / 或 /index.html 首页
// 4. 判断用户请求的是否为 /about.html 关于页面
if (url === '/' || url === '/index.html') {
content = '首页'
} else if (url === '/about.html') {
content = '关于页面'
}
// 5. 设置 Content-Type 响应头,防止中文乱码
res.setHeader('Content-Type', 'text/html; charset=utf-8')
// 6. 使用 res.end() 把内容响应给客户端
res.end(content)
})
```
## Node.js 中的模块化
### 加载模块(导入)
注意:使用 require() 方法加载其它模块时,会执行被加载模块中的代码。

### 导出模块成员
默认情况下,exports 和 module.exports 指向同一个对象。最终共享的结果,还是以 module.exports 指向的对象为准。
```
module.exports.属性 = '方法'
exports.属性 = '方法'
```
## 第三方模块(包)
**npm -v** 命令,来查看自己电脑上所安装的 npm 包管理工具的版本号。
### npm
如果需要安装指定版本的包,可以在包名之后,通过 @ 符号指定具体的版本。
```
下载:npm install 模块名称
卸载:npm uninstall package 模块名称
```
初次装包完成后,在项目文件夹下多一个叫做 node_modules 的文件夹和 package-lock.json 的配置文件。
#### node_modules
作用:用来存放所有已安装到项目中的包。require() 导入第三方包时,就是从这个目录中查找并加载包。
```
生成:npm install
```
#### package-lock.json
作用:件用来记录 node_modules 目录下的每一个包的下载信息,例如包的名字、版本号、下载地址等。
#### 包管理配置文件package.json
npm 规定,在项目根目录中,**必须**提供一个叫做package.json 的包管理配置文件。用来记录与项目有关的一些配置信息。
```
生成:npm init -y
```
#### package.json 文件中节点:
- dependencies 节点,专门用来记录您使用 npm install 命令安装了哪些包,某些包在开发和项目上线之后都需要用到。
- devDependencies 节点:**只在项目开发阶段**会用到,在**项目上线之后不会用到**,则建议把这些包记录到 devDependencies 节点中。

### 包的分类
#### **1. 项目包**
项目包又分为两类,分别是:
⚫ 开发依赖包(被记录到 devDependencies 节点中的包,只在开发期间会用到)
⚫ 核心依赖包(被记录到 dependencies 节点中的包,在开发期间和项目上线之后都会用到)

#### **2. 全局包**
在执行 npm install 命令时,如果提供了 -g 参数,则会把包安装为全局包。全局包会被安装到 C:\Users\用户目录\AppData\Roaming\npm\node_modules 目录下。

注意:
① 只有工具性质的包,才有全局安装的必要性。因为它们提供了好用的终端命令。
② 判断某个包是否需要全局安装后才能使用,可以参考官方提供的使用说明即可。
#### **3. i5ting_toc**
i5ting_toc 是一个可以把 md 文档转为 html 页面的小工具,使用步骤如下:
```
安装:npm install -g i5ting_toc
使用:i5ting_toc -f 要转换md文件路径 -o
```
### nodemon模块
```
安装:npm install nodemon -g
使用: nodemon 代替 node
作用:修改文件后会自动重新执行文件
```
## **Express**
Express 的中文官网: http://www.expressjs.com.cn/
安装:`npm install express`
### **基本使用**
```
// 1. 导入 express
const express = require('express')
// 2. 创建 web 服务器
const app = express()
// 4. 监听客户端的 GET 和 POST 请求,并向客户端响应具体的内容
app.get('请求url', (req, res) => {
// 调用 express 提供的 res.send() 方法,向客户端响应一个 JSON 对象
res.send({ name: 'zs', age: 20, gender: '男' })
})
app.post('请求url', (req, res) => {
// 调用 express 提供的 res.send() 方法,向客户端响应一个 文本字符串
res.send('请求成功')
})
// 3. 启动 web 服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
```
#### **监听** **GET 请求**

#### **监听** **POST 请求**

#### **获取 URL 中携带的查询参数**

#### 获取 URL 中的动态参数

### **托管静态资源**
#### **express.static()**
express 提供了一个非常好用的函数,叫做 express.static(),通过它,我们可以非常方便地创建一个静态资源服务器,例如,通过如下代码就可以将 public 目录下的图片、CSS 文件、JavaScript 文件对外开放访问了:
```
app.use(express.static('路径'))
```
现在,你就可以访问 public 目录中的所有文件了:
http://localhost:3000/images/bg.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/login.js
**注意:**
- Express 在指定的静态目录中查找文件,并对外提供资源的访问路径。因此,存放静态文件的目录名不会出现在 URL 中。
- 如果要托管多个静态资源目录,请多次调用 express.static() 函数。
#### **挂载**路径前缀

### 路由
**路由的使用**
```
router.js
//--------------------------------------------
// 1. 导入 express
const express = require('express')
// 2. 创建路由对象
const router = express.Router()
// 3. 挂载具体的路由
router.get('/user/list', (req, res) => {
res.send('Get user list.')
})
router.post('/user/add', (req, res) => {
res.send('Add new user.')
})
// 4. 向外导出路由对象
module.exports = router
```
**注册路由模块**
```
const express = require('express')
const app = express()
// app.use('/files', express.static('./files'))
// 1. 导入路由模块
const router = require('./03.router')
// 2. 注册路由模块
app.use(router)
//为路由模块添加前缀
app.use('/api', router)
// 注意: app.use() 函数的作用,就是来注册全局中间件
app.listen(80, () => {
console.log('http://127.0.0.1')
})
```
### 参数的获取
1、GET: `req.query`
2、POST:`req.body` *// 默认情况下,如果不配置解析表单数据的中间件,则 req.body 默认等于 undefined*
### 中间件
```
(req, res, next) =>{
... //中间件要做的事情
next()//把控制器交给下一个
}
注意点:
next():一定要写到中间件代码的最后一行;
中间件一定要写在路由代码的前面
```
#### 全局中间件
```
// 定义一个最简单的中间件函数
const mw = function (req, res, next) {
console.log('这是最简单的中间件函数')
// 把流转关系,转交给下一个中间件或路由
next()
}
// 将 mw 注册为全局生效的中间件
app.use(mw)
//-------------------------------------------------------
// 这是定义全局中间件的简化形式
//可以使用 app.use() 连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行
app.use((req, res, next) => {
console.log('这是最简单的中间件函数')
next()
})
app.get('/', (req, res) => {
console.log('调用了 / 这个路由')
res.send('Home page.')
})
app.get('/user', (req, res) => {
console.log('调用了 /user 这个路由')
res.send('User page.')
})
app.listen(80, () => {
console.log('http://127.0.0.1')
})
```
#### 局部中间件
```
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 1. 定义中间件函数
const mw1 = (req, res, next) => {
console.log('调用了局部生效的中间件')
next()
}
// 2. 创建路由
app.get('/', mw1, (req, res) => {
res.send('Home page.')
})
app.get('/user', (req, res) => {
res.send('User page.')
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
```

#### 错误处理中间件
错误级别中间件的 function 处理函数中,必须有 4 个形参,形参顺序从前到后,分别是 (err, req, res, next)。
注意:错误级别的中间件,必须注册在所有路由之后!
```
app.use((err, req, res, next) => {
console.log('发生了错误!' + err.message)
res.send('Error:' + err.message)
})
```
手动抛出错误: `throw new Error('服务器内部发生了错误!')`
捕获错误:
可以捕获异步函数以及同步错误
- ```
try {
//可能错误的代码
} cath(err) {
next(err.message)
}
```
#### **Express内置**的中间件
自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验:
① express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)
② express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
③ express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)

在 express@4.16.0 之前的版本中,经常使用 body-parser 这个第三方中间件,来解析请求体数据。使用步骤如下:
① 运行 npm install body-parser 安装中间件
② 使用 require 导入中间件
③ 调用 app.use() 注册并使用中间件
```
// 1. 导入解析表单数据的中间件 body-parser
const parser = require('body-parser')
// 2. 使用 app.use() 注册中间件
app.use(parser.urlencoded({ extended: false }))
```
#### **自定义中间件**
```
// 配置解析表单数据的中间件,处理POST请求数据
app.use(express.urlencoded({ extended: false }))
直接使用上面的方法即可,下面属于原生的方法
```
**定义中间件**

**监听 req 的** **data** **事件**

**监听 req 的** **end** **事件**

**使用 querystring 模块解析请求体数据**

**将解析出来的数据对象挂载为** **req.body**

**将自定义中间件封装为模块**

### **使用 Express 写接口**
接口.js
```
const express = require('express')
const app = express()
const router = require('./router.js')
app.use('/api', router)
app.listen(80, () => {
console.log('http://127.0.0.1')
})
```
router.js
```
const express = require('express')
const router = express.Router()
router.get('/get', (req, res) => {
// 通过 req.query 获取客户端通过查询字符串,发送到服务器的数据
const query = req.query
// 调用 res.send() 方法,向客户端响应处理的结果
res.send({
status: 0, // 0 表示处理成功,1 表示处理失败
msg: 'GET 请求成功!', // 状态的描述
data: query // 需要响应给客户端的数据
})
})
// 定义 POST 接口
router.post('/post', (req, res) => {
// 通过 req.body 获取请求体中包含的 url-encoded 格式的数据
const body = req.body
// 调用 res.send() 方法,向客户端响应结果
res.send({
status: 0,
msg: 'POST 请求成功!',
data: body
})
})
// 定义 DELETE 接口
router.delete('/delete', (req, res) => {
res.send({
status: 0,
msg: 'DELETE请求成功'
})
})
module.exports = router
```
# 数据库
## **SQL语句**
### 插入数据(insert into)
`INSERT into 表名 (列名1, 列名2) VALUES ('值1', '值2');`

### 删除数据(delete)

### 更新数据(update)

### 查询数据(select)

## SQL 语法
### **WHERE 子句**


### **AND 和 OR 运算符**
```
SELECT * FROM 表名 WHERE status=0 AND id<3
```
AND 和 OR 可在 WHERE 子语句中把两个或多个条件结合起来。
AND 表示必须同时满足多个条件,相当于 JavaScript 中的 && 运算符,例如 if (a !== 10 && a !== 20)
OR 表示只要满足任意一个条件即可,相当于 JavaScript 中的 || 运算符,例如 if(a !== 10 || a !== 20)
### ORDER BY 子句
ORDER BY 语句用于根据指定的列对结果集进行排序。
ORDER BY 语句**默认**按照升序对记录进行排序。
```
SELECT * FROM 表名 ORDER BY 列名;
SELECT * FROM users ORDER BY status ASC;
```
如果您希望按照**降序**对记录进行排序,可以使用 DESC 关键字。
```
SELECT * FROM users ORDER BY id DESC
```
**多重排序**
对 users 表中的数据,先按照 status 字段进行降序排序,再按照 username 的字母顺序,进行升序排序,示例如下:
```
SELECT * FROM users ORDER BY status DESC, username ASC
```
### **COUNT(\*) 函数**

**为列设置别名**
```
SELECT COUNT(*) AS 别名 FROM 表单名, WHERE status=2
```
## **在项目中操作 MySQL**
**安装** **mysql 模块**
```
npm install mysql
```
**配置** **mysql 模块**
```
// 1. 导入 mysql 模块
const mysql = require('mysql')
// 2. 建立与 MySQL 数据库的连接关系
const db = mysql.createPool({
host: '127.0.0.1', // 数据库的 IP 地址
user: 'root', // 登录数据库的账号
password: '79136969', // 登录数据库的密码
database: 'users' // 指定要操作哪个数据库
})
```
**测试 mysql 模块能否正常工作**
```
db.query('select 1', (err, results) => {
// mysql 模块工作期间报错了
if(err) return console.log(err.message)
// 能够成功的执行 SQL 语句
console.log(results)
}) */
案例:
// 查询 users 表中所有的数据
const sqlStr = 'select * from users'
db.query(sqlStr, (err, results) => {
// 查询数据失败
if (err) return console.log(err.message)
// 查询数据成功
// 注意:如果执行的是 select 查询语句,则执行的结果是数组
console.log(results)
})
// 向 users 表中,新增一条数据,其中 username 的值为 Spider-Man,password 的值为 pcc123
const user = { username: 'Spider-Man', password: 'pcc123' }
// 定义待执行的 SQL 语句
const sqlStr = 'insert into users (username, password) values (?, ?)'
// 执行 SQL 语句
db.query(sqlStr, [user.username, user.password], (err, results) => {
// 执行 SQL 语句失败了
if (err) return console.log(err.message)
// 成功了
// 注意:如果执行的是 insert into 插入语句,则 results 是一个对象
// 可以通过 affectedRows 属性,来判断是否插入数据成功
if (results.affectedRows === 1) {
console.log('插入数据成功!')
}
})
```
## **前后端的身份认证**
### **Cookie**
① 自动发送
② 域名独立
③ 过期时限
④ 4KB 限制
由于 Cookie 是存储在浏览器中的,而且**浏览器也提供了读写Cookie 的 API**,因此 **Cookie 很容易被伪造**,不具有安全性。因此不建议服务器将重要的隐私数据,通过 Cookie 的形式发送给浏览器。
注意:**千万不要使用 Cookie 存储重要且隐私的数据**!比如用户的身份信息、密码等。
### **Session** (推荐使用)
Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,**需要做很多额外的配置**,才能实现跨域 Session 认证。
注意:
⚫ 当前端请求后端接口**不存在跨域问题**的时候,**推荐使用 Session** 身份认证机制。
⚫ 当前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用 JWT 认证机制。
#### **在 Express 中使用 Session 认证**
1、**安装** **express-session 中间件**

2、**配置** **express-session 中间件**

3、**向 session 中****存数据**

4、**从 session 中****取数据**

5、 **清空 session**

#### 案例
```
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// TODO_01:请配置 Session 中间件
const session = require('express-session')
app.use(
session({
secret: 'itheima',
resave: false,
saveUninitialized: true
})
)
// 托管静态页面
app.use(express.static('./pages'))
// 解析 POST 提交过来的表单数据
app.use(express.urlencoded({ extended: false }))
// 登录的 API 接口
app.post('/api/login', (req, res) => {
// 判断用户提交的登录信息是否正确
if (req.body.username !== 'admin' || req.body.password !== '000000') {
return res.send({ status: 1, msg: '登录失败' })
}
// TODO_02:请将登录成功后的用户信息,保存到 Session 中
// 注意:只有成功配置了 express-session 这个中间件之后,才能够通过 req 点出来 session 这个属性
req.session.user = req.body // 用户的信息
req.session.islogin = true // 用户的登录状态
res.send({ status: 0, msg: '登录成功' })
})
// 获取用户姓名的接口
app.get('/api/username', (req, res) => {
// TODO_03:请从 Session 中获取用户的名称,响应给客户端
if (!req.session.islogin) {
return res.send({ status: 1, msg: 'fail' })
}
res.send({
status: 0,
msg: 'success',
username: req.session.user.username
})
})
// 退出登录的接口
app.post('/api/logout', (req, res) => {
// TODO_04:清空 Session 信息
req.session.destroy()
res.send({
status: 0,
msg: '退出登录成功'
})
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1:80')
})
```
### **JWT 认证机制**
是目前**最流行**的**跨域认证解决方案**。
JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。
其中:
⚫ **Payload** 部分**才是真正的用户信息**,它是用户信息经过加密之后生成的字符串。
⚫ Header 和 Signature 是**安全性相关**的部分,只是为了保证 Token 的安全性。
客户端收到服务器返回的 JWT 之后,通常会将它储存localStorage 或 sessionStorage 中。此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证。推荐的做法是**把 JWT 放在 HTTP** 请求头的 Authorization 字段中**,格式如下:

#### **在 Express 中使用 JWT**
1、 **安装** **JWT 相关的包**

2、**导入** **JWT 相关的包**

3、**定义 secret 密钥**
为了保证 JWT 字符串的安全性,防止 JWT 字符串在网络传输过程中被别人破解,我们需要专门定义一个用于**加密**和**解密**的 secret 密钥:① 当生成 JWT 字符串的时候,需要使用 secret 密钥对用户的信息进行加密,最终得到加密好的 JWT 字符串② 当把 JWT 字符串解析还原成 JSON 对象的时候,需要使用 secret 密钥进行解密

4、 **在登录成功****后生成 JWT 字符串**

5、**将** **JWT 字符串****还原为** **JSON 对象**
客户端每次在访问那些有权限接口的时候,都需要主动通过**请求头中的 Authorization 字段**,将 Token 字符串发送到服务器进行身份认证。此时,服务器可以通过 **express-jwt** 这个中间件,自动将客户端发送过来的 Token 解析还原成 JSON 对象:

6、 **使用 req.user 获取用户信息**
使用 req.user 获取用户信息**
7、 **捕获解析 JWT 失败后产生的错误**

#### 案例
```
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// TODO_01:安装并导入 JWT 相关的两个包,分别是 jsonwebtoken 和 express-jwt
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
// 允许跨域资源共享
const cors = require('cors')
app.use(cors())
// 解析 post 表单数据的中间件
const bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended: false }))
// TODO_02:定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'itheima No1 ^_^'
// TODO_04:注册将 JWT 字符串解析还原成 JSON 对象的中间件
// 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 req.user 属性上
app.use(expressJWT({ secret: secretKey, algorithms: ['HS256'] }).unless({ path: [/^\/api\//] }))
// 登录接口
app.post('/api/login', function (req, res) {
// 将 req.body 请求体中的数据,转存为 userinfo 常量
const userinfo = req.body
// 登录失败
if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
return res.send({
status: 400,
message: '登录失败!'
})
}
// 登录成功
// TODO_03:在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
// 参数1:用户的信息对象
// 参数2:加密的秘钥
// 参数3:配置对象,可以配置当前 token 的有效期
// 记住:千万不要把密码加密到 token 字符中
const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })
res.send({
status: 200,
message: '登录成功!',
token: tokenStr // 要发送给客户端的 token 字符串
})
})
// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function (req, res) {
// TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
console.log(req.user)
res.send({
status: 200,
message: '获取用户信息成功!',
data: req.user // 要发送给客户端的用户信息
})
})
// TODO_06:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {
// 这次错误是由 token 解析失败导致的
if (err.name === 'UnauthorizedError') {
return res.send({
status: 401,
message: '无效的token'
})
}
res.send({
status: 500,
message: '未知的错误'
})
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(8888, function () {
console.log('Express server running at http://127.0.0.1:8888')
})
```
# 语法
1、`throw`语句用来抛出一个用户自定义的异常
```
function getRectArea(width, height) {
if (isNaN(width) || isNaN(height)) {
throw 'Parameter is not a number!';
}
}
```
2、**`insertAdjacentHTML()`**方法 [`Element`](https://developer.mozilla.org/en-US/docs/Web/API/Element)将指定的文本解析为 HTML 或 XML,并将结果节点插入到 DOM 树的指定位置。
```
element.insertAdjacentHTML(position, text);
position
ADOMString表示相对于 的位置 element;必须是以下字符串之一:
'beforebegin': 在element 自己之前。
'afterbegin': 就在里面 element,在它的第一个孩子之前。
'beforeend': 就在里面 element,在它的最后一个孩子之后。
'afterend': 在element 本身之后。
text
要解析为 HTML 或 XML 并插入到树中的字符串。
```
3、Array().fill()
```
使用Array(10)创建的数组是一个只有长度的数组, 数组中储存的是empty, 不可循环遍历
fill() 方法进行填充时, 如果传入引用类型, 则数组中所有元素指向 同一个对象
语法:arr.fill(value,start,end)
// value: 用来填充数组元素的值
// start: 起始索引,默认值为0
// end: 终止索引,默认值为this.length,如果不填的话,就包括终止索引
注: 1.返回修改后的数组
// 2.如果start为负数,则开始索引被计算为length+start
// 3.如果end为负数,则结束索引被计算为length+end
// 4.start和end参数是可选的,默认值分别为0和arr.length
// 5.当一个对象被传递给fill方法时,填充数组的是这个对象的引用
```
4、document.documentElement.requestFullscreen() *// 原生js调用*
```
实现页面的全屏功能
document.documentElement.requestFullscreen() *// 原生js调用*
插件
npm i screenfull
```
t |