ES6类:从class到function

3,019 阅读4分钟

ES6中的类是function的语法糖,在编译时转换成了function。虽然class是ES6语法原生实现的,但我们仍可以看看从class到function到底发生了什么。

class:原生实现的function语法糖

一个标准的class由两部分构成:构造器和类成员。class要求显式地声明类成员。

构造器construtor方法做的是创建实例时的工作,比如初始化实例属性、实现单例模式等。构造器内的this指向的是实例。

var classDemo = class {
    constructor(params) {
    //初始化实例属性instanceMemberOne,this指向实例
    this.instanceMemberOne = params;
    }
};

类成员分为两种:实例成员和静态成员。实例成员分为实例属性和实例方法,静态成员分为静态属性和静态方法。属性和方法的实现方式是不同的,稍后会讲。

一个包含各类成员的class如下:

var classDemo = class {
    constructor(params) {
    this.instanceMemberOne = params;
    console.log(params)
    }
      
    //实例属性
    instanceMemberTwo = 100;
      
    //实例方法
    instanceFunction(param) {
        console.log("this is input :" + param) ;
    }

    //静态属性
    static classMember = 200;

    //静态方法
    static classMemberFunction(param) {
        console.log("this is static function :" + param);
    }

    //未初始化的实例属性
    instanceMember_undefined;

    //未初始化的静态属性
    static classMember_undefined;
};

编译成function后,类成员放在了三个地方 : function内,function属性、function.prototype属性。

  1. function内:构造器和实例属性
function classDemo(params) {
     (0, _classCallCheck3.default)(this, classDemo);   //类检查
     this.instanceMemberTwo = 100;                     //实例属性
     this.instanceMemberOne = params;                  //构造器
     console.log(params)
}
  1. function属性上:静态属性和静态方法
     classDemo.classMember === 200                          //true
     typeof classDemo.classMemberFunction === "function"    //true
  1. function.prototype属性上:实例方法
     typeof classDemo.prototype.instanceFunction === "function"   //true

而类成员中所有未初始化的实例属性和静态属性都不会被编译。

虽然class是function的语法糖,但class与function不同的是: function内可以写任意语句,而class内只能是声明语句。

class classDemo {
     if(condition) {}
     else{}                   //error
}

class内的声明语句还有以下要求:

  1. 不能有关键字。
  2. 声明时必须初始化,否则不会编译
  3. 声明语句之间不能有分号
class classDemo {
    var memberOne = "HelloWorld"    //error
    memberTwo                       //will not be compiled
    memberThree = "HelloWorld";     //error
}

class继承的本质:原型继承

子类的构造器里必须调用super()执行父类的构造器,调用super必须在this之前。

成员方法里可以通过super访问父类,不同的是,实例方法中super指向fatherFunction.prototype,静态方法中super指向fatherFunction

class classDemo {
      constructor(params) {
        this.instanceMemberOne = params;
        console.log(params);
      }

      //实例方法
      instanceFunction(param) {
        console.log("this is input :" + param);
      }

      //静态方法
      static classMemberFunction(param) {
        console.log("this is static function :" + param);
      }
    };

    class classDemoChild extends classDemo {
      constructor() {
        super();
      }
      
      //实例方法中super指向父函数的原型
      childInstanceFunction(param){
          super.instanceFunction(param)         //console.log("this is input :" + param);
      }
      
      //静态方法中super指向父函数
      static childClassMemberFunction(param){
          super.classMemberFunction(param)      //console.log("this is static function :" + param);
      }
    }

class的继承本质上是原型链的继承,即子函数的原型是父函数的实例

     classDemoChild.prototype.__proto__ .constructor === classDemo       //true

     classDemoChild.prototype.__proto__  === classDemo.prototype         //true

     classDemoChild.prototype instanceof === classDemo                   //true

class设计模式:单例模式和装饰者模式

单例模式

Javascript单例模式的实现和其他语言一样,在类中存放已创建的实例,每次new时如果实例已存在,则返回该实例,如果不存在则创建新实例。

class demo{
    constructor(params){
        if(demo.singleInstance){
            return demo.singleInstance
        }
        else{
            this.instanceMember = params
            demo.singleInstance = this               //编译后不存在constructor方法,this指向实例
        }
    }
}

装饰者模式

Javascript装饰者模式在不改变原有类结构的基础上,通过function在类声明时修改类结构。

装饰者分为两种:类装饰和成员装饰。类装饰用于修改类的静态成员,成员装饰用于修改类的实例成员的特性(attribute)

@decoratorClass
class demo{
}

//参数target指向demo
function decoratorClass(target){
    target.str = "HelloWorld";
}

console.log(demo.str)         // Hello world

装饰者模式的注解是一个参入target的function,当然也可以是高阶函数。

@decoratorClass('str')
class demo{
}

//参数target指向demo
function decoratorClass(memberName){
    return function(target){
        target[memberName] = "HelloWorld";
    }
}

console.log(demo.str)       // Hello world

成员装饰用于修改实例成员,比类装饰强大的是,成员装饰还可以修改实例成员的特性。

class demo{
    @decoratorInstance
    sum(a,b){
        return a + b;
    }
}

//target指向demo.protortpe
//name默认是装饰的方法名,即"sum"
//decorator必须要返回
function decoratorInstance(target,name,decorator){
    decorator.value = function(a,b){
        return a + b + 10;
    }
    decorator.writable = false
    return decorator
}

var demoInstance = new demo()  
console.log(demoInstance.sum(1,2))               // 13

成员装饰的效果和Object.defineProperty()一样

Object.defineProperty(demo.prototype,"sum",{
    value:function(a,b){
        return a + b + 10;
    },
    writable:false
})

@decoratorFunction注解语法需要babel的支持,使用前安装babel-plugin-transform-decorators-legacy,并在.babelrc文件里添加配置

{
    "plugins":["transform-decorators-legacy"]
}

由于JavaScript动态语言的特性,修改类结构变得很容易,因此实际使用时装饰者模式可以用于提取公共的装饰者逻辑。

总结

ES6在原生语法中加入了class,给JavaScript添加了类特性。TypeScript给JavaScript添加了静态检查,进一步完善了类特性。近来TS越来越热门,Vue3.0的更新也开始更好的支持TS。种种现象表明,虽然Javascript本质上未发生变化,但正向着Java等面向对象语言靠拢发展。

因此,未来对JavaScript的类特性、静态类型等特性上投入学习还是有必要的。