1、虽然ES6类表面上看起来是可以支持正式的面向对象编程,但是实际上它背后使用的仍然是原型和构造函数的概念
2、类与函数之间的区别
与函数的定义不同的是,虽然函数声明可以提升,但类的定义不可以提升
函数受函数作用域限制,而类受块作用域限制
类表达式的名称是可选的,把类表达式赋值给变量后,可以通过属性取得类表达式的名称字符串,但不能在表达式作用域外部访问这个标识符
let Person = class PersonName { identify() { console.log(Person.name, PersonName.name) } } let p = new Person() p.identify() console.log(Person.name) console.log(PersonName)
3、constructor关键字
constructor关键字用于在类定义块内部创建类的构造函数,方法名constructor会告诉解释器在使用new操作符创建类的新实例时,应该调用这个函数。构造函数的定义不是必须的,不定义构造函数相当于将构造函数定义为空函数。
4、实例化
使用new操作符实例化Person的操作等于使用new调用其构造函数。唯一可感知的不同之处就是,Javascript解释器知道使用new和类意味着应该使用constructor函数进行实例化
使用new调用类的构造函数会执行如下操作
(1)在内存中创建一个新对象
(2)这个新对象内部的[[ Prototype ]]指针被赋值为构造函数的prototype属性
(3) 构造函数内部的this被赋值为这个新对象(即this指向新对象)
(4)执行构造函数内部的代码(给新对象添加属性)
(5)如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
看看高程上关于对象和原型的部分
class Animal {} class Person { constructor() { console.log('person ctor') } } class Vegetable { constructor() { this.color = 'orange' } } let a = new Animal() let p = new Person() let v = new Vegetable() console.log(v.color)
类实例化时传入的参数会作用构造函数的参数,如果不需要参数,则类名后面的括号也是可选的
class Person { constructor(name) { console.log(arguments.length) this.name = name || null } } let p1 = new Person console.log(p1.name) let p2 = new Person() console.log(p2.name) let p3 = new Person('Jake') console.log(p3.name)
默认情况下,类构造函数会在执行之后返回this对象,构造函数返回的对象会被用作实例化的对象,如果没有什么引用新创建的this对象,那么这个对象会被销毁,不过,如果返回的不是this对象,而是其他对象,那么这个对象不会通过instanceof操作符检测出跟类有关联,因为这个对象的原型指针并没有修改
class Person { constructor(override) { this.foo = 'foo' if(override) { return { bar: 'bar' } } } } let p1 = new Person(), p2 = new Person(true); console.log(p1) console.log(p1 instanceof Person) console.log(p2) console.log(p2 instanceof Person)
类构造函数与构造函数的主要区别是:调用类构造函数必须使用new操作符,而普通构造函数如果不使用new调用,那么就会以全局的this(通常是window)作为内部对象
类通过typeof操作符检测类标识符,表明它是一个函数
class Person {} console.log(Person) console.log(typeof Person) console.log(Person.prototype) console.log(Person === Person.prototype.constructor)
在类的上下文中,类本身在使用new调用时就会被当成构造函数
类中定义的constructor方法不会被当作构造函数,在对它使用instanceof操作符时会返回false
如果在创建实例时直接将类构造函数当成普通构造函数来使用,那么instanceof操作符的返回值会反转
class Person {} let p1 = new Person() console.log(p1.constructor === Person) console.log(p1 instanceof Person) console.log(p1 instanceof Person.constructor) let p2 = new Person.constructor() console.log(p2.constructor === Person) console.log(p2 instanceof Person) console.log(p2 instanceof Person.constructor)
5、实例成员
每个实例都对应一个唯一的成员对象,所有成员都不会在原型上共享
class Person { constructor() { this.name = new String('Jack') this.nicknames = ['Jake', 'J-Dog'] } } let p1 = new Person(), p2 = new Person(); p1.sayName() p2.sayName() console.log(p1.name === p2.name) console.log(p1.sayName === p2.sayName) console.log(p1.nicknames === p2.nicknames)
6、为了在实例间共享方法,类定义语法把在类块中定义的方法作为原型方法
class Person { constructor() { // 添加到this的所有内容都会存在于不同的实例上 this.locate = () => console.log('instance') // 在类块中定义的所有内容都会定义在类的原型上 locate() { console.log('prototype') } } }
类定义也支持获取和设置访问器,语法和普通对象一样:
class Person { set name(newName) { this.name_ = newName } get name() { return this.name_ } } let p = new Person() p.name = 'Jake' console.log(p.name)
7、静态类方法
静态类成员在类定义中使用static关键字作为前缀
在静态成员中,this引用类自有约定跟原型成员一样
静态类方法非常适合作为实例工厂
8、类的继承
ES6中原生支持了类继承,虽然使用了新的语法,但是背后使用的还是原型链
类可以继承类,也可以继承普通的构造函数(保持向后兼容)
派生类都会通过原型链访问到类和原型上定义的方法
9、super
派生类的方法可以通过super关键字引用他们的原型
关键字只能在派生类中使用,而且仅限于类构造函数、实例方法和静态方法内部
在类构造函数中使用super可以调用父类构造函数
10、抽象基类
11、类混入