二三阶段知识点

二阶段知识点

JavaScript基础语法

学习目标

  1. js三种引用方式
    1. 行内
    2. 内联
    3. 外链
  1. js注释
    1. 单行注释
    2. 多行注释
  1. js输入输出语句
    1. 输入
    2. 输出
  1. 变量
    1. 声明
    2. 赋值
  1. 数据类型
  2. 判断数据类型
  3. 数据类型转换
  4. 运算符
    1. 数学
    2. 赋值
    3. 比较
    4. 逻辑
    5. 自增自减

JavaScript代码的书写位置

  • css 一样,我们的 js 也可以有多种方式书写在页面上让其生效
  • js 也有多种方式书写,分为 行内式内嵌式外链式

注意事项

一行的结束就被认定为语句的结束,通常在结尾加上一个分号";"来表示语句的结束

  1. “;”分号要在英文状态下输入,同样,JS中的代码和符号都要在英文状态下输入。
  2. 虽然分号“;”也可以不写,但我们要养成编程的好习惯,记得在语句末尾写上分号。
  3. JavaScript对换行、缩进、空格不敏感。也就是说:
  4. 虽然分号不是必须加的,但是为了程序今后要压缩,如果不加分号,压缩之后将不能运行了。

行内式 JS 代码(不推荐)

  • 写在标签上的 js 代码需要依靠事件(行为)来触发

内嵌式 JS 代码

  • 内嵌式的 js 代码会在页面打开的时候直接触发

外链式 JS 代码(推荐)

  • 外链式 js 代码只要引入了 html 页面,就会在页面打开的时候直接触发
  • 新建一个 .js 后缀的文件,在文件内书写 js 代码,把写好的 js 文件引入 html 页面

JS 中的注释

  • 学习一个语言,先学习一个语言的注释,因为注释是给我们自己看的,也是给开发人员看的
  • 写好一个注释,有利于我们以后阅读代码

单行注释

  • 一般就是用来描述下面一行代码的作用
  • 可以直接写两个 / ,也可以按 ctrl + /

多行注释

  • 一般用来写一大段话,或者注释一段代码
  • 可以直接写 /**/ 然后在两个星号中间写注释
    • 各个编辑器的快捷键不一样,vscodectrl + shift + a
    • 快捷键修改为:   ctrl + shift  +  /
      vscode → 首选项按钮 → 键盘快捷方式 → 查找 原来的快捷键 → 修改为新的快捷键 → 回车确认

JavaScript输入输出语句

为了方便信息的输入输出,JS中提供了一些输入输出语句,其常用的语句如下:

方法

说明

归属

alert(msg)

浏览器弹出警示框

浏览器

console.log(msg)

浏览器控制台打印输出信息

浏览器

prompt(info)

浏览器弹出输入框,用户可以输入

浏览器

document.write(msg)

将内容添加到html标签内部

浏览器

  • 注意:alert() 主要用来显示消息给用户,console.log() 用来给程序员自己看运行时的消息。document.write() 方法将内容写到 HTML 文档中。

变量(重点)

什么是变量

  • 语法: var 变量名 = 值
  • 白话:变量就是一个装东西的盒子。
    通俗:变量是用于存放数据的容器。 我们通过 变量名 获取数据,甚至数据可以修改。

变量在内存中的存储

本质:变量是程序在内存中申请的一块用来存放数据的空间。类似我们酒店的房间,一个房间就可以看做是一个变量。
变量是计算机内存中存储数据的标识符,根据变量名称可以获取到内存中存储的数据
也就是说,我们向内存中存储了一个数据,然后要给这个数据起一个名字,为了是我们以后再次找到他

定义变量及赋值

  • 注意:
    1. 一个变量名只能存储一个值
    2. 当再次给一个变量赋值的时候,前面一次的值就没有了
    3. 变量名称区分大小写(JS 严格区分大小写)

变量的使用

  • 变量的声明
  • 变量的赋值

声明变量

  • var 是一个 JS关键字,用来声明变量( variable 变量的意思 )。使用该关键字声明变量后,计算机会自动为变量分配内存空间,不需要程序员管
  • age 是程序员定义的变量名,我们要通过变量名来访问内存中分配的空间

赋值

  • = 用来把右边的值赋给左边的变量空间中   此处代表赋值的意思
  • 变量值是程序员保存到变量空间里的值

变量的初始化

变量语法扩展

  • 更新变量
    一个变量被重新复赋值后,它原有的值就会被覆盖,变量值将以最后一次赋的值为准。

  • 同时声明多个变量
    同时声明多个变量时,只需要写一个 var, 多个变量名之间使用英文逗号隔开。

  • 声明变量特殊情况

情况

说明

结果

var  age ; console.log (age);

只声明 不赋值

undefined

console.log(age)

不声明 不赋值  直接使用

报错

age   = 10; console.log (age);

不声明   只赋值

10

扩展: 交换变量的值

方法一

    var v1 = 1;
    var v2 = 2;
    var temp = v1;
    v1 = v2
    v2 = temp

方法二

var num1 = 10;
var num2 = 20;

//把num1的变量中的值和num2变量中的值,取出来相加,重新赋值给num1这个变量
num1 = num1 + num2;//30

//num1变量的值和num2变量的值取出来,相减的结果重新赋值给num2
num2 = num1 - num2;//10

//num1变量的值和num2变量的值取出来,相减的结果重新赋值给num1
num1 = num1 - num2;//20

console.log(num1, num2);

方法三

var num1 = 10;
var num2 = 20;
num1 = num1 ^ num2;
num2 = num1 ^ num2;
num1 = num1 ^ num2;
console.log(num1, num2);
// 位运算 按位异或 了解

变量的命名规则和命名规范

  • 规则: 必须遵守的,不遵守就是错
  1. 一个变量名称可以由 数字字母(a-zA-Z)、英文下划线(_)美元符号($) 组成,如:userrAge, num01, _name
  2. 严格区分大小写  var qf; 和 var Qf;
  3. 不能由数字开头  18age   是错误的
  4. 不能是 保留字 或者 关键字  编辑器中高亮的部分
  5. 不要出现空格

  • 规范: 建议遵守的(开发者默认),不遵守不会报错
  1. 变量名尽量有意义(语义化) nl   →     age
  2. 遵循驼峰命名规则,由多个单词组成的时候
    大驼峰 UserName 小驼峰  userNameKangbazi
  3. 不要使用中文

关键字和保留字

标识符

标识(zhi)符:就是指开发人员为变量、属性、函数、参数取的名字。

标识符不能是关键字或保留字。

关键字

关键字:是指 JS本身已经使用了的字,不能再用它们充当变量名、方法名。

包括:break、case、catch、continue、default、delete、do、else、finally、for、function、if、in、instanceof、new、return、switch、this、throw、try、typeof、var、void、while、with 等。

保留字

保留字:实际上就是预留的“关键字”,意思是现在虽然还不是关键字,但是未来可能会成为关键字,同样不能使用它们当变量名或方法名。

包括:boolean、byte、char、class、const、debugger、double、enum、export、extends、fimal、float、goto、implements、import、int、interface、long、mative、package、private、protected、public、short、static、super、synchronized、throws、transient、volatile 等。

数据类型(重点)

数据类型简介

  • 为什么需要数据类型

在计算机中,不同的数据所需占用的存储空间是不同的,为了便于把数据分成所需内存大小不同的数据,充分利用存储空间,于是定义了不同的数据类型。
js是一个工具,为了解决生活中各种各样的需求,需要不同的数据类型 姓名 年龄 薪资等
简单来说,数据类型就是数据的类别型号。

  • 变量的数据类型

变量是用来存储值的所在处,它们有名字和数据类型。变量的数据类型决定了如何将代表这些值的位存储到计算机的内存中。JavaScript 是一种弱类型或者说动态语言。这意味着不用提前声明变量的类型,在程序运行过程中,类型会被自动确定:

在代码运行时,变量的数据类型是由 JS引擎 根据 = 右边变量值的数据类型来判断 的,运行完毕之后, 变量就确定了数据类型。JavaScript 拥有动态类型,同时也意味着相同的变量可用作不同的类型:

  • 数据类型的分类

JS 把数据类型分为两类:

    • 基本数据类型 (Number,String,Boolean,Undefined,Null) 也可以叫简单类型
    • 复杂数据类型 (object)
  • 是指我们存储在内存中的数据的类型
  • 我们通常分为两大类 基本数据类型复杂数据类型

