类的出现,是因为通过函数实现继承,会导致代码冗长和混乱.类是ES6的新语法糖,表面上支持了面向对象编程,实际上内部还是原型和构造函数的概念.
1.类定义
声明式:
class Person{}
类表达式
const Animal = class {}
*注意类表达式不可以变量提升,如果在类表达式未定义之前对他进行调用,就会直接异常
*类受到块级作用域限制,函数是函数作用域
类的构成
类中可以包含: 构造函数方法,实例方法,获取方法,设置方法,静态类方法.这些方法都是非必需的.
类表达式的名称是可选的,可以在外面通过类表达式.name取到类的名字,但是无法直接去调用这个类的名字.
2.类构造函数
通过new一个类创建新实例的时候,会调用constructor函数,而如果没有定义构造函数,默认构造函数就是空函数.
实例化
通过new去实例化一个类的时候,就是调用了他的构造函数
使用new代表了:
1.先分配一块内存空间
2.将实例的内部指针指向构造函数的原型
3.将构造函数的this指向实例
4.执行构造函数中的内容
5.如果构造函数里返回了非空对象,就返回该对象,否则就返回刚创建的对象.
*第五步里,如果不手动返回对象,就会把this对象返回,而如果手动返回的不是this对象,而是其他对象,那么这个返回的实例无法通过instanceOf去监测和类的关系
*实例化的时候如果传入了参数,会作为构造函数的参数
之前也使用过普通的构造函数,普通的构造函数如果不使用new操作符,里面的this就是全局的window,而类构造函数如果不使用new,就会报错.类构造函数并不特殊,在实例化之后就是实例的普通方法,只不过还是得通过new去调用.
把类当作特殊函数
js中没有正式的类这个类型,其实类就是一种特殊函数,依据有三:
1. typeof 类名 为function
2.类具有prototype属性,并且prototype属性里也有constructor指回prototype
3.能够通过instanceOf检查类的实例是否存在于原型链中
类中定义的constructor函数不被当作构造函数,实例在对类名.constructor使用instanceOf的时候返回false。但是,如果在创建实例的时候使用的就是new 类名.constructor,那么结果就正相反。
类是js的一等公民,因此可以像其他对象或函数一样作为参数进行传递。
类可以和函数一样在任何地方被定义,比如数组中。
let classList = [
class {
constructor(id) {
this.id = id
console.log(`instance${this.id}`)
}
}
]
function createInstance(classItem, id) {
return new classItem(id)
}
let p1 = createInstance(classList[0],666) // instance666
和立即调用函数类似,类可以立即实例化
let p = new class Foo {
constructor(id) {
console.log(id)
}
}('666') // 666
3.实例,原型,类成员
类的语法可以比较方便的定义应该存在于实例的成员,原型的成员,和类本身上的成员
1.实例成员
每次调用new操作符,都会去执行构造函数,构造函数内部,可以为这个实例(this)设置自有属性,设置的值是没有什么限制的,生成的实例也是彼此独立,互不影响,不会在原型上相互影响.
2.原型方法和访问器
为了在实例间共享方法,类语法把在类块里定义的方法作为原型方法.
class Person {
constructor() {
this.locate = () => {console.log('实例方法')}
}
locate() {
console.log('原型方法')
}
}
let p = new Person()
p.locate() // '实例方法'
p.prototype.locate() // '原型方法'
但是注意不能在类块里添加原始值或者对象
class Person {
name: 'jake'
}
// 这么写会报错
类方法等同于对象属性,所以可以用字符串,符号,或者计算值作为键
类定义支持设置器和访问器
class Person {
set name(newName) {
this.name_ = newName
}
get name() {
return this.name_
}
}
let p = Person()
p.name = 'jake'
console.log(p.name) // 'jake'
3.静态方法类
静态类方法不执行特定于某个实例的方法,它是类的方法,并不会要求有实例.而且每个类里的静态方法不能重名.通过static关键字作为前缀,适合做实例工厂
class Person {
constructor(age) {
// 定义在实例上
this.age_ = age
}
// 定义在原型上
sayAge() {
console.log(this.age_)
}
// 使用随机年龄创建并返回Person对象
// 定义在类本身上
static create() {
return new Person(Math.floor(Math.random()*100))
}
}
console.log(Person.create) // 打印了Person的随机实例
4.非函数类型和类成员
虽然没办法在类块和原型里直接添加成员数据,但是在外面可以
class Person {
constructor() {
console.log(`${this.greeting}${this.name}`)
}
}
Person.greeting = 'my name is' // 在类上定义数据成员
Person.prototype.name = 'jake' // 在原型上定义数据成员
let p = new Person() // 'my name is jake'
5.迭代器和生成器方法
类语法可以在类本身和原型里去定义生成器方法
Person {
// 定义原型上的生成器函数
*createPrototypeIterator() {
yield 'jack';
yield 'mars';
yield 'jake';
}
// 定义类本身的生成器函数
static *createStaticIterator() {
yield 'zhangsan',
yield 'lisi',
yield 'wangwu'
}
}
let p1 = Person.createStaticIterator()
console.log(p1.next().value) // 'zhangsan'
console.log(p1.next().value) // 'lisi'
console.log(p1.next().value) // 'wangwu'
let p = new Person()
let p2 = p.createPrototypeIterator()
console.log(p2.next().value) // 'jack'
console.log(p2.next().value) // 'mars'
console.log(p2.next().value) // 'jake'
继承
Es6新特性里添加了类的继承,虽然使用的是新的语法,但是实际上背后使用的还是原型链.
1.基础继承
es6的类支持单继承,可以继承类和构造函数,通过extends
// 继承类
class Person {}
class Chinese extends Person{}
let zhangsan = new Chinese()
console.log(zhangsan instanceOf Chinese) // true
console.log(zhangsan instanceOf Person) // true
// 继承构造函数
function Animal() {}
class Cat extends Animal{}
let mimi = new Cat()
console.log(mimi instanceOf Cat) // true
console.log(mimi instanceOf Animal) // true
2.构造函数,super(),HomeObject
super()可以引用原型,但是必须在类构造函数/实例方法/静态方法
在类构造函数里使用:
class Father {
constructor() {
this.name = '张三'
}
}
class Son extends Father {
constructor() {
super()
console.log(this instanceof Father)
console.log(this)
}
}
new Son()
// true
// Son{name:'张三'}
// Son{name:'张三'}
在静态方法里,可以用super调用继承的父类的静态方法
class Father {
static look() {
console.log('father look')
}
}
class Son extends Father {
static see() {
super.look()
}
}
Son.see() // 'father look'
HomeObject
这个是个隐藏属性,能确保使用super的时候不会发生继承和调用错误,和super绑定,是个自动指针,简单理解就是指向父类.
关于super,需要注意下面几点:
- 只能在派生类构造函数和静态方法里使用
- 不能单独引用super关键字,要么super()调用构造函数,要么super.静态方法调用静态方法
- super()会调用父类构造函数并且把返回的实例赋值给this
- super()形同调用构造函数,如果要给父类构造函数传参得手动传入
- 如果派生类没有定义类构造函数,就会默认的调用super()
- 类构造函数中,不能在super()前使用this
- 如果派生类里定义了构造函数,就必须在里面调用super()或者返回一个对象
3.抽象基类
有时候我们希望一个类只能被继承而不能被实例化,就可以定义抽象基类.
抽象基类是通过new.target实现的,new.target能保存通过new操作符调用的类或者函数,可以在实例化的时候进行检测,判断这个类是不是抽象基类,是的话就报错就行了
class Father {
constructor() {
console.log(new.target)
if(new.target === Father) {
throw new Error('Father是抽象基类,不可实例化!')
}
}
}
class Son extends Father {}
new Son() // 正常
new Father() // 'Father是抽象基类,不可实例化!'
可以在抽象基类的构造函数中进行检查,让子类必须定义某个方法,因为原型方法在调用类构造函数之前就已经存在,所以可以用this进行检测
calss Father {
constructor() {
if(new.target === Father) {
throw new Error('Father是抽象基类,不可实例化!')
}
if(!this.look){
throw new Error('子类必须具有方法:look')
}
console.log('success!')
}
}
class Son1 extends Father{
look() {}
}
class Son2 extends Father{}
new Son1() // succsee
new Son2() // Error: 子类必须具有方法:look
4.继承内置类型
ES6内置了很多常用的类型,方便扩展
class FatherArray extends Array{
xipai() {
for(let i = this.length - 1; i > 0 ; i--) {
const j = Math.floor(Math.random() * (i + 1))
[this[i],this[j]] = [this[j],this[i]]
}
}
}
let arr = new FatherArray(1,2,3,4,5)
console.log(arr instanceOf Array) // true
console.log(arr instanceOf FatherArray) // true
console.log(arr) // [1,2,3,4,5]
arr.xipai()
console.log(arr) // [3,1,4,5,2]
内置类型有时候会返回新的实例,但是默认情况下返回的实例和原始实例的类型是一致的,所以不用担心.如果想要覆盖这个默认行为,可以覆盖Symbol.species访问器,这个访问器决定在创建返回的实例时使用的类.
5.类混入
不推荐使用,暂时先放着