美文网首页
【ES6 笔记】JavaScript中的类

【ES6 笔记】JavaScript中的类

作者: ___Jing___ | 来源:发表于2018-11-07 18:30 被阅读0次

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

相关文章

网友评论

      本文标题:【ES6 笔记】JavaScript中的类

      本文链接:https://www.haomeiwen.com/subject/ytjvxqtx.html