面向对象重点不就是对象吗?我们接下来会谈论到什么是对象,说到对象,在前言部分我想说说什么是抽象类?类的定义是什么?
JavaScript 传统方式是通过构造函数来定义生成对象的,所以说 function 既是对象,对象既是 function ,ECMAScript2015 之前是没有 class 概念的,在ECMAScript 2015 之后的ES6中为我们提供了更加接近传统语法的写法,类似于JAVA,PHP等语言,class 作为对象的模板,来根据业务需求抽象出一个公共类供对象使用,不过不要误解了我的说法,类是类,class 只是一个定义类的关键字,我们可以使用类实现很多功能,比如单例模式,访问器属性,静态方法,extends继承父类等等等等…
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的一次调用就可以了(按照分析好的步骤来依次执行解决问题)
面向对象是把事务分解成一个个对象,然后对象之间分工合作
与面向过程不同的是面向对象是以对象来划分问题,而不是步骤,每一个对象都是功能中心,具有明确分工,并且相对灵活,代码可复用、容易进行维护以及开发
性能相对于面向对象较高
相对于面向对象编程不易维护,不易复用,不易于扩展
易于维护,易于复用,易于扩展,高内聚低耦合,更灵活
性能相对于面向过程来说较低
面向对象其实本质就是描述我们现实生活中的事物,这个事物可以是具体的,也可以是抽象的
我们可以抽象一个对象,这个对象必然有一些属性,比如我们可以想象一下女娲视角:
女娲造人的时候如果一个一个的捏,细心的捏,那么这是很累的,很庞大工程,那么人都有生命特性,有心脏,有肺部等等,为什么不把这些功能封装起来作为一个抽象模板,身高面貌由女娲捏,而这些复杂的生命功能就可以直接使用抽象模板了?因为这是人的共有类啊
所以这就引出了面向对象的思维:
😀COOL!!
初学编程对于面向对象可能会有一点误解,不如我们基于 JavaScript 的 Object 数据类型来理解对象
ECMAScript 中的对象其实就是一组数据和功能的集合,我们可以通过 new 操作符和对象类型的名称来创建一个对象
const obj = new Object()
ECMAScript 中的 Object 其实也是派生其他对象的基类,Object 类型的所有属性和方法在派生的对象上同样存在
那么再回到我们的面向对象谈论中,什么是对象?
在 JavaScript 中对象是一组无序相关属性和方法的集合,所有事物都是对象,比如一个字面量,一个数组,一个函数等等都是一个对象
而对象是由属性和方法组成的:
或者你也可以基于 Web 开发来理解,CSS 可以称为这个网页的特征描述,JavaScript 可以称为这个网页的行为执行,而 HTML 则是这个网页的一个公共类,这个网页则是一个对象,基于 HTML 模板我们可以更改 CSS 和 JavaScript 来创建出一个新的网页对象 🤓
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 的指向问题:
指向调用者
这样一个思想// 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跑起来了
// 我跑起来了