引言:
在重新看红宝书的时候。复习到面向对象的程序设计这一章节,重温了一下对象,原型,属性这三个核心概念和用法。顺便想到,在ES6中,可以通过class这一个关键字,用一种清爽的方式来定义一个类(实际上是一个对象,但是在语义上当做一个class)。所以实际上,class实现了构造一个对象的语法糖。
ES5是怎么样的?
在es5的时候,定义一个对象的方法是:
function Circle(radius) {
this.radius = radius;
Circle.circlesMade++;
}
Circle.draw = function draw(circle, canvas) { /* Canvas drawing code */ }
Object.defineProperty(Circle, "circlesMade", {
get: function() {
return !this._count ? 0 : this._count;
},
set: function(val) {
this._count = val;
}
});
Circle.prototype = {
area: function area() {
return Math.pow(this.radius, 2) * Math.PI;
}
};
Object.defineProperty(Circle.prototype, "radius", {
get: function() {
return this._radius;
},
set: function(radius) {
if (!Number.isInteger(radius))
throw new Error("Circle radius must be an integer.");
this._radius = radius;
}
});
值得关注的点有:
1. 对象的私有属性和方法,在constructor内部通过this绑定。
2. 对象的公有方法和属性,通过prototype来绑定。
3. 对象的属性的getter和setter,如果需要定制,需要通过defineObject()的方式来定义。
明显这种写法对于标准的面向对象方式来讲,是很不友好的。因此ES6中class的定义,本质上是一种语法糖,将对象的定义,用一种“类”的方式写出来。但是本质上,还是实现了ES5的语法。
ES6该怎么写呢?
针对上面的类,如果按照ES6使用class来写,则有以下的写法:
class Circle {
constructor(radius) {
this.radius = radius;
Circle.circlesMade++;
};
static draw(circle, canvas) {
// Canvas drawing code
};
static get circlesMade() {
return !this._count ? 0 : this._count;
};
static set circlesMade(val) {
this._count = val;
};
area() {
return Math.pow(this.radius, 2) * Math.PI;
};
get radius() {
return this._radius;
};
set radius(radius) {
if (!Number.isInteger(radius))
throw new Error("Circle radius must be an integer.");
this._radius = radius;
};
}
从以上的定义中,可以看到,对于一个class的定义:
1. 私有属性还是通过constructor进行传递
·
2. 公共的方法和属性,在constructor的外部定义,同时省略关键字function,该方法对应prototype上的方法,所有的实例共享一个Prototype 对象
*3. static方法表明,该方法是定义在类上的。直接通过类来调用,而不能通过实例对象来调用。这个方法在原来的es5中并不存在,属于新的特性。
4.getter和setter的定义可以当做一个特殊的方法,在属性前面声明get /set,然后定义自定义的getter和setter。
ES5 和 ES6的转换
使用babel,将以上es5的代码经过编译,结果如下:
"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 Circle = function () { function Circle(radius) { _classCallCheck(this, Circle); this.radius = radius; Circle.circlesMade++; } _createClass(Circle, [{ key: "area", value: function area() { return Math.pow(this.radius, 2) * Math.PI; } }, { key: "radius", get: function get() { return this._radius; }, set: function set(val) { if (!Number.isInteger(val)) throw Error("not a nubmer"); this._radius = val; } }], [{ key: "circlesMade",
[{ key: "area", value: function area() { return Math.pow(this.radius, 2) * Math.PI; } }, { key: "radius", get: function get() { return this._radius; }, set: function set(val) { if (!Number.isInteger(val)) throw Error("not a nubmer"); this._radius = val; } }], [{ key: "circlesMade", get: function get() { return !this._count ? 0 : this._count; }, set: function set(val) { this._count = val; } }]
get: function get() { return !this._count ? 0 : this._count; }, set: function set(val) { this._count = val; } }]); return Circle;}(); 让我们看看babel做了那些事情。根据上文的分析,可以发现定义的属性有三类,1是私有的属性,2是公有的属性和方法,3是静态的属性和方法。babel首先遍历class,将这三类三种属性分别筛选出来,生成配置项。
私有的属性直接在function的构造器内部定义。公有的方法,将value设置为function的定义,有自定义getter和setter的属性,将get和set放在属性的配置项里面。这两类将成为成为公有的属性和方法,放在protoType里面。最后一类是静态属性和方法,定义在staticProps里面。将所有的定义和方法区分并且相应的config,最后形成的格式为:
[{ // protoProps,最终将define到Constructor.prototype里面
key: "area",
value: function area() {
return Math.pow(this.radius, 2) * Math.PI;
}
}, {
key: "radius",
get: function get() {
return this._radius;
},
set: function set(val) {
if (!Number.isInteger(val)) throw Error("not a nubmer");
this._radius = val;
}
}], [{ // staticProps,define到Constructor上
key: "circlesMade",
get: function get() {
return !this._count ? 0 : this._count;
},
set: function set(val) {
this._count = val;
}
}]
babel定义了一个包装器,接受这些配置项,然后将这些属性根据config,定义在相应的位置上。这个包装器的定义如下:
var _createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) { // 获取所有属性
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false; // 添加enumberable属性
descriptor.configurable = true; // 添加configurable属性
if ("value" in descriptor) descriptor.writable = true; //添加writable属性
Object.defineProperty(target, descriptor.key, descriptor); } // 将属性定义在target上
}
return function (Constructor, protoProps, staticProps)
{ if (protoProps)
defineProperties(Constructor.prototype, protoProps); // 如果有protoProps,则定义在constructor.prototype上
if (staticProps)
defineProperties(Constructor, staticProps); // 如果有staticProps,则定义在类本身身上。
return Constructor; };
}();
可以看到这个包装器做的事情就两件:
1,如何根据props,配置配置项,并且把根据defineObject,粘到Object上。
2,根据配置项的类别,选择是配置在protoType上,还是类本身身上,从而达到static的目的。
经过这样的变换之后,可以得到:
经过这样的变换之后,可以得到: