JavaScript面向对象
1 面向过程(POP)与面向对象(OOP)
面向过程(POP):分析出解决问题需要的步骤,然后用函数把这些步骤一个个实现,使用的时候依次调用。面向过程的核心是解决问题的过程。
面向对象(OOP):把需要解决的问题分解成一个个对象,建立对象不是为了实现一个步骤,而是为了描述每个对象在解决问题中的行为,面向对象的核心是对象。
举个例子,怎样将大象装进冰箱?
面向过程做法:①打开冰箱门;②将大象装进冰箱;③关上冰箱门。
面向对象要先找出对象:大象和冰箱。对于大象来说,它的功能有一个:进入冰箱。对于冰箱来说,它的功能有两个:打开与关闭,面向对象就是使用大象和冰箱的功能。
面向对象有如下优点:
1、模块化更深,封装性强。
2、更容易实现复杂的业务逻辑。
3、更易维护、易复用、易扩展。
面向对象的特征如下:
1、封装性:对象是属性(静态特征)和行为(动态特征)的结合体(封装体)。
2、继承性:父类(基类)的属性和行为可以派生到子类中。子类不需要重复定义。
3、多态性:同一个消息传递给不同的对象,得到的结果不同。
2 ES6中的类和对象
2.1 类和对象概述
对象:万物皆对象,对象是一个具体的事物,看得见摸得着的事物。例如,一本书、一辆汽车、一个人可以是对象,一个数据库、一张网页、一个与远程服务器的连接也可以是对象。
在JavaScript中,对象是一组无序的的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。
对象是由属性
和方法
组成的:
- 属性:事物的特征,在对象中用属性来表示(名词)
- 方法:事物的行为,在对象中用方法来表示(动词)
类:类抽象了对象的公共部分,它泛指某一大类。比如说在设计汽车的过程中,汽车的设计图纸就可以看作一个类,而根据该图纸生产出来的汽车就是这个类的一个一个对象。
类是具有相同属性和行为的对象的抽象,对象是类的实例(类的具体体现)。
2.2 类的基本语法
创建类时使用class
关键字,并且类名习惯性首字母大写,语法为:
class 类名 {
// 具体代码
}
利用类创建对象使用new
关键字来实现,语法为:
var 对象名 = new 类名([参数]);
示例代码:创建一个Person类,并创建一个p对象
// 创建类
class Person {
}
// 创建p实例对象
var p = new Person();
在类中可以定义constructor()
构造方法。constructor()
方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过new命令生成对象实例时,自动调用该方法。如果没有显示定义,类内部会自动给我们创建一个constructor()
,它的语法为:
class 类名 {
constructor([参数]) { // 构造方法:用来初始化对象的成员
// 方法体;
}
}
示例: 创建一个Person类,并初始化对象的成员
// 创建类
class Person {
// 定义构造方法
constructor(name, age) {
// this.x = x; 第一个x表示对象的属性,第二个x是传入的参数
// this 指向创建的实例
this.name = name;
this.age = age;
}
}
当使用new
创建实例对象时,就会自动执行constructor()
,那么参数就会传递给constructor()
,从而给对象添加相关的属性,如下所示:
// 创建实例对象
var xm = new Person("小明", 10);
var xh = new Person("小红", 11);
console.log(xm); // Person { name: '小明', age: 10 }
console.log(xh); // Person { name: '小红', age: 11 }
对象成员(包括属性和方法)的访问有两种方法,在ES6中constructor()
构造方法不能显式调用,具体语法如下:
// 方法一
对象名.属性名
// 方法二
对象名.方法名([参数])
示例:访问xm
的姓名和年龄
xm.name // 小明
xm.age // 小红
2.3 类中的方法
为类添加方法不需要写function
,多个函数方法之间也不需要加逗号分隔,具体语法为:
class 类名 {
constructor([参数]) { // 构造方法:用来初始化对象的成员
// 方法体;
}
方法名([参数]) {
// 具体代码
}
}
示例:为Person类添加一个say()方法并调用
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
say(address) {
console.log("你好,我叫" + this.name + ",我来自" + address);
}
}
var xm = new Person("小明", 10);
var xh = new Person("小红", 11);
xm.say("北京"); // 你好,我叫小明,我来自北京
xh.say("上海"); // 你好,我叫小红,我来自上海
2.4 类的继承
在JavaScript中,继承用来表示两个类之间的关系,子类可以继承父类的一些属性和方法,在继承以后还可以增加自己独有的属性和方法。继承使用extends
关键字来实现。
父类:又称为基类或超类,被继承的类
子类:又称为派生类,由父类派生来的类
语法为:
class 子类名 extends 父类名 {
constructor() {
}
//其他的成员方法
}
示例代码:定义父类Father和子类Son,实现继承
class Father {
eye() {
console.log("我有大眼睛");
}
}
class Son extends Father { }
// 创建子类实例对象
let s1 = new Son();
// 继承父亲的大眼睛
s1.eye(); // 我有大眼睛
2.5 super关键字
super
关键字用于访问和调用对象在父类上的方法,可以调用父类的构造方法,也可以调用父类的普通方法。也就是说,super关键字代表父类,用来访问和调用父类的成员。
具体语法如下:
// 调用父类的构造方法语法
super([参数]);
// 调用父类的普通方法
super.方法名([参数])
示例代码:子类Son调用父类Father的普通方法eye()
class Father { // 父类
eye() {
console.log("我有大眼睛");
}
}
class Son extends Father { // 子类
eye() {
super.eye();
}
}
// 创建子类实例对象
let s1 = new Son();
s1.eye(); // 我有大眼睛
在上面的代码中,子类和父类都有eye()方法,那么在调用某个方法时,会先看子类有没有这个方法,如果有就先执行子类的方法;如果子类中没有,就会去查找父类有没有这个方法,使用就近原则。
子类在构造函数中使用super
,必须放到this前面(先调用父类的构造方法,再调用子类的构造方法)。示例代码如下:
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() { // 定义加和的方法sum
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
super(x, y); // super必须在子类的this之前调用
this.x = x;
this.y = y;
}
subtract() { // 定义一个子类特有的做减法的方法
console.log(this.x - this.y);
}
}
var son = new Son(10, 5); // 实例化对象
// 调用父类的加和的方法
son.sum(); // 15
// 调用自己的减法方法
son.subtract(); // 5
2.6 类中的this指向
1、constructor()
构造方法中的this
指向的是创建的实例对象。
// 打印构造函数中的this,得到了实例对象
class Person {
constructor(name) {
this.name = name;
console.log(this); // Person { name: '小明' }
}
}
var xm = new Person("小明");
或者可以将构造函数中的this
与xm
相等,输出true
,所以this
指向的是创建的实例对象。
var that; // 用来保存constructor中的this
class Person {
constructor(name) {
that = this;
this.name = name;
}
}
var xm = new Person("小明");
console.log(that === xm); // true
2、普通方法中的this
看谁调用了这个方法,谁调用了这个方法this
就指向谁。
// 实例对象调用say()方法,this指向实例对象
var that;
class Person {
constructor(name) {
that = this;
this.name = name;
}
say() {
console.log(this);
}
}
var xm = new Person("小明");
xm.say(); // Person { name: '小明' }
console.log(that === xm); // true
如果在页面上写一个按钮,当点击按钮时输出当前的this值,那么最后输出的对象是该按钮,this指向了该按钮。
<button>点击按钮</button>
<script>
class Person {
constructor(name) {
this.name = name;
this.btn = document.querySelector("button"); // 获取按钮元素
this.btn.onclick = this.say; // this.say后面不加(),因为要点击之后再调用
}
say() {
console.log(this); // <button>点击按钮<button>
}
}
var xm = new Person("小明");
</script>
3 案例
设计雇员Employee类,记录雇员的情况,包括姓名、年薪、受雇时间,要求定义MyDate类作为受雇时间,其中包括工作的年、月、日,并用相应的方法(构造方法、显示信息的方法)对Employee类进行设置。
class MyDate { // 定义MyData类作为受雇时间
constructor(year, month, date) { // 年、月、日
//初始化对象的成员
this.year = year;
this.month = month;
this.date = date;
}
show() { // 构造显示受雇时间的方法
console.log(this.year + "年" + this.month + "月" + this.date + "日")
}
}
class Employee { // 定义雇员类
constructor(name, salary, workDate) { // 姓名、薪资、受雇时间
this.name = name;
this.salary = salary;
this.workDate = workDate;
}
disp() { // 构造显示姓名、年薪、受雇时间的方法
console.log("姓名:" + this.name);
console.log("年薪:" + this.salary);
console.log("受雇时间:");
this.workDate.show()
}
}
var work1 = new MyDate(2021, 6, 15); // 创建受雇时间对象
var e1 = new Employee("周瑜", 400000, work1); // 创建雇员对象
e1.disp();