长话简说JavaScript(8)类

ECMAScript 6 新引入的 class 关键字具有正式定义类的能力。支持正式的面向对象编程,但实际上它背后使用的仍然是原型和构造函数的概念。

类定义

// 类声明
class Person {} 
// 类表达式
const Animal = class {};

函数受函数作用域限制,而类受块作用域限制

类的构成

类可以包含构造函数方法、实例方法、获取函数、设置函数和静态类方法,但这些都不是必需的。空的类定义照样有效。编程风格必须类名的首字母要大写,以区别于通过它创建的实例

class Baz { 
// 有构造函数的类,有效
constructor() {} 
// 有获取函数的类,有效
 get myBaz() {} 
 // 有静态方法的类,有效
 static myQux() {} 
} 

类构造函数

constructor 关键字用于在类定义块内部创建类的构造函数。

class Person { 
 constructor(name) { 
 console.log(arguments.length); 
 this.name = name || null; 
 } 
} 
let p3 = new Person('Jake'); 
console.log(p3.name); // Jake

类构造函数与构造函数的主要区别是,调用类构造函数必须使用 new 操作符。而普通构造函数如果
不使用 new 调用,那么就会以全局的 this(通常是 window)作为内部对象。
使用 instanceof 操作符检查一个对象与类构造函数,以确定这个对象是不是类的实例。
类本身在使用 new 调用时就会被当成构造函数。重点在于,类中定义的 constructor 方法不会被当成构造函数,在对它使用
instanceof 操作符时会返回 false。

class Person {} 
let p1 = new Person(); 
console.log(p1.constructor === Person); // true 
console.log(p1 instanceof Person); // true 
console.log(p1 instanceof Person.constructor); // false 
let p2 = new Person.constructor(); 
console.log(p2.constructor === Person); // false 
console.log(p2 instanceof Person); // false 
console.log(p2 instanceof Person.constructor); // true

实例、原型和类成员

实例成员
每次通过new调用类标识符时,都会执行类构造函数。在这个函数内部,可以为新创建的实例(this)添加“自有”属性。

class Person { 
 constructor() { 
 // 这个例子先使用对象包装类型定义一个字符串
 // 为的是在下面测试两个对象的相等性
 this.name = new String('Jack'); 
 this.sayName = () => console.log(this.name); 
 this.nicknames = ['Jake', 'J-Dog'] 
 } 
} 
let p1 = new Person(), 
 p2 = new Person(); 
p1.sayName(); // Jack 
p2.sayName(); // Jack 
console.log(p1.name === p2.name); // false 
console.log(p1.sayName === p2.sayName); // false 
console.log(p1.nicknames === p2.nicknames); // false

原型方法与访问器
为了在实例间共享方法,类定义语法把在类块中定义的方法作为原型方法。
可以把方法定义在类构造函数中或者类块中,但不能在类块中给原型添加原始值或对象作为成员数据:

class Person { 
 constructor() { 
 // 添加到 this 的所有内容都会存在于不同的实例上
 this.locate = () => console.log('instance'); 
 }
  locate() { 
 console.log('prototype'); 
 } 
} 
let p = new Person(); 
p.locate(); // instance 
Person.prototype.locate(); // prototype

静态类方法
静态类成员在类定义中使用 static 关键字作为前缀。在静态成员中,this 引用类自身。

class Person { 
 constructor() { 
 // 添加到 this 的所有内容都会存在于不同的实例上
 this.locate = () => console.log('instance', this); 
 } 
 // 定义在类的原型对象上
 locate() { 
 console.log('prototype', this); 
 } 
 // 定义在类本身上
 static locate() { 
 console.log('class', this); 
 } 
} 
let p = new Person(); 
p.locate(); // instance, Person {} 
Person.prototype.locate(); // prototype, {constructor: ... } 
Person.locate(); // class, class Person {}

非函数原型和类成员
虽然类定义并不显式支持在原型或类上添加成员数据,但在类定义外部,可以手动添加:

