ES6实现继承
在前一篇的文章,我们学习了ES5通过原型 原型链实现继承的方式,那么ES6中是如何实现继承,其背后的原理又是怎样的?
建议在看这篇文章之前,要先阅读我的前一篇文章,会让你有醍醐灌顶的想法
重学JavaScript高级(六):以面向对象原型继承(ES5)搞懂原型原型链
认识class定义类
- ES5之前定义类的方式,使用
Function
进行定义,这种方法和 普通函数过于相似 - 所以在ES6中将
class
提升到了关键字 - 在
new Person
后,其内部的操作,与ES5的操作一致(具体操作可以看先前的文章) - 以下是定义方法
//拥有高内聚,低耦合的特点
class Person{
constructor(name,age){
//用于接收参数,默认调用
this.name= name
this.age = age
}
//这是实例方法,实际上是将方法添加到了Person.prototype中,是一种语法糖
running(){
console.log(this.name+"running")
}
}
let p1 = new Person("zhangcheng",18)
console.log(p1.__proto__ === Person.prototype)
与function的区别
- 通过以下代码,可以看出class定义的类,就是function定义类的语法糖,几乎一模一样
- 但是class定义的类,不能当作单独的函数调用,只能和new一起使用
//拥有高内聚,低耦合的特点
class Person1{
constructor(name,age){
//用于接收参数,默认调用
this.name= name
this.age = age
}
//这是实例方法,实际上是将方法添加到了Person.prototype中,是一种语法糖
running(){
console.log(this.name+"running")
}
}
//构造函数Person
function Person2(name) {
this.name = name;
}
//只能通过p1调用
Person.prototype.study = function () {
console.log("123");
};
let p1 = new Person1("zhangcheng",18)
let p2 = new Person2("lisi")
console.log(Person1.prototype === p1.__proto__)//true
console.log(Person2.prototype === p2.__proto__)//true
console.log(Person1.prototype.constructot) //Person1
console.log(Person2.prototype.constructot) //Person2
类的访问器方法(access)
监听对某个属性的读取以及写入
- 先来回顾对象中访问器方法的编写
//通过存储属性描述符进行编写
let obj = {
name: "zhangcheng",
};
let _name = "";
Object.defineProperty(obj, "name", {
set: function (value) {
_name = value;
console.log("set方法被调用了");
},
get: function () {
console.log("get方法被调用了");
return _name;
},
});
obj.name = "lisi";
console.log(obj.name);
- 以下就是类的访问器编写方法
- 实际开发中用的比较少
class Person1 {
constructor(name, age) {
//用于接收参数,默认调用
this.newName = name;
this.age = age;
}
//这是实例方法,实际上是将方法添加到了Person.prototype中,是一种语法糖
running() {
console.log(this.name + "running");
}
//在定义set的时候,一定要传入参数,否则会报错
set newName(value) {
this.name = value;
}
get newName() {
return this.name;
}
}
let p1 = new Person1("zhangcheng", 123);
console.log(p1.newName);
- 具体应用场景
- 类的访问器主要用在数据处理上面
- 比如我们现在写一个矩形的类,需要传入它的位置信息和大小信息
class Shape {
constructor(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
// position属性返回x值和y值
get position() {
return { x: this.x, y: this.y };
}
//size属性返回widht值和height值
get size() {
return { width: this.width, height: this.height };
}
}
let s1 = new Shape(10, 20, 100, 200);
console.log(s1.position); //{ x: 10, y: 20 }
console.log(s1.size);//{ width: 100, height: 200 }
类的静态方法(ES5的类方法)
通过类直接调用Person().running
- 在类里面写方法的时候,前面加上
static
即可
class Person {
constructor(name) {
this.name = name;
}
static personRunning() {
//这里的this指向的就是Person类本身
console.log("personRunning");
}
}
//类直接进行调用
Person.personRunning();
通过extends实现继承
- 首先 定义一个父类
- 子类在后面通过 extends进行继承父类
- 在子类的构造方法中(constructor)使用super关键字,调用父类的 属性
class Person {
constructor(pName, age) {
this.pName = pName;
this.age = age;
}
running() {
console.log("running");
}
}
class Teacher extends Person {
constructor(pName, age, title) {
//super一定要用在子类this前面
super(pName, age);
this.title = title;
}
teach() {
console.log("teach");
}
}
class Student extends Person {
constructor(pName, age, sno) {
//super一定要用在子类this前面
super(pName, age);
this.sno = sno;
}
study() {
console.log("study");
}
}
let tea1 = new Teacher("wanglaoshi", 58, "教授");
let stu1 = new Student("zhangcheng", 18, 200);
console.log(tea1);
console.log(stu1);
tea1.running();
stu1.running();
- super不仅可以在子类的构造方法中使用,也可以在实例方法和静态方法中使用
- 需要注意:子类的实例方法只能用super调用父类的实例方法,子类的静态方法亦是如此
class Person {
constructor(pName, age) {
this.pName = pName;
this.age = age;
}
running() {
console.log("running");
}
static walk() {
console.log("walk");
}
}
class Student extends Person {
constructor(pName, age, sno) {
//super一定要用在子类this前面
super(pName, age);
this.sno = sno;
}
//实例方法
study() {
console.log("study");
//只能用super调用父类的实例方法
super.running();
}
static play() {
console.log("play");
//只能用super调用父类的静态方法
super.walk();
}
}
let stu1 = new Student("zhangcheng", 18, 200);
stu1.study();
Student.play();
-
重写的理解
当我们对父类的方法,不满意的时候,在子类中可以重新编写一下这个方法,就叫重写
class Person {
constructor(pName, age) {
this.pName = pName;
this.age = age;
}
running() {
console.log("running");
}
static walk() {
console.log("walk");
}
}
class Student extends Person {
constructor(pName, age, sno) {
//super一定要用在子类this前面
super(pName, age);
this.sno = sno;
}
//实例方法重写
running() {
console.log("Student running");
}
//静态方法重写
static walk() {
console.log("Student walk");
}
}
let stu1 = new Student("zhangcheng", 18, 200);
stu1.running();
Student.walk();
继承内置类
比如继承js提供的内置类,对一些方法进行扩展
class ZcArray extends Array {
//打印数组的最后一位
lastItem() {
//这里的this指向的就是调用方法的实例,隐式绑定
//不理解的可以去看我的第一篇文章,this指向问题
console.log(this[this.length - 1]);
}
}
let arr = new ZcArray(10, 20, 30);
arr.lastItem();
类的混入mixin(用的不多)
JS的继承只能单继承,意思就是一个子类只能有一个父类
注意:主要是掌握这种思想,在后期React中,高阶组件会有类似的写法
- 现在有三个类
Bird、Flyer、Animals
,需要实现Bird
要继承Flyer、Animals
class Animals {
running() {
console.log("running");
}
}
class Flyer {
fly() {
console.log("flying");
}
}
class Bird {
getName() {
console.log("my name is bird");
}
}
- 其中一个方法就是,借助 mixin的思想
- 实际上是通过创建多个类,去一层一层的继承
function mixinAnimals(BaseClass) {
return class extends BaseClass {
running() {
console.log("running");
}
};
}
function mixinFlyer(BaseClass) {
return class extends BaseClass {
fly() {
console.log("flying");
}
};
}
class Bird {
getName() {
console.log("my name is bird");
}
}
class NewBird extends mixinAnimals(mixinFlyer(Bird)) {}
let bird = new NewBird();
bird.running();
bird.fly();
bird.getName();
babel可以将ES6转成ES5代码
- 大家可以访问https://babeljs.io/,将ES6的class类代码,转成ES5代码
JavaScript中的多态
面向对象的多态
继承是多态的前提
- 维基百科中的定义
- 多态指的是为不同数据类型的实体,提供统一的接口,或者使用一个单一的符号,来表示多个不同的类型
- 个人的理解
- 不同的数据类型,进行同一个操作(访问同一个接口),表现出不同的行为(得出不同的结果),就是多态的表现
- 在下面的代码中,我们就实现了将不同的数据类型,传入同一个函数,得出了不同的结果
/*
1.定义一个父类Shape
2.定义两个子类均继承父类
3.写一个函数,里面均让传入的参数调用getArea方法
4.实例出两个子类的对象,并传入函数中
5.这种表现形式可以称为多态
*/
class Shape {
getArea() {}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
class Circle extends Shape {
constructor(r) {
super();
this.r = r;
}
getArea() {
return this.r * this.r * 3.14;
}
}
/**
严格意义的面向对象中
1.必须有继承
2.必须有父类引用指向子类对象
*/
function getShapeArea(shape) {
//在严格的面向对象编程语言中,形参是有类型控制的,类似于shape:Shape,传入的参数必须是Shape类型的,在此就是父类引用
console.log(shape.getArea());
}
let rect1 = new Rectangle(10, 10);
let circle = new Circle(10);
getShapeArea(rect1);
getShapeArea(circle);
JS面向对象多态的表现
从维基百科的定义出发,那么JS中到处都是多态
- JS中针对不同数据类型,提供统一的接口,表现不同的行为
function sum(a,b){
return a+b
}
sum(200,300)
sum("hello",123)
- 使用一个单一的符号,来表示多个不同的类型
//同一个标识符foo
let foo = 123
foo = "zhangcheng"
foo = {
a:100
}
- 对于多态的理解不同人有不同的看法,有的人认为这种不算严格意义上的多态,因此以上只是我个人的理解