前言:最近开始学习react,之前学习vue没有把笔记整理的特别好,非常后悔,感觉学了等于没学,这次要好好整理啊!本次学习参考教程为B站,张天禹老师的react全家桶。
文章目录
- 类的基本知识
- 创建一个类
- 类的构造方法和一般方法
- 类的继承
- 总结
- 原生事件绑定
- 类中方法的this指向
- 类中的属性
- 展开运算符
- 对象相关知识
类的基本知识
创建一个类
- 定义一个Person类,然后实例化一个这个类的对象p1,并对这个对象进行输出后,查看控制台。代码如下:
// 创建一个person类
class Person {
}
// 创建一个person的实例对象
const p1 = new Person();
console.log(p1);
- 控制台打印效果如下。
- 注意,这里输出的不是这个类,而是这个类的实例对象。
- 前面的Person是为了区分这个对象是由哪个类new出来的。
类的构造方法和一般方法
- 给Person类添加构造方法和一般方法,并调用一般方法。代码如下:
// 创建一个person类
class Person {
// 构造器方法(函数)
constructor(name, age) {
// 构造器中的this是谁?——是类的实例对象,例如下面的p1和p2
this.name = name;
this.age = age;
}
// 一般方法(除了构造器以外的方法)
speak() {
// 一般方法(speak方法)放在了哪(通过打印p1是看不到speak的)?——放在了这个类的原型对象上,需要展开Prototype才能看到
// 一般方法(speak方法)是给谁用的?——放在原型对象上的方法是给实例对象用的
// 通过person实例调用speak时,speak中的this就是实例对象
// 注意原型链查找规则:当读取自身不存在的属性或方法,则按照原型链进行查找。
console.log(`我叫${this.name},我的年龄是${this.age}`);
}
}
// 创建一个person的实例对象
const p1 = new Person('tom', 18);
console.log(p1);
p1.speak();
p1.speak.call({ a: 1, b: 2 });
- 控制台打印效果如下。
- 注意,构造方法中的this是实例对象,例如代码中的p1和p2。而一般方法通过实例调用时,方法中的this才是实例对象。因为可以通过一般方法.call()等方法来更改函数中的this指向,例如p1.speak.call({a:1,b:2}),这时候,speak中的this指向的是{a:1,b:2}对象。
- 方法并没有在类中展示出来,而是Person这个类的原型对象上,供实例使用。
- 原型链的查找规则:当读取自身不存在的属性或方法,则按照原型链进行查找。
类的继承
- 创建一个Student类,继承Person类,这时候Student中就有了name、age等属性以及speak方法。因为继承了Person。
- Student类需要一个年级的属性,因此需要重新写一个Student的构造器。这时候必须要调用父类的构造器(如果定义了一个类并进行了继承,并且子类需要写构造器,则子类构造器必须调用父类的构造器。并且父类构造器要放在最上面)。
- 学生也可以进行speak,并且调用的是父类的speak,并且speak方法是在父类的原型对象上,这里需要一层一层的网上找,也就是原型链。
// 创建一个person类
class Person {
// 构造器方法(函数)
constructor(name, age) {
// 构造器中的this是谁?——是类的实例对象,例如下面的p1和p2
this.name = name;
this.age = age;
}
// 一般方法(除了构造器以外的方法)
speak() {
// 一般方法(speak方法)放在了哪(通过打印p1是看不到speak的)?——放在了这个类的原型对象上,需要展开Prototype才能看到
// 一般方法(speak方法)是给谁用的?——放在原型对象上的方法是给实例对象用的
// 通过person实例调用speak时,speak中的this就是实例对象
// 注意原型链查找规则:当读取自身不存在的属性或方法,则按照原型链进行查找。
console.log(`我叫${this.name},我的年龄是${this.age}`);
}
}
// 创建一个person的实例对象
const p1 = new Person('tom', 18);
console.log(p1);
p1.speak();
p1.speak.call({ a: 1, b: 2 });
// --------------------------------
// 创建一个Student类,继承Person类
class Student extends Person {
constructor(name, age, grade) {
// 什么时候需要调用父类构造器?——如果定义了一个类并进行了继承,并且子类需要写构造器,则子类构造器必须调用父类的构造器。并且父类构造器要放在最上面
super(name, age);// 调用父类构造器
this.grade = grade;
}
}
const s1 = new Student("张三", 15, '高一');
console.log(s1);
// 这里注意,speak方法是在父类的原型对象上,这里需要一层一层的网上找,也就是原型链
s1.speak();
- 控制台输出效果如下:
- 学生还需要再说出自己的年级,则需要重写父类的方法,此时Student原型对象上就有speak方法了,根据原型链查找规则,找到了就不继续找了。代码如下:
// 创建一个person类
class Person {
// 构造器方法(函数)
constructor(name, age) {
// 构造器中的this是谁?——是类的实例对象,例如下面的p1和p2
this.name = name;
this.age = age;
}
// 一般方法(除了构造器以外的方法)
speak() {
// 一般方法(speak方法)放在了哪(通过打印p1是看不到speak的)?——放在了这个类的原型对象上,需要展开Prototype才能看到
// 一般方法(speak方法)是给谁用的?——放在原型对象上的方法是给实例对象用的
// 通过person实例调用speak时,speak中的this就是实例对象
// 注意原型链查找规则:当读取自身不存在的属性或方法,则按照原型链进行查找。
console.log(`我叫${this.name},我的年龄是${this.age}`);
}
}
// 创建一个person的实例对象
const p1 = new Person('tom', 18);
console.log(p1);
p1.speak();
p1.speak.call({ a: 1, b: 2 });
// --------------------------------
// 创建一个Student类,继承Person类
class Student extends Person {
constructor(name, age, grade) {
// 什么时候需要调用父类构造器?——如果定义了一个类并进行了继承,并且子类需要写构造器,则子类构造器必须调用父类的构造器。并且父类构造器要放在最上面
super(name, age);// 调用父类构造器
this.grade = grade;
}
// 重写父类继承的方法,此时Student原型对象上就有speak方法了,根据原型链查找规则,找到了就不继续找了
speak() {
console.log(`我叫${this.name},我的年龄是${this.age},我读的年级是${this.grade}`);
}
}
const s1 = new Student("张三", 15, '高一');
console.log(s1);
// 这里注意,speak方法是在父类的原型对象上,这里需要一层一层的网上找,也就是原型链
s1.speak();
- 控制台打印效果如下:
- 学生还需要一个自己独有的方法,注意此方法的位置,以及方法中的this,代码如下:
// 创建一个person类
class Person {
// 构造器方法(函数)
constructor(name, age) {
// 构造器中的this是谁?——是类的实例对象,例如下面的p1和p2
this.name = name;
this.age = age;
}
// 一般方法(除了构造器以外的方法)
speak() {
// 一般方法(speak方法)放在了哪(通过打印p1是看不到speak的)?——放在了这个类的原型对象上,需要展开Prototype才能看到
// 一般方法(speak方法)是给谁用的?——放在原型对象上的方法是给实例对象用的
// 通过person实例调用speak时,speak中的this就是实例对象
// 注意原型链查找规则:当读取自身不存在的属性或方法,则按照原型链进行查找。
console.log(`我叫${this.name},我的年龄是${this.age}`);
}
}
// 创建一个person的实例对象
const p1 = new Person('tom', 18);
console.log(p1);
p1.speak();
p1.speak.call({ a: 1, b: 2 });
// --------------------------------
// 创建一个Student类,继承Person类
class Student extends Person {
constructor(name, age, grade) {
// 什么时候需要调用父类构造器?——如果定义了一个类并进行了继承,并且子类需要写构造器,则子类构造器必须调用父类的构造器。并且父类构造器要放在最上面
super(name, age);// 调用父类构造器
this.grade = grade;
}
// 重写父类继承的方法,此时Student原型对象上就有speak方法了,根据原型链查找规则,找到了就不继续找了
speak() {
console.log(`我叫${this.name},我的年龄是${this.age},我读的年级是${this.grade}`);
}
study() {
// speak放在了类的原型对象上,供实例使用
// 通过student实例调用study时,study中的this就是实例对象
console.log("我很努力的学习");
}
}
const s1 = new Student("张三", 15, '高一');
console.log(s1);
// 这里注意,speak方法是在父类的原型对象上,这里需要一层一层的网上找,也就是原型链
s1.speak();
s1.study();
- 控制台输出效果如下:
总结
- 类中的构造器不是必须要写的,要对实例进行一些初始化的操作时才要写。例如添加指定属性。
- 如果A类继承B类,并且A类中有构造器,则A类中的构造器必须调用super方法。
- 类中所有定义的方法,都是放在了类的原型对象上,供类的实例对象使用。
原生事件绑定
- 原生事件的绑定方式
- 拿到元素后,添加监听事件
- 拿到元素后,给元素添加点击事件
- 直接给元素添加点击事件,执行对应的js语句
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>02_原生事件绑定</title>
</head>
<body>
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<button onclick="demo()">按钮3</button>
<script>
// 方式1:拿到元素以后,添加事件监听器
const btn1 = document.getElementById("btn1");
btn1.addEventListener('click', () => {
alert("按钮1被点击了")
})
// 方式2:直接对元素添加点击事件
const btn2 = document.getElementById("btn2");
btn2.onclick = () => {
alert("按钮2被点击了")
}
// 方式3:当点击的时候,执行demo()这个js语句
function demo() {
alert("按钮3被点击了")
}
</script>
</body>
</html>
类中方法的this指向
- 只有使用实例调用方法的时候,this才是实例对象本身
- 通过将方法重新赋值给变量,然后变量调用的方式,this是undefined,原因如下。
- 因为其实给x的是一个方法(特殊的属性),然后x加括号则是直接调用。因此不是实例对象调用的,所以不会是实例对象本身。
- 内存结构图分析:
- 其实方法在堆内存中,然后栈中可以通过p1进行引用,但是赋值以后,栈中多了一个x的引用,x会直接引用堆内存,而不走实例对象进行引用
- 直接调用那么this应该是window,而这里为什么不是window,而是undefined。
- 因为类中所有的定义的方法,在方法的局部都开启了严格模式,因此this不会指向window。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>03_类中方法的this指向</title>
</head>
<body>
<script>
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
speak() {
// speak方法放在哪?——类的原型对象上,供实例使用
// 通过Person实例调用speak时,speak中的this就是Person实例
console.log(this);
}
}
const p1 = new Person("tom", 18);
p1.speak();// 通过实例调用speak方法
const x = p1.speak;
x();// 这种方式,speak中的this就是undefined了
/**
* 为什么赋值给x的形式无法让speak中的this输出实例对象?
* 因为其实给x的是一个方法(特殊的属性),然后x加括号则是直接调用。
* 内存结构图分析:
* 其实方法在堆内存中,然后栈中可以通过p1进行引用,但是赋值以后,栈中多了一个x的引用,x会直接引用堆内存,而不走实例对象进行引用
* 为什么输出的是undefined?
* 类中所有的定义的方法,在方法的局部都开启了严格模式,因此this不会指向window。
*/
</script>
</body>
</html>
类中的属性
- 如果初始化的属性,是需要通过传递过来的,则就需要构造器
- 如果不是外部传递进来的,则可以直接通过赋值的形式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>04_类中的属性</title>
</head>
<body>
<script>
class Car {
// 如果初始化的属性,是需要通过传递过来的,则就需要构造器
constructor(name, price) {
this.name = name
this.price = price
}
// 如果不是外部传递进来的,则可以通过如下形式
// 往实例自身上追加一个属性,名为wheel,值为4,这样所有的实例都会有wheel属性
wheel = 4
}
const c1 = new Car('奔驰c63', 199);
console.log(c1);
</script>
</body>
</html>
展开运算符
- 展开运算符也就是“…”。其主要用法包含以下4种
- 将数组展开进行输出,其实输出的是多个数,而不是一个数组
- 连接两个数组
- 函数传参,批量传递多个参数
- 进行对象复制,注意这里是深度复制
- 复制对象,同时修改属性,或者新增属性。其实就是合并两个对象,冲突的话以后面的为准
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>05_展开运算符</title>
</head>
<body>
<script>
let arr1 = [1, 3, 5, 7, 9];
let arr2 = [2, 4, 6, 8, 10];
// 1. 将数组展开进行输出,其实输出的是多个数,而不是一个数组
console.log(...arr1);
// 2.连接两个数组
let arr3 = [...arr1, ...arr2];
console.log(arr3);
// 3. 函数传参,批量传递多个参数
function sum(...numbers) {
// 计算数组中所有数值的和,可以使用数组身上的reduce方法,reduce中有两个参数,一个是之前的值,一个是当前的元素的值。注意这个之前的值,指的是之前的计算结果。
return numbers.reduce((preValue, currentValue) => {
return preValue + currentValue
})
}
console.log(sum(1, 2, 3));
// 4. 进行对象复制,注意这里是深度复制
let person = { name: "tom", age: "18" }
// console.log(...person); // 展开运算符号不能展开一个对象
let person2 = { ...person }// 因为这里加了一个花括号,就是复制对象了
person.name = "jerry";
console.log(person);
console.log(person2);
// 5. 复制对象,同时修改属性,或者新增属性。其实就是合并两个对象,冲突的话以后面的为准
let person3 = { ...person, name: "张三", address: "南京" }
console.log(person3);
</script>
</body>
</html>
对象相关知识
- 需求:对象中添加一个key,并且这个key是从一个变量中拿的。
- 代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>06_对象相关知识</title>
</head>
<body>
<script>
let a = 'name';
let obj = {}
// 要求obj变成:{name:'tom'},而这里的name是变量a中的内容
// 实现代码如下:
obj[a] = 'tom';// 这里就需要用到方括号
console.log(obj);
</script>
</body>
</html>