class Person { 
 sayName() { 
 console.log(`${Person.greeting} ${this.name}`); 
 } 
} 
// 在类上定义数据成员
Person.greeting = 'My name is';
// 在原型上定义数据成员
Person.prototype.name = 'Jake'; 
let p = new Person(); 
p.sayName(); // My name is Jake

迭代器与生成器方法

class Person { 
 // 在原型上定义生成器方法
 *createNicknameIterator() { 
 yield 'Jack'; 
 yield 'Jake'; 
 yield 'J-Dog'; 
 } 
 // 在类上定义生成器方法
 static *createJobIterator() { 
 yield 'Butcher'; 
 yield 'Baker'; 
 yield 'Candlestick maker'; 
 } 
} 
let jobIter = Person.createJobIterator(); 
console.log(jobIter.next().value); // Butcher 
console.log(jobIter.next().value); // Baker 
console.log(jobIter.next().value); // Candlestick maker 
let p = new Person(); 
let nicknameIter = p.createNicknameIterator(); 
console.log(nicknameIter.next().value); // Jack 
console.log(nicknameIter.next().value); // Jake 
console.log(nicknameIter.next().value); // J-Dog 
// 因为支持生成器方法,所以可以通过添加一个默认的迭代器

继承

ECMAScript 6 新增特性中最出色的一个就是原生支持了类继承机制。虽然类继承使用的是新语法,但背后依旧使用的是原型链。
extends 关键字:可以继承任何拥有[[Construct]]和原型的对象

class Vehicle {} 
// 继承类
class Bus extends Vehicle {} 
let b = new Bus(); 
console.log(b instanceof Bus); // true 
console.log(b instanceof Vehicle); // true 
function Person() {} 
// 继承普通构造函数
class Engineer extends Person {} 
let e = new Engineer(); 
console.log(e instanceof Engineer); // true 
console.log(e instanceof Person); // true

构造函数、HomeObject 和 super()
通过 super 关键字引用它们的原型。这个关键字只能在派生类中使用,而且仅
限于类构造函数、实例方法和静态方法内部。在类构造函数中使用 super 可以调用父类构造函数。
super():

class Vehicle { 
 constructor() { 
 this.hasEngine = true; 
 } 
} 
class Bus extends Vehicle { 
 constructor() { 
 // 不要在调用 super()之前引用 this,否则会抛出 ReferenceError 
 super(); // 相当于 super.constructor() 
 console.log(this instanceof Vehicle); // true 
 console.log(this); // Bus { hasEngine: true } 
 } 
} 
new Bus();

super注意事项
super 只能在派生类构造函数和静态方法中使用。

class Vehicle { 
 constructor() { 
 super(); 
 // SyntaxError: 'super' keyword unexpected 
 } 
}

不能单独引用 super 关键字,要么用它调用构造函数,要么用它引用静态方法。

class Vehicle {} 
class Bus extends Vehicle { 
 constructor() { 
 console.log(super); 
 // SyntaxError: 'super' keyword unexpected here 
 } 
}

调用 super()会调用父类构造函数,并将返回的实例赋值给 this。

class Vehicle {} 
class Bus extends Vehicle { 
 constructor() { 
 super(); 
 console.log(this instanceof Vehicle); 
 } 
} 
new Bus(); // true

super()的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入。

class Vehicle { 
 constructor(licensePlate) { 
 this.licensePlate = licensePlate; 
 } 
} 
class Bus extends Vehicle { 
 constructor(licensePlate) { 
 super(licensePlate); 
 } 
} 
console.log(new Bus('1337H4X')); // Bus { licensePlate: '1337H4X' }

如果没有定义类构造函数,在实例化派生类时会调用 super(),而且会传入所有传给派生类的
参数。

class Vehicle { 
 constructor(licensePlate) { 
 this.licensePlate = licensePlate; 
 } 
} 
class Bus extends Vehicle {} 
console.log(new Bus('1337H4X')); // Bus { licensePlate: '1337H4X' }

