三、class的继承
继承是面向对象编程中一个非常重要的概念,那什么是继承呢?
(1)继承的概念
继承就是使一个类获得另一个类的属性和方法。就好比一个手艺精湛的师傅传授给你他所有的毕生绝学,那么就相当于你继承了他,此时你既学会了你师傅教你的技能,同时你也一定有属于自己的技能,这不是从你师傅那学来的。
(2)ES5中实现继承
其实在ES5中是通过修改原型链实现继承的,我们可以来看一下简单的例子
// 创建构造函数 Parent function Parent() { // 定义了实例对象属性 name1 this.name1 = 'parent' } // 为 Parent原型定义方法 show1 Parent.prototype.show1 = function() { console.log('我是Parent的show1方法') } // 创建构造函数 Child function Child() { this.name2 = 'child' } // 将构造函数 Child的原型设置成 Parent的实例对象 Child.prototype = new Parent() // 为Child原型定义方法 show2 Child.prototype.show2 = function() { console.log('我是Child的show2方法') } // 生成实例对象 child var child = new Child() console.log(child.name1) // parent console.log(child.name2) // child child.show1() // 我是Parent的show1方法 child.show2() // 我是Child的show2方法
我们可以看到,我们通过改变构造函数 Child
的原型 prototype
为构造函数 Parent
生成的实例对象,实现了继承,即通过构造函数 Child
生成的实例对象具有 Parent
中定义的属性name1
和方法show1
,同时也具有属于自己的属性name2
和方法show2
(3)ES6中class实现继承
ES5中实现继承的写法显然有些麻烦,所以在 class
类中,我们可以通过关键字 extends
来实现继承
我们来改写一下ES5中的继承实现
class Parent{ constructor() { this.name1 = 'parent' } show1() { console.log('我是Parent的show1方法') } } // Child类 继承 Parent类 class Child extends Parent{ constructor() { super(); this.name2 = 'child' } show2() { console.log('我是Child的show2方法') } } var child = new Child() console.log(child.name1) // parent console.log(child.name2) // child child.show1() // 我是Parent的show1方法 child.show2() // 我是Child的show2方法
继承得实现整体上看上去非常得简洁
在上述代码中,我们看到了,我们在定义 Child
类时用到了关键字 extends
,申明了 Child
类继承Parent
类,同时在 Child
类得 constructor
函数中调用了 super
函数。仅仅用两个关键字就实现了继承,这里我们要对 super
进行详细得讲解
(4)super
在ES6中规定了,在子类继承了父类以后,必须先在子类的 constructor
函数中调用 super
函数,其表示的就是父级的 constructor
函数,作用就是为子类生成 this
对象,将父类的属性和方法赋值到子类的 this
上。因此,若没有调用 super
函数,则子类无法获取到 this
对象,紧接着就会报错
class A{ constructor() { this.name1 = 'A' } } class B extends A{ constructor() { this.name2 = 'B' } } var b = new B() /* this.name2 = 'B' ^ ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor */
上述代码中,B
类继承 A
类,但 B
类的 constructor
函数中没有调用 super
函数,因此没有生成 this
对象,所以 this.name2 = 'B'
就报错了
若子类省略了 constructor
函数,则默认会帮你调用 super
函数的
class A{ constructor() { this.name1 = 'A' } } class B extends A{ } var b = new B() // 没有报错
super()
代表的是父类的构造函数,其实 super
还可以作为对象使用,即不作为函数调用。当 super
在子类的普通方法内时,指向的是父类的原型对象;在子类的静态方法内时,指向的时父类
class A{ show1() { console.log('我是A类的show1方法') } } class B extends A{ constructor() { super() } show2() { super.show1() } } var b = new B() b.show2() // 我是A类的show1方法
上述代码,B
类继承 A
类,其中 A
类有一个 show1
方法,是写在其原型 A.prototype
上的,而在 B
类的 show2
方法中调用了 super.show1()
,我们说过 super
在普通的方法中指向的是父类的原型对象,所以 super.show1()
相当于 A.prototype.show1()
我们再来看一个 super
在子类的静态方法中的例子
class A{ static hide1() { console.log('我是A类的hide1方法') } } class B extends A{ constructor() { super() } static hide2() { super.hide1() } } B.hide2() // 我是A类的hide1方法
上述代码,B
类继承 A
类,B
类在其静态方法 hide2
中调用了 super.hide1()
,因为 super
在静态方法中指向的是父类,所以 super.hide1()
就相当于 A.hide1()
说到静态方法,其实类的继承,也是可以继承静态方法的
class A{ static show() { console.log('我是A类的show方法') } } class B extends A{ } B.show() // 我是A类的show方法
还需要注意的是,当我们在子类的普通方法中通过 super
调用父类的方法时,方法中的 this
指向的是当前子类的实例对象
class A { constructor() { this.name = 'Jack' } show1() { console.log(this.name) } } class B extends A{ constructor() { super(); this.name = 'Lpyexplore' } show2() { super.show1() } } var b = new B() b.show2() // Lpyexplore
那么,当我们在子类的静态方法中通过 super
调用父类的方法时,方法中的 this
指向的是子类,而不是子类的实例对象
class A { constructor() { this.x = 1 } static show1() { console.log(this.x) } } class B extends A{ constructor() { super(); this.x = 2 } static show2() { super.show1() } } B.show2() // undefined B.x = 3 B.show2() // 3
上述代码中,我们在 B
类的静态方法 show2
中通过 super
调用了 A
类的静态方法 show1
,执行代码 console.log(this.x)
,此时的 this
指向的是 B
类,但因为 B
类的 constructor
函数中定义的属性 x
是定义在 B
类的实例对象上的,所以 this.x
返回的是 undefined
。
所以我们在 B
类上定义一个属性 x
并且值为 3
,此时再此调用 B.show2()
,返回的就是 3
了。
(5)prototype和__proto__
在 class
类中有两个属性,分别表示着一条继承链,即 prototype
和 __proto__
子类的__proto__
总是指向父类;子类的 prototype
的 __proto__
总是指向父类的原型
我们来验证一下
class A{} class B extends A{} console.log(B.__proto__ === A) // true console.log(B.prototype.__proto__ === A.prototype) // true
四、class类的补充
对于 class
类还有几点需要补充以下
(1)不存在变量提升
构造函数本身就是个函数,存在变量提升,所以通过构造函数生成实例对象时,可以将构造函数写在生成实例对象的代码后面
var person = new Person() function Person() { this.name = 'Lpyexplore' } // 没有报错
虽然 class
类的数据类型也属于 function
,但是它是不存在变量提升的,即不可以在申明类之前生成实例对象,否则就会报错
var person = new Person() class Person{} /* 报错: var person = new Person() ^ ReferenceError: Cannot access 'Person' before initialization */
(2)new.target
class
类必须通过 new
来生成实例对象,因此ES6引入了一个新的属性 new.target
,该属性一般用于 constructor
函数中,表示通过关键字 new
作用的构造函数的名称,若不是通过 new
命令调用的,则返回 undefined
class A{ constructor() { if(new.target === 'undefined') { console.log('请通过关键字new调用') } else { console.log(new.target) } } } var a = new A() // [class A]
当子类继承父类,并调用父类的 constructor
函数时,new.target
返回的是子类的构造函数名以及继承自哪个父类
class A{ constructor() { console.log(new.target) } } class B extends A{ constructor() { super() } } var b = new B() // [class B extends A]
五、结束语
好了,ES6的 class
语法就讲到这里,希望这篇文章能帮助到大家。