Es6 Class

  用jest写react的测试的时候有出现一个问题,就是组件中有用到anime.js来制作动画(为啥不用CSSTransition,因为这玩意貌似满足不了需求,也可能是我太菜了..),结果在jest执行到anime.js的某个方法后卡住(现在想想其实是一直不输出code coverage,可能是Istanbul的问题..)。

  后来呢就决定绕过所有anime.js方法。具体是怎么做的呢,首先因为anime.js是直接操作dom的,所以anime.js的所有功能都写在了componentDidMount里面,所以在测试的时候在beforeEach里面添了这么一行

1
MyComponent.prototype.componentDidMount = () => {}

  这么一来,在测试componentDidMount的时候就是啥也没执行了。看到这里,应该就很清楚,js里面的class其实本质上就是一个function,在class里面定义一个成员方法大致等于

1
2
3
4
5
6
7
8
9
10
class MyClass {
MyFunction(){}
}

function MyClass(){}

MyClass.prototype={
constructor: MyClass,
MyFunction: function(){}
}

  到这里还没结束,基础不牢固的我(得找个时候去看看犀牛书啥的..)又开始很奇怪了:为啥这里要用function来模拟class呢?而且function在js里面到底算是一种什么样的结构,js的基本类型里面可没有function。

  在尝试使用object来模仿class时,发现object是没有构造函数的,而直接使用const a = {}会直接获得一个object的实例,也就是说用object来声明一个类是不可能的,因为获得的永远是一个实例。如果非要用object来模拟class,就只能使用工厂模式,定义一个方法,传入必要参数返回一个object。具体形式如下

1
2
3
4
5
6
7
function classFactory(name,description){
const object = {
name: name,
description: description
}
return object;
}

  这样的方法怎么能叫做class?它甚至无法做到继承。所以目光又转向了function。那么function到底是个啥玩意儿呢?
  js里面在基本类型之外还有一大把的内置对象,其中有个内置对象是Function,而所有定义的function都是Function对象的实例。所以以下两种写法其实作用一样。

1
2
3
4
function sum(a,b){
return a+b;
}
var sum = new Function('a', 'b', 'return a + b');

  至于Function的对象的特性啥的,建议看MDN
  重点来了,如何使用function来模拟一个class并且能让他可以继承呢?现在的我已经是看过一点原型了,所以自己的第一想法是张这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function Person(name) {
this.name = name;
this.getDesc = () => {
return `Myname is ${this.name}`;
}
this.getName = () => {
return `${this.name}`;
}
}


function Male(){
Person.apply(this,arguments);
this.gender = 'male';
this.getDesc = () => {
return `Myname is ${this.name} and I'm ${this.gender}`;
}
}

Male.prototype = new Person('people');

function MaleStudent(){
Person.apply(this,arguments);
this.job = 'student';
this.getDesc = () => {
return `Myname is ${this.name} and I'm ${this.gender} I'm a ${this.job}`;
}
}

MaleStudent.prototype = new Male();

  尝试看看function是什么样的: result
  看起来还ok的样子,但是这个继承是没法继承构造方法的,而且这个prototype直接等于一个实例看着有点膈应..,要应用构造函数的话,可以在子类里面调用父类的apply或者call,类似这样Person.apply(this,arguments)。反正网上js模拟类的方法多得是,看着给人的感觉就是在写…茴的四种写法..所以具体性能优劣等碰到了再说。
  还有一点要提的是,在阮一峰博客中看到,使用这种方法继承的时候需要在Male.prototype = new Person(‘people’);后面添加Male.prototype.constructor=Male,并且指明了在所有改变prototype之后都需要重新设定constructor,但是我这里尝试添加和没有添加constructor发现这个构造方法都是生效的,instanceof关键字也都是true(讲道理,instanceof 是判断构造函数是否在当前的instance的原型链上面,所以子类实例 instanceof 父类构造函数就应该是true吧)。
  所以说是说需要重新指定constructor,但是并没有找到这么做的现实原因…
  到这里貌似结束了?不,当我们写ES6以上的语法时候,由于node环境是不支持class语法的,所以我们都是用babel来进行编译的,react的jsx编译也用的babel的编译器,讲道理,最广泛的说法应该是babel编译出来的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }

function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
var A = function A() {
_classCallCheck(this, A);


this.name = 'name';
};

var B =
/*#__PURE__*/
function (_A) {
_inherits(B, _A);

function B() {
_classCallCheck(this, B);

return _possibleConstructorReturn(this, _getPrototypeOf(B).apply(this, arguments));
}

return B;
}(A);

  忽略check方法咱默认check方法都通过,那么这里具体执行的其实就是 _inherits(B, _A);和getPrototyoeOf().apply(this.arguments)。看样子好像就是首先将父类的赋予子类的prototype,自定义一个constructor,然后父类构造函数绑定。

  好了,到此为止,这个componentDidMount引出来的发散出来的思考就这些了。不知道为什么,查了一大堆资料,却总觉得我是在挖JavaScript的黑历史…我相信这些原型链搞出来的继承应该是 Ecma International希望屏蔽掉(这些对象的定义之类的就应该标准化…),不希望开发者用到的。而且有了ES6 class和babel之后再用prototype确实给人感觉很蠢…