前端自我总结 - HTML、CSS、移动Web、JavaScript、Ajax、Git、Node、Vue、VueRouter、Vuex、webpack

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的注释

    1. 注释语法

      <!--这里面是注释的内容-->
      • 注释是让程序员观看的,注释部分不会被浏览器渲染

      • 快捷键 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. 媒体标签

  1. 图片标签

  2. 路径

  3. 音频标签

  4. 视频标签

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. 列表标签

  1. 无序列表

  2. 有序列表

  3. 自定义列表

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)

    • 合并单元格步骤

        1. 明确合并哪几个单元格

        1. 通过左上原则,确定保留谁删除谁 • 上下合并→只保留最上的,删除其他 • 左右合并→只保留最左的,删除其他

        1. 给保留的单元格设置:跨行合并(rowspan)或者跨列合并(colspan) • rowspan:跨行合并→垂直方向合并 • colspan:跨列合并→水平方向合并

15 表单标签

  1. input系列标签

  2. button按钮标签

  3. select下拉菜单标签

  4. textarea文本域标签

  5. 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>

  • 使用方法

      1. 使用label标签把内容(如:文本)包裹起来

      2. 在表单标签上添加id属性

      3. 在label标签的for属性中设置对应的id属性值

      1. 直接使用label标签把内容(如:文本)和表单标签一起包裹起来

      2. 需要把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. 字体实符

  • 常见

    • &nbsp; 空格

    • &gt; 大于号

    • &lt; 小于号

CSS 层叠样式表

  1. CSS引入方式

  2. 基础选择器

  3. 字体和文本样式

1. CSS 引入方式

  1. 内嵌式

  2. 外联式

  3. 行内式

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. 基础选择器

  1. 标签选择器

  2. 类选择器

  3. id选择器

  4. 通配符选择器

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. 字体和文本样式

  1. 字体样式

  2. 文本样式

  3. line-height行高

3.1 字体样式

  1. 字体大小:font-size

  2. 字体粗细:font-weight

  3. 字体样式:font-style

  4. 字体类型:font-family

  5. 字体类型: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 文本样式

  1. 文本缩进:text-indent

  2. 文本水平对齐方式:text-align

  3. 文本修饰: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 知识点

  1. 宽度不写的情况下的默认设置是 auto;宽度为自动时,其大小为父盒子的宽度 - 子盒子的内外边距和边框。

  2. 快盒子的高度是被内容撑开的;给html设置高度为100%,那么就会得到一个和视口一样大的盒子;给body设置高度为100%,那么body会和html高度大小一样。

  3. 背景图默认会平铺,值为两个(x轴 y轴);也可以为方位名词,left的含义是让背景图的左侧边缘和盒子的左侧边缘重叠,其他方位名词同理。

  4. pc端宽度一般都是给个固定版心,移动端宽度多数是100%

  5. 分辨率:指的是水平和垂直方向的像素

    1. 物理分辨率:出场设置的物理分辨率

    2. 逻辑分辨率:通过软件缩放后显示的分辨率

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 写法(原理)

    1. 设置HTML标签字号

    2. 设置盒子的尺寸是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可以共用,但是这样会很麻烦

  • 适配方案

    1. 使用 vw 进行屏幕适配不需要动态调整字号

      • 不需要使用媒体查询

      • 不使用 flexbile.js

    2. 只需要让【设计稿中的元素尺寸 / 视口的 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 [ 框架 ]

  • 参考中文文档:全局 CSS 样式 · Bootstrap v3 中文文档 | Bootstrap 中文网

  • 可以定制更改其中的部分内容,然后重新下载即可。(可更改视口宽度为多少时隐藏)

  • 样式框架:别人写好的代码,拿过来可以直接用,通过它可以少些样式,就能开发漂亮的网页

  • 功能类别是有区别的

    • 有的框架是针对样式的

      • BootStrap 等

    • 有的框架是针对逻辑的

      • Vue 等

  • 操作步骤

    1. 先引入 bootstrap 的样式代码

      • 切记路径问题

    2. 给 html 标签添加【对应的】类名

      • 所谓对应的类名不能乱写,必须是 bootstrap 中定义好的

    3. 保证以上两点, 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 组件

14.6 图标

