ES5中的近类结构
自定义类型:创建一个构造函数,然后定义另一个方法并赋值给构造函数的原型:
function PersonType(name){
this.name = name
}
PersonType.prototype.sayName = function (){
console.log(this.name)
}
var person = new PersonType('欧阳不乖');
person.sayName(); // '欧阳不乖'
类的声明
-
基本的类声明语法
要声明一个类,首先编写class关键字,紧跟着的是类的名字,其他部分的语法类似于对象字面量方法的简写形式,但不需要在类的各个元素之间使用逗号分隔。
class PersonClass{
// 等价于PersonType构造类型
constructor(name){
this.name = name ;
}
// 等价于PersonType.prototype.sayName
sayName(){
console.log(this.name)
}
}
let person = new PersonClass('欧阳不乖');
person.sayName(); // '欧阳不乖'
直接在类中通过特殊的constructor方法名来定义构造函数,由于这种类使用简洁语法来定义方法,因而不需要添加function关键字,除constructor外没有其他保留的方法名。
自有属性是实例中的属性,不会出现在原型上,且只能在类的构造函数活方法中创建,建议在构造函数中创建所有的自有属性,从而只通过一处就可以控制类中的所有自有属性。
类声明仅仅是基于已有自定义类型声明的语法糖,实际上创建了一个具有构造函数方法行为的函数。
与函数不同的是,类属性不可被赋予新值,在之前的示例中,PersonClass.prototype就是这样一个只读的类属性。
-
为何使用类语法
- 函数声明可以被提升,而类声明与let声明类似,不能被提升;真正执行声明语句之前,它们会一直存在于临时死区中;
- 类声明中的所有代码将自动运行在严格模式中,而且无法强行让代码脱离严格模式执行;
- 在自定义类型中,需要通过Object.defineProperty()方法手工指定某个方法为不可枚举;而在类中,所有方法都是不可枚举的;
- 每个类都有一个名为[[Construct]]的内部方法,通过关键字new调用那些不含[[Construct]]的方法会导致程序抛出错误;
- 使用除关键字new以外的方式调用类的构造函数会导致程序抛出错误;
- 在类中修改类名会导致程序报错。
类的名称只在类中为常量,所以尽管不能在类的方法中修改类名,但是可以在外部修改
class Foo{
constructor(){
Foo = "bar"; // 执行时会抛出错误
}
}
// 但在类声明结束后就可以修改
Foo = "bar"
类表达式
类和函数都有两种存在形式:声明形式和表达式形式。
声明形式的函数和类都由相应的关键字(分别为function和class)进行定义,随后紧跟一个标识符;表达式形式的函数和类与之相似,只是不需要在关键字后添加标识符。
- 基本的类表达式语法
let PersonClass = class {
// 等价于PersonType构造函数
constructor(name){
this.name = name ;
}
// 等价于PersonType.prototype.sayName
sayName(){
console.log(this.name)
}
}
let person = new PersonClass('欧阳不乖');
person.sayName(); // '欧阳不乖'
-
命名类表达式
声明时,在关键字class后添加一个标识符即可定义为命名类表达式:
let PersonClass = class PersonClass2{
// 等价于PersonType构造函数
constructor(name){
this.name = name ;
}
// 等价于PersonType.prototype.sayName
sayName(){
console.log(this.name)
}
}
console.log(typeof PersonClass); // function
console.log(typeof PersonClass2); // undefined
对于类声明来说,通过let定义的外部绑定与通过const定义的内部绑定具有相同的名称;而命名类表达式通过const定义名称,从而PersonClass2只能在类的内部使用。
作为一等公民的类
在程序中,一等公民是指一个可以传入函数,可以从函数返回,并且可以赋值给变量的值。
ES6中类也是一等公民。
可以将类作为参数传入函数中:
function createObject(classDef){
return new classDef();
}
let obj = createObject( class {
sayHi(){
console.log('Hi!');
}
})
obj.sayHi(); // Hi!
类表达式还有另外一种使用方式,通过立即调用类构造函数可以创建单例。用new调用类表达式,紧接着通过一对小括号调用这个表达式:
let person = new class{
constructor(name){
this.name = name ;
}
sayName(){
console.log( this.name );
}
}('欧阳不乖');
person.sayName(); // '欧阳不乖'
访问器属性
类支持直接在原型上定义访问器属性。
创建getter时,需要在关键字get后紧跟一个空格和相应的标识符;
创建setter时,需要在关键字set后紧跟一个空格和相应的标识符:
class CustomHTMLElement{
constructor(element){
this.element = element ;
}
get html(){
return this.element.innerHTML ;
}
set html(value){
return this.element.innerHTML = value ;
}
}
var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype,"html");
console.log( 'get' in descriptor ); // true
console.log( 'set' in descriptor ); // true
可计算成员名称
let methodName = 'sayName';
class PersonClass {
constructor(name){
this.name = name;
}
[methodName](){
console.log(this.name)
}
}
let me = new PersonClass('欧阳不乖');
me.sayName(); //'欧阳不乖'
生成器方法
class Collection{
constructor(){
this.items = [];
}
*[Symbol.iterator](){
yield *this.items.values();
}
}
let collection = new Collection();
collection.items.push(1);
collection.items.push(2);
for (val of collection){
console.log(val)
}
// 1
// 2
静态成员
在ES5中直接将方法添加到构造函数中来模拟静态成员
function PersonType(name){
this.name = name ;
}
// 静态方法
PersonType.create = function(name){
return new PersonType(name);
}
//实例方法
PersonType.prototype.sayName = function(){
console.log(this.name)
}
var person = PersonType.create('欧阳不乖')
在ES6的类方法简化了创建静态成员的过程,在方法或访问器属性名前使用正式的静态注释即可:
class PersonClass {
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name);
}
// 等价于PersonType.create
static create(name){
return new PersonClass(name);
}
}
let person = PersonClass.create('欧阳不乖');
类中的所有方法和访问器属性都可以用static关键字来定义,唯一的限制是不能将static用于定义构造函数方法。
不可在实例中访问静态成员,必须要直接在类中访问静态成员。
继承和派生类
ES5中的继承是这样实现的:
function Rectangle (length, width){
this.length = length;
this.width = width;
}
Rectangle.prototype.getArea = function(){
return this.length*this.width;
}
function Square(length){
Rectangle.call(this, length, length)
}
Square.prototype = Object.create(Rectangle.prototype,{
constructor:{
value : Square,
enumerable : true,
writable : true,
configurable : true
}
})
let square = new Square(3);
console.log(square.getArea()); // 9
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true
ES6中通过extends关键字可以指定类继承的函数,原型会自动调整,通过调用super()方法即可访问基类的构造函数:
class Rectangle{
constructor(length, width){
this.length = length;
this.width = width;
}
getArea(){
return this.length*this.width;
}
}
class Square extends Rectangle{
constructor(length){
// 等价于 Rectangle.call(this, length, length)
super(length, length)
}
}
let square = new Square(3);
console.log(square.getArea()); // 9
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true
在Square构造函数中通过调用super()调用Rectangle构造函数并传入相应参数。
继承自其它类的类被称作派生类,如果在派生类中指定了构造函数则必须要调用super(),如果不这样做程序就会报错。如果选择不使用构造函数,则当创建新的类实例时会自动调用super()并传入所有参数。
-
使用super()的小贴士
- 只可在派生类的构造函数中使用super(),如果尝试在非派生类(不是用extends声明的类)或函数中使用则会导致程序抛出错误;
- 在构造函数中访问this之前一定要调用super(),它负责初始化this,如果在调用super()之前尝试访问this会导致程序出错;
- 如果不想调用super(),则唯一的方法是让类的构造函数返回一个对象。
-
类方法遮蔽
派生类中的方法总会覆盖基类中的同名方法
class Square extends Rectangle{
constructor(length){
super(length,length)
}
// 覆盖并遮蔽Rectangle.prototype.getArea()方法
getArea(){
return this.length* this.length
}
// 覆盖遮蔽后调用Rectangle.prototype.getArea()方法
getArea(){
return super.getArea();
}
}
- 静态成员继承
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){
// 等价于 Rectangle.call(this, length, length)
super(length, length)
}
}
let rect = Square.create(3,4);
console.log(rect.getArea()); // 12
console.log(rect instanceof Rectangle); // true
console.log(rect instanceof Square); // false
-
派生自表达式的类
只要表达式可以被解析为一个函数并且具有[[Construct]]属性和原型,那么就可以用extends进行派生
function Rectangle(length, width){
this.length = length;
this.width = width;
}
Rectangle.prototype.getArea = function (){
return this.length*this.width;
}
class Square extends Rectangle{
constructor(length){
// 等价于 Rectangle.call(this, length, length)
super(length, length)
}
}
let x = new Square(3);
console.log(x.getArea()); // 9
console.log(x instanceof Rectangle); // true
extends强大的功能使得类可以继承自任意类型的表达式,从而创造更多可能性,例如动态地确定类的继承目标:
function Rectangle(length, width){
this.length = length;
this.width = width;
}
Rectangle.prototype.getArea = function(){
return this.length*this.width
};
function getBase(){
return Rectangle;
}
class Square extends getBase(){
constructor(length){
super(length,length)
}
}
getBase()函数是类声明的一部分,直接调用后返回Rectangle,此示例实现的功能与之前的示例等价。
由于可以动态确定使用哪个基类,因而可以创建不同的继承方法:
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();
this.length = length;
this.width = length;
}
}
var x = new Square(3);
console.log(x.getArea()); // 9
console.log(x.Serialize()) //{"length":3,"width":3}
-
内建对象的继承
ES6中的类继承与传统的继承相反,先由基类创建this的值,然后派生类的构造函数再修改这个值,所以一开始通过this访问基类的所有内建功能,然后再正确地接收所有与之相关的功能:
class MyArray extends Array{
// do something
}
var colors = new MyArray();
colors[0]= "red";
console.log(colors.length);// 1
colors.length = 0;
console.log(colors[0]); // undefined
-
Symbol.species属性
Symbol.species是诸多内部Symbol中的一个,它被用于定义返回函数的静态访问器属性。
一般来说,只要想在类方法中调用this.constructor,就应该使用Symbol.species属性,从而让派生类重写返回类型。而如果正从一个已定义Symbol.species属性的类创建派生类,那么要确保使用那个值而不是使用构造函数。
在类的构造函数中使用new.target
class Shape{
constructor(){
if(new.target === Shape){
throw new Error('这个类不能被直接实例化')
}
}
}
class Rectangle extends Shape{
constructor(length, width){
super();
this.length = length;
this.width = width;
}
}
var x = new Shape(); // 抛出错误
var y = new Rectangle(3,4); //不会抛出错误
console.log(y instanceof Shape); // true
因为类必须通过new关键字才能调用,所以在类的构造函数中,new.target属性永远不会是undefined
网友评论