接上篇 ES5对象创建与继承
搞清楚ES5是怎么创建对象与继承的之后,接下来看看,ES6的对象创建与继承。
开头先定个性,ES6的类(也就是class),其实是ES5创建对象的一种语法糖,本质上还是基于原型的对象创建形式以及继承方式。
再说透一点,class的继承其实就是寄生组合式继承。
类
类定义
ES6中类有2中定义方式
- 类声明
- 类表达式
// 类声明
class A {}
// 类表达式
var a = class A {} // A可写可不写
需要注意的是,和函数声明不同的是,类声明不能提升,在声明之前调用,会报错。
类的构成
- 构造函数方法
- 实例方法?(是实例方法还是原型方法?)
- get、set
- 静态类方法
所有以上部分,都不是必须的,空的类也能声明。
name属性
name 可以访问到 类名称,内部可以通过 类标识符[name]访问 以及 变量[name]访问,外部只能通过 变量[name]访问,见代码:
var Person = class PersonName{
identiify () {
console.log(Person.name, PersonName.name)
}
}
let p = new Person()
p.identiify() // PersonName PersonName
console.log(Person.name) // PersonName
console.log(PersonName.name) // 报错
类构造函数 constructor
类的构造函数方法 constructor ,可以理解成es5中的构造函数,在new关键字调用的时候执行。
constructor不是必须写的,如果不写,会自动补全一个空函数给constructor。
实例化
通过new关键字调用类的时候,相当于调用constructor进行实例化,步骤与ES5的new关键字调用步骤是一样的,简单回顾如下:
- 先创建一个空对象O
- 将对象O上的 [[prototyp]] 属性,指向构造函数的 prototype对象
- 在O之上调用constructor,将构造函数中绑定在this上的属性绑定到对象O上(可以理解为constructor.call(O))
- 返回对象O
new ClassName() 是否需要这个括号?
可写可不写。当需要constructor传参的时候需要,不需要传参的时候可以不写。
var Person = class PersonName {
constructor (name) {
this.name = name
}
}
let p1 = new Person // 可以创建
let p2 = new Person('Kobe') // 也可以创建
constructor执行后返回的是什么?
当constructor 有显式return返回值时,那么new接收到的就是constructor的返回值。没有return时,默认返回this。
这里要注意,如果显式了return了值,那么实例与class之间,instanceOf的链接就断了。
class Person {
constructor (override) {
if (override) {
return { name: 'new instance'}
}
}
}
let p1 = new Person
let p2 = new Person(true)
console.log(p1 instanceof Person) // true
console.log(p2 instanceof Person) // false
类构造函数能不能像普通构造函数一样不用new调用?
不行。会报错。
“类”的数据类型是什么?
是函数。
到目前为止,JS中还没有正式的class类型,class的类型实际上是function。
class Person {}
typeof Person // function
实例属性、原型属性 和 其他类成员
实例属性
这里实例属性,泛指属性以及方法。
想要给 class 添加实例属性,只要将属性在constructor方法内部绑定到其this上即可。
通过new关键字调用类构造函数,会将类构造函数中this上的属性全部赋值给新创建的实例,且不会在各实例之间共享。
在实例化之后,也可以为新的实例添加实例属性或者修改已有实例属性。
class Person {
constructor () {
this.name = 'Bob'
this.nickName = ['Will', 'Jean']
}
}
let p1 = new Person()
let p2 = new Person()
console.log(p1.name) // Bob
console.log(p2.name) // Bob
console.log(p1.name === p2.name) // false
p1.name = p1.nickname[0]
p2.name = p2.nickname[1]
console.log(p1.name) // Will
console.log(p2.name) // Jean
多说一句,对比ES5的继承,实际上,属性是更适合定义在constructor上的,但方法不适合。原因再简单回顾一下:
原型上定义引用类型的属性,会被实例共享;
构造函数内定义方法,会有代码复用的问题。
原型方法与访问器
一句话,在类块中定义的方法,就是原型方法。
class Person {
constructor () {
this.name = 'Bob'
this.sayName = function () { // 定义在实例的方法
console.log(this.name + ' in instance')
}
}
sayName () {
console.log(this.name + ' in prototype') // 定义在原型的方法
}
}
类块中能不能定义原始值类型或对象类型属性?
不可以,会报错。类块中,只能定义方法。
class Person {
name: 'Bob' // 不合法,会报错
}
这里提一句,“实例属性的新写法”,简单说就是,在类块的最顶部,用赋值表达式,可以为实例先定义一批属性,相比于写在constructor里面的好处是,一上来就能看到所有的实例属性。(个人建议不要这么做,简化操作,避免出错)
class Person {
name = 'Bob'
age = 20
constructor () {}
}
getter和setter
类中支持定义属性的getter和setter,写法是直接将get、set方法写在类块中。
class Person {
get name () {
return this.name_ // 这里为什么是name_?
}
set name (newName) {
this.name_ = newName // 这里为什么是name_?
}
}
let p = new Person();
p.name = 'Jake';
console.log(p.name); // Jake
上面代码,为什么get和set中,赋值的属性是this.name_而不是this.name?
这里其实是创建了一个新的私有属性,来代替name,因为如果在set内部调用this.name,相当于在递归调用set,最终结果就是“栈溢出”。
静态类方法
什么是静态类方法?
只能通过类本身调用,且不会被实例继承的方法。注意,这里说的是“方法”。
用关键字 static,在类块内部创建,或者,直接给类添加也可以。
class Person {
static staticFun () { console.log('im a static function') } // 在类块内部创建静态类方法
}
// 在外部创建静态类方法
Person.staticFun2 = function () { console.log('im another static function') }
let p1 = new Person()
p1.staticFun() // 报错,TypeError: staticFun is not a function
p1.stati2cFun() // 报错,TypeError: staticFun2 is not a function
Person.staticFun() // im a static function
Person.staticFun2() // im another static function
有没有静态类属性?
有的。但是和静态类方法的定义方式不同,静态类属性只能再类块外部绑定。
class Person {}
Person.staticName = 'staticName' // 目前只能通过这样的方式创建静态类属性
let p1 = new Person()
console.log(p1.staticName) // undefined
原理上,静态属性应该是不能被绑定到实例的this以及构造函数的this上,所以无法被实例继承,可以看下面的代码示例:
class Person {
sayName1 () {
console.log(Person.staticName)
}
sayName2 () {
console.log(this.staticName)
}
}
Person.staticName = 'staticName' // 目前只能通过这样的方式创建静态类属性
let p1 = new Person()
p1.sayName1() // staticName
p1.sayName2() // undefined
类继承
先明确一点,ES6的class继承,背后一样也是基于ES5的原型链继承,或者更直接一点,就是“寄生组合式继承”。
继承基础
ES6中,使用extends关键字实现继承,既可以继承class类,也可以继承构造函数。
// 继承类
class Vehicle {}
class Bus extends Vehicle {}
// 继承构造函数
function Person () {}
class Child extends Person {}
注意静态属性和方法也是可以被继承的。
class Vehicle {
static staticFun () {console.log('im static function')}
}
Vehicle.staticName = 'staticName'
class Bus extends Vehicle {}
Bus.staticFun() // im static function
console.log(Bus.staticName) // staticName
super方法
super是干什么用的?
super是class继承语法中,用来让子类能够继承父类的实例方法或属性的方法。相当于调用了 父类的构造函数。
super只能在子类的构造函数或者静态方法中使用。
class Vehicle {
constructor () {
this.hasEngine = true
}
}
class Bus extends Veicle {
constructor () {
super()
}
}
在super调用之前,不能在constructor中引用this,否则会报错
class Vehicle {
constructor () {
this.hasEngine = true
}
}
class Bus extends Veicle {
constructor () {
this.name = 'bus' // 在实例化Bus的时候报错
super()
}
}
let bus = new Bus() // 报错
super能不能不写?
如果在子类中有显式声明类构造函数,那super必须写,或者让子类的constructor return一个对象。
class Vehicle {}
class Bus extends Veicle {
constructor () {}
}
let bus = new Bus() // 会报错
class Person {}
class Doctor extends Person {
constructor () {
return {}
}
}
let doc = new Doctor() // 没问题
super可以用来给父类构造函数传参
class Vehicle {
constructor (hasEngine) {
this.hasEngine = hasEngine
}
}
class Bus extends Vehicle {
constructor () {
super(true)
}
}
let bus = new Bus()
console.log(bus.hasEngine) // true