基本数据类型

  1. 数值类型(number)
    • 一切数字都是数值类型(包括二进制,十进制,十六进制等)
    • NaN(not a number),一个非数字
  1. 字符串类型(string)
    • 被引号包裹的所有内容(可以是单引号也可以是双引号)
  1. 布尔类型(boolean)
    • 只有两个(true 或者 false
  1. null类型(null)
    • 只有一个,就是 null,表示空的意思
  1. undefined类型(undefined)
    • 只有一个,就是 undefined,表示没有值的意思

  • 字面量
    字面量是在源代码中一个固定值的表示法,通俗来说,就是字面量表示如何表达这个值。
    • 数字字面量:8, 9, 10
    • 字符串字面量:'千锋济南', "大前端"
    • 布尔字面量:true,false
  • 数字型 Number
    JavaScript 数字类型既可以保存整数,也可以保存小数(浮点数)。

    1. 数字型进制
      最常见的进制有二进制、八进制、十进制、十六进制。


现阶段我们只需要记住,在JS中八进制前面加0,十六进制前面加 0x

    1. 数字型范围
      JavaScript中数值的最大和最小值
      • 最大值:Number.MAX_VALUE,这个值为: 1.7976931348623157e+308
      • 最小值:Number.MIN_VALUE,这个值为:5e-32

  1. 数字型三个特殊值
    • Infinity ,代表无穷大,大于任何数值
    • -Infinity ,代表无穷小,小于任何数值
    • NaN ,Not a number,代表一个非数值
  1. isNaN
    用来判断一个变量是否为非数字的类型,返回 true 或者 false

  • 字符串型 String
    字符串型可以是引号中的任意文本,其语法为 双引号 "" 和 单引号''


因为 HTML 标签里面的属性使用的是双引号,JS 这里我们更推荐使用单引号。

    1. 字符串引号嵌套
      JS 可以用单引号嵌套双引号 ,或者用双引号嵌套单引号 (外双内单,外单内双)

    1. 字符串转义符
      类似HTML里面的特殊字符,字符串中也有特殊字符,我们称之为转义符。
      转义符都是 \ 开头的,常用的转义符及其说明如下:

转义符

解释说明

\n

换行符,n   是   newline   的意思

\ \

斜杠   \

'

'   单引号

"

”双引号

\t

tab  缩进

\b

空格 ,b   是   blank  的意思

    1. 字符串长度

字符串是由若干字符组成的,这些字符的数量就是字符串的长度。通过字符串的 length 属性可以获取整个字符串的长度。

    1. 字符串拼接
      • 多个字符串之间可以使用 + 进行拼接,其拼接方式为 字符串 + 任何类型 = 拼接之后的新字符串
      • 拼接前会把与字符串相加的任何类型转成字符串,再拼接成一个新的字符串

        • + 号总结口诀:数值相加 ,字符相连
    1. 字符串拼接加强

      • 经常会将字符串和变量来拼接,变量可以很方便地修改里面的值
      • 变量是不能添加引号的,因为加引号的变量会变成字符串
      • 如果变量两侧都有字符串拼接,口诀“引引加加 ”,删掉数字,变量写加中间
  • 布尔型Boolean
    布尔类型有两个值:true 和 false ,其中 true 表示真(对),而 false 表示假(错)。
    布尔型和数字型相加的时候, true 的值为 1 ,false 的值为 0。

  • Undefined和 Null
    一个声明后没有被赋值的变量会有一个默认值undefined ( 如果进行相连或者相加时,注意结果)


一个声明变量给 null 值,里面存的值为空(学习对象时,我们继续研究null)

复杂数据类型(暂时先不讲)

  1. 对象类型(object)
  2. 函数类型(function)

判断数据类型

  • 既然已经把数据分开了类型,那么我们就要知道我们存储的数据是一个什么类型的数据
  • 使用 typeof 关键字来进行判断
  • 获取检测变量的数据类型
    typeof 可用来获取检测变量的数据类型,不同类型的返回值

数据类型转换

  • 数据类型之间的转换,比如数字转成字符串,字符串转成布尔,布尔转成数字等

其他数据类型转成数值

  1. Number(变量)

可以把一个变量强制转换成数值类型

可以转换小数,会保留小数

可以转换布尔值

遇到不可转换的都会返回 NaN

  1. parseInt(变量)

从第一位开始检查,是数字就转换,知道一个不是数字的内容

开头就不是数字,那么直接返回 NaN

不认识小数点,只能保留整数

  1. parseFloat(变量)

从第一位开始检查,是数字就转换,知道一个不是数字的内容

开头就不是数字,那么直接返回 NaN

认识一次小数点

  1. 除了加法以外的数学运算

运算符两边都是可运算数字才行

如果运算符任何一遍不是一个可运算数字,那么就会返回 NaN

加法不可以用

其他数据类型转成字符串

  1. 变量.toString()

有一些数据类型不能使用 toString() 方法,比如 undefinednull

  1. String(变量)

所有数据类型都可以

  1. 使用加法运算

在 JS 里面,+ 由两个含义

字符串拼接: 只要 + 任意一边是字符串,就会进行字符串拼接

加法运算:只有 + 两边都是数字的时候,才会进行数学运算

其他数据类型转成布尔

  1. Boolean(变量)

在 js 中,只有 ''0nullundefinedNaN,这些是 false

其余都是 true

运算符

  • 就是在代码里面进行运算的时候使用的符号,不光只是数学运算,我们在 js 里面还有很多的运算方式

数学运算符

  1. +

只有符号两边都是数字的时候才会进行加法运算

只要符号任意一边是字符串类型,就会进行字符串拼接

  1. -

会执行减法运算

会自动把两边都转换成数字进行运算

  1. *

会执行乘法运算

会自动把两边都转换成数字进行运算

  1. /

会执行除法运算

会自动把两边都转换成数字进行运算

  1. %

会执行取余运算

会自动把两边都转换成数字进行运算

浮点数的精度问题

浮点数值的最高精度是 17 位小数,但在进行算术计算时其精确度远远不如整数。

所以:不要直接判断两个浮点数是否相等 !

表达式和返回值

表达式:是由数字、运算符、变量等以能求得数值的有意义排列方法所得的组合

简单理解:是由数字、运算符、变量等组成的式子

表达式最终都会有一个结果,返回给开发者,称为返回值

赋值运算符

  1. =

就是把 = 右边的赋值给等号左边的变量名

var num = 100

就是把 100 赋值给 num 变量

那么 num 变量的值就是 100

  1. +=

a += 10 等价于 a = a + 10

  1. -=

var a = 10;

a -= 10 等价于 a = a - 10

  1. *=

a *= 10 等价于 a = a * 10

  1. /+

a /= 10 等价于 a = a / 10

  1. %=

a %= 10 等价于 a = a % 10

比较运算符

  1. ==
    • 比较符号两边的值是否相等,不管数据类型

1 == '1'

两个的值是一样的,所以得到 true

  1. ===
    • 比较符号两边的值和数据类型是否都相等

1 === '1'

两个值虽然一样,但是因为数据类型不一样,所以得到 false

  1. !=
    • 比较符号两边的值是否不等

1 != '1'

因为两边的值是相等的,所以比较他们不等的时候得到 false

  1. !==
    • 比较符号两边的数据类型和值是否不等

1 !== '1'

因为两边的数据类型确实不一样,所以得到 true

  1. >=
    • 比较左边的值是否 大于或等于 右边的值

1 >= 1  结果是 true

1 >= 0  结果是 true

1 >= 2  结果是 false

  1. <=
    • 比较左边的值是否 小于或等于 右边的值

1 <= 2  结果是 true

1 <= 1  结果是 true

1 <= 0  结果是 false

  1. >
    • 比较左边的值是否 大于 右边的值

1 > 0  结果是 true

1 > 1  结果是 false

1 > 2  结果是 false

  1. <
    • 比较左边的值是否 小于 右边的值

1 < 2  结果是 true

1 < 1  结果是 false

1 < 0  结果是 false

逻辑运算符

  1. &&
    • 进行 且 的运算

符号左边必须为 true 并且右边也是 true,才会返回 true

只要有一边不是 true,那么就会返回 false

true && true  结果是 true

true && false  结果是 false

false && true  结果是 false

false && false  结果是 false

  1. ||
    • 进行 或 的运算

符号的左边为 true 或者右边为 true,都会返回 true

只有两边都是 false 的时候才会返回 false

true || true  结果是 true

true || false  结果是 true

false || true  结果是 true

false || false  结果是 false

  1. !
    • 进行 取反 运算

本身是 true 的,会变成 false

本身是 false 的,会变成 true

!true  结果是 false

!false  结果是 true

短路运算(逻辑中断)

短路运算的原理:当有多个表达式(值)时,左边的表达式值可以确定结果时,就不再继续运算右边的表达式的值;

  • 逻辑与
    语法: 表达式1 && 表达式2

- 如果第一个表达式的值为真,则返回表达式2

- 如果第一个表达式的值为假,则返回表达式1

  • 逻辑或
    语法: 表达式1 || 表达式2

- 如果第一个表达式的值为真,则返回表达式1

- 如果第一个表达式的值为假,则返回表达式2

自增自减运算符(一元运算符)递增和递减运算符概述

如果需要反复给数字变量添加或减去1,可以使用递增(++)和递减(--)运算符来完成。

在 JavaScript 中,递增(++)和递减(--)既可以放在变量前面,也可以放在变量后面。放在变量前面时,我们可以称为前置递增(递减)运算符,放在变量后面时,我们可以称为后置递增(递减)运算符。

注意:递增和递减运算符必须和变量配合使用。

  • 递增运算符
    • 前置递增运算符
      ++num 前置递增,就是自加1,类似于 num =  num + 1,但是 ++num 写起来更简单。
      使用口诀:先自加,后返回值

    • 后置递增运算符
      num++ 后置递增,就是自加1,类似于 num =  num + 1 ,但是 num++ 写起来更简单。
      使用口诀:先返回原值,后自加

  • 递减运算符
    • 前置递减运算符
      --num 前置递减,就是自减1,类似于 num =  num - 1,但是 --num 写起来更简单。
      使用口诀:先自减,后返回值

    • 后置递减运算符
      num-- 后置递减,就是自减1,类似于 num =  num - 1 ,但是 num-- 写起来更简单。
      使用口诀:先返回原值,后自减

运算符优先级

  • 一元运算符里面的逻辑非优先级很高
  • 逻辑与比逻辑或优先级高

分支结构


在一个程序执行的过程中,各条代码的执行顺序对程序的结果是有直接影响的。很多时候我们要通过控制代码的执行顺序来实现我们要完成的功能。

简单理解:流程控制就是来控制代码按照一定结构顺序来执行,就是根据我们设定好的条件来决定要不要执行某些代码

流程控制主要有三种结构,分别是顺序结构分支结构循环结构,代表三种代码执行的顺序。
 

二三阶段知识点_第1张图片


顺序流程控制


顺序结构是程序中最简单、最基本的流程控制,它没有特定的语法结构,程序会按照代码的先后顺序,依次执行,程序中大多数的代码都是这样执行的。
IF 条件分支结构(重点)

由上到下执行代码的过程中,根据不同的条件,执行不同的路径代码(执行代码多选一的过程),从而得到不同的结果

JS 语言提供了两种分支结构语句:if 语句、switch 语句




if 语句



● 通过一个 if 语句来决定代码是否执行
● 语法: if (条件) { 要执行的代码 }
● 通过 () 里面的条件是否成立来决定 {} 里面的代码是否执行


if else 语句




● 通过 if 条件来决定,执行哪一个 {} 里面的代码
● 语法: if (条件) { 条件为 true 的时候执行 } else { 条件为 false 的时候执行 }
● 两个 {} 内的代码一定有一个会执行


if else if ... 语句




● 可以通过 if 和 else if 来设置多个条件进行判断
● 语法:if (条件1) { 条件1为 true 的时候执行 } else if (条件2) { 条件2为 true 的时候执行 }
● 会从头开始依次判断条件
○如果第一个条件为 true 了,那么就会执行后面的 {} 里面的内容
○如果第一个条件为 false,那么就会判断第二个条件,依次类推
● 多个 {} ,只会有一个被执行,一旦有一个条件为 true 了,后面的就不在判断了


if else if … else 语句

● 和之前的 if else if ... 基本一致,只不过是在所有条件都不满足的时候,执行最后 else 后面的 {}


SWITCH 条件分支结构(重点)

switch 语句也是多分支语句(条件判断语句),它用于基于不同的条件来执行不同的代码。当要针对变量设置一系列的特定值的选项时,就可以使用 switch

● 是对于某一个变量的判断
● 语法:

●要判断某一个变量 等于 某一个值得时候使用
● 例子: 根据变量给出的数字显示是星期几

● switch :开关 转换 , case :小例子 选项
● 关键字 switch 后面括号内可以是表达式或值, 通常是一个变量
● 关键字 case , 后跟一个选项的表达式或值,后面跟一个冒号
● switch 表达式的值会与结构中的 case 的值做比较
● 如果存在匹配全等(===) ,则与该 case 关联的代码块会被执行,并在遇到 break 时停止,整个 switch 语句代码执行结束
● 如果所有的 case 的值都和表达式的值不匹配,则执行 default 里的代码 注意: 执行case 里面的语句时,如果没有break,则继续执行下一个case里面的语句。
● switch 语句和 if else if 语句的区别
○一般情况下,它们两个语句可以相互替换
○switch...case 语句通常处理 case为比较确定值的情况, 而 if…else…语句更加灵活,常用于范围判断(大于、等于某个范围)
○switch 语句进行条件判断后直接执行到程序的条件语句,效率更高。而if…else 语句有几种条件,就得判断多少次。
○当分支比较少时,if… else语句的执行效率比 switch语句高。
○当分支比较多时,switch语句的执行效率比较高,而且结构更清晰。

三元运算(扩展)

● 三元运算,就是用 两个符号 组成一个语句
● 三元运算只是对 if else 语句的一个简写形式
● 语法: 条件 ? 条件为 true 的时候执行 : 条件为 false 的时候执行

循环结构



●循环结构,就是根据某些给出的条件,重复的执行同一段代码
●循环必须要有某些固定的内容组成
a初始化
b条件判断
c要执行的代码
d自身改变

WHILE 循环

●while,其实就是当条件满足时就执行代码,一旦不满足了就不执行了
● 语法 while (条件) { 满足条件就执行 }
● 因为满足条件就执行,所以我们写的时候一定要注意,就是设定一个边界值,不然就一直循环下去了
执行思路:
○1 先初始化条件,然后执行条件表达式,如果结果为 true,则执行循环体代码;如果为 false,则退出循环,执行后面代码
○2 执行循环体代码
○3 循环体代码执行完毕后,程序会继续判断执行条件表达式,如条件仍为true,则会继续执行循环体,直到循环条件为 false 时,整个循环过程才会结束
注意:
○使用 while 循环时一定要注意,它必须要有退出条件,否则会成为死循环

断点调试

断点调试是指自己在程序的某一行设置一个断点,调试时,程序运行到这一行就会停住,然后你可以一步一步往下调试,调试过程中可以看各个变量当前的值,出错的话,调试到出错的代码行即显示错误,停下。断点调试可以帮助观察程序的运行过程

断点调试的流程:

1浏览器中按 F12--> sources -->找到需要调试的文件-->在程序的某一行设置断点
2Watch: 监视,通过watch可以监视变量的值的变化,非常的常用。前提设置监视那个变量的值。
3点击F11,单步执行,让程序一行一行的执行,这个时候,观察watch中变量的值的变化。

DO WHILE 循环

● 是一个和 while 循环类似的循环
●while 会先进行条件判断,满足就执行,不满足直接就不执行了
● 但是 do while 循环是,先不管条件,先执行一回,然后在开始进行条件判断
● 语法: do { 要执行的代码 } while (条件)
执行思路
● 先执行一次循环体代码
● 再执行条件表达式,如果结果为 true,则继续执行循环体代码,如果为 false,则退出循环,继续执行后面代码 注意:先再执行循环体,再判断,do…while循环语句至少会执行一次循环体代码

FOR 循环

● 和 while 和 do while 循环都不太一样的一种循环结构
● 道理是和其他两种一样的,都是循环执行代码的 语法: for (var i = 0; i < 10; i++) { 要执行的代码 }

执行过程:
ⅰ初始化变量,初始化操作在整个 for 循环只会执行一次。执行条件表达式,如果为true,则执行循环体语句,否则退出循环,循环结束。
ⅱ执行操作表达式,此时第一轮结束。
ⅲ第二轮开始,直接去执行条件表达式(不再初始化变量),如果为 true ,则去执行循环体语句,否则退出循环。
ⅳ继续执行操作表达式,第二轮结束。
ⅴ后续跟第二轮一致,直至条件表达式为假,结束整个 for 循环。
● 这个只是看起来不太舒服,但是用起来比较好用

BREAK 终止循环

● 在循环没有进行完毕的时候,因为我设置的条件满足,提前终止循环
● 比如:我要吃五个包子,吃到三个的时候,不能在吃了,我就停止吃包子这个事情
● 要终止循环,就可以直接使用 break 关键字


CONTINUE 结束本次循环

● 在循环中,把循环的本次跳过去,继续执行后续的循环
● 比如:吃五个包子,到第三个的时候,第三个掉地下了,不吃了,跳过第三个,继续吃第四个和第五个
● 跳过本次循环,就可以使用 continue 关键字

双重for循环
双重 for 循环概述
循环嵌套是指在一个循环语句中再定义一个循环语句的语法结构,例如在for循环语句中,可以再嵌套一个for 循环,这样的 for 循环语句我们称之为双重for循环。
双重 for 循环语法
○内层循环可以看做外层循环的循环体语句
○ 内层循环执行的顺序也要遵循 for 循环的执行顺序
○ 外层循环执行一次,内层循环要执行全部次数
○ 打印五行五列星星

名称

作用

初始化变量

通常被用于初始化一个计数器,该表达式可以使用 var 关键字声明新的变量,这个变量帮我们来记录次数。

条件表达式

用于确定每一次循环是否能被执行。如果结果是 true 就继续循环,否则退出循环。

操作表达式

用于确定每一次循环是否能被执行。如果结果是 true 就继续循环,否则退出循环。

函数(1)

函数(上)

●我们代码里面所说的函数和我们上学的时候学习的什么三角函数、二次函数之类的不是一个东西

函数的概念

● 对于 js 来说,函数就是把任意一段代码放在一个 盒子 里面
● 在我想要让这段代码执行的时候,直接执行这个 盒子 里面的代码就行

二三阶段知识点_第2张图片


● 先看一段代码


函数的两个阶段(重点)

●按照我们刚才的说法,两个阶段就是 放在盒子里面让盒子里面的代码执行

函数定义阶段

● 定义阶段就是我们把代码 放在盒子里面
● 我们就要学习怎么 放进去,也就是书写一个函数
● 我们有两种定义方式 声明式赋值式

声明式

● 使用 function 这个关键字来声明一个函数
● 语法:

●function 是声明函数的关键字,必须小写
●由于函数一般是为了实现某个功能才定义的, 所以通常我们将函数名命名为动词,比如 getSum
赋值式

● 其实就是和我们使用 var 关键字是一个道理了
● 首先使用 var 定义一个变量,把一个函数当作值直接赋值给这个变量就可以了
● 语法:


函数调用阶段

●就是让 盒子里面 的代码执行一下
●让函数执行
●两种定义函数的方式不同,但是调用函数的方式都以一样的

调用一个函数

● 函数调用就是直接写 函数名() 就可以了

○注意: 定义完一个函数以后,如果没有函数调用,那么写在 {} 里面的代码没有意义,只有调用以后才会执行

●调用的时候千万不要忘记添加小括号
●口诀:函数不调用,自己不执行 注意:声明函数本身并不会执行代码,只有调用函数时才会执行函数体代码。
调用上的区别

● 虽然两种定义方式的调用都是一样的,但是还是有一些区别的
● 声明式函数: 调用可以在 定义之前或者定义之后

● 赋值式函数: 调用只能在 定义之后


函数的参数(重点)

● 我们在定义函数和调用函数的时候都出现过 ()
● 现在我们就来说一下这个 () 的作用
● 就是用来放参数的位置
● 参数分为两种 行参实参




行参和实参的作用

1 行参
○ 就是在函数内部可以使用的变量,在函数外部不能使用
○ 每写一个单词,就相当于在函数内部定义了一个可以使用的变量(遵循变量名的命名规则和命名规范)
○ 多个单词之间以 , 分隔

○ 如果只有行参的话,那么在函数内部使用的值个变量是没有值的,也就是 undefined
行参的值是在函数调用的时候由实参决定的
2 实参
○ 在函数调用的时候给行参赋值的
○ 也就是说,在调用的时候是给一个实际的内容的

函数内部的行参的值,由函数调用的时候传递的实参决定
多个参数的时候,是按照顺序一一对应的

1调用的时候实参值是传递给形参的
2形参简单理解为:不用声明的变量
3实参和形参的多个参数之间用逗号(,)分隔
参数个数的关系


注意:在JavaScript中,形参的默认值是undefined。
1 行参比实参少
○ 因为是按照顺序一一对应的
○ 行参少就会拿不到实参给的值,所以在函数内部就没有办法用到这个值

2 行参比实参多
○ 因为是按照顺序一一对应的
○ 所以多出来的行参就是没有值的,就是 undefined

小结:

●函数可以带参数也可以不带参数
●声明函数的时候,函数名括号里面的是形参,形参的默认值为 undefined
●调用函数的时候,函数名括号里面的是实参
●多个参数中间用逗号分隔
●形参的个数可以和实参个数不匹配,但是结果不可预计,我们尽量要匹配
函数的return(重点)

●return 返回的意思,其实就是给函数一个 返回值终断函数
●如果函数没有 return ,返回的值是 undefined
终断函数

● 当我开始执行函数以后,函数内部的代码就会从上到下的依次执行
● 必须要等到函数内的代码执行完毕
● 而 return 关键字就是可以在函数中间的位置停掉并返回指定的值,让后面的代码不在继续执行


返回值

● 函数调用本身也是一个表达式,表达式就应该有一个值出现
● 现在的函数执行完毕之后,是不会有结果出现的

●return 关键字就是可以给函数执行完毕一个结果

○我们可以在函数内部使用 return 关键把任何内容当作这个函数运行后的结果
break ,continue ,return 的区别

●break :结束当前的循环体(如 for、while)
●continue :跳出本次循环,继续执行下次循环(如 for、while)
●return :不仅可以退出循环,还能够返回 return 语句中的值,同时还可以结束当前的函数体内的代码
函数的优点

●函数就是对一段代码的封装,在我们想调用的时候调用
●函数的几个优点
a封装代码,使代码更加简洁
b复用,在重复功能的时候直接调用就好
c代码执行时机,随时可以在我们想要执行的时候执行

预解析(重点)

预解析 其实就是聊聊 js 代码的编译和执行
●js 是一个解释型语言,就是在代码执行之前,先对代码进行通读和解释,然后在执行代码
●也就是说,我们的 js 代码在运行的时候,会经历两个环节 解释代码执行代码

解释代码

● 因为是在所有代码执行之前进行解释,所以叫做 预解析(预解释)
● 需要解释的内容有两个
○声明式函数
■在内存中先声明有一个变量名是函数名,并且这个名字代表的内容是一个函数
○var 关键字
■在内存中先声明有一个变量名
● 看下面一段代码

● 经过预解析之后可以变形为

● 赋值式函数会按照 var 关键字的规则进行预解析

函数(2)

函数(下)

作用域(重点)

●什么是作用域,就是一个变量可以生效的范围
●变量不是在所有地方都可以使用的,而这个变量的使用范围就是作用域

全局作用域

● 全局作用域是最大的作用域
● 在全局作用域中定义的变量可以在任何地方使用
● 页面打开的时候,浏览器会自动给我们生成一个全局作用域 window
● 这个作用域会一直存在,直到页面关闭就销毁了


局部作用域

● 局部作用域就是在全局作用域下面有开辟出来的一个相对小一些的作用域
● 在局部作用域中定义的变量只能在这个局部作用域内部使用
在 JS 中只有函数能生成一个局部作用域,别的都不行
● 每一个函数,都是一个局部作用域


变量使用规则(重点)

●有了作用域以后,变量就有了使用范围,也就有了使用规则
●变量使用规则分为两种,访问规则赋值规则

访问规则

● 当我想获取一个变量的值的时候,我们管这个行为叫做 访问
● 获取变量的规则:
○首先,在自己的作用域内部查找,如果有,就直接拿来使用
○如果没有,就去上一级作用域查找,如果有,就拿来使用
○如果没有,就继续去上一级作用域查找,依次类推
○如果一直到全局作用域都没有这个变量,那么就会直接报错(该变量 is not defined)
● 变量的访问规则 也叫做 作用域的查找机制
● 作用域的查找机制只能是向上找,不能向下找


赋值规则

● 当你想给一个变量赋值的时候,那么就先要找到这个变量,在给他赋值
● 变量赋值规则:
○先在自己作用域内部查找,有就直接赋值
○没有就去上一级作用域内部查找,有就直接赋值
○还没有再去上一级作用域查找,有就直接赋值
○如果一直找到全局作用域都没有,那么就把这个变量定义为全局变量,再给他赋值

递归函数

● 什么是递归函数
● 在编程世界里面,递归就是一个自己调用自己的手段
● 递归函数: 一个函数内部,调用了自己,循环往复

● 其实递归函数和循环很类似
● 需要有初始化,自增,执行代码,条件判断的,不然就是一个没有尽头的递归函数,我们叫做 死递归

简单实现一个递归

● 我们先在用递归函数简单实现一个效果
● 需求: 求 1 至 5 的和
○先算 1 + 2 得 3
○再算 3 + 3 得 6
○再算 6 + 4 得 10
○再算 10 + 5 得 15
○结束
● 开始书写,写递归函数先要写结束条件(为了避免出现 “死递归”)

● 再写不满足条件的时候我们的递归处理


简单了解对象

● 对象是一个复杂数据类型
● 其实说是复杂,但是没有很复杂,只不过是存储了一些基本数据类型的一个集合
● 这里的 {} 和函数中的 {} 不一样
● 函数里面的是写代码的,而对象里面是写一些数据的
对象就是一个键值对的集合
●{} 里面的每一个键都是一个成员
● 也就是说,我们可以把一些数据放在一个对象里面,那么他们就互不干扰了
● 其实就是我们准备一个房子,把我们想要的数据放进去,然后把房子的地址给到变量名,当我们需要某一个数据的时候,就可以根据变量名里面存储的地址找到对应的房子,然后去房子里面找到对应的数据

创建一个对象

● 字面量的方式创建一个对象

● 内置构造函数的方式创建对象

○Object 是 js 内置给我们的构造函数,用于创建一个对象使用的

对象

什么是对象,其实就是一种类型,即引用类型。而对象的值就是引用类型的实例。在ECMAScript中引用类型是一种数据结构,用于将数据和功能组织在一起。它也常被称做为类,但ECMAScript中却没有这种东西。虽然ECMAScript是一门面向对象的语言,却不具备传统面向对象语言所支持的类和接口等基本结构。

一.Object类型
到目前为止,我们使用的引用类型最多的可能就是Object类型了。虽然Object的实例不具备多少功能,但对于在应用程序中的存储和传输数据而言,它确实是非常理想的选择。
创建Object类型有两种。一种是使用new运算符,一种是字面量表示法。

1.使用new运算符创建Object


2.new关键字可以省略

3.使用字面量方式创建Object

4.属性字段也可以使用字符串星矢

5.使用字面量及传统复制方式

6.两种属性输出方式

PS:在使用字面量声明Object对象时,不会调用Object()构造函数(Firefox除外)。

7.给对象创建方法

8.使用delete删除对象属性

在实际开发过程中,一般我们更加喜欢字面量的声明方式。因为它清晰,语法代码少,而且还给人一种封装的感觉。字面量也是向函数传递大量可选参数的首选方式。


for...in语句
for...in语句是一种精准的迭代语句,可以用来枚举对象的属性。

with语句
with语句的作用是将代码的作用域设置到一个特定的对象中。

数组

数组

● 什么是数组?
● 字面理解就是 数字的组合
● 其实不太准确,准确的来说数组是一个 数据的集合
● 也就是我们把一些数据放在一个盒子里面,按照顺序排好

● 这个东西就是一个数组,存储着一些数据的集合

数据类型分类

●number / string / boolean / undefined / null / object / function / array / ...
● 数组也是数据类型中的一种
● 我们简单的把所有数据类型分为两个大类 基本数据类型复杂数据类型
● 基本数据类型: number / string / boolean / undefined / null
● 复杂数据类型: object / function / array / ...

创建一个数组

●数组就是一个 []
●在 [] 里面存储着各种各样的数据,按照顺序依次排好

字面量创建一个数组

● 直接使用 [] 的方式创建一个数组


内置构造函数创建数组

● 使用 js 的内置构造函数 Array 创建一个数组


数组的 length

●length: 长度的意思
●length 就是表示数组的长度,数组里面有多少个成员,length 就是多少


数组的索引

● 索引,也叫做下标,是指一个数据在数组里面排在第几个的位置
● 注意: 在所有的语言里面,索引都是从 0 开始的
● 在 js 里面也一样,数组的索引从 0 开始

● 上面这个数组中,第 0 个 数据就是字符串 hello,第 1 个 数据就是字符串 world
● 想获取数组中的第几个就使用 数组[索引] 来获取


数据类型之间存储的区别(重点)

●既然我们区分了基本数据类型和复杂数据类型
●那么他们之间就一定会存在一些区别
●他们最大的区别就是在存储上的区别
●我们的存储空间分成两种
●栈: 主要存储基本数据类型的内容
●堆: 主要存储复杂数据类型的内容


复杂数据类型在内存中的存储情况


● 复杂数据类型的存储
a在堆里面开辟一个存储空间
b把数据存储到存储空间内
c把存储空间的地址赋值给栈里面的变量
● 这就是数据类型之间存储的区别

数据类型之间的比较

● 基本数据类型是 之间的比较

● 复杂数据类型是 地址 之间的比较

○因为我们创建了两个对象,那么就会在 堆空间 里面开辟两个存储空间存储数据(两个地址)
○虽然存储的内容是一样的,那么也是两个存储空间,两个地址
○复杂数据类型之间就是地址的比较,所以 obj 和 obj2 两个变量的地址不一样
○所以我们得到的就是 false

数组的常用方法

● 数组是一个复杂数据类型,我们在操作它的时候就不能再想基本数据类型一样操作了
● 比如我们想改变一个数组

○这样肯定是不合理,因为这样不是在改变之前的数组
○相当于心弄了一个数组给到 arr 这个变量了
○相当于把 arr 里面存储的地址给换了,也就是把存储空间换掉了,而不是在之前的空间里面修改
○所以我们就需要借助一些方法,在不改变存储空间的情况下,把存储空间里面的数据改变了

数组常用方法之 push

●push 是用来在数组的末尾追加一个元素


数组常用方法之 pop

●pop 是用来删除数组末尾的一个元素


数组常用方法之 unshift

●unshift 是在数组的最前面添加一个元素


数组常用方法之 shift

●shift 是删除数组最前面的一个元素


数组常用方法之 splice

●splice 是截取数组中的某些内容,按照数组的索引来截取
● 语法: splice(从哪一个索引位置开始,截取多少个,替换的新元素) (第三个参数可以不写)

○arr.splice(1, 2) 表示从索引 1 开始截取 2 个内容
○第三个参数没有写,就是没有新内容替换掉截取位置
○arr.splice(1, 2, '我是新内容') 表示从索引 1 开始截取 2 个内容
○然后用第三个参数把截取完空出来的位置填充

数组常用方法之 reverse

●reverse 是用来反转数组使用的


数组常用方法之 sort

●sort 是用来给数组排序的

○这个只是一个基本的简单用法

数组常用方法之 concat

●concat 是把多个数组进行拼接
● 和之前的方法有一些不一样的地方,就是 concat 不会改变原始数组,而是返回一个新的数组

○注意: concat 方法不会改变原始数组

数组常用方法之 join

●join 是把数组里面的每一项内容链接起来,变成一个字符串
● 可以自己定义每一项之间链接的内容 join(要以什么内容链接)
● 不会改变原始数组,而是把链接好的字符串返回

○注意: join 方法不会改变原始数组,而是返回链接好的字符串

for 和 for in 循环

● 因为数组的索引就可以获取数组中的内容
● 数组的索引又是按照 0 ~ n 顺序排列
● 我们就可以使用 for 循环来循环数组,因为 for 循环我们也可以设置成 0 ~ n 顺序增加
● 我们把这个行为叫做 遍历

○i < arr.length 因为 length 就是数组的长度,是一个数字,所以我们可以直接用它来决定循环次数
○console.log(arr[i]) 因为随着循环,i 的值会从 0 开始依次增加
○所以我们实际上就相当于在打印 arr[0] / arr[1] / ...
● 因为 对象 是没有索引的,所以我们没有办法使用 for 循环来遍历
● 这里我们使用 for in 循环来遍历对象
● 先来看一段代码

○for in 循环的遍历是按照对象中有多少成员来决定了
○有多少成员,就会执行多少次
○key 是我们自己定义的一个变量,就和 for 循环的时候我们定义的 i 一个道理
○在每次循环的过程中,key 就代表着对象中某一个成员的 属性名

数组的排序

●排序,就是把一个乱序的数组,通过我们的处理,让他变成一个有序的数组
●今天我们讲解两种方式来排序一个数组 冒泡排序选择排序

冒泡排序

● 先遍历数组,让挨着的两个进行比较,如果前一个比后一个大,那么就把两个换个位置
● 数组遍历一遍以后,那么最后一个数字就是最大的那个了
● 然后进行第二遍的遍历,还是按照之前的规则,第二大的数字就会跑到倒数第二的位置
● 以此类推,最后就会按照顺序把数组排好了
a 我们先来准备一个乱序的数组

■接下来我们就会用代码让数组排序
b 先不着急循环,先来看数组里面内容换个位置

c 第一次遍历数组,把最大的放到最后面去

■第一次结束以后,数组中的最后一个,就会是最大的那个数字
■然后我们把上面的这段代码执行多次。数组有多少项就执行多少次
d 按照数组的长度来遍历多少次

e 给一些优化
■ 想象一个问题,假设数组长度是 9,第八次排完以后
■ 后面八个数字已经按照顺序排列好了,剩下的那个最小的一定是在最前面
■ 那么第九次就已经没有意义了,因为最小的已经在最前面了,不会再有任何换位置出现了
■ 那么我们第九次遍历就不需要了,所以我们可以减少一次

■ 第二个问题,第一次的时候,已经把最大的数字放在最后面了
■ 那么第二次的时候,其实倒数第二个和最后一个就不用比了
■ 因为我们就是要把倒数第二大的放在倒数第二的位置,即使比较了,也不会换位置
■ 第三次就要倒数第三个数字就不用再和后两个比较了
■ 以此类推,那么其实每次遍历的时候,就遍历 当前次数 - 1 次

f 至此,一个冒泡排序就完成了

选择排序

● 先假定数组中的第 0 个就是最小的数字的索引
● 然后遍历数组,只要有一个数字比我小,那么就替换之前记录的索引
● 知道数组遍历结束后,就能找到最小的那个索引,然后让最小的索引换到第 0 个的位置
● 再来第二趟遍历,假定第 1 个是最小的数字的索引
● 在遍历一次数组,找到比我小的那个数字的索引
● 遍历结束后换个位置
● 依次类推,也可以把数组排序好
a 准备一个数组

b 假定数组中的第 0 个是最小数字的索引

c 遍历数组,判断,只要数字比我小,那么就替换掉原先记录的索引

d 按照数组的长度重复执行上面的代码

e 一些优化
■ 和之前一样,倒数第二次排序完毕以后,就已经排好了,最后一次没有必要了

■ 在交换变量之前,可以判断一下,如果我们遍历后得到的索引和当前的索引一直
■ 那么就证明当前这个就是目前最小的,那么就没有必要交换
■ 做一我们要判断,最小作引和当前作引不一样的时候,才交换

f 至此,选择排序完成

函数参数传递基本数据类型和复杂数据类型的区别

● 之前我们知道了,基本数据类型和复杂数据类型在存储上是有区别的
● 那么他们在赋值之间也是有区别的
● 基本数据类型之间的赋值

○相当于是把 num 的值复制了一份一摸一样的给了 num2 变量
○赋值以后两个在没有关系
● 复杂数据类型之间的赋值

○因为复杂数据类型,变量存储的是地址,真实内容在 堆空间 内存储
○所以赋值的时候相当于把 obj 存储的那个地址复制了一份给到了 obj2 变量
○现在 obj 和 obj2 两个变量存储的地址一样,指向一个内存空间
○所以使用 obj2 这个变量修改空间内的内容,obj 指向的空间也会跟着改变了

函数的参数

● 函数的参数也是赋值的之中,在函数调用的时候,实参给行参赋值
● 和之前变量赋值的规则是一样的
● 函数传递基本数据类型

○和之前变量赋值的时候一样,在把 num 的值复制了一份一摸一样的给到了函数内部的行参 n
○两个之间在没有任何关系了
● 函数传递复杂数据类型

○和之前变量赋值的时候一样,把 obj 内存储的地址复制了一份一摸一样的给到函数内部的行参 o
○函数外部的 obj 和函数内部的行参 o,存储的是一个地址,指向的是一个存储空间
○所以两个变量操作的是一个存储空间
○在函数内部改变了空间内的数据
○obj 看到的也是改变以后的内容

字符串的操作

ES5/String

严格模式(了解)

●我们都知道 js 是一个相对不很严谨的语言
●而且开发的时候,一些代码也不是很严格要求
●而严格模式就是对开发的时候写的一些内容做了要求

开启严格模式

● 想开启严格模式,直接在代码最开始的位置写上字符串 use strict


严格模式的规则

1 声明变量必须有 var 关键字

○之前了解过,在声明变量的时候,如果没有 var 关键字,那么按照作用域的规则会自动定义成全局变量
○严格模式下不可以,会报错
2 函数的行参不可以重复

○在非严格模式下,函数两个行参一样,是不会报错的,只不过就是相当于在函数内部只有一个变量了
○但是在严格模式下会报错

ES5 中常见的数组常用方法

●之前我们讲过的数组常用方法都是 ES3 的方法
●今天来说一些 ES5 中的方法

indexOf

●indexOf 用来找到数组中某一项的索引
● 语法: indexOf(你要找的数组中的项)

○我们要找的是数组中值为 3 的那一项
○返回的就是值为 3 的那一项在该数组中的索引
● 如果你要找的内容在数组中没有,那么就会返回 -1

○你要找的值在数组中不存在,那么就会返回 -1

forEach

● 和 for 循环一个作用,就是用来遍历数组的
● 语法:arr.forEach(function (item, index, arr) {})

○forEach() 的时候传递的那个函数,会根据数组的长度执行
○数组的长度是多少,这个函数就会执行多少回

map

● 和 forEach 类似,只不过可以对数组中的每一项进行操作,返回一个新的数组


filter

● 和 map 的使用方式类似,按照我们的条件来筛选数组
● 把原始数组中满足条件的筛选出来,组成一个新的数组返回

○我们设置的条件就是 > 1
○返回的新数组就会是原始数组中所有 > 1 的项
回顾对象
什么是对象? 在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。 对象是由属性和方法组成的。
○属性:事物的特征,在对象中用属性来表示(常用名词)
○方法:事物的行为,在对象中用方法来表示(常用动词)
● 为什么需要对象?保存一个值时,可以使用变量,保存多个值(一组值)时,可以使用数组。



创建字符串(了解)

● 我们创建字符串也分为两种方法 字面量构造函数
● 字面量:

● 构造函数创建


ASCII 字符集(了解)

●我们都知道,计算机只能存储 0101010 这样的二进制数字
●那么我们的 a ~ z / A ~ Z / $ / @ /… 之类的内容也有由二进制数字组成的
●我们可以简单的理解为, a ~ z / A ~ Z / $ / @ /… 之类的内容都有一个自己的编号,然后在计算机存储的时候,是存储的这些编号,我们看的时候,也是通过这些编号在解析成我们要看到的内容给我们看到




●上面的就是 ASCII 对照表,我们只需要知道他是这么存储的就好

unicode 编码

●我们看到了,ASCII 只有这 128 个字符的编码结构
●但是因为 ASCII 出现的比较早,而且是美国发明的,早先时候这些内容就够用了
●因为存储一些英文的内容,传递一些英文的文章什么的都够用了
●那么对于这个世界来说肯定是不够用的
●因为我们的汉字没有办法存储,包括一些其他国家的语言也没有办法存储
●所以就出现了 unicode 编码,也叫(万国码,统一码)
●unicode 对照表就是一个和 ASCII 一样的对照表,只不过变得很大很大,因为存储的内容特别的多
●而且包含了世界上大部分国家的文字,所以我们的文字和字符现在在存储的时候,都是按照 unicode 编码转换成数字进行存储
●我们的 UTF-8 就是一种 8 位的 unicode 字符集

字符串的常用方法

●我们操作字符串,也有一堆的方法来帮助我们操作
●字符串和数组有一个一样的地方,也是按照索引来排列的

charAt

●charAt(索引) 是找到字符串中指定索引位置的内容返回

○因为字符串也是按照索引进行排列的,也是同样从 0 开始
○所以索引 2 的位置就是 c
● 如果没有对应的索引,那么就会返回 空字符串

○这个字符串根本没有索引 10 的位置
○所以就会返回一个空字符串 ''

charCodeAt

●charCodeAt(索引) 就是返回对应索引位置的 unicode 编码

○因为 J 在 unicode 对照表里面存储的是 74,所以就会返回 74

indexOf

●indexOf 就是按照字符找到对应的索引

○因为字符 J 在字符串 Jack 中的索引位置是 0
○所以会返回 0

substring

●substring 是用来截取字符串使用的
● 语法: substring(从哪个索引开始,到哪个索引截止),包含开始索引,不包含结束索引

○从索引 1 开始,到索引 3 截止,包含前面的索引不包含后面的索引
○所以返回的是 el

substr

●substr 也是用来截取字符串的
● 语法:substr(从哪个索引开始,截取多少个)

○这个方法和 substring 不一样的是,第二个参数是截取多少个
○从索引 1 开始,截取 3 个,所以得到的是 ell

toLowerCase 和 toUpperCase

● 这两个方法分别使用用来给字符串转成 小写字母大写字母

Math和Date

Math 和 Date

●Math 是 js 的一个内置对象,提供了一堆的方法帮助我们操作 数字
●Date 是 js 的一个内置对象,提供了一堆的方法帮助我们操作 时间

Math

●没有什么多余的东西,就是一堆的方法来操作数字

random

●Math.random() 这个方法是用来生成一个 0 ~ 1 之间的随机数
● 每次执行生成的数字都不一样,但是一定是 0 ~ 1 之间的
生成的数字包含 0 ,但是不包含 1


round

●Math.round() 是将一个小数 四舍五入 变成一个整数


abs

●Math.abs() 是返回一个数字的 绝对值


ceil

●Math.ceil() 是将一个小数 向上取整 得到的整数


floor

●Math.floor() 是将一个小数 向下取整 的到的整数


max

●Math.max() 得到的是你传入的几个数字之中 最大 的那个数字


min

●Math.min() 得到的是你传入的几个数字之中 最小 的那个数字


PI

●Math.PI 得到的是 π 的值,也就是 3.1415936...

○因为计算机的计算精度问题,只能得到小数点后 15 位
○使用 Math.PI 的时候,是不需要加 () 的

数字转换进制

1toString() 方法可以在数字转成字符串的时候给出一个进制数
○ 语法: toString(你要转换的进制)

2parseInt() 方法可以在字符串转成数字的时候把字符串当成多少进制转成十进制
○ 语法: parseInt(要转换的字符串,当作几进制来转换)


Date

●js 提供的内置构造函数,专门用来获取时间的

new Date()

●new Date() 在不传递参数的情况下是默认返回当前时间

●new Date() 在传入参数的时候,可以获取到一个你传递进去的时间

●new Date() 传递的参数有多种情况
a 传递两个数字,第一个表示年,第二个表示月份

b 传递三个数字,前两个不变,第三个表示该月份的第几天,从 1 到 31

c 传递四个数字,前三个不变,第四个表示当天的几点,从 0 到 23

d 传递五个数字,前四个不变,第五个表示的是该小时的多少分钟,从 0 到 59

e 传递六个数字,前五个不变,第六个表示该分钟的多少秒,从 0 到 59

f 传入字符串的形式


将日期字符串格式化成指定内容

●比如我们得到的时间字符串是 Sun Feb 03 2019 13:13:13 GMT+0800 (中国标准时间)
●我指向得到这个日期中是那一年,我们就要靠截取字符串的形式得到
●但是现在 js 为我们提供了一系列的方法来得到里面的指定内容

getFullYear

●getFullYear() 方式是得到指定字符串中的哪一年


getMonth

●getMonth() 方法是得到指定字符串中的哪一个月份

○这里要有一个注意的地方
○月份是从 0 开始数的
○0 表示 1月,1 表示 2月,依此类推

getDate

●getDate() 方法是得到指定字符串中的哪一天


getHours

●getHours() 方法是得到指定字符串中的哪小时


getMinutes

●getMinutes() 方法是得到指定字符串中的哪分钟


getSeconds

●getSeconds() 方法是得到指定字符串中的哪秒钟

getDay

●getDay() 方法是得到指定字符串当前日期是一周中的第几天(周日是 0,周六是 6)


getTime

●getTime() 方法是得到执行时间到 格林威治时间 的毫秒数


获取时间差

●是指获取两个时间点之间相差的时间
●在 js 中是不能用时间直接做 减法 的
●我们需要一些特殊的操作
●在编程的世界里面,有一个特殊的时间,是 1970年01月01日00时00分00秒
●这个时间我们叫做 格林威治时间
●所有的编程世界里面,这个时间都是一样的,而且 格林威治时间 的数字是 0
●从 格林威治时间 开始,每经过1毫秒,数字就会 + 1
●所以我们可以获取到任意一个时间节点到 格林威治时间 的毫秒数
●然后在用两个毫秒数相减,就能得到两个时间点之间相差的毫秒数
●我们在通过这个毫秒数得到准确的时间

BOM和DOM(上)

BOM 和 DOM(上)

●今天开始我们开始使用 js 去操作 浏览器页面中的 html 元素了

BOM

●BOM(Browser Object Model): 浏览器对象模型
●其实就是操作浏览器的一些能力
●我们可以操作哪些内容
○获取一些浏览器的相关信息(窗口的大小)
○操作浏览器进行页面跳转
○获取当前浏览器地址栏的信息
○操作浏览器的滚动条
○浏览器的信息(浏览器的版本)
○让浏览器出现一个弹出框(alert / confirm / prompt)
○...
●BOM 的核心就是 window 对象
●window 是浏览器内置的一个对象,里面包含着操作浏览器的方法

获取浏览器窗口的尺寸

●innerHeight 和 innerWidth
● 这两个方法分别是用来获取浏览器窗口的宽度和高度(包含滚动条的)


浏览器的弹出层

●alert 是在浏览器弹出一个提示框


○这个弹出层知识一个提示内容,只有一个确定按钮
○点击确定按钮以后,这个提示框就消失了
●confirm 是在浏览器弹出一个询问框


○这个弹出层有一个询问信息和两个按钮
○当你点击确定的时候,就会得到 true
○当你点击取消的时候,就会得到 false
●prompt 是在浏览器弹出一个输入框


○这个弹出层有一个输入框和两个按钮
○当你点击取消的时候,得到的是 null
○当你点击确定的时候得到的就是你输入的内容

浏览器的地址信息

●在 window 中有一个对象叫做 location
●就是专门用来存储浏览器的地址栏内的信息的

location.href

●location.href 这个属性存储的是浏览器地址栏内 url 地址的信息

○会把中文变成 url 编码的格式
●location.href 这个属性也可以给他赋值


location.reload

●location.reload() 这个方法会重新加载一遍页面,就相当于刷新是一个道理

○注意: 不要写在全局,不然浏览器就会一直处在刷新状态

浏览器的历史记录

●window 中有一个对象叫做 history
●是专门用来存储历史记录信息的

history.back

●history.back 是用来会退历史记录的,就是回到前一个页面,就相当于浏览器上的 ⬅️ 按钮

○前提是你要有上一条记录,不然就是一直在这个页面,也不会回退

history.forword

●history.forword 是去到下一个历史记录里面,也就是去到下一个页面,就相当于浏览器上的 ➡️ 按钮

○前提是你要之前有过回退操作,不然的话你现在就是最后一个页面,没有下一个

浏览器的版本信息(了解)

●window 中有一个对象叫做 navigator
●是专门用来获取浏览器信息的

navigator.userAgent

●navigator.userAgent 是获取的浏览器的整体信息


navigator.appName

●navigator.appName 获取的是浏览器的名称


navigator.appVersion

●navigator.appVersion 获取的是浏览器的版本号


navigator.platform

●navigator.platform 获取到的是当前计算机的操作系统


浏览器的 onload 事件

● 这个不在是对象了,而是一个事件
● 是在页面所有资源加载完毕后执行的


在 html 页面中把 js 写在 head 里面


在 html 页面中把 js 写在 body 最后面


浏览器的 onscroll 事件

● 这个 onscroll 事件是当浏览器的滚动条滚动的时候触发
● 或者鼠标滚轮滚动的时候出发

○注意:前提是页面的高度要超过浏览器的可是窗口才可以

浏览器滚动的距离

●浏览器内的内容即然可以滚动,那么我们就可以获取到浏览器滚动的距离
●思考一个问题?
○浏览器真的滚动了吗?
○其实我们的浏览器是没有滚动的,是一直在那里
○滚动的是什么?是我们的页面
○所以说,其实浏览器没有动,只不过是页面向上走了
●所以,这个已经不能单纯的算是浏览器的内容了,而是我们页面的内容
●所以不是在用 window 对象了,而是使用 document 对象

scrollTop

● 获取的是页面向上滚动的距离
● 一共有两个获取方式
○document.body.scrollTop
○document.documentElement.scrollTop
○两个都是获取页面向上滚动的距离
○区别:
■IE 浏览器
●没有 DOCTYPE 声明的时候,用这两个都行
●有 DOCTYPE 声明的时候,只能用 document.documentElement.scrollTop
■Chrome 和 FireFox
●没有 DOCTYPE 声明的时候,用 document.body.scrollTop
●有 DOCTYPE 声明的时候,用 document.documentElement.scrollTop
■Safari
●两个都不用,使用一个单独的方法 window.pageYOffset

scrollLeft

● 获取页面向左滚动的距离
● 也是两个方法
○document.body.scrollLeft
○document.documentElementLeft

○ 两个之间的区别和之前的 scrollTop 一样

定时器

●在 js 里面,有两种定时器,倒计时定时器间隔定时器

倒计时定时器

● 倒计时多少时间以后执行函数
● 语法: setTimeout(要执行的函数,多长时间以后执行)
● 会在你设定的时间以后,执行函数

○时间是按照毫秒进行计算的,1000 毫秒就是 1秒钟
○所以会在页面打开 1 秒钟以后执行函数
○只执行一次,就不在执行了
○返回值是,当前这个定时器是页面中的第几个定时器

间隔定时器

● 每间隔多少时间就执行一次函数
● 语法: setInterval(要执行的函数,间隔多少时间)

○时间和刚才一样,是按照毫秒进行计算的
○每间隔 1 秒钟执行一次函数
○只要不关闭,会一直执行
○返回值是,当前这个定时器是页面中的第几个定时器

定时器的返回值

● 设置定时器的时候,他的返回值是部分 setTimeout 和 setInterval 的
● 只要有一个定时器,那么就是一个数字


关闭定时器

● 我们刚才提到过一个 timerId,是表示这个定时器是页面上的第几个定时器
● 这个 timerId 就是用来关闭定时器的数字
● 我们有两个方法来关闭定时器 clearTimeout 和 clearInterval

○关闭以后,定时器就不会在执行了
○关闭以后定时器就不会在执行了
● 原则上是
○clearTimeout 关闭 setTimeout
○clearInterval 关闭 setInterval
● 但是其实是可以通用的,他们可以混着使用


DOM(上)

●DOM(Document Object Model): 文档对象模型
●其实就是操作 html 中的标签的一些能力
●我们可以操作哪些内容
○获取一个元素
○移除一个元素
○创建一个元素
○向页面里面添加一个元素
○给元素绑定一些事件
○获取元素的属性
○给元素添加一些 css 样式
○...
●DOM 的核心对象就是 docuemnt 对象
●document 对象是浏览器内置的一个对象,里面存储着专门用来操作元素的各种方法
●DOM: 页面中的标签,我们通过 js 获取到以后,就把这个对象叫做 DOM 对象

获取一个元素

●通过 js 代码来获取页面中的标签
●获取到以后我们就可以操作这些标签了

getElementById

●getElementById 是通过标签的 id 名称来获取标签的
● 因为在一个页面中 id 是唯一的,所以获取到的就是一个元素

○获取到的就是页面中的那个 id 为 box 的 div 标签

getElementsByClassName

●getElementsByClassName 是用过标签的 class 名称来获取标签的
● 因为页面中可能有多个元素的 class 名称一样,所以获取到的是一组元素
● 哪怕你获取的 class 只有一个,那也是获取一组元素,只不过这一组中只有一个 DOM 元素而已

○获取到的是一组元素,是一个长得和数组一样的数据结构,但是不是数组,是 伪数组
○这个一组数据也是按照索引排列的,所以我们想要准确的拿到这个 div,需要用索引来获取

getElementsByTagName

●getElementsByTagName 是用过标签的 标签 名称来获取标签的
● 因为页面中可能有多个元素的 标签 名称一样,所以获取到的是一组元素
● 哪怕真的只有一个这个标签名,那么也是获取一组元素,只不过这一组中只有一个 DOM 元素而已

○和 getElementsByClassName 一样,获取到的是一个长得很像数组的元素
○必须要用索引才能得到准确的 DOM 元素

querySelector

●querySelector 是按照选择器的方式来获取元素
● 也就是说,按照我们写 css 的时候的选择器来获取
● 这个方法只能获取到一个元素,并且是页面中第一个满足条件的元素


querySelectorAll

●querySelectorAll 是按照选择器的方式来获取元素
● 这个方法能获取到所有满足条件的元素,以一个伪数组的形式返回

○获取到的是一组数据,也是需要用索引来获取到准确的每一个 DOM 元素

操作属性

●通过我们各种获取元素的方式获取到页面中的标签以后
●我们可以直接操作 DOM 元素的属性,就能直接把效果展示在页面上

innerHTML

● 获取元素内部的 HTML 结构

● 设置元素的内容

○设置完以后,页面中的 div 元素里面就会嵌套一个 p 元素

innerText

● 获取元素内部的文本(只能获取到文本内容,获取不到 html 标签)

● 可以设置元素内部的文本

○设置完毕以后,会把

hello

当作一个文本出现在 div 元素里面,而不会把 p 解析成标签

getAttribute

● 获取元素的某个属性(包括自定义属性)


setAttribute

● 给元素设置一个属性(包括自定义属性)


removeAttribute

● 直接移除元素的某个属性


style

● 专门用来给元素添加 css 样式的
● 添加的都是行内样式

○页面中的 div 就会变成一个宽高都是 100,背景颜色是粉色

className

● 专门用来操作元素的 类名的

● 也可以设置元素的类名,不过是全覆盖式的操作

○在设置的时候,不管之前有没有类名,都会全部被设置的值覆盖

DOM(下)

DOM(下)

●DOM 树就是我们 html 结构中一个一个的节点构成的
●不光我们的标签是一个节点,我们写的文本内容也是一个节点,注释,包括空格都是节点

DOM节点

●DOM 的节点我们一般分为常用的三大类 元素节点 / 文本节点 / 属性节点
●什么是分类,比如我们在获取元素的时候,通过各种方法获取到的我们叫做元素节点(标签节点)
●比如我们标签里面写的文字,那么就是文本节点
●写在每一个标签上的属性,就是属性节点

元素节点

●我们通过 getElementBy... 获取到的都是元素节点

属性节点

●我们通过 getAttribute 获取的就是元素的属性节点

文本节点

●我们通过 innerText 获取到的就是元素的文本节点

获取节点

●childNodes:获取某一个节点下 所有的子一级节点

○我们会发现,拿到以后是一个伪数组,里面有三个节点
○一个 text:从

一直到

中间有一个换行和一堆空格,这个是第一个节点,是一个文本节点
○一个 p:这个 p 标签就是第二个节点,这个是一个元素节点
○一个 text:从

一直到
中间有一个换行和一堆空格,这个是第三个节点,是一个文本节点
○这个时候就能看到我们有不同的节点类型了
●children :获取某一节点下所有的子一级 元素节点

○我们发现只有一个节点了,因为 children 只要元素节点
○div 下面又只有一个元素节点,就是 p
○所以就只有一个,虽然只有一个,但是也是一个 伪数组
●firstChild:获取某一节点下子一级的 第一个节点

○这个是只获取一个节点,不再是伪数组了
○获取的是第一个
○第一个就是
一直到

的那个换行和空格,是个文本节点
●lastChild:获取某一节点下子一级的 最后一个节点

○只获取一个节点,不再是伪数组
○获取的是最后一个
○最后一个就是

一直到
之间的换行和空格,是个文本节点
●firstElementChild:获取某一节点下子一级 第一个元素节点

○只获取一个节点,不在是伪数组
○获取的是第一个 元素节点
○第一个元素节点就是 p 标签,是一个元素节点
●lastElementChild:获取某一节点下子一级 最后一个元素节点

○只获取一个节点,不在是伪数组
○获取的是最后一个 元素节点
○最后一个元素节点是

world

,是一个元素节点
●nextSibling:获取某一个节点的 下一个兄弟节点

○只获取一个节点,不在是伪数组
○获取的是 id="b" 这个 li 的下一个兄弟节点
○因为 id="b" 的下一个节点,是两个 li 标签之间的换行和空格,所以是一个文本节点
●previousSibling:获取某一个节点的 上一个兄弟节点

○只获取一个节点,不在是伪数组
○获取的是 id="b" 这个 li 的上一个兄弟节点
○因为 id="b" 的上一个节点,是两个 li 标签之间的换行和空格,所以是一个文本节点
●nextElementSibling:获取某一个节点的 下一个元素节点

○只获取一个节点,不在是伪数组
○获取的是 id="b" 这个 li 的下一个兄弟元素节点
○因为 id="b" 的下一个兄弟元素节点就是 id="c" 的 li,是一个元素节点
●previousElementSibling:获取某一个节点的 上一个元素节点

○只获取一个节点,不在是伪数组
○获取的是 id="b" 这个 li 的上一个兄弟元素节点
○因为 id="b" 的上一个兄弟元素节点就是 id="a" 的 li,是一个元素节点
●parentNode:获取某一个节点的 父节点

○只获取一个节点,不在是伪数组
○获取的是当前这个 li 的父元素节点
○因为这个 li 的父亲就是 ul,所以获取到的就是 ul,是一个元素节点
●attributes:获取某一个 元素节点 的所有 属性节点

○获取的是一组数据,是该元素的所有属性,也是一个伪数组
○这个 li 有三个属性,id / a / test 三个,所以就获取到了这三个

节点属性

● 我们已经知道节点会分成很多种,而且我们也能获取到各种不同的节点
● 接下来我们就来聊一些各种节点之间属性的区别
● 我们先准备一段代码


nodeType

●nodeType:获取节点的节点类型,用数字表示

○nodeType === 1 就表示该节点是一个 元素节点
○nodeType === 2 就表示该节点是一个 属性节点
○nodeType === 3 就表示该节点是一个 注释节点

nodeName

●nodeName:获取节点的节点名称

○元素节点的 nodeName 就是 大写标签名
○属性节点的 nodeName 就是 属性名
○文本节点的 nodeName 都是 #text

nodeValue

●nodeValue: 获取节点的值

○元素节点没有 nodeValue
○属性节点的 nodeValue 就是 属性值
○文本节点的 nodeValue 就是 文本内容

汇总

操作 DOM 节点

●我们所说的操作无非就是 增删改查(CRUD)
●创建一个节点(因为向页面中增加之前,我们需要先创建一个节点出来)
●向页面中增加一个节点
●删除页面中的某一个节点
●修改页面中的某一个节点
●获取页面中的某一个节点

创建一个节点

●createElement:用于创建一个元素节点

○创建出来的就是一个可以使用的 div 元素
●createTextNode:用于创建一个文本节点


向页面中加入一个节点

●appendChild:是向一个元素节点的末尾追加一个节点
● 语法: 父节点.appendChild(要插入的子节点)

●insertBefore:向某一个节点前插入一个节点
● 语法: 父节点.insertBefore(要插入的节点,插入在哪一个节点的前面)


删除页面中的某一个节点

●removeChild:移除某一节点下的某一个节点
● 语法:父节点.removeChild(要移除的字节点)


修改页面中的某一个节点

●replaceChild:将页面中的某一个节点替换掉
● 语法: 父节点.replaceChild(新节点,旧节点)


获取元素的非行间样式

● 我们在操作 DOM 的时候,很重要的一点就是要操作元素的 css 样式
● 那么在操作 css 样式的时候,我们避免不了就要获取元素的样式
● 之前我们说过可以用 元素.style.xxx 来获取
● 但是这个方法只能获取到元素 行间样式,也就是写在行内的样式

● 不管是外链式还是内嵌式,我们都获取不到该元素的样式
● 这里我们就要使用方法来获取了 getComputedStylecurrentStyle
● 这两个方法的作用是一样的,只不过一个在 非 IE 浏览器,一个在 IE 浏览器

getComputedStyle(非IE使用)

● 语法:window.getComputedStyle(元素, null).要获取的属性

○这个方法获取行间样式和非行间样式都可以

currentStyle(IE使用)

● 语法: 元素.currentStyle.要获取的属性


获取元素的偏移量

●就是元素在页面上相对于参考父级的左边和上边的距离

offsetParent

●获取元素的偏移量参考父级
●其实就是假设你要给一个元素 绝对定位 的时候
●它是根据谁来进行定位的
●那么这个元素的偏移量参考父级就是谁

offsetLeft 和 offsetTop

●获取的是元左边的偏移量和上边的偏移量
●offsetLeft : 该元素相对于参考父级的左侧偏移量
●offsetTop : 该元素相对于参考父级的上侧偏移量

获取元素尺寸

●就是获取元素的 "占地面积"

offsetWith 和 offsetHeight

●offsetWidth : 获取的是元素 内容 + padding + border 的宽度
●offsetHeight : 获取的是元素 内容 + padding + border 的高度

clientWidth 和 clientHeight

●clientWidth : 获取的是元素 内容 + padding 的宽度
●clientHeight : 获取的是元素 内容 + padding 的高度

注意

●获取到的尺寸是没有单位的数字
●当元素在页面中不占位置的时候, 获取到的是 0
○display: none; 元素在页面不占位
○visibility: hidden; 元素在页面占位

获取浏览器窗口尺寸

● 我们之前学过一个 innerWidth 和 innerHeight
● 他们获取到的是窗口包含滚动条的尺寸
● 下面我们学习两个不包含滚动条的尺寸获取方式
●document.documentElement.clientWidth : 可视窗口的宽度
●document.documentElement.clientHeight : 可视窗口的高度

EVENT(上)



●之前我们简单的了解过一些事件,比如 onclick / onload / onscroll / ...
●今天开始,我们详细的学习一些 事件

什么是事件

● 一个事件由什么东西组成
○触发谁的事件:事件源
○触发什么事件:事件类型
○触发以后做什么:事件处理函数
○我们想要在点击 div 以后做什么事情,就把我们要做的事情写在事件处理函数里面
○当我们点击 div 的时候,就会执行事件处理函数内部的代码
○每点击一次,就会执行一次事件处理函数

事件对象

● 什么是事件对象?
● 就是当你触发了一个事件以后,对该事件的一些描述信息
● 例如:
○你触发一个点击事件的时候,你点在哪个位置了,坐标是多少
○你触发一个键盘事件的时候,你按的是哪个按钮
○...
● 每一个事件都会有一个对应的对象来描述这些信息,我们就把这个对象叫做 事件对象
● 浏览器给了我们一个 黑盒子,叫做 window.event,就是对事件信息的所有描述
○比如点击事件
○你点在了 0,0 位置,那么你得到的这个事件对象里面对应的就会有这个点位的属性
○你点在了 10, 10 位置,那么你得到的这个事件对象里面对应的就会有这个点位的属性
○...
● 这个玩意很好用,但是一般来说,好用的东西就会有 兼容性问题
● 在 IE低版本 里面这个东西好用,但是在 高版本IE 和 Chrome 里面不好使了
● 我们就得用另一种方式来获取 事件对象
● 在每一个事件处理函数的行参位置,默认第一个就是 事件对象

● 综上所述,我们以后在每一个事件里面,想获取事件对象的时候,都用兼容写法


点击事件的光标坐标点获取

●刚才即然说了,可以获取到坐标点,那么接下来我们就学习一下怎么获取坐标点
●我们的每一个点击事件的坐标点都不是一对,因为要有一个相对的坐标系
●例如:
○相对事件源(你点击的元素)
○相对页面
○相对浏览器窗口
○...
●因为都不一样,所以我们获取的 事件对象 里面的属性也不一样

相对于你点击的元素来说

●offsetX 和 offsetY
● 是相对于你点击的元素的边框内侧开始计算
 

二三阶段知识点_第3张图片




相对于浏览器窗口你点击的坐标点

●clientX 和 clientY
● 是相对于浏览器窗口来计算的,不管你页面滚动到什么情况,都是根据窗口来计算坐标



相对于页面你点击的坐标点

●pageX 和 pageY
● 是相对于整个页面的坐标点,不管有没有滚动,都是相对于页面拿到的坐标点


● 根据页面左上角来说
○margin-left 是 30
○左边框是 10
○左右 padding 各是 20
○内容区域是 300
○pageX : 300 + 20 + 20 + 10 + 30 = 380
○margin-top 是 20
○上边框是 10
○上下 padding 各是 20
○内容区域是 300
○pageY : 300 + 20 + 20 + 10 + 20 = 270

点击按键信息(了解)

●我们的鼠标一般都有两个按键,一个左键一个右键
●我们的事件对象里面也有这个信息,确定你点击的是左键还是右键
●我们使用 事件对象.button 来获取信息
●0 为鼠标左键,2 为鼠标右键

常见的事件(了解)

●我们在写页面的时候经常用到的一些事件
●大致分为几类,浏览器事件 / 鼠标事件 / 键盘事件 / 表单事件 / 触摸事件
●不需要都记住,但是大概要知道

浏览器事件

●load : 页面全部资源加载完毕
●scroll : 浏览器滚动的时候触发
●...

鼠标事件

●click :点击事件
●dblclick :双击事件
●contextmenu : 右键单击事件
●mousedown :鼠标左键按下事件
●mouseup :鼠标左键抬起事件
●mousemove :鼠标移动
●mouseover :鼠标移入事件
●mouseout :鼠标移出事件
●mouseenter :鼠标移入事件
●mouseleave :鼠标移出事件
●...

键盘事件

●keyup : 键盘抬起事件
●keydown : 键盘按下事件
●keypress : 键盘按下再抬起事件
●...

表单事件

●change : 表单内容改变事件
●input : 表单内容输入事件
●submit : 表单提交事件
●...

触摸事件

●touchstart : 触摸开始事件
●touchend : 触摸结束事件
●touchmove : 触摸移动事件
●...

键盘事件

● 刚才了解了一下鼠标事件,现在来聊聊键盘事件
● 我们在键盘事件里面最主要的就是要做两个事情
○判断点击的是哪个按键
○有没有组合按键,shift + a / ctrl + b / ...
● 我们先要明确一个问题,就是是不是所有元素都可以绑定键盘事件
○我们说事件有一个关键的东西是,该事件是由谁来触发的
○一个 div 元素在页面上,我怎么能让一个键盘事件触发在 div 上
○所以说,我们一般只给能在页面上选中的元素(表单元素) 和 document 来绑定键盘事件

确定按键

● 我们的键盘上每一个按键都有一个自己独立的编码
● 我们就是靠这个编码来确定我们按下的是哪个按键的
● 我们通过 事件对象.keyCode 或者 事件对象.which 来获取
● 为什么要有两个呢,是因为 FireFox2.0 不支持 keycode 所以要用 which


常见的键盘码(了解)

●8: 删除键(delete)
●9: 制表符(tab)
●13: 回车键(ebter)
●16: 上档键(shift)
●17: ctrl 键
●18: alt 键
●27: 取消键(esc)
●32: 空格键(space)
●...

组合按键

● 组合案件最主要的就是 alt / shift / ctrl 三个按键
● 在我点击某一个按键的时候判断一下这三个键有没有按下,有就是组合了,没有就是没有组合
● 事件对象里面也为我们提供了三个属性
○altKey :alt 键按下得到 true,否则得到 false
○shiftKey :shift 键按下得到 true,否则得到 false
○ctrlKey :ctrl 键按下得到 true,否则得到 false
● 我们就可以通过这三个属性来判断是否按下了


事件的绑定方式

● 我们现在给一个注册事件都是使用 onxxx 的方式
● 但是这个方式不是很好,只能给一个元素注册一个事件
● 一旦写了第二个事件,那么第一个就被覆盖了

○当你点击的时候,只会执行第二个,第一个就没有了
● 我们还有一种事件监听的方式去给元素绑定事件
● 使用 addEventListener 的方式添加
○这个方法不兼容,在 IE 里面要使用 attachEvent

事件监听

●addEventListener : 非 IE 7 8 下使用
● 语法: 元素.addEventListener('事件类型', 事件处理函数, 冒泡还是捕获)

○当你点击 div 的时候,两个函数都会执行,并且会按照你注册的顺序执行
○先打印 我是第一个事件 再打印 我是第二个事件
○注意: 事件类型的时候不要写 on,点击事件就是 click,不是 onclick
●attachEvent :IE 7 8 下使用
● 语法: 元素.attachEvent('事件类型', 事件处理函数)

○当你点击 div 的时候,两个函数都会执行,并且会按照你注册的顺序倒叙执行
○先打印 我是第二个事件 再打印 我是第一个事件
○注意: 事件类型的时候要写 on,点击事件就行 onclick

两个方式的区别

●注册事件的时候事件类型参数的书写
○addEventListener : 不用写 on
○attachEvent : 要写 on
●参数个数
○addEventListener : 一般是三个常用参数
○attachEvent : 两个参数
●执行顺序
○addEventListener : 顺序注册,顺序执行
○attachEvent : 顺序注册,倒叙执行
●适用浏览器
○addEventListener : 非 IE 7 8 的浏览器
○attachEvent : IE 7 8 浏览器

EVENT(下)

EVENT(下)

● 今天来聊一聊事件的执行机制
● 什么是事件的执行机制呢?
○ 思考一个问题?
○ 当一个大盒子嵌套一个小盒子的时候,并且两个盒子都有点击事件
○ 你点击里面的小盒子,外面的大盒子上的点击事件要不要执行
 


事件的传播

●就像上面那个图片一样,我们点击在红色盒子身上的同时,也是点击在了粉色盒子上
●这个是既定事实,那么两个盒子的点击事件都会触发
●这个就叫做 事件的传播
○当元素触发一个事件的时候,其父元素也会触发相同的事件,父元素的父元素也会触发相同的事件
○就像上面的图片一样
○点击在红色盒子上的时候,会触发红色盒子的点击事件
○也是点击在了粉色的盒子上,也会触发粉色盒子的点击事件
○也是点击在了 body 上,也会触发 body 的点击事件
○也是点击在了 html 上,也会触发 html 的点击事件
○也是点击在了 document 上,也会触发 document 的点击事件
○也是点击在了 window 上,也会触发 window 的点击事件
○也就是说,页面上任何一个元素触发事件,都会一层一层最终导致 window 的相同事件触发,前提是各层级元素得有注册相同的事件,不然不会触发
●在事件传播的过程中,有一些注意的点:
a只会传播同类事件
b只会从点击元素开始按照 html 的结构逐层向上元素的事件会被触发
c内部元素不管有没有该事件,只要上层元素有该事件,那么上层元素的事件就会被触发
●到现在,我们已经了解了事件的传播,我们再来思考一个问题
○事件确实会从自己开始,到 window 的所有相同事件都会触发
○是因为我们点在自己身上,也确实逐层的点在了直至 window 的每一个元素身上
○但是到底是先点在自己身上,还是先点在了 window 身上呢
○先点在自己身上,就是先执行自己的事件处理函数,逐层向上最后执行 window 的事件处理函数
○反之,则是先执行 window 的事件处理函数,逐层向下最后执行自己身上的事件处理函数

冒泡、捕获、目标

●我们刚才聊过了,每一个事件,都是有可能从自己到 window ,有可能要执行多个同类型事件
●那么这个执行的顺序就有一些说法了

目标

●你是点击在哪个元素身上了,那么这个事件的 目标 就是什么

冒泡

●就是从事件 目标 的事件处理函数开始,依次向外,直到 window 的事件处理函数触发
●也就是从下向上的执行事件处理函数

捕获

●就是从 window 的事件处理函数开始,依次向内,只要事件 目标 的事件处理函数执行
●也就是从上向下的执行事件处理函数

冒泡和捕获的区别

●就是在事件的传播中,多个同类型事件处理函数的执行顺序不同

事件委托

●就是把我要做的事情委托给别人来做
●因为我们的冒泡机制,点击子元素的时候,也会同步触发父元素的相同事件
●所以我们就可以把子元素的事件委托给父元素来做

事件触发

● 点击子元素的时候,不管子元素有没有点击事件,只要父元素有点击事件,那么就可以触发父元素的点击事件

○像上面一段代码,当你点击 ul 的时候肯定会触发
○但是当你点击 li 的时候,其实也会触发

target

● target 这个属性是事件对象里面的属性,表示你点击的目标
● 当你触发点击事件的时候,你点击在哪个元素上,target 就是哪个元素
● 这个 target 也不兼容,在 IE 下要使用 srcElement

○上面的代码,当你点击 ul 的时候,target 就是 ul
○当你点击在 li 上面的时候,target 就是 li

委托

● 这个时候,当我们点击 li 的时候,也可以触发 ul 的点事件
● 并且在事件内不,我们也可以拿到你点击的到底是 ul 还是 li
● 这个时候,我们就可以把 li 的事件委托给 ul 来做

○上面的代码,我们就可以把 li 要做的事情委托给 ul 来做

总结

●为什么要用事件委托
○我页面上本身没有 li
○我通过代码添加了一些 li
○添加进来的 li 是没有点击事件的
○我每次动态的操作完 li 以后都要从新给 li 绑定一次点击事件
○比较麻烦
○这个时候只要委托给 ul 就可以了
○因为新加进来的 li 也是 ul 的子元素,点击的时候也可以触发 ul 的点击事件
●事件委托的书写
○元素的事件只能委托给结构父级或者再结构父级的同样的事件上
○li 的点击事件,就不能委托给 ul 的鼠标移入事件
○li 的点击事件,只能委托给 ul 或者在高父级的点击事件上

默认行为

●默认行为,就是不用我们注册,它自己就存在的事情
○比如我们点击鼠标右键的时候,会自动弹出一个菜单
○比如我们点击 a 标签的时候,我们不需要注册点击事件,他自己就会跳转页面
○...
●这些不需要我们注册就能实现的事情,我们叫做 默认事件

阻止默认行为

● 有的时候,我们不希望浏览器执行默认事件
○比如我给 a 标签绑定了一个点击事件,我点击你的时候希望你能告诉我你的地址是什么
○而不是直接跳转链接
○那么我们就要把 a 标签原先的默认事件阻止,不让他执行默认事件
● 我们有两个方法来阻止默认事件
○e.preventDefault() : 非 IE 使用
○e.returnValue = false :IE 使用
● 我们阻止默认事件的时候也要写一个兼容的写法

○这样写完以后,你点击 a 标签的时候,就不会跳转链接了
○而是会在控制台打印出 a 标签的 href 属性的值

正则

● 正则表达式,又名 “规则表达式”
● 由我们自己来书写 “规则”,专门用来检测 字符串 是否符合 “规则” 使用的
● 我们使用一些特殊的字符或者符号定义一个 “规则公式”,然后用我们定义好的 “规则公式” 去检测字符串是不是合格

○上面的变量 reg 就是定制好的规则
○检测 str1 这个字符串的时候,符合规则
○检测 str2 这个字符串的时候,不符合规则

创建一个正则表达式

●想制定 “规则”,必须要按照人家要求的方式来制定
●把一些字母和符号写在 // 中间的东西,叫做正则表达式,比如 /abcdefg/
●创建正则表达式有两个方式 字面量构造函数创建

字面量创建


●这个正则表达式就可以去检测字符串了

构造函数创建


●使用构造函数方式创建的和字面量创建的,得到的结果一样

正则表达式里面的符号

●知道了怎么创建一个正则表达式以后,我们就来详细的说一下正则表达式里面涉及到的一些符号了

元字符

●. : 匹配非换行的任意字符
●\ : 转译符号,把有意义的 符号 转换成没有意义的 字符,把没有意义的 字符 转换成有意义的 符号
●\s : 匹配空白字符(空格/制表符/...)
●\S : 匹配非空白字符
●\d : 匹配数字
●\D : 匹配非数字
●\w : 匹配数字字母下划线
●\W : 匹配非数字字母下划线
● 有了元字符我们就可以简单的制定一些规则了


限定符

●* : 前一个内容重复至少 0 次,也就是可以出现 0 ~ 正无穷
●+ : 前一个内容重复至少 1 次,也就是可以出现 1 ~ 正无穷
●? : 前一个内容重复 0 或者 1 次,也就是可以出现 0 ~ 1
●{n} : 前一个内容重复 n 次,也就是必须出现 n
●{n,} : 前一个内容至少出现 n 次,也就是出现 n ~ 正无穷
●{n,m} : 前一个内容至少出现 n 次至多出现 m 次,也就是出现 n ~ m
● 限定符是配合元字符使用的


边界符

●^ : 表示开头
●$ : 表示结尾
● 边界符是限定字符串的开始和结束的


特殊符号

●() : 限定一组元素
●[] : 字符集合,表示写在 [] 里面的任意一个都行
●[^] : 反字符集合,表示写在 [^] 里面之外的任意一个都行
●- : 范围,比如 a-z 表示从字母 a 到字母 z 都可以
●| : 或,正则里面的或 a|b 表示字母 a 或者 b 都可以
● 现在我们就可以把若干符号组合在一起使用了


标识符

●i : 表示忽略大小写
○这个 i 是写在正则的最后面的
○/\w/i
○就是在正则匹配的时候不去区分大小写
●g : 表示全局匹配
○这个 g 是写在正则的最后面的
○/\w/g
○就是全局匹配字母数字下划线

正则表达式的方法

●正则提供了一些方法给我们使用
●用来检测和捕获字符串中的内容的

test

●test 是用来检测字符串是否符合我们正则的标准
● 语法: 正则.test(字符串)
● 返回值: boolean


exec

●exec 是把字符串中符合条件的内容捕获出来
● 语法: 正则.exec(字符串)
● 返回值: 把字符串中符合正则要求的第一项以及一些其他信息,以数组的形式返回

○数组第 0 项就是匹配到的字符串内容
○index 属性表示从字符串的索引几开始是匹配的到字符串

字符串的方法

●字符串中有一些方法也是可以和正则一起使用的

search

●search 是查找字符串中是否有满足正则条件的内容
● 语法: 字符串.search(正则)
● 返回值 : 有的话返回开始索引,没有返回 -1

match

●match 找到字符串中符合正则条件的内容返回
● 语法: 字符串.match(正则)
● 返回值 :
○没有标示符 g 的时候,是和 exec 方法一样
○有标示符 g 的时候,是返回一个数组,里面是匹配到的每一项

replace

●replace 是将字符串中满足正则条件的字符串替换掉
● 语法: 字符串.replace(正则,要替换的字符串)
● 返回值 : 替换后的字符串

ES5和ES6



●我们所说的 ES5 和 ES6 其实就是在 js 语法的发展过程中的一个版本而已
●比如我们使用的微信
○最早的版本是没有支付功能的
○随着时间的流逝,后来出现了一个版本,这个版本里面有支付功能了
●ECMAScript 就是 js 的语法
○以前的版本没有某些功能
○在 ES5 这个版本的时候增加了一些功能
○在 ES6 这个版本的时候增加了一些功能
●因为浏览器是浏览器厂商生产的
○ECMAScript 发布了新的功能以后,浏览器厂商需要让自己的浏览器支持这些功能
○这个过程是需要时间的
○所以到现在,基本上大部分浏览器都可以比较完善的支持了
○只不过有些浏览器还是不能全部支持
○这就出现了兼容性问题
○所以我们写代码的时候就要考虑哪些方法是 ES5 或者 ES6 的,看看是不是浏览器都支持

JSON 方法

●json 是一种特殊的字符串个是,本质是一个字符串

● 就是对象内部的 key 和 value 都用双引号包裹的字符串(必须是双引号)

JSON的两个方法

●我们有两个方法可以使用 JSON.parse

●json.stringify 是将 js 的对象或者数组转换成为 json 格式的字符串

JSON.parse

●JSON.parse 是将 json 格式的字符串转换为 js 的对象或者数组

○obj 就是我们 js 的对象
○arr 就是我们 js 的数组

JSON.stringify

●JSON.parse 是将 json 格式的字符串转换为 js 的对象或者数组

○jsonObj 就是 json 格式的对象字符串
○jsonArr 就是 json 格式的数组字符串

this 关键字

● 每一个函数内部都有一个关键字是 this
● 可以让我们直接使用的
● 重点: 函数内部的 this 只和函数的调用方式有关系,和函数的定义方式没有关系
● 函数内部的 this 指向谁,取决于函数的调用方式
○ 全局定义的函数直接调用,this => window

○ 对象内部的方法调用,this => 调用者

○ 定时器的处理函数,this => window

○ 事件处理函数,this => 事件源

○ 自调用函数,this => window


call 和 apply 和 bind

●刚才我们说过的都是函数的基本调用方式里面的 this 指向
●我们还有三个可以忽略函数本身的 this 指向转而指向别的地方
●这三个方法就是 call / apply / bind
●是强行改变 this 指向的方法

call

●call 方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向
● 语法: 函数名.call(要改变的 this 指向,要给函数传递的参数1,要给函数传递的参数2, ...)

○fn() 的时候,函数内部的 this 指向 window
○fn.call(obj, 1, 2) 的时候,函数内部的 this 就指向了 obj 这个对象
○使用 call 方法的时候
■会立即执行函数
■第一个参数是你要改变的函数内部的 this 指向
■第二个参数开始,依次是向函数传递参数

apply

●apply 方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向
● 语法: 函数名.apply(要改变的 this 指向,[要给函数传递的参数1, 要给函数传递的参数2, ...])

○fn() 的时候,函数内部的 this 指向 window
○fn.apply(obj, [1, 2]) 的时候,函数内部的 this 就指向了 obj 这个对象
○使用 apply 方法的时候
■会立即执行函数
■第一个参数是你要改变的函数内部的 this 指向
■第二个参数是一个 数组,数组里面的每一项依次是向函数传递的参数

bind

●bind 方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向
● 和 call / apply 有一些不一样,就是不会立即执行函数,而是返回一个已经改变了 this 指向的函数
● 语法: var newFn = 函数名.bind(要改变的 this 指向); newFn(传递参数)

○bind 调用的时候,不会执行 fn 这个函数,而是返回一个新的函数
○这个新的函数就是一个改变了 this 指向以后的 fn 函数
○fn(1, 2) 的时候 this 指向 window
○newFn(1, 2) 的时候执行的是一个和 fn 一摸一样的函数,只不过里面的 this 指向改成了 obj

ES6新增的内容

●之前的都是 ES5 的内容
●接下来我们聊一下 ES6 的内容

let 和 const 关键字

● 我们以前都是使用 var 关键字来声明变量的
● 在 ES6 的时候,多了两个关键字 let 和 const,也是用来声明变量的
● 只不过和 var 有一些区别
alet 和 const 不允许重复声明变量
blet 和 const 声明的变量不会在预解析的时候解析(也就是没有变量提升)

clet 和 const 声明的变量会被所有代码块限制作用范围

●let 和 const 的区别
alet 声明的变量的值可以改变,const 声明的变量的值不可以改变

blet 声明的时候可以不赋值,const 声明的时候必须赋值


箭头函数

● 箭头函数是 ES6 里面一个简写函数的语法方式
● 重点: 箭头函数只能简写函数表达式,不能简写声明式函数

● 语法: (函数的行参) => { 函数体内要执行的代码 }


箭头函数的特殊性

● 箭头函数内部没有 this,箭头函数的 this 是上下文的 this

○按照我们之前的 this 指向来判断,两个都应该指向 obj
○但是 fun 因为是箭头函数,所以 this 不指向 obj,而是指向 fun 的外层,就是 window
● 箭头函数内部没有 arguments 这个参数集合

● 函数的行参只有一个的时候可以不写 () 其余情况必须写

● 函数体只有一行代码的时候,可以不写 {} ,并且会自动 return


函数传递参数的时候的默认值

● 我们在定义函数的时候,有的时候需要一个默认值出现
● 就是当我不传递参数的时候,使用默认值,传递参数了就使用传递的参数

○在 ES6 中我们可以直接把默认值写在函数的行参位置
○这个默认值的方式箭头函数也可以使用
○注意: 箭头函数如果你需要使用默认值的话,那么一个参数的时候也需要写 ()

解构赋值

●解构赋值,就是快速的从对象或者数组中取出成员的一个语法方式

解构对象

● 快速的从对象中获取成员


解构数组

● 快速的从数组中获取成员


注意

●{} 是专门解构对象使用的
●[] 是专门解构数组使用的
●不能混用

模版字符串

● ES5 中我们表示字符串的时候使用 '' 或者 ""
● 在 ES6 中,我们还有一个东西可以表示字符串,就是 ``(反引号)

● 和单引号好友双引号的区别
a 反引号可以换行书写

b 反引号可以直接在字符串里面拼接变量


展开运算符

● ES6 里面号新添加了一个运算符 ... ,叫做展开运算符
● 作用是把数组展开

● 合并数组的时候可以使用

● 也可以合并对象使用

● 在函数传递参数的时候也可以使用

OOP

一、基本概念

(一)、对象

一切能被人类利用的工具

一切事物都可以看作工具,万事万物皆对象

(二)、面向对象

以对象为核心(导向)

(三)、面向对象的思想

一切问题都通过对象来解决的思想

即:用对象来解决所有问题的思想

该思想就是人的思想

(四)、OOP

1. 概念

Object Oriented Programming

对象 面向 编程

面向对象编程

计算机中的一切问题都通过对象来解决

2. 优点:

(1). 代码更简洁

(2). 代码可以重用

(五)、POP

Procedure Oriented Programming

过程 面向 编程

面向过程(指令)编程

注意事项:

面对对象和面向过程是相对的

(六)、构造函数

1. 概念:

用于构造对象的函数

2. 作用:

(1). 构造对象

(2). 设置对象

(3). 返回对象

3. 特点

(1). 通过new来调用

(2). 一般首字母大写

(3). 在通过new调用函数的时候,js会在最开始执行

let this=new Object();

在最末尾执行

return this;

(4). 构造函数中的this永远执行构造出来的新对象

二、基本操作

(一)、OOP步骤

1. 根据问题找对象

2. 自己来创造对象

3. 控制对象做事情

扫地

找扫帚

控制扫帚扫地

拖地

擦桌子

擦窗户

原型/原型链

一、基本概念
(一)、原型

1. 概念

和构造函数一起诞生的一个特殊对象

(二)、构造函数的执行过程

1. let this=new Object();

2. this.__proto__=构造函数.prototype

3. return this;

(三)、原型的特征

1. 构造函数和原型是同时诞生,关系也自动建立

即:

构造函数.prototype 指向它的原型

原型的constructor 指向构造函数

2. 任何对象的__proto__ 都指向它的缔造者(构造函数) 的原型

3. 所有原型对象都是由 Object构造函数构造出来的

4. Object的原型没有缔造者,Object的原型.__proto__指向null

(四)、原型链

1. 概念

原型__proto__所组成的一条链子

例如:

构造函数Cat的原型、Object的原型、null组成了一条链子

2. 作用

在原型链上的某个对象上面增加了某个属性,该对象后面的每个对象,都可以访问到该属性

注意事项:

JS查找某个特征的时候,只会在该对象和该对象的原型链上去查找,而绝对不会到构造函数上去查找

二、基本操作

(一)、构造函数.prototype

1. 作用:

获取构造函数对应的原型对象

2. 格式:

构造函数.prototype

例如:

3. 应用

(1). 在JS中访问对象上的某个属性的时候,会先到js对象中进行查找,如果找不到,

就会到构造函数对应的原型对象上去查找

(2). 一般会把每个对象共同的动词特征(函数)放到构造函数对应的原型上

(二)、实例对象.__proto__

1. 作用:

获取实例对象对应的原型对象

2. 格式:

实例对象.__proto__

例如:

结果为:true

注意事项:

在浏览器中,__proto__会显示为[[Prototype]]

拦截器和代理器

一、基本概念

(一)、单向绑定

JS中的对象改变了,网页中对应的内容也会跟着改变

(二)、双向绑定

JS中的对象改变了,网页中对应的内容也会跟着改变

网页中的内容改变了,JS中的对象也会跟着改变

(三)、拦截器(Object.defineProperty)

1. 作用:

拦截任何对象中任何属性的操作

2. 格式:

Object.defineProperty(对象,”属性名”,处理方式)

处理方式是一个对象

3. 处理对象中的属性:

(1). set:修改操作

set:funciton(val){}

val表示修改的值

(2). get:获取操作

get:function(){

return 数值

}

作为获取的结果

注意事项:

在拦截器中,如果有set,那么get后处理函数的默认值为:function(){}

不写set,get就不会出现(get操作就不会拦截)

二、拦截器

(一). 创建

(二). 常见属性

1. get:获取属性值

2. set:设置属性值

3. writable:可修改

4. configurable:柯删除

5. enumerable:可遍历

6. value:设置属性的值

注意事项:

get/set 为一组

set不可和writable共存

value不能和get/set共存

(三). 应用

数据绑定

在vue2.0中就是采用拦截器的方式实现数据双向绑定

三、代理器(Proxy)

(一)、作用:

代理对象中属性的操作

(二)、创建

new Proxy(代理对象,{

处理方式

})

例如:代理data对象

(三)、处理方式

1. get:代理获取操作

get:function(obj,prop){}

obj:代理的对象

prop:操作的属性

例如:

2. set:代理修改操作

function(obj,prop,val){}

obj:代理的对象

prop:操作的属性

val:操作的值

例如:

闭包

一、基本概念

(一)、闭包

1. 概念

封闭的包含局部变量的空间

2. 背景

(1). 全局变量的优缺点

优点:范围广泛

缺点:会一直占据内存

(2). 局部变量的优缺点:

优点:不会一直占据内存

缺点:范围小

(3). 如何让局部变量不消失

GC在清除内存中函数的执行空间时,会检测该空间中,是否存在会被后面代码使用的空间

如果函数的执行看空间中存在会被函数调用后的代码使用的空间,那么整个函数的执行空间都不会被清除

函数的执行空间 不被清除的前提是:

函数中存在 会被后续代码使用的空间

(4). 如何让GC不清除函数的执行空间?

在函数中返回:复杂数据类型

且在函数的外边 有机会使用它

在函数外必须用变量接收它

4. 如何清除闭包空间

让函数外的变量不保存函数返回的复杂类型数据即可

(二)、闭包技术

1. 概念:

一切创建不销毁空间的技术

2. 实现方式

一般通过在函数中返回复杂类型的数据并在函数外用变量接收返回值

3. 优缺点:

优点:在函数外可以使用局部变量

缺点:造成内存溢出

(三)、GC

Garbbage Collection

垃圾 回收

相当于 JS中的清洁工

GC在清除内存空间前,会检测该空间中的内容是否会被后续使用

如果不被使用,则会清除它

终极目标:

有一个特殊局部变量,该变量需要的时候,就存在,想让它消失,就立刻消失、

二、基本操作

(一)、创建闭包

1. 创建一个具有局部变量的函数

2. 在函数中返回一个复杂类型数据

3. 在函数外用一个变量去接收它

(二)、清除闭包

让函数外的变量装其它数据即可

例如:

p=null;

三、闭包的应用

(一)、柯里化

1. 概念

把一个具有多个参数的函数,转换为多个只有一个参数的函数

即:

把一个具有多个参数的函数,转换为多个函数,每个函数中只有一个参数

2. 实现

多个参数的柯里化:

3. 优点

在有多个参数的情况下,能够简化函数的调用

(二)、防抖

当同时触发多个相同的操作时,只执行一次

(三)、节流

继承

一、基本概念

(一)、继承

如果某个类A 是另一个类B的一种,就说该A类继承于B类

A类称为子类

B类称为父类

例如:

猫类是动物类的一种,就说猫类继承于动物类

猫类称为子类动物类称为父类

自行车、汽车是车的一种,自行车、汽车继承于车类

二、基本操作

(一)、原型继承

1. 概念

通过修改子类对象的原型,来实现继承

2. 实现方式

把子类对象的构造函数的原型修改为父类构造函数所构造的对象

3. 特点

子类对象只能访问父类的特征,并不真正拥有

(二)、构造继承

1. 概念

通过在子类的构造函数中调用父类的构造函数,来实现的继承

2. 实现方式

在子类的构造函数中,用this去调用父类构造函数

例如:动物有特征huxi,子类继承它

例如:子类有特征climb,父类有特征huxi

3. 特点

让子类对象真正拥有父类的特征

4. 用构造继承简化子类的代码

(三)、联合继承

1. 概念

同时使用原型继承和构造继承

2. 实现方式

子类构造函数.prototype=父类构造函数.prototype;

在子类构造函数中,写上父类构造函数.call(this,参数...)

结果:

3. 特点

子类对象既能拥有父类构造函数中的特征,也能访问父类原型上的特征

(四)、ES6继承

1. 概念

通过extends实现的继承

2. 实现方式

class 子类 extends 父类{...}

例如:

3. 特点

相当于前面的构造继承和原型继承的结合,即:等价于前面的联合继承

设计模式

一、基本概念

(一)、设计模式

设计构造函数(类)的方式

(二)、设计模式的种类

1. 单例模式

能够让对象最多只有1个

2. 工厂模式

3. 订阅者/发布者模式

二、单例模式

(一)、概念

构造函数所创建的对象,实例最多只有一个

(二)、实现方式

1. 隐藏构造函数(防止构造函数被直接调用)

把构造函数放到另一个函数中fun

2. 在fun中返回一个操作构造函数的函数

3. 准备一个变量(instance)来保存实例

三、发布者/订阅者模式

1. 概念

(1). 发布者

发布信息(物品)的一方

例如:京东、淘宝...

(2). 订阅者

订阅信息(物品)的一方

消费者...

2. 实现方式

(1). 普通版

测试代码:

(2). 构造函数版

(3). 面向对象版

测试代码:

Ajax

一、基本概念

(一)、请求/响应

1. 请求(Request)

(1). 概念

发给被请求方的要求

请别人满足(达到)自己的要求

请求方的需求

(2). 格式:

协议名://计算机/文件夹/文件

例如:

请求本机中f盘中的ww文件夹中的1.txt文件

file:///f:/ww/1.txt

2. 响应(Response)

被请求方给请求方的回应

(二)、浏览器的作用:

发送请求并解析响应数据(把响应数据转换为网页中的内容)

发送请求并把接收到的响应数据渲染到网页中

(三)、协议

1. 概念

双方必须遵守的规范

例如:买东西

2. 常见协议

(1). ip协议

规定网络中的每台计算机必须要有IP地址,否则不能上网

(2). HTTP协议

超文本传输协议,浏览网络中的信息必须要遵守的协议

(3). FILE协议

获取本机资源必须要遵守的协议

例如:

(四)、Ajax

1. 概念:

Asynchronized Javascript And Xml

异步的 JS 和 xml

2. 作用:

(1). 能够以异步的方式来发送和处理请求

(2). 能够处理接收到的响应数据

普通请求浏览器一旦接收到响应数据,就会用它来刷新整个网页

根本就不给我们出来响应数据的机会

3. 状态码

(1). readyState:过程状态码

0 ajax对象创建完毕

1 ajax设置完毕

2 服务端已经接收到了请求

3 服务器正在处理请求

4 接收到了响应数据

(2). status:结果状态码

200 正常接收到响应

404 请求的目标不存在

405 不能处理对应的请求

500 服务器出错

504 网关超时

(五)、XML

1. 概念

eXtensive Markup Language

可拓展的标记 语言

2. 作用:

解释和描述数据

二、基本操作

(一)、发送请求的方式

1. 通过浏览器直接发送请求

2. 通过超链接发送请求

3. 表单控件

(1). 格式:

在form的action属性中设置请求的目标(url)

(2). 请求的方式(method)

A. get

有数据量的限制,速度更快,在浏览器地址栏中能够看到发送的数据

B.post

没有数据量的限制,速度更慢,在浏览器地址栏中不能够看到发送的数据

4. JS

location.href=请求的代码

(二)、Ajax发送请求

1. 创建ajax对象

2. 设置ajax对象

3. 在回调函数中接收并处理请求

三阶段

什么叫模块化

// 什么叫做模块化
        // 当代码量比较少的时候 模块化没有必要 当代码量变多变大的时候 就需要划分模块 把相同的功能放在一起 
        // 这种开发代码的方式叫做模块化。
        // ES6之前想要使用模块化必须借助第三方工具, sea.js  和  require.js
        // node也是基于模块化的方式开发的。遵守的模块化规范: commonJS 
        // ES6中,就可以直接使用模块化了。 遵守的模块化规范: es module
        // 开启模块化的方式: script标签的type属性设置为module
        // 模块的特点:封闭 
        // 如果一个功能性模块,该如何向外提供功能。
        // var a = 10;
        // console.log(a);

        // 重点:如何定义一个模块  如何向外导出功能  如何引入别的模块的功能



        // 引入也需要通过关键字 import 
        // 第一种引入
        // import {a} from '../js/modules/a.js' 
        // console.log(a)

        // 第二种引入
        // import * as xx from '../js/modules/a.js'
        // console.log(xx)
        // setInterval(function () {
        //     console.log(xx)
        // }, 1000)



        // 第三种引入 引入默认值
        // import a from '../js/modules/a.js'
        // console.log(a)

2.一些webpack的指令

// 1.文件的作用:
// package.json     ===》记录你所需要的模块(需要下载什么)
//                      ·可以在此文件并行的情况下运行到终端 去 npm install   就可以下载你所有需要的包 并不需要单个下载   
//                      ·此文件怎么出来呢    通过使用  npm init
// package-lock.json===> 记录你所需要的模块的具体信息
// node_modules     ===》下载后的包都放在此处

// 2.想使用webpack工具就要下载webpack模块
// npm install webpack webpack-cli -g

// 3.想要打包并加载sass的文件就需要先下载sass-loader sass 还要将style-loader css-loader都下载
// npm install sass-loader sass webpack --save-dev

// 4.代理服务器解决跨域问题
// 下载插件
// webpack-dev-server
// 命令
// npm install webpack-dev-server -D

// 运行服务器
// webpack-dev-server()

// webpack和webpack-dev-server的区别
// webpack==>真正的生成bundle.js和我所需要的index.html
// webpack-dev-server==》将我所需要的文件以缓存的形式带到浏览器

// 5. 下载完毕后运行后会找不到我们的index.html
// ·怎么找到我们的index.html
// ·需要下载配置HtmlWebpackPlugin,让webpack编译的时候自动将我们的index.html作为模板带到dist中
// HtmlWebpackPlugin
//     命令:npm install --save-dev html-webpack-plugin
// 还需要将htmlWebpackServer引入到配置文件中,并进行设置
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 6.怎么将一些高版本的代码让ie11所解析 即让Babel去转译js文件
// 需要下载babel-loader
// npm install -D babel-loader @babel/core @babel/preset-env webpack

// 7.怎么样配置访问路径呢
// 服务器配置
/* devServer: {
    // 配置代理
    proxy: {
        // 将指定的请求转发到目标服务器
        "/upload": "http://192.168.1.117:3001"
    }
} */


// 8.设置自动更新,除非更改webpack.config.js需要重新运行,其他时候都不需要重新运行
// hot:true

// 运行直接写 webpack他会自己去找此文件

// 9.转译时将webpack5降到webpack4并且不使用箭头函数
# 还需要下载包label-loader
environment: {
            // 该配置项用于设置打包后的webpack的模块模拟方式:不使用箭头函数
            arrowFunction: false
        }
// 10. css sass 图片配置
    module: {
        rules: [
        {
            test: /\.css$/i,
            use: ["style-loader", "css-loader"],
        },
        {
            test: /\.s[ac]ss$/i,
            use: [
              // 将 JS 字符串生成为 style 节点
              'style-loader',
              // 将 CSS 转化成 CommonJS 模块
              'css-loader',
              // 将 Sass 编译成 CSS
              'sass-loader',
            ],
        },
        {
            test: /\.m?js$/,
            exclude: /(node_modules|bower_components)/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: ['@babel/preset-env']
              }
            }
        },
        {
            test: /\.(png|svg|jpg|jpeg|gif)$/i,
            type: 'asset/resource',
        },
     ],
    },
// 11. 怎么将我index.html设置为模板并生成新的index.html
     plugins: [new HtmlWebpackPlugin({
        template:'./index.html'
    })],
// 12. 怎么将服务器自动重启
    devServer: {
        proxy: {
          '/upload': 'http://localhost:3000',
        },
        hot: true,
    },

3.TS

# 1.如何自动生成tsconfig.json
    · 先下载typescript npm i typescript -g
    · 在总文件夹下运行   tsc --init
# 2. tsconfig.json 怎么配置才能在相应的位置自动生成ts的js
    · 生成的js文件向那个文件夹放
    · "outDir": "./dist",   
    · 根据那个文件去生成,他的根目录文件夹在哪
    · "rootDir": "./src",   
# 3. 运行指令
    · tsc

ts的基本类型

/*
 * @Descripttion: 
 * @Version: 
 * @Author: sun
 * @Date: 2022-12-22 16:36:11
 * @LastEditors: Please set LastEditors
 * @LastEditTime: 2022-12-23 11:43:09
 */
var str: string = '';
var num: number = 123;
var nul: null = null;
var undef: undefined = undefined;
var bool: boolean = true;

// []
// 数组定义方式1
var a: number[] = [12, 3];
var b: string[] = ['s'];
// 数组定义方式2
var c: Array = [1];

// 元组tuple 特殊数组
var d: [string, number] = ['1', 1];

// enum 枚举
enum Color {
    张三 = 2,//可以随意更改索引值
    李四,
    王五,
    赵六

}
console.log(Color['张三']);
console.log(Color[0]);

// any
// 当我们不确定的时候就可以使用any类型
// 不过不建议时常使用,否则就会变成any script
// 其执行机制就是,他本省有一个类型推断机制,如果不写类型的话,只要你给他赋值,那就是什么类型,这样达不到我们想让他是什么类型就是什么类型的想法
// 想实现只能用any ,意思就是不用管我是什么类型的数据,就像等于普通的js
var e: any = 's';
e = 1;

// void 
// 其在普通的html中,不想使用a超链接跳转的时候就会使用
// 
// 而在ts中的意思就是 我没有返回值
// 一般是在函数中使用
/* function fun(f1:string,f2:number)在此处写返回值类型{

} */
function fun1(f1: number, f2: number): number {
    return f1 + f2;
}
console.log(fun1(1, 2));

// nerve
// 抛出异常
throw new Error('运行出错,请认真检查!')

ts的联合类型

/*
 * @Descripttion: 
 * @Version: 
 * @Author: sun
 * @Date: 2022-12-23 09:24:46
 * @LastEditors: 
 * @LastEditTime: 2022-12-23 11:09:30
 */
