ES6-类--【面向对象编程思想】

面向对象编程

面向对象重点不就是对象吗?我们接下来会谈论到什么是对象,说到对象,在前言部分我想说说什么是抽象类?类的定义是什么?

JavaScript 传统方式是通过构造函数来定义生成对象的,所以说 function 既是对象,对象既是 function ,ECMAScript2015 之前是没有 class 概念的,在ECMAScript 2015 之后的ES6中为我们提供了更加接近传统语法的写法,类似于JAVA,PHP等语言,class 作为对象的模板,来根据业务需求抽象出一个公共类供对象使用,不过不要误解了我的说法,类是类,class 只是一个定义类的关键字,我们可以使用类实现很多功能,比如单例模式,访问器属性,静态方法,extends继承父类等等等等…

两大编程思想

  • 面向过程
  • 面向对象

面向过程(POP)

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的一次调用就可以了(按照分析好的步骤来依次执行解决问题)

  • 比如我要完成学习 ES6 的任务,那么我们怎么使用面向过程的思想来完成呢?
  1. 打开书
  2. 看书
  3. 实践
  4. 合上书
  • 一步一步进行,依次进行,这就是面向过程

面向对象(OOP)

面向对象是把事务分解成一个个对象,然后对象之间分工合作

  • 同样我要完成学习 ES6 的任务,那么我们怎么使用面向对象的思想来完成呢?
  1. 我们有一个书的对象
  • 书的属性:可以打开
  • 属的属性:可以合上
  1. 我也是一个对象
  • 我的属性:看书
  • 我的属性:实践
  1. 使用我的和大象的功能来完成任务

与面向过程不同的是面向对象是以对象来划分问题,而不是步骤,每一个对象都是功能中心,具有明确分工,并且相对灵活,代码可复用、容易进行维护以及开发

  • 特性
  1. 封装性
  2. 继承性
  3. 多态性

我们怎么去选择使用?

  1. 面向过程:
  • 优点:

性能相对于面向对象较高

  • 缺点:

相对于面向对象编程不易维护,不易复用,不易于扩展

  1. 面向对象
  • 优点:

易于维护,易于复用,易于扩展,高内聚低耦合,更灵活

  • 缺点:

性能相对于面向过程来说较低

进一步理解面向对象编程思想

面向对象其实本质就是描述我们现实生活中的事物,这个事物可以是具体的,也可以是抽象的

我们可以抽象一个对象,这个对象必然有一些属性,比如我们可以想象一下女娲视角:

女娲造人的时候如果一个一个的捏,细心的捏,那么这是很累的,很庞大工程,那么人都有生命特性,有心脏,有肺部等等,为什么不把这些功能封装起来作为一个抽象模板,身高面貌由女娲捏,而这些复杂的生命功能就可以直接使用抽象模板了?因为这是人的共有类啊

所以这就引出了面向对象的思维:

  • 抽象对象共有属性和行为封装为一个类
  • 对类进行实例化,获取类的对象(每一个实例化的对象都具有公有类的功能和方法)

😀COOL!!


类和对象

对象

初学编程对于面向对象可能会有一点误解,不如我们基于 JavaScript 的 Object 数据类型来理解对象

ECMAScript 中的对象其实就是一组数据和功能的集合,我们可以通过 new 操作符和对象类型的名称来创建一个对象

const obj = new Object()

ECMAScript 中的 Object 其实也是派生其他对象的基类,Object 类型的所有属性和方法在派生的对象上同样存在

那么再回到我们的面向对象谈论中,什么是对象?

在 JavaScript 中对象是一组无序相关属性和方法的集合,所有事物都是对象,比如一个字面量,一个数组,一个函数等等都是一个对象

而对象是由属性和方法组成的:

  • 属性:描述了事物的特征
  • 方法:描述了事物的行为

或者你也可以基于 Web 开发来理解,CSS 可以称为这个网页的特征描述,JavaScript 可以称为这个网页的行为执行,而 HTML 则是这个网页的一个公共类,这个网页则是一个对象,基于 HTML 模板我们可以更改 CSS 和 JavaScript 来创建出一个新的网页对象 🤓

类(class)

OK,我想现在是时候回到代码中了,我们可以使用class关键字来声明一个类,之后我们将以这个类进行实例化创建对象

我们之前也提到了,类抽象了对象的公共部分,它其实是泛指的某一大类,对象是特指的一个事物对象,是通过实例化得到的一个具体对象

我们怎么创建一个类

class Name {
	// class body
}

// 创建实例
const username = new Name()

怎么使用类添加共有属性

  • 传递参数使用 constructor 构造函数,它用于 传递参数,返回实例对象 ,通过 new 命令生成对象实例时 自动调用 该对象,如果没有显示定义,类内部会 自动帮我们创建 一个 constructor() 构造函数
class User {
	// 存放类的共有属性
	constructor(...user) {
		this.user = [...user].flat()
	}
}

const user1 = new User("Anna", 18, ["打代码", "玩游戏"])
const user2 = new User("Bun", 20, ["打代码", "写文章"])
const user3 = new User("Jons", 22, ["打代码", "跑步"])

console.log(user1.user)
console.log(user2.user)
console.log(user3.user)

// [ 'Anna', 18, '打代码', '玩游戏' ]
// [ 'Bun', 20, '打代码', '写文章' ]
// [ 'Jons', 22, '打代码', '跑步' ]

怎么使用类添加共有方法

我们直接把方法写到类里面就可以,相当于类的共享方法

class User {
	constructor(...user) {
		this.user = [...user].flat()
	}
	testMethod(timeOut) {
		setTimeout(() => {
			console.log(`每个人有${this.user.length}条信息`)
		}, timeOut)
	}
}

