当前位置:   article > 正文

【es6入门】类的声明与继承,class与extends语法糖,如何用原型模拟继承_extends类与类

extends类与类

前言

es5其实从严格意义上来说并没有类的写法,也没有继承的写法,如果想实现类还需要手写各种模式的构造函数(结合原型),已经十分过时了。

es6后续语法中将其统一,提供了官方的类的声明。

虽然刚刚说es5的相关写法已经十分过时了,但架不住面试会问啊,我放在后面补充。


class来声明类

看代码:

class Parent {
    constructor(name, age) {
        this.name = name; // 意思是给类添加一个name属性,值为实例化后传进来的name
        this.age = age;
    }
    showName() { // 写方法
        console.log(this.name);
    }
    
    // 以下是直接赋值法(这个初学者很容易忽略)
    cando = '吃饭' // 直接实例化对象添加属性
    candoFn = function () {
        console.log('candoFn');
    } // 直接给实例化对象添加一个方法属性
	
	//  静态方法
    static nocandoFn() {  // 静态方法,给类本身添加一个方法,实例化对象拿不到
        console.log('nocandoFn') 
    } 
}
let p1 = new Parent('xiaoqiang', 123); // 实例化一个类,获取一个新对象
p1.showName() // xiaoqiang
console.log(p1.cando); // 吃饭
p1.candoFn() // candoFn
// p1.nocandoFn() // 报错

console.log(Parent.cando); // undefined
// Parent.candoFn() // 报错
Parent.nocandoFn() // nocandoFn
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

咱们把p1打印出来看看

在这里插入图片描述

结合图片总结下

  • class能够直接声明一个类

  • constructor是指定的构造函数,当实例化以后被自动执行,里面写属性,实例化后的属性都给到对象本身

  • 类中定义的方法最终是挂在对象的原型上的,实例化对象调用时,也是通过隐式引用到原型上执行

  • 如果是直接赋值法定义的属性和方法,就被定义到实例化对象本身上了。且类是无法拿到这些东西的,必须实例化

  • 静态方法只有类能直接拿到


extends赖继承

继承上面的父类:

class Parent {
    constructor(name, age) {
        this.name = name; 
        this.age = age;
        this.weight = '120kg'
    }
    showName() { 
        console.log(this.name);
    }
    showWeight() {
        console.log(this.weight);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
class Child extends Parent{
}
let c1 = new Child('a')
c1.showName() // a
  • 1
  • 2
  • 3
  • 4
class Child extends Parent {
    constructor(name) { 
        super(name) //super 必须先调这个,且当想在实例化的时候用到父类中某些定义的属性,需要手动写入参
    }
}

let c1 = new Child('b')
c1.showName() // a
c1.showWeight() // 120kg
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
class Child extends Parent{
    constructor(name, age, gender) {
        super(name, age);  
        this.gender = gender // 子类自己的方法
    }
    // 可以添加子类新方法...
    showGender(){
    	console.log('gender', this.gender)
    }
}

let c1 = new Child('a', 13, 'male')
c1.showName() // a
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

打印出子类实例:

在这里插入图片描述

总结下:

  • extends声明继承关系,自动继承父类方法与所有constructor里的属性

  • 所以如果子类不需要新增属性,那么constructor可以省略不写

  • 如果子类需要新增属性,写constructor的同时,在里面必须第一个调用super()去继承父类的this,因为子类自己是没有this的,并且还可以通过入参指定继承父类哪些属性;

  • 父类的方法自动继承到子类实例的原型的原型上(如果子类改写了父类的方法,也只是添加到子类实例的原型上,原型的原型上父类的方法还在)

  • 补充:static的静态方法子类也是能够继承的。

继承的好处

  1. 新建类时,可以继承有相同内容的类的基础上添加内容;
  2. 父类内容有扩展、变动时,继承的子类自动跟随;

super的补充

如果想在子类的方法中调用父类的方法,可以这样子:

class Animal {
    constructor(name) {
        this.name = name;
    }

    speak() {
        console.log(this.name + ' makes a noise.');
    }
}

class Dog extends Animal {
    speak() {
        super.speak(); //调用父类的speak方法
        console.log(this.name + ' barks.');
    }
}

let d = new Dog('Mitzie');
d.speak(); //输出:"Mitzie makes a noise." 和 "Mitzie barks."
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

这里有个讨论,super究竟是什么?这兄弟总结的还不错可以看看,地址


this的指向

class Person {
	constructor(name) {
		this.name = name
	}
	fn = function() { console.log(this) }
	fn1(){
		console.log(this, this.name) 
	}
	fn2 = () => { console.log(this) } // 2 箭头函数,指向实例
	fn3 = () => { console.log(this) }
	static fn4() { console.log(this) } // 4 静态属性,指向class本身
}
const man = new Person('小夏')
man.fn() // 打印实例
man.fn1() // 打印实例 ‘小夏’
man.fn2() // 打印实例
man.fn3.call(this) // 箭头函数不能改变指向,打印实例
Person.fn4() // class本身
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

总结

  • 类定义的普通函数,指向实例化的对象
  • 直接赋值法的箭头函数,普通函数,都指向实例化的对象
  • 静态方法,指向类本身

有一个知识可以看看,如果是man.__proto__.fn1()去调用,this会指向什么?

this只会是指向原型自己,因为是__proto__自己去调用了,所以this.name为undefined。

那有人会说man调用fn1()也是通过原型去调的,为什么this指向就没问题,是因为JS底层帮你把this指向处理好了。


关于typeof的判断

typeof=== 'function' // true
  • 1

说明class其实是由函数包装而来的一种语法糖;


用原型来实现继承

如何用原型来实现继承?这个问题主要的考察点还是你对原型对象和构造函数的理解。

可以先看我这篇文章,理解原型的概念。

接下来,让我来细细给你讲明白~

先写父类

首先咱们先写个父类:

function Animal(name) {
    this.name = name; // 只有一个属性,name
}
  • 1
  • 2
  • 3

这个时候咱们去new一个变量,this就会指向这个新的变量,name就会赋值给这个变量。

let A = new Animal('dog')
console.log(A);
  • 1
  • 2

在这里插入图片描述
想加方法咋办呢?前面我们也知道es6 class里的方法最终是加在了实例对象的原型上,这个原型其实就是构造函数Animal的原型。

下面我为了图方便,把实例对象上的__proto__也写成prototype哈哈,能明白就行

在这里插入图片描述

所以咱们加方法的话就是直接给Animal的原型加上即可:

Animal.prototype.sayHello = function () {
    console.log("Hello, my name is " + this.name);
};

console.log(A);
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述
咱们可以用class写个同样的父类然后实例化打印出来看看:

class Animal1 {
    constructor(name) {
        this.name = name
    }
    sayHello() {
        console.log("Hello, my name is " + this.name);
    }
}
let animal1 = new Animal1('animal1')
console.log('animal1', animal1);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在这里插入图片描述

看起来差不多

再写子类

来写子类,并且先只考虑继承父类的属性name

function Dog(name, breed) {
    Animal(name) // Animal本质是个函数
    this.breed = breed;
}
// 相当于
function Dog(name, breed) {
    this.name = name
    this.breed = breed;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

但是!!!!咱们这里要考虑this的指向,当我们执行Animal()时,他里面的this是指向window的。

this的指向看我这篇文章,很好懂的

这时候你去实例化子类

function Animal(name) {
    console.log('Animal+this', this); // 父级补充个打印吧
    this.name = name;
}
let dog = new Dog('wang', '1')
console.log('dog', dog);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在这里插入图片描述

因为this并不是指向新对象上,所以可以看到name没有加在实例化子类对象上,加到window上了哈哈哈哈。

咋办?改写this指向呗!

function Dog(name, breed) {
    Animal.call(this, name); // 把Animal里的this强行指向子类
    this.breed = breed;
}
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述
完美还差一步,咋继承父类的方法?

咱都知道原型链可以找到不属于自己身上的属性或方法,你想想,是不是可以在Dog.prototype和最高层的Object的原型中间夹一层Animal.prototype呢?

说干就干!

Dog.prototype = Object.create(Animal.prototype); // 以Animal.prototype为一个空对象的原型,创建一个新的空对象,赋值给Dog.prototype
  • 1

是不是有点晕,画个图给你看就明白了!

在这里插入图片描述
在这里插入图片描述

你会发现打印出来后,新的原型对象原型少了constructor,咱给加上:

Dog.prototype.constructor = Dog; // 还原 constructor
  • 1

在这里插入图片描述

这个新的原型对象就是Dog的新原型对象,他的prototype属性指向父类的原型对象。

所以这时候就可以直接在dog对象上调用父类方法了,利用的就是原型链模拟继承。

所以以后给子类加新方法直接:

Dog.prototype.bark = function () {
    console.log("Woof!");
};
  • 1
  • 2
  • 3

在这里插入图片描述
对比下class写法:

class Animal1 {
    constructor(name) {
        this.name = name
    }
    sayHello() {
        console.log("Hello, my name is " + this.name);
    }
}
// let animal1 = new Animal1('animal1')
// console.log('animal1', animal1);
class Dog1 extends Animal1 {
    constructor(name, breed) {
        super(name)
        this.breed = breed;
    }
    bark() {
        console.log("Woof!");
    }
}
let dog1 = new Dog1('dog1', '1')
console.log('dog1', dog1);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

在这里插入图片描述
基本一样了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/78761
推荐阅读
相关标签
  

闽ICP备14008679号