14.7 插件

  • 使用步骤

    1. 引入css样式

    2. 引入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 提升
  1. var 是 ES5 中,let 是 ES6 中

  2. var 可以重复声明变量,let 不可以重复声明变量

  3. 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个要素,能避免出错

    1. 起始值

    2. 中止值

    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. 断点调试
  • 属于(高级的调试功能)

  • 所谓的断点调试 是【浏览器】提供的一个调试代码工具

  • 它可以让代码【暂停】下来,看到代码的执行过程

  • 进而帮助我们分析程序的问题可能出现在哪里

  • 步骤

      1. 通过调试工具【源代码】找到要调试的 js

      1. 在代码中点击相应的【行号】,便可以添加【断点】

      1. 刷新页面,让程序重新执行

      1. 再次点击行号,可以删除断点

JavaScript 高级

1. 作用域

  • 全局作用域

    • 【函数外】,在全局作用域定义的变量

    • 全局变量可以在任何地方使用

  • 局部作用域

    • 【函数内】,在局部作用域定义变量

    • 局部变量只能在当前作用域使用

  • 块级作用域(ES6)

    • 【大括号内】,在块级作用域定义的变量

    • 块级变量只能在当前块级使用

2. 声明变量关键字

  • varletconst(常量)

  • varlet 区别:

    1. var声明的比那辆属于window, let声明的变量不属于window

    2. var声明的变量不具有块级作用域, let声明的变量具有块级作用域

    3. var可以重复声明,let不可以重复声明

  • conts(常量)

    1. const声明的变量不属于window

    2. const声明的变量具有块级作用域

    3. const不可以重复声明

    4. const声明的变量必须初始化有值

    5. const声明的变量不允许改值

    • 建议

      1. 声明常量时,常量名全大写

      2. 不变的值建议用常量定义

3. 作用域链

  • 作用域链:由作用域串联起来链

  • 作用:提供查找变量的机制

  • 当一个作用域访问一个变量时,先从当前作用域查找,如果不存在这个变量,那么就会往上级查找

4. 闭包

  • 闭包

    • 一个作用域有权访问另外一个作用域的局部变量

    • 子函数访问了父函数的局部变量,然后把子函数返回到外面

  • 作用

    • 延伸变量的使用范围,避免变量污染

// fn 为闭包函数
function fn () {
    let num = 666
    return function fun () {
        console.log( num )
    }
}
let re = fn()
re()  // 666

5. 预解析

  • js运行三部曲

    1. 先语法分析 扫面一遍看看有没有语法错误

    2. 预解析

    3. 解释执行 解释一行执行一行

  • 预解析四部曲

    1. 创建AO对象

    2. 找形参和变量声明,将变量和形参名作为AO属性名,值为 undefined

    3. 将实参值和形参值统一

    4. 在函数里面找函数声明,值赋予函数体

  • 预解析:代码执行之前先要预解析

    • 变量:带有声明的变量,也叫变量的提升

      • 把变量的声明语法提升到当前作用域的最前面

      • 注意:只声明不复制

    • 函数:带有名字的函数

      • 把函数的声明语法提升到当前作用域的最前面

      • 注意:只声明不调用

    • 预解析

      • 函数声明整体提升 -- 声明变量提升 - 变量不赋值

      • 变量 声明提升 -- 声明函数提成 - 函数不执行

  • let 生命前不可以用,生命后才可以使用,constlet

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))
  • 箭头函数注意事项

    1. 在箭头函数中不存在arguments,所以箭头函数不可以使用arguments

    let fn1 = ( ...a ) => {
      // console.log( arguments ) // 报错
      console.log( a ) // [1, 5, 6, 8, 651, 5]
    }
    fn1( 1, 5, 6, 8, 651, 5 )
    1. 箭头函数不存在预解析,所以是用箭头函数的时候必须先声明后调用

    let fn2 = () => {
      console.log( 123 )
    }
    fn2()
    1. 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()
    1. 箭头函数可以有 事件对象

    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 函数的注意事项
  1. 函数如果有return, 会把结果返回

  2. 如果函数只写return不写值,那么默认返回一个undefined

  3. 如果函数不写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
  • 注意:

    1. 对象里的成员是无序的

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 )
  • 注意

    1. 如果不加参数,那么Object构造函数后面的小括号可以省略

    2. 构造函数建议首字母大写

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指向
  1. 事件处理函数里的this指向事件源

  2. 箭头函数里的this指向父级的this指向

  3. 构造函数里的this指向的是当前实例对象(new 出来的对象叫实例对象)

8.4 new的执行
  • 执行过程

    1. 开辟空间

    2. this 指向这个函数

    3. 执行函数

    4. 返回这个对象

// 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. 内置构造函数

引用类型:

  1. Object

  2. Array

  3. RegExp

