JavaScript 系列之类(二)

557 阅读2分钟

这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战

二、ES6 上的类经过 Babel 编译

2.1 编译一

ES6 代码为:

class Person {
  constructor(name) {
    this.name = name;
  }
}

Babel 编译为:

"use strict";

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

var Person = function Person(name) {
  _classCallCheck(this, Person);

  this.name = name;
};

_classCallCheck 的作用是检查 Person 是否是通过 new 的方式调用,在上面,我们也说过,类必须使用 new 调用,否则会报错。

使用 new 来调用 Person 时,我们会构造一个新对象并把它绑定到 Person() 调用中的 this 上。

当我们使用 var person = Person() 的形式调用的时候,this 指向 window,所以 instance instanceof Constructor 就会为 false,与 ES6 的要求一致。

2.2 编译二

ES6 代码为:

class Person {
  // 实例属性
  foo = 'foo';
  
  // 静态属性
  static bar = 'bar';

  constructor(name) {
    this.name = name;
  }
}

Babel 编译为:

'use strict';

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

var Person = function Person(name) {
  _classCallCheck(this, Person);

  this.foo = 'foo';

  this.name = name;
};

Person.bar = 'bar';

2.3 编译三

ES6 代码为:

class Person {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    return 'hello, I am ' + this.name;
  }

  static onlySayHello() {
    return 'hello'
  }

  get name() {
    return 'kevin';
  }

  set name(newName) {
    console.log('new name 为:' + newName)
  }
}

对应到 ES5 的代码应该是:

function Person(name) {
  this.name = name;
}

Person.prototype =  {
  sayHello: function () {
    return 'hello, I am ' + this.name;
  },
  get name() {
    return 'kevin';
  },
  set name(newName) {
    console.log('new name 为:' + newName)
  }
}

Person.onlySayHello = function () {
  return 'hello'
};

Babel 编译后为:

'use strict';

var _createClass = function() {
  function defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }
  return function(Constructor, protoProps, staticProps) {
    if (protoProps) defineProperties(Constructor.prototype, protoProps);
    if (staticProps) defineProperties(Constructor, staticProps);
    return Constructor;
  };
}();

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

var Person = function() {
  function Person(name) {
    _classCallCheck(this, Person);

    this.name = name;
  }

  _createClass(Person, [{
    key: 'sayHello',
    value: function sayHello() {
      return 'hello, I am ' + this.name;
    }
  }, {
    key: 'name',
    get: function get() {
      return 'kevin';
    },
    set: function set(newName) {
      console.log('new name 为:' + newName);
    }
  }], [{
    key: 'onlySayHello',
    value: function onlySayHello() {
      return 'hello';
    }
  }]);

  return Person;
}();

我们可以看到 Babel 生成了一个 _createClass 辅助函数,该函数传入三个参数:

  • 第一个是构造函数,在这个例子中也就是 Person
  • 第二个是要添加到原型上的函数数组
  • 第三个是要添加到构造函数本身的函数数组,也就是所有添加 static 关键字的函数。

该函数的作用就是将函数数组中的方法添加到构造函数或者构造函数的原型中,最后返回这个构造函数。

在其中,又生成了一个 defineProperties 辅助函数,使用 Object.defineProperty 方法添加属性。

默认 enumerable 为 false,configurable 为 true,这个在上面也有强调过,是为了防止 Object.keys() 之类的方法遍历到。然后通过判断 value 是否存在,来判断是否是 getter 和 setter。如果存在 value,就为 descriptor 添加 value 和 writable 属性,如果不存在,就直接使用 get 和 set 属性。