【笔记】JS面向对象

前言

JS面向对象学习笔记

ES6

定义类

  • 类名首字母大写
1
2
3
class 类名 {
...
}

定义构造方法

  • constructor()方法为构造方法,在创建对象时会自动执行
    • 构造方法不需要function关键字修饰
    • 构造方法通过形参为对象初始化属性值
    • 如果不写构造方法,类中会有默认的构造方法
  • 必须通过this关键字调用当前类实例的属性或方法
1
2
3
4
5
class 类名 {
constructor(形参) {
this.属性名 = 形参
}
}

创建对象

  • 创建对象必须在定义类之后
1
2
3
4
5
6
7
class 类名 {
constructor(形参) {
this.属性名 = 形参
}
}

let 对象名 = new 类名();

定义方法

  • 类中定义方法时不需要使用function关键字修饰
  • 类中定义的多个方法不需要使用,分隔
1
2
3
4
5
class 类名 {
方法名(形参) {
...
}
}

继承

  • 通过extends关键字继承父类
  • 继承父类后可以直接通过子类调用父类的属性和方法
1
2
3
4
5
6
7
class 父类名 {
...
}

class 子类名 extends 父类名 {
...
}

子类调用父类方法

调用父类构造方法
  • 子类通过super()调用父类构造方法
1
2
3
4
5
6
7
8
9
10
11
class 父类名 {
constructor(形参) {
this.属性名 = 形参
}
}

class 子类名 extends 父类名 {
constructor(形参) {
super(形参)
}
}
同时将形参传递给子类构造和父类构造
  • 同时将构造方法的形参传递给子类构造方法和父类构造方法时,必须先调用父类构造方法再调用子类构造方法,super关键字需要在this关键字之前
1
2
3
4
5
6
7
8
9
10
11
12
class 父类名 {
constructor(形参) {
this.属性名 = 形参
}
}

class 子类名 extends 父类名 {
constructor(形参) {
super(形参)
this.属性名 = 形参
}
}
调用父类普通方法
1
2
3
4
5
6
7
8
9
10
11
class 父类名 {
父类方法名() {
...
}
}

class 子类名 extends 父类名 {
子类方法名() {
super.父类方法名()
}
}

方法的重写

  • 子类重写父类同名方法
1
2
3
4
5
6
7
8
9
10
11
class 父类名 {
父类方法名() {
...
}
}

class 子类名 extends 父类名 {
父类方法名() {
...
}
}

ES6之前

通过 new Object() 创建对象

1
let 对象名 = new Object();

通过对象字面量创建对象

1
2
3
let 对象名 = {
属性名: 属性值
};

通过构造函数创建对象

  • 构造函数的函数名首字母大写
  • 构造函数需要与new配合使用才有意义
  • new在执行时会做的事
    • 在内存中创建一个新的空对象
    • 让this指向这个对象
    • 执行构造函数的代码,给这个对象添加属性和方法
    • 返回这个新对象,无需return关键字
1
2
3
4
5
function 构造函数名(形参) {
this.属性名 = 形参;
}

let 对象名 = new 构造函数名();

实例成员

  • 通过this添加的成员属性或成员方法,实例成员只能通过实例化的对象来访问,不可以通过构造函数名直接访问实例成员
1
2
3
4
5
6
7
8
function 构造函数名(形参) {
// 定义实例成员
this.属性名 = 形参;
}

let 对象名 = new 构造函数名();
// 调用实例成员
对象名.属性名;

静态成员

  • 在静态函数中直接定义的属性或成员方法,只能通过构造函数名定义和访问,不能通过对象名定义和访问
1
2
3
4
5
// 定义静态成员
构造函数名.属性名 = 属性值;

// 调用静态成员
构造函数名.属性名;

通过原型对象实现方法的共享

  • 由于每次创建对象时,其内部的方法在内存存储时都需要重新开辟空间
  • 通过原型对象可以实现方法的共享,防止反复创建相同的方法
  • 通过构造函数的prototype属性可以定义公共方法,在对象中的__proto__属性指向了构造函数的prototype属性,所以可以直接调用构造函数中prototype属性定义的公共方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function 构造函数名() {
...
}
// 添加一个方法
构造函数名.prototype.方法名 = function() {
...
}
// 添加多个方法
构造函数名.prototype = {
constructor: 构造函数名,
方法名: function() {
...
},
方法名: function() {
...
}
}

let 对象名1 = new 构造函数名();
对象名1.方法名();
let 对象名2 = new 构造函数名();
对象名2.方法名();
  • 原型对象(prototype__proto__)中定义了一个constructor属性,指向了构造函数本身
  • 添加多个方法时,由于会覆盖原型对象中的所有属性,所以会丢失constructor属性,需要重新指向构造函数
    • 内置构造函数不能通过对象覆盖的方式为原型对象添加多个方法
1
2
构造函数名.prototype.constructor
对象名.__proto__.constructor

原型链

  • 原型链
    • 通过构造函数创建的对象,包含一个__proto__属性,这个__proto__属性指向了构造函数的原型对象prototype
    • 构造函数的原型对象prototype也是一个对象,所以也有__proto__属性,这个__proto__属性指向了Object.prototype
    • Object的原型对象prototype也是一个对象,所以也有__proto__属性,这个__proto__属性指向了null
  • 原型链中属性查找机制
    • 首先查找这个对象自身是否有指定的属性
    • 然后查找__proto__属性指向的原型对象中是否有指定的属性
    • 然后查找Object的原型对象中是否有指定的属性
    • 直到找到null为止,还没找到,返回undefined
1
2
3
4
5
6
7
8
9
10
function 构造函数名() {
...
}

let 对象名 = new 构造函数名();
对象名.方法名();

对象名.属性名 = 属性值;
构造函数名.prototype.属性名 = 属性值;
Object.prototype.属性名 = 属性值;
  • this指向
    • 在构造函数中,this指向的是对象实例
    • 原型对象中的this指向的是对象实例

原型对象的应用

扩展内置类方法
1
2
3
内置类.prototype.方法名 = function {
...
}

继承

  • 组合继承:通过构造函数+原型对象模拟继承的实现
  • 通过call()函数,将子函数中的this交给父函数,作为父函数this的指向,从而实现继承属性
  • 通过将子构造函数的prototype指向父构造函数的实例,从而实现继承父构造函数中的函数,仍然要重新修改constructor的指向
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 继承属性
function 父函数名(形参列表) {
...
}

function 子函数名() {
父函数名.call(this, 形参列表)
}

// 继承函数

父构造函数名.prototype.函数名 = function() {
...
};

子构造函数名.prototype = new 父构造函数名();
子构造函数名.prototype.constructor = 子构造函数名;

完成

参考文献

哔哩哔哩——黑马前端