ES6类class(详细介绍)

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

类定义

与函数类型相似,定义类也有两种方式:类声明和类表达式。这两种方式都使用class关键字加大括号

  1. 类声明:class Person()
  2. 类表达式:const Animal = class {}

与函数表达式类似,类表达式在它们被求值前也不能引用。不过,与函数定义不用的是,虽然函数声明可以提升,但类定义不能

console.log(FunctionExpression); //undefined
var FunctionExpression = function() {};
console.log(FunctionExpression); //function(){}

console.log(FunctionDeclaration); //FunctionDeclaration(){}
function FunctionDeclaration(){};
console.log(FunctionDeclaration); //FunctionDeclaration(){}

console.log(ClassExpression); //undefined
var ClassExpression = class() {};
console.log(ClassExpression); //class{}

console.log(ClassDeclaration); // ReferenceError:ClassDeclarationis not defined
class ClassDeclaration{};
console.log(ClassDeclaration); // class ClassDeclaration{}

另一个跟函数声明不同的地方是,函数受函数作用域的限制,而类受块作用域的限制:

{
  function FunctionDeclaration() {}
  class ClassDeclaration {}
}
console.log(FunctionDeclaration);//FunctionDeclaration
console.log(ClassDeclaration);// ReferenceError:ClassDeclaration not defined

类的构成

类可以包含构造函数方法、实例方法、获取函数、设置函数和静态类方法,但这些都不是必需的。

空的类定义照样有效。默认情况下,类定义中的代码都是在严格模式下执行。

与构造函数一样,多数编程风格都建议类名的首字母要大写,以区别通过它创建的实例(比如,通过class Foo{} 创建实例 foo)

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

类表达式的名称是可选的。在把类表达式复制给变量后,可以通过name属性取得类表达式的名称字符串。

但不能在类表达式作用域外部访问这个标识符。

let Person = class PersonName {
  identify() {
    console.log(Person.name, PersonName.name);
  }
}

let p = new Person();
p.identify();//PersonName PersonName

console.log(Person.name);//PersonName
console.log(PersonName);//ReferenceError:PersonName not defined

类构造函数

constructor关键字用于在类定义块内部创建类的构造函数。方法名constructor会告诉解释器在使用new操作符创建类的新实例时,应该调用这个函数。

构造函数的定义不是必须的,不定义构造函数相当于将构造函数定义为空函数。

1. 实例化

使用new操作符实例化Person的操作等于使用new调用其构造函数。

当使用new和类意味着应该使用constructor函数进行实例化。

使用new调用类的构造函数会执行如下操作

  1. 会在堆内存开辟一个空间,创建一个新对象
  2. 为该对象添加隐式原型属性__proto__,并赋值为构造函数的显示原型属性,指向构造函数的原型对象
  3. 将该对象作为构造函数中的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(); //person ctor
let v = new Vegetable();
console.log(v.color)//orange

类实例化时传入的参数会用作构造函数的参数。如果不需要参数,则类名后面的括号也是可选的:

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

默认情况下,类构造函数会在执行之后返回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);//Person{foo : 'foo'}
console.log(p1 instanceof Person);//true

console.log(p2);//{bar:'bar'}
console.log(p2 instanceof Person);//false

类构造函数与构造函数的主要区别:调用类构造函数必须使用new操作符。而普通构造函数如果不使用new调用,那么就会以全局的this(通常是window)作为内部对象。调用类构造函数时如果不使用new则会抛出错误

function Person() {}
class Animal {}
//把window作为this来构建实例
let p = Person();
let a = Animal();
//TypeError:class constructor Animal cannot be invoked without 'new'

类构造函数没有什么特殊之处,实例化之后,它会成为普通的实例方法(但作为类构造函数,仍然要使用new调用)。因此,实例化之后可以在实例上引用它:

class Person {}
//使用类创建一个新实例
let p1 = new Person();

p1.constructor();
//TypeError:class constructor Person cannot be invoked without 'new'

//使用对类构造函数的引用创建一个新实例
let p2 = new P1.constructor();

2. 把类当成特殊函数

ECMAScript中没有正式的这个类型。声明一个类之后,通过typeof操作符检测类标识符,返回值是function

class Person{}
console.log(typeof Person);//function

类标识符有prototype属性,而这个原型也有一个constructor属性指向自身

class Person{}
console.log(Person.prototype);//{constructor:f()}
console.log(Person === Person.prototype.constructor);//true

类是JavaScript的一等公民,因此可以像其他对象或函数引用一样把类作为参数传递:

//类可以像函数一样在任何地方定义,比如在数组中
let classList = [
  class {
    constructor(id){
      this.id_=id;
      console.log(`instance ${this.id_}`)
    }
  }
];

function createInstanceof(classDefinition, id){
  return new classDefinition(id);
}

let foo = createInstanceof(classList[0], 3141); // instance 3141

实例、原型和类成员

实例成员

每次通过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();

console.log(p1.name === p2.name);  //false 
console.log(p1.sayName === p2.sayName);//false
console.log(p1.nicknames === p2.nicknames);//false

p1.name = p1.nicknames[0];
p2.name = p2.nicknames[1];

p1.sayName();  //"Jake"
p2.sayName();  //"J-Dog"

原型方法与访问器

为了在实例间共享方法,类定义语法把在类块中定义的方法作为原型方法。

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

可以把方法定义在类构造函数中或者类块中,但不能在类块中给原型添加原始值或对象作为成员数据

class Person1 {
    name: 'jack' //Uncaught SyntaxError: Unexpected token
}

 类定义也支持获取和设置访问器。语法与行为跟普通对象一样:

class Person {
    set(newName) {
        this.name_ = newName;
    }
    get name() {
        return this.name_;
    }
}

let p = new Person();
p.name_ = 'Jake';
console.log(p.name_); // 'Jake'

静态类方法

可以在类上定义静态方法。这些方法通常用于执行不特定于实例的操作,也不要求存在类的实例。

静态类成员在类定义中使用static关键字作为前缀。在静态成员中,this引用类自身。其他所有约定跟原型成员一样:

class Person {
    constructor() {
        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 Person{...}

继承

继承继承

ES6类支持单继承,使用extends关键字,就可以继承任何拥有[[constructor]]和原型的对象。

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

派生类都会通过原型链访问到类和原型上定义的方法。

this的值会反映调用相应方法的实例或者类:

class Vehicle {
    identifyPrototype(id) {
        console.log(id, this);
    }
    static identifyClass(id) {
        console.log(id, this);
    }
}
class Bus extends Vehicle { }
let v = new Vehicle();
let b = new Bus();
b.identifyPrototype('bus');// 'bus', Bus {}
v.identifyPrototype('vehicle');// 'vehicle', Vehicle {}

Bus.identifyClass('bus');// 'bus', class Bus extends Vehicle {...}
Vehicle.identifyClass('vehicle');// 'vehicle', class Vehicle {...}

构造函数、HomeObject和super()

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

在类构造函数内部使用super可以调用父类构造函数

class Vehicle {
    constructor() {
        this.hasEngine = true;
    }
}

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

new Bus();

静态方法中可以通过super调用继承的类上定义的静态方法


class Vehicle {
    static identify() {
        console.log('Vehicle');
    }
}

class Bus extends Vehicle {
    static identify() {
        super.identify();
    }
}

Bus.identify()//'Vehicle'

在使用super时要注意几个问题:

  1. super只能在派生类构造函数和静态方法中使用
  2. 不能单独引用super关键字,要么用它调用构造函数,要么用它引用静态方法
  3. 调用super()会调用父类构造函数,并将返回的实例赋值给this(调用super会创建父类的实例,然后赋值给this)
  4. super()的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入
  5. 如果没有定义类构造函数,在实例化派生类时会自动调用super(),而且会传入所有传给派生类的参数
  6. 在类构造函数中,不能在调用super()之前引用this
  7. 如果在派生类中显式定义了构造函数,则要么必须在其中调用super(),要么必须在其中返回一个对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值