// 联合类型就是定义多属性
function fun(f1:string|number):string|number|boolean{
    // 这里的方法只能是两者都有的不能使用自己特有的
    // 想要使用自己特有的要声明属于什么类型 即类型缩小、类型判断
    if(typeof f1 === 'string' ){
        // 这里就可以使用string 特有的一些方法
        f1.replace('<','');
    }
    // return !fun;
    // return f1;
    return 0;
}
fun('');
fun(0);
// 这样就不行
// fun(true);

ts定义类型

/*
 * @Descripttion: 
 * @Version: 
 * @Author: sun
 * @Date: 2022-12-23 11:10:51
 * @LastEditors: Please set LastEditors
 * @LastEditTime: 2022-12-23 16:27:23
 */
// ts有关键字可以定义自己的类型
// 1. type
// 2. interface接口

// 1.
type Animal = {
    name:string,
    age:number,
    sex:string,
    type:string,
    // 属性签名:可以允许在 符合上述属性之后再增加新的属性
    [prop:string]:any 
}
let animal:Animal = {
    name : '哈',
    age : 10,
    sex : '雄',
    type : '哈士奇',
    // 我类型中有什么属性,我下面就要有什么属性,不可以增减,那么想要定义比上述类型多的
    // 就要在上述类型中多谢个
    color : '白',
    fly:'飞'
}

// 2.
interface Animals1  {
    name:string,
    age:number,
    sex:string,
    type:string,
    // 属性签名:可以允许在 符合上述属性之后再增加新的属性
    [prop:string]:any 
}

let animals1:Animals1 = {
    name : '哈',
    age : 10,
    sex : '雄',
    type : '哈士奇',
    // 我类型中有什么属性,我下面就要有什么属性,不可以增减,那么想要定义比上述类型多的
    // 就要在上述类型中多谢个
    color : '白',
    fly:'飞'
}

面向对象

/*
 * @Descripttion: 
 * @Version: 
 * @Author: sun
 * @Date: 2022-12-23 11:22:00
 * @LastEditors: Please set LastEditors
 * @LastEditTime: 2022-12-23 11:53:49
 */
/* class Dog{
    constructor(name:string,age:number,sex:string ){
        // 怎么解决我Dog中不存在name呢,解决方法有两种
        this.name = name
    }
} */

// 1.
class Dog1 {
    // 在这里提前声明
    name: string;
    age: number;
    sex: string;


    constructor(name: string, age: number, sex: string) {
        // 怎么解决我Dog中不存在name呢,解决方法有两种
        this.name = name,
            this.age = age,
            this.sex = sex
    }
}
let dog1 = new Dog1('小美', 10, '雌');

// 2.
class Dog2 {
    // 2.可以在属性钱加修饰关键字 
    // 修饰关键字有:
    // ==》public 共有的
    // ==》在哪都可以访问
    // ==》protected 受保护的
    // ==》只可以在函数体本省才可以访问,使用,可以被继承给子代
    // ==》private   私有的
    // ==》只可以在函数体本省才可以访问,使用,不可以被继承给子代
    constructor(public name: string, protected age: number, private sex: string) {
        // 怎么解决我Dog中不存在name呢,解决方法有两种
        this.name = name,
            this.age = age,
            this.sex = sex
    }
    sayHello() {
        console.log(this.age);
        console.log(this.sex);
    }
}
let dog2 = new Dog2('小美', 10, '雌');
console.log(dog2.name);
// console.log(dog2.age);
// console.log(dog2.sex);



// ===>继承

// 哈士奇
class Huskie extends Dog2 {
    constructor(public name: string, protected age: number, private color: string) {

        super(name,age,'雌');
    }
    sayHello(): void {
        super.sayHello();
        console.log(this.color);
        
    }
}
let ha1 = new Huskie('小花', 10, '白色');
console.log(ha1);
ha1.sayHello();

接口

/*
 * @Descripttion: 
 * @Version: 
 * @Author: sun
 * @Date: 2022-12-23 11:54:31
 * @LastEditors: Please set LastEditors
 * @LastEditTime: 2022-12-23 12:02:01
 */
// 接口是用来定义方法,然后去实现的
class Animals{
    constructor(public name: string, protected age1: number, private sex1: string){
        this.name = name,
        this.age1 = age1,
        this.sex1 = sex1
    }
}
interface Run{
    run():void;
}
class Monkey extends Animals implements Run{
    constructor(public name: string, protected age1: number, private color: string){
        super(name,age1,'公')
    }
    run(): void {
        console.log('跑起来!');
        
    }
}
let monkey1 = new Monkey('小猴子',10,'红色');
console.log(monkey1);
monkey1.run();

抽象类

/*
 * @Descripttion: 
 * @Version: 
 * @Author: sun
 * @Date: 2022-12-23 16:14:34
 * @LastEditors: 
 * @LastEditTime: 2022-12-23 16:26:19
 */
// 函数有两种
// 1.普通函数
// 2.抽象函数:不可被实例化
// 

// 抽象关键字 abstract
// 类似于接口,但又不完全相同
// 接口interface中只能提前声明,并不能实现
// 抽象中是可以实现具体方法得
// 具体用法:被其他类继承
abstract class Cat{
    constructor(public name:string,public age:number,public color:string , public type:string){
        this.name = name;
        this.age = age;
        this.color = color;
        this.type = type;
    }
    miaow():void{
        console.log('喵喵喵');
    }
}
class lbld extends Cat{
    constructor(public name:string,public age:number,public color:string , public type:string){
        super(name,age,color,type);
    }
    miaow(): void {
        
        super.miaow();
    }
}
let cat  = new lbld('笑话',10,'白','拉布拉多');
console.log(cat);
cat.miaow();

装饰器

/*
 * @Descripttion: 
 * @Version: 
 * @Author: sun
 * @Date: 2022-12-23 16:29:03
 * @LastEditors: Please set LastEditors
 * @LastEditTime: 2022-12-23 16:39:51
 */
// m得意思就是将整个class Chicken作为参数传到函数haha里面所以Chicken.a可以获取到具体的值
function HaHa(m:any){
    m.a = '1'
}
@HaHa
class Chicken{
    constructor(public name:string,public color:string){
        this.name = name,
        this.color = color
    }

}
let h1 = new Chicken('鸡1','红色');
// 可以输出之正确的
// console.log(Chicken.a);

4.怎么才能既commonjs规则又esmoudle规则

// 只要将文件名改为以mjs为后缀即可

5.sass

// 1.设置变量在其前加$
$w:100px;

// 2.方法和混合
// 混合是@mixin关键字加名字在家{}
// 方法也就是在混合的基础上再加上小括号用来传值
// 1.
/* @mixin a{
    width: 100px;
    height: 100px;
    background-color: gray;
}
.div1{
    @include a;
    background-color: aqua
}
.div{
    // 关键字引入
    @include a;
    background-color: blue;
} */

// 2.方法
@mixin a($w,$h,$bc){
    width: $w;
    height: $h;
    background-color: $bc;
}
.a{
    @include a(100px,30px,skyblue);
}

// 3.继承

// 继承  @extend
.a{
    width: 100px;
    height: 100px;
    background-color: skyblue;
}
// b继承a的
.b{
    @extend .a;

    background-color: purple;
}

// 4.

6.管理异步的generator和async





    
    
    
    Document


    
    
    





    
    
    
    Document


    
    
    

async






    
    
    
    Document



    
    


其他知识点

1.获取对象的属性





    
    
    
    Document


    
    

2.图片预览




    
    
    
    Document


    
    
    

React

简介

React 是一个用于构建用户界面的 JavaScript 库。

Create React App

Create React App 是一个用于学习 React 的舒适环境,也是用 React 创建新的单页应用的最佳方式。

环境要求:Node >= 8.10 和 npm >= 5.6

要创建项目,请执行:

npm install create-react-app 
npx create-react-app my-app
cd my-app
npm start

Create React App 不会处理后端逻辑或操纵数据库;它只是创建一个前端构建流水线(build pipeline),所以你可以使用它来配合任何你想使用的后端。它在内部使用 Babel 和 webpack,但你无需了解它们的任何细节。

目录结构

自定义Create React App

下载模块

npm install customize-cra react-app-rewired -D

创建 config-overrides.js

const { override, addDecoratorsLegacy, disableEsLint, addWebpackPlugin } = require("customize-cra");
​
module.exports = override(
  addDecoratorsLegacy(),
  disableEsLint(),
  // 手动新增ModuleFederationPlugin插件
  addWebpackPlugin(
    new ModuleFederationPlugin({
      name: "ccomponent",
      filename: "ccomponent.js",
      exposes: {
        "./button": "./src/components/button",
      },
      shared: ["react", "react-dom"],
    })
  )
);

替换package.json里面的scripts命令

原来的

"scripts": {
  "start": "react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test",
  "eject": "react-scripts eject"
}

现在的

"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test"
},

使用craco自定义Create React App

下载模块

npm i @craco/craco -D

修改配置

原来的

"scripts": {
  "start": "react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test",
  "eject": "react-scripts eject"
}

现在的

"scripts": {
  "start": "craco start",
  "build": "craco build",
  "test": "craco test"
}

创建配置文件

const path = require('path')
module.exports = {
    webpack: {
        alias: {
            "@": path.resolve('./src')
        },
        resolve: {
            extensions: ['.js', '.jsx', '.ts', '.tsx', "..."],
        },
    },
    babel: {}
}

CNPM

CNPM是一个Node模块管理器。

安装

npm install cnpm -g 

使用

cnpm install express 

自己搭建脚手架

# 1 找一个空文件夹 
npm init
# 2 下载所需模块
npm install react react-dom -S
npm install webpack webpack-cli webpack-dev-server   -D
npm install babel-loader@8 @babel/core @babel/preset-env @babel/preset-react -D
npm install html-webpack-plugin -D
npm i sass-loader css-loader style-loader sass -D 

配置文件

webpack.config.js

var path = require('path');
var htmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    mode: "development",
    entry: path.resolve(__dirname, './index.js'),
    output: {
        path: path.resolve(__dirname, './dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env', "@babel/preset-react"]
                    }
                }
            }
        ]
    },
    plugins: [
        new htmlWebpackPlugin({
            filename: "index.html", // 打包之后的文件名字
            template: path.resolve(__dirname, './public/index.html'), // 打包之前的html文件
            inject: 'body' // 把webpack打包之后的js文件放在哪里
        })
    ]
}

JSX

JSX,是一个 JavaScript 的语法扩展。

function App() {
    return 
你好
}

上面的这个函数返回的部分就是JSX。

插值语法

{}

{} 就是JSX中的插值语法,里面是一个JS环境。里面可以书写插值、变量、表达式、调用方法等。

绑定事件

{ ... }}>

本质

JSX的本质就是 createElement 的语法糖

