目录
class in javascript (类)
class 的特点
class PersonClass{
constructor(name){
this.name = name
}
sayName(){
console.log(this.name)
}
}
let person = new PersonClass('lolita')
class 与 custom type (自定义类型)之间的差异在于:
- class 声明不能提升。类似于let, 在声明前会处于 时间死亡区域 (temporal dead zone)
- class 内部的代码 均为 严格模式。
- 所有class 中的method都不可枚举。
- 必须用 new 来调用 class
- 不能在 class 内部 重新命名 class 的名字。
class PersonClass 等价于如下代码
let PersonType2 = (function(){
'use strict'
const PersonType2 = function(name){
if(typeof new.target === 'undefined'){
throw new Error('constructor must be called with new')
}
this.name = name
}
Object.defineProperty(PersonType2.prototype, 'sayName', {
value: function(){
if(typeof new.target !== 'undefined'){
throw new Error('method cannot be called with new')
}
console.log(this.name)
},
enumerable: false,
writable: true,
configurable: true
})
return PersonType2
}())
在类中定义generator
class Collection{
constructor(arr){
this.items = arr
}
*[Symbol.iterator](){
yield *this.items.entries()
}
}
var collection = new Collection([1,3,4,5])
for(let a of collection){
console.log(a)
}
static 关键词
- 只能用className来直接获取,不能在实例上操作。
class PersonClass(){
...
static create(name){
return new PersonClass(name)
}
}
let person = PersonClass.create('nicholas')
// 可以加在 class 中的任何 method 上,除了 constructor
扩展类 derived classes
class Rectangle {
constructor(length,width){
this.length = length
this.width = width
}
getArea(){
return this.length * this.width
}
static create(length, width) {
return new Rectangle(length, width);
}
}
class Square extends Rectangle {
constructor(length){
// same as Rectangle.call(this, length, length)
super(length,length)
}
getArea(){
return super.getArea()
}
}
var square = new Square(3)
var r = Square.create(4,5)
console.log( r instanceof Square ) //false
- extends 的类需要在 constructor 中使用 上一级的 方法,super ….., super 是关键词
- 如果不写 constructor, 则会自动代入所有创建实例用的参数
- 只要一个函数有[[constructor]] 和 原型,那么无论是以class 方式去写,还是以 es5的方式去写,都可以去 extends
- 可以动态指定 extends
- null 和 generator 函数不可以被 extends
let SerializableMixin = {
serialize() {
return JSON.stringify(this);
}
};
let AreaMixin = {
getArea() {
return this.length * this.width;
}
};
function mixin(...mixins) {
var base = function() {};
Object.assign(base.prototype, ...mixins);
return base;
}
class Square extends mixin(AreaMixin, SerializableMixin) {
constructor(length) {
super(); // 必须先使用super再去取 this 的值
this.length = length;
this.width = length;
}
}
var x = new Square(3);
console.log(x.getArea()); // 9
console.log(x.serialize()); // "{"length":3,"width":3}"
从built-in的类型中继承
之前会用apply的方式继承
function MyArray(){
Array.apply(this,arguments)
}
MyArray.prototype = Object.create(Array.prototype,{
constructor:{
value: MyArray,
writable: true,
configurable: true,
enumerable: true
}
})
但这样的继承,this的值最先从衍生类中创建,然后原始constructor被调用(Array.apply)。也就是说, this 先作为 MyArray 的实例,然后再被 Array 的特性润饰。
而 ES6 中的 extends, this 的值先由 Array 创建,然后被 MyArray 修改。
this starts with all the built-in functionality of the base and correctly receives all functionality related to it.
class MyArray extends Array{
}
Symbol.species
对于从 built-ins 中继承来的衍生类而言,任何方法返回built-in 实例的方法都会自动转成返回 该衍生类的实例。以上面的 MyArray 为例:
let items = new MyArray(1,2,3,4), subitems = items.slice(1,2)
items instanceof MyArray // true
subitems instanof MyArray // true
这是由 [Symbol.species] 属性决定的。这是一个静态的 accessor property。返回一个constructor函数。当一个实例通过实例方法创建的时候(而不是 new 出来的实例),会被调用。
它只有 get 而没有 set。
class MyClass{
static get [Symbol.species](){
return this
}
constuctor(value){
this.value = value
}
clone(){
return new this.constructor[Symbol.species](this.value)
}
}
class MyDerivedClass1 extends MyClass{
}
class MyDerivedClass2 extends MyClass{
static get [Symbol.species](){
return MyClass
}
}
let ins1 = new MyDerivedClass1('foo'), clone1 = ins1.clone()
let ins2 = new MyDerivedClass2('bar'), clone2 = ins2.clone()
clone1 instanceof MyClass //true
clone1 instanceof MyDerivedClass1 //true
clone2 instanceof MyClass // true
clone2 instanceof MyDerivedClass2 //false
// 可以这么修改 MyArray
class MyArray extends Array{
static get [Symbol.species](){
return Array
}
}
在 class constructor 中使用 new.target
class Shape{
constructor(){
if(new.target === Shape){
throw new Error('this class is abstract and cannot be instantiated directly')
}
}
}
class Rectangle extends Shape{
constructor(length, width){
super()
this.length = length
this.width = width
}
}
var x = new Shape() // throws error
var y = new Rectangle(3,4) // no error
console.log(y instanceof Shape) // true
// 由于 class 只能用 new 来调用,因此 new.target 属性永远不会是 undefined
summary
- ES6的类 更像是 语法糖,但加入了很多别的特性 来避免错误。
- 通过 在 class 原型上加入非静态的 方法来继承,而静态的方法直接作用于 constructor 本身。
- 所有的方法都不可枚举,这跟 built-ins 的对象更为贴近。
- class 必须用 new 来调用。
- 可以从 class, function 或者 expression 中衍生出别的类。可以用mixin 和别的组成形式来创建新的 class。
- 可以从 built-ins中 衍生新的类,并且让他们像 想要的那样。
- new.target 可以被用在 class constructor 中to make it behave differently depending on how the class is called. 尤其是用于创建一个抽象类。