const user1 = new User("Anna", 18, ["打代码", "玩游戏"])
const user2 = new User("Bun", 20, ["打代码", "写文章"])
const user3 = new User("Jons", 22, ["打代码", "跑步"])

user1.testMethod(1000)
user2.testMethod(1000)
user3.testMethod(1000)

// 4
// 4
// 4

所以我们刚才了解的是类的 封装性 ,将属性于方法放在一个类中进行统一调用管理,非常的方便 😀

当然,我们也可以使用原型挂载:

class Test {
  constructor() {
    // 属性
    (this.name = 'Brave-AirPig'), (this.age = 22);
  }
}

// 原型挂载方法

Test.prototype.run = () => console.log('我跑起来了');

// 实例化

const test = new Test();

console.log(test.name, test.age);
test.run();

// Brave-AirPig 22
// 我已经跑起来了

类的继承性

字面意思:子承父类呗,对于熟练使用 CSS 的我们,对于继承这个思想应该非常的熟悉了,关于什么是继承我就不做过多的阐述了

继承关键词: extends

class User {
	constructor() {
		this.username = "Anna"
	}
	testMethod() {
		console.log(this.username)
	}
}

class UserEctype extends User {}

const userEctype = new UserEctype()

userEctype.testMethod()

访问调用父类上的构造函数

关键字:super

用于访问和调用对象父类上的函数,可以调用父类的构造函数,也可以调用父类的普通函数

class User {
	constructor(name) {
		this.username = name
	}
	testMethod() {
		console.log(this.username)
	}
}

class UserEctype extends User {
	constructor(name) {
		super(name) // 调用父类中的构造函数
	}
}

const userEctype = new UserEctype("Anna")

userEctype.testMethod()

如果我们不写 super() 的话,子类传递的实参是不能被父类的构造函数所获取并执行的

访问调用父类上的构造函数

依然是我们的 super 关键字:

class User {
	testMethod() {
		console.log("Anna")
	}
}

class UserEctype extends User {
	testMethod() {
		super.testMethod() // 调用父类中的构造函数
	}
}

const userEctype = new UserEctype()

userEctype.testMethod()

所以我们得出了一个结论:在继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的;如果子类没有该方法,就去父类中查找有没有这个方法,如果有的话,就执行父类的这个方法;使用 super 关键字可以修改优先级,先去查找父类中的方法,子类想在构造函数中使用 super 调用父类构造函数,需要放到子类构造函数的顶部

this 的指向问题:

  • constructor 构造函数中的 this 指向当前创建的实例对象
  • 方法中的 this 指向方法调用者,其实这样说有点不严谨了,函数中的 this 指向我们都应该遵循 指向调用者这样一个思想

简单总结与扩展

// ES5怎么写
function Test() {
  this.name = 'Brave-AirPig';
  this.age = 22;
}

Test.prototype.run = () => console.log('我跑起来了');

const test = new Test();

console.log(test.name, test.age);
test.run();

// Brave-AirPig 22
// 我跑起来了

// ES6写法
class Test {
  constructor() {
    // 属性
    this.name = 'Brave-AirPig';
    this.age = 22;
  }
  // 方法
  run() {
    console.log('我已经跑起来了');
  }
}

// 实例化

const test = new Test();

console.log(test.name, test.age);
test.run();

// Brave-AirPig 22
// 我已经跑起来了

表达式形式定义类

const Test = class {
  constructor() {
    this.name = 'Brave-AirPig';
    this.age = 22;
  }
};
const test = new Test();

console.log(test.name, test.age);

// Brave-AirPig 22

单例模式

const Test = new (class {
  constructor() {
    this.name = 'Brave-AirPig';
    this.age = 22;
  }
})();

console.log(Test.name, Test.age);

// Brave-AirPig 22

访问器属性

访问器属性我们通过 set 来监听属性值的变化来设置对应值,我们可以做一些操作

set那就必须存在get,否则的话set就没有用武之地了,通过 get 来获取值

const Test = new (class {
  constructor(name, age) {
    this.name = name;
    this.age = age;
    this.message = '';
  }
  set age(value) {
    if (value >= 18) this.message = '成年';
    else this.message = '未成年';
  }

  get age() {
    return this.message;
  }
})('Brave-AirPig', 22);

// 修改值被set监听到,get来进行获取
Test.age = 17;
console.log(Test.message);

Test.age = 18;
console.log(Test.message);

// 未成年
// 成年

静态方法

class Test {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  static run() {
    console.log('我跑起来了');
  }
}

Test.run();
// 我跑起来了
// 我们发现静态方法是不需要实例化就可以执行方法
// 并且该类的实例无法直接执行

继承类

派生类继承自基类,也就是说子类继承于父类

class TestFather {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  run() {
    console.log('我跑起来了');
  }
}

class TestSon extends TestFather {
  constructor(name, age) {
    // 派生类和基类有具有constructor构造函数,产生了冲突,我们必须使用super调用基类的构造函数
    // 当然你可以选择派生类不写构造函数,这样的话,也不会发生错误,JavaScript自动添加了构造函数以及super
    super(name, age);
  }
  // 覆盖基类方法
  run() {
    console.log(`${this.name}跑起来了`);
    // 我基类派生类的方法都要 -- super 继承
    super.run();
  }
}

const testSon = new TestSon('Brave-AirPig', 22);

console.log(testSon.name, testSon.age);

testSon.run();

// Brave-AirPig 22
// Brave-AirPig跑起来了
// 我跑起来了
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Try Tomato

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值