import React, { Component, createElement } from 'react';
console.log(React)
class Index extends Component { 
    render() { 
        // return 
你好
// 这两条代码等效 return createElement("div", {id: 'box'}, "你好") } } export default Index

样式设置

方式一

第一种样式修改方式 行内样式

方式二

第二种行内样式,引入外部的样式文件,通过设置className添加样式

方式三

// 安装
npm install classnames -S 
// 在组件内
import classNames from 'classnames'
安装一个classnames插件

方式四

// 安装
npm i styled-components -S

// 定义一个样式文件
import styled from 'styled-components';
const HelloWrapper = styled.div `
    color: ${props => props.color};
    font-size: 50px;
`
export {
    HelloWrapper
};

// 组件内
import { HelloWrapper } from './styledIndex';
通过styled-components插件来完成

Class组件

定义

import React, { Component, createElement } from 'react';
console.log(React)
class Index extends Component {

    // 实现render方法
    render() {
        // 返回jsx 或者 createElement所创建的元素
        return 
你好
// return createElement("div", {id: 'box'}, "你好") } } export default Index

状态

定义状态

状态(state):是class的一个属性,该属性用于存放该组件自己的数据.

import React, { Component, Fragment } from 'react'

export default class Index extends Component {

    // 状态是class组件中用于存放自身数据的属性
    state = {
        count: 0
    }

    // 定义一个方法
    // clickHandler() {
    //     // 因为当前的定义方式所定义的方法是function函数 function函数的this看调用
    //     // 当点击的时候 是这个函数自己执行 this将不会指向当前组件
    //     console.log(this)
    // }

    clickHandler = () => {
        // React不像Vue 数据更新视图自动变化
        // this.state.count++;
        // React提供了一个方法 setState() 

        // setState是一个触发react视图渲染的方法,react视图渲染是异步
        // this.setState({
        //     count: this.state.count + 1
        // })
        // this.setState({
        //     count: this.state.count + 1
        // })
        // this.setState({
        //     count: this.state.count + 1
        // })


        // setState的第二个参数是渲染数据完毕之后的回调函数
        // this.setState({
        //     count: this.state.count + 1
        // }, function() {
        //     // 渲染完毕之后的回调函数
        //     console.log(this.state.count)
        // })


        // setState的第一个参数也可以是一个函数 函数里面可以拿到状态和属性
        // this.setState(function (state, props) {
        //     // state是当前组件的状态对象
        //     // props是当前组件的属性对象
        //     return {
        //         count: state.count + 1
        //     }
        // })

    }

    render() {
        return (
            
Index
{this.state.count}
) } }

定义方法的时候,方法名称可以自定义。但是需要注意定义方式,因为this的指向问题,最好使用箭头函数。

修改状态

setState(): 是触发视图渲染的方法,从Component类继承而来。

  1. setState(obj)

  2. setState(fun)

  3. setState(fun, callback)

// 第一种
this.setState({
    count: this.state.count + 1
})

// 第二种
this.setState(function (state, props) {
    // state是当前组件的状态对象
    // props是当前组件的属性对象
    return {
        count: state.count + 1
    }
})

// 第三种
this.setState({
    count: this.state.count + 1
}, function() {
    // 渲染完毕之后的回调函数 这里拿到的数据是更新之后的数据
    console.log(this.state.count)
})

react中的视图渲染是异步操作

forceUpdate(): 是强制更新页面

强制修改状态

默认情况下,当组件的 state 或 props 发生变化时,组件将重新渲染。如果 render() 方法依赖于其他数据,则可以调用 forceUpdate() 强制让组件重新渲染。

通常你应该避免使用 forceUpdate(),尽量在 render() 中使用 this.propsthis.state

// 应该尽可能少的使用
this.forceUpdate(); 

属性

当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。

举例:

使用组件


组件的定义

import React, { Component } from 'react' 
export default class Child extends Component {
    // 构造函数一般用不着 所以可以不写
    // constructor(props) {
    //     super(props)
    // }
    // this.props是获取属性的方式 
    // 有哪些属性由外部父组件传递的 
    // 属性的类型在没有约束的情况下 可以传递任意数据
 
    render() {
        return (
            
接收父组件传递过来的数据 { // 使用属性 this.props.a }
) } }

属性的只读性:属性是只读的,不允许在组件内部修改属性值。

属性的验证

自 React v15.5 起,React.PropTypes 已移入另一个包中。请使用 prop-types 库 代替。

下载prop-types库

npm install prop-types -S

定义验证规则

import React, { Component } from 'react'
import PropTypes from 'prop-types'; 
console.log(PropTypes) // 查看有哪些类型
export default class Child extends Component { 
    // 属性验证
    static propTypes = {
        a: PropTypes.string,
        children: PropTypes.arrayOf(function (propValue, key, componentName, location, propFullName) {
            console.log(propValue, key, componentName, location, propFullName)
            if (propValue[key].age <= 18) {
                throw new Error('该数组的第' + (key + 1) + '个值年龄不足18岁')
            }
        })
    }

    render() {
        return (
            
接收父组件传递过来的数据 { this.props.a } { this.props.child }
) } }

属性默认值

import React, { Component } from 'react'
import PropTypes from 'prop-types';

console.log(PropTypes)
export default class Child extends Component {
    //  我们可以给属性添加一些默认值 这一般用于 props 未赋值,但又不能为 null 的情况
    static defaultProps = {
        a: 10,
        onChange: function() {
            console.log("hahhaa")
        }
    }

    render() {
        return (
            
接收父组件传递过来的数据 { this.props.a }
) } }

特殊属性children

需特别注意,this.props.children 是一个特殊的 prop,通常由 JSX 表达式中的子组件组成,而非组件本身定义。

父组件

import React, { Component } from 'react'
import Child from './Child'


export default class Index extends Component {
    state = {
        style: { fontSize: "20px", width: "200px", height: 400, background: "red" }
    }
    render() {
        return (
            
父组件给子组件传递属性
游戏王
// 对应子组件中的children[0]
战斧
// 对应子组件中的children[1]
拳皇
// 对应子组件中的children[2]
) } }

子组件

import React, { Component } from 'react'
export default class Child extends Component {
    render() {
        return (
            
我是一个子组件
{ this.props.children[0] //
游戏王
}

{ this.props.children[1] //
战斧
}

{ this.props.children[2] //
拳皇
}
) } }

条件渲染

 {this.state.name === "home" && } 

如果 this.state.name 为 'home' 则渲染 Home 组件

列表渲染

import React, { Component } from 'react'

export default class Index extends Component {
    state = {
        arr: [
            "张三", "李四", "王五", "赵六"
        ]
    }
    render() {
        // 列表渲染  
        return (
            
{ this.state.arr.map((value, index) => { return
{ console.log(e) }} key={value}>{value}
}) }
) } }

受控组件

import React, { Component, createRef } from 'react'
/* 所谓的受控组件 指的是在书写input textarea等这样的表单组件的时候 一旦设置了value属性 则必须要加onChange这样的函数 状态受到控制的目的 */


/* createRef() 这个函数的作用是创建一个ref属性 这个函数返回值是一个对象 对象有一个current属性 */


export default class Controlled extends Component {
    state = {
        name: "你好",
        age:13,
    }
    constructor() {
        super();
        this.sex = createRef(); // this.sex = {current: null}
    }
    changeHandler = (e) => {
        this.setState({
            name: e.target.value
        })
    }
    changeAge = (e) => {
        this.setState({
            age: e.target.value
        })
    }
    changeSex = () => {
        this.setState({
            sex: this.sex.current.value
        })
    }
    render() {
        return (
            

受控组件

{this.state.name}

非受控组件

{this.state.age}

非受控组件

{this.state.sex}
) } }

生命周期钩子#

挂载期

constructor(props)

如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。

在 React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前前调用 super(props)。否则,this.props 在构造函数中可能会出现未定义的 bug。

通常,在 React 中,构造函数仅用于以下两种情况:

  • 通过给 this.state 赋值对象来初始化内部 state。

  • 为事件处理函数绑定实例

constructor() 函数中不要调用 setState() 方法。如果你的组件需要使用内部 state,请直接在构造函数中为 this.state 赋值初始 state

constructor(props) {
  super(props);
  // 不要在这里调用 this.setState()
  this.state = { counter: 0 };
  this.handleClick = this.handleClick.bind(this);
}

只能在构造函数中直接为 this.state 赋值。如需在其他方法中赋值,你应使用 this.setState() 替代。

要避免在构造函数中引入任何副作用或订阅。如遇到此场景,请将对应的操作放置在 componentDidMount 中。

注意: 避免将 props 的值复制给 state!这是一个常见的错误:

constructor(props) {
super(props);
// 不要这样做
this.state = { color: props.color };
}

如此做毫无必要(你可以直接使用 this.props.color),同时还产生了 bug(更新 prop 中的 color 时,并不会影响 state)。

只有在你刻意忽略 prop 更新的情况下使用。此时,应将 prop 重命名为 initialColordefaultColor。必要时,你可以修改它的 key,以强制“重置”其内部 state。

请参阅关于避免派生状态的博文,以了解出现 state 依赖 props 的情况该如何处理。

static getDerivedStateFromProps(props, state)

getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props。例如,实现 `` 组件可能很方便,该组件会比较当前组件与下一组件,以决定针对哪些组件进行转场动画。

派生状态会导致代码冗余,并使组件难以维护。 确保你已熟悉这些简单的替代方案:

  • 如果你需要执行副作用(例如,数据提取或动画)以响应 props 中的更改,请改用 componentDidUpdate。

  • 如果只想在 prop 更改时重新计算某些数据,请使用 memoization helper 代替。

  • 如果你想在 prop 更改时“重置”某些 state,请考虑使组件完全受控或使用 key 使组件完全不受控 代替。

此方法无权访问组件实例。如果你需要,可以通过提取组件 props 的纯函数及 class 之外的状态,在getDerivedStateFromProps()和其他 class 方法之间重用代码。

请注意,不管原因是什么,都会在每次渲染前触发此方法。这与 UNSAFE_componentWillReceiveProps 形成对比,后者仅在父组件重新渲染时触发,而不是在内部调用 setState 时。

render()

render() 方法是 class 组件中唯一必须实现的方法。

render 被调用时,它会检查 this.propsthis.state 的变化并返回以下类型之一:

  • React 元素。通常通过 JSX 创建。例如,会被 React 渲染为 DOM 节点, 会被 React 渲染为自定义组件,无论是 还是 均为 React 元素。

  • 数组或 fragments。 使得 render 方法可以返回多个元素。欲了解更多详细信息,请参阅 fragments 文档。

  • Portals。可以渲染子节点到不同的 DOM 子树中。欲了解更多详细信息,请参阅有关 portals 的文档

  • 字符串或数值类型。它们在 DOM 中会被渲染为文本节点

  • 布尔类型或 null。什么都不渲染。(主要用于支持返回 test && 的模式,其中 test 为布尔类型。)

render() 函数应该为纯函数,这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。

如需与浏览器进行交互,请在 componentDidMount() 或其他生命周期方法中执行你的操作。保持 render() 为纯函数,可以使组件更容易思考。

注意, 如果 shouldComponentUpdate() 返回 false,则不会调用 render()

componentDidMount()

componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。

这个方法是比较适合添加订阅的地方。如果添加了订阅,请不要忘记在 componentWillUnmount() 里取消订阅

你可以在 componentDidMount()直接调用 setState()。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在 render() 两次调用的情况下,用户也不会看到中间状态。请谨慎使用该模式,因为它会导致性能问题。通常,你应该在 constructor() 中初始化 state。如果你的渲染依赖于 DOM 节点的大小或位置,比如实现 modals 和 tooltips 等情况下,你可以使用此方式处理

更新期

shouldComponentUpdate(nextProps, nextState)

根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染。大部分情况下,你应该遵循默认行为。

当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。首次渲染或使用 forceUpdate() 时不会调用该方法。

此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。你应该考虑使用内置的 PureComponent 组件,而不是手动编写 shouldComponentUpdate()PureComponent 会对 props 和 state 进行浅层比较,并减少了跳过必要更新的可能性。

如果你一定要手动编写此函数,可以将 this.propsnextProps 以及 this.statenextState 进行比较,并返回 false 以告知 React 可以跳过更新。请注意,返回 false 并不会阻止子组件在 state 更改时重新渲染。

我们不建议在 shouldComponentUpdate() 中进行深层比较或使用 JSON.stringify()。这样非常影响效率,且会损害性能。

目前,如果 shouldComponentUpdate() 返回 false,则不会调用 UNSAFE_componentWillUpdate(),render() 和 componentDidUpdate()。后续版本,React 可能会将 shouldComponentUpdate 视为提示而不是严格的指令,并且,当返回 false 时,仍可能导致组件重新渲染。

render()

同上

getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()

此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。

应返回 snapshot 的值(或 null)。

例如:

class ScrollingList extends React.Component {
  constructor(props) {
    super(props);
    this.listRef = React.createRef();
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // 我们是否在 list 中添加新的 items ?
    // 捕获滚动位置以便我们稍后调整滚动位置。
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
    // 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
    //(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
    if (snapshot !== null) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      
{/* ...contents... */}
); } }

在上述示例中,重点是从 getSnapshotBeforeUpdate 读取 scrollHeight 属性,因为 “render” 阶段生命周期(如 render)和 “commit” 阶段生命周期(如 getSnapshotBeforeUpdatecomponentDidUpdate)之间可能存在延迟。

componentDidUpdate(prevProps, prevState, snapshot)

componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。

当组件更新后,可以在此处对 DOM 进行操作。如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。(例如,当 props 未发生变化时,则不会执行网络请求)。

componentDidUpdate(prevProps) {
  // 典型用法(不要忘记比较 props):
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}

你也可以在 componentDidUpdate()直接调用 setState(),但请注意它必须被包裹在一个条件语句里,正如上述的例子那样进行处理,否则会导致死循环。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。不要将 props “镜像”给 state,请考虑直接使用 props。 欲了解更多有关内容,请参阅为什么 props 复制给 state 会产生 bug。

如果组件实现了 getSnapshotBeforeUpdate() 生命周期(不常用),则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。

注意

如果 shouldComponentUpdate() 返回值为 false,则不会调用 componentDidUpdate()

卸载期

componentWillUnmount()

componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

componentWillUnmount()不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。

context

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但此种用法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。

使用 context, 我们可以避免通过中间元素传递 props。

createContext

import React from 'react';
const MyContext = React.createContext(defaultValue);

// 或者
import {createContext} from 'react';
const MyContext = createContext(defaultValue)

创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。

只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。此默认值有助于在不使用 Provider 包装组件的情况下对组件进行测试。注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。

contextType

挂载在 class 上的 contextType 属性可以赋值为由 React.createContext() 创建的 Context 对象。此属性可以让你使用 this.context 来获取最近 Context 上的值。你可以在任何生命周期中访问到它,包括 render 函数中。

class MyClass extends React.Component {
  static contextType = MyContext;
  render() {
    let value = this.context;
    /* 基于这个值进行渲染工作 */
  }
}

Provider

import MyContext from './mycontext.js'


// 或者
import {Provider} from './mycontext.js'


每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。

Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。

当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。从 Provider 到其内部 consumer 组件(包括 .contextType 和 useContext)的传播不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件跳过更新的情况下也能更新。

通过新旧值检测来确定变化,使用了与 Object.is 相同的算法。

注意

当传递对象给 value 时,检测变化的方式会导致一些问题:详见注意事项。

Consumer

一个 React 组件可以订阅 context 的变更,此组件可以让你在函数式组件中可以订阅 context。

这种方法需要一个函数作为子元素(function as a child)。这个函数接收当前的 context 值,并返回一个 React 节点。传递给函数的 value 值等价于组件树上方离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等同于传递给 createContext()defaultValue


  {value => /* 基于 context 值进行渲染*/}

高阶组件

High Order Component 高等级组件 也就是高阶组件。

HOC 是纯函数,没有副作用。

  • 高阶组件就是一个函数.

  • 此函数接受一个组件作为参数.

  • 此函数返回一个组件.

hoc.js

import React, {Component} from 'react';

export default function(Comp) {
    return class extends Component {
        render() {
            return (
                
            )
        }
    }
}

index.jsx

/* 高阶组件 HOC   High Order Component */
// 指的是可以在组件上添加内容的函数
import React, { Component } from 'react'
import HOC from './Hoc';
@HOC 
class Index extends Component {
    render() {
        return (
            
你好 {this.props.title}
) } } export default Index; // 在webpack中使用装饰器 需要添加babel-loader的功能

Portal

Index.jsx

/* 我们的组件最终生成的所有的元素都渲染在#root内。像模态框这样的东西,一般是固定定位,最好是放在和#root平级的位置。 */

import React, { Component } from 'react'
import Dialog from './Dialog'
import { createPortal } from 'react-dom';
export default class Index extends Component {
    state = {
        show: false
    }
    showDialog = () => {
        this.setState({
            show: true
        })
    }
    clickHandler() {
        console.log("我是Index")
    }
    render() {
        return (
            

Index

{ (() => { if (this.state.show) { return } })() }
) } }

Dialog.jsx

import React, { Component } from 'react'
import { createPortal } from 'react-dom'
export default class Dialog extends Component {
    clickHandler() { 
        console.log('我是dialog组件')
    }
    render() {
        return (
            createPortal(
                
Dialog
, document.body ) ) } }

Redux

是什么

Redux 是一个使用叫做 “action” 的事件来管理和更新应用状态的模式和工具库 它以集中式 Store(centralized store)的方式对整个应用中使用的状态进行集中管理,其规则确保状态只能以可预测的方式更新。

为什么要用

Redux 帮你管理“全局”状态 - 应用程序中的很多组件都需要的状态。

Redux 提供的模式和工具使你更容易理解应用程序中的状态何时、何地、为什么、state 如何被更新,以及当这些更改发生时你的应用程序逻辑将如何表现

安装

npm install redux

state

{
  todos: [{
    text: 'Eat food',
    completed: true
  }, {
    text: 'Exercise',
    completed: false
  }],
  visibilityFilter: 'SHOW_COMPLETED'
}

这个对象就像 “Model”,区别是它并没有 setter(修改器方法)。因此其它的代码不能随意修改它,以避免造成难以复现的 bug。

action

规则:要想更新 state 中的数据,你需要发起一个 action。

action 就是一个普通 JavaScript 对象(注意到没,并没有什么魔法?)用来描述发生了什么。下面是一些 action 的示例:

{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }

reducer

强制使用 action 来描述所有变化可以让我们清晰地知道应用中到底发生了什么。如果一些东西改变了,就可以知道为什么变。actions 就像是描述所发生事情的面包屑导航。 最终,我们开发一个函数将 action 和 state 联系起来,这个函数就是 reducer。reducer 只是一个接收 state 和 action作为其参数,并返回给应用新的 state 的函数。

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return state.concat([{ text: action.text, completed: false }])
    case 'TOGGLE_TODO':
      return state.map((todo, index) =>
        action.index === index
          ? { text: todo.text, completed: !todo.completed }
          : todo
      )
    default:
      return state
  }
}

使用

// 引入redux的全部内容
import * as redux from 'redux';
// 解构createStore
let { createStore } = redux;
// 通过createStore创建store 
let store = createStore(function(state = {}, action) {
    if (action.type === 'xxx') {
        console.log(action); // { type: xxx, text: '...'} 
        return {
            // xxx
        }
    } else if (action.type === 'yyy') {
        return {
            // yyy
        }
    }
    return state; 
})
// 导出store
export default store;

// 组件中使用
import store from '../../store'
// 获取数据
store.getState();
// 触发行为修改数据
store.dispatch({type: 'xxx', text: 'Go to swimming pool'})

createStore(reducer, [preloadedState], [enhancer])

创建一个包含程序完整 state 树的 Redux store 。 应用中应有且仅有一个 store。

参数

reducer (Function): 接收两个参数,分别是当前的 state 树和要处理的 action,返回新的 state 树。

[preloadedState] (any): 初始时的 state。你可以决定是否把服务端传来的 state 水合(hydrate)后传给它,或者从之前保存的用户会话中恢复一个传给它。如果你使用 combineReducers 创建 reducer,它必须是一个普通对象,与传入的 keys 保持同样的结构。否则,你可以自由传入任何 reducer 可理解的内容。

enhancer (Function): Store enhancer。你可以选择指定它以使用第三方功能,如middleware、时间旅行、持久化来增强 store。Redux 中唯一内置的 store enhander 是 applyMiddleware()。

返回值 (Store): 保存了应用程序所有 state 的对象。改变 state 的惟一方法是 dispatch action。你也可以 subscribe state 的变化,然后更新 UI。

// 1 引入redux
import * as redux from 'redux';
// 2 解构createStore(reducer)
let { createStore } = redux;
// 3 调用createStore创建store
let store = createStore(function (state = { count: 0 }, action) {
    if (action.type === "add") {
        if (action.num) {
            return {
                count: state.count + action.num
            }
        } else {
            return {
            count: state.count + 1
        }
        }
    } else if (action.type === "bdd") {
        return {
            count: state.count - 1
        }
    } else if (action.type === "jiashi") {
        return {
            count: state.count + 10
        }
    } else {
        return state;
    }
});
export default store;

getState()

返回应用当前的 state 树。

store.getState(); // {count: 0}

dispatch(action)

将使用当前 getState() 的结果和传入的 action 以同步方式的调用 store 的 reducer 函数。它的返回值会被作为下一个 state。

store.getState(); // {count: 0}
store.dispatch({type:'add'}); // 再次强调 action就是一个对象 除了type是必需的 其余随意
store.getState(); // {count: 1}

applyMiddleware

redux自带的函数

作用:应用中间件

使用:包含自定义功能的 middleware 来扩展 Redux 是一种推荐的方式。

import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import * as reducers from './reducers'

const reducer = combineReducers(reducers)
// applyMiddleware 为 createStore 注入了 middleware: 从此 store.dispatch就可以接收一个函数
const store = createStore(reducer, applyMiddleware(thunk))


// 原来的store.dispatch
store.dispatch({
    type: "xxx",
    payload: "xxx"
})

// 现在的store.dispatch
store.dispatch((dispatch) => {
    setTimeout(function() {
        dispatch({
            type: "xxx",
            payload: "xxx"
        })
    }, 3000)
})

combineReducers

redux自带的函数

作用:将多个reducer函数合并成一个。

使用:接受一个对象,其中键名将成为根 state 对象中的键,值是描述如何更新 Redux 状态的 slice reducer 函数。

let reducer = function (state = { count: 0 }, action) {
    let name = "zhangsan";
    let arr = action.type.split('/');
    if (arr[0] === name) {
        if (arr[1] === "add") {
            if (action.num) {
                return {
                    count: state.count + action.num
                }
            } else {
                return {
                    count: state.count + 1
                }
            }
        } else if (arr[1] === "bdd") {
            return {
                count: state.count - 1
            }
        } else if (arr[1] === "jiashi") {
            return {
                count: state.count + 10
            }
        }
    }

    return state;
}

let reducer1 = function (state = { count: 0 }, action) {
    let name = "lisi";
    let name1 = action.type.split('/')[0];
    let type = action.type.split('/')[1];
    if (name === name1) {
        if (type === "add") {
            if (action.num) {
                return {
                    count: state.count + action.num
                }
            } else {
                return {
                    count: state.count + 1
                }
            }
        } else if (type === "bdd") {
            return {
                count: state.count - 1
            }
        } else if (type === "jiashi") {
            return {
                count: state.count + 10
            }
        }
    }
    return state;
}
// 把两个reducer合并
let newReducer = combineReducers({
    zhangsan: reducer,
    lisi: reducer1
})
                       // ↓↓↓
let store = createStore(newReducer, applyMiddleware(thunk));

redux-thunk(这个属于redux的插件)

安装

npm install redux-thunk -S 

使用

// redux本身只能dispatch同步行为  
// redux-thunk 是可以增强redux的dispatch方法。允许dispatch接收一个函数作为参数 在这个函数内可以做一些异步操作。等待异步操作结束,再去dispatch
import * as redux from 'redux';
import thunk from 'redux-thunk'; // 引入redux-thunk
let { createStore, applyMiddleware } = redux;
let store = createStore(reducer, applyMiddleware(thunk));

// 此时 参数可以是函数
store.dispatch((dispatch) => {
    // dispatch就是store.dispatch
    setTimeout(function() {
        dispatch({type: "...", payload: '...'})
    }, 2000)
})

redux-toolkit

介绍

redux-toolkit是redux官方推荐的工具,简称RTK。因为原生的redux写起来太麻烦。使用RTK可以让代码结构更加美观,优雅,方便维护。

安装

npm install @reduxjs/toolkit -S

使用

// Redux Tool Kit  简称 RTK
import { configureStore, createSlice } from '@reduxjs/toolkit';  
// createSlice方法用于创建一个'切片'  切片就是整合了状态、reducer、action的对象
let obj = createSlice({
    name: "zhangsan",
    initialState: {
        count: 0
    },
    reducers: {
        add: function(state) {
            // RTK中应用了一个插件 "immutable" 它可以实现直接修改的写法
            state.count += 1;
        },
        min: function(state) {
            state.count -= 1;
        }
    }
})

// 配置store
let store = configureStore({
    // 配置reducer就是将所有的切片整合到一起
    reducer: {
        // key是状态的属性名  
        zhangsan: obj.reducer,
    },
})
export default store; 

react-redux

介绍

react-redux是一个NodeJS第三方库。用于将redux与react结合使用。

安装

npm i react-redux -S

使用

// 第一步 在入口js文件中引入react-redux 并解构出Provider组件
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import { Provider } from 'react-redux'
import store from './src/views/20_react-redux/store'
var root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
    
        
    
)


// 第二步 在想要使用state的组件中 解构react-redux中的connect函数 并传递两个映射函数 完成状态和dispatch的融入
import React, { Component } from 'react' 
import { connect } from 'react-redux'  // 解构connect

class Index extends Component {
    clickHandler = () => {
        // 2.4 调用add方法  mapDispatchToProps提供的
        this.props.add(); 
    }
    render() {
        return (  
            
Index-{this.props.count} // 调用count mapStateToProps提供的
) } } // 2.1 定义方法 映射状态给属性 function mapStateToProps(state) { return { count: state.zhangsan.count } } // 2.2 定义方法 映射dispatch方法给属性 function mapDispatchToProps(dispatch) { return { add: function() { dispatch({type: "zhangsan/add"}) } } } // 2.3 通过connect方法将store的状态和dispatch方法交给当前组件的props export default connect(mapStateToProps, mapDispatchToProps)(Index)

路由

安装

npm install react-router-dom -S

使用

导入路由模块

// history路由
import {BrowserRouter} from 'react-router-dom'
// hash路由
import {HashRouter} from 'react-router-dom'

使用方式

注意React中的路由的使用方式

  1. 必须在外面使用上面的两个组件之一进行包裹

  2. 导入 Route 组件

  3. 在组件中使用Route

app.jsx

// 引入模块
import { HashRouter as Router } from 'react-router-dom'

// 包裹在最外侧
export default function() {
    return (
        
            
        
    )
}

MyComponent.jsx

// 引入模块
import {Route, Routes} from 'react-router-dom';

export default function() {
    return (
    	
// 在任何一个位置都可以添加 Routes 组件 // path就是路由的路径 element就是对应的组件 需要注意的是得用<>展开 否则File就只是一个函数 }> }> }>
    ...
) }

此时我们就可以在页面中输入

localhost:3000/#/ => 电影组件显示

localhost:3000/#/theater => 影院组件显示

localhost:3000/#/mine => 我的组件显示

来回切换试试看

跳转路由方式

编程式

// 1 引入模块
import { useNavigate } from 'react-router-dom'

// 2 调用方法得到跳转函数
let navigate = useNavigator();

// 3 使用该函数进行跳转
navigate("/xxx") // 绝对路径
navigate("xxx") // 相对路径
navigate("../xxx") // 相对路径
navigate(-1)   //  后退一步	

组件式

Link

to 用于决定跳转到的路由路径

// 1 引入模块
import { Link } from 'react-router-dom'

// 2 使用组件
export default function() {
    return (
        //... 其他内容
    	电影
		影院
		我的
        //...其他内容
    )
}

NavLink

to 字符串 用于决定跳转到的路由路径

className 回调函数 用于决定当前路由显示时的类名

style 回调函数 用于决定当前路由显示时的样式

通常className和style只用一个即可

 { return isActive ? "active" : '' }} to='/'>电影
 { return isActive ? "active" : '' }} to='/theater'>影院
 { return isActive ? "active" : '' }} to='/mine'>我的

路由重定向

}>
}>
}>
// 在路由的最后书写
}>

路由传参

动态路由方式

// 路由定义
}>

// 路由传参:组件式
影院

// 路由传参:编程式
navigate('/theater/longchenglu1/dashuaige1')

// 获取参数:在组件内
import { useParams } from 'react-router-dom';
let params = useParams();
console.log(params); // {id: 'longchenglu', username: 'shuaige'}

query字符串方式

// 路由定义


// 路由使用: 组件式


// 路由使用:编程式
navigate("/mine?a=1&b=2")

// 获取参数:在组件内
import { useSearchParams } from 'react-router-dom'
let params = useSearchParams()[0];

params.get("a"); // 1
params.get("b"); // 2

路由嵌套

第一种


    // 一级路由
    }> 
        // 二级路由   需要注意在Film组件中书写一个  
        }>
        }>
    
    }>
    }>

第二种

// Index.jsx

    //  如果当前路由下还有子路由 需要在当前路由的最后加一个/* 
    }>
    }>
    }>


// Film.jsx
Film
}> }>

其它

hooks名 作用 说明
useParams 返回当前参数 根据路径读取参数
useNavigate 返回当前路由 代替原有V5中的 useHistory
useOutlet 返回根据路由生成的element
useLocation 返回当前的location 对象
useRoutes 同Routers组件一样,只不过是在js中使用
useSearchParams 用来匹配URL中?后面的搜索参数

第二种路由配置

import React from 'react'
import {useRoutes,useLocation,Navigate} from 'react-router-dom'
import Admin from '../views/admin'
import {useSelector} from 'react-redux'


export default function IndexRouter() {
  const routes = useRoutes([
    {
      path:'/',
      element:,
      children:[
        {
          path:'',
          element:
        },
        {
          path:'/admin/dashboard',
          element:lazyload('dashboard')
        },
        {
          path:'/admin/article',
          element:lazyload('article')
        },
        {
          path:'/admin/article/edit/:id',
          element:lazyload('article/edit')
        },
        {
          path:'/admin/settings',
          element:lazyload('settings')
        },
        {
          path:'/admin/notifications',
          element:lazyload('notifications')
        }
      ]
    },
    {
      path:'/login',
      element:lazyload('login')
    },
    {
      path:'/notfound',
      element:lazyload('notfound')
    },
    {
      path:'*',
      element:
    }
  ])
  const isLogin = useSelector(state=>state.user.isLogin)
  const location = useLocation()
  if(!isLogin && location.pathname!=='/login'){ // 如果是没有登录,redux的isLogin就是false
    return 
  }
  return routes
}

// 路由懒加载的方式渲染组件
function lazyload(path){
  const Comp = React.lazy(()=>import('../views/'+path))
  return (
    loading...}>
      
    
  )
}

redux持久化

原生redux持久化