在类构造函数中,不能在调用 super()之前引用 this。

class Vehicle {} 
class Bus extends Vehicle { 
 constructor() { 
 console.log(this); 
 } 
} 
new Bus(); 
// ReferenceError: Must call super constructor in derived class 
// before accessing 'this' or returning from derived constructor

如果在派生类中显式定义了构造函数,则要么必须在其中调用 super(),要么必须在其中返回
一个对象。

class Vehicle {} 
class Car extends Vehicle {} 
class Bus extends Vehicle { 
 constructor() { 
 super(); 
 } 
} 
class Van extends Vehicle { 
 constructor() { 
 return {}; 
 } 
} 
console.log(new Car()); // Car {} 
console.log(new Bus()); // Bus {} 
console.log(new Van()); // {}

抽象基类
这个就是与Java中的抽象类差不多,可以供其他类继承,但是本身不会被实例化。
new.target

// 抽象基类 
class Vehicle { 
 constructor() { 
 console.log(new.target); 
 if (new.target === Vehicle) { 
 throw new Error('Vehicle cannot be directly instantiated');
  } 
 } 
} 
// 派生类
class Bus extends Vehicle {} 
new Bus(); // class Bus {} 
new Vehicle(); // class Vehicle {} 
// Error: Vehicle cannot be directly instantiated

通过在抽象基类构造函数中进行检查,可以要求派生类必须定义某个方法。因为原型方法在
调用类构造函数之前就已经存在了,所以可以通过 this 关键字来检查相应的方法:

// 抽象基类
class Vehicle { 
 constructor() { 
 if (new.target === Vehicle) { 
 throw new Error('Vehicle cannot be directly instantiated'); 
 } 
 if (!this.foo) { 
 throw new Error('Inheriting class must define foo()'); 
 } 
 console.log('success!'); 
 } 
} 
// 派生类
class Bus extends Vehicle { 
 foo() {} 
} 
// 派生类
class Van extends Vehicle {} 
new Bus(); // success! 
new Van(); // Error: Inheriting class must define foo()

继承内置类型

class SuperArray extends Array { 
 shuffle() { 
 // 洗牌算法
 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 a = new SuperArray(1, 2, 3, 4, 5); 
console.log(a instanceof Array); // true 
console.log(a instanceof SuperArray); // true
console.log(a); // [1, 2, 3, 4, 5] 
a.shuffle(); 
console.log(a); // [3, 1, 4, 5, 2]

类混入

class Vehicle {} 
let FooMixin = (Superclass) => class extends Superclass { 
 foo() { 
 console.log('foo'); 
 } 
}; 
let BarMixin = (Superclass) => class extends Superclass { 
 bar() { 
 console.log('bar'); 
 } 
}; 
let BazMixin = (Superclass) => class extends Superclass { 
 baz() { 
 console.log('baz'); 
 } 
}; 
class Bus extends FooMixin(BarMixin(BazMixin(Vehicle))) {} 
let b = new Bus(); 
b.foo(); // foo 
b.bar(); // bar 
b.baz(); // baz 
通过写一个辅助函数,可以把嵌套调用展开:
class Vehicle {} 
let FooMixin = (Superclass) => class extends Superclass { 
 foo() { 
 console.log('foo'); 
 } 
}; 
let BarMixin = (Superclass) => class extends Superclass { 
 bar() { 
 console.log('bar'); 
 } 
}; 
let BazMixin = (Superclass) => class extends Superclass { 
 baz() { 
 console.log('baz'); 
 } 
}; 
function mix(BaseClass, ...Mixins) { 
 return Mixins.reduce((accumulator, current) => current(accumulator), BaseClass); 
} 
class Bus extends mix(Vehicle, FooMixin, BarMixin, BazMixin) {} 
let b = new Bus(); 
b.foo(); // foo 
b.bar(); // bar 
b.baz(); // baz

这种方式已被很多开发框架弃用,组合好于混入,提供极大的灵活性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

听不见你的名字

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值