包装类型:

  1. String

  2. Number

  3. 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索引位置上对应的字符

  • 注意:

    1. 不管是substring还是substr,截取字符串

    2. 如果只有一个参数,那么就是从这个字符串开始截取到最后

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

添加成员:

  1. 实例成员 - 属性名 = 属性值

  2. 静态成员 - 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 () {}

注意:

  1. 构造方法,在new的时候自动执行

  2. 构造方法,如果不写的话,那么内部会默认的添加上

  3. 构造方法,名字是固定的,不可以更改

  4. 构造方法,用来接收参数,做一些初始化的操作

  5. 一个类里面只能有一个构造方法,顺序无要求,建议写前面

// 创建类
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 种常见的操作

    1. POST -- 向服务器新增数据

    2. GET -- 从服务器获取数据

    3. DELETE -- 删除服务器上的数据

    4. PUT -- 更新服务器上的数据(侧重于完整更新:例如更新用户的完整信息)

    5. PATCH -- 更新服务器上的数据(侧重于部分更新:例如只更新用户的手机号)

5. axios

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. 接口的概念

9.1 接口文档
  • 接口文档就是接口的使用说明书,它是我们调用接口的依据。

  • 请参考一个现成的接口文档

9.2 接口测试工具

10. 表单的三部分

  • 网页中采集数据的表单由三个部分组成,分别是:表单标签表单域表单按钮

  1. HTML 的 <form> 就是表单标签,它是一个“容器”,用来将页面上指定的区域划定为表单区域

  2. 表单域提供了采集用户信息的渠道,常见的表单域有:input、textarea、select 等。

    • 注意:每个表单域必须包含 name 属性,否则用户填写的信息无法被采集到

  3. 当表单数据填写完毕后,用户点击表单按钮,会触发表单的提交操作,从而把采集到的数据提交给服务器

    • 注意:

      • type="submit" 表示提交按钮的意思

      • type 属性的默认值就是 submit,因此 type="submit" 可以省略不写

11. 同源策略&跨域

  1. 什么是同源:

    指的是两个url地址具有相同的协议、主机名、端口号

  2. 什么是跨域:

    同源指的是两个 URL 的协议、主机名、端口号完全一致,反之,则是跨域。

  3. 什么是同源策略:

    是浏览器提供的一个安全功能

    浏览器的同源策略规定:不允许非同源的 URL 之间进行资源的交互。

Ajax 语法

1. axios 的基本使用

url: 地址

method: 请求方式,不写默认是 GET

