美文网首页
【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