// 存储文件 store/index.js
import {applyMiddleware, legacy_createStore as createStore} from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
const persistConfig = {
  key: 'root',
  storage,
}
const persistedReducer = persistReducer(persistConfig, reducer) 
const store = createStore(persistedReducer,applyMiddleware(thunk))
let persistor = persistStore(store)
export  {store, persistor}

// 入口文件 index.js 
import {store,persistor} from './store'
import { PersistGate } from 'redux-persist/integration/react'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  
    
      
    
  
);

redux-toolkit持久化

// 数据存储文件 store/index.js
import { combineReducers } from 'redux'
import thunk from 'redux-thunk'
// 持久化存储
import { configureStore } from '@reduxjs/toolkit'
import { persistReducer, persistStore } from 'redux-persist'
import storage from 'redux-persist/es/storage'

import userInfoSlice from '@/store/slice/userInfoSlice'
import SimpleData from '@/store/slice/asyncSlice'
// 缓存数据配置
const persistConfig = {
    key: 'root',
    storage,
    blacklist: [ 'userInfoSlice', 'SimpleData' ] // 写在这块的数据不会存在storage
}

const reducers = combineReducers({
    userInfoSlice,
    SimpleData
})

const persistedReducer = persistReducer(persistConfig, reducers)

export const store = configureStore({
    reducer: persistedReducer,
    devTools: process.env.NODE_ENV !== 'production',
    middleware: [ thunk ]
})

export const persist = persistStore(store) // 数据持久化存储



// 入口文件 index.js
import { persist, store } from '@/store'
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react'
import {OutLet} from 'react-router-dom'

export default (props: { children: any }) => (
    
        
            
        
    
)

函数式组件

定义组件

import react from 'react';

function Welcome(props) {
  return 

Hello, {props.name}

; }

props代表当前组件的属性

使用组件


特点

函数式组件中没有定义state的方式。除非你使用了Hooks。

Hooks

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数

useState

useState是一个函数,可以从react模块中解构,用于创建“函数式组件中的state”

当调用它的时候可以传递一个参数,它的返回值是一个数组。

1. 第一个成员是你要使用的数据,也就是参数。
  1. 第二个成员是一个函数,该函数用于改变这个数据,并同时更新组件。 3. 该函数可以多次调用,创建多个状态。

let { useState } from 'react';
let [ value, change ] = useState(); 

useEffect

useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 API。

useEffect 做了什么?

通过使用这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作。

React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。

为什么在组件内部调用 useEffect

useEffect 放在组件内部让我们可以在 effect 中直接访问 count state 变量(或其他 props)。我们不需要特殊的 API 来读取它 —— 它已经保存在函数作用域中。Hook 使用了 JavaScript 的闭包机制,而不用在 JavaScript 已经提供了解决方案的情况下,还引入特定的 React API。

useEffect 会在每次渲染后都执行吗?

是的,默认情况下,它在第一次渲染之后每次更新之后都会执行。你可能会更容易接受 effect 发生在“渲染之后”这种概念,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。

提示: 与 componentDidMountcomponentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。

用法1: 接收一个函数作为参数,相当于componentDidMount 和 componentDidUpdate

useEffect(function () {
    console.log(123)
})

用法2:接收第二个参数,是一个依赖数组,该数组中的任何一项发生变化就会执行一次这个函数如果该数组中没有任何依赖 表示这个函数将只会在一开始执行一次 之后再也不会执行 相当于 componentDidMount

useEffect(function() {
	console.log(123)
}, [])

用法3:自定义useUpdate ,react中useEffect没有与componentDidUpdate相匹配的用法 所以需要我们自己定义

import { useEffect, useRef } from "react";
export default function useUpdate(fun) {
    const mounting = useRef(false);
    useEffect(() => {
        if (mounting.current) {
            fun()
        } else {
            mounting.current = true;
        }
    })
}

useContext

const value = useContext(MyContext);

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 MyContext.Providervalue prop 决定。

当组件上层最近的 MyContext.Provider 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即使祖先使用 React.memo 或 shouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。

调用了 useContext 的组件总会在 context 值变化时重新渲染。如果重渲染组件的开销较大,你可以 通过使用 memoization 来优化。

提示: 如果你在接触 Hook 前已经对 context API 比较熟悉,那应该可以理解,useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者

useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 来为下层组件提供 context。

useReducer

const [state, dispatch] = useReducer(reducer, defaultState)

第一个参数是reducer,第二个参数是state(可选),返回值是一个数组,数组中是state和dispatch

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)

在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

返回一个 memoized 回调函数。

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。

函数式组件可以理解为类式组件的render函数。每当组件重新渲染时,该函数会重新执行一次。定义在这个函数内的东西都会重新定义一次。

该函数用于让一个函数具备缓存性。

memo

const MyComponent = React.memo(function MyComponent(props) {
  /* 使用 props 渲染 */
});

React.memo 为高阶组件。

如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState,useReducer 或 useContext 的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。

默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

此方法仅作为性能优化的方式而存在。但请不要依赖它来“阻止”渲染,因为这会产生 bug。

useMemo

memo是一个高阶组件,是一个函数。 useMemo是一个钩子,是一个hook。用于实现计算属性。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

返回一个 memoized 值。

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行不应该在渲染期间内执行的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo

如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。将来,React 可能会选择“遗忘”以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo,以达到优化性能的目的。

useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。

本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。

你应该熟悉 ref 这一种访问 DOM 的主要方式。如果你将 ref 对象以 ` 形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的.current` 属性设置为相应的 DOM 节点。

然而,useRef()ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。

这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。

请记住,当 ref 对象内容发生变化时,useRef不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。

useImperativeHandle

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return ;
}
FancyInput = forwardRef(FancyInput);

在本例中,渲染 的父组件可以调用 inputRef.current.focus()。

useLayoutEffect

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

尽可能使用标准的 useEffect 以避免阻塞视觉更新。

提示: 如果你正在将代码从 class 组件迁移到使用 Hook 的函数组件,则需要注意 useLayoutEffectcomponentDidMountcomponentDidUpdate 的调用阶段是一样的。但是,我们推荐你一开始先用 useEffect,只有当它出问题的时候再尝试使用 useLayoutEffect

如果你使用服务端渲染,请记住,无论 useLayoutEffect 还是 useEffect 都无法在 Javascript 代码加载完成之前执行。这就是为什么在服务端渲染组件中引入 useLayoutEffect 代码时会触发 React 告警。解决这个问题,需要将代码逻辑移至 useEffect 中(如果首次渲染不需要这段逻辑的情况下),或是将该组件延迟到客户端渲染完成后再显示(如果直到 useLayoutEffect 执行之前 HTML 都显示错乱的情况下)。

若要从服务端渲染的 HTML 中排除依赖布局 effect 的组件,可以通过使用 showChild && 进行条件渲染,并使用 useEffect(() => { setShowChild(true); }, []) 延迟展示组件。这样,在客户端渲染完成之前,UI 就不会像之前那样显示错乱了。

Redux

理解Flux架构

在2013年,Facebook让React亮相的同时推出了Flux框架,React的初衷实际上是用来替代jQuery的,Flux实际上就可以用来替代Backbone.jsEmber.js等一系列MVC架构的前端JS框架。

其实FluxReact里的应用就类似于Vue中的Vuex的作用,但是在Vue中,Vue是完整的mvvm框架,而Vuex只是一个全局的插件。

React只是一个MVC中的V(视图层),只管页面中的渲染,一旦有数据管理的时候,React本身的能力就不足以支撑复杂组件结构的项目,在传统的MVC中,就需要用到Model和Controller。Facebook对于当时世面上的MVC框架并不满意,于是就有了Flux, 但Flux并不是一个MVC框架,他是一种新的思想。

  • View: 视图层

  • ActionCreator(动作创造者):视图层发出的消息(比如mouseClick)

  • Dispatcher(派发器):用来接收Actions、执行回调函数

  • Store(数据层):用来存放应用的状态,一旦发生变动,就提醒Views要更新页面

Flux的流程:

  1. 组件获取到store中保存的数据挂载在自己的状态上

  2. 用户产生了操作,调用actions的方法

  3. actions接收到了用户的操作,进行一系列的逻辑代码、异步操作

  4. 然后actions会创建出对应的action,action带有标识性的属性

  5. actions调用dispatcher的dispatch方法将action传递给dispatcher

  6. dispatcher接收到action并根据标识信息判断之后,调用store的更改数据的方法

  7. store的方法被调用后,更改状态,并触发自己的某一个事件

  8. store更改状态后事件被触发,该事件的处理程序会通知view去获取最新的数据

理解Redux单向数据流流程

React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两个方面,它没涉及。

  • 代码结构

  • 组件之间的通信

2013年 Facebook 提出了 Flux 架构的思想,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。

如果你不知道是否需要 Redux,那就是不需要它

只有遇到 React 实在解决不了的问题,你才需要 Redux

简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。

  • 用户的使用方式非常简单

  • 用户之间没有协作

  • 不需要与服务器大量交互,也没有使用 WebSocket

  • 视图层(View)只从单一来源获取数据

需要使用Redux的项目:

  • 用户的使用方式复杂

  • 不同身份的用户有不同的使用方式(比如普通用户和管理员)

  • 多个用户之间可以协作

  • 与服务器大量交互,或者使用了WebSocket

  • View要从多个来源获取数据

从组件层面考虑,什么样子的需要Redux:

  • 某个组件的状态,需要共享

  • 某个状态需要在任何地方都可以拿到

  • 一个组件需要改变全局状态

  • 一个组件需要改变另一个组件的状态

Redux的设计思想:

  1. Web 应用是一个状态机,视图与状态是一一对应的。

  2. 所有的状态,保存在一个对象里面(唯一数据源)。

注意:flux、redux都不是必须和react搭配使用的,因为flux和redux是完整的架构,在学习react的时候,只是将react的组件作为redux中的视图层去使用了。

redux使用三大原则

  • Single Source of Truth(唯一的数据源)

  • State is read-only(状态是只读的) - 无法直接修改状态

  • Changes are made with pure function(数据的改变必须通过纯函数完成)

$ cnpm i redux -S

src/13_redux/store.js 创建状态管理器

// createStore 方法在新版本中已经被弃用
import { createStore } from 'redux'
​
// 2.创建所有操作 reducer 以及定义初始化的状态 - 纯函数
// 第一个参数为管理的状态,需要在此处书写 初始的状态
const reducer = (state = {
  msg: 'hello redux',
  count: 10
}, action) => { // action中type代表动作的标识,用于触发行为,payload代表传递的参数
  switch (action.type) {
    case 'CAHNGE_MSG': // type的名字可以根据自己公司的需求进行更改
      // 必须返回最新的状态
      return { ...state, msg: action.payload }
    case 'INCREMENT_COUNT':
      return { ...state, count: state.count + action.payload }
    case 'DECREMENT_COUNT':
      return Object.assign({}, state, { count: state.count - action.payload })
    default:
      return state
  }
}
​
// 创建状态管理器
const store = createStore(reducer)
​
export default store

src/index.js 配置状态管理器 - 状态管理器修改订阅视图的更新

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
​
// 引入状态管理器
import store from './13_redux/store'
​
import App from './13_redux/App' 
​
console.log(store)
// dispatch 可以触发reducer的更改
// getState 可以用来获取状态管理器的数据
// replaceReducer 替换reducer
// subscribe 订阅数据的变化
​
const root = ReactDOM.createRoot(document.getElementById('root'))
​
// 标签形式调用
root.render(
  
)
​
// 订阅数据的变化,重新渲染视图
store.subscribe(() => {
  // 一旦检测到数据发生改变,此处代码就会执行
  root.render(
    
  )
})

src/13_redux/App.jsx使用状态管理器

import React from 'react';
import Child1 from './Child1';
import Child2 from './Child2';
const App = () => {
  return (
    
     

使用redux

           
         
); }; ​ export default App;

src/13_redux/Child1.jsx

import React from 'react';
import store from './store'
const Child1 = () => {
  const state = store.getState() // 获取状态管理器中的状态
  return (
    
     

child1

     
      { state.msg } - { state.count }                      
   
); }; ​ export default Child1;

src/13_redux/Child2.jsx

import React from 'react';
import store from './store'
const Child2 = () => {
  const state = store.getState() // 获取状态管理器中的状态
  return (
    
     

child2

    { state.msg } - { state.count }                  
); }; ​ export default Child2;

以上代码是最最最最最最最基本的redux的使用,现在企业里基本上不使用这种原始的方法

redux + react-redux配合使用

redux是属于js的状态管理模式,不独属于react,在react中需要结合 react-redux 模块使用

react-redux提供两个核心的api:

  • Provider: 提供store

  • connect: 用于连接容器组件和展示组件

    • Provider

      根据单一store原则 ,一般只会出现在整个应用程序的最顶层。

    • connect

      语法格式为

      connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)(component)

      一般来说只会用到前面两个,它的作用是:

      • store.getState()的状态转化为展示组件的props

      • actionCreators转化为展示组件props上的方法

只要上层中有Provider组件并且提供了store, 那么,子孙级别的任何组件,要想使用store里的状态,都可以通过connect方法进行连接。如果只是想连接actionCreators,可以第一个参数传递为null

$ cnpm i react-redux -S

创建状态管理器

src/14_redux-react_redux/store.js

import { createStore } from 'redux'
​
const reducer = (state = {
  proList: [],
  kindList: []
}, { type, payload }) => { // action  解构了 { type, payload }
  switch (type) {
    case 'CHANGE_PRO_LIST':
      return { ...state, proList: payload }
    case 'CHANGE_KIND_LIST':
      return { ...state, kindList: payload }
    default:
      return state
  }
}
​
const store = createStore(reducer)
​
export default store

1.引入 createStore 方法(新版本中建议使用rtk)

2.创建reducer(包含初始化状态,包含了修改状态的标识,返回了新的状态(对象合并))

3.创建store

4.暴露store

使用react-redux

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'

// 引入状态管理器
import { Provider } from 'react-redux' // 提供Provier组件,可以添加store属性
import store from './14_redux_react-redux/store'
import App from './14_redux_react-redux/App'

const root = ReactDOM.createRoot(document.getElementById('root'))

// 标签形式调用
root.render(
  
    
  
)

使用了 react-redux 提供的组件组件 Provider 配合 store 完成传递数据

src/14_redux_react-redux/App.jsx

import React from 'react';
import Home from './Home';
import Kind from './Kind';
const App = () => {
  return (
    

redux + react-redux


); }; export default App;

src/14_redux_react-redux/Home.jsx 业务逻辑写到了组件中

import React, { useEffect } from 'react';
// 引入 connect 高阶组件 实际上 connect() 返回值才是高阶组件
import { connect } from 'react-redux';

// 展示组件   ---- 理想情况:  负责数据的展示以及发起指令
const Home = (props) => {
  console.log(props) // { proList: [], dispatch: function()}
  const { proList, dispatch } = props
  useEffect(() => {
    // 在展示组件内 写业务逻辑
    fetch('http://121.89.205.189:3001/api/pro/list').then(res => res.json()).then(res => {
      console.log(res.data) 
      // 修改状态
      dispatch({
        type: 'CHANGE_PRO_LIST',
        payload: res.data
      })
    })
  }, [dispatch]) // 依赖项最好 不要直接使用props,先将props进行解构,后添加依赖项
  return (
    

home

    { proList && proList.map(item => { return (
  • { item.proname }
  • ) }) }
); }; // mapStateToProps 负责给展示组件提供数据,提供给展示组件的props属性 const mapStateToProps = (state) => { // state 参数即为管理的所有的状态 return { // 必须含有返回值,返回什么取决于你的展示组件需要什么数据 proList: state.proList } } // 返回的是一个 容器组件 ---- 理想情况: 负责给展示组件提供数据 以及执行 业务逻辑 export default connect(mapStateToProps)(Home);

src/14_redux_react-redux/Kind.jsx 业务逻辑写到容器组件中

import React, { useEffect } from 'react';
import { connect } from 'react-redux'
const Kind = (props) => {
  const { kindList, getKindListData } = props
  useEffect(() => {
    // 展示组件发起指令,后续交给容器组件处理
    getKindListData()
  }, [getKindListData])
  return (
    

kind

{ kindList && kindList.map(item => { return (

{ item }

) }) }
); }; const mapStateToProps = (state) => { return { kindList: state.kindList } } // 将业务逻辑交给容器组件组件处理 const mapDispatchToProps = (dispatch) => { // dispatch 为默认参数,可以通过此 触发 reducer 中状态的更新 return { getKindListData () { // 返回自定义函数,供 展示组件调用,展示组件通过 props 访问即可 fetch('http://121.89.205.189:3001/api/pro/categorylist').then(res => res.json()).then(res => { console.log(res.data) // 修改状态 dispatch({ type: 'CHANGE_KIND_LIST', payload: res.data }) }) } } } export default connect(mapStateToProps, mapDispatchToProps)(Kind);

vuex中 可以把异步操作提取到vuex中的actions中,react中异步操作方式会有很多,最常见的就是 redux-thunk

redux+react-redux分模块使用

如果项目很大,或者项目划分比较明确,需要将状态管理器也分模块去处理

假设现在有一个home模块,管理 bannerList 以及 proList

还有一个模块 kind 模块,管理 kindList

src/15_redux_react-redux_combine/store/modules/home.js

// 单独管理home模块的状态管理器
const reducer = (state = {
  bannerList: [],
  proList: []
}, { type, payload }) => {
  switch (type) {
    case 'CHANGE_BANNER_LIST':
      return { ...state, bannerList: payload }
    case 'CHANGE_PRO_LIST':
      return { ...state, proList: payload }
    default:
      return state
  }
}

export default reducer

src/15_redux_react-redux_combine/store/modules/kind.js

// 单独管理kind模块的状态管理器
const reducer = (state = {
  kindList: []
}, { type, payload }) => {
  switch (type) {
    case 'CHANGE_KIND_LIST':
      return { ...state, kindList: payload }
    default:
      return state
  }
}

export default reducer

整合多个reducer 为一个,因为 有一个原则 : 单一数据源 的原则

src/15_redux_react-redux_combine/store/index.js

import { combineReducers, createStore } from 'redux'
import home from './modules/home'
import kind from './modules/kind'

const reducer = combineReducers({
  home,
  kind
})

const store = createStore(reducer)

export default store

入口文件处引入状态管理器

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'

// 引入状态管理器
import { Provider } from 'react-redux' // 提供Provier组件,可以添加store属性
import store from './15_redux_react-redux_combine/store/index'
import App from './15_redux_react-redux_combine/App'

const root = ReactDOM.createRoot(document.getElementById('root'))

// 标签形式调用
root.render(
  
    
  
)

组件中使用状态管理器

src/15_redux_react-redux_combine/App.jsx

import React from 'react';
import Home from './views/Home'
import Kind from './views/Kind'
const App = () => {
  return (
    

); }; export default App;

src/15_redux_react-redux_combine/views/Home.jsx 业务逻辑在展示型组件中