<!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: GETPOST 数据的提交方式(默认值为 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 请求时设置该属性没有意义

  • 功能:约定参数格式

  1. application/x-www-form-urlencoded 最常用,参数都是文字、数字、布尔,不写默认就是该值

  2. multupart/form-data 上传文件

  3. text/plain 几乎不用,纯文本

6.4 结合from和ajax

from只用来收集数据

ajax负责提交

submit 提交事件 阻止默认行为 e.preventDefault()

7. axios请求方法的别名

  1. GET的请求方法的别名

    语法:axios.get()

    第一个参数,必填,url地址

    第二个参数,选填,对象,对象中设置params,参数

  2. 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的基本使用
  1. 创建 XMLHttpRequest 对象 -- readyState 为 0

    const xhr = new XMLHttpRequest()

  2. 调用 open 方法,设置请求方式和请求地址 -- readyState 为 1

    第一个参数:请求方式

    第二个参数:请求的地址

    xhr.open('请求方式', '请求地址')

  3. 调用 send 方法,发起请求 -- readyState 为 2

    xhr.send()

      • readyState 为 3,表示数据正在传输的过程中

  4. 监听事件(两种形式),获取服务端返回的数据

    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请求
  1. 请求体参数应放在send参数里

  2. 设置请参数的编码格式

    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`

格式规定:

  1. key 必须使用双引号包裹

  2. value 必须是 字符串(必须用双引号包裹)、数字、布尔值、null、数组、对象类型

JSON 操作:

  1. 序列化:把真实的数据转换为字符串的过程

  2. 反序列化:把字符串转换为真实数据的过程

// 数据交换格式
// 数据交换格式,就是服务器端与客户端之间数据传输的格式。
// 两种数据交换格式:
// 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

  1. 发送数据时可以发送 json

    一定要设置 json 格式的请求头

    xhr.setRequestHeader('Content-Type', 'application/json')

  2. 后端给我们返回的是 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 的

频繁触发某个操作时,只执行最后一次

过程:

  1. 在事件外面声明定时器 id 变量

  2. 在每一次事件触发时都干掉上一次的 timer

  3. 定义一个延迟定时器,在定时器内执行事件的逻辑,保存定时器 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)

  1. 节流阀,标识符,标记当前是否有一个定时器

  2. 在事件中判断是否有一个定时器

  3. 在定时任务纲要创建时,把节流阀修改状态

  4. 创建定时器 -- 要执行的逻辑

  5. 把节流阀修改回初始状态

<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仓库

    1. 工作区:正在写的项目文件夹区域

    2. 暂存区:临时存放已经完成的文件区域

    3. Git仓库:把最终存放的文件区域

3. Git 中的三个状态

  • 本别是已修改、已暂存、已提交

    1. 已修改:表示修改了文件,但还没 将修改的结果放到暂存区

    2. 已暂存:表示对已修改文件的当前 版本做了标记,使之包含 在下次提交的列表中

    3. 已提交:表示文件已经安全地保存 在本地的 Git 仓库中

4. 本的 Git 工作流程

  1. 在工作区中修改文件

  2. 将你想要下次提交的更改进行暂存

  3. 提交更新,找到暂存区的文件,将快照永久性 存储到 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仓库

  1. 将本地的目录初始化为Git仓库(本地已有或刚创建项目时)

    1. 打开项目文件夹

    2. 在该文件夹中右键->Git Bash Here 打开Git命令

    3. 在命令行输入 git init

  2. 从远程服务器克隆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 的格式规范如下:

    1. 以 # 开头的是注释

    2. 以 / 结尾的是目录

    3. 以 / 开头防止递归

    4. 以 ! 开头表示取反

    5. 可以使用 glob 模式进行文件和文件夹的匹配(glob 指简化了的正则表达式)

      • 所谓的 glob 模式是指简化了的正则表达式:

        1. 星号 * 匹配零个或多个任意字符

        2. [abc] 匹配任何一个列在方括号中的字符 (此案例匹配一个 a 或匹配一个 b 或匹配一个 c)

        3. 问号 ? 只匹配一个任意字符

        4. 在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹 配所有 0 到 9 的数字)

        5. 两个星号 ** 表示匹配任意中间目录(比如 a/**/z 可以匹配 a/z 、 a/b/z 或 a/b/c/z 等)

8. 远程创建仓库连接

  1. 创建的仓库(创建时,最后有复选框,勾选和不勾选会导致了两种关联方式)

    1. 不勾选复选框(创建了空仓库)

      1. 在本地创建项目文件夹

      2. git init

      3. 保证文件夹内有文件(建议新建一个README.md文件,这个是网页首页显示的)

      4. git add .

      5. git commit -m "备注"

      6. git remota add origin 仓库地址

      7. git push -u origin "master"(-u origin master设置默认推送)

    2. 勾选复选框(创建非空仓库,或者到单位给一个非空的项目)

      1. 进入到要存放的项目文件夹

      2. 在文件夹上打开命令行

      3. git clone 仓库地址(克隆/下载复制来的地址)

      4. 不需要做任何配置,直接进行后续的开发

    3. 开发

      1. 每完成一个功能(页面)

      2. git add .

      3. git commit -m "备注"

      4. git push

        234三个步骤重复执行

9. Git 分支

  • 分支概念:

    • 分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努 力学习SVN。

    • 如果两个平行宇宙互不干扰,那对现在的你也没啥影响。

    • 不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!

  • 分支在实际开发中的作用

    • 在进行多人协作开发的时候,为了防止互相干扰,提高协同开发的体验,建议每个开发者都基于分支进行项 目功能的开发

  • master 主分支

    • 在初始化本地 Git 仓库的时候,Git 默认已经帮我们创建了一个名字叫做 master 的分支。通常我们把这个 master 分支叫做主分支。

    • 作用:

      • 用来保存和记录整个项目已完成的功能代码。因此,不允许程序员直接在 master 分支上修改代码,因为这样做的风险太高,容易导致整个项目崩溃。

  • 功能分支

    • 功能分支指的是专门用来开发新功能的分支,它是临时从 master 主分支上分叉出来的,当新功能开发且测 试完毕后,最终需要合并到 master 主分支上。

10. Git 本地分支操作

  1. 查看分支列表

    # 使用如下的命令,可以查看当前 Git 仓库中所有的分支列表:
    # 注意:分支名字前面的 * 号表示当前所处的分支。
    git branch
  2. 创建新分支

    # 使用如下的命令,可以基于当前分支,创建一个新的分支,此时,新分支中的代码和当前分支完全一样:
    # 基于主分支上来创建的 复制主分支所有内容到新分支
    git branch 分支名
  1. 切换分支

    # 使用如下的命令,可以切换到指定的分支上进行开发:
    git checkout 分支名
  1. 合并分支

    # 切换到主分支
    git checkout 主分支名
    # 把选择的功能分支合并到主分支上
    git merge 功能分支名
  1. 分支的快速创建和切换

# 使用如下的命令,可以创建指定名称的新分支,并立即切换到新分支上:
get checkout -b 分支名称
  1. 删除本地分支

# 当把功能分支的代码合并到 master 主分支上以后,就可以使用如下的命令,删除对应的功能分支:
get branch -d 分支名称
  1. 遇到冲突时的分支合并

    • 发生冲突后手动选择留下哪一个或者都留下

11. Git 远程分支操作

  1. 如果是第一次将本地分支推送到远程仓库,需要运行如下的命令:

例如:

# -u:把本地分支和远程分支进行关联,远程的 origin 这个仓库,本地的时 list 仓库和远程的 li 分支进行关联 
git push -u origin list:li
注:后续在本地list分支推送到远程li分支的时候只需要 git push 就好了,不用加 -u 后的代码

#-u:把本地分支和远程分支进行关联,远程的 origin 这个仓库,本地的时 list 仓库和远程的 list 分支进行关联 
git push -u origin list
  1. 查看远程仓库中所有的分支列表

    git remote show 远程仓库名称
  2. 拉取远程分支的最新的代码

    # 从远程仓库,拉取当前分支最新的代码,保持本地分支代码和远程分支代码一致
    git pull

Node

1. 什么是 Node.js

Node.js:是一个基于Chrome V8 引擎的 JavaScript运行环境

Node.js官方地址: Node.js — 在任何地方运行 JavaScript

注意:

  1. 浏览器是JavaScript的前端运行环境。

  2. Node.jsJavaScript的后端运行环境。

  3. Node.js中无法调用DOMBOM等浏览器内置API

2. 使用node命令执行JS文件

操作步骤:

  1. 打开终端

  2. 注意路径,进入到你要运行的项目文件目录下

  3. 输入node 要执行的js文件

注意:

  1. 执行文件的时候,需要保证node xxx.js这种格式

  2. 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. 自定义模块

  1. 导出文件 - 导出

    方式一:

    module.exports = { 变量名, 方法名,...}

    方式二:

    exports.xx = xx

  2. 导入文件 - 导入

    引入:let xxx = require('地址URL')

    console.log(xxx.变量名)

示例:

  1. 导出内容 (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
  1. 导入内容 (userModule.js)

// 引入模块
let m1 = require('./02-m1')
console.log(m1.PI);
console.log(m1.fn);

7. 内置模块

内置模块是Node.js平台自带的一套基本API(功能模块)。也叫做核心模块

注意:

  • 加载内置模块时,不能写路径,这是和加载自定义模块的区别

7.1 path模块

示例

// 第一步:先加载要使用的模块
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 模块
  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
  • 注意:

    1. 初始化之后,会在项目目录中生成 package.json 的文件。

    2. package name 默认使用当前文件夹 当做 包的名字

    3. 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"
  }
}
  1. name - 项目名,不能有中文

  2. version - 版本号,只能数字和点

  3. description - 描述

  4. main - 项目的入口文件是哪一个文件

  5. scripts - 声明简化命令

{
  "简化命令": "真实命令(命令行能用的名)",
  "start": "node ./index.js",
  // "start": "ipconfig"
}
// 执行简化命令,在命令行中输入 npm run 简化命令
// start特殊,start可以 npm run start 也可以 npm start
  1. author - 作者

  2. license - 许可证

  3. dependencies - 项目运行依赖(第三方包的信息,这里包含的包只要没有,项目就无法运行)

  4. devDependencies - 项目开发依赖(开发中会用到,在项目运行时不会用,例如:less,sass)

9. requeire加载机制

  1. 判断缓存中有没有,如果有,使用缓存中的内容

  2. 缓存中没有,那么表示第一次加载,加载完会缓存

  3. 判断模块名有没有带路径(./)

  4. 模块名中有路径,加载自定义模块(自己写的文件)const xx = require('./xx')

    1. 优先加载同名文件,加载一个叫做 xx 的文件

    2. 再次加载js文件,加载 xx.js 文件

    3. 再次加载json文件,加载 xx.json 文件

    4. 最后加载node文件,加载 xx.node文件

    5. 如果上述文件都没有,则报错 “Cannot find module './xx'”

  5. 模块名没有路径优先加载核心模块如果没有核心模块,则加载第三方模块

  6. 加载第三方模块的查找方式

    1. 优先在当前文件夹的node_modules里面查找第三方模块

    2. 在当前文件夹的上级目录的node_modules里面查找第三方模块

    3. 继续向上层文件夹查找第三方模块

    4. .........

10. 开发属于自己的包

更多关于npm的命令:npm中文文档 | npm中文网

10.1 规范的包结构

在清楚了包的概念、以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构。

📂 - sy123
		📃 - package.json  (package.json包的配置文件)
		📃 - index.js      (入口文件)
		📃 - README.md     (说明文档)

一个规范的包结构,需要符合以下 3 点要求:

  1. 包必须以单独的目录而存在

  2. 包的顶级目录下要必须包含 package.json 这个包管理配置文件

  3. 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
  • 注意:

    1. 删除的包,在 24 小时内不允许重复发布(同名的包)

    2. npm unpublish 删除的包,在 24 小时内不允许重复发布

    3. 发布包的时候要慎重,尽量不要往 npm 上发布没有意义的包!

11. 模块化

是一种编程方式,将整体打碎,编程一个个的模块,每个模块是独立的功能,把模块合到一起拼成完成的项目。

11.1 浏览器中使用模块

  1. AMD - 依赖前置。require.js

  2. CMD - 依赖就近。sea.js

    注意:非官方实现规范

11.2 NodeJs中使用模块

CommonJs,require引入,module.exports导出

  1. 自定义模块

    require('./要引入的js文件路径')

    来源:自己编写

  2. 核心模块

    require('包名')

    来源:nodejs自带

  3. 第三方模块

    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 - 基本语法

  1. 定义Promise对象

    const p1 = new Promise((resolve,reject) => {
    	// 成功时执行resolve,比如成功获取服务端数据,读取文件内容成功。
      // 失败时执行reject,因为参数错误(权限不够)导致业务执行失败
      // resolve(想传给外面的数据,比如成果获取到服务数据或读取到文件内容)
      // reject(失败信息)
    })
  2. 使用

    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. 当有任务时:

    • 从最先进入的任务开始执行。

  2. 没有其他任务,休眠直到出现任务,然后转到第 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 的基本使用

初级使用步骤

  1. 在项目根目录创建 webpack.config.js 文件

  2. 在这个文件内写一下代码

    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'
      }
    }
  3. 在package.json中script内声明 "build": "webpack"

  4. 运行简化命令行 npm run build

  5. 在html中引入生成的main.js

注意

  1. 默认的打包入口文件为 src -> index.js

  2. 默认的输出文件路径为 dist -> main.js

  3. 可以在 webpack.config.js 中修改打包的默认约定

18.1 安装和配置webpack

目标:

  • 通过 webpack 处理 JS 代码的兼容性问题

  • 了解 webpack 的基本配置步骤。

安装 webpack 相关的两个包: webpack@5.58.2webpack-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中常见的指令

  1. 内容渲染指令(对应dom中的文本节点

    • {{ }}

    • v-html

  2. 属性绑定指令(对应dom中的属性节点

    • v-bind

    • :

  3. 事件绑定指令(对应dom中的事件

    • v-on:事件类型="事件处理函数"

    • @事件类型="事件处理函数"

    • 事件修饰符

      • .stop

      • .prevent

    • 按键修饰符

      • .enter

      • ...

  4. 双向绑定指令(vue中特有的,react中想做需要自己实现)

    • v-model

    • 专属修饰符

      • .number

      • .trim

      • .lazy

  5. 条件渲染指令(在页面模板中写if

  6. 列表渲染指令(在页面模板中写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>
  • 注意

    1. 如果事件处理函数只有一行代码,可以写到行内

      <button @click="count = 0"> 点击恢复初始值 0 </button>
    2. 事件传参

      <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指令还支持一个可选的第二个参数,即当前的索引

  • 注意:

    1. 使用 v-for 指令时 一定要指定 key 的值(既提升性能,又防止列表渲染混乱)

    2. key 的值只能是字符串或数字类型

    3. 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 框架的特性,主要体现在两方面:

    1. 数据驱动视图

      • 在使用了 vue 的页面中,data 数据的变化,会导致页面结构的重新渲染。

      • 好处:减少了程序员对 DOM 的

      • 注意:数据驱动视图是单向的数据绑定。

    2. 双向数据绑

      • data 数据的变化,会导致页面的重新渲染

      • 表单数据的变化,会被自动更新到 data 数据中

      • 好处:开发者不再需要手动操作 DOM 元素,来获取表单元素最新的值

3.2 MVVM

MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。

MVVM 指的是 Model、View 和 ViewModel。

它把每个 HTML 页面都拆分成了这三个部分,如图所示:

4. vue 过滤器

  1. 定义过滤器

    Vue.filter('过滤器名字', fn)

  2. 使用过滤器

    {{ 要转换格式的数据 | 过滤器的名字 }}

  • 作用:

    过滤器时给在模板中使用的数据做格式转换的

  • 用途:过滤器可以使用在两个地方:

    1. 插值表达式

    2. v-bind 属性绑定

  • 使用步骤:

    1. 定义过滤器

      Vue.filter('dollar', (str) => {
      	return '$' + str
      })
    2. 使用过滤器

      <h2>{{ money | dollar }}<h2>
      data: {
      	money: 100
      }
  • 注意:

    1. 过滤器必须定义在 new Vue 之前。

    2. fn 函数的格式:(要转换格式的数据) => { return 转换格式后的结果 }

    3. 过滤器仅在 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
      }
    }
  • 注意:

    1. 侦听器要写成对象形式,参数放在 handler 函数里面

    2. 添加 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
      }
    }
  • 注意:

    1. 侦听器要写成对象形式,参数放在 handler 函数里面

    2. 如果监听的数据是一个对象,想要对象里任何一个属性发生变化监听到,就要设置 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 数据发生变化,这个计算属性也会跟着变化。

  • 示例:

    1. 定义计算属性:

      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 }}
  • 注意:

    1. 计算属性声明在 computed 选项下面

    2. 计算属性声明的时候被定义为一个方法,使用的时候是作为一个属性去使用。

    3. 计算属性中,必须 return 一个计算的结果

6.2 计算属性 - 计算属性的缓存

  • 计算属性能做的事,都可以使用方法代替。

  • 区别:计算属性会缓存计算的结果,只有计算属性依赖的数据变化时,才会重新进行计算。而函数不会缓存计算的 结果,所以计算属性的性能比函数要好。

  • 示例:

    1. 声明一个计算属性和一个方法

      // 使用 computed 选项声明计算属性
      computed: {
        // 计算属性定义的的时候是一个函数
        multi() {
          // 不管调用调用几次,只要data里的值不变就会调用一次,因为计算结果会缓存
          // 只有计算属性依赖的数据变化时,再会再次调用一次
          console.log('调用了计算属性');
          // 计算属性中,要通过 return 返回一个值
          return this.n1 * this.n2
        }
      },
      // 使用方法也可以是实现计算
      methods: {
        multiByMethod() {
          // 调用几次,打印几次
          console.log('调用了计算方法');
          return this.n1 * this.n2
        }
      }
    2. 在模板中分别多次调用使用计算属性和方法

      <!-- 使用 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>
    • 打印结果:

      调用了计算属性

      调用了计算方法

      调用了计算方法

      调用了计算方法

  • 注意:

    1. 使用 computed 选项声明计算属性

    2. 计算属性定义的的时候是一个函数

    3. 不管调用调用几次,只要data里的值不变就会调用一次,因为计算结果会缓存

    4. 只有计算属性依赖的数据变化时,再会再次调用一次

    5. 计算属性中,要通过 return 返回一个值

    6. 使用方法,调用几次,打印几次

7. vue - cli - 安装和使用

  1. 全局安装 vue-cli npm install -g @vue/cli

    使用 vue -V 命令检查是否安装成功

  2. 基于 vue-cli 快速生成工程化的 Vue 项目

    vue create 项目的名称(如:vue create demo-1)

  3. 启动项目

    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>
  • 导致组件之间样式冲突的原因是:

    1. 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的

    2. 每个组件中的样式,都会影响整个 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 样式穿透 - 深度选择器

  1. css中

    >>>

  2. less中

    /deep/

在给当前组件的 style 添加了 scoped 属性后,如果想在父组件中修改子组件的样式,就需要使用 深度选择器。

  1. 在 css 中使用 >>>

    示例:Left.vue

    <style scoped>
      /* 在 css 中使用 >>> 实现样式穿透 */
      div >>> h4 {
        color: skyblue;
      }
    </style>
  2. 在 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 方法

  • 步骤:

    1. 父组件(接收方)

      在要接收的子组件的标签上,注册一个自定义事件,并设置事件处理函数,把事件处理函数写到methods的方法中,事件处理函数的形参就是接收的值

    2. 子组件(传送方)

      使用语法:$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。

  • 结论:

  1. 在接收数据的组件中,使用 EventBus.$on 来注册一个自定义事件,用于接收数据。

  2. 在发送数据的组件中,使用 EventBus.$emit 来触发一个自定义事件,将数据传递出去。

  • 步骤:

    1. 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象

    2. 数据接收方:调用 bus.$on('事件名称', 事件处理函数) 方法注册一个自定义事件

      注意:接收方把接收注册的自定义事件要写在 created钩子函数里

    3. 数据发送方:调用 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 元素

  • 步骤:

    1. 使用 ref 属性,为对应的元素标签上添加引用名称

    2. 通过 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 - 获取 组件实例
  • 步骤:

    1. 使用 ref 属性,为对应的组件元素标签上添加引用名称

    2. 通过 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 名称。这种带有具体名称的插槽叫做“具名插槽”。

  • 步骤:

    1. 在封装组件时,给插槽起一个名字。

    2. 在之用组件,向插槽传递内容的时候,指定要给哪个插槽传递。

      两种方法:

      旧语法(只能在 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 默认插槽
  • 注意:

    1. 没有指定 name 名称的插槽,叫做默认插槽

    2. 它有隐含的名称叫做 “default”。

    3. 将来使用组件的时 候,所有没有指定要传给具名插槽的内容,都会被默认插槽所接收

  • 示例:

    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 作用域插槽

使用组件时,在组件的开始和结束标签的内容中,使用的数据如果是来自组件内部,那么这个插槽就是作用 域插槽。

  • 步骤:

    1. 封装组件时,在 slot 上绑定自定义属性,给使用组件时传递数据。

    2. 在使用组件时,接收组件内部传递出来的数据。

  • 示例:

    步骤一: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-bindv-onv-ifv-forv-model 等常用的指令。除此之外 vue 还允许我们开发自定 义指令。

17.2 自定义指令的分类
  • 自定义指令分为两类:

    1. 全局自定义指令

    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 为自定义指令动态绑定数值
  • 步骤:

    1. 使用自定义指令时,可以指令后面添加等号并赋值,为当前指令动态绑定参数值;

    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 函数的简写形式
  • 如果 bindupdate 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式

  • 示例:

    简写前:

    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
  1. 什么是 URL

    URL(Uniform Resource Locator,统一资源定位符)用于定位网络上的资源。

  2. URL 的组成部分

  3. Hash(哈希):

    • # 号后面的部分,专业术语为“Hash 地址”

    • Hash 地址发生变化,不会导致浏览器页面的刷新

    • Hash 地址发生变化,会形成浏览器历史记录

    • vue 就是基于 hash 的这些特性来做路由的跳转,实现页面的切换的

1.2 应用类型与路由的概念
  • 多页应用与后端路由

    MPA(Multi Page Application):在多页应用中,想要切换页面就是跳转到一个新的 html 页面。 路由工作是由后端完成的

  • 单页应用与前端路由

    SPA(Single Page Application):在单页应用中,只有唯一的一个 HTML 页面,所以想要切换页面就只能切换组件来达到切换页面的效果

    这种切换组件达到页面切换的效果,就是前端路由。由前端程序员来完成

  • 前端路由:访问不同的 Hash 地址,显示不同的组件

1.3 前端路由的工作方式
  1. 用户点击了页面上的路由链接

  2. 导致了 URL 地址栏中的 Hash 值发生了变化

  3. 前端路由监听了到 Hash 地址的变化

  4. 前端路由把当前 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 的基本使用

  • 步骤:

    1. 安装 vue-router 包

    2. 创建路由模块

    3. 挂载路由模块

    4. 配置路由规则

    5. 声明路由链接占位符

  • 示例:

    步骤一:安装 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 分别是

    1. this.$router.push('hash 地址')

      • 跳转到指定 hash 地址,并增加一条历史记

    2. 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 的好处:

    1. 数据的存取一步到位,不需层层传递

    2. 数据的流动非常清晰

    3. 存储在 Vuex 中的数据都是响应式

1.2 项目中安装和配置 Vuex

  1. 安装 Vuex 的依赖包

    npm i vuex@3.6.2 -S
  2. 创建 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
  3. 挂载 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;
        }
      }
    });
  • 注意:

    1. mutation 函数中,接受的第一个参数是 state

    2. 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)
    }

    注意:

    1. 调用全局的 mutation 里面的方法来修改全局的数据

    2. 语法:this.$store.commit('mutation函数的名字')

    3. 通过传参可以提高 mutation 方法的通用性。

    4. 语法: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

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.2webpack-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',
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值