HTML (超文本标记语言)
1. 前端通过什么软件转换成用户眼中的页面的
-
通过浏览器转化(解析和渲染)成用户看到的网页
2. 五大浏览器
-
IE浏览器[内核:Trident]、
-
火狐浏览器(Firefox)[内核:Gecko]
-
谷歌浏览器(Chrome)[内核:Blink]
-
Safari浏览器[内核:Webkit]
-
欧朋浏览器(Opera)[内核:Blink]
3. 为什么需要Web标准?
-
让不同的浏览器按照相同的标准显示结果,让展示的效果统一!
4. Web标准中分成三个构成:
-
HTML(页面元素)
-
CSS(页面样式)
-
JavaScript(页面交互)
5. HTML骨架结构
```html <html> <!--网页的整体--> <heat> <!--网页的头部--> <title>网页的标题</title> <!--网页的标题--> </head> <body> <!--网页的主题内容--> 网页的主题内容 </body> </html> ```
6. HTML的注释
-
-
注释语法
<!--这里面是注释的内容-->
-
注释是让程序员观看的,注释部分不会被浏览器渲染
-
快捷键 ctrl + /
-
-
7. HTML的标签结构
-
单标签
<br>
-
双标签
<strong>两个尖括号之间的文字被加粗</strong>
8. HTML标签与标签之间的关系
-
父子关系(嵌套关系)
<head> <title></title> </head>
-
兄弟关系(并列关系)
<head> </head> <body> </body>
9. 排版标签
9.1 标题标签
-
代码
<h1>1级标题</h1> <h2>2级标题</h2> <h3>3级标题</h3> <h4>4级标题</h4> <h5>5级标题</h5> <h6>6级标题</h6>
-
语义
-
1~6级标题,重要程度依次递减
-
-
特点
-
文字都有加粗
-
文字都变大,并且从h1 → h6文字逐渐减小
-
独占一行
-
-
注意点
-
h1标签对于网页尤为重要,开发中有特定的使用场景,如:新闻的标题、网页的logo部分
-
9.2 段落标签
-
代码
<p>我是第一段文字</p>
-
语义
-
段落
-
特点
-
段落之间存在间隙
-
独占一行
-
9.3 换行标签
-
代码
<br>
-
语义
-
换行
-
特点
-
单标签
-
让文字强制换行
-
9.4 水平线标签
-
代码
<hr>
-
语义
-
主题的分割转换
-
特点
-
单标签
-
在页面中显示一条水平线
-
10. 文本格式化标签
-
代码
<!-- 普通语义 --> <b>加粗</b> <i>斜体</i> <u>下划线</u> <s>删除线</s> <!-- 强调语义 --> <strong>加粗</strong> <em>斜体</em> <ins>下划线</ins> <del>删除线</del>
-
语义
-
突出重要性的强调语境
-
11. 媒体标签
图片标签
路径
音频标签
视频标签
11.1 图片标签
-
代码
<img src="" alt="">
-
属性
-
src="" 路径
-
alt="" 图片失效时显示的文字
-
title="" 鼠标停留在图片上时显示的文字
-
-
特点
-
单标签
-
img标签需要展示对应的效果,需要借助标签的属性进行设置!
-
11.2 路径
-
绝对路径
-
本地路径 - 从盘符开始写到目标位置
-
外地路径 - 完整的网络地址 - 百度一下,你就知道
-
-
相对路径
-
同级目录
<img src="./1.jpg">
-
下级目录
<img src="./images/1.jpg">
-
上级目录
<img src="../images/1.jpg">
-
11.3 音频标签
-
代码
<audio src="" controls></audio>
-
属性
-
scr="" 路径
-
controls 显示播放的控件
-
autoplay 自动播放(部分浏览器不支持)
-
loop 循环播放
-
-
注意
-
音频标签目前支持三种格式:MP3、Wav、Ogg
-
11.4 视频标签
-
代码
<video src="" controls></video>
-
属性
-
src="" 路径
-
controls 显示播放的控件
-
autoplay 自动播放(谷歌浏览器中需配合muted实现静音播放)
-
loop 循环播放
-
-
注意点
-
视频标签目前支持三种格式:MP4 、WebM 、Ogg
-
12. 链接标签
-
代码
<a href="" target="_blank">链接文字</a>
-
属性
-
href="" 跳转地址(路径)
-
target="" 打开方式
-
_blank 新的窗口打开
-
_ self 默认为当前页面打开
-
-
-
特点
-
a标签默认文字有下划线
-
a标签从未点击过,默认文字显示蓝色
-
a标签点击过之后,文字显示为紫色(清除浏览器历史记录可恢复蓝色)
-
-
注意
-
href="#" 开发中不确定该链接最终跳转位置,用空链接占个位置
-
13. 列表标签
无序列表
有序列表
自定义列表
13.1 无序列表
-
代码
<ul> <li></li> </ul>
-
特点
-
列表的每一项前默认显示圆点标识
-
注意
-
ul标签中只允许包含li标签
-
li标签可以包含任意内容
-
13.2 有序列表
-
代码
<ol> <li></li> </ol>
-
特点
-
列表的每一项前默认显示圆点标识
-
注意
-
ol标签中只允许包含li标签
-
li标签可以包含任意内容
-
13.3 自定义列表
-
代码
<dl> <dt></dt> <dd></dd> <dd></dd> </dl>
-
特点
-
dd前会默认显示缩进效果
-
注意
-
dl标签中只允许包含dt/dd标签
-
dt/dd标签可以包含任意内容
-
14. 表格标签
-
代码
<table> <caption>表格的大标题</caption> <!-- 表格的头部部分 --> <thead> <tr> <th></th> <th></th> </tr> </thead> <!-- 表格的身体部分 --> <tbody> <tr> <!-- 跨列合并 --> <td colspan="2"></td> <!-- <td></td> --> </tr> <tr> <!-- 跨行合并 --> <td rowspan="2"></td> <td></td> </tr> <tr> <!-- <td></td> --> <td></td> </tr> </tbody> <!-- 表格的尾部部分 --> <tfoot> <tr> <td></td> <td></td> </tr> </tfoot> </table>
-
属性
-
border="1" 边框为1 - 写在table标签里
-
注意
-
caption标签书写在table标签内部
-
th标签书写在tr标签内部(用于替换td标签)
-
只有同一个结构标签中的单元格才能合并,不能跨结构标签合并(不能跨:thead、tbody、tfoot)
-
合并单元格步骤
-
-
明确合并哪几个单元格
-
-
-
通过左上原则,确定保留谁删除谁 • 上下合并→只保留最上的,删除其他 • 左右合并→只保留最左的,删除其他
-
-
-
给保留的单元格设置:跨行合并(rowspan)或者跨列合并(colspan) • rowspan:跨行合并→垂直方向合并 • colspan:跨列合并→水平方向合并
-
-
-
15 表单标签
input系列标签
button按钮标签
select下拉菜单标签
textarea文本域标签
label标签
15.1 input系列标签
-
代码
<form> <!-- 文本框 --> <input type="text"> <!-- 密码框 --> <input type="password"> <!-- 单选框 想要实现单选,必须name是一样的 --> <!-- 默认选中添加 checked 属性 --> <input type="radio"> <!-- 多选框 --> <!-- 默认选中添加 checked 属性 --> <input type="checkbox"> <!-- 文件选择 --> <input type="file"> <!-- 提交按钮 提交表单域里 收集的信息 --> <input type="submit"> <!-- 重置按钮 重置表单域里 填写的信息 前提 有表单域 --> <input type="reset"> <!-- 普通按钮 默认没功能 --> <input type="button"> </form>
-
属性
-
placeholder 占位符。提示用户输入内容 - - text - password内用
-
checked 默认选中 - - radio - checkbox内用
-
multiple file中用 - - 多文件选择
-
value属性:用户输入的内容,提交之后会发送给后端服务器
-
name属性:当前控件的含义,提交之后可以告诉后端发送过去的数据是什么含义
-
后端接收到数据的格式是:name的属性值 = value的属性值
-
15.2 button按钮
-
代码
<button>我是按钮</button> <button type="submit">提交按钮</button> <button type="reset">重置按钮</button> <button type="button">普通按钮,没有任何作用</button>
-
注意
-
谷歌浏览器中button默认是提交按钮
-
button标签是双标签,更便于包裹其他内容:文字、图片等
-
15.3 select下拉菜单标签
-
代码
<form action=""> 请选择您的家乡: <select> <option>北京</option> <option>上海</option> <option>广州</option> <option>深圳</option> <option>杭州</option> <option selected>石家庄</option> </select> </form>
-
属性
-
selected 下拉菜单默认选中
-
15.4 textarea文本域标签
-
代码
<form> 个人简介: <br><br> <textarea cols="50" rows="10"></textarea> </form>
-
属性
-
cols:规定了文本域内可见宽度
-
rows:规定了文本域内可见行数
-
-
注意
-
右下角可以拖拽改变大小
-
实际开发时针对于样式效果推荐用CSS设置
-
15.5 label标签
-
代码
<form> 性别: <!-- 方法一 利用label标签里的for属性跳转到input标签里id的属性值--> <input type="radio" name="sex" id="nan"><label for="nan">男</label> <!-- 方法二 直接用label标签把整个包含起来--> <label> <input type="radio" name="sex">女</label> </form>
-
使用方法
-
-
使用label标签把内容(如:文本)包裹起来
-
在表单标签上添加id属性
-
在label标签的for属性中设置对应的id属性值
-
-
-
直接使用label标签把内容(如:文本)和表单标签一起包裹起来
-
需要把label标签的for属性删除即可
-
-
16. 语义化标签
-
代码
<div></div> <span></span> <!-- HTML5新增有语义化标签 --> <header>网页头部</header> <nav>网页导航</nav> <footer>网页底部</footer> <aside>网页侧边栏</aside> <section>网页区块</section> <article>网页文章</article>
-
注意
-
div标签:一行只显示一个(独占一行)
-
span标签:一行可以显示多个
-
header:网页头部
-
nav:网页导航
-
footer:网页底部
-
aside:网页侧边栏
-
section:网页区块
-
article:网页文章
-
17. 字体实符
-
常见
-
空格 -
>
大于号 -
<
小于号
-
CSS 层叠样式表
CSS引入方式
基础选择器
字体和文本样式
1. CSS 引入方式
内嵌式
外联式
行内式
1.1 内嵌式
-
代码
<html> <head> <title>Document</title> <style> .div { color: red; } </style> </head> <body> <div>div</div> </body> </html>
-
注意
-
style标签虽然可以写在页面任意位置,但是通常约定写在 head 标签中
-
1.2 外联式
-
代码
<html> <head> <title>Document</title> <link rel="stylesheet" href="./01.css"> </head> <body> <div>div</div> </body> </html>
-
注意
-
CSS 写在一个单独的.css文件中,需要通过link标签在网页中引入
-
1.3 行内式
-
代码
<html> <head> <title>Document</title> </head> <body> <div style="color: red;">div</div> </body> </html>
-
注意
-
CSS 写在标签的style属性中,基础班不推荐使用,之后会配合js使用
-
2. 基础选择器
标签选择器
类选择器
id选择器
通配符选择器
2.1 标签选择器
-
代码
div{ color: green; }
-
作用
-
通过标签名,找到页面中所有这类标签,设置样式
-
-
注意
-
标签选择器选择的是一类标签,而不是单独某一个
-
标签选择器无论嵌套关系有多深,都能找到对应的标签
-
2.2 类选择器
-
代码
.box{ color: green; }
-
作用
-
通过类名,找到页面中所有带有这个类名的标签,设置样式
-
-
注意
-
所有标签上都有class属性,class属性的属性值称为类名(类似于名字)
-
类名可以由数字、字母、下划线、中划线组成,但不能以数字或者中划线开头
-
一个标签可以同时有多个类名,类名之间以空格隔开
-
类名可以重复,一个类选择器可以同时选中多个标签
-
2.3 id选择器
-
代码
#box{ color: green; }
-
作用
-
通过id属性值,找到页面中带有这个id属性值的标签,设置样式
-
-
注意
-
所有标签上都有id属性
-
id属性值类似于身份证号码,在一个页面中是唯一的,不可重复的!
-
一个标签上只能有一个id属性值
-
一个id选择器只能选中一个标签
-
2.4 通配符选择器
-
代码
* { margin: 0; padding: 0; }
-
作用
-
找到页面中所有的标签,设置样式
-
-
注意
-
开发中使用极少,只会在极特殊情况下才会用到
-
一般只用他来为所有标签清除内外边距
-
3. 字体和文本样式
字体样式
文本样式
line-height行高
3.1 字体样式
字体大小:font-size
字体粗细:font-weight
字体样式:font-style
字体类型:font-family
字体类型:font属性连写
3.1.1 字体大小
-
代码
div { font-size: 16px; }
-
取值
-
数字 + px
-
-
注意
-
谷歌浏览器默认文字大小是16px
-
单位需要设置,否则无效
-
3.1.2 字体粗细
-
代码
div { font-weight: 400; }
-
取值
-
关键字
-
正常 normal
-
加粗 bold
-
-
纯数字 - - 100~900 的正百数
-
正常 400
-
加粗 700
-
-
-
注意
-
不是所有字体都提供了九种粗细,因此部分取值页面中无变化
-
实际开发中以:正常、加粗两种取值使用最多
-
3.1.3 字体样式(是否倾斜)
-
代码
div { font-style: normal; }
-
取值
-
正常(默认值):normal
-
倾斜:italic
-
3.1.4 字体系列
-
代码
p { /* sans-serif 非衬线字体系列 */ font-family: '楷体', '黑体', sans-serif; }
-
常见取值
-
具体字体:"Microsoft YaHei"、微软雅黑、黑体、宋体、楷体等……
-
字体系列:sans-serif、serif、monospace等……
-
-
渲染规则
-
从左往右按照顺序查找,如果电脑中未安装该字体,则显示下一个字体
-
如果都不支持,此时会根据操作系统,显示最后字体系列的默认字体
-
-
注意
-
如果字体名称中存在多个单词,推荐使用引号包裹
-
最后一项字体系列不需要引号包裹
-
网页开发时,尽量使用系统常见自带字体,保证不同用户浏览网页都可以正确显示
-
windows系统 默认 - - 微软雅黑
-
macOS系统 默认 - - 苹方
-
3.1.5 font符合属性
-
代码
p { /* 复合写法,可以写五个值,样式、粗细、行高可以省略 文字大小和字体不可以省,中间用空格隔开 */ font: italic 700 30px/1.5 '黑体'; font-style: normal; }
3.2 文本样式
文本缩进:text-indent
文本水平对齐方式:text-align
文本修饰:text-decoration
3.2.1 文本缩进
-
代码
p { text-indent: 2em; }
-
取值
-
数字 + px
-
数字 + em(推荐:1em = 当前标签的font-size的大小)
-
3.2.2 文本水平对齐方式
-
代码
h1 { /* 文本靠左对齐 */ /* text-align: left; */ /* 文本靠中对齐 */ text-align: center; /* 文本靠右对齐 */ /* text-align: right; */ }
-
取值
-
left 左对齐
-
center 居中对齐
-
right 右对齐
-
-
注意
-
如果需要让文本水平居中,text-align属性给文本所在标签(文本的父元素)设置
-
-
总结
-
text-align : center 能让哪些元素水平居中?
-
文本
-
span标签、a标签
-
input标签、img标签
-
-
3.2.3 文本修饰
-
代码
p { /* 添加上划线 */ text-decoration: overline; } span { /* 添加下划线 */ text-decoration: underline; } h2 { /* 添加删除线 */ text-decoration: line-through; } a { /* 去掉下划线 */ text-decoration: none; }
-
取值
-
underline 下划线(常用)
-
line-through 删除线(不常用)
-
overline 上划线(几乎不用)
-
none 无装饰线(常用)
-
-
注意
-
开发中会使用 text-decoration : none ; 清除a标签默认的下划线
-
3.3 行高
-
代码
p { /* 值有px则表示像素单位,值只为数字则表示是字号的数字倍 */ /* 设置行高为30px; */ line-height: 30px; /* 设置行高为字号的2倍 */ /* line-height: 2; */ }
-
取值
-
数字+px
-
倍数(当前标签font-size的倍数)
-
-
应用
-
让单行文本垂直居中可以设置 line-height : 文字父元素高度
-
网页精准布局时,会设置 line-height : 1 可以取消上下间距
-
-
行高与font连写的注意点:
-
如果同时设置了行高和font连写,注意覆盖问题
-
font : style weight size/line-height family ;
-
4. 网页整体部分变为灰色
// 给整个html添加样式 filter: grayscale(100%);
5. TODO
移动 WEB
1. 平面转换 [transform]
1.1 位移 [transform: translate(x, y);]
-
代码
transform: translateX(-200px); transform: translateY(200px); transform: translate(-200px, 200px); transform: translate(100%, 100%);
-
两个值,第一个x轴,第二个y轴,中间用逗号隔开
-
方向的上下左右通过值的正负号来决定,负数(上左),正数(下右)
1.1.1 位移实现盒子水平垂直居中
.son { position: absolute; left: 50%; top: 50%; /* margin-left: -100px; margin-top: -50px; */ /* 百分比: 参考自身尺寸计算 */ transform: translate(-50%, -50%); width: 200px; height: 100px; background-color: pink; }
1.2 旋转 [ transform: rotate(360deg); ]
-
代码
transform: rotate(360deg);
-
角度单位:deg
-
正负值都可以:正值顺时针,负值逆时针
1.2.1 转换原点 [ transform-origin: (left center) ]
-
代码
transform-origin: left center; transform-origin: 50px 100px; /* 使用百分比参考的是自身的宽高 */ transform-origin: 10% 50%;
-
转换原点默认为中心点
-
值用空格隔开,第一个值为x轴,第二个值为y轴
1.3 缩放 [ transform: scale(2); ]
-
代码
/*变大*/ transform: scale(1.6); /* 变小*/ transform: scale(0.6); /*默认*/ transform: scale(1, 1);
-
值为倍数没有单位,第一个为x轴的倍数,第二个为y轴的倍数,
-
缩放是转换的一种方式,能够改变盒子的大小
-
值大于1变大,小于1变小,等于1表示不变
1.4 符合属性写法
-
代码
/* 旋转一定写在最后,因为先写旋转,那么旋转会改变x轴的方向,所以会转圆的同时越来越远 */ transform: translate(600px) scale(2) rotate(360deg);
-
特点
-
transform是符合属性,不能重复使用,会被层叠掉
-
同时有多个状态需要用符合属性写法
-
旋转一定写在最后,因为先写旋转,那么旋转会改变x轴的方向,所以会转圆的同时越来越远
-
2. 渐变 [background-image: linear-gradient(颜色1,颜色2);]
-
代码
background-image: linear-gradient( /*渐变的方向,可以跟度数,正商为0度,顺时针增加度数*/ /*to right,*/ to 135deg, pink,skyblue );
background-image: linear-gradient( /* 向上渐变,由第一个颜色向第二个颜色渐变*/ to top, /*第一个颜色透明度为 1 的黑色*/ rgba(0, 0, 0, 1), /*透明色*/ transparent /*rgba(0, 0, 0, 0)*/ );
-
概念:一个颜色逐渐向另一个颜色发生变化
-
to top: 决定渐变的方向,可以跟方位名词,也可以跟度数,上方的中间为 0 度开始算,顺时针的增加度数
3. 空间转换 [transfrom]
-
空间转换也叫做3D转换,x, y, z 三条坐标构成了一个立体空间,z轴位置与视线方向相同。
1.1 3D-位移
3.1 3D-位移 [ transform: translate3d(x, y, z); ]
-
代码
.box:hover { /* translate3d不可以简写成一个值,必须写三个 */ /* transform: translate3d(100px); -- 错误的写法 */ /* 中间用逗号隔开 第一个值x轴移动距离 第二个值y轴移动距离 第三个值z轴移动距离*/ /* transform: translate3d(100px, 0, 0); */ /* transform: translate3d(100px, 100px, 0); */ /* z轴移动的距离不加透视效果是看不出来移动的,要给他的父亲加透视 */ transform: translate3d(100px, 100px, 100px); transform: translateX(100px) translateY(100px) translateZ(100px); }
-
注意点:
-
不可以简写成一个值,必须写三个值
-
中间用逗号隔开
-
第一个值为x轴移动距离,第二个值为y轴移动距离,第三个为z轴的移动距离(不加透视效果显示不出来z轴的移动)
-
正负值均可,值可以取单位数值,可以取百分比
-
3.2 透视效果 [perspective]
-
!!!需要将该属性添加给父盒子!!!
-
通过 perspective 可以增加立体的效果,让人产生 3D 的感觉
-
如果对 z 轴有操作,一定要加perspective,添加上这个属性才可以显示出立体效果
-
效果取决于后面的值,可以改变视觉的强和弱,建议值(800 ~ 1200),值小效果强,值大效果弱
3.3 3D-旋转
-
代码
/*空间旋转-X轴*/ transform: rotateX(90deg); /*空间旋转-Y轴*/ transform: rotateY(365deg); /*空间旋转-Z轴*/ transform: rotateZ(360deg);
-
旋转的方向,f左手法则,大拇指指向正值方向
3.4 立体呈现 [ transform-style: preserve-3d; ]
-
代码
transform-style: preserve-3d;
-
注意
-
想让盒子叠着的两个盒子分开就给他的父级加这个属
-
空间内,转换元素都有自己独立的坐标互不影响
-
4. 动画 [ animation ]
4.1 体验动画
-
先定义在调用,代码如下:
/* 一. 定义动画:从200变大到600 */ @keyframes move { from { width: 200px; } to { width: 600px; } } /* 二. 调用动画:anmiation 调用*/ .box { animation: move 2s 3; }
-
定义
@keyframes move { /*使用百分比时,他是时间总长的百分比*/ 10% { width: 200px; } 30% { width: 300px; } 50% { width: 400px; } } .box { width: 200px; height: 100px; background-color: pink; animation: move 4s; }
-
分比,10% 是在4s的百分之十开始变化成第一帧的样子; 30% 变化到时间的百分之三十的时候结束第一帧开始第二帧;50% 变化到时间的百分之五十结束第二帧,开始第三帧到结束
-
4.2 复合属性
-
属性
-
animation: 动画名称 动画时长 速度曲线 延迟时间 重复次数 动画方向 执行完毕时的状态;
-
-
值
-
速度曲线:linear 匀速
-
延迟时间:3s 3秒后开始,第二个时间为延迟时间
-
重复次数:infinite 无限次数 (3 数字没有时间单位)
-
动画方向:alternate 反向也有动画
-
播放完毕时的状态:forwards 动画结束时保留最后一帧的样式
-
-
注意
-
动画名称和动画时长必须赋值
-
取值不分先后
-
如果有两个时间值,第一个表示动画时长,第二个表示延迟时间
-
4.3 拆分写法
-
代码
.box { width: 200px; height: 100px; background-color: pink; /* 动画的名称 */ animation: change; /* 动画时长 */ animation-duration: 1s; /* 执行的次数 */ animation-iteration-count: infinite; /* 延迟时间 这个值是写在第二个的 第一个值是动画的运行时间*/ animation-delay: 2s; /* 动画完毕时的状态 frowards:最后一帧状态 backwards:第一帧的状态 */ animation-fill-mode: forwards; /* 动画执行的方向 */ animation-direction: altemate; /*为反向8? /* 速度曲线 */ /* animation-timing-function: linear; */ animation-timing-function: steps(3); /* 暂停动画 paused为暂停,通常配合:hover使用*/ animation-play-state:paused; } @keyframes change { from { width: 200px; } to { width: 600px; } }
5. 视口 [ viewport ]
-
概念:用来显示网页内容的区域(视口在【移动设备】上不等于浏览器窗口)
-
解决移动页面的首要问题是处理视口与浏览器窗口大小的关系
-
写移动端页面需要将视口的宽度调整至留恋其窗口的大小一致!!!
-
通过meat来调整视口的大小,目的是让视口和设备宽度的大小一致
-
方法一:让视口的宽度等于设备宽度
/*让视口和设备宽度的大小一致*/ <meta name="viewport" content="width=device-width">
-
方法二:让视口的宽度和设备的宽度是 1 : 1
/*调整视口缩放比例 让设备宽度和视口宽度一样*/ <meta name="viewport" content="initial-scale=1">
-
方法三(推荐):前两种方法的合体,为了兼容性问题,适配横竖屏,和更多机型
/*前两种方法的合体,为了兼容性问题,适配横竖屏,和更多机型*/ <meta name="viewport" content="width=device-width, initial-scale=1.0">
-
-
总结
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dY09hRr3-1649576773954)(\images\1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iN6s72GM-1649576773960)(\images\2.png)]
6. 二倍图
-
理论:图片是按照物理分辨率来计算的,CSS写样式是按逻辑分辨率写的。
-
作用:照片比照片的本身大两倍(避免模糊)
7. Flex布局/弹性布局
-
适用于来布局手机网页的一个新的方案
-
不脱标
-
要使用Flex,必须有父子关系,给父亲添加display: Flex;
-
父级添加了Flex布局,子元素行内样式可以直接设置宽高,不用转换显示模式
-
父元素设置了felx布局属性,子盒子的folat,clear,vertical-align会失效
7.1 组成部分
-
弹性容器(父级)
-
弹性盒子(子级)
-
主轴(水平方向)
-
侧轴/交叉轴 (垂直方向)
-
7.2 调节主轴/水平对齐方式 [ justify-content ]
7.2.1 主轴靠左 [ justify-content: flex-start; ]
-
代码
/* 设置给父级 */ display: flex; /* 默认值 */ justify-content: flex-start;
7.2.2 主轴靠右 [ justify-content: flex-end; ]
-
代码
/* 设置给父级 */ display: flex; justify-content: flex-end;
7.2.3 主轴靠中间 [ justify-content: center; ]
-
代码
/* 设置给父级 */ display: flex; justify-content: center;
7.2.4 主轴对齐-两端对齐 [ justify-content: space-between; ]
-
代码
/* 设置给父级 */ display: flex; justify-content: space-between;
7.2.5 主轴对齐-环绕对齐 [ justify-content: space-around; ]
-
代码
/* 设置给父级 */ display: flex; justify-content: space-around;
7.2.6 主轴对齐-等距离环绕对齐 [ justify-content: space-evenly; ]
-
代码
/* 设置给父级 */ display: flex; justify-content: space-evenly;
7.3 主轴的方向 [ flex-direction ]
-
默认情况水平是主轴,垂直为侧轴
-
修改垂直为主轴时:在使用 jusity-content 时,调整的为垂直的对齐
-
主轴修改为垂直时,侧轴变为水平
-
代码
/*默认主轴为行*/ /* flex-direction: row;*/ /*改变主轴为列*/ flex-direction: column; /* 主轴(垂直方向)两端对齐 */ justify-content: space-between; /* 主轴(垂直方向)环绕 */ justify-content: space-around; /* 主轴(垂直方向)等距离环绕 */ justify-content: space-evenly; /* 此时在使用侧轴时调整的是水平方向的对齐 */ /* 侧轴(水平方向)对齐方式为居中 */ align-items: center; /* 默认为拉伸,前提是子盒子没宽,主轴(水平方向)拉伸,因为主轴方向已经调整 */ align-items: stretch;
7.4 Flex 弹性盒子换行
-
不缩放盒子,强制(碰到父亲盒子的边界)换行
-
如果使用 flex-wrap: wrap; 将盒子换行后可以针对每一行进行布局处理
-
align-content 的取值与 justify-conter 一致
-
代码
display: flex; /* 不收缩盒子,遇到边界强制换行 */ flex-wrap: wrap; /* 如果使用 flex-wrap: wrap; 将盒子换行后,可以针对每一行进行布局 */ /* align-content 的取值与 justify-content 一直 */ align-items: flex-start; align-items: flex-end; align-items: center; align-items: space-between; align-items: space-around; /* 注意:没有 align-items: space-evenly; */
7.5 单独控制侧轴对齐
-
通过align-self 可以单独控制某个盒子在【侧轴】的对齐
.box { display: flex; align-items: center; width: 600px; height: 400px; border: 1px solid #ccc; } /*让第二个子盒子单独侧轴靠上*/ .items:nth-child(2) { align-self: flex-start; }
7.6 调节垂直/侧轴对齐方式 [ align-items ]
7.6.1 子盒子拉伸 [ align-items: stretch; ]
-
代码
/* 设置给父级 */ display: flex; /* 默认值 子盒子没有高度的时候会拉伸到父盒子的高度*/ align-items: stretch;
7.6.2 子盒子靠顶部 [ align-items: flex-start; ]
-
代码
/* 设置给父级 */ display: flex; align-items: flex-start;
7.6.3 子盒子靠底部 [ align-items: flex-end; ]
-
代码
/* 设置给父级 */ display: flex; align-items: flex-end;
7.6.4 子盒子靠中间 [ align-items: center; ]
-
代码
/* 设置给父级 */ display: flex; align-items: center;
7.6.5利用flex布局让块盒子居中
-
代码
.box { width: 600px; height: 400px; border: 1px solid green; /* 1.给父盒子添加模式转换为Flex布局 */ display: flex; } .item { /* 2.子盒子margin设置为auto,可实现垂直水平居中 */ margin: auto; width: 100px; height: 100px; background-color: skyblue; }
7.7 伸缩比例
-
flex属性是设置给子盒子的,父盒子要设置Flex布局
-
子设置伸缩比例时,比例关系是【分配】其父盒子剩余的空间
-
代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Flex 伸缩比例</title> <style> * { margin: 0; padding: 0; } .box { display: flex; width: 600px; height: 300px; margin: 100px auto; border: 1px solid #ccc; } .box .item { width: 150px; height: 100px; text-align: center; line-height: 100px; background-color: pink; } .box .item:nth-child(2) { /* box 的第二个人子盒子占剩余空间的 2/3 */ flex: 2; } .box:last-child { /* box 的第三个人子盒子占剩余空间的 1/3 */ flex: 1; } </style> </head> <body> <div class="box"> <div class="item">1</div> <div class="item">2</div> <div class="item">3</div> </div> </body> </html>
7.8 知识点
宽度不写的情况下的默认设置是 auto;宽度为自动时,其大小为父盒子的宽度 - 子盒子的内外边距和边框。
快盒子的高度是被内容撑开的;给html设置高度为100%,那么就会得到一个和视口一样大的盒子;给body设置高度为100%,那么body会和html高度大小一样。
背景图默认会平铺,值为两个(x轴 y轴);也可以为方位名词,left的含义是让背景图的左侧边缘和盒子的左侧边缘重叠,其他方位名词同理。
pc端宽度一般都是给个固定版心,移动端宽度多数是100%
分辨率:指的是水平和垂直方向的像素
物理分辨率:出场设置的物理分辨率
逻辑分辨率:通过软件缩放后显示的分辨率
7.9 主轴的方向 [ flex-direction ]
-
默认情况水平是主轴,垂直为侧轴
-
修改垂直为主轴时:在使用 jusity-content 时,调整的为垂直的对齐
-
主轴修改为垂直时,侧轴变为水平
-
代码
/*默认主轴为行*/ /* flex-direction: row;*/ /*改变主轴为列*/ flex-direction: column; /* 主轴(垂直方向)两端对齐 */ justify-content: space-between; /* 主轴(垂直方向)环绕 */ justify-content: space-around; /* 主轴(垂直方向)等距离环绕 */ justify-content: space-evenly; /* 此时在使用侧轴时调整的是水平方向的对齐 */ /* 侧轴(水平方向)对齐方式为居中 */ align-items: center; /* 默认为拉伸,前提是子盒子没宽,主轴(水平方向)拉伸,因为主轴方向已经调整 */ align-items: stretch;
7.10 Flex 弹性盒子换行
-
不缩放盒子,强制(碰到父亲盒子的边界)换行
-
如果使用 flex-wrap: wrap; 将盒子换行后可以针对每一行进行布局处理
-
align-content 的取值与 justify-conter 一致
-
代码
display: flex; /* 不收缩盒子,遇到边界强制换行 */ flex-wrap: wrap; /* 如果使用 flex-wrap: wrap; 将盒子换行后,可以针对每一行进行布局 */ /* align-content 的取值与 justify-content 一直 */ align-items: flex-start; align-items: flex-end; align-items: center; align-items: space-between; align-items: space-around; /* 注意:没有 align-items: space-evenly; */
7.11 单独控制侧轴对齐
-
通过align-self 可以单独控制某个盒子在【侧轴】的对齐
.box { display: flex; align-items: center; width: 600px; height: 400px; border: 1px solid #ccc; } /*让第二个子盒子单独侧轴靠上*/ .items:nth-child(2) { align-self: flex-start; }
8. margin负值
-
子盒子的margin取负值,可以把父盒子的padding值给抵消掉,如果超出父盒子就不会撑大盒子但是子盒子会变大
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .box { margin: 0 auto; width: 600px; height: 100px; padding: 0 20px; box-sizing: border-box; background-color: blue; } .xbox { margin-left: -20px; margin-right: -40px; height: 80px; background-color: pink; } </style> </head> <body> <div class="box"> <div class="xbox"></div> </div> </body> </html>
9. rem
9.1 rem体验
-
rem 是一个单位
-
rem 长度是不固定,px单位是固定的,百分比或者flex布局只能保证宽度自适应高度不可以
-
rem 写法(原理)
-
设置HTML标签字号
-
设置盒子的尺寸是rem单位
-
/* root */ 根标签 <html>
-
1rem = 当前 根标签[html] 的字号大小
-
-
-
只要 html 字号大小发生改变,那么 rem 也会跟着变化
9.2 媒体查询
-
媒体查询相当于是一个条件判断
<style> /* @media (具体条件) { 执行的 css } */ @media (width: 375px) { html{ background-color: pink; } } @media (width: 414px) { html { background-color: green; } } /* 大于375像素的时候 背景颜色改为pink */ @media (min-width: 375px) { html{ background-color: pink; } } /* 大于414像素的时候 背景颜色改为green */ @media (min-width: 414px) { html { background-color: green; } } /* 使用min-width的时候 要【从小到大】顺序书写,不然会产生覆盖问题 */ </style>
-
注意代码顺序带来的影响
-
使用min-width 的时候,切记书写顺序要【从小到大】
-
9.3 rem 适配
-
rem + 媒体查询 就可以实现移动端的适配了
-
rem + flexble.js 就可以实现移动端的适配了
9.3.1核心内容
```css @media (width: 320px) { html { font-size: 32px; } } @media (width: 375px) { html { font-size: 37.5px; } } @media (width: 414px) { html { font-size: 41.4px; } } ```
-
在使用rem来适配网页布局的时候,有两个选择,分别为 媒体查询、JavaScript
-
但是 JavaScript会【更灵活】能够保证任意尺寸的改变都会设置新的 html 字号
-
媒体查询 和 JavaScript 实现原理一样
9.3.2 过程原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4uKUXe2p-1649576773963)(C:\Users\Hi\AppData\Roaming\Typora\typora-user-images\image-20220312120044883.png)]
9.3.3 结论
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DMjswHxn-1649576773965)(.\images\rem-2.png)]
-
元素实际大小 / (设计稿宽度 / 10)
9.4 Less语法
-
需要配合vscode插件使用
-
插件名称:Easy LESS
-
9.4.1 体验less
-
结论:
-
less 预处理器,他能让CSS更容易被管理!!!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D2ea2pkb-1649576773967)(.\images\less.png)]
-
less是不可以被浏览器识别的,必须要将less转成css
-
安装vscode的插件【Easy LESS】,保存就自动生成css文件按了
-
9.4.2 less注释
-
CSS 的注释在 LESS 依然可以使用
-
/*
注释内容,一般用于【多行的注释】*/
-
//
注释的内容,一般用于【单行注释】
-
// 注释的文字 /* 多行注释 多行注释 多行注释 */
9.4.3 计算
-
less 能够很好的对 CSS 进行管理,其中的一个用法就是能够进行数学运算。
-
运算符号
-
加 +
-
减 -
-
乘 *
-
除 ( / )
-
-
由于CSS的语法本身就包含 / 所以出号必须【用括号括起来】
-
如果表达式有多个单位, 以第一个单位为准
-
-
less输入以下代码
.box { // 加 width: 100rem + 50px; // 减 height: 100px - 50px; // 乘 margin: 10px * 2px; // 除 padding: (20px / 5px); }
-
保存后生成以下css文件,代码如下
.box { width: 150rem; height: 50px; margin: 20px; padding: 4px; }
9.4.4 变量
-
变量就是一个代号
-
如何定义一个代号
-
@代号名:所代表的内容;
// 变量就是一个【代号】 // 如何定义一个代号? // 使用 @代号名:所代表的内容 // @name: 小明; // 使用 name 来代表小名,我们将 @name 称为变量 @color: green; .box { background-color: @color; } .navs a { color: @color; }
-
9.4.5 实现px转换rem
less 代码如下:
// 设计稿尺寸为 375px 除以 10 // 根据设计稿进行转换 rem 时需要除的数字 @rootSize: (375 / 10); // 假如设计稿有一个盒子的宽度为 68px 高度为 29px 外边距为 15px .box { width: (68rem / @rootSize); height: (29rem / @rootSize); margin: (15rem / @rootSize); }
保存转换后的 CSS 代码:
.box { width: 1.81333333rem; height: 0.77333333rem; margin: 0.4rem; }
9.4.6 嵌套
-
在嵌套中可以使用特殊符号 & 来代表【当前选择器】
less 代码如下:
.box { width: 200px; height: 200px; .nav { height: 100%; box-sizing: border-box; &-left { background-color: skyblue; } a { font-size: 15px; &:hover { color: red; } } } }
保存转换后的 CSS 代码:
.box { width: 200px; height: 200px; } .box .nav { height: 100%; box-sizing: border-box; } .box .nav-left { background-color: skyblue; } .box .nav a { font-size: 15px; } .box .nav a:hover { color: red; }
9.4.7 导入
-
导入从另一个 .less 文件中将代码加载(合并)进来
-
语法格式: @import '路径'
-
不要使用如下方式来导入!!!!
-
@import url()
-
这是CSS的标准代码,但是很少使用(性能不好)
-
-
正确书写如下
less 代码如下:
@import './01-体验less.less'; @import './02-注释.less';
保存转换后的 CSS 代码:
.father { width: 150px; width: 1.81333333rem; } /* 块 / 多行 注释 */ /* 多行注释 多行注释 多行注释 */
9.4.8 导出 (不是Less,而是【Easy LESS】插件提供的)
-
导出指的时将 .less 转换成一个 .css
-
这个要写在最顶端,而且要注释起来
-
默认情况 Easy LESS 在文件保存的时候就自动导出了,导出名与原来一样就变了后缀,导出目录路径跟当前是一样的
-
我们可以设置导出css 的名称、路径,只需要在【文件最顶端】写一个【注释】
-
语法
out: .css 文件路径及文件
// out: ./css/out.css @import './03-计算.less';
9.4.9 禁止导出(不是Less,而是【Easy LESS】插件提供的)
-
第一行添加一行注释内容
// out: false, main: index.less
-
保存不导出,但是会导出index.css
10. 关于位置伪类选择器
-
交集选择器
-
即满足标签名是第一个,也满足第二个
-
div.box {}
-
a:hover {}
-
li:hover {}
-
p:hover {}
-
-
-
li:first-child {}
-
即满足是自己是 li 标签
-
也要满足他是父元素的第一个元素
-
<html lang="en"> <head> <title>Document</title> <style> /* 这是错误的,这样改变不了 .box b:first-child { color: red; } */ /* 这是对的,即是 b 标签,又是box盒子里的第三个元素标签 */ .box b:nth-child(3) { color: red; } </style> </head> <body> <div class="box"> <i>wo</i> <em>ni</em> <b>ta</b> </div> </body> </html>
11. 粘性定位 [ position: sticky; ]
-
可以理解为 相对定位 + 固定定位
-
到达top的距离订在那
<style> .box { position: relative; /* width: 200px; */ height: 2000px; } .xbox { position: sticky; top: 0px; /* width: 100%; */ height: 50px; background-color: skyblue; } </style>
12. vw / vh (新的也是用来做适配移动端)
-
相对视口计算结果,他是一个单位长度
-
他是视口宽度或者高度的百分之一
-
根据视口的宽度来决定vw和vh的大小
-
vw: viewport width
-
1vw = 1/100 视口宽度
-
-
vw: viewport height
-
1vh = 1/100 视口高度
-
-
-
总结
-
通过 vw 或 vh 可以实现同 rem 一样的效果,屏幕大了,网页内容就会大一些,屏幕小了内容就会变小
-
但是也是有区别的,使用 rem 人为的将设计稿分为 10 份,使用 vw 或 vh 则是有官方分成了 100 份
-
建议都用 vw 来做,因为高度设备可能不一样,vw和vh可以共用,但是这样会很麻烦
-
-
适配方案
-
使用 vw 进行屏幕适配不需要动态调整字号
-
不需要使用媒体查询
-
不使用 flexbile.js
-
-
只需要让【设计稿中的元素尺寸 / 视口的 1%】
-
13. 媒体查询 [ @media () {} ]
-
通过媒查询能够实现屏幕尺寸不同区间,执行不同的样式,在执行样式过程中会产生层叠。
-
实现响应式的核心
-
提到响应式布局,第一要想到的是媒体查询
-
在开发响应式的时候,媒体查询条件不可以是一个固定的宽度,应当是一个范围
-
代码
/* 媒体查询的语法 @media (条件) { 执行的 css } */ /* 当屏幕的宽度大于等于1000时,表示的是一个范围 */ @media (max-width: 1000px) { body { background-color: skyblue; } } /* 当屏幕的宽度小于等于900时,表示的是一个范围 */ @media (max-width: 900px) { body { background-color: pink; } }
13.1 媒体查询-书写顺序
-
【 min-width 和 max-width 不可以混着用 】
-
语法上不是说不能混着用,只是容易混乱
-
-
在使用媒体查询表示一个范围时要考虑层叠性
-
书写顺序
-
min-width: 从小往大写
-
条件(小于等于的时候)
-
-
max-width: 从大往小写
-
条件(大于等于的时候)
-
-
-
在实际使用中遵行书写顺序的规则,无论使用 min-width 还是 max-width 都可以表示区间,一定要保证顺序,不可以两者混着使用
-
扩充点 优先使用
-
移动(WEB) 优先
-
优先使用 min-width
-
因为默认情况下是 WEB 样式
-
-
PC(WEB) 优先
-
优先使用 max-width
-
因为默认情况下是 PC 样式
-
-
13.2 媒体查询-link
-
屏幕宽度满足设定的区间时才会调用对应的样式,可以调用多个,注意 link 引入的顺序
-
代码
<head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <!-- 注意书写顺序 --> <link rel="stylesheet" href="./two.css" media="(min-width: 768px)"> <link rel="stylesheet" href="./one.css" media="(min-width: 1200px)"> <title>媒体查询</title> </head>
13.3 媒体查询关键字
13.3.1 简写
/* 简写 */ /* @media () { } */
13.3.2 带屏幕 并且
/* 当浏览器在处于【带屏幕】的设备中运行网页 */ /* and 翻译成是【并且】的意思 */ @media screen and (min-width: 768px) { } /* 【不建议这样写】可以这么写但是容易混乱 */ @media (min-width: 768px) and (max-width: 1200px) { }
13.3.3 仅仅[only] 带屏幕[screen] 并且
/* only 是【仅仅的】意思 */ /* 【仅仅】是在【带屏幕】的设备上运行网页 */ /* 【并且】屏幕宽度大于等于768px */ @media only screen and (min-width: 768px) { }
13.3.4 除了[not] 带屏幕[screen] 并且[and]
/* not 是【除了】意思 */ /* 【除了】是在【带屏幕】的设备上运行网页 */ /* 【并且】屏幕宽度大于等于768px */ @media not screen and (min-width: 768px) { }
13.3.5 屏幕方向[landscape/portrait]
/* 当屏幕的方向处于横屏时 */ @media (orientation: landscape) { body { background-color: pink; } } /* 当屏幕的方向处于竖屏时 */ @media (orientation: portrait) { body { background-color: green; } }
14. BootStrap [ 框架 ]
-
可以定制更改其中的部分内容,然后重新下载即可。(可更改视口宽度为多少时隐藏)
-
样式框架:别人写好的代码,拿过来可以直接用,通过它可以少些样式,就能开发漂亮的网页
-
功能类别是有区别的
-
有的框架是针对样式的
-
BootStrap 等
-
-
有的框架是针对逻辑的
-
Vue 等
-
-
-
操作步骤
-
先引入 bootstrap 的样式代码
-
切记路径问题
-
-
给 html 标签添加【对应的】类名
-
所谓对应的类名不能乱写,必须是 bootstrap 中定义好的
-
-
保证以上两点, bootstrap 即可正常工作
<head> <!-- 1. 引入 bootstrap 的代码 --> <link rel="stylesheet" href="./bootstrap-3.4.1-dist/css/bootstrap.css"> </head> <body> <!-- 没有写一行样式,那么表格就可以很美观的展示了1 --> <table class="table table-bordered table-striped table-hover"> </table> </body>
-
14.1 全局样式-按钮
一定要先引入框架CSS
14.1.1 样式
<!-- 要先引入框架的 CSS 样式 --> <a type="button" class="btn btn-default">(默认样式)Default</a> <button type="button" class="btn btn-primary">(首选项)Primary</button> <input type="button" value="(成功)Success" class="btn btn-success"> <button type="button" class="btn btn-info">(一般信息)Info</button> <button type="button" class="btn btn-warning">(警告)Warning</button> <button type="button" class="btn btn-danger">(危险)Danger</button> <button type="button" class="btn btn-link">(链接)Link</button>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rdvX1Mc8-1649576773969)(./images/btn-01.png)]
14.1.2 大小
<p> <button type="button" class="btn btn-primary btn-lg">(大按钮)Large button</button> <button type="button" class="btn btn-default btn-lg">(大按钮)Large button</button> </p> <p> <button type="button" class="btn btn-primary">(默认尺寸)Default button</button> <button type="button" class="btn btn-default">(默认尺寸)Default button</button> </p> <p> <button type="button" class="btn btn-primary btn-sm">(小按钮)Small button</button> <button type="button" class="btn btn-default btn-sm">(小按钮)Small button</button> </p> <p> <button type="button" class="btn btn-primary btn-xs">(超小尺寸)Extra small button</button> <button type="button" class="btn btn-default btn-xs">(超小尺寸)Extra small button</button> </p>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wvto0IMc-1649576773970)(./images/btn-02.png)]
14.1.3 拉伸至父元素100%的宽度
<button type="button" class="btn btn-primary btn-lg btn-block">(块级元素)Block level button</button> <button type="button" class="btn btn-default btn-lg btn-block">(块级元素)Block level button</button>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0z4NgvR4-1649576773971)(./images/btn-03.png)]
14.1.4 激活状态、 禁用状态
<!--激活状态--> <button type="button" class="btn btn-primary btn-lg active">Primary button</button> <button type="button" class="btn btn-default btn-lg active">Button</button> <!--禁用状态--> <a href="#" class="btn btn-primary btn-lg active" role="button">Primary link</a> <a href="#" class="btn btn-default btn-lg active" role="button">Link</a>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2yiiaB4L-1649576773973)(./images/btn-04.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SE3MEjZR-1649576773974)(./images/btn-05.png)]
14.2 全局样式-表格
-
具体参考中文文档
-
条纹状表格
-
table-striped
-
-
带边框的表格
-
table-bordered
-
-
鼠标悬停
-
table-hover
-
-
紧缩表格
-
table-condensed
-
-
状态类 - (设置背景色,单元格或一行)
-
active
-
鼠标悬停在行或单元格上时所设置的颜色
-
-
success
-
标识成功或积极的动作
-
-
info
-
标识普通的提示信息或动作
-
-
warning
-
标识警告或需要用户注意
-
-
danger
-
标识危险或潜在的带来负面影响的动作
-
-
14.3 全局样式-表单
-
具体参考中文文档
-
示例-代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>全局CSS样式-表单</title> <link rel="stylesheet" href="./bootstrap-3.4.1-dist/css/bootstrap.min.css"> </head> <body> <div class="container"> <h4>学习使用 bootstrap </h4> <!-- 行 row 可以省略... --> <div class="form-group"> <label for="">用户名:</label> <input type="text" class="form-control"> </div> <div class="form-group"> <label for="">密码:</label> <input type="password" class="form-control"> </div> <div class="form-group"> <label for="">性别:</label> <input type="radio"> 男 <input type="radio"> 女 </div> </div> </body> </html>
-
结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ahu0ewZy-1649576773976)(./images/BiaoDan-01.png)]
-
14.4 栅格系统
-
所谓的栅格指的是将网页人为分为一个个小格子,然后在小格子添加内容
-
bootstrap 默认支持栅格,
-
row 是 bootstrap 提供的类名,用来定义【行】(也可以省略)
-
col-xx-xx 也是 bootstrap 提供的类名,用于定义【列】
-
bootstrap 将每个行分成了均等的 12 份,col-sm-4 表示占 4 等份
-
-
<div class="row"> <!-- bootstrap 将每个行分成了均等的 12 份,col-sm-4 表示占 4 等份 --> <div class="col-sm-4">第1列</div> <div class="col-sm-4">第2列</div> <div class="col-sm-4">第3列</div> </div> <div class="row"> <div class="col-sm-6">第1列</div> <div class="col-sm-6">第2列</div> </div>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KHaDF1ID-1649576773977)(./images/ShanGe-01.png)]
-
bootstrap,它是支持【响应式】的栅格
-
不同屏幕尺寸,列的布局是不一样的
-
container 这个类名在不同屏幕尺寸其大小是一样的
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BUM3PTXX-1649576773978)(./images/ShanGe-02.png)]
-
col-xs- 对应一个尺寸<768px
-
col-sm- 对应一个尺寸≥768px
-
col-md- 对应一个尺寸≥992px
-
col-lg- 对应一个尺寸≥1200px
-
-
例子-代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>栅格系统-类</title> <link rel="stylesheet" href="./bootstrap-3.4.1-dist/css/bootstrap.css" /> <style> .conrainer > div { height: 200px; background-color: pink; } </style> </head> <body> <!-- 响应式时就加上 container --> <div class="conrainer"> <!-- 行 row 是可以省略的。。。 --> <!-- 大屏幕时一行显示 4 个,每个占 3 份 --> <!-- 0 ~ 768(col-xs-xx), 768 ~ 992(col-sm-xx), 992 ~ 1200(col-md-xx), 1200 ~ 无限大(col-lg-xx) --> <!-- 视口: 0 ~ 768(col-xs-12) div占12份 默认 视口: 768 ~ 992(col-sm-6) div占6份 一行显示两个 视口: 992 ~ 1200(col-md-3) div占3份 一行显示四个 视口: 1200 ~ 无限大(col-lg-3) div占3份 一行显示四个 --> <div class="col-sm-12 col-sm-6 col-md-3 col-lg-3">1</div> <div class="col-sm-12 col-sm-6 col-md-3 col-lg-3">2</div> <div class="col-sm-12 col-sm-6 col-md-3 col-lg-3">3</div> <div class="col-sm-12 col-sm-6 col-md-3 col-lg-3">4</div> </div> </body> </html>
14.5 组件
-
组件就是具有【实际功能】意义的区块
-
具体参考中文文档
-
参考文档,需要哪里 copy 哪里,样式不一样的时候,自己写覆盖他的样式
14.6 图标
14.7 插件
-
使用步骤
-
引入css样式
-
引入js文件: jquery.js 和 bootstrap.js
-
jquery.js 引入要在bootstrap.js前面
-
-
-
不引入 js 文件功能无法生效!!!
JavaScript
JavaScript 基础
JavaScript 语法
1. 输出语句
document.write('输出到body的内容文字'); alert('警告窗输出的内容文字'); console.log('控制台输出的内容文字');
2. 输入语句
prompt('请输入您的姓名:');
3. 变量声明
// ES5 中的声明变量 var var cpu; // ES6 let 不可以重复声明一个变量名 let cup; let age = 18; let name = '小明',age = 18,gender = '男';
4. 数组
let arr = ['张三', '李四', '王五', '赵六']; console.log(arr[2]) // '王五'
5. 常量
// 1. 初始化的时候必须赋值 // 2. 常量的值是不可改变的 const author = 'itcast'
6. 数据类型
// 1. 基本数据类型 // 1.1 数字型 Number let age = 18 let price = 99.99 let num = -50 console.log(typeof age) // number // 1.2 字符串型 String let name = '张三' // 推荐使用单引号 let gender = "男" let stt = `反引号` console.log(typeof name) // string // 1.3 布尔类型 Boolean let yes = true let no = false console.log(typeof name) // boolean // 1.4 未定义型 undefined let zxc console.log(typeof zxc) // undefined let qqq = undefined console.log(typeof qqq) // undefined // 1.5 空类型 null // 原型链的顶端 为 null // 初始成 null let n = null // 现在开发中一般不推荐使用 null // 检测数据类型,使用 typeof console.log(typeof n) // object // 2. 引用数据类型 // 3. 检测数据类型 typeof
7. 模板字符串
// document.write() 会识别标签 // 字符串拼接 let school1 = '传智' let school2 = '黑马' let html1 = '<a href="#">去' + school1 + '学习吧</a>' document.write(html1) // 去传智学习吧 console.log(html1) // <a href="#">去传智学习吧</a> // ES6 字符串拼接 let html2 = `<a href="#">去${school2}学习吧</a>` document.write(html2) // 去黑马学习吧 console.log(html2) // <a href="#">去黑马学习吧</a>
8. 类型转换
// 1.隐式转换 console.log('haha' - 1) // NaN console.log('10' - 1) // 9 console.log(+'10') // 10 console.log(-'10') // -10 // 2. 显示转换 // Number() 纯数字形式的字符串,如 '100',但是很少用 // parseFloat() 单纯只是提取字符串开头部分的数字 // parseInt() 如果是想把字符串中开头的【整数部分】提取出来 // 2.1 Number() 能够将字符串【整体】部分数字开头的部分提取出来,没数字开头就是 NaN console.log(Number('10') + 20) // 30 console.log(Number(true)) // 1 console.log(Number('123abc')) // NaN console.log(Number('haha')) // NaN // 2.2 parseInt() 能够将字符串中以数字开头的整数数字部分提取出来 console.log(parseInt('123abc')) // 123 console.log(parseInt('abc123')) // NaN console.log(parseInt('12.34px')) // 12 // 2.3 parseFloat() 能够将字符串中以数字开头的含小数,数字部分提取出来 console.log(parseFloat('12.345元')) // 12.345 // 2.4 String() 转换为字符串 // 2.5 变量.toString(进制)
9. 运算符
// 1. 赋值运算符 // [ += -= *= /= ] let num = 10 num = num - 1 console.log(num) // 9 let num1 = 10 num1 -= 1 console.log(num1) // 9 let num2 = 9 num2 += 1 console.log(num2) // 10 let num3 = 10 num3 *= 2 console.log(num3) // 20 let num4 = 10 num4 /= 2 console.log(num4) // 5 let num5 = 10 num5 %= 3 console.log(num5) // 1 // 2. 一元运算符 // [ +(正) -(负) ++(自增) --(自减) ] // 一元运算符只能操作一个变量(数据) let t = 0 t++ // 相当于 t = t + 1 console.log(t) // 1 t++ console.log(t) // 2 let x = 10 x-- // 相当于 x = x - 1 console.log(x) // 9 x-- console.log(x) // 8 // ++ -- 有两种写法 前置和后置 // 例:++i、--i、i++、i-- let i = 0 i++ console.log(i) // 1 ++i console.log(i) // 2 let p = 10 console.log(p++ + 1) // 11 p = 11 console.log(++p + 1) // 13 p = 12 let a = 1 console.log(a++ + ++a + a) // 2 + 2 + 3 // 3. 比较运算符 // [ > < == >= <= != === !== ] // == 判断左右值是否相同(开发中有的时候会报错) // === 判断左右类型和值是否一模一样【推荐】 let c = '10' let d = 10 // 全等 【推荐】 console.log(c === d) // false // 全不等 【推荐】 console.log(c !== d) // ture // 字母做对比的时候 逐次比较ASCII码 let e = 'a' // ascii 97 let f = 'b' // ascii 98 // 对比字符的大小 console.log(e > f) // false // 注意事项 【不要对小数进行对比】 console.log(0.1 + 0.2) // 约等于 0.3 // 不是Js 问题 而是计算机知识问题 // 进制小数点的表示方法IEEE754 标准 console.log(0.1 + 0.2 === 0.3) // false // 4. 逻辑运算符 // [ && || ! ] // 布尔类型运算叫做逻辑运算 let isFine = true let newIsFine = false let isCool = false let newIsCool = true // && 且 [左右只要有一个为false,结果就为false;左右都为true时结果为true] console.log(isFine && isCool) // false console.log(isFine && newIsCool) // true // || 或 [左右只要有一个为true,结果则为true;] console.log(isFine || newIsFine) // true console.log(isFine || newIsCool) // true console.log(newIsFine || isCool) // false // ! 否 [通常是取反:true变flase,false变true] console.log(!isFine) // false console.log(!isCool) // true // 结论:逻辑运算符的结果为布尔类型 // 5. 运算符优先级 // 参考MDN文档,搜索优先级查看 ![images]()
10. 分支语句
// 1. if 判断 [ 单分支 ] // if (true) { // 则执行这里的代码 // } let age = 18 if (age > 20) { alert('年龄大于 20 了') } // 2. if else 语句 [ 双分支 ] // if (true) { // 为 true 执行这里的代码 // } else { // 为 false 执行这里的代码 // } let gender = '男' if (gender === '男') { alert('欢迎你男士') } else { alert('欢迎你女士') } // 3. if else if [ 多分支 ] // if (条件1) { // 执行语句 // } else if (条件2) { // 执行语句 // } else if (条件n) { // 执行语句 // } else { // 最后的 else 不是必须要有 // } let age = 16 if (age >= 18) { alert('成年人了') } else if (age >= 14) { alert('青少年') } else if (age >= 7) { alert('小学生') } else { alert('幼儿园') } // 4. 三元运算符 [ 双分支 ] // 三元运算符会返回一个值 // 条件 ? 满足时执行的代码 : 不满足执行的代码 let age = 14 let sort = age >= 18 ? '成年人' : '未成年人' console.log(sort) // 5. switch 语句 [ 多分支 ] // switch 配合 case 、default 【全等===】 // switch (判断条件) { // case 条件1: // 判断条件的值和条件1全等时执行这里的代码 // break // 中断执行 // case 条件2: // 判断条件的值和条件2全等时执行这里的代码 // break // 中断执行 // case 条件3: // 判断条件的值和条件3全等时执行这里的代码 // break // 中断执行 // default: // default 一般放在最后 // 值都不全等的时候执行这里的代码 // } let num = prompt('请输入一个5以内的整数:') switch (num) { case '1': console.log('你输入了一个1') break case '2': console.log('你输入了一个2') break case '3': console.log('你输入了一个3') break default: console.log('你没有输入1或者2或者3') }
11. 循环语句
// 1. while 循环 // let i = 5 // while (i > 0) { // console.log('我会【一直】执行下去') // i-- // 变化量 // } let i = 1 while (i <= 10) { document.write('键盘敲烂,薪资过万<br>') i++ } // 2. for 循环 // for (声明起始值; 终止值条件; 变化值) { // 循环的代码 // } for (let i = 1; i <= 10; i++) { document.write('键盘敲烂,月薪过万!<br>') } // 3. for 循环嵌套 // for (外部声明起始值; 中止值条件; 变化量) { // for (外部声明起始值; 中止值条件; 变化量) { // } // } for (let i = 0; i < 3; i++) { console.log('此处是 i 的循环') for (let j = 0; j < 3; j++) { console.log('此处是 j 的循环') } }
12. 数组
let arr = [] arr.length // 数组的长度 arr.push() // 向数组的【结尾】添加一个新的单元 arr.unshift() // 向数组的【开头】添加一个新的单元 arr.pop() // 删除数组的最后一个单元 arr.shift() // 删除数组的第一个单元 arr.splice(1,4) // 第一个值是哪个单元开始删,第二个值是删除几个单元 arr.splice(1,2,1,1) //第一个值哪里开始删,第二个删除几个,删除的第一个要更改的值,删除的第二个要更改的值
// 语法:数组要放到 [] 里,每个数据用 逗号分隔 // 1. 索引值(下标)从 0 开始编号 // 2. 单元值(元素)是数组中具体数据 // 获取数组中数据: arr[0] arr[1] // 1. 数组遍历 // 循环初始值必须从 0 开始 let arr = ['刘备','关羽','张飞','黄钟','马超'] for (let i = 0; i < arr.length; i++) { console.log(arr[i]) } // 2. 数组操作 // 2.1 增 let arr = [1, 2, 3, 4] // 2.1.1 push 在数组【结尾】添加一个新的单元 arr.push(6) console.log(arr) // [1, 2, 3, 4, 6] // 2.1.2 unshift 在数组的【开头】添加一个新的单元 arr.unshift(0) console.log(arr) // [0, 1, 2, 3, 4, 6] // 2.2 删 let arr = [1, 2, 3, 4] // 2.2.1 pop 删除数组的最后一个单元 arr.pop() console.log(arr) // 2.2.2 shift 删除数组的第一个单元 arr.shift() console.log(arr) // 3. splice let arr = [0, 1, 2, 3, 4, 6] // 3.1 可以删除 // arr.splice(索引值, 单元个数) // 第一个值是哪个单元开始删,第二个值是删除几个单元 arr.splice(1,4) console.log(arr) // [0, 6] // 3.2 可以修改 arr.splice(1,2,6,4) // 第一个值哪里开始删,第二个删除几个,删除的第一个要更改的值,删除的第二个要更改的值 console.log(arr) // [0, 6, 4, 3, 4, 6]
13. 函数 == [ 封装 ]
// 函数的语法格式如下: // function 函数名() { // // 函数体 // } // 函数必须要调用!!!不调用不执行!!! function abc() { console.log('哈哈哈哈') } // 函数名() 即表示要调用这个函数了 abc() abc() // 函数可以多次调用,不调用不执行 // 1. 定义函数时通过参数能够让函数的功能更灵活 // 2. 带参数的函数语法如下 // function 函数名(参数1, 参数2, 参数n) { // 函数体 // } // 3. 参数其实就是一个变量!!! // function sum(x, y) { // // x y 就被当作变量来使用 // console.log(x + y) // } // sum(10, 5) // 4. 函数的参数(变量)是在调用的时候被赋值的 // 5. 参数两种形式 // 5.1 【形参】 形式上的参数 // 5.2 【实参】 实际上的参数 // 函数定义时的 x 和 y 两个参数,叫做 形参 // 调用函数传递的值叫做 实参 // 6. 形参的个数与实参的个数可以不一致,但是尽量一直,不然逻辑会出问题 // function summ(x, y, z) { // // 10 + 5 + undefined == undefined // console.log(x + y + z); // NaN // } // summ(10, 5) // 如果 【形参】 个数大于 【实参】,我们可以给 【形参】 设置一个默认值 // 形参有三个 function sum (x, y, z = 2) { console.log(x + y + z) } // 调用的时候 实参只有两个 sum(10, 5) // 17 sum(10, 5, 8) // 23 // 结论:给形参设置一个默认值,可以让函数更灵活 // 7. 函数的返回值 就是把函数的执行的结果交给函数外部 // 函数的返回值 就是把函数的执行的结果交给函数外部 // 如果一个函数中没有 return 那么它的默认值是 undefined // 返回值 就是函数传出来的一个(结果) function sum(x, y) { let s = x + y return s // s 为返回值 执行sum()函数 会得到一个返回值s } let z = sum(10, 5) console.log(z) // 15 // 返回值原本的意思是将函数内部的结果交给外部使用 // 但是一旦使用了 return 那么函数内部的代码就停止运行了 function summ() { // 将函数内部的 5 交给外部使用 return 5 // 函数到此结束,函数内后边的代码不执行 let k = 1 + 2 console.log(k) } let i = sum() // i = 5 // k 不会输出 function sayHi(name) { // 如果 name 的值不是字符串型 停止后面的语句 if (typeof name !== 'string') return return `${name}你好` } console.log(sayHi('小明')) // 小明你好 console.log(sayHi('小明明')) // 小明明你好 console.log(sayHi(123)) // undefined // 没有名字叫匿名函数 // 有名字叫具名 // 1. 具名函数 function sum() {} function sayHi() {} // 2. 匿名函数 // function () {} // 这是一个没有名字的函数 // 匿名函数作为一个值,赋值给一个变量 // 把匿名函数赋值给一个变量是常见的用法 let y = function () { consoloe.log('我没有名字') } // 变量名可以当成函数名来对待 y() // 立即执行函数 // 在使用立即执行函数的时候最好加一个(分号)结束符 // 函数声明完立刻就被调用了 // 正常情况下函数【声明】和【调用】是分开的 // 1. 声明 // function abc() {} // 2. 调用 // abc() // 立即执行函数是声明和调用二合一 // 写法 1 【推荐】 ()() 柯里化 // (定义一个函数)() ;(function fn() { console.log('我立刻就被执行了') })() // 写法 2 ;(function foo() { console.log('我也立即被执行了!!!') }())
14. 作用域
// 在函数里定义变量,只能在函数里使用,出了这个范围就不在生效了 function class201 () { let name1 = 'haha' console.log(name1) // 'haha' } class201() function class202 () { let name1 = 'haha' console.log(name1) // 'haha' } class202() function class203 () { console.log(name1) // 报错 } class203() // 作用域在 js 中分为两大类: // 1. 全局作用域 【里面的变量为 全局变量】 // 直接写在 最外层的 代码属于全局作用域 let x = 1 // 全局范围(作用域)中变量 let y = 5 // 全局范围(作用域)中变量 console.log(x, y) // 全局的变量在【任意地方】都可以访问 function getNum() { console.log(x, y) } getNum() // 1 5 // 2. 局部作用域 【里面的变量为 局部变量】 // 局部作用一般是定义在函数内部,并且也只在函数内部生效(被访问) // 2.1 函数作用域 function sayHi() { let name1 = '小明' } // 调用函数 sayHi() console.log(name1) // 报错 // 2.2 块作用域
15. 对象
// 对象就是用来完整描述事件信息的数据类型 // 对象的数据更完整 // 对象其实就是由若干个普通的数据组合(大杂烩)而成 // 1. 定义对象 // 语法 // let 变量名 = {} // 对象可以【完整】的描述一个人或者一个物的信息 let xiaoMing = { // 属性名: 属性值, // 属性名: 属性值 age: 18, gender: "男", hobby: "写代码", }; let xiaoHong = { age: 20, gender: "女", }; // 2. 对象的读取 // 语法:对象名.属性名 console.log(xiaoMing.age); console.log(xiaoMing.hobby); // 3. 对象属性的修改 // 语法:属性名.属性值 = 新的值 xiaoMing.age = 19 console.log(xiaoMing) // 4. 对象属性的新增 xiaoHong.hobby = 'IT行业' console.log(xiaoHong) // 5. 对象属性的删除 // 语法:delete 对象名.属性名 delete xiaoMing.hobby console.log(xiaoMing) // 对象的读取还有一种语法 let person = { name: '小明', age: 18 } // person.name person.age // 还有一种语法用 [] 即:对象名['属性名'] console.log(person.name) console.log(person['name']) // 如果属性名比较[特殊]的时候,使用 [] 的语法 // 距离说明 let person1 = { name: '小明', age: 20, 'his-id': 01 } // person1.his-id 语法错误,会识别成 person1.his 减 id // 此时只能使用[] 语法即, console.log(person1['his-id']) // 对象的补充内容 // 1. 如果一个对象中没有某个属性,非要读取 // 结果会是什么? 答:undefined let car = { color: '白色', brand: '宝马', price: '20万' } console.log(car.year) // undefined // 2.如果对象中没有某个属性,我非要给他赋值 // 结果会是啥样? 答:动态为对象增加了一个[新属性] car.year = '2020年' console.log(car) // 在对象属性进行赋值时,如果属性已存在则为修改 // 如果不存在则会新增一个属性(重要!!!) // 对象的方法 // 1. 方法即函数,函数如果在对象中即为方法 let person = { name: 'andy', age: 20, gender: '男', // 方法名: 函数 canSing: function() { console.log('哈哈, 我会唱歌') } } // 方法就是函数,函数是需要调用的 // 问: 如何对这个方法进行调用呢 // 举例 person.canSing() // 2. 带参数的方法 let person1 = { name: '小明', age: 18, sayHi: function (name) { // name 为 形参 console.log(name) } } // 形参(定义时的参数),实参(调用时的参数) person1.sayHi('小小明') // '小小明' 为 实参 person1.sayHi('小小小明') // 3. 方法的参数可不可以有【默认值】? // 答:可以 let person2 = { name: '小红', gender: '女', // 方法名 sum sum: function (x, y = 0) { console.log(x + y) } } person2.sum(10) // 4. 方法中可不可以写 return // 答:可以 let person3 = { name: '小丽', age: 19, total: function (n) { let sum = 0 for (let i = 1; i <= n; i++) { sum += i } return sum } } let num100 = person3.total(100) console.log(num100) // 方法即为函数,函数如果在对象中即为方法 // 对象的遍历 // 所谓的对象遍历是指将对象中的属性全部的获取得到 // 对象要通过 for in 来实现 // 语法: // for (let 变量名 in 对象名) { // 变量明就可以读取到对象的属性 // 及对象的【属性】就【赋值】给了【变量】 // } let person = { name: '小明', age: 18, gender: '男' } for (let key in person) { console.log(key) // 依次输出如下 // name // age // gender // 对象的属性值如何访问? // 答:对象名.属性名 对象名[属性名] // 错误写法 // console.log(person.key) // undefinde // 上行代码不会把key当作变量对待,而是直接识别成了属性名 // 而 person 对象又没有这个属性,所以结果为 undefined // 正确写法 console.log(person[key]) // 特殊情况下使用[]: // a) 属性中包括了 - 或 空格 // b) 如果属性名是一个变量 }
16. 内置对象 - Math
// js 中有很多内置对象 // 这里只用 Math // Math 是 js 中的一个对象,它主要是提供一些数学相关的逻辑处理 // 对象 = 属性 + 方法 构成 // 在学习一个对象的时候就是在学习它有哪些属性,它有哪些方法 // 属性是什么意思,方法能做什么事情 // 1. Math 的属性 Math.PI -> 3.1415926 圆周率 console.log(Math.PI) // 3.14.5926... 如果不去计算圆面积一般很少用 // 2. Math 的方法 // 2.1 Math.max() 求一堆数中最大数 console.log(Math.max(10, 23, 45, 69, 6)) // 2.2 Math.min() 求一堆数中最小数 console.log(Math.min(10, 23, 45, 69, 6)) // 2.3 Math.floor() 向下整数 -- 取整数部分,直接丢弃小数部分 console.log(Math.floor(10.2345)) // 10 // 2.4 Math.ceil() 向上整数 -- 取整数部分,有小数就丢弃小数部分,整数加一 console.log(Math.ceil(10.2345)) // 11 // 2.5 Math.round() 取整数 -- 四舍五入 console.log(Math.round(10.5345)) // 11 // 2.6 Math.pow() 求次幂 -- 几次方 console.log(Math.pow(2, 2)) // 4 2的2次方 console.log(Math.pow(2, 3)) // 8 2的3次方 // 2.7 Math.abs() 求绝对值 -- 变成正数 console.log(Math.abs(-10)) // 10 // 2.8 Math.random() 随机生成一个 0 到 1 之间的随机数 console.log(Math.random()) // 0 到 1 的随机数带小数 // 让 Math.random 乘以一个数字 // Math.random() * 10 ==> 0 到 10 之间的数 带小数 console.log(Math.random() * 10) // 带小数 0 到10 let intNumber = Math.floor(Math.random() * 10) console.log(intNumber) // 0 到 10 之间的整数 /*------------------随机数难点------------------------------*/ // 请你求出来 0 ~ 10 之间的随机数 (包含10) let i = Math.random() * (10 + 1) // 0 ~ 11 (不包含 11) Math.floor(i) // 向下取整 ==> 0 ~ 10 (包含 10) // 过程 求 5 到 10 之间的随机数 (包含5,包含10) // Math.random() 0 ~ 0.999999999999 // Math.random() * 10 0 ~ 9.999999999999 // Math.random() * (10 + 1) 0 ~ 10.99999999999 // Math.random() * 5 + 1 0 ~ 5.999999999999 // Math.random() * (5 + 1) + 5 5 ~ 10.999999999999 // 公式 求 N 到 M 之间的随机(整)数 Math.random() * (M - N + 1) + N Math.floor(Math.random() * (M - N + 1) + N)
17. 网页整体部分变为灰色
// 给整个html添加样式 filter: grayscale(100%);
JavaScript 知识点
1. js 基础知识
1.1 js是什么?
一种运行在客户端(浏览器)的编程语言,实现人机交互效果
1.2 js作用
-
网页特效
-
表单验证
-
数据交互
1.3 js构成
-
ECMAScript(js语言基础)
-
Web APIs
-
DOM(页面文档对象模型)
-
BOM(浏览器对象模型)
-
2. js书写位置
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Javascript 的书写位置</title> <style> .container { padding: 10px 30px; } </style> </head> <body> <div class="container"> <h4>Javascript 的书写位置有 3 种情况:</h4> <ul> <li οnclick="alert('内联方式js')">内部 Javascript</li> <li>外部 Javascript</li> <li>行内 Javascript</li> </ul> </div> <!-- 1. 内部 Javascript 写在 script 标签内部 --> <script> alert("欢迎来到js基础部分的学习!!!"); </script> <!-- 2. 外部 Javascript 通过 script 的 src 属性引入外部的 .js 文件 --> <script src="./index.js"></script> <!-- 3. 行内 Javascript 可以直接写在 HTML 标签中(了解) --> </body> </html>
-
注:
-
建议标签写在 </body> 标签的前面
-
在 js 引入的 script 标签中不要写内容,会被忽略
-
3. js 的注释
3.1 单行注释
-
符号
-
//
-
3.2 块注释
-
符号
-
/* */
-
4. js 结束符
-
英文状态下 ;
-
可以省略
-
结束符可以使用,也可以省略不写,(js引擎)会自动判断语句的结束位置
-
多数人会选择省略分号,要根据开发团队的需求来决定写还是不写
5. 字面量
-
字面量(literal)是计算机中描述 事/物
6. 变量
-
计算机中存储数据的地方,相当于一个空间
-
变量【不允许重复声明】
7. 命名规范
-
不能用关键字
-
字母、数字、下划线、$、
-
不能使用数字开头
-
变量名严格区分大小写
-
规范小驼峰命名法
-
首单词全小写,第二个单词开始往后单词的首字母都大写
-
例如
-
myFristName
-
-
8. var 和 let 提升
-
var 是 ES5 中,let 是 ES6 中
-
var 可以重复声明变量,let 不可以重复声明变量
-
var 变量提升,如果一个变量在没有声明时就可以使用,我们就叫变量提升
9. 数组
-
声明语法
-
let 数组名 = [数据1, 数据2, 数据3, ... , 数据n]
-
-
索引或下标
-
第0个是第一个数据,第1个是第2个数据
-
10. 常量
-
用 const 声明的变量称为 “常量”
-
当某个变量永远【不会改变的时候】,就可以使用 const 来声明
-
【声明的时候必须赋值,值是不可改变的】
11. 数据类型
11.1 基本数据类型
11.1.1 数字型 [ Number ]
-
数字型:整数、小数、正数、负数
11.1.2 字符串型 [ string ]
-
只有被引号引起来的内容才叫字符串类型,没有被字符串引起来的不叫字符串类型
-
单引号(推荐)、双引号、反引号
11.1.3 布尔型 [ boolean ]
-
只有两个值
-
true 真值
-
false 假值
-
11.1.4 未定义型 [ undefined ]
-
一个变量的类型取决于变量的值
-
没有值的变量 类型为 indefined,或者直接赋值为undefined
11.1.5 空类型 [ null ]
-
原型链的顶端 为 null
-
现在开发中一般不推荐使用 null
-
【变量初始值为null的时候,ytpeof 检测会成为 object】
11.2 引用数据类型
12. NaN
-
Not a Number
-
非数字
-
计算机无法进行数学运算时的结果
-
NaN 与任何数字做运算结果都为 NaN
-
13. 字符串的运算
-
主要用来做拼接
-
加号左右只有有一个字符串 就是【拼接】
-
运算中,只要使用了引号 最后结果就会变成整个字符串
let str = '我叫' let name = 'ZH' let str1 = str + name console.log(str1); // 我叫ZH console.log(str - 1); // NaN console.log(str + 1); // 我叫1 console.log('1' + 10 + 5); // 1105
14. 模板字符串
-
模板字符串啥时候用?答:当字符串需要拼接的时候
-
如果不拼接,模板字符串与 单音 双引 没有区别
-
语法是 使用反引号
// 字符串拼接 let school1 = '传智' let school2 = '黑马' let html1 = '<a href="#">去' + school1 + '学习吧</a>' document.write(html1) // 去传智学习吧 console.log(html1) // <a href="#">去传智学习吧</a> // ES6 字符串拼接 let html2 = `<a href="#">去${school2}学习吧</a>` document.write(html2) // 去黑马学习吧 console.log(html2) // <a href="#">去黑马学习吧</a>
15. 类型转换
-
为什么要数据转换?
-
答:将某些数类型进行转换才能得到【理想的结果】
-
15.1 隐式类型转换
-
由 js 内部自己根据需要对数据进行转换
-
隐式转换存在着很多不确定性,不必计较到底谁转成了谁
15.1.1 算数运算符 ( + - * / )
-
加号会拼接,在做运算时,会把 boolean转换为 number 参与运算
-
true + 1 结果为 2
-
false + 1 结果为 1
-
-
减、乘、除,会隐式转换
-
‘11’ - 1 结果为 10
-
’10‘ * 1 结果为 10
-
’10‘ / 1 结果为 10
-
15.1.2 一目运算符 ( + - )【不建议】
-
+(正)
-
+‘10’ 结果为 10
-
-
-(负)
-
-‘10’ 结果为 -10
-
15.2 显示类型转换
15.2.1 Number()
-
用来转数字的,整体转的了数字就转,转不了就是 NaN
15.2.2 parseInt()
-
能够将字符串中以数字开头的整数,数字部分提取出来
15.2.3 parseFloat()
-
能够将字符串中以数字开头的含小数,数字部分提取出来
16. 运算符
16.1 赋值运算符
-
+=
-=
*=
/=
16.2 一元运算符
-
+ (正号)
- (负号)
++ (自增)
--(自减)
-
++
--
有两种写法 前置 和后置 -
前置时先做自身运算,后在做其他运算
-
后置时先做其他运算,后在做自生运算
16.3 比较运算符
-
>
<
==
>=
<=
!=
===
!==
-
== 判断左右值是否相同 (开发中有时双等于会报错)
-
=== 判断左右类型和值是否一模一样开发(推荐,性能好)
-
注意事项 不要对小数进行对比
console.log(0.1 + 0.2) // 约等于 0.3 // 不是Js 问题 而是计算机知识问题 // 进制小数点的表示方法IEEE754 标准 console.log(0.1 + 0.2 === 0.3) // false
16.4 逻辑运算符
-
&& (且)
|| (或)
! (非)
-
&& 左右有一个为false时就为false,只有左右都为true时结果才为ture
-
|| 或 左右只要有一个为true,结果则为true
-
! 否 通常是取反:true变flase,false变true
-
16.5 逻辑运算符的短路运算
// 为什么要使用短路? 答:需要赋值的时候 // 短路:逻辑运算符的一个规律 // javascript 中的 && 和 || 正在使用的时候会发生短路 // 发生短路的时候只会执行【部分代码】 // && 一假即假 // 当第1个值为true时,会把第二个值返回 // 当第1个值为false时,会把第一个值返回 let num = 10 let newNum = num > 5 && 6 console.log(newNum) // 6 // || 一真即真 // 当第1个值为true时,会把第一个true的值返回 // 当第1个值为false时,会把第后面的第一个true的值返回 let str = '小明' let newStr = false || str console.log(newStr) // 应用 // 给某个变量一个【默认值】 let name = prompt('请输入您的姓名:') name = name || '小明' console.log(name)
17. 循环
-
写循环的时候要注意3个要素,能避免出错
-
起始值
-
中止值
-
变化量
-
18. continue 和 break
-
区别
-
continue: [ 中止 ]结束本次后面的循环
-
break: [ 终止 ]结束整个循环
-
19. 数组
-
语法:数组要放到 [] 里,每个数据用 逗号分隔
-
let arr = ['张三', '李四', '王五']
-
-
索引值(下标)从 0 开始编号
-
单元值(元素)是数组中具体数据
-
获取数组中数据: arr[0] arr[1]
-
20. 转义字符
-
\r 回车 linux 换行
-
\n 换行 win 换行
21. 函数
-
函数可以理解为封装
-
为什么要封装
-
为了重复使用,提高开发效率
-
-
函数的参数(变量)是在调用的时候被赋值的
-
参数两种形式
-
【形参】 形式上的参数
-
【实参】 实际上的参数
-
函数定义时的 x 和 y 两个参数,叫做 形参
-
调用函数传递的值叫做 实参
-
-
-
形参的个数与实参的个数可以不一致,但是尽量一直,不然逻辑会出问题
-
结论:给形参设置一个默认值,可以让函数更灵活
-
返回值
-
函数的返回值 就是把函数的执行的结果交给函数外部
-
如果一个函数中没有 return 那么它的默认值是 undefined
-
22. 作用域链
-
作用域的查找机制:作用域链
-
如果自己没有某变量,就去找他的祖先,直到找到全局
-
23. 匿名函数
24. 变量命名 name 的问题
-
name 在 js 中 默认就有这么一个变量(属性)
-
默认 name = ''
-
在变量进行命名的时候尽量不要在全局下使用 name 这个变量名
25. 断点调试
-
属于(高级的调试功能)
-
所谓的断点调试 是【浏览器】提供的一个调试代码工具
-
它可以让代码【暂停】下来,看到代码的执行过程
-
进而帮助我们分析程序的问题可能出现在哪里
-
步骤
-
-
通过调试工具【源代码】找到要调试的 js
-
-
-
在代码中点击相应的【行号】,便可以添加【断点】
-
-
-
刷新页面,让程序重新执行
-
-
-
再次点击行号,可以删除断点
-
-
JavaScript 高级
1. 作用域
-
全局作用域
-
【函数外】,在全局作用域定义的变量
-
全局变量可以在任何地方使用
-
-
局部作用域
-
【函数内】,在局部作用域定义变量
-
局部变量只能在当前作用域使用
-
-
块级作用域(ES6)
-
【大括号内】,在块级作用域定义的变量
-
块级变量只能在当前块级使用
-
2. 声明变量关键字
-
var
、let
、const
(常量) -
var
和let
区别:-
var
声明的比那辆属于window,let
声明的变量不属于window -
var
声明的变量不具有块级作用域,let
声明的变量具有块级作用域 -
var
可以重复声明,let
不可以重复声明
-
-
conts
(常量)-
const
声明的变量不属于window -
const
声明的变量具有块级作用域 -
const
不可以重复声明 -
const
声明的变量必须初始化有值 -
const
声明的变量不允许改值
-
建议
-
声明常量时,常量名全大写
-
不变的值建议用常量定义
-
-
3. 作用域链
-
作用域链:由作用域串联起来链
-
作用:提供查找变量的机制
-
当一个作用域访问一个变量时,先从当前作用域查找,如果不存在这个变量,那么就会往上级查找
4. 闭包
-
闭包
-
一个作用域有权访问另外一个作用域的局部变量
-
子函数访问了父函数的局部变量,然后把子函数返回到外面
-
-
作用
-
延伸变量的使用范围,避免变量污染
-
// fn 为闭包函数 function fn () { let num = 666 return function fun () { console.log( num ) } } let re = fn() re() // 666
5. 预解析
-
js运行三部曲
-
先语法分析 扫面一遍看看有没有语法错误
-
预解析
-
解释执行 解释一行执行一行
-
-
预解析四部曲
-
创建AO对象
-
找形参和变量声明,将变量和形参名作为AO属性名,值为
undefined
-
将实参值和形参值统一
-
在函数里面找函数声明,值赋予函数体
-
-
预解析:代码执行之前先要预解析
-
变量:带有声明的变量,也叫变量的提升
-
把变量的声明语法提升到当前作用域的最前面
-
注意:只声明不复制
-
-
函数:带有名字的函数
-
把函数的声明语法提升到当前作用域的最前面
-
注意:只声明不调用
-
-
预解析
-
函数声明整体提升 -- 声明变量提升 - 变量不赋值
-
变量 声明提升 -- 声明函数提成 - 函数不执行
-
-
-
let
生命前不可以用,生命后才可以使用,const
同let
function test(a, b){ console.log(a); // function a () {} console.log(b); // undefined var b = 234; console.log(b); // 234 a = 123; console.log(a); // 123 function a () {} var a; b = 234; var b = function () {} console.log(a); // 123 console.log(b); // function () {} }
6. 函数
6.1 参数默认值
// 求任意两个数的和 function getSum ( a = 0, b = 0 ) { // let a = 0 // let b = 0 let sum = a + b console.log( sum ) } getSum( 1 ) // 1 + 0 = 1 getSum() // 0 + 0 = 0 getSum( undefined, 6 ) // 0 + 6 = 6
6.2 动态参数 - arguments
-
遇到参数不固定的情况:
arguments
-
arguments: 函数内部的特殊对象,用于接收所有参数
-
注意:
-
arguments
是一个伪数组
-
-
建议:
-
如果参数不固定用
arguments
, 但是如果参数固定的情况下直接用形参
-
function fn2 () { // console.log( arguments ) // arguments = [1, 2, 3, 'a', 'aa'] for ( let i = 0; i < arguments.length; i++ ) { console.log( arguments[i] ) } } // 实参的个数不确定 fn2( 1, 2, 3, 'a', 'aa' ) // 1 2 3 a aa // 求若干个数的最大值 function getMax () { let max = arguments[0] for ( let i = 1; i < arguments.length; i++ ) { if ( max < arguments[i] ) max = arguments[i] } console.log( max ) } getMax( 123, 5, 62, 81, 21, 564, 551, 621, 22 )
6.3 ...
- 剩余参数、扩展运算符
6.3.1 剩余参数 - ...
-
ES6
-
接收剩余参数
...
-
只能放到形参的最后一位
function fn1(a, b, ...c) { console.log(a, b, c) // 刘备 关羽 ['张飞', '黄忠', '马超'] } fn1("刘备", "关羽", "张飞", "黄忠", "马超") function fn2(...a) { console.log(a) // 刘备 关羽 ['张飞', '黄忠', '马超'] } fn2("刘备", "关羽", "张飞", "黄忠", "马超") // ['刘备', '关羽', '张飞', '黄忠', '马超'] // 求若干数的和 function getSum(...ary) { // console.log( ary ) // [3, 6, 4, 84, 84, 89, 5, 12] let sum = 0 for (let i = 0; i < ary.length; i++) { sum += ary[i] } console.log(sum) } // 任意个数的实参 getSum(3, 6, 4, 84, 84, 89, 5, 12) // 287
6.3.2 扩展运算符 - ...
let arr = [23, 25, 69, 84, 92, 62] // console.log(Math.max(arr)) // 报错 Math.max(这里需要一堆数而不是一个数组) console.log(...arr) // 23 25 69 84 92 62 let re2 = Math.max(...arr) console.log(re2) // 92
6.4 箭头函数 - () => {}
-
箭头函数
-
let fn = () => {}
-
let fn = ( a, b ) => {return a * b}
-
let fn = a => {return a * a}
-
let fn = a => a * a
-
// 1.箭头函数基本用法 let fn1 = (a, b) => { console.log(a, b) } fn1(11, 22) // 11 22 // 2.箭头函数其他形式 // 2.1 如果函数的参数只有一个,那么可以省略小括号 let fn2 = (a) => { return a * a } console.log(fn2(3)) // 9 // 2.2 如果函数只有一行代码,那么可以省略大括号 // 注意:如果省略了大括号,那么会默认返回结果 let fn3 = (a) => a * a console.log(fn3(6)) // 3. 箭头函数使用场景: // 3.1 间歇定时器 ;setInterval(() => { console.log("aaa") }, 1000); // 3.2 forEach遍历数组 [1, 2, 3].forEach((item) => console.log(item))
-
箭头函数注意事项
-
在箭头函数中不存在arguments,所以箭头函数不可以使用arguments
let fn1 = ( ...a ) => { // console.log( arguments ) // 报错 console.log( a ) // [1, 5, 6, 8, 651, 5] } fn1( 1, 5, 6, 8, 651, 5 )
-
箭头函数不存在预解析,所以是用箭头函数的时候必须先声明后调用
let fn2 = () => { console.log( 123 ) } fn2()
-
this
: 指向问题
-
箭头函数中,
this
指向的是上级作用域的this
(指向所在作用域的this
) -
建议:有
this
不建议使用箭头函数
let obj = { uname : '张三丰', fei : function () { console.log( this ) // Object setInterval( function () { console.log( this ) // window },1000 ) setInterval( () => { console.log( this ) // Object },1000 ) } } obj.fei()
-
箭头函数可以有 事件对象
document.documentElement.addEventListener('click', e => { console.log( e.target ) // html })
-
箭头函数 this 指向例子
document.documentElement.addEventListener('click', function (e) { console.log( this ) // html }) document.documentElement.addEventListener('click', e => { console.log( this ) // window })
-
6.5 函数的注意事项
-
函数如果有
return
, 会把结果返回 -
如果函数只写
return
不写值,那么默认返回一个undefined -
如果函数不写
return
,在函数执行完成之后也会返回undefined
7. 解构赋值
-
作用
-
解开数据结构,赋给变量 == 赋值给变量的一种简介写法
-
7.1 数组解构 - 核心:一一对应
let [a, b, c, d, e] = ['刘备', '关羽', '张飞', '黄忠', '马超'] console.log( a, b, c, d, e ) // 刘备 关羽 张飞 黄忠 马超 // 1.1 变量少值多 let [a1, b1, c1] = ['刘备', '关羽', '张飞', '黄忠', '马超'] console.log( a1, b1, c1 ) // 刘备 关羽 张飞 // 1.2 变量多值少 let [a2, b2, c2, d2, e2, f2, g2] = ['刘备', '关羽', '张飞', '黄忠', '马超'] console.log( a2, b2, c2, d2, e2, f2, g2 ) // 刘备 关羽 张飞 黄忠 马超 undefined undefined // 1.3 按需取值 let [, a3, , , b3] = ['刘备', '关羽', '张飞', '黄忠', '马超'] console.log( a3, b3 ) // 关羽 马超 // 1.4 剩余值取法 let [a4, b4, c4, ...d4] = ['刘备', '关羽', '张飞', '黄忠', '马超'] console.log( a4, b4, c4, d4 ) // 刘备 关羽 张飞 ['黄忠', '马超'] // 1.5 多维结构 let [, a5, , [b5, , c5]] = ['刘备', '关羽', '张飞', ['孙悟空', '猪八戒', '沙悟净']] console.log( a5, b5, c5 ) // 关羽 孙悟空 沙悟净
7.2 对象解构 - 核心:把属性名当作变量名
let uname = '哇哈哈' // 注意:如果已经存在某个变量名,用冒号改名字 let { uname:userName, score, age, agee } = { uname : '张三丰', age : 22, sex : '男', inedx : 6, score : 99 } // 注意:取的对象属性里没有,输出就是undefined console.log( userName, score, age, agee ) // 张三丰 99 22 undefined // 2.1 多维结构 let {car : {width, height}} = { windth : '150CM', windth : '150CM', car : { color : 'red', width : '550CM', height : '850CM', } } console.log( width, height ) // 550CM 850CM
8. 对象-构造函数
8.1 创建对象
8.1.1 字面量创建对象
// 1. 字面量创建对象 let obj = { // 属性 : 属性值 // 键 : 值 // 成员 uname : 'HiHi', sex : '男', age : 22, // 方法 say : () => console.log('我说了一句话'), bai : () => console.log('拜拜') } // 1.1 访问属性: // 语法1:对象.属性 // 固定属性访问 console.log( obj.uname ) // HiHi // 语法2:对象['属性名'] // 动态属性访问 console.log( obj['age'] ) // 22 // 1.2 调用方法: // 语法:对象.方法() // console.log( obj.say ) // () => console.log('hello') console.log( obj.say() ) // hello
-
注意:
-
对象里的成员是无序的
-
8.1.2 构造函数创建对象
-
构造函数:构造函数也是函数,只不过这个函数和new一起使用
-
Object:
-
实例化对象: 就是 new 出来的对象
// 创建举例1: let o = new Object() // 添加成员 o.uname = 'Hi-Z' // 没有就添加 o.uname = 'Pi-M' // 有就修改 o.eat = () => console.log('吃饭中...') o.say = () => console.log('说话中...') // 创建举例2: let o1 = new Object( {uname : 'haha', age : 22, sex : '男'} ) console.log( o1 )
-
注意
-
如果不加参数,那么Object构造函数后面的小括号可以省略
-
构造函数建议首字母大写
-
8.1.3 自定义构造函数
-
构造函数里的this指向当前实例对象
function Person ( uname, age, sex ) { this.uname = uname this.age = age this.sex = sex this.say = () => { console.log( 'say方法' ) } this.eat = () => { console.log( 'eat方法' ) } } // 实例化对象 let o1 = new Person( '阿飞', 18, '男' ) console.log( o1 ) // Person {uname: '阿飞', age: 18, sex: '男', say: ƒ, eat: ƒ} let o2 = new Person( '假阿', 18, '女' ) console.log( o2 ); // Person {uname: '假阿', age: 18, sex: '女', say: ƒ, eat: ƒ}
8.2 遍历对象/遍历数组 - for in
// 遍历对象 for in for ( let key in obj ) { // key: 代表对象里所有键 // console.log( obj.key ) // 固定属性写法 都是undefined console.log( `${key}:${obj[key]}` ) // 动态属性写法 } // 遍历数组 for in let arr = [ 1, 3, 5, 7, 9 ] for ( let i in arr ) { console.log( arr[i] ) }
8.3 构造函数-this
指向
-
事件处理函数里的this指向事件源
-
箭头函数里的this指向父级的this指向
-
构造函数里的this指向的是当前实例对象(new 出来的对象叫实例对象)
8.4 new
的执行
-
执行过程
-
开辟空间
-
this 指向这个函数
-
执行函数
-
返回这个对象
-
// 3. 执行函数 function Person ( uname, age ) { this.uname = uname this.age = age this.say = () => console.log('哇哈哈') } // 4.返回这个函数 // 1.开辟空间 2.this指向这个函数 let p1 = new Person( '张飞', 18 ) console.log( p1 )
8.5 构造函数-功能关键字
instanceof: 检测对象是否是某个构造函数的
constructor: 用来指回构造函数本身
function A () {} function B () {} // instanceof: 检测对象是否是某个构造函数的 // 语法:对象 instanceof 构造函数 let n = new B() console.log( n instanceof A ) console.log( n instanceof B ) // constructor: 用来指回构造函数本身 // 语法:对象.constructor console.log( n.constructor )
8.6 构造函数-成员
-
静态成员:
-
直接在函数身上的成员,称为静态成员
-
只有构造函数才可以使用
-
-
实例成员:
-
函数内部给实例对象准备的成员,称为实例成员
-
只有实例对象才可以使用
-
function Person (uname, age) { // 实例成员 (下三行) this.nuame = uname this.age = age this.say = function () { console.log('说话') } } // 实例化对象(p1) let p1 = new Person('张飞', 22) console.log( p1 ) // console.log( Person instanceof Object ) // true // 静态成员 (下两行) Person.language = '汉语' Person.eat = function () { console.log('吃饭') }
8.7 基本类型和引用类型的传递
-
传递:
-
值传递:把数据复制一份,传递 = 两份数据
-
引用传递:把数据地址复制一份,传递 = 一份数据
-
let n = 1 let m = n // 基础类型传递的是值 复制值并传递 n = 2 console.log( n, m ) // 2 1 let o = {uname : '阿飞', age : 18} let obj = o // 复杂类型传递的是地址 复制地址并传递 o.uname = '阿灰' // 因为地址相同,一变多变 console.log( o, obj ) // {uname: '阿灰', age: 18} {uname: '阿灰', age: 18}
9. 内置构造函数
引用类型:
Object
Array
RegExp
包装类型:
String
Number
Boolean
9.1 Object
构造函数
Object
构造函数的两个方法:
Object.keys
: 获取对象所有键
Object.values
: 获取对象所有值
-
Object
: 是内置的一个构造函数
// 创建对象 // 字面量:{键值对} let obj = {} // 构造函数:Object let o = new Object() // 字面量创建obj1对象 let obj1 = { uname : '张三丰', age : 20, say : function () { console.log('say方法') } } // 静态方法 // Object.keys: 获取对象所有键 let re1 = Object.keys( obj1 ) console.log( re1 ) // ['uname', 'age', 'say'] // Object.values: 获取对象所有值 let re2 = Object.values( obj1 ) console.log( re2 ) // ['张三丰', 20, ƒ]
9.2 Array
构造函数-方法
// 字面量: let a = [1, 2, 3] // Array: 构造函数,用于创建数组的构造函数 let arr = new Array('a', 'b', 'c') console.log( a, arr) // [1, 2, 3] ['a', 'b', 'c']
-
字面量创建数组,也就相当于
new
了一个实例对象
9.2.1 reverse
- 反转
reverse
: 反转数组顺序
-
注意:会改变元数组
let arr1 = ['a', 'b', 'c', 'd', 'e'] let re1 = arr1.reverse() console.log( re1 ) // ['e', 'd', 'c', 'b', 'a'] console.log( arr1 ) // ['e', 'd', 'c', 'b', 'a']
9.2.2 join
- 拼接单元
join: 拼接数组的每个元素,拼接成为字符串
let arr2 = ['a', 'b', 'c', 1, 2, 3] let re2 = arr2.join('') // 引号里可以填数组单元值之间用什么隔开 console.log( re2 ) // abc123 console.log( arr2 ) // ['a', 'b', 'c', 1, 2, 3]
9.2.3 concat
- 拼接数组
concat
: 把多个数组或者元素拼接成数组
let a1 = [1, 2, 3] let b1 = ['a', 'b', 'c'] let c1 = [11, 22, 33] let arr3 = a1.concat( b1, c1, '哇哈哈' ) console.log( arr3 ) // [1, 2, 3, 'a', 'b', 'c', 11, 22, 33, '哇哈哈']
9.2.4 inedxOf
lastInedxOf
inedxOf
: 查找某个元素在数组中首次出现的索引位置,
lastInedxOf
: 查找某个元素在数组中最后一次出现的索引位置,注意:如果找不到返回-1
let arr4 = ['a', 'b', 'c', 'd', 'a', 'b', 'c'] let re4 = arr4.indexOf('c') let re44 = arr4.lastIndexOf('c') console.log( re4 ) // 2 console.log( re44 ) // 6
9.2.5 Array.isArray()
- 静态方法
Array.isArray(值)
: 判断是否数组
let n = 123 let nn = [1, 2, 3] console.log( Array.isArray( n ) ) // false console.log( Array.isArray( nn ) ) // true
9.2.6 Array.from()
- 静态方法
Array.from(对象)
:把伪数组转换为真数组注意:伪数组必须要有length属性
let o6 = { 0 : 'a', 1 : 'b', 2 : 'c', 3 : 'd', 4 : 'e', 5 : 'f', length : 6 } let arr6 = Array.from( o6 ) console.log( arr6 ) // ['a', 'b', 'c', 'd', 'e', 'f']
9.3 Array
构造函数-遍历方法
-
语法
// 数组.forEach( function ( item, index, o ) { // 第一个形参数:代表当前项 // 第二个形参数:代表当前项的索引值 // 第三个形参数:代表当前数组 // } )
9.3.1 forEach
- 遍历数组
forEach
: 遍历数组
let arr = ['张飞', '关羽', 'blue', 'red', 1, 2, 3] arr.forEach( (item, index, o ) => console.log(item, index, o) )
9.3.2 find
- 遍历查找满足条件的第一个元素并返回
find
: 遍历并查找满足条件的一个元素,并返回
let arr2 = [5, 3, 6, 8, 7, 9, 12, 2] let re2 = arr2.find( (item) => { return item > 3 } ) console.log( re2 ) // 5
9.3.3 some
- 遍历查找是否具有满足条件的元素,有一个即为 true
some
: 遍历并查找是否具有满足条件的元素,有一个即为 true
let arr3 = [5, 3, 6, 8, 7, 9, 12, 2] let re3 = arr3.some( (item, index, o) => { return item > 3 } ) console.log( re3 ) // true
9.3.4 every
- 遍历并查找是否有满足条件的元素,都满足则为 true
every
: 遍历并查找是否有满足条件的元素,都满足则为true
let arr4 = [5, 3, 6, 8, 7, 9, 12, 2] let re4 = arr4.every( (item) => { return item > 3 } ) console.log( re4 ) // false
9.3.5 filter
- 遍历并筛选,把满足条件的值放到新的数组返回
filter
: 遍历并筛选,把满足条件的值放到新的数组返回
let arr5 = [5, 3, 6, 8, 7, 9, 12, 2] let re5 = arr5.filter( (item) => { return item % 2 === 0 } ) console.log( re5 ) // [6, 8, 12, 2]
9.3.6 map
- 遍历并让每一个元素执行一遍回调函数,把结果放到数组中返回
map
: 遍历并让每一个元素执行一遍回调函数,把结果放到数组中返回
let arr6 = [5, 3, 6, 8, 7, 9, 12, 2] let re6 = arr6.map( (item) => { return item * item } ) console.log( re6 ) // [25, 9, 36, 64, 49, 81, 144, 4]
9.4 RegExp
构造函数
// RegExp: 是内置的一个构造函数【正则对象】 // 字面量创建: let reg = /abc/ // 构造函数创建: let reg = new RegExp(/abc/)
9.5 String
构造函数-方法
// 字面量 let str = 'sadfdadfadfafadfa' // 构造函数创建 let strr = new String('sadfdadfadfafadfa') console.log( str, strr); // 1. length: 字符串的长度,或字符串的个数 let str1 = 'sadfdadfadfafadfa' console.log(str1.length) // 17 // 2. 索引值 let str2 = 'sadfdadfadfafadfa' console.log(str1[0]) // s // 可以循环遍历字符串每一位,不可以用forEach,因为是数组方法,for in 可以 for ( let i = 0; i < str.length; i++ ) { console.log( str[i] ) }
9.5.1 split
- 分隔字符串为数组
split
: 用于分隔字符串为数组
let str1 = 'abcdefabcder' let re1 = str1.split('') console.log(re1) // ['a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd', 'e', 'r']
9.5.2 indexOf
lastindexOf
indexOf
: 在字符串中查找某个字符,找到返回首次出现的索引值找不到返回-1
lastindexOf
: 在字符串中查找某个字符,找到返回最后依次一次出现的索引值找不到返回-1
let str2 = 'abcdefabcder' let re2 = str2.indexOf('c') console.log( re2 ); // 2 let str22 = 'abcdefabcder' let re22 = str22.lastIndexOf('c') console.log( re22 ); // 8
9.5.3 toLowerCase
toUpperCase
toLowerCase
: 转换为小写
toUpperCase
: 转换为大写
let str3 = 'abcdEFGHABCabc' let re3 = str3.toLowerCase( str3 ) console.log( re3 ) // abcdefghabcabc let str33 = 'abcdEFGHABCabc' let re33 = str33.toUpperCase( str33 ) console.log( re33 ) // ABCDEFGHABCABC
9.5.4 substring
substr
substring(start, end)
: 截取字符串
substr(start, num)
: 截取字符串
-
substring(start, end)
: 截取字符串-
从索引值为start开始截取,截取到索引值位置为end的地方
-
注意:不包含end索引位置上对应的字符
-
-
substr(start, num)
: 截取字符串-
从索引值为start开始截取,截取几位
-
注意:不包含num索引位置上对应的字符
-
-
注意:
-
不管是
substring
还是substr
,截取字符串 -
如果只有一个参数,那么就是从这个字符串开始截取到最后
-
let str4 = 'abcdEFGHABCabc' let re4 = str4.substring(3, 6) console.log( re4 ) // dEF let str44 = 'abcdEFGHABCabc' let re44 = str44.substr(3, 6) console.log( re44 ) // dEFGHA
9.5.5 trim
: 去除字符串两端的空格
trim
: 去除字符串两端的空格
let str5 = ' abcdEFG HABCabc ' let re5 = str5.trim( str5 ) console.log(re5) // abcdEFG HABCabc
9.5.6 replace
: 字符串替换
replace
: 字符串替换i: 全局 g: 不区分大小写
let str6 = 'abcaabbcc' let re6 = str6.replace(/a/gi, 'A') console.log( re6 ) // AbcAAbbcc
9.6 Number
构造函数 - 方法
// 字面量 let n = 1 console.log( n ) // 1 // 构造函数创建 let m = new Number(123) console.log( m ) // 123
9.6.1 toFixed
: 保留两位有效数字
toFixed
: 保留两位有效数字
-
注意:该方法有四舍五入
let prive = 36.766161568 let re = prive.toFixed(2) console.log( re ) // 36.77
9.6.2 parseInt
parseFloat
parseInt
: 取整
parseFloat
: 取浮点数
-
注意:
ES6
不让在全局用,归到Number
构造函数下
let num = '156.682px' let re2 = Number.parseInt( num ) console.log( re2 ) // 156 let re22 = Number.parseFloat( num ) console.log( re22 ) // 156.682
9.7 Boolean
构造函数
// 字面量 let falg = true console.log( falg ) // 构造函数 let f = new Boolean(true) console.log( f ) // 万物皆对象 // 构造函数:let fun = new Function() // function fun() {}
9.8 公共方法
9.8.1 toString
- 转字符串
toString
:转字符串
// 包装类型、引用类型 都可以用 // toString:转字符串 // 注意:undefined、null 不可以用这个方法,会报错 let num = 123 let arr = [1, 2, 3] let re = num.toString() console.log(re) // 123 let ree = arr.toString() console.log(ree) // 1,2,3
9.8.2 强制转换
String
:构造函数、强制转换
Boolean
: 构造函数、强制转换
Number
: 构造函数、强制转换
// undefined、null 转字符串 // String:构造函数、强制转换 console.log(String(null)) // null console.log(String(undefined)) // undefined // Boolean: 构造函数、强制转换 console.log(Boolean(null)) // false console.log(Boolean(undefined)) // false console.log(Boolean(123)) // true // Number: 构造函数、强制转换 console.log(Number(null)) // 0 console.log(Number(undefined)) // NaN console.log(Number("123")) // 123
9.8.3 valueOf
: 获取原始值
valueOf
: 获取原始值
// valueOf: 获取原始值 let str = new String("abcdefg") console.log(str.valueOf()) // abcdefg let time = new Date() console.log( time.valueOf() ) // 1649823350825(时间戳)
10. 封装
防止变量污染
10.1 命名空间封装
// let uname = '张三丰' // let age = 22 // let emial = 'zsf@qq.com' // let uname = 'Hi-ZZ' // let age = 22 // let emial = 'Hi-ZZ@qq.com' // 命名空间封装 // 字面量对象:把变量当作属性,把函数当作方法 let user1 = { uname : '张三丰', age : 22, emial : 'zsf3@qq.com' } let user2 = { uname : '张大丰', age : 28, emial : 'zsf1@qq.com' } let user3 = { uname : '张二丰', age : 25, emial : 'zsf2@qq.com' } console.log( user1.uname ) // 张三丰 console.log( user2.uname ) // 张大丰 console.log( user3.uname ) // 张二丰
10.2 构造函数封装
每构建一个实例化对象就会多一个公共方法,浪费空间
// 公共部分 // 姓名 性别 年龄 邮箱 function Person (uname, gender, age, email) { this.uname = uname this.gender = gender this.age = age this.email = email this.say = function () { console.log('你好啊') } this.eat = () => console.log('恰饭'); } let o1 = new Person('张三', '男', '22', 'zsf@qq.com') console.log( o1 ) let o2 = new Person('Hi', '男', 18, 'Hi@qq.com') console.log( o2 )
11. 原型对象
prototype
:指向构造函数的原型
constructor
:指回构造函数本身
__proto__
:用来指向原型对象
-
原型对象:构造函数或函数的一个属性
指向了一个对象,我们把这个对象成为原型,或原型对象
-
每一个构造函数都有一个
prototype
:指向构造函数的原型 -
每个原型对象都有一个
constructor
:指回构造函数本身 -
作用:共享成员
function Person(uname, age) { this.uname = uname this.age = age // this.eat = function() { // console.log('吃饭'); // } } // 在构造函数的原型上添加方法 // 【原型对象上的方法构造函数的成员可以用】 Person.prototype.eat = function () { console.log("吃饭中...") } let o1 = new Person("张三", 18) o1.eat() // 吃饭中... let o2 = new Person("李四", 20) let o3 = new Person("王五", 12)
-
每个对象都有一个属性:
__proto__
-
作用:用来指向原型对象
-
当一个对象,访问一个成员的,现在自身查找,如果查不到,沿着
__proto__
继续查找 -
注意:
__proto__
是一个非标准属性
// 构造函数 function Person (uname, age) { this.uname = uname this.age = age this.langage = '汉语' } // 原型对象 Person.prototype.eat = function () { console.log('吃饭中...'); } Person.prototype.head = 1 // 实例化对象 let obj = new Person('张三', 22) console.log( obj.__proto__ === Person.prototype ) // true
11.1 原型对象 - 原理图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l6S3yYKC-1650621858212)(./images/YuanXing.png)]
12. 继承
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wgXOjDfj-1650621858215)(./images/JiCheng.png)]
12.1 原型继承
继承: 第一步 把实例对象赋值给原型对象
Chinese.prototype = new Person() // 继承
继承 第二步 让原型上constructor指回自己的构造函数
注意:原型上不存在 constructor, 因为覆盖问题
Chinese.prototype.constructor = Chinese
// 公共构造函数 function Person() { this.head = 1 this.legs = 2 this.eyes = 2 this.eat = function () { console.log('吃饭'); } this.say = function () { console.log('say方法'); } } // 单个构造函数 function Chinese () { this.language = '汉语' this.skin = '黄皮肤' } function Japanese () { this.language = '日语' this.skin = '黄皮肤' } function America () { this.language = '英语' this.skin = '白皮肤' } // 继承 // console.log( Chinese.prototype ) // 没继承前 // 继承 第一步 把实例对象赋值给原型对象 Chinese.prototype = new Person() // 继承 // 继承 第二步 让原型上constructor指回自己的构造函数 // 原型上不存在 constructor, 因为覆盖问题 Chinese.prototype.constructor = Chinese // console.log( Chinese.prototype ) // 继承后 Japanese.prototype = new Person() Japanese.prototype.constructor = Japanese America.prototype = new Person() America.prototype.constructor = America // 实例化 let c = new Chinese() console.log( c ) let j = new Japanese() console.log( j ) let a = new America() console.log( a )
12.2 原型链
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jB0cV2HA-1650621858217)(./images/YuanXingLian.png)]
function People () { this.head = 1 this.legs = 2 } function Chinese () { this.language = '汉语' this.skin = '黄皮肤' } // 继承 Chinese.prototype = new People() Chinese.prototype.constructor = Chinese let c1 = new Chinese() console.log( c1 ) console.log(c1.constructor); // 原型链上添加方法 Object.prototype.lanqiu = function () { console.log( '打篮球...' ) } // 原型链上添加方法 People.prototype.lanqiu = function () { console.log( '...打篮球...' ) } let c2 = new Chinese() c2.lanqiu() // ...打篮球...
13. this
指向
重点:谁调用,
this
指向谁】严格模式下普通函数
this
为undefined】箭头函数
this
之指向所在作用域的this;或者上级作用域的this
】
// 1. 普通函数 function fn() { console.log(this) // window、调用者 } fn() // 2. 事件处理函数 document.addEventListener("click", function () { console.log(this) // 事件源、调用者 }) // 3. 构造函数 function Person() { console.log(this) // 实例对象 } new Person() // 4. 方法 let obj = { uname: "张飞", fei: function () { console.log(this) // obj、调用者 }, } obj.fei() // 5. 定时器 setTimeout(function () { console.log(this) // window、调用者 }, 1000) // 6. // 7. 箭头函数 let o = { uname: "阿飞", fei: function () { // console.log(this) // o // setTimeout( function () { // console.log(this) // window // }, 1000) setTimeout( () => { console.log(this) // o }, 1000) } } o.fei()
14. 改变this
指向 - call
call
: 他是函数的一个方法
语法:函数.call(this, 参数1, 参数2, ...)
14.1 call
- 函数的方法-改变this指向
// call: 他是函数的一个方法 // 语法:函数.call(this, 参数1, 参数2, ...) // 执行过程:函数调用了call方法,call会回头执行函数,在执行函数的过程中this会指向call的第一个参数 // 使用场景:想改函数里this指向的时候 function fn(a, b) { console.log(this, a, b); } // fn() let obj = {uname : '阿飞', age : 22} // fn函数调用了call方法,call会回头执行fn函数,在执行函数的过程中this会指向call的第一个参数 fn.call(obj, 1, 2)
14.2 apply
- 函数的方法-改变this指向
apply: 他是函数的一个方法
语法:函数.call(this, 参数1, 参数2, ...)
// 2. apply: 他是函数的一个方法 // 语法:函数.apply(this, [参数1, 参数2, ...]) // 执行过程:函数调用了apply方法,apply会回头执行函数,在执行函数的过程中this会指向apply的第一个参数 // 注意:apply的参数第一个是this的指向对象,第二个是一个数组,数组里面是任意个参数 // 使用场景:既要改变this指向,又要设计数组的时候 function fn2(a, b) { console.log(this); console.log(a, b); } let obj2 = { uname : '哇哈哈', age : 22 } fn2.apply(obj2, [11, 22]) // 使用举例 let arr = [23, 25, 69, 84, 92, 62] let re = Math.max.apply(null, arr) console.log(re) // 92 // ...:剩余值(接收参数时) // ...:扩展运算符(复杂值时) console.log(...arr) // 23 25 69 84 92 62 let re2 = Math.max(...arr) console.log(re2) // 92
14.3 bind - 函数的方法-改变this指向
bind: 他是函数的一个方法
语法: 函数.bind(this, 参数1, 参数2, ...)
// 3. bind: 他是函数的一个方法 // 语法: 函数.bind(this, 参数1, 参数2, ...) // 执行过程:函数调用了bind方法,bind不会回头执行函数,而是悄悄改变了函数的this指向,然后返回新的函数 // 使用场景:需要改变this,但是不需要立马执行函数 function fn3(a, b) { console.log(this) console.log(a, b) } let o3 = { uname: "aaa" } let re3 = fn3.bind(o3, 111, 222) re3() // 使用举例: let btn = document.querySelector("input") btn.addEventListener("click", function () { // 禁用元素 this.disabled = true setTimeout( function () { // 启用元素 this.disabled = false }.bind(this),3000) // 这里用call改变函数内this指向会回头立马执行函数;这行显示的this是事件源 })
15. 严格模式
严格模式:严格
开启严格模式:"use strict"
放到哪个作用域哪个作用域就遵守严格模式
注意:必须放到作用域的开头
// 开启严格模式后 // 变量必须生说明 // n = 1 // console.log( n ) // 报错 // 普通函数中的 this,不在指向window,而是undefined function fn () { console.log(this) // undefined } fn() // 函数形参不可以重名 // function fun(a, a) { // console.log(a + a) // 报错 // } // fun()
16. 类 - class
16.1 类和对象
// ES6之前 // 模板: // function Person () {} // 人:泛泛 // 姚明: 高, 篮球, 有钱 // ES6 // 类:泛泛概念 // 对象:具体概念 // 可以确定下来具体的东西是对象,确定不下来不够具体的是类
16.2 类的基本使用
创建类:
语法:class 类名 {}
注意:类名首字母大写
实例化:
语法:new 类名()
// 创建类 class Person {} // 实例化 let p1 = new Person() console.log(p1); // 检测实例化的对象是不是对象,是对象的话就是Object构造出来的 // console.log( p1 instanceof Object ) // true // 练习创建类 class Dog {} let hei = new Dog() console.log(hei);
16.3 类的操作 - 添加成员 - static
添加成员:
实例成员 - 属性名 = 属性值
静态成员 -
static
注意:方法直接自动添加到原型上了(Person.prototype)
// 1. 添加实例成员 // 创建类 class Person1 { // 实例属性 (定义变量不加let) uname = '张三丰' age = 22 head = 1 // 实例方法 (定义函数不加function) // 方法直接自动添加到原型上了(Person.prototype) eat () { console.log('eat'); } say () { console.log('say'); } } // 实例化 let o1 = new Person1() console.log(o1);
// 2. 添加静态成员 // 创建类 class Person2 { // 静态属性 static language = '汉语' static sjin = '黄皮肤' // 静态方法 static sleep () { console.log('睡觉'); } static walk () { console.log('走路'); } } // 实例化 let o2 = new Person2() console.log( o2 ) // console.log( o2.language ) // undefined console.log( Person2.language ) // 汉语
16.4 类的操作 - 构造函数 - constructor () {}
构造函数、构造方法、构造器
constructor () {}
注意:
构造方法,在new的时候自动执行
构造方法,如果不写的话,那么内部会默认的添加上
构造方法,名字是固定的,不可以更改
构造方法,用来接收参数,做一些初始化的操作
一个类里面只能有一个构造方法,顺序无要求,建议写前面
// 创建类 class Person { head = 1 constructor (uname, age, gender) { // 类里面固定的方法,用来接收参数,初始化操作,不可以改名 this.uname = uname this.age = age this.gender = gender } say () { console.log('say') } eat () { console.log('eat') } } // 实例化 (注意方法在原型对象上) let o1 = new Person('张飞', 22, '男') console.log(o1) // Person {head: 1, uname: '张飞', age: 22, gender: '男'} let o2 = new Person('关羽', 23, '男') console.log(o2) // Person {head: 1, uname: '关羽', age: 23, gender: '男'} let o3 = new Person('貂蝉', 21, '女') console.log(o3) // Person {head: 1, uname: '貂蝉', age: 21, gender: '女'}
16.5 类的操作 - 继承 - extends
、super
extends
: 申明一个类为子类
super
: 调用父类的方法
class Father { constructor (uname, age) { this.uname = uname this.age = age } qian () { console.log('转它一个亿'); } } // extends: 申明一个类为子类 // Son为子类,Father为父类 class Son extends Father { constructor ( uname, age, score ) { // 在继承时,如果子类有自己的constructor,那么必须使用super调用父类的方法 // super: 调用父类的方法 super(uname, age) this.score = score // 注意:必须先用super调用父级的构造方法,在用自己的构造方法 } qian () { super.qian() // console.log('转它半个亿'); } } // 实例化 let s1 = new Son('儿子', 2, 99) console.log(s1) // Son {uname: '儿子', age: 2, score: 99} s1.qian() // 赚它一个亿
16.6 类的本质
类的本质:函数
还是哪个构造函数function Person(){}
语法糖、语法盐
i++ 是 i = i + 1 的语法糖
i = i + 1 是 i++ 的语法盐
// 构造函数 // function Person() {} // let o = new Person() // 类 class Person {} let o = new Person() // console.log( typeof Person ) // function // console.log( Person.prototype ) // console.log( Person.__proto__ ) // 类的本质:函数 // 还是哪个构造函数function Person(){} // 语法糖 // 语法盐 // 例:i = i + 1 和 i++ // i++ 是 i = i + 1 的语法糖 // i = i + 1 是 i++ 的语法盐
17. 拷贝
新对象 = JSON.parse( JSON.stringify(旧对象) )
17.1 拷贝-浅拷贝
let obj = { uname : '张三丰', age : 22, gender : '男', message : { index : 6, score : 99 } } let newObj = {} // 浅拷贝 有个方法 assign // 语法:Objecy.assign( newObj, obj ) // Object.assign( newObj, obj ) // 浅拷贝-原理 // 遍历 // for ( let key in obj ) { // newObj[key] = obj[key] // }
17.2 拷贝-深拷贝
// 封装深层拷贝函数 function kaobei(newObj, obj) { for (let key in obj) { // 必须先判断数组,因为数组也是对象,对象不是数组 if (obj[key] instanceof Array) { // obj[key]可能是数组 newObj[key] = [] kaobei(newObj[key], obj[key]) } else if (obj[key] instanceof Object) { // obj[key]可能是对象 newObj[key] = {} kaobei(newObj[key], obj[key]) } else { newObj[key] = obj[key] } } } let obj = { uname: "张三丰", age: 22, gender: "男", message: { index: 6, score: 99, }, } let newObj = {} kaobei(newObj, obj)
Ajax
Ajax 知识点
1. 客户端与服务器通信的过程
-
客户端与服务器之间的通信过程,分为请求 - 响应两个步骤。
-
请求的概念:客户端通过网络去找服务器要资源的过程,叫做“请求”
-
响应的概念:服务器把资源通过网络发送给客户端的过程,叫做“响应”
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m8q6gyLP-1650621958357)(./images/TongXinGuoCheng.png)]
2. URL地址
-
URL 地址用来表示服务器上每个资源的确切访问路径
-
一个标准的 URL 地址主要由以下 4 个部分构成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FrElN0Y9-1650621958359)(./images/URL.png)]
-
URL 地址 - 协议(protocol)
-
概念:协议是网络协议的简称,用来保证通信的双方能读懂彼此发送过来的消息内容。
-
-
URL 地址 - 主机名(hostname)
-
概念:主机名用来标识互联网中服务器的唯一性。
-
好处:通过主机名,可以保证客户 端在万千的服务器中,找到自己想 访问的那台服务器!
-
-
URL 地址 - 端口号(port)
-
概念:端口号是 0 - 65535 之间的整数,它的主要作用是表示一台计算机中的特定进程所提供的服务。
-
注意:默认是:80端口,可以省略,80端口是网站的默认端口,省略就是80端口
-
-
URL 地址 - 路径(path)
-
概念:路径用来表示资源在服务器上具体的存放位置。
-
-
3. 什么是 Ajax
-
客户端网页向服务器请求数据的技术
4. Ajax请求数据的5种方式
-
客户端浏览器在请求服务器上的数据时,根据操作性质的不同,可以分为以下 5 种常见的操作
-
POST -- 向服务器新增数据
-
GET -- 从服务器获取数据
-
DELETE -- 删除服务器上的数据
-
PUT -- 更新服务器上的数据(侧重于完整更新:例如更新用户的完整信息)
-
PATCH -- 更新服务器上的数据(侧重于部分更新:例如只更新用户的手机号)
-
5. axios
-
axios(发音:艾克C奥斯) 是前端圈最火的、专注于数据请求的库。
-
英文官网地址:axios - npm
6. GET请求 和 POST请求 有什么区别
-
GET 请求只能在 URL 中携带少量的数 据
-
POST 请求适合用来提交大量的数 据
-
结论:POST 为了能够提交大量的数据,所以没有把数据拼接到 URL 的末尾;而是放到了独立的“请求体”中。
7. 什么是请求报文和响应报文
-
客户端与服务器通信的过程是基于请求与响应的。其中:
-
请求报文规定了客户端以什么格式把数据发送给服务器
-
响应报文规定了服务器以什么格式把数据响应给客户端
-
8. http 响应状态码
-
概念:http 响应状态码(Status Code)由三位数字组成,用来标识响应成功与否的状态。
-
作用:客户端浏览器根据响应状态码,即可判断出这次 http 请求是成功还是失败了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cwlCLoIx-1650621958365)(./images/http响应状态码.png)]
-
常见 http 状态码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pVKCvx9H-1650621958367)(./images/常见http状态码.png)]
9. 接口的概念
-
使用 Ajax 请求数据时,被请求的 URL 地址,就叫做数据接口(简称:接口或 API 接口)。
-
例如
-
http://www.liulongbin.top:3009/api/getbooks 获取图书列表的接口(GET 请求)
-
http://www.liulongbin.top:3009/api/addbook 添加图书的接口(POST 请求)
-
9.1 接口文档
-
接口文档就是接口的使用说明书,它是我们调用接口的依据。
-
请参考一个现成的接口文档
9.2 接口测试工具
-
测试工具:Postman
10. 表单的三部分
-
网页中采集数据的表单由三个部分组成,分别是:表单标签、表单域、表单按钮。
-
HTML 的
<form>
就是表单标签,它是一个“容器”,用来将页面上指定的区域划定为表单区域: -
表单域提供了采集用户信息的渠道,常见的表单域有:input、textarea、select 等。
-
注意:每个表单域必须包含 name 属性,否则用户填写的信息无法被采集到!
-
-
当表单数据填写完毕后,用户点击表单按钮,会触发表单的提交操作,从而把采集到的数据提交给服务器。
-
注意:
-
type="submit" 表示提交按钮的意思
-
type 属性的默认值就是 submit,因此 type="submit" 可以省略不写
-
-
11. 同源策略&跨域
-
什么是同源:
指的是两个url地址具有相同的协议、主机名、端口号
-
什么是跨域:
同源指的是两个 URL 的协议、主机名、端口号完全一致,反之,则是跨域。
-
什么是同源策略:
是浏览器提供的一个安全功能
浏览器的同源策略规定:不允许非同源的 URL 之间进行资源的交互。
Ajax 语法
1. axios
的基本使用
url
: 地址
method
: 请求方式,不写默认是GET
-
axios
(发音:艾克C奥斯) 是前端圈最火的、专注于数据请求的库。 -
英文官网地址:axios - npm
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>axios的基本使用</title> <!-- 1. 引入 axios.js 文件 --> <script src="./lib/axios.js"></script> </head> <body> <script> // 2. 调用 axios // 调用后就会发起请求 axios({ // 1. URL 地址 url: ' http://www.liulongbin.top:3009/api/getbooks', // 2. method 请求方式,不写默认是 GET method: 'GET' }).then((res) => { // 在 then 回调中获取数据,回调函数的参数就是我们要获取的数据 console.log(res); }) </script> </body> </html>
2. 参数 - params
语法:
params: {id: 1}
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./lib/axios.js"></script> </head> <body> <script> axios({ method: 'GET', // 查询参数的本质形式 url: ' http://www.liulongbin.top:3009/api/getbooks', // url 后面拼接 ? 参数名=参数值&参数名=参数值 // url: ' http://www.liulongbin.top:3009/api/getbooks?id=1', // params 指定查询参数 params: { // 参数 id: 1 } }).then((res) => { // res 从服务器获取到的数据 console.log(res); }) </script> </body> </html>
3. url编码
encodeURI() 编码
decodeURI() 解码
<script> // 在绝大部分情况下是不需要手动的进行编码的转换,因为浏览器会自动的将中文等特殊字符进行转换 // 记住浏览器会自动进行url编码转码 // encodeURI() 编码 // 将中文转换成utf8编码 console.log( encodeURI('西游记') ) // %E8%A5%BF%E6%B8%B8%E8%AE%B0 // decodeURI() 解码 // 将编码转换成中文 console.log(decodeURI('%E8%A5%BF%E6%B8%B8%E8%AE%B0'))// 西游记 </script>
4. 套壳后的结果
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>套壳后的结果</title> <script src="./lib/axios.js"></script> </head> <body> <script> axios({ url: 'http://www.liulongbin.top:3009/api/getbooks', method: 'GET', params: { bookname: '西游记' } }).then(({ data:res }) => { // res 是 axios 把服务器返回的数据进行套壳后的结果 // 服务器的数据在 res 的 data 中 // 解构赋值可以解决使用数据啰嗦问题 // 1. 参数必须有括号 () => {} // 2. 在括号种声明花括号 ({}) => {} // 3. 在花括号中声明你要取出来使用的属性名 ({ data }) // 4. 如果需要把属性更改成别名在属性名后面添加 :别名 ({ data:res }) console.log(res.data) }) </script> </body> </html>
5. 使用axios发起POST请求
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>使用axios发起POST请求</title> <script src="./lib/axios.js"></script> </head> <body> <script> // 发起一个 POST 请求,添加数据 axios({ url: 'http://www.liulongbin.top:3009/api/addbook', method: 'POST', // 提交要添加的(参数) // POST 请求的参数要放在 data 对象中 data: { // 以下参数都是后端告诉我们的 // 图书名 bookname: '哈喽哈喽', // 作者名 author: 'XXX', // 出版社 publisher: 'XXX' } }).then(({ data }) => { console.log(data) }) </script> </body> </html>
6. form
表单
表单域:用来收集信息,一定要有
name
属性,name
的值就是 参数名表单按钮:用于提交数据的按钮
type
默认是submit
,可以省略不写,不能写别的
<!-- 表单标签 --> <form action=""> <!-- 表单域:用来收集信息,一定要有 name 属性, name 的值就是 参数名 --> <input type="text" name="username"> <input type="password" name="password"> <!-- 表单按钮:用于提交数据的按钮 --> <!-- type 默认是 submit,可以省略不写,不能写别的 --> <!-- <input type="submit" value="提交"> --> <button type="submit">提交</button> </form>
6.1 表单 GET
方式提交
action
: 接口的url
地址 把表单采集到的数据,提交到哪个接口中
method
:GET
或POST
数据的提交方式(默认值为GET
)
enctype
:application/x-www-form-urlencoded
-- 数据的编码格式。具体指的是:`multipart/form-data` 把表单数据提交给服务器之前,如何对将要提交的数据进行编码 `text/plain`(很少用) (默认值 `application/x-www-form-urlencoded`)注意:
enctype
属性只能搭配POST
提交方式一起使用;如果是GET
提交,则enctype
没有意义!
<!-- 使用 form 表单做 GET 方式的提交 --> <!-- 指定接口地址和请求 --> <form action="http://www.liulongbin.top:3009/api/form" method="GET"> <div> <span>用户名:</span> <input type="text" name="username"> </div> <div> <span>密码:</span> <input type="password" name="password"> </div> <button type="submit">提交</button> </form>
6.2 表单 POST
方式提交
<!-- 使用 form 表单做 POST 方式的提交 --> <!-- 指定接口地址和请求 --> <!-- enctype 默认 为application/x-www-form-urlencoded,所以一般可以省略不写 --> <form action="http://www.liulongbin.top:3009/api/form" method="POST" enctype="application/x-www-form-urlencoded"> <div> <span>用户名:</span> <input type="text" name="username"> </div> <div> <span>密码:</span> <input type="password" name="password"> </div> <button type="submit">提交</button> </form>
6.3 enctype
属性的区别
enctype
属性的作用:把请求体里面的参数进行编码,GET
没有请求体,在发送GET
请求时设置该属性没有意义功能:约定参数格式
application/x-www-form-urlencoded
最常用,参数都是文字、数字、布尔,不写默认就是该值
multupart/form-data
上传文件text/plain 几乎不用,纯文本
6.4 结合from
和ajax
from只用来收集数据
ajax
负责提交
submit
提交事件 阻止默认行为e.preventDefault()
7. axios请求方法的别名
GET
的请求方法的别名语法:
axios.get()
第一个参数,必填,url地址
第二个参数,选填,对象,对象中设置params,参数
POST
请求方法的别名第一个参数,必填,url地址
第二个参数,选填,直接就是参数对象
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>axios请求方法的别名</title> </head> <body> <script src="./lib/axios.js"></script> <script> // 1. GET的请求方法的别名 // 语法:axios.get() // 第一个参数,必填,url地址 // 第二个参数,选填,对象,对象中设置params,参数 // 1.1 没有传参 axios.get('http://www.liulongbin.top:3009/api/get').then(({data}) => { console.log(data); }) // 1.2 传参 axios.get('http://www.liulongbin.top:3009/api/get',{ params: { name: 'hh', age: 99 } }).then(({data}) => { console.log(data); }) // 2. POST 请求方式 // 第一个参数,必填,url地址 // 第二个参数,选填,直接就是参数对象 // 2.1 没有参数 axios.post('http://www.liulongbin.top:3009/api/post').then(({data}) => { console.log(data); }) // 2.1 传参 axios.post('http://www.liulongbin.top:3009/api/post',{ name: 'HiHi', age: 24 }).then(({data}) => { console.log(data); }) </script> </body> </html>
8. 设置请求的全局根路径
axios.defaults.baseURL = '根路径'
<script src="./lib/axios.js"></script> <script> // 设置全局请求根路径 axios.defaults.baseURL = 'http://www.liulongbin.top:3006' // 设置好全局根路径后,后续的请求就不用在添加根路径 axios.get('/api/get').then(res => { console.log(res); }) </script>
9. 拦截器
什么是拦截器
拦截器(interceptors)用来全局拦截 axios 的每一次请求与响应。
好处:可以把每个请求中,某些重复性的业务代码封装到拦截器中,提高代码的复用性。
<script src="./lib/axios.js"></script> <script> // 什么是拦截器 // 拦截器(interceptors)用来全局拦截 axios 的每一次请求与响应。 // 好处:可以把每个请求中,某些重复性的业务代码封装到拦截器中,提高代码的复用性。 // 1. axios拦截器可以拦截什么? // 拦截axios每一次的请求和响应 // 2. 拦截器的好处是什么? // 可以封装请求和响应中重复的业务代码 // 添加请求拦截器 axios.interceptors.request.use(function (config) { // 在发送请求之前做些什么 // 打开弹窗 loading.style.display = 'block' return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); }); // 添加响应拦截器 axios.interceptors.response.use(function (response) { // 对响应数据做点什么 // 关闭弹窗 loading.style.display = 'none' return response; }, function (error) { // 对响应错误做点什么 // 失败也需要关闭弹窗 loading.style.display = 'none' return Promise.reject(error); }); const get = document.querySelector('#get') const post = document.querySelector('#post') const loading = document.querySelector('#loading-box') get.addEventListener('click', function() { // 请求打开弹窗 // loading.style.display = 'block' axios.get('http://www.liulongbin.top:3009/api/get').then(({data}) => { // 请求关闭弹窗 // loading.style.display = 'none' console.log(data); }) }) post.addEventListener('click', function() { // 请求打开弹窗 // loading.style.display = 'block' axios.post('http://www.liulongbin.top:3009/api/post').then(({data}) => { // 请求关闭弹窗 // loading.style.display = 'none' console.log(data); }) }) </script>
10. 使用FormData
FormData
概念:
FormData
是浏览器提供的一个 WebAPI,它以键值对的方式存储数据。作用:
FormData
配合 Ajax 技术,能够向服务器发送multipart/form-data
格式的请求体数据。典型应用场景:FormData + Ajax 技术实现文件上传的功能。
<script src="./lib/axios.js"></script> <script> // FormData // 概念:FormData 是浏览器提供的一个 WebAPI,它以键值对的方式存储数据。 // 作用:FormData 配合 Ajax 技术,能够向服务器发送 multipart/form-data 格式的请求体数据。 // 典型应用场景:FormData + Ajax 技术实现文件上传的功能。 const fd = new FormData() fd.append('username', '张三') fd.append('age', 20) // 无法直接用log查看 // console.log(fd) // 1. fd.get 方法接收数据名作为参数,作用是获取与数据名对应值 console.log(fd.get('username')) // 2.fd.delete 方式接收数据名作为参数,作用删除数据名对应的数据 fd.delete('username') console.log(fd.get('username')) // 3.forEach() fd.forEach((value, key) => { console.log(key + ':' + value); }) </script>
11. FormData
结合ajax发送数据
<script src="./lib/axios.js"></script> <script> // 准备 FormData 数据 const fd = new FormData() fd.append('name', 'Hii') fd.append('age', '24') // 使用 ajax 发送请求 axios.post('http://www.liulongbin.top:3009/api/formdata', fd).then(({data}) => { console.log(data); }) </script>
12. XMLHttpRequest
是浏览器内置的一个构造函数
作用:基于
new
出来的XMLHttpRequest
实例对象,可以发起 Ajax 的请求。
12.1 xhr的基本使用
创建
XMLHttpRequest
对象 -- readyState 为 0
const xhr = new XMLHttpRequest()
调用
open
方法,设置请求方式和请求地址 -- readyState 为 1第一个参数:请求方式
第二个参数:请求的地址
xhr.open('请求方式', '请求地址')
调用
send
方法,发起请求 -- readyState 为 2
xhr.send()
readyState 为 3,表示数据正在传输的过程中
监听事件(两种形式),获取服务端返回的数据
load
事件是后台返回数据全部加载完毕的事件readyState 为 4,表示传输完毕
// 1. 创建 XMLHttpRequest 对象 const xhr = new XMLHttpRequest() // readyState 为 0 // 2. 调用 open 方法,设置请求方式和请求地址 // 第一个参数:请求方式 // 第二个参数:请求的地址 xhr.open('get', 'http://www.liulongbin.top:3009/api/get') // readyState 为 1 // 3. 调用 send 方法,发起请求 xhr.send() // readyState 为 2 // readyState 为 3,表示数据正在传输的过程中 // 4. 监听事件(两种形式),获取服务端返回的数据 // 第一种形式 // load 事件是后台返回数据全部加载完毕的事件 xhr.addEventListener('load', function() { // 获取服务器的返回结果 console.log(xhr.response) }) // 第二种形式 xhr.addEventListener('readystatechange', function() { // readyState 为 4,表示传输完毕 if ( xhr.readyState === 4 ) { // 传输完成,可以获取数据了 console.log(xhr.response) } })
12.2 使用xhr发起get
请求
在后面直接拼接要传的参数
<script> const xhr = new XMLHttpRequest() xhr.open('GET', 'http://www.liulongbin.top:3009/api/get?name=张三&age=18') xhr.send() xhr.addEventListener('load', function() { console.log(xhr.response); }) </script>
12.3 使用xhr发起post
请求
请求体参数应放在
send
参数里设置请参数的编码格式
2.1 设置请求头
xhr.setRequestHeader('设置请求头', '编码格式')
第一个参数为设置请求头(固定单词)
第二个参数为设置编码格式
<script> const xhr = new XMLHttpRequest() // 发起 post 请求,open的第一个参数要设置成post // 在 url 后面的参数叫做查询参数 xhr.open('POST', 'http://www.liulongbin.top:3009/api/post') // 请求体参数应放在send参数里 // 设置请参数的编码格式 // 设置请求头 // 第一个参数为设置请求头(固定单词) // 第二个参数为设置编码格式 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') // send 字符串 xhr.send('name=张三&age=18') xhr.addEventListener('load', function() { console.log(xhr.response); }) </script>
13. 数据交换格式
数据交换格式:
数据交换格式,就是服务器端与客户端之间数据传输的格式。两种数据交换格式:
1. `XML`(很少用) 2. `JSON`(主流)
14. JSON
字符串
JSON
格式用字符串表示对象或数组 就是 `JSON`格式规定:
key 必须使用双引号包裹
value 必须是 字符串(必须用双引号包裹)、数字、布尔值、
null
、数组、对象类型
JSON
操作:
序列化:把真实的数据转换为字符串的过程
反序列化:把字符串转换为真实数据的过程
// 数据交换格式 // 数据交换格式,就是服务器端与客户端之间数据传输的格式。 // 两种数据交换格式: // 1. XML(很少用) // 2. JSON(主流) // JSON 格式 // 用字符串表示对象或数组 就是 JSON // 格式规定: // 1. key 必须使用双引号包裹 // 2. value 必须是 字符串(必须用双引号包裹)、数字、布尔值、null、数组、对象类型 // JSON 格式写法 const data = '{"name":"张三","age":18,"isOver":false}' // 数组中只能放 字符串(必须用双引号包裹)、数字、布尔值、null、数组、对象(符合json对象字符串的规则) const arr = '["abc",18,{"name":"zs"}]' // JSON 操作 // 1. 序列化:把真实的数据转换为字符串的过程 // 2. 反序列化:把字符串转换为真实数据的过程 // 序列化:把真实数据转换为字符串的过程 // 将对象或数组转换成 JSON 字符串 console.log( JSON.stringify({name: "张三", age: 20}) ) // 反序列化:把字符串转换为真实数据的过程 // 将 JSON 转换成可以操作的对象或数组 console.log( JSON.parse(data) )
15. 在 ajax 中使用 JSON
发送数据时可以发送
json
一定要设置
json
格式的请求头
xhr.setRequestHeader('Content-Type', 'application/json')
后端给我们返回的是 JSON 不好操作
使用反序列化,将 json 转换成可以操作的对象
console.log( JSON.parse(xhr.response) )
const obj = { name: "张三", age: 18 } const xhr = new XMLHttpRequest() xhr.open('POST', 'http://www.liulongbin.top:3009/api/post') // 发送数据时可以发送 json // 1. 一定要设置 json 格式的请求头 xhr.setRequestHeader('Content-Type', 'application/json') // 为了操作方便,先创建对象,使用序列化将对象转换成json xhr.send( JSON.stringify(obj) ) xhr.addEventListener('load', function() { // 后端给我们返回的是 JSON 不好操作 // 使用反序列化,将 json 转换成可以操作的对象 console.log( JSON.parse(xhr.response) ) })
16. 案例 - 封装自己的 ajax
js 文件
html 文件
-
js 文件
function ajax(options) { // 处理 params 参数 // 先判断调用者是否传递了 params 参数对象 if (options.params) { // 准备数组 const arr = [] // 遍历 params 对象 for(let key in options.params) { // 按照 key = value 的格式保存到数组中 arr.push(`${key}=${options.params[key]}`) } // 判断数组中是否有内容,判断 params 是不是空 if(arr.length > 0) { options.url += '?' + arr.join('&') } } // 1. 创建 xhr const xhr = new XMLHttpRequest() // 2. 调用 open 设置请求方式和请求地址,通过传递过来的参数设置 xhr.open(options.method, options.url) // 处理 data 的参数 // POST 的时候才需要处理 data if ( options.method.toUpperCase() === 'POST' && options.data ) { // 1. 如果 options.data 是一个对象,application/json if ( typeof options.data === 'object' && !(options.data instanceof FormData) ) { // 设置编码格式 xhr.setRequestHeader('Content-Type', 'application/json') // 发送数据 xhr.send( JSON.stringify(options.data) ) } // 2. 如果 options.data 是一个字符串,application/x-www-form-urlencoded if ( typeof options.data === 'string' ) { // 设置编码格式 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') // 发送数据 xhr.send(options.data) } // 3. 如果 options.data 是一个FormData if ( options.data instanceof FormData ) { // 直接发给后台 xhr.send(options.data) } } else { // 3. 调用 send xhr.send() } // 4. 监听完成事件 xhr.addEventListener('load', function() { // 把服务器返回的数据传给函数的调用者,调用 success 回调 options.success( JSON.parse(xhr.response) ) }) }
-
html 文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>封装自己的ajax</title> </head> <body> <script src="./js/myAjax.js"></script> <script> let fd = new FormData() fd.append('name', 'zs') fd.append('age', 18) ajax({ method: 'POST', url: 'http://www.liulongbin.top:3009/api/post', // data: fd, // data: {name:'zs', age:18}, data: 'name=zs&age=18', success: function (res) { console.log(res); } }) </script> </body> </html>
17. 防抖(debounce)
用来优化 ajax 的
频繁触发某个操作时,只执行最后一次
过程:
在事件外面声明定时器 id 变量
在每一次事件触发时都干掉上一次的 timer
定义一个延迟定时器,在定时器内执行事件的逻辑,保存定时器 id
<script> // 需求:输入时打印输入框的文字 // 1. 在事件外面声明定时器 id 变量 let timer = null let inp = document.querySelector('.ipt') inp.addEventListener('input', function() { // 用 console.log 来替代 ajax 请求 // 防抖:只打印输入完成的全部内容,之前输入的不做处理 - 防抖 // 2. 在每一次事件触发时都干掉上一次的 timer clearTimeout(timer) // 3. 定义一个延迟定时器,在定时器内执行事件的逻辑,保存定时器 id timer = setTimeout(() => { console.log(inp.value) }, 500) }) </script>
18. 节流(throttle)
节流阀,标识符,标记当前是否有一个定时器
在事件中判断是否有一个定时器
在定时任务纲要创建时,把节流阀修改状态
创建定时器 -- 要执行的逻辑
把节流阀修改回初始状态
<script> // 需求每点赞一次就像后台发送一次请求 const box = document.querySelector('#box') // 1. 节流阀,标识符,标记当前是否有一个定时器 let isOk = false box.addEventListener('click',function() { // 2. 在事件中判断是否有一个定时器 // 如果有就 return,后续代码不再执行,如果没有就继续执行 if (isOk) return // 3. 在定时任务纲要创建时,把节流阀修改状态 isOk = true // 4. 创建定时器 setTimeout(() => { // 要执行的逻辑 console.log('你点击了1次'); // 5. 把节流阀修改回初始状态 isOk = false },3000) }) </script>
Git
1. 什么是 Git
-
Git 是一个开源的分布式版本控制系统,是目前世界上最先进、最流行的版本控制系统。
2. Git 中的三个区域
-
分别是工作区、暂存区、Git仓库
-
工作区:正在写的项目文件夹区域
-
暂存区:临时存放已经完成的文件区域
-
Git仓库:把最终存放的文件区域
-
3. Git 中的三个状态
-
本别是已修改、已暂存、已提交
-
已修改:表示修改了文件,但还没 将修改的结果放到暂存区
-
已暂存:表示对已修改文件的当前 版本做了标记,使之包含 在下次提交的列表中
-
已提交:表示文件已经安全地保存 在本地的 Git 仓库中
-
4. 本的 Git 工作流程
-
在工作区中修改文件
-
将你想要下次提交的更改进行暂存
-
提交更新,找到暂存区的文件,将快照永久性 存储到 Git 仓库
5. 安装并配置 Git
5.1 在 Windows 中下载并安装 Git
在开始使用 Git 管理项目的版本之前,需要将它安装到计算机上。可以使用浏览器访问如下的网址,根据自 己的操作系统,选择下载对应的 Git 安装包: Git - Downloads
5.2 配置用户信息
-安装完 Git 之后,要做的第一件事就是设置自己的用户名和邮件地址。因为通过 Git 对项目进行版本管理的 时候,Git 需要使用这些基本信息,来记录是谁对项目进行了操作:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8J2MNmTC-1650621958370)(./images/git配置.png)]
5.3 Git 的全局配置文件
通过 git config --global user.name 和 git config --global user.email 配置的用户名和邮箱地址,会被写 入到 C:/Users/用户名文件夹/.gitconfig 文件中。这个文件是 Git 的全局配置文件,配置一次即可永久生效 。 可以使用记事本打开此文件,从而查看自己曾经对 Git 做了哪些全局性的配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sQ37yDkc-1650621958371)(./images/git5.3.png)]
5.4 检查配置信息
除了使用记事本查看全局的配置信息之外,还可以运行如下的终端命令,快速的查看 Git 的全局配置信息:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WDKpSFGV-1650621958372)(./images/git5.4.png)]
6. Git 基本操作
6.1 获取Git仓库 - git init
注:每个项目用一次
git init
将本地的目录初始化为Git仓库
-
将本地的目录初始化为Git仓库(本地已有或刚创建项目时)
-
打开项目文件夹
-
在该文件夹中右键->Git Bash Here 打开Git命令
-
在命令行输入
git init
-
-
从远程服务器克隆Git仓库(项目要保存到网上或者公司的服务器已有项目)
6.2 查看文件状态 - git status
git status
【推荐】查看文件状态
git status -s
以精简方式查看文件状态,这个是下面的简写
git status --short
以精简方式查看文件状态
git status # 以精简方式查看文件状态,下面两个命令是一回事,第一个是第二个的简写 git status -s git status --short # 未跟踪文件前面有红色的 ?? 标记,
6.3 将工作区的代码提交到暂存区 - git add .
注:每一个新文件或每对一个文件进行修改后
git add 文件名
将工作区某一个文件的代码提交到暂存区
git add .
【推荐】将工作区的所有文件的代码提交到暂存区
git add 文件名 # 下面这条是常用名,将文件夹内所有的文件(新建的文件或已修改的文件)都添加到暂存区 git add .
6.4 将暂存区的文件提交到Git仓库 - git commit -m "本次提交的注释消息"
建议:每完成一个功能提交一次
git commit -m "本次提交的注释消息"
git commit -m "本次提交的注释信息"
6.5 流程
# 1. 在项目文件夹中初始化git仓库,一个项目一次 git init # 2. 将新建文件或修改后的文件提交到暂存区 git add . # 3. 将暂存区的文件提交到git仓库 git commit -m "提交的注释消息" # 2-3步是频繁执行,只要有新内容提交,就需要执行
7. Git 忽略文件
一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。 在这种情况下,我们 可以创建一个名为 .gitignore 的配置文件,列出要忽略的文件的匹配模式。
-
文件 .gitignore 的格式规范如下:
-
以 # 开头的是注释
-
以 / 结尾的是目录
-
以 / 开头防止递归
-
以 ! 开头表示取反
-
可以使用 glob 模式进行文件和文件夹的匹配(glob 指简化了的正则表达式)
-
所谓的 glob 模式是指简化了的正则表达式:
-
星号 * 匹配零个或多个任意字符
-
[abc] 匹配任何一个列在方括号中的字符 (此案例匹配一个 a 或匹配一个 b 或匹配一个 c)
-
问号 ? 只匹配一个任意字符
-
在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹 配所有 0 到 9 的数字)
-
两个星号 ** 表示匹配任意中间目录(比如 a/**/z 可以匹配 a/z 、 a/b/z 或 a/b/c/z 等)
-
-
-
8. 远程创建仓库连接
-
创建的仓库(创建时,最后有复选框,勾选和不勾选会导致了两种关联方式)
-
不勾选复选框(创建了空仓库)
-
在本地创建项目文件夹
-
git init
-
保证文件夹内有文件(建议新建一个README.md文件,这个是网页首页显示的)
-
git add .
-
git commit -m "备注"
-
git remota add origin 仓库地址
-
git push -u origin "master"
(-u origin master设置默认推送)
-
-
勾选复选框(创建非空仓库,或者到单位给一个非空的项目)
-
进入到要存放的项目文件夹
-
在文件夹上打开命令行
-
git clone 仓库地址(克隆/下载复制来的地址)
-
不需要做任何配置,直接进行后续的开发
-
-
开发
-
每完成一个功能(页面)
-
git add .
-
git commit -m "备注"
-
git push
234三个步骤重复执行
-
-
9. Git 分支
-
分支概念:
-
分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努 力学习SVN。
-
如果两个平行宇宙互不干扰,那对现在的你也没啥影响。
-
不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!
-
-
分支在实际开发中的作用
-
在进行多人协作开发的时候,为了防止互相干扰,提高协同开发的体验,建议每个开发者都基于分支进行项 目功能的开发
-
-
master 主分支
-
在初始化本地 Git 仓库的时候,Git 默认已经帮我们创建了一个名字叫做 master 的分支。通常我们把这个 master 分支叫做主分支。
-
作用:
-
用来保存和记录整个项目已完成的功能代码。因此,不允许程序员直接在 master 分支上修改代码,因为这样做的风险太高,容易导致整个项目崩溃。
-
-
-
功能分支
-
功能分支指的是专门用来开发新功能的分支,它是临时从 master 主分支上分叉出来的,当新功能开发且测 试完毕后,最终需要合并到 master 主分支上。
-
10. Git 本地分支操作
-
查看分支列表
# 使用如下的命令,可以查看当前 Git 仓库中所有的分支列表: # 注意:分支名字前面的 * 号表示当前所处的分支。 git branch
-
创建新分支
# 使用如下的命令,可以基于当前分支,创建一个新的分支,此时,新分支中的代码和当前分支完全一样: # 基于主分支上来创建的 复制主分支所有内容到新分支 git branch 分支名
-
切换分支
# 使用如下的命令,可以切换到指定的分支上进行开发: git checkout 分支名
-
合并分支
# 切换到主分支 git checkout 主分支名 # 把选择的功能分支合并到主分支上 git merge 功能分支名
-
分支的快速创建和切换
# 使用如下的命令,可以创建指定名称的新分支,并立即切换到新分支上: get checkout -b 分支名称
-
删除本地分支
# 当把功能分支的代码合并到 master 主分支上以后,就可以使用如下的命令,删除对应的功能分支: get branch -d 分支名称
-
遇到冲突时的分支合并
-
发生冲突后手动选择留下哪一个或者都留下
-
11. Git 远程分支操作
-
如果是第一次将本地分支推送到远程仓库,需要运行如下的命令:
例如:
# -u:把本地分支和远程分支进行关联,远程的 origin 这个仓库,本地的时 list 仓库和远程的 li 分支进行关联 git push -u origin list:li 注:后续在本地list分支推送到远程li分支的时候只需要 git push 就好了,不用加 -u 后的代码 #-u:把本地分支和远程分支进行关联,远程的 origin 这个仓库,本地的时 list 仓库和远程的 list 分支进行关联 git push -u origin list
-
查看远程仓库中所有的分支列表
git remote show 远程仓库名称
-
拉取远程分支的最新的代码
# 从远程仓库,拉取当前分支最新的代码,保持本地分支代码和远程分支代码一致 git pull
Node
1. 什么是 Node.js
Node.js
:是一个基于Chrome V8 引擎的 JavaScript
运行环境
Node.js
官方地址: Node.js — 在任何地方运行 JavaScript
注意:
-
浏览器是
JavaScript
的前端运行环境。 -
Node.js
是JavaScript
的后端运行环境。 -
Node.js
中无法调用DOM
和BOM
等浏览器内置API
2. 使用node命令执行JS文件
操作步骤:
-
打开终端
-
注意路径,进入到你要运行的项目文件目录下
-
输入
node 要执行的js文件
注意:
-
执行文件的时候,需要保证
node xxx.js
这种格式 -
node只能运行JS代码(也就是不要node xxx.html)
3. 什么是模块化
模块化就是,把一个大文件拆分成若干个小文件,而且还能把小文件通过特定的语法组合到一起实现过程。
4. 模块化规范
-
AMD - 依赖前置 (require.js)
-
CMD - 依赖就近 (sea.js)
-
CommonJS(Node中的模块化,使用的是这种方案)
-
ES6(ES6-JS的语法)
Node使用的是CommonJS规范
5. 模块的分类
-
自定义模块
-
NodeJS中,创建的JS文件都是自定义模块。(也就是处处皆模块)
-
-
内置模块(核心模块)
-
安装Node之后,自带了很多内置模块。我们可以直接加载使用他们。
-
-
第三方模块
-
其他人编写的模块,发布到 npm 网站 上,我们可以下载使用。
-
6. 自定义模块
导出文件 - 导出
方式一:
module.exports = { 变量名, 方法名,...}
方式二:
exports.xx = xx
导入文件 - 导入
引入:let xxx = require('地址URL')
console.log(xxx.变量名)
示例:
-
导出内容 (m1.js)
const PI = 3.141592645656475634534 function fn() { console.log('我是一个方法'); } // 1. 导出模块 - module.exports 的值是什么外部引入的就是什么 // 第一种方式倾向于导出一个整体 module.exports = { PI, fn } // exports是modules.exports(对象)引用 // 第二种倾向于导出多个内容 // exports.PI = PI // exports.fn = fn
-
导入内容 (userModule.js)
// 引入模块 let m1 = require('./02-m1') console.log(m1.PI); console.log(m1.fn);
7. 内置模块
内置模块是Node.js平台自带的一套基本API(功能模块)。也叫做核心模块
注意:
-
加载内置模块时,不能写路径,这是和加载自定义模块的区别
7.1 path模块
-
path
是Node本身提供的API,专门用来处理路径
示例
// 第一步:先加载要使用的模块 const path = require('path') // 或 let path = require('path') // 方法1:extname - 获取文件后缀 console.log(path.extname('index.html')) // .html console.log(path.extname('readme.md')) // .md // 方法2:join - 智能拼接路径 console.log(path.join('./', 'aa', 'cc', '../bb', 'c')) // aa\bb\c console.log(path.join('./', 'html', 'index.html')) // html\index.html // __dirname - 表示当前js文件的绝对路径 console.log(path.join(__dirname, 'css', 'index.css')) // F:\黑马程序员\前端就业班\07-node.js\day01\code\lianXi\css\index.css
7.2 fs模块
-
fs,即 file system,文件系统,该模块可以实现对文件、文件夹的操作
示例
// 第一步:引入 fs 模块 const fs = require('fs') // 方法1:readFile() - 读取文件 // 第一个形参:必填 - 读取文件的路径 // 第二个形参:可选 - 编码的格式 // 第三个形参:必填 - 回调,文件读取成功要做的那些事 fs.readFile('./test', 'utf-8', function (err,data) { // 该回调函数里有两个形参 // 第一个形参:错误对象,当读取错误时会出现的对象,读取正确的时候为 null // 第二个形参:读取文件的结果 // 如果读取错误返回一条错误信息 并结束 if (err !== null) return console.log('读取出错啦~') // 执行完毕没错的时候执行 console.log(data) }) // 方法2:writeFile() - 写入文件 注:会覆盖之前文件的内容 // 第一个形参:必填 - 写入文件的路径,没有会进行创建 // 第二个形参:必填 - 要写入的数据,可以放对象等等 // 第三个形参:可选 - 编码的格式,不写默认utf-8 // 第四个形参:可选 - 写入完成后的回调函数 fs.writeFile('./test2.txt', '这里是要写入的内容', function (err) { if (err !== null) return console.log('写入出错了!!!') console.log('写入成功~') })
7.3 http模块
-
目标:实现网站服务器
// 引入 http 模块 const http = require("http") //引入 fs 模块 const fs = require("fs") // 2. 创建服务器对象 const server = http.createServer() // 3. 监听客户端的请求 server.on("request", function (request, response) { // 当客户端请求,就会触发这个回调函数 // request请求对象,包含客户端请求时携带的一些信息(参数,路径,请求头。。。) // 指定就是端口号后面的路径 - 获取get console.log(request.url) // /favicon.ico // 获取 请求方式 console.log(request.method) // GET // response相应对象,返回数据时使用 console.log("有人访问了我们的服务器") //相应给客户端 //相应数据 // response.end('{ "name" : "zs", "age" : 18}') // 相应文件的写法 // 解决中文乱码 response.setHeader("Content-Type", "text/html;charset=utf-8") fs.readFile("./index.html", "utf8", function (err, data) { if (err !== null) { return response.end("404") } response.end(data) }) }) // 4. 启动服务器 // 第一个参数:端口号,必填 // 第二个参数:运行成功回调,选填 server.listen(80, function () { console.log("服务器启动成功,运行在 http://127.0.0.1:80") }) // 终止服务器运行 Ctrl+c
8. NPM
8.1 初始化操作
npm init
npm init -y
npm下载 淘宝源
npm config set registry https://registry.npm.taobao.org
-
示例
# 初始化,需要输入项目信息(可以使用默认,但是项目名称不能为中文不然会报错),输入完成后,可以一路回车 npm init # 初始化,直接全部采用默认信息 npm init -y
-
注意:
-
初始化之后,会在项目目录中生成 package.json 的文件。
-
package name 默认使用当前文件夹 当做 包的名字
-
package name 不能有中文、不能有特殊符号、不能和需要安装的第三方模块同名
-
-
建议:
-
初始化完毕后的操作 - 这样下载国外网址的包会更快
建议在安装第三方模块之前,先执行如下命令。 下面的命令只需要执行一次即可(不管以后重启vscode还是重启电脑,都不需要执行第二次) npm config set registry https://registry.npm.taobao.org
-
8.2 下载包
npm install 包名
npm install 模块名 模块名 模块名
npm install -D 包名
npm install -g 包名
npm ls -g --depth 0
npm install
-
下载安装第三方模块
# 正常的下载安装 npm inster 包名 # 下载指定版本依赖 npm i 包名@版本号 # 下载多个包 npm install 包名 包名 包名... # 下载开发依赖 npm install -D 包名 # 或 npm install --save-d 包名 # 下载全局 - 下载工具性质的包,比如vue-cli(创建vue项目的脚手架工具)、nrm(切换下载源) npm install -g nrm # mac电脑(需要管理员权限) sudo npm install -g nrm # 查看全局下载的包 npm ls -g --depth 0 # 根据package.json文件dependencies和devDependencies字段自动下载对应的包,在新接触一个项目后,第一件事就是使用该命令来安装依赖 npm install # !!!所有的 install 都可以替换为 i
-
注意:
-
代码文件夹不能有中文;代码文件夹不能和模块名同名。
-
-
关于本地模块的说明
-
下载安装的模块,存放在当前文件夹的
node_modules
文件夹中,同时还会生成一个记录下载的文件package-lock.json
-
下载的模块,在哪里可以使用
-
在当前文件夹
-
在当前文件夹的子文件夹
-
在当前文件夹的子文件夹的子文件夹
-
......
-
反过来讲,当查找一个模块的时候,会在当前文件夹的 node_modules 文件夹查找,如果找不到,则去上层文件夹的node_modules文件夹中查找,.....依次类推。
-
-
8.3 删除包
npm uninstall 包名
npm unin
# 删除指定第三方包 npm uninstall 包名 # 简化写法 npm un 包名 # 删除多个 npm un 包名 包名... # 删除全局包 npm un -g 包名
8.4 使用第三方包
// 1. 任何包第一步都需要引入,require里面直接写包名 const pkg = require('包名') // 2. 参考文档使用包 // 2.1 去官方找,比较优秀的包都是官网的,例如:vue,react,angular,lodash... // 2.2 npmjs.com 搜索包,包主页会有文档 // 2.3 百度搜
8.5 nrm的使用
作用:用来切换下载镜像源
nrm ls
nrm user 源名
# 以列表的形式查看源 nrm ls # 切换源 nrm use 源名
8.6 package.json 文件
{ "name": "npm-test", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "ipconfig" }, "author": "", "license": "ISC", "dependencies": { "cors": "^2.8.5", "jquery": "^3.6.0", "moment": "^2.29.3", "sass": "^1.51.0" } }
-
name
- 项目名,不能有中文 -
version
- 版本号,只能数字和点 -
description
- 描述 -
main
- 项目的入口文件是哪一个文件 -
scripts
- 声明简化命令
{ "简化命令": "真实命令(命令行能用的名)", "start": "node ./index.js", // "start": "ipconfig" } // 执行简化命令,在命令行中输入 npm run 简化命令 // start特殊,start可以 npm run start 也可以 npm start
-
author
- 作者 -
license
- 许可证 -
dependencies
- 项目运行依赖(第三方包的信息,这里包含的包只要没有,项目就无法运行) -
devDependencies
- 项目开发依赖(开发中会用到,在项目运行时不会用,例如:less,sass)
9. requeire加载机制
-
判断缓存中有没有,如果有,使用缓存中的内容
-
缓存中没有,那么表示第一次加载,加载完会缓存
-
判断模块名有没有带路径(./)
-
模块名中有路径,加载自定义模块(自己写的文件)
const xx = require('./xx')
-
优先加载同名文件,加载一个叫做 xx 的文件
-
再次加载js文件,加载 xx.js 文件
-
再次加载json文件,加载 xx.json 文件
-
最后加载node文件,加载 xx.node文件
-
如果上述文件都没有,则报错 “Cannot find module './xx'”
-
-
模块名没有路径,优先加载核心模块,如果没有核心模块,则加载第三方模块
-
加载第三方模块的查找方式
-
优先在当前文件夹的node_modules里面查找第三方模块
-
在当前文件夹的上级目录的node_modules里面查找第三方模块
-
继续向上层文件夹查找第三方模块
-
.........
-
10. 开发属于自己的包
更多关于npm的命令:npm中文文档 | npm中文网
10.1 规范的包结构
在清楚了包的概念、以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构。
📂 - sy123 📃 - package.json (package.json包的配置文件) 📃 - index.js (入口文件) 📃 - README.md (说明文档)
一个规范的包结构,需要符合以下 3 点要求:
-
包必须以单独的目录而存在
-
包的顶级目录下要必须包含 package.json 这个包管理配置文件
-
package.json 中必须包含 name,version,main 这三个属性,分别代表包的名字、版本号、包的入口。
-
name 包的名字,我们使用 require()加载模块的时候,使用的就是这个名字
-
version 版本,1.2.18
-
main 入口文件。默认是index.js 。如果不是,需要使用main指定
-
注意: 以上 3 点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:
https://yarnpkg.com/zh-Hans/docs/package-json
10.2 开发属于自己的包
-
初始化 package.json
{ "name": "sy123", // 包(模块)的名字,和文件夹同名。别人加载我们的包,找的就是这个文件夹 "version": "1.0.0", "description": "This is a package by Laotang", "main": "index.js", // 别人加载我们的模块用,require加载的就是这里指定的文件 "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ // 在npm网站中,通过关键字可以搜索到我们的模块,按情况设置 "laotang", "itcast", "test" ], "author": "Laotang", // 作者 "license": "ISC" // 开源协议 }
注意,JSON文件不能有注释,下面加注释,是为了理解。
关于更多 license 许可协议相关的内容,可参考 开源协议GPL/MIT License/Apache License - 简书
-
index.js 中定义功能方法
// 别人加载的就是我的 index.js // 所以,必须在 index.js 中导出内容 function a() { console.log('aaa') } function b() { console.log('bbb') } module.exports = { a, b }
-
编写包的说明文档
包根目录中的 README.md 文件,是包的使用说明文档。通过它,我们可以事先把包的使用说明,以 markdown 的 格式写出来,方便用户参考。
README 文件中具体写什么内容,没有强制性的要求;只要能够清晰地把包的作用、用法、注意事项等描述清楚即可。
10.3 发布npm包
# 切换npm源 - 切换镜像源为npm(不能发布到淘宝,所以必须切换镜像源为npm主站) npm use npm # 登录npm - 按照提示输入账号、密码(输入的密码是看不见的,正常)、邮箱、邮箱收到的一次性密码 npm login # 发布 - 执行命令的文件夹,必须是包的根目录 npm publish
10.4 更新npm包
# 在package.json中version,升级版本号 # 推送不上去,就使用 npm login 重新登录一下 npm publish
10.5 删除已发布npm包
npm unpublish 包名 --force
-
注意:
-
删除的包,在 24 小时内不允许重复发布(同名的包)
-
npm unpublish 删除的包,在 24 小时内不允许重复发布
-
发布包的时候要慎重,尽量不要往 npm 上发布没有意义的包!
-
11. 模块化
是一种编程方式,将整体打碎,编程一个个的模块,每个模块是独立的功能,把模块合到一起拼成完成的项目。
11.1 浏览器中使用模块
-
AMD - 依赖前置。require.js
-
CMD - 依赖就近。sea.js
注意:非官方实现规范
11.2 NodeJs中使用模块
CommonJs,require引入,module.exports导出
-
自定义模块
require('./要引入的js文件路径')
来源:自己编写
-
核心模块
require('包名')
来源:nodejs自带
-
第三方模块
require('包名')
来源:需要下载
npm install 包名 npm i 包名 npm i 包名 包名... npm i 包名@版本号 # 下载开发依赖 npm i -D 包名 # 下载全局包 npm i -g 包名
12. ES6模块化
是js官方退出,为了解决js在不同的平台没有同意模块化标准问题。
nodejs版本要在13版本以上,在package.json中声明 type: "module"
12.1 默认导入和导出
export default 要导出的内容
import 自定义名称 from '路径.js'
场景:导出一个内容
示例
导出文件 01-module.js
const PI = 3.1415926256252236 function fn() { console.log(111) } export default { PI, fn }
导入文件 01-导入.js
import m1 from './01-module.js' console.log(m1)
12.2 按需导入和导出
export let a = 1
import { 导出变量名 } from '路径.js'
示例
导出文件 02-module.js
// export 后面直接跟导出内容的完整的语句 export let a = 111 export function fn() { console.log(222) } // 这一个顶上面两个 export { a, fn } // 报错,因为没有提供接口(取数据数据名) //export 1 //export a
导入文件 02-导入
// import { 导出的值(名字不能变,导入的是什么名,导出的就是什么名,导入也要是什么名) } from '路径' // 如果要给引入的内容起别名使用 as // 语法:导出名 as 别名 import { a as a1, fn } from "./02-module.js" console.log(a1) console.log(fn)
12.3 直接导入、不导出
import './03-module.js'
示例
文件 03-module.js
for (let i = 0; i < 5; i++) { console.log(i) }
导入文件 03-直接导入.js
import './03-module.js'
13. Promise
解决问题:回调地狱
13.1 回调地狱
多个异步任务是无法保证执行顺序的,当我们想让多个异步任务按照固定的顺序执行,写出来的代码就是回调地狱
回调地狱 - 示例
import fs from 'fs' fs.readFile('./a.txt', 'utf-8', function (err, data) { if (err) return console.log(data) fs.readFile('./b.txt', 'utf-8', function (err, data) { if (err) return console.log(data) fs.readFile('./c.txt', 'utf-8', function (err, data) { if (err) return console.log(data) }) }) })
13.2 Promise - 基本语法
-
定义Promise对象
const p1 = new Promise((resolve,reject) => { // 成功时执行resolve,比如成功获取服务端数据,读取文件内容成功。 // 失败时执行reject,因为参数错误(权限不够)导致业务执行失败 // resolve(想传给外面的数据,比如成果获取到服务数据或读取到文件内容) // reject(失败信息) })
-
使用
p1.then( // 必填,调用resolve时执行,参数就是resolve中的参数 res => {}, // 可选,调用reject时执行,参数就是reject中的参数 err => {} )
13.3 三种状态
-
最初状态:pending,等待中,此时promise的结果为 undefined;
-
当 resolve(value) 调用时,达到最终状态之一:fulfilled,(成功的)完成,此时可以获取结果value
-
当 reject(error) 调用时,达到最终状态之一:rejected,失败,此时可以获取错误信息 error
当达到最终的 fulfilled 或 rejected 时,promise的状态就不会再改变了。
特点
当调用 resolve的时候,Promise 将到达最终的状态。 达到最终状态之后,Promise的状态就不会再改变了。
多次调用 resolve 函数,只有第一次有效,其他的调用都无效。
const fs = require('fs'); // 1. 创建 Promise 对象;(承诺) let p = new Promise((resolve, reject) => { resolve(123); resolve(456); // 这次调用无效 }); // 2. 获取异步任务的结果 // p.then(函数1, [函数2]); p.then(res => { console.log(res); // 123 }, err => { console.log(err); });
13.4 同步异步
Promise
构造函数,是同步的
then
里面的函数,是异步的
// 同步 console.log(1) const p1 = new Promise(resolve => { // 同步 console.log(2) resolve() console.log(3) }) p1.then(() => { // 异步 console.log(4) }) // 同步 console.log(5) // 结果:12354
13.5 then方法的链式调用
-
前一个then里面返回的字符串,会被下一个then方法接收到。但是没有意义;
-
前一个then里面返回的Promise对象,并且调用resolve的时候传递了数据,数据会被下一个then接收到
-
前一个then里面如果没有调用resolve,则后续的then不会接收到任何值
14. async 和 await 修饰符
ES6 --- ES2015
async 和 await 是 ES2017 中提出来的。
异步操作是 JavaScript 编程的麻烦事,麻烦到一直有人提出各种各样的方案,试图解决这个问题。
从最早的回调函数,到 Promise 对象,再到 Generator 函数,每次都有所改进,但又让人觉得不彻底。它们都有额外的复杂性,都需要理解抽象的底层运行机制。
异步I/O不就是读取一个文件吗,干嘛要搞得这么复杂?异步编程的最高境界,就是根本不用关心它是不是异步。
==async 函数就是隧道尽头的亮光,很多人认为它是异步操作的终极解决方案==。
ES2017 提供了async和await关键字。await和async关键词能够将异步请求的结果以返回值的方式返回给我们。
-
async 用于修饰一个 function
-
async 修饰的函数,总是返回一个 Promise 对象
-
函数内的返回值,将自动包装在 resolved 的 promise 中
-
-
await 只能出现在 async 函数内
-
await 让 JS 引擎等待直到promise完成并返回结果
-
语法:let value = await promise对象; // 要先等待promise对象执行完毕,才能得到结果
-
由于await需要等待promise执行完毕,所以await会暂停函数的执行,但不会影响其他同步任务
-
-
对于错误处理,可以选择在async函数后面使用
.catch()
或 在promise对象后使用.catch()
const fs = require('fs'); // 将异步读取文件的代码封装 function myReadFile (path) { return new Promise((resolve, reject) => { fs.readFile(path, 'utf-8', (err, data) => { err ? reject(err) : resolve(data.length); }); }).catch(err => { console.log(err); }); } async function abc () { let a = await myReadFile('./a.txt'); let b = await myReadFile('./b.txt'); let c = await myReadFile('./c.txt'); console.log(b); console.log(a); console.log(c); } abc();
错误处理
前提是得到几个Promise对象,代码如下:
let fs = require('then-fs'); let p1 = fs.readFile('./files/a.txt', 'utf-8'); let p2 = fs.readFile('./files/bbb.txt', 'utf-8'); // 注意,这里故意写错路径 let p3 = fs.readFile('./files/c.txt', 'utf-8');
获取Promise的结果,可以通过 then 来获取。也可以通过 async 和 await 获取。
如果使用 then 获取结果,那么错误如何处理:在链式调用的尾端,加一个catch方法即可
// ---------------------- 通过 then 获取结果 ---------------------------- p1.then(res => { console.log(res.length); return p2; }).then(res => { console.log(res.length); return p3; }).then(res => { console.log(res.length); }).catch(err => { console.log(err); })
如果使用async和await获取结果,最好的错误处理方案,就是使用 try ... catch ...
// ---------------------- 通过 async/await 获取结果 ---------------------------- async function abc() { try { // 尝试做一些事情 let r1 = await p1; // 正常得到结果 let r2 = await p2; // 这里出错了,就会抛出错误 throw err。 let r3 = await p3; console.log(r1.length, r2.length, r3.length); } catch (e) { console.log(e); // catch这里,会抓住前面try里面抛出的错误 } } abc();
15. 宏任务和微任务、事件循环
JavaScript是单线程的,也就是说,同一个时刻,JavaScript只能执行一个任务,其他任务只能等待。
15.1 为什么JavaScript是单线程的
js是运行于浏览器的脚本语言,因其经常涉及操作dom,如果是多线程的,也就意味着,同一个时刻,能够执行多个任务。
试想,如果一个线程修改dom,另一个线程删除dom,那么浏览器就不知道该先执行哪个操作。
所以js执行的时候会按照一个任务一个任务来执行。
15.2 为什么任务要分为同步任务和异步任务
试想一下,如果js的任务都是同步的,那么遇到定时器、网络请求等这类型需要延时执行的任务会发生什么?
页面可能会瘫痪,需要暂停下来等待这些需要很长时间才能执行完毕的代码
所以,又引入了异步任务。
-
同步任务:同步任务不需要进行等待可立即看到执行结果,比如console
-
异步任务:异步任务需要等待一定的时候才能看到结果,比如setTimeout、网络请求
16. 事件循环(Event Loop)
事件循环的比较简单,它是一个在 "JavaScript 引擎等待任务","执行任务"和"进入休眠状态等待更多任务"这几个状态之间转换的无限循环。
引擎的一般算法:
-
当有任务时:
-
从最先进入的任务开始执行。
-
-
没有其他任务,休眠直到出现任务,然后转到第 1 步。
17. 任务队列
根据规范,事件循环是通过任务队列的机制来进行协调的。一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。setTimeout/Promise 等API便是任务源。
在事件循环中,每进行一次循环的关键步骤如下:
-
在此次循环中选择最先进入队列的任务(oldest task),如果有则执行(一次)
-
检查是否存在 微任务(Microtasks),如果存在则不停地执行,直至清空 微任务队列(Microtasks Queue)
-
更新 render(DOM渲染)
-
以上为一次循环,主线程重复执行上述步骤
在上述循环的基础上需要了解几点:
-
JS分为同步任务和异步任务
-
同步任务都在主线程上执行,形成一个执行栈
-
主线程之外,宿主环境管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
-
一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BZFDf1R5-1653472547308)(F:\黑马程序员\前端就业班\07-node.js\02-案例\项目前置课-03\文档\其他.assets\bV31Xm)]
17.1 宏任务
(macro)task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
任务(代码) | 宏任务 | 环境 |
---|---|---|
script | 宏任务 | 浏览器 |
事件 | 宏任务 | 浏览器 |
网络请求(Ajax) | 宏任务 | 浏览器 |
setTimeout() 定时器 | 宏任务 | 浏览器 / Node |
fs.readFile() 读取文件 | 宏任务 | Node |
比如去银行排队办业务,每个人的业务就相当于是一个宏任务;
17.2 微任务
微任务(microtask)是宏任务中的一个部分,它的执行时机是在同步代码执行之后,下一个宏任务执行之前。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iOSCSokB-1653472547310)(F:\黑马程序员\前端就业班\07-node.js\02-案例\项目前置课-03\文档\其他.assets\image-20210418173900754.png)]
微任务包含:
Promise.then process.nextTick(Node.js 环境)
比如一个人,去银行存钱,存钱之后,又进行了一些了操作,比如买纪念币、买理财产品、办信用卡,这些就叫做微任务。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D1IICVgU-1653472547311)(F:\黑马程序员\前端就业班\07-node.js\02-案例\项目前置课-03\文档\其他.assets\image-20210326115124196.png)]
17.3 运行机制
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
-
执行一个宏任务(执行栈中没有就从事件队列中获取)
-
执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
-
宏任务里的同步代码执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
-
当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
-
渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8S6A8DeZ-1653472547312)(F:\黑马程序员\前端就业班\07-node.js\02-案例\项目前置课-03\文档\其他.assets\image-20210623200912106.png)]
18. webpack 的基本使用
初级使用步骤
-
在项目根目录创建 webpack.config.js 文件
-
在这个文件内写一下代码
const path = require('path') module.exports = { // 打包方式 - development 开发环境(编写程序的时候使用), production 生产环境(项目完成上线的时候) mode: 'development', // 决定了 main.js 编译的方式 // 修改入口文件 entry: './src/index1.js', // 修改出口文件(打包后的文件路径和文件名) output: { // 指定打包好后文件的路径 path: path.join(__dirname, 'dist'), // 指定打包好后文件的文件文件夹名称 filename: 'a.js' } }
-
在package.json中script内声明
"build": "webpack"
-
运行简化命令行
npm run build
-
在html中引入生成的main.js
注意
-
默认的打包入口文件为 src -> index.js
-
默认的输出文件路径为 dist -> main.js
-
可以在 webpack.config.js 中修改打包的默认约定
18.1 安装和配置webpack
目标:
-
通过 webpack 处理 JS 代码的兼容性问题
-
了解 webpack 的基本配置步骤。
安装 webpack 相关的两个包: webpack@5.58.2
和 webpack-cli@4.9.0
(前面已经统一安装过了)
① 在项目根目录中,创建名为 webpack.config.js
的 webpack 配置文件,并初始化如下的基本配置:
module.exports = { // 打包模式 mode: 'development', // production:生成 development:开发 }
② 在 package.json 的 scripts 节点下,新增 dev 脚本如下:
"scripts": { "dev": "webpack" },
③ 在终端中运行 npm run dev
命令,启动 webpack 进行项目的打包构建
18.2 打包结果
执行 npm run dev
命令后,会在项目根目录生成 dist
文件夹,并在 dist
文件夹中生成 main.js
main.js 就是 jquery.js 和 index.js 的结合体。
index.html 中引入 main.js ,再看效果。
18.3 mode 的可选值
目标:清楚每个 mode 值的作用,知道每个值的具体应用场景。
mode 节点的可选值有两个,分别是:
① development
-
开发环境
-
不会对打包生成的文件进行代码压缩和性能优化
-
打包速度快,适合在开发阶段使用
② production
-
生产环境
-
会对打包生成的文件进行代码压缩和性能优化
-
打包速度很慢,仅适合在项目发布阶段使用
18.4 webpack.config.js的作用
webpack.config.js是什么
-
webpack.config.js 是 webpack 的配置文件。
作用
-
告诉 webpack 怎么对项目进行打包。
被读取的时机
-
npm run dev 的时候(打包的时候),会先读取配置文件,再对项目进行打包处理。
18.5 webpack 中的默认约定
在 webpack 4.x 和 5.x 的版本中,有如下的默认约定:
① 默认的打包入口文件为 src -> index.js
② 默认的输出文件路径为 dist -> main.js
注意:可以在 webpack.config.js 中修改打包的默认约定
18.6 自定义打包的入口与出口
在 webpack.config.js 配置文件中,通过 entry 节点指定打包的入口。通过 output 节点指定打包的出口。
示例代码如下:
// 引入path 并解构出 join方法 const { join } = require('path'); module.exports = { // 打包模式 mode: 'development', // production:生成 development:开发 // 入口文件 entry: join(__dirname, 'src', 'index.js'), // 出口文件 output: { path: join(__dirname, 'dist'), // 要使用绝对路径,否则报错 filename: 'bundle.js', } }
重新运行 npm run dev
,即可得到新的打包结果 bundle.js
.
index.html 中引入新的打包结果 bundle.js。
19. webpack 中的插件
最常用的 webpack 插件有如下3个:
① clean-webpack-plugin
-
每次打包时,自动清理 dist 目录
② webpack-dev-server
-
类似于 node.js 阶段用到的 nodemon 工具
-
每当修改了源代码,webpack 会自动进行项目的打包和构建
③ html-webpack-plugin
-
webpack 中的 HTML 插件
-
可以通过此插件自定制 index.html 页面的内容
19.1 clean-webpack-plugin
作用:每次打包构建的时候,自动清理 dist 目录下的旧文件,保证 dist 目录的代码是最新的。
安装依赖包:clean-webpack-plugin@4.0.0 (前面已经统一安装过)
在 webpack.config.js 中增加配置:
// 1. 配置自动清理插件(在打包的时候,插件就会自动清理 dist 中没用的文件) const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const cleanPlugin = new CleanWebpackPlugin(); module.exports = { mode: 'development', // 其他项省略...... // 插件配置 plugins: [ cleanPlugin ] }
重新运行 npm run dev
,即可将 dist
文件夹中没用的文件清理掉。
19.2 webpack-dev-server
19.2.1 基本使用
作用:可以让 webpack 监听项目源代码的变化,从而进行自动打包构建。
安装包 webpack-dev-server@4.3.1(前面已经统一安装过)
配置 webpack-dev-server
① 修改 package.json -> scripts 中的 dev 命令如下:
"scripts": { "dev": "webpack serve" // 注意这里是 serve ,不是 server },
② 在 webpack.config.js 配置文件中,增加 devServer 节点对 webpack-dev-server 插件进行更多的配置
devServer: { port: 9000, // 实时打包所用的端口号 open: true // 初次打包完成后,自动打开浏览器 }
③ 再次运行 npm run dev 命令,重新进行项目的打包
④ 在浏览器中访问 http://localhost:8080 地址,查看自动打包效果
注意:凡是修改了 webpack.config.js 配置文件,或修改了 package.json 配置文件,必须重启实时打包的服务器,否则最新的配置文件无法生效!
19.2.2 打包生成的文件哪儿去了?
① 不配置 webpack-dev-server 的情况下,webpack 打包生成的文件,会存放到实际的物理磁盘上
-
严格遵守开发者在 webpack.config.js 中指定配置
-
根据 output 节点指定路径进行存放
② 配置了 webpack-dev-server 之后,打包生成的文件存放到了内存中
-
不再根据 output 节点指定的路径,存放到实际的物理磁盘上
-
提高了实时打包输出的性能,因为内存比物理磁盘速度快很多
19.2.3 生成到内存中的文件该如何访问?
-
webpack-dev-server 生成到内存中的文件,默认放到了项目的根目录中,而且是虚拟的、不可见的。
-
可以直接用 / 表示项目根目录,后面跟上要访问的文件名称,即可访问内存中的文件
-
例如 /bundle.js 就表示要访问 webpack-dev-server 生成到内存中的 bundle.js 文件
所以,index.html 中应该这样引入js <script src="/bundle.js"></script>
19.3 html-webpack-plugin
html-webpack-plugin 是 webpack 中的 HTML 插件。
作用:自动把生成好的 bundle.js 注入到 HTML 页面中。
安装包 html-webpack-plugin@5.3.2 (前面已经统一安装过)
在webpack.config.js中配置 html-webpack-plugin
const HtmlPlugin = require('html-webpack-plugin'); const htmlPlugin = new HtmlPlugin({ template: path.join(__dirname, 'public', 'index.html'), // public中,你的html叫什么 filename: 'index.html' // 打包后的html叫什么(这个文件会生成到dist文件夹) }); module.exports = { mode: 'development', // 其他项省略...... // 插件配置 plugins: [ cleanPlugin, htmlPlugin ] }
其他说明:
① 通过 HTML 插件复制到项目根目录中的 index.html 页面,也被放到了内存中,所以看不到
② HTML 插件在生成的 index.html 页面,自动注入了打包的 bundle.js 文件
20. webpack 中的 loader
TODO
vue
1. 什么是 vue
-
官方给出的概念:vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式**框架**。
-
构建用户界面:前端每天就是在做画用户界面的工作,vue 帮助我们以更简单的方式来做这些事情。
-
渐进式:vue 不强求程序员一次性接受并使用它的全部功能和特性。
-
框架:框架指的就是程序员必须遵守的规则或约束。
2. vue指令 - vue 中6中常见的指令
内容渲染指令(对应dom中的文本节点)
{{ }}
v-html
属性绑定指令(对应dom中的属性节点)
v-bind
:
事件绑定指令(对应dom中的事件)
v-on:事件类型="事件处理函数"
@事件类型="事件处理函数"
事件修饰符
.stop
.prevent
按键修饰符
.enter
...
双向绑定指令(vue中特有的,react中想做需要自己实现)
v-model
专属修饰符
.number
.trim
.lazy
条件渲染指令(在页面模板中写
if
)列表渲染指令(在页面模板中写
for
)
-
概念:指令(Directives)是 vue 为开发者提供的一套特殊语法。
-
vue模板:指的是被vue控制区域里面的html部分
2.1 内容渲染指令
-
{{ }}
又名:插==值==表达式
作用:将数据动态的渲染到 DOM 的内容区域
<p>姓名:{{ username }}</p> <p>性别:{{ gender }}</p>
-
v-html
作用:可以将带有HTML标签的字符串渲染成真正的 HTML 元素
<p v-html="str"></p>
2.2 属性绑定指令
-
v-bind
作用:将数据动态的绑定到元素的属性身上
语法:v-bind:属性名="属性值"
<input type="text" v-bind:placeholder="inpValue">
-
简写:
:
[推荐]<input type="text" :placeholder="inpValue">
2.3 事件绑定指令
-
语法:
v-on:事件类型="事件处理函数"
<button v-on:click="addCount"> 点击 +1 </button>
-
简写:
@事件类型="事件处理函数"
[推荐]<button @click="redCount"> 点击 -1 </button>
-
注意
-
如果事件处理函数只有一行代码,可以写到行内
<button @click="count = 0"> 点击恢复初始值 0 </button>
-
事件传参
<button @click="take(2)"> 点击 * 2 </button> <button @click="take(10)"> 点击 * 10 </button>
-
-
示例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>事件绑定指令</title> <script src="./lib/vue.js"></script> </head> <body> <div id="app"> <h3>cont 的值是:{{ count }}</h3> <!-- v-on --> <!-- 语法:v-on:事件类型="事件处理函数" --> <button v-on:click="addCount"> 点击 +1 </button> <!-- 简写:@事件类型="事件处理函数" --> <button @click="redCount"> 点击 -1 </button> <hr> <!-- 如果事件处理函数只有一行代码,可以写到行内 --> <button @click="count = 0"> 点击恢复初始值 0 </button> <hr> <!-- 事件传参 --> <button @click="take(2)"> 点击 * 2 </button> <button @click="take(10)"> 点击 * 10 </button> </div> <script> const vm = new Vue({ el: '#app', data: { count: 0 }, methods: { // addCount: function() { // 下一行为这一行的简写 addCount() { // 上一行的简写,ES6中,对象里面函数的简写方式 // 方法中通过 this.数据名 来拿到data中定义的数据 this.count++ }, redCount() { this.count-- }, take(num) { this.count *= num } } }) </script> </body> </html>
2.3.1 事件修饰符
-
.prevent
阻止事件的默认行为<a :href="www" @click.prevent="toBai">{{ txt }}</a>
-
.stop
阻止事件冒泡,谁产生的冒泡加给谁<div class="outer-box" @click="outerClick"> <!-- .stop 阻止事件冒泡,谁产生的冒泡加给谁 --> <div class="inner-box" @click.stop="innerClick"></div> </div>
2.3.2 按键修饰符
-
enter
只有按下enter键才会触发<input type="text" @keyup.enter="submit">
-
esc
只有按下esc键才会触发<input type="text" @keyup.esc="clearsubmit">
2.4 双向绑定指令
-
v-model
-
特点:数据变化视图更新,视图变化数据更新。
-
注意:v-model 只能运用在表单元素上。
<input type="text" v-model="username">
2.4.1 专属修饰符
-
.number
-
作用:将v-model 绑定的数据装换成数值类型(number类型)
<input type="text" v-model.number="n1"> + <input type="text" v-model.number="n2"> = {{ n1 + n2 }}
-
-
.trim
-
作用:自动的将 v-model 绑定的数据去掉前后的空格
<input type="text" v-model.trim="str">
-
-
.lazy
-
作用:将 v-model 默认的input事件(改变就触发),改变为change事件(失去焦点并且改变了触发)
<input type="text" v-model.lazy="str2">
-
2.5 条件渲染指令
-
v-if
(推荐)v-show
-
v-if 和 v-show 都是用来控制 DOM 元素的显示与隐藏
-
v-if 和 v-show 绑定的 data 数据值为 true 显示,false 隐藏
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>条件渲染指令</title> <script src="./lib/vue.js"></script> </head> <body> <div id="app"> <!-- 显示隐藏 --> <button @click="flag = !flag">显示与隐藏</button> <hr> <!-- v-if 和 v-show 都是用来控制 DOM 元素的显示与隐藏 --> <!-- v-if 和 v-show 绑定的 data 数据值为 true 显示,false 隐藏 --> <!-- 推荐 v-if --> <p v-if="flag">这是 v-if 控制的 DOM 元素</p> <p v-show="flag">这是 v-show 控制的 DOM 元素</p> <hr> </div> <script> const vm = new Vue({ el: '#app', data: { a: 10, b: 5, flag: true } }) </script> </body> </html>
-
2.5.1 v-if的配套指令
-
v-else-if
v-else
-
注意:v-else 和 v-else-if 指令必须配合 v-if 一起使用,否则报错
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>v-if的配套指令</title> <script src="./lib/vue.js"></script> </head> <body> <div id="app"> <!-- v-if 的配套指令:v-else-if v-else --> <!-- 注意:v-else 和 v-else-if 指令必须配合 v-if 一起使用,否则报错 --> <p v-if="score === 'A'">优秀</p> <p v-else-if="score === 'B'">良好</p> <p v-else-if="score === 'C'">一般</p> <p v-else>差</p> </div> <script> const vm = new Vue({ el: '#app', data: { score: 'A' } }) </script> </body> </html>
2.6 列表渲染指令
-
v-for
-
语法:v-for="数组中的每一项 in 数组"
-
v-for需要循环渲染哪个元素,就在哪个元素身上添加 v-for
-
v-for指令还支持一个可选的第二个参数,即当前的索引
-
-
注意:
-
使用 v-for 指令时 一定要指定 key 的值(既提升性能,又防止列表渲染混乱)
-
key 的值只能是字符串或数字类型
-
key 的值必须具有唯一性(建议把数据项id属性的值作为key的值)
-
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>列表渲染指令</title> <script src="./lib/vue.js"></script> </head> <body> <div id="app"> <!-- 注意事项: 1. 使用 v-for 指令时 一定要指定 key 的值(既提升性能,又防止列表渲染混乱) 2. key 的值只能是字符串或数字类型 3. key 的值必须具有唯一性(建议把数据项id属性的值作为key的值) --> <ul> <!-- v-for需要循环渲染哪个元素,就在哪个元素身上添加 v-for --> <!-- 语法:v-for="数组中的每一项 in 数组" --> <li v-for="item in list" :key="item.id">姓名:{{ item.name }}</li> <hr> <!-- v-for指令还支持一个可选的第二个参数,即当前的索引 --> <li v-for="(item, index) in list" :key="item.id"> {{ list[index] }} </li> </ul> </div> <script> const vm = new Vue({ el: '#app', data: { list: [ {id: 1, name: '张三', age: 23}, {id: 2, name: '李四', age: 24}, {id: 3, name: '王五', age: 25}, {id: 4, name: '赵六', age: 25} ] } }) </script> </body> </html>
3. MVVM 的概念
3.1 vue 的特性
-
vue 框架的特性,主要体现在两方面:
-
数据驱动视图
-
在使用了 vue 的页面中,data 数据的变化,会导致页面结构的重新渲染。
-
好处:减少了程序员对 DOM 的
-
注意:数据驱动视图是单向的数据绑定。
-
-
双向数据绑
-
data 数据的变化,会导致页面的重新渲染
-
表单数据的变化,会被自动更新到 data 数据中
-
好处:开发者不再需要手动操作 DOM 元素,来获取表单元素最新的值
-
-
3.2 MVVM
MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。
MVVM 指的是 Model、View 和 ViewModel。
它把每个 HTML 页面都拆分成了这三个部分,如图所示:
4. vue 过滤器
定义过滤器
Vue.filter('过滤器名字', fn)
使用过滤器
{{ 要转换格式的数据 | 过滤器的名字 }}
-
作用:
过滤器时给在模板中使用的数据做格式转换的
-
用途:过滤器可以使用在两个地方:
-
插值表达式
-
v-bind 属性绑定
-
-
使用步骤:
-
定义过滤器
Vue.filter('dollar', (str) => { return '$' + str })
-
使用过滤器
<h2>{{ money | dollar }}<h2>
data: { money: 100 }
-
-
注意:
-
过滤器必须定义在
new Vue
之前。 -
fn 函数的格式:(要转换格式的数据) => { return 转换格式后的结果 }
-
过滤器仅在 vue 2.x 和 vue1.x 中支持,在 vue3.x 的版本中剔除了过滤器相关功能。如果时 vue 3.x 的项目,官方建议使用计算属性或
methods
方法实现对应功能就好了。
-
5. 侦听器
5.1 函数形式 - 侦听器
-
作用:watch 侦听器可以监听数据的变化,从而针对数据的变化做特定的操作。
-
使用步骤:
<div id="app"> <input type="text" v-model="username"> </div>
data: { username: 'zs' }, watch: { // 这里的函数名要对应你要监听的数据名 username(newVal, oldVal) { // newVal 是变化后的新值,oldVal 是变化前的老值 console.log(newVal, oldVal) } }
-
注意:不需要带选项得时候这样写,这样写无法带选项
5.2 对象形式 - 侦听器
-
作用:watch 侦听器可以监听数据的变化,从而针对数据的变化做特定的操作。
-
特点可以带选项
5.2.1 innediate 选项
-
功能:默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器一进入页面就立即被 调用一次,则需要使用 immediate 选项。
<input type="text" v-model="username">
watch: { username: { handler(newVal, oldVal) { console.log(newVal, oldVal) }, immediate: true } }
-
注意:
-
侦听器要写成对象形式,参数放在
handler
函数里面 -
添加
immediate
选项为true
-
5.2.2 deep 选项
-
用途:如果 watch 侦听的是一个对象,对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep
-
示例:
<input type="text" v-model="user.age">
data: { user: { age: 18 } }, watch: { user: { handler(newVal, oldVal) { console.log(newVal.age, oldVal.age) }, deep: true } }
-
注意:
-
侦听器要写成对象形式,参数放在
handler
函数里面 -
如果监听的数据是一个对象,想要对象里任何一个属性发生变化监听到,就要设置
deep
选项值为true
-
5.2.3 监听对象中单个属性的变化
-
如果只想监听对象中单个属性的变化,则可以按照如下的方式定义 watch 侦听器:
-
示例:
<input type="text" v-model="user.age">
watch: { 'user.age'(newVal, oldVal) { console.log(newVal, oldVal) } }
6. 计算属性
6.1 计算属性 - 基本使用
-
定义:计算属性是依赖于 data 中的数据动态计算出来的一个值
-
特点:计算属性所依赖的任何一个 data 数据发生变化,这个计算属性也会跟着变化。
-
示例:
-
定义计算属性:
data: { n1: 1, n2: 1 }, // 计算属性声明在 computed 选项下面 computed: { // 计算属性声明的时候被定义为一个方法,使用的时候是作为一个属性去使用。 // 计算属性中,必须 return 一个计算的结果 chengJi() { return this.n1 * this.n2 } }
2. 使用计算属性: ```html <!-- 计算属性 --> <input type="number" v-model="n1"> * <input type="number" v-model="n2"> = {{ chengJi }}
-
-
注意:
-
计算属性声明在 computed 选项下面
-
计算属性声明的时候被定义为一个方法,使用的时候是作为一个属性去使用。
-
计算属性中,必须 return 一个计算的结果
-
6.2 计算属性 - 计算属性的缓存
-
计算属性能做的事,都可以使用方法代替。
-
区别:计算属性会缓存计算的结果,只有计算属性依赖的数据变化时,才会重新进行计算。而函数不会缓存计算的 结果,所以计算属性的性能比函数要好。
-
示例:
-
声明一个计算属性和一个方法
// 使用 computed 选项声明计算属性 computed: { // 计算属性定义的的时候是一个函数 multi() { // 不管调用调用几次,只要data里的值不变就会调用一次,因为计算结果会缓存 // 只有计算属性依赖的数据变化时,再会再次调用一次 console.log('调用了计算属性'); // 计算属性中,要通过 return 返回一个值 return this.n1 * this.n2 } }, // 使用方法也可以是实现计算 methods: { multiByMethod() { // 调用几次,打印几次 console.log('调用了计算方法'); return this.n1 * this.n2 } }
-
在模板中分别多次调用使用计算属性和方法
<!-- 使用 computed 计算属性 --> <div>通过 computed 计算属性,计算 n1 * n2 的值:{{ multi }}</div> <div>通过 computed 计算属性,计算 n1 * n2 的值:{{ multi }}</div> <div>通过 computed 计算属性,计算 n1 * n2 的值:{{ multi }}</div> <!-- 使用 methods 中的方法 --> <div>通过 methods 计算属性,计算 n1 * n2 的值:{{ multiByMethod() }}</div> <div>通过 methods 计算属性,计算 n1 * n2 的值:{{ multiByMethod() }}</div> <div>通过 methods 计算属性,计算 n1 * n2 的值:{{ multiByMethod() }}</div>
-
打印结果:
调用了计算属性
调用了计算方法
调用了计算方法
调用了计算方法
-
-
注意:
-
使用 computed 选项声明计算属性
-
计算属性定义的的时候是一个函数
-
不管调用调用几次,只要data里的值不变就会调用一次,因为计算结果会缓存
-
只有计算属性依赖的数据变化时,再会再次调用一次
-
计算属性中,要通过 return 返回一个值
-
使用方法,调用几次,打印几次
-
7. vue - cli - 安装和使用
-
全局安装
vue-cli npm install -g @vue/cli
使用
vue -V
命令检查是否安装成功 -
基于 vue-cli 快速生成工程化的 Vue 项目
vue create 项目的名称(如:vue create demo-1)
-
启动项目
cd 项目的名称
npm run serve
8. Vue 组件
8.1 什么是组件化开发
组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构封装为组件,提高页面 UI 结构的复用性, 从而方便项目的开发和维护。
8.2 vue 组件的三个组成部分
-
每个 vue 组件都由 3 部分构成,分别是:
-
template
-> 组件的模板结构 -
script
-> 组件的 JavaScript 行为 -
style
-> 组件的样式
-
-
其中,每个组件中必须包含
template
模板结构,而script
行为和style
样式是可选的
8.3 模板
示例:App.vue
<!-- vue 规定:每个组件对应的模板结构,需要定义到 <template> 节点中 --> <template> <div id="app"> <!-- 当前组件的 DOM 结构,需要定义在 template 标签内部 --> <h1>这是 App.vue 组件</h1> <!-- 注意: 1. template 是 vue 提供的容器标签,只起到包裹的作用,它不会被渲染为真正的 DOM 元素 2. template 中只能包含唯一的根节点 --> </div> </template> <!-- vue 规定:开发者可以在 <script> 节点中封装组件的 JavaScrript 业务逻辑 --> <script> // 今后,组件相关的 data 数据、methods 方法等。 // 都需要定义到 export default 所导出的对象中 export default { // vue 规定:vue 组件中的 data 必须是一个函数,不能直接指向一个数据对象。 data() { return { username: 'HI' } } } </script> <!-- vue 规定:开发者可以在 <style> 节点中编写样式美化当前组件的 UI 结构,组件内的 <style> 节点是可选的 --> <!-- 让 style 中支持 less 语法 在 <style> 标签上添加 lang="less" 属性,即可使用 less 语法编写组件的样式: 前提是需要安装 less:npm install less less-loader@7.3.0 --> <style lang="less"> h1 { color: green; } </style>
8.4 使用组件
8.4.1 组件的私有注册
通过 components 注册的是私有组件,它只能在注册它的组件中使用,不能在其他组件中使用。
-
步骤一:使用
import
语法导入需要的组件示例:App.vue
import Left from '@/components/Left.vue' import Right from '@/components/Right.vue'
-
步骤二:使用
components
节点注册组件示例:App.vue
export default { components: { Left, Right } }
-
步骤三:以标签形式使用刚才注册的组件
示例:App.vue
<div id="app"> <Left></Left> <Right></Right> </div>
8.4.2 组件的全局注册
在项目的入口文件 main.js
中,注册全局组件。
示例:main.js
// 全局注册 // 1. 导入需要全局注册的组件 import Count from '@/components/Count.vue' // 2. 全局注册 // 注意:需要在 main.js 中,new Vue() 构造函数之前,进行全局组件的注册 // 语法:Vue.component('组件的注册名字', 导入的全局组件的名称) Vue.component('Count', Count)
9. prop
9.1 prop 是什么
prop 是组件的自定义属性。在封装通用组件的时候,合理地使用 prop 可以极大的提高组件的复用性!
可以理解为 prop 是在使用组件的时候,从==外面传递给组件的一个参数==。
-
需求:在 Count 组件中,实现 +1 功能
示例:Count.vue
<p>count 的值是:{{ count }}</p> <button @click="count += 1">点击 +1</button>
data() { return { count: 0 } }
9.2 prop 的使用 - prop['']
prop: [' ']
-
需求:在 Left 中 Count 实现 +1,在 Right 中的 Count 实现 +2
-
使用 prop 实现
示例:
Count.vue
// 定义 props 选项,用来接收外界传进来的参数 props: ['n']
Left.vue
<!-- 通过自定义属性给组件传参 --> <Count :n="1"></Count>
Right
<!-- 通过自定义属性给组件传参 --> <Count :n="2"></Count>
9.3 prop 的配置选项 - type
type
在声明 prop
时,可以通过 type
来规定 prop
的数据类型。
如果传递过来的数据不符合 type 设置的数据类型,vue 会在控制台中给出报错信息。
-
注意:给
prop
设置配置选项,需要使用对象形式 -
示例:
// 注意:给 prop 设置配置选项,需要使用对象形式 props: { n: { // type 可以规定传递过来的 prop 的数据类型 // 常用的数据类型:String Number Boolean Object Array type: Number } }
9.4 prop 设置默认值 - default
default
在声明 prop
时,可以通过 default
来设置 prop
的默认值。
当外界在使用组件的时候,没有传递自定义属性,prop 就使用这个默认值。
-
注意:给
prop
设置配置选项,需要使用对象形式 -
示例:
// 注意:给 prop 设置配置选项,需要使用对象形式 props: { n: { // default 用来给 prop 设置默认值 // 外界使用组件的时候,没有传递自定义属性,prop 就使用这个默认值 default: 1 } }
9.5 prop 设置是否必填 - required:true
required: true
在声明 prop
时,可以通过 required
选项,将该 prop
设置为必填项,强制外界使用组件的时候必须传递自定义属性。 否则 vue 会给出报错。
-
注意:给
prop
设置配置选项,需要使用对象形式 -
注意:优先级比 设置默认值default 高,所以两者尽量不要同时出现
-
示例:
// 注意:给 prop 设置配置选项,需要使用对象形式 props: { n: { // required: true 规定该 prop 是必填项 // 强制使用组件的时候必须传递自定义属性,否则报错 required: true } }
9.6 prop 是只读的
9.6.1 不可修改
vue 规定:组件中封装的自定义属性是只读的,我们不能直接修改 prop 的值。否则会直接报错。
-
示例:
props: { init: { type: Number, default: 0 } }
<!-- prop 是只读的,不能直接修改 --> <p>init 的值是:{{ init }}</p> <button @click="init += n">这个按钮不要点击,会报错,因为prop是只读的</button>
9.6.2 解决办法
要想修改 prop 传进来的初始值,可以把 prop 的值转存到 data 中,然后修改 data,因为 data 中的数据都 是可读可写的!
-
示例:
data() { return { count: 0, // 如果想修改 prop 传递过来的初始值,可以将 prop 的值转存到 data 中,然后修改 data coun: this.init } }
<!-- 利用转存到 data 中在修改 --> <p>init 的值是:{{ coun }}</p> <button @click="coun += n">利用转存到 data 解决不可修改,点击 +{{ n }}</button>
10. 解决组件之间的样式冲突
10.1 样式冲突的产生
默认情况下,写在 vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。
-
示例:Left.vue 和 Right.vue 都有 h3 标签。我们给 Left.vue 中的 h3 设置一个样式。
<style> h3 { color: green; } </style>
-
导致组件之间样式冲突的原因是:
-
单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的
-
每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素
-
10.2 解决样式冲突 - scoped
scoped
-
style 节点的 scoped 属性
vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题:
-
示例:Left.vue
<style scoped> /* 给组件的 style 标签设置 scoped 属性,该样式只会总用于当前组件,并不会影响其他组件的样式 */ h3 { color: green; } </style>
-
注意:实际开发中,建议在每个组件的 style 身上都加上 scoped 属性
10.3 样式穿透 - 深度选择器
css中
>>>
less中
/deep/
在给当前组件的 style 添加了 scoped 属性后,如果想在父组件中修改子组件的样式,就需要使用 深度选择器。
-
在 css 中使用
>>>
示例:Left.vue
<style scoped> /* 在 css 中使用 >>> 实现样式穿透 */ div >>> h4 { color: skyblue; } </style>
-
在 less 中使用
/deep/
示例:Right.vue
<style scoped lang="less"> /* 在 less 中使用 /deep/ 实现样式穿透 */ div /deep/ h4 { color: skyblue; } </style>
-
应用场景:需要修改第三方组件库中组件的原有样式的时候。
11. 组件的生命周期
11.1 生命周期 & 生命周期函数(钩子函数)
-
生命周期:
是指一个组件从创建 -> 更新 -> 销毁的整个过程
-
生命周期函数(钩子函数):
指的是伴随着组件的生命周期,自动按次序执行的一些 vue 框架内部提供的内置函数。这些 函数也叫做生命周期钩子函数。
-
注意:
生命周期强调的是时间段,生命周期函数强调的是时间点。
11.2 组件生命周期函数的分类
beforeCreate、created、beforeMount、mounted | beforeUpdate、updated | beforeDestroy、destroyed
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WXmXNGlJ-1653472695034)(./images/生命周期图.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1NQ8Zxzq-1653472695043)(./images/生命周期图2.png)]
-
示例:Lifecycle.vue
<template> <div> <h2>生命周期演示</h2> <h3>当前n的值是:{{ n }}</h3> <button @click="add">点我 n + 1</button> <button @click="bye">点击销毁</button> </div> </template> <script> export default { data() { return { n: 0 } }, methods: { add() { this.n++ }, bye() { this.$destroy() } }, // 1. 组件创建阶段 // 1.1 beforeCreate 钩子函数:此时,无法访问 data 中的数据还有 methods 中的方法 beforeCreate() { console.log('beforeCreate执行了') // beforeCreate执行了 console.log(this.n) // undefined }, // 1.2 created 钩子函数:此时,可以访问 data 中的数据还有 methods 中的方法了 created() { console.log('created执行了') // created执行了 console.log(this.n) // 0 }, // 1.3 beforeMount 钩子函数: // 1.3.1 页面呈现的是未经Vue编译的DOM结构 // 1.3.2 所有对DOM的操作,最终都不奏效 beforeMount() { console.log('beforeMount执行了') // beforeMount执行了 console.log(document.querySelector('button')) // null }, // 1.4 mounted钩子函数: // 1.4.1 页面中呈现的是经过Vue编译的DOM // 1.4.2 对DOM的操作均有效,一般这里进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作 mounted() { console.log('mounted执行了') // beforeMount执行了 console.log(document.querySelector('button')) // <button @click="add">点我 n + 1</button> }, // 2. 组件更新阶段 // 2.1 beforeUpdate钩子函数:此时数据是新的,但是页面是旧的,即:页面尚未和数据保持同步 beforeUpdate() { console.log('beforeUpdate执行了'); // beforeUpdate执行了 console.log(this.n); // 1 }, // 2.2 updated钩子函数:此时数据是新的,页面也是新的,即:页面和数据保持同步 updated() { console.log('updated执行了') //updated执行了 console.log(this.n); // 1 }, // 3. 组件销毁阶段 // 3.1 beforeDestroy钩子函数: // 3.1.1 此时,data、methods、指令等等,都处于可用状态,马上要执行销毁过程, // 3.1.2 一般在此阶段:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作 beforeDestroy() { console.log('beforeDestroy执行了') // beforeDestroy执行了 console.log(this.n); // 5 }, // 3.2 destroyed钩子函数:一切已经结束,忽略这个钩子函数即可 destroyed() { console.log('beforeDestroy执行了') // beforeDestroy执行了 } } </script>
12. 组件间的通信
12.1 父向子传递数据
父组件向子组件传递数据需要使用自定义属性和 prop
。
-
示例:
父组件:App.vue
<Left :msg="message" :user="userinfo"></Left>
data() { return { message: 'Hello Vue', userinfo: { name: 'HI', age: 24 } } }
子组件:Left.vue
<template> <div> <h3>Left 组件</h3> <p>父组件传递过来的 msg :<br> {{ msg }}</p> <p>父组件传递过来的 user :<br> {{ user }}</p> </div> </template>
export default { props: ['msg', 'user'] }
12.2 子向父传数据
子组件向父组件传递数据使用自定义事件和 $emit
方法
-
步骤:
-
父组件(接收方)
在要接收的子组件的标签上,注册一个自定义事件,并设置事件处理函数,把事件处理函数写到
methods
的方法中,事件处理函数的形参就是接收的值 -
子组件(传送方)
使用语法:
$emit('接收方定义的事件名', 要传递的数据)
就会传递到接收方设置的事件处理函数的形参上
-
-
示例:
父组件(接收方):App.vue
<Left @countChange="getCount"></Left>
export default { data() { return { countFromLeft: 0 } }, methods: { // 用来接收传递过来的数据 getCount(res) { this.countFromLeft = res } } }
子组件(发送方):Left.vue
export default { data() { return { count: 0 } }, methods: { // 每次调用这个方法,count 就会自身加一,然后把count的数据传到定义countChange这个事件的组件中。 add() { this.count++ this.$emit('countChange', this.count) } } }
12.3 兄弟之间传递数据
他们之间就没有一个直接的关联关系。所以想要进行通信,就需要借助第三方来作为 一个桥梁,才能实现通信。这个桥梁就是 EventBus。
-
结论:
-
在接收数据的组件中,使用 EventBus.$on 来注册一个自定义事件,用于接收数据。
-
在发送数据的组件中,使用 EventBus.$emit 来触发一个自定义事件,将数据传递出去。
-
步骤:
-
创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象
-
数据接收方:调用 bus.$on('事件名称', 事件处理函数) 方法注册一个自定义事件
注意:接收方把接收注册的自定义事件要写在 created钩子函数里
-
数据发送方:调用 bus.$emit('事件名称', 要发送的数据) 方法触发自定义事件
-
-
示例:
eventBus.js 模块
// 导入 vue import Vue from 'vue' // 向外共享 Vue 的实例对象 export default new Vue()
兄弟组件(数据接收方):Right
import bus from '@/eventBus.js' export default { data() { return { countFromLeft: 0 } }, // created钩子函数,页面加载后渲染前就开始注册事件 created() { bus.$on('share', res => { this.countFromLeft = res }) } }
兄弟组件(数据发送方):Left
import bus from '@/eventBus.js' export default { data() { return { count: 0 } }, methods: { add() { this.count++ bus.$emit('share', this.count) } } }
13. ref 引用
用来获取页面上的DOM元素或组件实例的引用
-
把ref属性添加在标签上就是获取DOM元素
-
把ref属性添加在组件标签上就是获取组件实例
13.1 ref - 获取 DOM 元素
-
步骤:
-
使用 ref 属性,为对应的元素标签上添加引用名称
-
通过 this.$refs.索引名称,就可以获取DOM
-
-
示例:Right.vue
<h3 ref="myh3">Right 组件</h3> <button @click="getDom">点我把标题变绿</button>
export default { methods: { getDom() { this.$refs.myh3.style.color = 'green' } } }
13.2 ref - 获取 组件实例
-
步骤:
-
使用 ref 属性,为对应的组件元素标签上添加引用名称
-
通过 this.$refs.索引名称,就可以获取组件的示例
-
-
示例:App.vue
<Left ref="leftRef"></Left> <button @click="meToo">点我也能 +1 </button>
methods: { // 用来接收传递过来的数据 getCount(res) { this.countFromLeft = res }, meToo() { // 打印的是 Left 组件里 count 的数据 console.log(this.$refs.leftRef.count) // 调用组件上的方法 this.$refs.leftRef.add() } }
14. $nextTick()
方法
-
$nextTick(cb)
方法中的 cb 为回调函数,会在组件的 DOM 更新完成之后,再执行。从而能保证在 cb 回调函数里 面可以拿到最新的 DOM 元素 -
使用场景:当数据变化,想拿到最新的 DOM,要将获取 DOM 的代码放到 this.$nextTick(cb) 的 cb 回调函数中去
-
示例:
showInput() { this.isShow = true this.$nextTick(() => { // focus() 方法:自动获取焦点 this.$refs.onputRef.focus() }) }
15. 动态组件
-
什么是动态组件
-
动态组件指的是,基于
<component>
组件,来动态的切换组件的显示与隐藏 -
<component>
上 is 属性的值是谁,就是先哪个组件
-
15.1 动态组件的使用方法
-
示例:App.vue
<!-- 动态组件 <component> --> <!-- 通过 is 属性,指定要渲染的组件的名称 --> <component :is="compName"></component> <button @click="compName = 'Left'">展示 Left 组件</button> <button @click="compName = 'Right'">展示 Right 组件</button>
esport default { data() { return { compName: 'Left' } } }
15.2 keep-alive 缓存组件
-
切换组件时,会将隐藏的组件销毁掉,显示时,会重新创建。因此无法保持组件的状态。
-
所以可以使用
<keep-alive>
将<component>
进行包裹,将组件缓存,从而保持动态组件的状态。 -
示例:
<!-- 使用 keep-alive 缓存组件,保留组件的状态 --> <keep-alive> <component :is="compName"></component> </keep-alive>
注: include 属性用来指定:只有 include 属性中包含的组件会被缓存。多个组件名之间使用英文的逗号分隔
<!-- 默认情况下,keep-alive 会缓存它包裹的所有组件 --> <!-- 使用 include 属性来告诉 keep-alive,哪些组件需要被缓存 --> <keep-alive include="Left,Right"> <component :is="compName"></component> </keep-alive>
15.3 被缓存组件的生命周期函数
-
被 keep-alive 缓存的组件的生命周期函数
-
被 keep-alive 缓存的组件,会有两个生命周期函数。
-
当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。
-
当组件被激活时,会自动触发组件的 activated 生命周期函数。
-
-
示例:
export default { // 当组件被缓存时,会自动触发 deactivated 生命周期函数 deactivated() { console.log('Left 组件被缓存了'); }, // 当组件被激活时,会自动触发 activated 生命周期函数 activated() { console.log('Left 组件被激活了'); } }
16. 插槽
-
什么是插槽
可以提高组件的复用性
使用插槽可以给组件传递一段 html 内容
-
注意:默认情况下,如果在封装组件时没有设置任何插槽,则使用组件时,在组件的开始和结束标签中间传递的内容将会被丢弃。
16.1 插槽的基本使用
-
在封装组件时,可以定义一个插槽(坑位),用来接收使用组件时传递过来的内容。
-
在使用组件时,可以在组件的开始和结束标签中间传递一段 html 内容,这段内容就会被渲染到组件中插槽所在的位置。
-
注意:如果占位插槽内有收到传递内容的话是可以设置默认内容的,插槽占位的时候直接往插槽内写内容,就是默认内容
-
示例:
Left.vue
<Article> <h3>咏鹅</h3> </Article>
Article.vue
<template> <div> <!-- <h3>文章的标题</h3> --> <slot></slot> <div> <p>这是文章的段落1。</p> <p>这是文章的段落2。</p> <p>这是文章的段落3。</p> <p>这是文章的段落4。</p> </div> <!-- 文章作者的插槽 --> <slot> <!-- 插槽默认内容 --> <h6>文章的作者: xxx</h6> </slot> </div> </template>
16.2 具名插槽
当我们从外界传递过来多个内容时,这时就需要设置多个插槽进行接收,为了区分不同的插槽,就需要为插 槽指定具体的 name 名称。这种带有具体名称的插槽叫做“具名插槽”。
-
步骤:
-
在封装组件时,给插槽起一个名字。
-
在之用组件,向插槽传递内容的时候,指定要给哪个插槽传递。
两种方法:
旧语法(只能在 vue2.x 中使用) 新语法(vue2.x 和 vue3.x中都能使用)
-
-
注意:新语法中可以简写
v-slot:title
可以简写成#title
-
示例:
Article.vue
<template> <div> <!-- <h3>文章的标题</h3> --> <slot name="title"> <!-- 插槽的默认内容 --> <h3>标题</h3> </slot> <div> <p>这是文章的段落1。</p> <p>这是文章的段落2。</p> <p>这是文章的段落3。</p> <p>这是文章的段落4。</p> </div> <!-- 文章作者的插槽 --> <slot name="author"> <!-- 插槽默认内容 --> <h6>文章的作者: xxx</h6> </slot> </div> </template>
旧语法 (只能在 vue2.x 中使用):
<Article> <h3 slot="title">咏鹅</h3> <h6 slot="author">文章的作者:骆宾王</h6> </Article>
新语法 (vue2.x 和 vue3.x中都能使用):
<Article> <template v-slot:title> <h3>咏鹅</h3> </template> <!-- v-slot 的简写是 # --> <template #author> <h6>文章的作者:骆宾王</h6> </template> </Article>
16.3 默认插槽
-
注意:
-
没有指定 name 名称的插槽,叫做默认插槽
-
它有隐含的名称叫做 “
default
”。 -
将来使用组件的时 候,所有没有指定要传给具名插槽的内容,都会被默认插槽所接收。
-
-
示例:
Article.vue
<template> <div> <!-- <h3>文章的标题</h3> --> <slot name="title"> <!-- 插槽的默认内容 --> <h3>标题</h3> </slot> <div> <p>这是文章的段落1。</p> <p>这是文章的段落2。</p> <p>这是文章的段落3。</p> <p>这是文章的段落4。</p> </div> <!-- 文章作者的插槽 --> <slot name="author"> <!-- 插槽默认内容 --> <h6>文章的作者: xxx</h6> </slot> <!-- 默认插槽 --> <!-- 隐藏的名称叫做 default --> <!-- 注意:使用组件的时候,所有没有指定要传给具名插槽的内容,都会被默认插槽所接收 --> <!-- <slot name="default"></slot> --> <slot></slot> </div> </template>
Left.vue
<template> <div class="left-container"> <h3>Left 组件</h3> <!-- 渲染 Article 组件 --> <!-- 新语法 --> <Article> <template v-slot:title> <h3>咏鹅</h3> </template> <template v-slot:author> <h6>文章的作者:骆宾王</h6> </template> <p>111</p> <p>222</p> <p>333</p> </Article> </div> </template>
16.4 作用域插槽
使用组件时,在组件的开始和结束标签的内容中,使用的数据如果是来自组件内部,那么这个插槽就是作用 域插槽。
-
步骤:
-
封装组件时,在 slot 上绑定自定义属性,给使用组件时传递数据。
-
在使用组件时,接收组件内部传递出来的数据。
-
-
示例:
步骤一:Article.vue
<!-- 在插槽上,使用自定义属性,给插槽的内容传递数据 --> <slot name="author" :age="age"></slot>
步骤二:Left.vue
-
新语法(vue2.x 和 vue3.x 中都能使用)
<!-- scope 对象里,存放数组传递出来的数据 --> <template #author="scope"> <h6>文章的作者:骆宾王 <br> 年龄:-- {{ scope.age }}</h6> </template>
-
旧语法(只能在 vue2.x 中使用)
<h6 slot="author" solt-scope="scope"> 文章的作者:骆宾王 <br> 年龄:-- {{ scope.age }} </h6>
-
17. 自定义指令
17.1 什么是自定义指令
-
vue 官方提供了 v-
bind
、v-on
、v-if
、v-for
、v-model
等常用的指令。除此之外 vue 还允许我们开发自定 义指令。
17.2 自定义指令的分类
-
自定义指令分为两类:
-
全局自定义指令
-
私有自定义指令
-
17.3 全局自定义指令 (常用)
17.3.1 自定义指令的使用
-
全局的自定义指令需要通过
Vue.directive()
进行声明 -
示例:
使用自定义属性:App.vue
<template> <div class="app-container"> <h1 v-color>App 根组件</h1> </div> </template>
声明自定义属性:main.js
// 在 main.js 中,new Vue() 之后定义全局的自定义指令 // Vue.directive('自定义指令的命令', { 配置 }) Vue.directive('color', { bind(el) { // el 就是绑定了该指令的 DOM 元素 // console.log(el); el.style.color = 'red' } })
17.3.2 为自定义指令动态绑定数值
-
步骤:
-
在使用自定义指令时,可以指令后面添加等号并赋值,为当前指令动态绑定参数值;
-
在声明自定义指令时,使用
bind
函数的第二个参数binding
来获取动态绑定的参数值。
-
-
示例:
使用自定义指令:App.vue
<template> <div class="app-container"> <h1 v-color="color">App 根组件</h1> </div> </template> <script> export default { data() { return { color: 'red' } } } </script>
声明自定义指令:main.js
Vue.directive('color', { bind(el, binding) { // el 就是绑定了该指令的 DOM 元素 // 可以通过 binding 参数的 value 属性,拿到动态绑定的值 el.style.color = binding.value } })
17.3.3 update
函数
-
bind
函数只在组件初始化时调用 1 次。当 DOM 更新时bind
函数不会被触发。 -
update
函数会在数据变化后,组件更新时被调用 -
示例:
Vue.directive('color', { // 组件首次渲染时,当 vue 解析绑定到该指令的元素时,就会调用 bind 函数 bind(el, binding) { el.style.color = binding.value }, // 当 DOM 更新的时候,会调用 update 函数 update(el, binding) { el.style.color = binding.value } })
17.3.4 bind
函数和 update
函数的简写形式
-
如果
bind
和update
函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式。 -
示例:
简写前:
Vue.directive('color', { // 组件首次渲染时,当 vue 解析绑定到该指令的元素时,就会调用 bind 函数 bind(el, binding) { el.style.color = binding.value }, // 当 DOM 更新的时候,会调用 update 函数 update(el, binding) { el.style.color = binding.value } })
简写后:
Vue.directive('color', function(el, binding) { // 在 bind 和 updata 的时候,都会执行这个函数 el.style.color = binding.value })
17.4 私有自定义指令
-
在每个 vue 组件中,可以在
directives
节点下声明私有自定义指令 -
示例:App.vue
export default { directives: { // 完整写法 color: { bind(el, binding) { el.style.color = binding.value }, update(el, binding) { el.style.color = binding.value } }, // 简写形式 color(el, binding) { el.style.color = binding.value } } }
vue - router
1. 路由 - 概念
1.1 URL
-
什么是 URL
URL(Uniform Resource Locator,统一资源定位符)用于定位网络上的资源。
-
URL 的组成部分
-
格式 scheme://host:port/path?query#hash
-
示例 http://www.liulongbin.top:8083/images/cat.png?name=ls#abc
-
-
Hash(哈希):
-
# 号后面的部分,专业术语为“Hash 地址”
-
Hash 地址发生变化,不会导致浏览器页面的刷新
-
Hash 地址发生变化,会形成浏览器历史记录
-
vue 就是基于 hash 的这些特性来做路由的跳转,实现页面的切换的
-
1.2 应用类型与路由的概念
-
多页应用与后端路由
MPA(Multi Page Application):在多页应用中,想要切换页面就是跳转到一个新的 html 页面。 路由工作是由后端完成的
-
单页应用与前端路由
SPA(Single Page Application):在单页应用中,只有唯一的一个 HTML 页面,所以想要切换页面就只能切换组件来达到切换页面的效果
这种切换组件达到页面切换的效果,就是前端路由。由前端程序员来完成
-
前端路由:访问不同的 Hash 地址,显示不同的组件
1.3 前端路由的工作方式
-
用户点击了页面上的路由链接
-
导致了 URL 地址栏中的 Hash 值发生了变化
-
前端路由监听了到 Hash 地址的变化
-
前端路由把当前 Hash 地址对应的组件渲染到浏览器中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T3whTLup-1653472695046)(./images/路由工作方式.png)]
-
强调:前端路由,指的是 ==Hash 地址==与==组件之间==的==对应关系==
2. 模拟简易的前端路由
-
步骤一:在 App.vue 组件中,为
<a>
链接添加对应的hash
值,来改变浏览器地址栏中的hash
地址:<!-- 1. 为 a 连接添加跳转的 hash 地址 --> <a href="#/home">首页</a> <a href="#/movie">电影</a> <a href="#/about">关于</a>
-
步骤二:通过
<component>
标签,动态渲染组件。示例代码如下:App.vue<!-- 2. 使用 <component> 标签,动态的徐娜然组件。示例代码如下:App.vue --> <component :is="compName"></component>
-
步骤三:使用浏览器的
window.onhashchange
事件,监听浏览器地址栏中hash
地址的变化,然后通过location.hash
拿到页面最新的hash
值。根据这个最新的hash
值,渲染对应的组件。以达到改变hash
切换组件的目的created() { // 3. 监听 hash 地址的变化,拿到最新的 hash, 渲染与该 hash 对应的组件 window.onhashchange = () => { const hash = location.hash if (hash === '#/home') { this.compName = 'Home' } else if (hash === '#/movie') { this.compName = 'Movie' } else if (hash === '#/about') { this.compName = 'About' } } }
3. vue-router 简介
-
什么是 vue-router
-
vue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换。
-
vue-router 的官方文档地址:Vue Router | Vue.js 的官方路由
-
4. vue-router 的基本使用
-
步骤:
-
安装 vue-router 包
-
创建路由模块
-
挂载路由模块
-
配置路由规则
-
声明路由链接和占位符
-
-
示例:
步骤一:安装 vue-router 包
# 在 vue2 的项目中,安装 vue-router@3.5.2 -S npm i vue-router@3.5.2 -S
步骤二:创建路由模块
在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下的代码
// 1. 引入 Vue 和 VueRouter 的包 import Vue from 'vue' import VueRouter from 'vue-router' // 2. 调用 Vue.use() 函数,把 VueRouter 安装为插件 Vue.use(VueRouter) // 3. 创建路由的实例对象 const router = new VueRouter() // 4. 导出路由实例对象 export default router
步骤三:挂载路由模块
在 src/main.js 入口文件中,导入并挂载路由模块
import Vue from 'vue' import App from './App.vue' // 导入路由模块 import router from '@/router/index.js' new Vue({ render: h => h(App), // 2. 挂载路由模块 // router: router // 简写如下行 router }).$mount('#app')
步骤四:配置路由规则
在 src/router/index.js 路由模块中,通过
routes
数组声明路由的匹配规则// 引入要展示的组件 import Home from '@/components/Home.vue' import Movie from '@/components/Movie.vue' import About from '@/components/About.vue' const router = new VueRouter({ // 使用 routes 选项配置路由规则 // 路由规则:hash 地址和对应的组件 routes: [ // { path: 'hash地址', component: 要展示的组件名 } // 注意:pash 里面的 hash 地址是不需要加 # 的 { path: '/home', component: Home }, { path: '/movie', component: Movie }, { path: '/about', component: About }, ] })
步骤五:声明路由链接和占位符
在 src/App.vue 组件中,使用 vue-router 提供的
<router-link>
和<router-view>
声明路由链接和路由占位符<template> <div class="app-container"> <h1>App 根组件</h1> <!-- 声明路由跳转连接 --> <router-link to="/home">首页</router-link> <router-link to="/movie">电影</router-link> <router-link to="/about">关于</router-link> <hr /> <!-- 声明路由占位符 --> <router-view></router-view> </div> </template>
5. vue-router 的常见用法
5.1 路由重定向
-
路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示地址 C 对应的组件页面。
-
通过路由规则的
redirect
属性,指定一个新的路由地址,可以很方便地设置路由的重定向
// 创建路由的实例对象 const router = new VueRouter({ routes: [ // 使用重定向设置访问根路径的组件为 Home.vue // 语法:{ path: '用户要访问的 hash 地址', redirect: '重定向的 hash 地址' } { path: '/', redirect: '/home' }, { path: '/home', component: Home }, { path: '/movie', component: Movie }, { path: '/about', component: About }, ] })
5.2 嵌套路由
-
在本身就是通过路由展示的组件中,再使用路由去控制其他组件的展示,就会形成嵌套路由。 这种通过路由实现组件的嵌套展示,叫做嵌套路由
-
示例:
步骤一: 声明子路由规则
在 src/router/index.js 路由模块中,使用
children
属性声明子路由规则import Tab1 from '@/components/tabs/Tab1.vue' import Tab2 from '@/components/tabs/Tab2.vue' const router = new VueRouter({ routes: [ { path: '/home', component: Home }, { path: '/movie', component: Movie }, { path: '/about', component: About, // 要在哪个组件中嵌套展示其他组件,children 就是在哪个路由规则下面 // 通过 children 属性,定义子路由规则 children: [ { path: '/about/tab1', component: Tab1 }, { path: '/about/tab2', component: Tab2 } ] } ] })
步骤二:声明子路由链接和子路由占位符
在 About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符
<template> <div class="about-container"> <h3>About 组件</h3> <!-- 在关于页面中,声明子路由连接 --> <router-link to="/about/tab1">Tab1</router-link> <router-link to="/about/tab2">Tab2</router-link> <hr> <!-- 在关于页面中,声明子路由占位符 --> <router-view></router-view> </div> </template>
5.3 动态路由
-
概念:
把 Hash 地址中动态变化的部分定义为一个动态的参数项,从而提高路由规则的复用性。 在 vue-router 中使用英文的冒号(:)来定义路由的参数项。
-
示例:
// 在路由地址中声明动态参数项 // 将动态的部分使用 :动态参数项的名称 代替 { path: '/movie/:id', component: Movie }
<!-- 将以下 3 个路由规则,合并成了一个,调高了路由规则的复用性 --> <router-link to="/movie/1">电影1</router-link> <router-link to="/movie/2">电影2</router-link> <router-link to="/movie/3">电影3</router-link>
-
使用场景:一般都是在从列表页,跳转到详情页的时候,使用动态路由。
5.4 通过 $route.params
获取动态路由参数(推荐)
-
在动态路由渲染出来的组件中,可以使用
this.$route.params
对象访问到动态匹配的参数值。this.$route
里面存放的是当前页面路由的相关信息。 -
示例:
<template> <div class="movie-container"> <!-- this.$route 里面存放的是当前页面路由的相关信息 --> <h3>Movie 组件 -- {{ this.$route.params.id }}</h3> <button @click="logRoute">打印 this.$route</button> </div> </template> <script> export default { name: 'Movie', methods: { logRoute() { console.log(this.$route); } } } </script>
5.5 使用 props 接收路由参数
-
为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props 传参,那么在组件中就可以使用 props 来接 收路由上的参数了。
-
示例:
步骤一:在路由规则中开启 props 选项(router/index.js)
// 开启 props 选项,允许组件使用 props 接收路由参数 { path: '/movie/:id', component: Movie, props: true }
步骤二:在组件中使用 props 接收路由参数(Movie.vue)
<template> <div class="movie-container"> <h3>Movie 组件 -- {{ id }}</h3> </div> </template> <script> export default { // 使用 props 接收路由参数 props: ['id'], } </script>
5.6 编程式导航
-
声明式导航 & 编程式导
-
声明式导航
-
在浏览器中,点击链接实现路由跳转的方式,叫做声明式导航。
-
例如:普通网页中点击
<a>
链接、vue 项目中点击<router-link>
都属于声明式导航
-
-
编程式导
-
在浏览器中,调用 JS API 方法实现路由跳转的方式,叫做编程式导航。
-
例如:普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航
-
-
-
vue-router 提供了许多编程式导航的 API,其中最常用的导航 API 分别是
-
this.$router.push('hash 地址')
-
跳转到指定 hash 地址,并增加一条历史记
-
-
this.$router.go(数值 n)
-
实现导航历史前进、后
-
-
-
示例一:$router.push
调用 this.$router.push() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。
<template> <div class="home-container"> <h3>Home 组件</h3> <button @click="goAbout">跳转到关于页面</button> </div> </template> <script> export default { methods: { goAbout() { // 编程式导航 API: // 语法:this.$router.push('hash地址') this.$router.push('/about') } } } </script>
-
示例二:$router.go
调用 this.$router.go() 方法,可以在浏览历史中前进和后退。类似于浏览器的前进后退功能。
<template> <div class="about-container"> <h3>About 组件</h3> <button @click="goBack">返回</button> </div> </template> <script> export default { methods: { goBack() { // 返回到记录的上一条 // 负数回退,正数前进 this.$router.go(-1) } } } </script>
6. 导航守卫
-
什么是导航守卫
导航守卫可以对路由跳转进行拦截,从而达到控制路由的访问权限的功能。
6.1 全局的前置守卫
-
每次发生路由的导航跳转时,还没有真正发生跳转之前,会触发全局前置守卫。因此,我们可以在全局前置 守卫中,对每个路由进行访问权限的控制
// 声明全局前置守卫 // 每次发生路由跳转的时候,都会触发 beforeEach 中的 fn 回调函数 // router.brforeEach(fn) router.beforeEach(() => { console.log('触发了 beforeEach 中的 fn 回调函数') // 做一些权限控制的工作 })
6.2 前置守卫方法的3个形参
router.beforeEach((to, from, next) => { // to: 将要访问的路由信息对象 console.log(to) // from: 将要离开的页面的路由信息对象 console.log(from) // next 是一个函数,调用 next, 表示放行,允许这次路由的跳转 next() })
vue - vuex
1. vuex 概念
1.1 Vuex 是什么
-
Vuex:
Vuex 是 vue 项目中实现全局的、大范围的数据通信的解决方案。
-
作用:
能够方便、高效地实现组件之间的数据通信
-
使用 Vuex 的好处:
-
数据的存取一步到位,不需层层传递
-
数据的流动非常清晰
-
存储在 Vuex 中的数据都是响应式的
-
1.2 项目中安装和配置 Vuex
-
安装 Vuex 的依赖包
npm i vuex@3.6.2 -S
-
创建 store 模块
// 1. 导入 Vue 和 Vuex 依赖包 import Vue from 'vue' import Vuex from 'vuex' // 2. 把 Vuex 安装为 Vue 的插件 Vue.use(Vuex) // 3. 创建 store 的实例对象 const store = new Vuex.Store() // 4. 向外导出 store 的实例对象 export default store
-
挂载 store 的实例对象到 new Vue() 中
// 1. 导入 store 实例对象 import store from '@/store/index.js' new Vue({ render: h => h(App), // 2. 挂载 store 实例对象 store }).$mount('#app')
2. State 的基本使用
2.1 store 的概念
-
state 本质上就是一个对象,用来存储全局的数据的。
2.2 state 的使用步骤
2.2.1 定义全局数据
-
定义:在 Vuex 的 state 选项对象中,定义全局数据
store/index.js
const store = new Vuex.Store({ // 在 state 中,定义全局数据 state: { count: 0 } })
2.2.2 组件访问 state 数据
-
使用方式一:
state
属性在组件中通过
this.$store.state.全局数据名称
访问全局数据Left.vue
<template> <div class="left-container"> <p>count 值: -- {{ this.$store.state.count }} </p> </div> </template>
-
使用方式二:
mapState
辅助函数在组件中通过 计算属性 访问全局数据
Right.vue
<template> <div class="right-container"> <p>count 值: -- {{ count }} </p> </div> </template>
// 1. 导入辅助函数 mapState import { mapState } from 'vuex' // mapState 函数的返回值是一个对象。里面存放的就是 state 中全局数据的映射 export default { computed: { // 将得到的 state 中全局数据的映射,通过扩展运算符,放入 computed 中 ...mapState(['count']) } }
2.2.3 解决命名冲突
-
<template> <div class="right-container"> <p>count 值: -- {{ countR }} </p> </div> </template>
-
import { mapState } from 'vuex' export default { data() { return { count: 666 } }, computed: { // 解决命名冲突 - state中的数据名,在这个组件已经被定义 // 语法:mapState({ '计算属性名字': ‘要访问的全局数据的名字’ }) ...mapState({ countR: 'count' }) } }
3. Mutation 的基本使用
注意:Vuex 规定: mutation 必须是同步函数 。
3.1 开启严格模式
-
开启 vuex 严格模式
-
开启严格模式之后,直接修改全局数据就会报错,可以防止程序员写垃圾代码
-
但是严格模式性能上有损耗,所以项目上线之前,要关掉严格模式
-
-
示例:store/index.js
const store = new Vuex.Store({ // 开启严格模式 // 会消耗性能,开发阶段开启,上线之前关闭 strict: true, });
3.2 mutation 定义
-
mutation 是什么
-
mutation 本质上是 JavaScript 函数,专门用来变更 store 中的数据。
-
-
特点:想要修改 state 中的数据,只能调用 mutation 方法!
-
好处:能够确保修改来源的唯一性,方便调试和后期维护。
3.3 mutation 的使用步骤
3.3.1 定义 mutation 方法
-
定义:在 Vuex 中定义 mutation 方法
-
示例:store/index.js
const store = new Vuex.Store({ strict: true, state: { count: 0, n1: 0, n2: 0 }, // 【在 mutations 中,声明修改全局数据的方法】 mutations: { addCount(state, n = 1) { // mutation 函数中,接受的第一个参数是 state // mutation 函数中,使用第二个参数,来接受调用时传递过来的参数 state.count += n; } } });
-
注意:
-
mutation 函数中,接受的第一个参数是 state
-
mutation 函数中,使用第二个参数,来接受调用时传递过来的参数
-
3.3.2 调用 mutation 函数
-
调用方式一:
commit
方法在组件中,通过
this.$store.commit('mutations中定义的函数名')
调用 mutation 方法示例:Left.vue
add(n) { // 在组件中,调用全局的mutation,来修改全局数据 // 调用全局的 mutation 里面的方法来修改全局的数据 // 语法:this.$store.commit('mutation函数的名字') this.$store.commit('addCount', n) }
注意:
-
调用全局的 mutation 里面的方法来修改全局的数据
-
语法:
this.$store.commit('mutation函数的名字')
-
通过传参可以提高 mutation 方法的通用性。
-
语法:
this.$store.commit('mutation函数的名字', '参数')
-
-
调用方式二:
mapMutations
辅助函数Vuex 提供了 mapMutations 辅助函数,可以方便的把 store 中的 mutation 方法,映射到前组件的 methods 中
示例:Right.vue
import { mapMutations } from 'vuex' export default { methods: { // 使用 mapMutations 将全局的 mutation 函数,映射到当前组件的 methods 中去 ...mapMutations(['addCount']) }) } }
3.3.3 解决命名冲突
-
示例:
import { mapMutations } from 'vuex' export default { methods: { // 使用 mapMutations 中的对象格式的传参,解决命名冲突 ...mapMutations({ // 自定义的名称: '全局 mutation名称' addCount2: 'addCount' }) } }
4. Action 的基本使
注意:专门用来处理 Vuex 中的异步操作
4.1 action 的定义
-
action 是什么
-
action 本质上是 JavaScript 函数,专门用来处理 Vuex 中的异步操作
-
4.2 action 的使用步骤
4.2.1 定义 action 方法
-
定义:在 Vuex 中定义 action 方法
-
示例:store/index.vue
const store = new Vuex.Store({ strict: true, // 【在 actions 中,声明处理异步操作的方法】 actions: { addCountAsync(ctx, {n, time}) { // action 函数中,接收的第一个参数是 ctx,ctx 中有 commit 方法 setTimeout(() => { // 这里不可以直接修改全局数据,要通过调用 mutations 里的方法来修改数据 // 使用 ctx.commit 方法调用 mutation ctx.commit('addCount', n) }, time); } }, });
4.2.2 调用 action 中的异步方法
-
调用方式一:
dispatch
方法在组件中,通过
this.$store.dispatch('actions中定义的函数名')
调用 action 方法示例:Left.vue
export default { methods: { addAsync(n, time) { // 在组件中,调用全局的action,来实现异步处理函数,修改全局数据 // 在组件中调用全局的 action 里面的异步函数 // 语法:this.$store.dispatch('action函数名') // 第二个参数是 要传递的参数 this.$store.dispatch('addCountAsync', {n, time}) } } }
-
调用方式二:
mapActions
辅助函数Vuex 提供了 mapActions 辅助函数,可以方便的把 store 中的 action 方法,映射到当前组件的 methods 中
示例:Right.vue
import { mapActions } from 'vuex' export default { computed: { // 使用 mapAction 辅助函数,将全局的 action,映射到当前组件的 methods 中去 ...mapActions (['addCountAsync']) } }
4.2.3 解决命名冲突
-
示例:
import { mapActions } from 'vuex' export default { computed: { // 重新命名 ...mapActions({ addCountAsync2: 'addCountAsync' }) } }
5. Getter 的基本使用
5.1 getter 定义
-
getter 是什么
getter 可以理解为是 Vuex 中的计算属性,它内部依赖于 state 中的数据,state 中的值变化,getter 的值 会自动更新。
5.2 getter 的使用步骤
5.2.1 定义 getter 方法
-
定义:在 Vuex 中定义 action 方法
-
示例:store/index.js
const store = new Vuex.Store({ strict: true, state: { n1: 0, n2: 0 }, // 使用 geeter 定义全局的计算属性 // 某一个值,是依赖于全局的数据动态计算出来 // 这时就是使用 getter getters: { sum(state) { // getter 函数接收的第一个参数是 state 全局数据 return state.n1 + state.n2 } } });
5.2.2 访问 getter
-
访问方式一:
getters
属性在组件中,通过
this.$store.getters.getters中定义的函数名
,访问 gette示例:Left.vue
<template> <div class="left-container"> <!-- 在组件中,通过 this.$store.getters.getter函数名 --> <p>全局数据 n1 + n2 = {{ $store.getters.sum }}</p> </div> </template>
-
访问方式二:
mapGetters
辅助函数Vuex 提供了 mapGetters 辅助函数,可以方便的把 store 中的 getter,映射到当前组件的 computed 中
示例:Right.vue
import { mapGetters } from 'vuex' // mapGetters 要结合 computed 一起使用 export default { computed: { ...mapState(['count']) } }
5.2.3 解决命名冲突
-
示例:
import { mapGetters } from 'vuex' export default { computed: { // 重新命名 ...mapState({ sum2: 'count' }) } }
6. Module 的基本使
6.1 module 是什么
Vuex 中的 module 表示按照模块化的开发思想,把有业务关联的数据和方法封装在一起。module 就是 Vuex 中的模块化
6.2 module 的使用场景
当一个项目的页面数量很少,逻辑功能简单的情况下,是完全可以不使用 module 模块的。
但是当一个项目的页面数量很多,逻辑功能复杂的时候,所有的全局数据、方法都集中在了一起,会导致 Vuex 的结构混乱,不利于现阶段的开发和后期的维护,那么此时就需要使用模块来管理全局的数据和方法。
6.3 module 定义和注册模块
-
定义模块
-
每个模块都是彼此独立的,都可以拥有自己的 state、mutations、actions、getters 节点:
-
示例:store/count.js
// count 模块 export default { // 当前模块的数据 // 函数的形式为了解决 // 模块被多次注册,并且开启命名空间时,公用同一份数据 state: { num: 0 }, // 当前模块修改 state 数据的函数 mutations: { show() { console.log('调用了 count 模块下的 show 方法'); }, addNum(state, n = 1) { state.num += n } }, // 当前模块的异步操作 actions: { }, // 当前模块的计算属性 getters: { } }
-
-
注册模块
-
示例:store/index.js
// 1. 导入要注册的模块 import count from './count.js' import task from './task.js' const store = new Vuex.Store({ // 使用 modules 选项注册模块 modules: { // 2. 注册模块 // 语法: // 模块的注册名称:导入的模块 // count: count count, task } })
-
6.4 namespaced
(命名空间)
-
namespaced(命名空间)可以解决不同模块之间成员名称冲突的问题。在实际项目开发中,
-
建议: 为每 个 module 模块都开启命名空间!
6.4.1 开启命名空间 - namespaced: true
-
在定义模块时,只需在模块中声明 namespaced: true 选项,即可为当前模块开启命名空间:
-
示例:store/count.js && store/task.js
// count 模块 export default { // 开启命名空间 - 用于隔离模块 namespaced:true, state: {}, mutations: {}, actions: {}, getters: {} }
// task 模块 export default { // 开启命名空间 - 用于隔离模块 namespaced:true, state: {}, mutations: {}, actions: {}, getters: {} }
6.4.2 通过模块注册名访问模块下成员
当模块启用了 namespaced: true
选项之后,模块就有了自己的命名空间。
-
注意:访问时要加上模块的注册名 称才能够访问到。
-
示例:Left.vue
add() { // 在组件中,通过 this.$store.commit('模块注册的名称/mutation函数名') this.$store.commit('count/addNum') }
6.4.3 在组件中访问模块内 state 的两种方式
-
方式一:通过
this.$store.state.模块注册名称.要访问的数据
示例:Left.vue
<template> <div class="left-container"> <!-- 在组件中,通过 this.$store.state.模块的注册名称.模块中的数据 --> <p>count 模块下的 num 的值:{{ $store.state.count.num }}</p> </div> </template>
-
方式二:通过
mapState
辅助函数示例:Right.vue
<template> <div class="right-container"> <h3>Right 组件</h3> <p>count 模块下的 num 的值:{{ num }}</p> <p>task 模块下的 num2 的值:{{ num2 }}</p> </div> </template> <script> import { mapState, mapMutations } from 'vuex' export default { computed: { // ...mapState('模块的注册名称', [‘模块中数据’]) ...mapState('count', ['num']), ...mapState('task', { num2: 'num' }) } } </script>
6.4.4 在组件中调用模块内 mutation 的两种方式
-
方式一:通过
this.$store.commit('组件的注册名称/要调用的mutation函数名称' , 参数)
示例:Left.vue
<template> <div class="right-container"> <h3>Right 组件</h3> <p>count 模块下的 num 的值:{{ num }}</p> <p>task 模块下的 num2 的值:{{ num2 }}</p> <hr> <button class="btn btn-warning" @click="addNum(-1)">-1</button> </div> </template>
-
方式二:通过
mapMutations
辅助函数示例:Right.vue
<template> <div class="left-container"> <button class="btn btn-primary" @click="add">+1</button> <hr> <button @click="show">打印 show</button> <hr> </div> </template> <script> export default { methods: { show() { this.$store.commit('count/show') }, add() { // 在组件中,通过 this.$store.commit('模块注册的名称/mutation函数名') this.$store.commit('count/addNum') } } } </script>
6.4.5 在组件中调用模块内 action 的两种方式
-
方式一:通过
this.$store.dispatch('组件的注册名称/要调用的action函数名称' , 参数)
示例:Left.vue
<template> <div> <!-- this.$store.dispatch('组建的注册名称/要调用的action函数名称', 参数) --> <button class="btn btn-info" @click="$store.dispatch('count/addAsync', -1)">1秒后 +1</button> </div> </template>
-
方式二:通过
mapActions
辅助函数示例:Right.vue
<template> <div class="right-container"> <button class="btn btn-warning" @click="addAsync(-1)">-1</button> </div> </template> <script> import { mapActions } from 'vuex' export default { methods: { ...mapMutations('count', ['addAsync']) } } </script>
6.4.6 在组件中访问模块内 getter 的两种方式
-
方式一:通过
this.$store.getters['模块的注册名称/要访问的getter名称']
示例:Left.vue
<template> <div class="left-container"> <h3>Left 组件</h3> <p>count 值:{{ this.$store.getters['count/numPlus'] }}</p> </div> </template>
-
方式二:通过
mapGetters
辅助函数示例:Right.vue
<template> <div class="right-container"> <p>count 值: {{ numPlus }}</p> </div> </template> <script> import { mapGetters } from 'vuex' export default { computed: { ...mapGetters('count', ['numPlus']) } } </script>
webpack
代码示例
webpack.config.js
// 引入 path 为了使用 join 方法 const path = require('path') // webpack 插件 // clean-webpack-plugin - 清理之前编译过的文件 // 1. 引入插件 const { CleanWebpackPlugin } = require('clean-webpack-plugin') // 2. 创建插件对象 - 然后再配置信息对象里使用 const cleanPlugin = new CleanWebpackPlugin() // webpack-dev-server 打开开发的服务器并把服务器编译好的内容到服务器中 // 当我们改变代码的时候,浏览器中页面会自动刷新,页面属于运行在服务器中 // 1. 在 package.json 中设置一个简易命令 例如:"serve": "webpack serve" // 2. 在命令行中执行 serve 命令 nom run serve // 3. 命令输入完后,会返回一个url,通过那里打开 // 4. 注意:使用插件后,所有编译的内容都在内存中,通过路径访问文件时,统一在根目录/文件名 // html-webpack-plugin 编译html,自动引入 js 文件 // 1. 引入插件 const HtmlPlugin = require('html-webpack-plugin') // 2. 创建插件对象 const html = new HtmlPlugin({ // 编译哪个 html template: path.join(__dirname, 'public', 'index.html'), // 编译后,html文件名是什么 filename: 'index.html' }) // 作用,声明 webpack 配置信息 module.exports = { // 能看到正确的报错位置,但是可以通过浏览器看到源码 - 开发中使用 devtool: 'source-map', // 能看到正确的报错位置,但是无法通过浏览器看到源码 - 生产中使用 devtool: 'nosources-source-map', // mode 声明打包方式 // development 开发环境 - 开发时 // production 生产环境 - 上线运行时 mode: 'development', // 入口文件 entry: './src/index.js', // 出口文件(打包后的文件路径和文件名) output: { // 指定打包好的文件 文件夹名称 - 打包到当前绝对路径下的 dist 文件夹中 path: path.join(__dirname, 'dist'), // 打包后的文件名称 filename: 'index.js', // 设置资源文件输出路径 assetModuleFilename: 'images/[name][ext]' }, // 3. 声明插件数组,在数组中放入船舰的插件对象 plugins: [cleanPlugin, html], // 修改开发服务器端口号 devServer: { // 端口号 port: 3000, // 运行完是否自动打开网页 open: true }, // loader npm i style-loader css-loader less-loader url-loader -D module: { rules: [ // 一个对象代表一个文件类型的 loader { // 声明要处理的文件格式 test: /\.css$/, // 匹配所有的 .css文件 // 用什么 loader 处理这个类型的文件 use: ['style-loader', 'css-loader'] }, // less loader 处理 less 文件 { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, // // 处理图片- 适合用在 webpack5 版本以前 设置以下配置 - 图片可以在 js 和 css 中使用 // { // // 识别的图片类型 // test: /\.(jpg|png|gif)$/, // // limit 用来指定图片的大小,单位是字节(byte) // // 只有 ≤ limit 大小的图片,才会被转为 base64 格式的图片;> limit的图片直接使用图片文件。 // // outputPath 输出路径 // // 写法一 // // use: 'url-loader?limit=23500&outputPath=images', // // 写法二 // loader: 'url-loader', // options: { // limit : 235000, // outputPath: 'images', // // 文件生成的名称 // // [name] 原文件名 - [ext] 拓展名 - [hash:8] 哈希值前八位 // name: '[name].[ext]', // // 这一项是用来在css中使用图片 // esModule: false // }, // // 这一项是用来在css中使用图片 // type: 'javascript/auto' // }, // 处理图片- 适合用在 webpack5 版本及以后版本 设置以下配置 - 图片可以在 js 和 css 中使用 { // asset test: /\.(png|gif|jpg)$/, type: 'asset' }, // 处理图片 - 图片可以在 html 中使用 // html-loader 需要提前 npm i html-loader -D { test: /\.html$/, use: 'html-loader' }, // 处理 js 高级语法 { test: /\.js$/, use: 'babel-loader', // 排除 node_modules 文件 exclude: /node_modules/ } ] } }
package.json
{ "name": "code", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --mode production", "serve": "webpack serve" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "jquery": "^3.6.0" }, "devDependencies": { "@babel/core": "^7.15.8", "@babel/plugin-proposal-decorators": "^7.15.8", "babel-loader": "^8.2.2", "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.4.0", "file-loader": "^6.2.0", "html-loader": "^4.1.0", "html-webpack-plugin": "^5.3.2", "less": "^4.1.2", "less-loader": "^10.1.0", "style-loader": "^3.3.0", "url-loader": "^4.1.1", "webpack": "^5.58.2", "webpack-cli": "^4.10.0", "webpack-dev-server": "^4.3.1" } }
前端工程化
前端小白眼中的前端开发 vs 实际的前端开发
-
前端小白眼中的前端开发:
-
① 会写 HTML + CSS + JavaScript 就是合格的前端程序员
-
② 东拼西凑,不成体系
-
-
实际的前端开发(四个现代化):
-
① 模块化(js 的模块化、css 的模块化、资源的模块化)
-
② 组件化(复用现有的 UI 结构、样式、行为)
-
③ 规范化(目录结构的划分、编码规范化、接口规范化、文档规范化、 Git 分支管理)
-
④ 自动化(自动化构建、自动部署、自动化测试)
-
什么是前端工程化
-
概念
-
以模块化、组件化、规范化、自动化为基础,进项前端项目开发的方式,叫做前端工程化
-
-
为什么进行工程化
-
工程化提供了一套标准的开发方案和流程,让前端开发自成体系
-
注意: 企业中的 Vue 和 React 项目,都是基于工程化的方式进行开发的 前端工程化
前端工程化的解决方案
-
早期的前端工程化解决方案:
-
目前主流的前端工程化解决方案:
-
webpack( webpack | webpack中文文档 | webpack中文网 )
-
parcel( Parcel 中文网 )
-
webpack 的基本概念
什么是 webpack
webpack 是前端项目工程化的具体解决方案。
为什么要学习 webpack
webpack 让前端开发变得更高效
① 代码压缩混淆
② 处理浏览器端 JavaScript 的兼容性
③ 以模块化的方式处理项目中的资源
注意:目前绝大部分的 Vue/React 等前端项目,都是基于 webpack 进行工程化开发的。
安装全部所需的包
目的:一次性把今天所需的包全部安装好。
# 切换镜像源 nrm use taobao
# 初始化 npm init
# 安装项目依赖包 npm install jquery
# 安装项目开发依赖包 npm install webpack@5.58.2 webpack-cli@4.9.0 clean-webpack-plugin@4.0.0 webpack-dev-server@4.3.1 html-webpack-plugin@5.3.2 style-loader@3.3.0 css-loader@6.4.0 less-loader@10.1.0 less@4.1.2 url-loader@4.1.1 file-loader@6.2.0 babel-loader@8.2.2 @babel/core@7.15.8 @babel/plugin-proposal-decorators@7.15.8 -D
webpack 的基本使用
创建列表隔行变色项目
目的:以模块化的方式实现列表隔行变色的项目,为 webpack 的学习做准备。
具体步骤:
① 在项目目录中,新建 src 源代码目录
② 在项目目录中,新建public 静态资源目录
③ src目录中创建index.js
④ public目录中创建index.html,引入index.js
⑤ 初始化首页基本的结构(创建ul>li这样的结构)
⑥ 通过 ES6 模块化的方式导入 jQuery,实现列表隔行变色效果
import $ from 'jquery'; $(function () { $('li:odd').css('background-color', '#ccffcc'); $('li:even').css('background-color', '#ffccff'); })
分析项目中遇到的问题
-
报错的原因:
-
浏览器对 ES6+ 高级语法支持的不好,有兼容性问题
-
-
如何解决:
-
在开发的时候,让程序员尽可能 “写得爽”
-
在运行的时候,让浏览器不出现“兼容性问题”
-
-
具体方案:
-
在项目中安装和配置 webpack,对 JS 代码进行降级处理
-
安装和配置webpack
目标:
-
通过 webpack 处理 JS 代码的兼容性问题
-
了解 webpack 的基本配置步骤。
安装 webpack 相关的两个包: webpack@5.58.2
和 webpack-cli@4.9.0
(前面已经统一安装过了)
① 在项目根目录中,创建名为 webpack.config.js
的 webpack 配置文件,并初始化如下的基本配置:
module.exports = { // 打包模式 mode: 'development', // production:生成 development:开发 }
② 在 package.json 的 scripts 节点下,新增 dev 脚本如下:
"scripts": { "dev": "webpack" },
③ 在终端中运行 npm run dev
命令,启动 webpack 进行项目的打包构建
打包结果
执行 npm run dev
命令后,会在项目根目录生成 dist
文件夹,并在 dist
文件夹中生成 main.js
main.js 就是 jquery.js 和 index.js 的结合体。
index.html 中引入 main.js ,再看效果。
mode 的可选值
目标:清楚每个 mode 值的作用,知道每个值的具体应用场景。
mode 节点的可选值有两个,分别是:
① development
-
开发环境
-
不会对打包生成的文件进行代码压缩和性能优化
-
打包速度快,适合在开发阶段使用
② production
-
生产环境
-
会对打包生成的文件进行代码压缩和性能优化
-
打包速度很慢,仅适合在项目发布阶段使用
webpack.config.js的作用
webpack.config.js是什么
-
webpack.config.js 是 webpack 的配置文件。
作用
-
告诉 webpack 怎么对项目进行打包。
被读取的时机
-
npm run dev 的时候(打包的时候),会先读取配置文件,再对项目进行打包处理。
webpack 中的默认约定
在 webpack 4.x 和 5.x 的版本中,有如下的默认约定:
① 默认的打包入口文件为 src -> index.js
② 默认的输出文件路径为 dist -> main.js
注意:可以在 webpack.config.js 中修改打包的默认约定
自定义打包的入口与出口
在 webpack.config.js 配置文件中,通过 entry 节点指定打包的入口。通过 output 节点指定打包的出口。
示例代码如下:
const { join } = require('path'); module.exports = { // 打包模式 mode: 'development', // production:生成 development:开发 // 入口文件 entry: join(__dirname, 'src', 'index.js'), // 出口文件 output: { path: join(__dirname, 'dist'), // 要使用绝对路径,否则报错 filename: 'bundle.js', } }
重新运行 npm run dev
,即可得到新的打包结果 bundle.js
.
index.html 中引入新的打包结果 bundle.js。
webpack 中的插件
概述
webpack 插件的作用
通过安装和配置第三方的插件,可以拓展 webpack 的能力,从而让 webpack 用起来更方便。
最常用的 webpack 插件有如下3个:
① clean-webpack-plugin
-
每次打包时,自动清理 dist 目录
② webpack-dev-server
-
类似于 node.js 阶段用到的 nodemon 工具
-
每当修改了源代码,webpack 会自动进行项目的打包和构建
③ html-webpack-plugin
-
webpack 中的 HTML 插件
-
可以通过此插件自定制 index.html 页面的内容
clean-webpack-plugin
作用:每次打包构建的时候,自动清理 dist 目录下的旧文件,保证 dist 目录的代码是最新的。
安装依赖包:clean-webpack-plugin@4.0.0 (前面已经统一安装过)
在 webpack.config.js 中增加配置:
// 1. 配置自动清理插件(在打包的时候,插件就会自动清理 dist 中没用的文件) const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const cleanPlugin = new CleanWebpackPlugin(); module.exports = { mode: 'development', // 其他项省略...... // 插件配置 plugins: [ cleanPlugin ] }
重新运行 npm run dev
,即可将 dist
文件夹中没用的文件清理掉.
webpack-dev-server
基本使用
作用:可以让 webpack 监听项目源代码的变化,从而进行自动打包构建。
安装包 webpack-dev-server@4.3.1(前面已经统一安装过)
配置 webpack-dev-server
① 修改 package.json -> scripts 中的 dev 命令如下:
"scripts": { "dev": "webpack serve" // 注意这里是 serve ,不是 server },
② 在 webpack.config.js 配置文件中,增加 devServer 节点对 webpack-dev-server 插件进行更多的配置
devServer: { port: 9000, // 实时打包所用的端口号 open: true // 初次打包完成后,自动打开浏览器 }
③ 再次运行 npm run dev 命令,重新进行项目的打包
④ 在浏览器中访问 http://localhost:8080 地址,查看自动打包效果
注意:凡是修改了 webpack.config.js 配置文件,或修改了 package.json 配置文件,必须重启实时打包的服务器,否则最新的配置文件无法生效!
打包生成的文件哪儿去了?
① 不配置 webpack-dev-server 的情况下,webpack 打包生成的文件,会存放到实际的物理磁盘上
-
严格遵守开发者在 webpack.config.js 中指定配置
-
根据 output 节点指定路径进行存放
② 配置了 webpack-dev-server 之后,打包生成的文件存放到了内存中
-
不再根据 output 节点指定的路径,存放到实际的物理磁盘上
-
提高了实时打包输出的性能,因为内存比物理磁盘速度快很多
生成到内存中的文件该如何访问?
-
webpack-dev-server 生成到内存中的文件,默认放到了项目的根目录中,而且是虚拟的、不可见的。
-
可以直接用 / 表示项目根目录,后面跟上要访问的文件名称,即可访问内存中的文件
-
例如 /bundle.js 就表示要访问 webpack-dev-server 生成到内存中的 bundle.js 文件
所以,index.html 中应该这样引入js <script src="/bundle.js"></script>
html-webpack-plugin
html-webpack-plugin 是 webpack 中的 HTML 插件。
作用:自动把生成好的 bundle.js 注入到 HTML 页面中。
安装包 html-webpack-plugin@5.3.2 (前面已经统一安装过)
在webpack.config.js中配置 html-webpack-plugin
const HtmlPlugin = require('html-webpack-plugin'); const htmlPlugin = new HtmlPlugin({ template: path.join(__dirname, 'public', 'index.html'), // public中,你的html叫什么 filename: 'index.html' // 打包后的html叫什么(这个文件会生成到dist文件夹) }); module.exports = { mode: 'development', // 其他项省略...... // 插件配置 plugins: [ cleanPlugin, htmlPlugin ] }
其他说明:
① 通过 HTML 插件复制到项目根目录中的 index.html 页面,也被放到了内存中,所以看不到
② HTML 插件在生成的 index.html 页面,自动注入了打包的 bundle.js 文件
webpack 中的 loader
loader 概述
loader加载器有什么用?
-
在实际开发过程中,webpack 默认只能打包处理以 .js 后缀名结尾的模块。
-
其他非 .js 后缀名结尾的模块, webpack 默认处理不了
-
需要调用 loader 加载器才可以正常打包非 js 文件,否则会报错!
所以,loader 加载器的作用是协助 webpack 打包处理特定的文件模块。比如:
-
css-loader 可以打包处理 .css 相关的文件
-
less-loader 可以打包处理 .less 相关的文件
-
babel-loader 可以打包处理 webpack 无法处理的高级 JS 语法
loader 的调用过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7CXnpKq0-1660726620410)(C:\Users\hp\Desktop\02-案例-资料-笔记\02-案例-资料-笔记\NodeJS资料\项目前置课-05\webpack文档\md-imgs\image-20211224205337478.png)]
打包处理 css 文件
① 安装包 style-loader@3.3.0 和 css-loader@6.4.0 (前面已经统一安装过)
② 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:
module.exports = { mode: 'development', // 其他项省略...... module: { rules: [ // 处理css文件 { test: /\.css$/, use: ['style-loader', 'css-loader'] }, ] } }
其中,test 表示匹配的文件类型, use 表示对应要调用的 loader
注意:
-
use 数组中指定的 loader 顺序是固定的
-
多个 loader 的调用顺序是:从后往前调用
打包处理 less 文件
① 安装 less-loader@10.1.0 和 less@4.1.2(前面已经安装过)
② 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:
module.exports = { mode: 'development', // 其他项省略...... module: { rules: [ // 处理css文件 { test: /\.css$/, use: ['style-loader', 'css-loader'] }, // 处理less文件 { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, ] } }
打包处理与 url 路径相关的文件
① 安装 url-loader@4.1.1和 file-loader@6.2.0(前面已经统一安装过)
② 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:
module.exports = { mode: 'development', // 其他项省略...... module: { rules: [ // 处理css文件 { test: /\.css$/, use: ['style-loader', 'css-loader'] }, // 处理less文件 { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, // 处理图片文件 { test: /\.(jpg|png|gif)$/, use: 'url-loader?limit=6055&outputPath=images' }, ] } }
其中 ? 之后的是 loader 的参数项:
-
limit 用来指定图片的大小,单位是字节(byte)
-
只有 ≤ limit 大小的图片,才会被转为 base64 格式的图片;> limit的图片直接使用图片文件。
打包处理 js 文件中的高级语法
webpack 只能打包处理一部分高级的 JavaScript 语法。对于那些 webpack 无法处理的高级 js 语法,需要借 助于 babel-loader 进行打包处理。
例如 webpack 无法处理下面的 JavaScript 代码:
// js装饰器 function info(target) { target.abc = '哈哈哈哈哈哈哈,这是新语法'; } // 下面的语法,表示给Person加了一个abc属性 @info class Person { } consle.log(Person.abc);
安装 babel-loader 相关的包 babel-loader@8.2.2 和 @babel/core@7.15.8 和 @babel/plugin-proposal-decorators@7.15.8 (前面已经统一安装过)
在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:
module.exports = { mode: 'development', // 其他项省略...... module: { rules: [ // 处理css文件 { test: /\.css$/, use: ['style-loader', 'css-loader'] }, // 处理less文件 { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, // 处理图片文件 { test: /\.(jpg|png|gif)$/, use: 'url-loader?limit=6055&outputPath=images' }, // .js 文件使用 babel-loader去处理。但是不要处理 node_moduels文件夹中的第三方模块 { test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ } ] } }
配置 babel-loader
在项目根目录下,创建名为 babel.config.js
的配置文件,定义 Babel 的配置项如下:
module.exports = { // 声明 babel 可用的插件 plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]] }
打包发布
为什么要打包发布
项目开发完成之后,需要使用 webpack 对项目进行打包发布,主要原因有以下两点:
① 开发环境下,打包生成的文件存放于内存中,无法获取到最终打包生成的文件
② 开发环境下,打包生成的文件不会进行代码压缩和性能优化
为了让项目能够在生产环境中高性能的运行,因此需要对项目进行打包发布。
配置 webpack 的打包发布
在 package.json 文件的 scripts 节点下,新增 build 命令如下:
"scripts": { "dev": "webpack serve", "build": "webpack --mode production" },
-
--model 是一个参数项,用来指定 webpack 的运行模式。
-
production 代表生产环境,会对打包生成的文件 进行代码压缩和性能优化。
注意:通过 --model 指定的参数项,会覆盖 webpack.config.js 中的 model 选项。
把 JavaScript 文件统一生成到 js 目录中
-
在 webpack.config.js 配置文件的 output 节点中,进行如下的配置:
output: { path: path.join(__dirname, 'dist'), filename: 'js/bundle.js', // 这里加入 js 文件夹。 },
把图片文件统一生成到 image 目录中
-
修改 webpack.config.js 中的 url-loader 配置项,新增 outputPath 选项即可指定图片文件的输出路径:
module: { rules: [ { test: /\.(jpg|png|gif)$/, use: 'url-loader?limit=6055&outputPath=images' }, ] }
Source Map
目前的问题
-
开发环境中,错误行号对应不上。比如本来是在第23行报错了,但是浏览器提示在第20行报错。
-
打包后,又不希望我们的代码被其他人看到源码。
上述问题,可以通过sourceMap解决。
什么是 Source Map
Source Map 就是一个信息文件,里面储存着位置信息。
也就是说,Source Map 文件中存储着压缩混淆后的代码,所对应的转换前的位置。
有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码,能够极大的方便后期的调试。
配置
开发环境
推荐在 webpack.config.js 中添加如下的配置,即可保证运行时报错的行数与源代码的行数 保持一致:
module.exports = { // eval-source-map 仅限在开发模式中使用 //(开发中,程序员需要排错,需要准确的定位错误行号) devtool: 'eval-source-map', }
生产环境
module.exports = { // nosources-source-map 适合生产环境。 //(生产环境,我们不希望别人看到我们的源码,这个配置项只显示行号,但不会显示源码) devtool: 'nosources-source-map', }