import React, { useEffect } from 'react';
import { connect } from 'react-redux'
const Home = ({ bannerList, proList, dispatch }) => {

  useEffect(() => {
    fetch('http://121.89.205.189:3001/api/banner/list').then(res => res.json()).then(res => {
      dispatch({
        type: 'CHANGE_BANNER_LIST',
        payload: res.data
      })
    })
    fetch('http://121.89.205.189:3001/api/pro/list').then(res => res.json()).then(res => {
      dispatch({
        type: 'CHANGE_PRO_LIST',
        payload: res.data
      })
    })
  }, [dispatch])
  return (
    

Home

{ bannerList && bannerList.map(item => ( {item.alt} )) }
    { proList && proList.map(item => { return (
  • { item.proname }
  • ) }) }
); }; export default connect( // (state) => ({ bannerList: state.home.bannerList, proList: state.home.proList }) // ({ home }) => ({ bannerList: home.bannerList, proList: home.proList }) // ({ home: { bannerList, proList } }) => ({ bannerList: bannerList, proList: proList }) ({ home: { bannerList, proList } }) => ({ bannerList, proList }) )(Home);

src/15_redux_react-redux_combine/views/Kind.jsx 业务逻辑在容器组件中

import React from 'react';
import { useEffect } from 'react';
import { connect } from 'react-redux'
// const Kind = connect(
//   ({ kind: { kindList }}) => ({ kindList }), // state => { return { kindList: state.kind.kindList }}
//   (dispatch) => ({
//     getKindListData () {
//       fetch('http://121.89.205.189:3001/api/pro/categorylist').then(res => res.json()).then(res => {
//         dispatch({
//           type: 'CHANGE_KIND_LIST',
//           payload: res.data
//         })
//       })
//     }
//   })
// )(({ kindList, getKindListData }) => {
//   useEffect(() => {
//     getKindListData()
//   }, [getKindListData])
//   return (
//     
//

Kind

// { // kindList && kindList.map(item => { // return ( //

{ item }

// ) // }) // } //
// ); // }); // export default Kind; const Kind = ({ kindList, getKindListData }) => { useEffect(() => { getKindListData() }, [getKindListData]) return (

Kind

{ kindList && kindList.map(item => { return (

{ item }

) }) }
); }; export default connect( ({ kind: { kindList }}) => ({ kindList }), // state => { return { kindList: state.kind.kindList }} (dispatch) => ({ getKindListData () { fetch('http://121.89.205.189:3001/api/pro/categorylist').then(res => res.json()).then(res => { dispatch({ type: 'CHANGE_KIND_LIST', payload: res.data }) }) } }) )(Kind);

细心的你发现,虽然展示组件的代码变少了,但是容器组件中还有 异步相关操作,能否把这些异步操作提取出去

redux + react-redux + 分模块+ 异步操作

配合redux 常用的异步模块 为 redux-thunk, redux-saga

redux-thunk

$ cnpm i redux-thunk -S

src/16_redux_react-redux_redux-thunk_combine/store/modules/home.js

const reducer = (
  state = {
    bannerList: [],
    proList: []
  },
  { type, payload }
) => {
  switch (type) {
    case 'CHANGE_BANNER_LIST':
      return { ...state, bannerList: payload }
    case 'CHANGE_PRO_LIST':
      return { ...state, proList: payload }
    default:
      return state
  }
}

export default reducer

src/16_redux_react-redux_redux-thunk_combine/store/modules/kind.js

const reducer = (
  state = {
    kindList: []
  },
  { type, payload }
) => {
  switch (type) {
    case 'CHANGE_KIND_LIST':
      return { ...state, kindList: payload }
    default:
      return state
  }
}

export default reducer

整合多个reducer 为一个,因为 有一个原则 : 单一数据源 的原则

并且后续需要使用 异步操作,将异步操作模块 使用进来

src/16_redux_react-redux_redux-thunk_combine/store/index.js

import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

import home from './modules/home'
import kind from './modules/kind'

const reducer = combineReducers({
  home,
  kind
})

// 第二个参数表示代码中含有异步操作,且异步操作需要提取出 容器组件
const store = createStore(reducer, applyMiddleware(thunk))

export default store

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'

// 引入状态管理器
import { Provider } from 'react-redux' // 提供Provier组件,可以添加store属性
import store from './16_redux_react-redux_redux-thunk_combine/store'
import App from './16_redux_react-redux_redux-thunk_combine/App'

const root = ReactDOM.createRoot(document.getElementById('root'))

// 标签形式调用
root.render(
  
    
  
)

src/16_redux_react-redux_redux-thunk_combine/App.jsx

import React from 'react';
import Home from './views/Home'
import Kind from './views/Kind'
const App = () => {
  return (
    

); }; export default App;

上一个案例 异步操作在组件,现在需要将其放到 异步操作模块中去 actionCreator

src/16_redux_react-redux_redux-thunk_combine/api/home.js

export function getBannerList () {
  return fetch('http://121.89.205.189:3001/api/banner/list').then(res => res.json())
}

export function getProList (params) {
  return fetch('http://121.89.205.189:3001/api/pro/list?limitNum=' + params.limitNum).then(res => res.json())
}

src/16_redux_react-redux_redux-thunk_combine/api/kind.js

export function getKindList () {
  return fetch('http://121.89.205.189:3001/api/pro/categorylist').then(res => res.json())
}

src/16_redux_react-redux_redux-thunk_combine/store/actions/home.js

// 容器组件抽离的异步操作都写在这里
// 每一个action的函数记住一个原则,默认的参数为dispatch
// 如果接口调用不需要传递参数,直接给函数写默认参数 dispatch
// 如果接口调用需要传递参数,一般在返回一个函数,返回函数的 参数为 dispatch
import { getBannerList, getProList } from '../../api/home'

// actionCreator 本质是一个带有 dispatch 参数的函数
const action = {
  getBannerListAction (dispatch) { // 因为此接口不需要参数,所有拥有了默认参数 dispatch
    getBannerList().then(res => {
      dispatch({
        type: 'CHANGE_BANNER_LIST',
        payload: res.data
      })
    })
  },
  getProListAction (params) { // 组件调用需要传递参数
    return (dispatch) => {
      getProList(params).then(res => {
        dispatch({
          type: 'CHANGE_PRO_LIST',
          payload: res.data
        })
      })
    }
  }
}

export default action

src/16_redux_react-redux_redux-thunk_combine/store/actions/kind.js

import { getKindList } from '../../api/kind'

const action = {
  getKindListAction (dispatch) {
    getKindList().then(res => {
      dispatch({
        type: 'CHANGE_KIND_LIST',
        payload: res.data
      })
    })
  }
}

export default action 

记住接口有无参数影响 action 的写法,还影响其调用

src/16_redux_react-redux_redux-thunk_combine/views/Home.jsx

import React from 'react';
import { useEffect } from 'react';
import { connect } from 'react-redux'

import action from '../store/actions/home'
const Home = connect((state) => {
  return {
    bannerList: state.home.bannerList,
    proList: state.home.proList
  }
}, (dispatch) => {
  return {
    getBannerList () {
      dispatch(action.getBannerListAction) // 因为没有参数,所以不加()
    },
    getProList () {
      dispatch(action.getProListAction({ count: 2, limitNum: 3 }))  // 因为有参数,所以加()
    }
  }
})(({ bannerList, proList, getBannerList, getProList }) => {
  useEffect(() => {
    getBannerList()
    getProList()
  }, [getBannerList, getProList])
  return (
    

首页

    { bannerList && bannerList.map(item => ( )) }
    { proList && proList.map(item => (
  • { item.proname }
  • )) }
); }); export default Home;

src/16_redux_react-redux_redux-thunk_combine/views/Home.jsx 异步在组件

import React, { useEffect } from 'react';
import { connect } from 'react-redux'
// import { getBannerList, getProList } from '../api/home'
import action from '../store/actions/home' // 异步操作
const Home = ({ bannerList, proList, getBannerListData, getProListData }) => {
  useEffect(() => {
    getBannerListData()
    getProListData()
  }, [getBannerListData, getProListData])
  return (
    

Home

{ bannerList && bannerList.map(item => ( {item.alt} )) }
    { proList && proList.map(item => { return (
  • { item.proname }
  • ) }) }
); }; export default connect( ({ home: { bannerList, proList }}) => ({ bannerList, proList }), (dispatch) => ({ getBannerListData () { // getBannerList().then(res => { // dispatch({ // type: 'CHANGE_BANNER_LIST', // payload: res.data // }) // }) // 根据生成的actionCreate 规则去写,午餐不加() dispatch(action.getBannerListAction) }, getProListData () { // getProList().then(res => { // dispatch({ // type: 'CHANGE_PRO_LIST', // payload: res.data // }) // }) dispatch(action.getProListAction({ limitNum: 2 })) } }) )(Home);

src/16_redux_react-redux_redux-thunk_combine/views/Kind.jsx 异步在store

import React, { useEffect } from 'react';
import { connect } from 'react-redux'
// import { getKindList } from '../api/kind'
import action from '../store/actions/kind'
const Kind = ({ kindList, getKindListData }) => {
  useEffect(() => {
    getKindListData()
  }, [getKindListData])
  return (
    

Kind

{ kindList && kindList.map(item => { return (

{ item }

) }) }
); }; export default connect( ({ kind: { kindList } }) => ({ kindList }), (dispatch) => ({ getKindListData () { // getKindList().then(res => { // dispatch({ // type: 'CHANGE_KIND_LIST', // payload: res.data // }) // }) dispatch(action.getKindListAction) } }) )(Kind);

虽然引入了redux-thunk,但是仍然可以把 异步放在组件中,具体根据项目的需求而定

创建状态模块

整合模块

创建异步的 actionCreator

组件触发 actionCreator

传递数据以及修改界面

18.6.2 redux-saga

先要了解解决异步操作方案:回调函数、promise、async await、generator yield

ES6 入门教程

$ cnpm i redux-saga -S

src/17_redux_react-redux_redux-saga_combine/store/modules/home.js

const reducer =  (state = {
  bannerList: [],
  proList: []
}, { type, payload }) => {
  switch (type) {
    case 'CHANGE_BANNER_LIST':
      return { ...state, bannerList: payload }
    case 'CHANGE_PRO_LIST':
      return { ...state, proList: payload }
    default:
      return state
  }
}

export default reducer

src/17_redux_react-redux_redux-saga_combine/store/modules/kind.js

const reducer = (state = {
  kindList: []
}, { type, payload }) => {
  switch (type) {
    case 'CHANGE_KIND_LIST':
      return { ...state, kindList: payload }
    default:
      return state
  }
}
export default reducer

src/17_redux_react-redux_redux-saga_combine/api/home.js

export function getBannerList () {
  return fetch('http://121.89.205.189:3001/api/banner/list').then(res => res.json())
}

export function getProList (params) {
  return fetch('http://121.89.205.189:3001/api/pro/list?limitNum=' + params.limitNum).then(res => res.json())
}

src/17_redux_react-redux_redux-saga_combine/api/kind.js

export function getKindList () {
  return fetch('http://121.89.205.189:3001/api/pro/categorylist').then(res => res.json())
}

src/17_redux_react-redux_redux-saga_combine/store/mySaga.js

// put 类似 dispatch put({ type: '', payload: ''}) 触发状态的修改
// call 用来调用数据请求   call(getBannerList)  ===>    getBannerList()
// takeLatest 用来响应触发哪一个异步操作
import { put, call, takeLatest } from 'redux-saga/effects'

import { getBannerList, getProList } from '../api/home'
import { getKindList } from '../api/kind'

// 异步操作
function * getBannerListAction () {
  const res = yield call(getBannerList)
  console.log('banner', res.data)
  // 修改状态
  yield put({
    type: 'CHANGE_BANNER_LIST',
    payload: res.data
  })
}

function * getProListAction (action) {
  console.log(111, action)
  const res = yield call(getProList, action.payload)
  console.log('pro', res.data)
  yield put({
    type: 'CHANGE_PRO_LIST',
    payload: res.data
  })
}

function * getKindListAction () {
  const res = yield call(getKindList)
  console.log('kind', res.data)
  yield put({
    type: 'CHANGE_KIND_LIST',
    payload: res.data
  })
}

// 定义异步触发的条件
function * mySaga () {
  // 组件触发 REQUEST_BANNER 即可执行 getBannerListAction 异步行为
  yield takeLatest('REQUEST_BANNER', getBannerListAction)
  // 组件触发 REQUEST_PRO 即可执行 getProListAction 异步行为
  yield takeLatest('REQUEST_PRO', getProListAction)
   // 组件触发 REQUEST_KIND 即可执行 getKindListAction 异步行为
  yield takeLatest('REQUEST_KIND', getKindListAction)
}

export default mySaga

src/17_redux_react-redux_redux-saga_combine/store/index.js

import { createStore, combineReducers, applyMiddleware } from 'redux'
import createSagaMiddleWare from 'redux-saga' // 导入生成中间件的函数

import mySaga from './mySaga' // 异步行为

// 分模块
import home from './modules/home'
import kind from './modules/kind'

const reducer = combineReducers({ home, kind }) // 整合reducer

const middleware = createSagaMiddleWare() // 生成中间件

const store = createStore(reducer, applyMiddleware(middleware)) // 创建状态管理器

middleware.run(mySaga) // 中间件使用异步,放到store创建之后

export default store

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'

// 引入状态管理器
import { Provider } from 'react-redux' // 提供Provier组件,可以添加store属性
import store from './17_redux_react-redux_redux-saga_combine/store/index'
import App from './17_redux_react-redux_redux-saga_combine/App'

const root = ReactDOM.createRoot(document.getElementById('root'))

// 标签形式调用
root.render(
  
    
  
)

src/17_redux_react-redux_redux-saga_combine/views/Home.jsx

import React from 'react';
import { useEffect } from 'react';
import { connect } from 'react-redux'
const Home = ({ bannerList, proList, dispatch }) => {
  useEffect(() => {
    // 触发某个行为,从而触发 异步操作
    dispatch({ type: 'REQUEST_BANNER' })
    dispatch({ type: 'REQUEST_PRO', payload: { limitNum: 3 } })
  }, [dispatch])
  return (
    

Home

{ bannerList && bannerList.map(item => ( {item.alt} )) }
    { proList && proList.map(item => { return (
  • { item.proname }
  • ) }) }
); }; export default connect( ({ home: { bannerList, proList }}) => ({ bannerList, proList }) )(Home);

src/17_redux_react-redux_redux-saga_combine/views/Kind.jsx

import React, { useEffect } from 'react';
import { connect } from 'react-redux'
const Kind = ({ kindList, getKindListData }) => {
  useEffect(() => {
    getKindListData()
  }, [getKindListData])
  return (
    

Kind

{ kindList && kindList.map(item => { return (

{ item }

) }) }
); }; export default connect( ({ kind: { kindList }}) => ({kindList}), (dispatch) => ({ getKindListData () { dispatch({ type: 'REQUEST_KIND' }) } }) )(Kind);

使用immutable不可变数据数据结构

immutable不可变的数据解构,意思就是一旦数据被创建,需要使用特殊的方法进行修改(修改过后的数据也是符合immutable数据解构的,他是一个全新的对象)

学习immutable: immutable - npm

$ cnpm i immutable redux-immutable -S

重点就是修改 reducer 的数据机构以及组件中获取状态的方式(整合reducer时使用 redux-immutable 提供的 combineReducers)

什么是不可变数据

不可变数据 (Immutable Data )就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是持久化数据结构( Persistent Data Structure),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的s性能损耗,Immutable 使用了 结构共享(Structural Sharing),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。

immutable.js的优缺点

优点:

降低mutable带来的复杂度 节省内存 历史追溯性(时间旅行):时间旅行指的是,每时每刻的值都被保留了,想回退到哪一步只要简单的将数据取出就行,想一下如果现在页面有个撤销的操作,撤销前的数据被保留了,只需要取出就行,这个特性在redux或者flux中特别有用 拥抱函数式编程:immutable本来就是函数式编程的概念,纯函数式编程的特点就是,只要输入一致,输出必然一致,相比于面向对象,这样开发组件和调试更方便 缺点:

需要重新学习api 资源包大小增加(源码5000行左右) 容易与原生对象混淆:由于api与原生不同,混用的话容易出错。

GIT 和 GITHUB

  • 在学习之前我们要先要清楚几个东西

  • gitgithub 是两个东西,不是一个东西

  • 就像 javajavascript 一样,是两个东西

  • 需要搞清楚几个词语的意思

    • 本地:我自己的终端

    • 远程:在网络那一头的终端

    • 仓库:一个被 git 管理了的文件夹

什么是 GIT

  • git 官方名称: 分布式版本管理器

  • 私人解释: 就是一个管理我们文件夹的工具

    • 只不过可以保留所有的版本信息

  • 就是我们安装一个软件

    • 然后用这个软件来管理我们的一个文件夹

    • 这个文件夹被 git 管理以后,我们可以对他进行各种操作

    • 保证我们写过的内容不会丢失

什么是 GITHUB

  • github 是一个网站,是一个我们托管项目的平台

  • 是一个 世界级别 的大型网站

  • 也就是说,我们写的项目的 源码 可以放在上面保存,只要我们不自己删除,不会丢失

    • 就相当于百度云

    • 只不过功能更加强大,上面都是开发人员(世界级别)

  • 因为 github 只接受 git 来上传代码,所以叫做 github

  • 也就是说,我们可以通过 git 这个软件来管理我们本地的文件夹

    • 并且可以把文件夹里面的代码上传到 github 保存

    • 也可以写一个插件之类的工具,上传到 github 上面让其他开发者使用

    • 也可以从 github 上找到其他开发者写的插件之类的东西下载使用

  • 所以说,github 也是一个大型的 开源 的资源共享平台

GIT 使用

  • 刚才我们说过,git 是一个管理我们文件夹的工具

  • 那么我们就要先安装工具,再使用工具管理我们的文件夹

GIT 安装

  • 下载 git 直接到官网下载就可以

    • git官网

    • git下载中心

  • 找到和操作系统对应的版本就可以了

    1. 方法一: 打开官网,直接点击下载

    2. 方法二: 打开下载中心,选择操作系统,选择下载版本

  • 下载好以后,直接双击进行安装就行

  • 一直下一步就可以,安装再默认路径就行

  • 安装完毕后检测一下安装是否成功

    1. 方法一: 打开 cmd 窗口,输入指令检查

      # 检测 git 是否安装
      $ git --version
      • 出现版本号,说明安装成功

    2. 方法二: 随便找个地方单机 鼠标右键,出现下图内容,说明安装成功

  • 安装完毕,接下来我们就可以开始使用了

GIT 使用

  • git 是一个软件没错,但是不是那种安装了会再桌面出现一个图标的软件

  • 而是一个需要在 命令行 进行操作的软件

  • 我们单机鼠标右键,点击 Git Bash Here

  • 我们发现点开以后,就是一个 命令行窗口

  • 其实就是给我们写一些指令使用的,只不过色彩鲜艳一点

    • 使用 cmd 或者 powershell 窗口来运行 git 指令也可以

    • 只要是再终端运行就行

    • OS 操作系统直接在 终端 中运行就行

  • Git Bash Here 表示再当前目录下运行 Git Base

  • 所以,你在哪个文件夹下点击的,那么你出来的命令行窗口的目录就是哪里

  • 我们就在命令行里面使用 git 来管理我们的文件夹

GIT 初始化

  • 我们希望一个文件夹被 git 管理的话,那么就要在一个文件夹下进行 git 初始化

  • 找到一个希望被 git 管理的文件夹

  • 在文件夹内单击鼠标右键,点开 Git Bash Here

  • 输入指令

    # git 初始化的指令
    $ git init
  • 然后文件夹内会多一个 .git 的文件夹(这个文件夹是一个隐藏文件夹)

  • 这个时候,我的这个 git_demo 文件夹就被 git 管理了

    • git 不光管理这一个文件夹,包括所有的子文件夹和子文件都会被管理

  • 注意: 只有当一个文件夹被 git 管理以后,我们才可以使用 git 的功能去做版本管理

    • 也就是说,我们必须要把我们电脑中的某一个文件夹授权给 git

    • git 才能对这个文件夹里面的内容进行各种操作

    • git init 就是在进行这个授权的操作

GIT 暂存区

  • 当一个文件夹被 git 管理了以后

  • git 就会对当前文件夹进行 “分区”

  • 会分为三个区域

    1. 工作区:我们书写的源码就在工作区里面属性

    2. 暂存区:把我们想要存储的内容放在暂存区

    3. 历史区:把暂存区里面的内容拿出来形成一个历史版本

  • 也就是说,我们需要把想成为一个版本的代码

    • 要先放在暂存区

    • 然后才能在暂存区里面放到历史去

    • 才可以生成一个版本保存下来

  • 我们要放入暂存区,要使用 git add 指令

  • 把单独一个文件放在暂存区

    # 把文件夹下的 index.txt 文本放在暂存区
    $ git add index.txt
  • 把单独一个文件夹放在暂存区(暂存区不能存放空文件夹)

    # 把文件夹下的 ceshi文件夹 放在暂存区
    $ git add ceshi/
  • 把所有文件都放在暂存区

    # 把文件夹下所有的内容都放在暂存区
    $ git add --all
    ​
    # git add --all 有一个简单的写法
    $ git add .
    • 全部存放的时候使用上面两个指令哪个都行

  • 把已经放在暂存区的内容在拉回到工作区

    # 拉回暂存区的 index.txt 文件
    $ git reset HEAD -- index.txt
    ​
    # 拉回暂存区的 ceshi 文件夹
    $ git reset HEAD -- ceshi/
    ​
    # 拉回暂存区的 所有文件
    $ git reset HEAD -- .
    • 注意: -- 两边都有空格,拉回所有文件的时候有个 .

  • 暂存区,只是帮我们暂时存放内容,我们删除了还是会丢的

  • 要想帮我们保存下来,那么还需要把暂存区的内容放到历史区

GIT 历史区

  • git 的历史区,就是把我们暂存区里面的文件变成一个历史版本

  • 当一些文件形成一个版本的时候,就会被一直记录下来了

  • 向历史区里面添加内容的时候,必须保证 暂存区 有内容

  • 因为历史区就是把暂存区里面的内容收录进去

  • 向历史区添加内容使用 git commit -m "做一个简单的说明"

    # 把暂存区的内容放到历史区
    $ git commit -m "我是第一个版本"
    • 我们一定要写一个简单的说明

    • 因为当我们的历史版本多了以后,我们自己也记不住哪个版本做了哪些修改

    • 所以有个简单的说明会好很多

  • 这个时候历史区就有了我们的第一个版本

  • 我们使用 git log 这个指令查看版本信息

    # 查看当前历史区版本信息
    $ git log
    • commit:这一个版本的版本编号

    • Author:作者

    • Date:本次版本的记录时间

    • 后面的内容就是我刚才提交的时候写的说明

  • 接下来我们对文件夹内的内容进行一些修改,然后再次创建一个历史版本

  • index.txt 中的文本内容进行修改

    • hello world 改变成 你好 世界

  • 然后我们再次打印日志看一下

    • 我们发现,日志信息变成了两条,也就是我们存在着两个版本的内容了

  • 放在历史区的内容,理论上就丢不了了

  • 现在我们可以把我们本地工作区中的内容给删除了,删除以后我们进行历史回退

  • 我们使用 git reset --hard 版本编号 进行历史回退

    # 回退到第一次提交的版本
    $ git reset --hard ce0c17f7a703c6847552c7aaab6becea6f0197f2
    ​
    # 回退到第二次提交的版本
    $ git reset --hard abb2c4f12566440e04bc166c3285f855a37a3bb2
  • 这个时候我们就可以来回来去的玩我们的历史记录了

添加用户名和邮箱

在commit之前,我们需要设置用户名和邮箱。(并不是每一次都需要,而是第一次需要设置。之后的提交都会使用这个用户名和邮箱)

$ git config --global user.name "Firstname Lastname"
$ git config --global user.email "[email protected]"

这个命令,会在“~/.gitconfig”中以如下形式输出设置文件

[user]
 name = Firstname Lastname
 email = [email protected]

想更改这些信息时,可以直接编辑这个设置文件。这里设置的姓名和邮箱地址会用在 Git 的提交日志中。

GIT 分支

  • git 分支,就是我们自己把我们的整个文件夹分成一个一个独立的区域

  • 比如我在开发 登录 功能的时候,可以放在 login 分支下进行开发

    • 开发 列表 功能的时候,可以放在 list 分支下进行开发

    • 大家互不干扰,每一个功能都是一个独立的功能分支

  • 这样开发就会好很多

  • git 在初始化的时候,会自动生成一个分支,叫做 master

  • 是表示主要分支的意思

  • 我们就可以自己开辟出很多独立分支

  • 开辟一个分支使用 git branch 分支名称 指令

    # 开辟一个 login 分支
    $ git branch login
  • 查看一下当前分支情况

    # 查看当前分支情况
    $ git branch
    • 会看到,当前有两个分支了

  • 一个是 master,一个是 login

    • 前面有个 * 号,并且有高亮显示的,表示你当前所处的分支

  • 我们对 登录 功能的开发要移动到 login 分支去完成

  • 我们切换所处分支使用 git checkout 分支名称

    # 切换到 login 分支
    $ git checkout login
  • 然后我们在整个分支上进行 登录 功能的开发

  • 开发完毕以后,我们就在当前分支上进行提交

  • 提交以后我们进行分支切换

    • 发现 master 上面还是最初始的状态

    • login 分支上有我们新写的 登录 功能的代码

  • 我们按照分支把所有功能都开发完毕了以后

    • 只要把所有代码都合并到 master 主分支上就行了

  • git 的合并分支,只能是把别的分支的内容合并到自己的分支上

  • 使用的指令是 git merge

    # 切换到 master 分支
    $ git checkout master
    ​
    # 把 login 的内容合并到自己的分支
    $ git merge login
  • 这个时候,我们刚才在 login 上开发的东西就都来到了 master 主分支上

  • 如果是有多个分支的话,那么所有的最后都合并到 master 分支上的时候

  • 我们的主分支上就有完整网站的所有页面

    • 各个分支上都是单独的页面和功能

  • 这个时候我们开辟的分支就没有什么用了,就可以删除分支了

    1. 先切换到别的分支

    2. 使用指令 git branch -d 分支名称 来删除

      # 先切换到别的分支
      $ git checkout master
      ​
      # 删除 login 分支
      $ git branch -d login

常用的分支命名

  • 我们的分支命名也要规范一些

  • 我们有一些分支名称大家都默认是有特殊意义的

  • 比如我们之前的写的 login 分支就是不规范的分支名称

    • 而且也是不规范的分支操作

  • 常见的分支名称

    1. master:主分支,永远只存储一个可以稳定运行的版本,不能再这个分支上直接开发

    2. develop: 主要开发分支,主要用于所用功能开发的代码合并,记录一个个的完整版本

      • 包含测试版本和稳定版本

      • 不要再这个分支上进行开发

    3. feature-xxx:功能开发分支,从 develop 创建的分支

      • 主要用作某一个功能的开发

      • 以自己功能来命名就行,例如 feature-login / feature-list

      • 开发完毕后合并到 develop 分支上

    4. feature-xxx-fix: 某一分支出现 bug 以后,在当前分支下开启一个 fix 分支

      • 解决完 bug 以后,合并到当前功能分支上

      • 如果是功能分支已经合并之后发现 bug 可以直接在 develop 上开启分支

      • 修复完成之后合并到 develop 分支上

    5. hotfix-xxx: 用于紧急 bug 修复

      • 直接在 master 分支上开启

      • 修复完成之后合并回 master

GIT 推送

  • 我们的所有内容已经全部保留在了本地历史区

  • 理论上是不会丢失了

    • 但是如果把文件夹删除了,还是没有了

  • 所以我们要使用 git 把我们所有的内容推送到 github 上面保存起来

  • 那么就本地文件夹就算删除了,那么远程上面还有一份,还可以拿回来使用

  • 所以我们现在就要把本地内容推送到远程

  • 这个时候我们接需要一个 github 的账号了

  • 先去 github官网 注册一个账号

开辟一个远程仓库

  • 有了 github 账号以后

  • 我们就登录 github 网站,开辟一个远程仓库(也可以使用gitee或者gitlab,它们和github本质上是一样的)

  • github 的远程也是以一个仓库一个仓库的形式来保存代码

  • 我们可以在一个 github 上保存很多的项目

    • 只要一个项目一个仓库就可以了

  • 按照下面步骤开辟仓库

    1. 先点击新建仓库

    2. 按照要求填写内容

    3. 出现下图表示创建仓库成功

  • 现在我们的远程仓库建立完毕了,我们就要把我们的项目代码整个上传了

添加仓库地址

  • 接下来,要使用 git 上传代码了

  • 我们先要告诉 git 上传到哪里

  • 也就是给 git 添加一个上传的地址

  • 我们还是来到我们的项目文件夹

  • 使用 git remote add origin 仓库地址 来添加

    # 在项目文件夹下打开 git base
    # 添加仓库地址
    $ git remote add origin https://github.com/guoxiang910223/ceshi1913.git
    • remote:远程的意思

    • add:添加的意思

    • origin:是一个变量名(就是指代后面一长串的地址)

建立信任关系

想要上传至github或者其它远程仓库,必须让你的电脑和远端服务器互相信任。

这就需要一种加密技术。可以选择SSH或者其它方式进行。

这里我们采取SSH。

$ ssh-keygen -t rsa -C "[email protected]"

PS:上面的邮箱部分可以随意填写

接着,需要按几次回车键。这样就会在你当前电脑的账户下生成一个.ssh文件夹。

里面有两个文件: id_rsa 和 id_rsa.pub

带有pub后缀的是公钥文件,另一个是私钥文件。

下一步就是将公钥文件中的字符串添加到github。

点击你的github账户,并找到添加SSHkey选项。

有两个输入框,一个是标题文本框,一个是公钥文本框。

起一个适当的标题,并将公钥文件中的字符串复制粘贴到公钥文本框中。

选择保存即可。

上传

  • 上传到哪里的地址我们已经添加好了

  • 信任关系也已经建立了

  • 接下来就是上传内容了

    • 上传要保证 历史区 里面有内容

    • 上传的过程会把 历史区 里面所有的内容上传到远端

  • 我们使用 git push 指令来上传

    # 上传内容
    $ git push -u origin master
    # 表示把内容上传到 origin 这个地址
    # master 是上传到远程的 master 分支
    • -u 是我们第一次的使用用到了,是为了记录一下用户名和密码

    • 下次上传的时候就不需要再写了

  • 第二次上传

    • 第二次上传的时候,因为有刚才的记录,就不需要再写 originmaster

    • 会默认传递到 origin 这个地址的 master 分支上

    • 除非你要传递到别的分支上的时候再进行书写

      # 第二次上传
      $ git push
  • 到这里,就完成了一次 git 推送

  • 这个时候本地的文件夹就真的可以删除了

  • 因为远程有一份我们的内容,本地的删除了,可以直接把远程的拉回来就行

GIT 克隆

  • git 克隆是指把远程仓库里面的内容克隆一份到本地

  • 可以克隆别人的 公开 的仓库,也可以克隆自己的仓库

  • 克隆别人的仓库,我们只能拿下来用,修改后不能从新上传

  • 克隆自己的仓库,我们修改后还可以再次上传更新

  • 我们先找到一个别人的仓库,或者自己的仓库(这里以 jQuery 的仓库为例)

  • 复制好地址以后,选择一个我们要存放内容的文件夹(我这里以桌面为例)

  • 直接在想存放内容的位置打开 git base

  • 输入克隆指令 git clone 仓库地址

    # 直接克隆仓库
    $ git clone https://github.com/jquery/jquery.git
  • 等待一段时间

  • 你就会发现你的文件夹里面多了一个叫做 jquery 的文件夹

  • 里面就是人家仓库的所有内容

GIT 下拉

  • 不管是你克隆下来的仓库还是别的方式弄得本地仓库

  • 当人家的代码更新以后,你想获得最新的代码

  • 我们不需要从新克隆

  • 只要拉取一次代码就可以了

  • 直接在项目文件夹里面使用指令下拉

    # 拉取远程最新代码
    $ git pull
  • 这样一来,你本地的仓库就可远程的仓库同步了

GIT 冲突

  • git 冲突是指在我们的上传过程中

  • 本地的版本和远程的版本不一致导致的

  • 这个时候只要先使用 git pull 拉取回来

    • 让本地和远程保持一致

  • 然后再从新上传就好了

  • 但是 git pull 相对不安全,因为会自动和本地内容合并

  • 我们也可以选择使用 git fetch

    # 使用 fetch 获取远程最新信息并开辟一个临时分支
    $ git fetch origin master:tmp
    ​
    # 讲当前分支和临时分支的内容进行对比
    $ git diff tmp
    ​
    # 再选择合并分支内容
    $ git merge tmp

你可能感兴趣的:(前端)