这是一篇番外篇,其实他的主线是由一篇ES6-Class科普文章引起的“惨案”。虽然是番外篇,可是有些剧情仍是不容错过的。javascript
如今咱们来进入番外篇的主线:ES6的Class
是ES5构造函数的语法糖。java
那仍是让咱们简单的回归一下ES5的类
是如何构建的:es6
//构造函数
var Circle = function(name){
this.name = name;
Circle.circlesMade++;
}
//定义一个类属性
Object.defineProperty(Circle, "circlesMade", {
get: function() {
return !this._count ? 0 : this._count;
},
set: function(val) {
this._count = val;
}
});
//定义构造函数的prototype
Circle.prototype = {
constructor:Circle,
toString:function(){
return this.name+'南蓁';
}
}
复制代码
虽然是封装(OOP编程三大特色之一:封装性)了用于表明一系列数据和操做的Circle
类。可是这段代码有些许的冗余。也就是说,实例相关的属性和方法,须要一坨代码,prototype相关的也须要一坨,定义在类上的又双叒一坨。可是写一个类,须要照顾不少(n>3)的情绪。作一个ES5的类
好蓝啊。编程
因此,他的彩霞仙子来了。用最优雅的方式来装X。数组
Talk is cheap ,show you the code.bash
class Circle {
construcotr(name){
this.name = name;
Circle.circlesMade++
}
static get circlesMade() {
return !this._count ? 0 : this._count;
};
static set circlesMade(val) {
this._count = val;
};
toString(){
return this.name+'南蓁';
}
}
复制代码
经过对Prototype
糖化处理,代码看起来简洁不少。babel
可是具体如何实现的呢。咱们来进行脱糖处理。函数
一口吃不成胖子,咱们对上面的代码,进行由浅入深的分析一下。可是在分析以前,须要对JS的Class
有一个基本的认识。其实JS中的Class
和其余传统OOP语言(C++,JAVA)是不同的。它是基于Prototype
的类的构造和继承的实现的。post
它的公式以下:ui
JsClass = Constructor Function + Prototype
其中Constructor Function
用于定义实例中行为(属性+方法)Prototype
用于定义实例共有的行为
因此咱们来构建半个类,只拥有Constructor Function
。
Note:这里须要解释半个类,因为为了更好的解释如何进行糖化的,就按ES5的结构类比介绍。可是须要注意的就是,在ES6class
中定义的方法都是挂载在prototype
上的。(不包含static
方法)
class Test {
constructor(x,y){
this.x =x;
this.y = y;
}
}
复制代码
来直接看看脱糖以后的效果。
"use strict";
function _instanceof(left, right) {
if (
right != null &&
typeof Symbol !== "undefined" &&
right[Symbol.hasInstance]
) {
return !!right[Symbol.hasInstance](left);
} else {
return left instanceof right;
}
}
function _classCallCheck(instance, Constructor) {
if (!_instanceof(instance, Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Test = function Test(x, y) {
_classCallCheck(this, Test);
this.x = x;
this.y = y;
};
复制代码
会发现这里的仅仅比常规的Constructor Function
多了一步_classCallCheck(this, Test);
其实就是多了这一步,就制约了ES6的Class
不能像ES5的构造函数同样,进行直接调用。(虽然,ES6的Class
的typeof Class == 'funtion' //ture
) 具体缘由能够参照主线剧情
而后咱们继续完善一个完整的类: 可是在此以前,咱们须要明确一点就是
class中全部的方法,都是挂载在
prototype
static
属性的"完整"类class Test {
constructor(x,y){
this.x =x;
this.y = y;
}
toString(){
return '北宸'
}
}
复制代码
脱糖处理以后的对应代码以下:
为了针对重点解释,咱们就将_classCallCheck
相关代码简化,只留下重点代码:
"use strict";
function _instanceof(left, right) { }
function _classCallCheck(instance, Constructor) {}
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);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
var Test =
/*#__PURE__*/
(function() {
function Test(x, y) {
_classCallCheck(this, Test);
this.x = x;
this.y = y;
}
_createClass(Test, [
{
key: "toString",
value: function toString() {
return "北宸";
}
}
]);
return Test;
})();
复制代码
让咱们分析一波:
首先映入眼帘的是一个IIFE(当即执行函数/自执行匿名函数):在定义时就会执行的函数。
那这个IIFE函数被执行以后,发生了啥。咦。有一段代码很熟悉。
function Test(x, y) {
_classCallCheck(this, Test);
this.x = x;
this.y = y;
}
复制代码
这不就是定义了一个构造函数吗,并对这个函数进行_classCallCheck
检查。
其实这段代码,最神奇的地方在于_createClass
这个函数的调用。
先把谜底揭晓一下,这个方法就是在为目标构造prototype
的过程。
而后咱们来参考一下实现思路:
_createClass(Test, [
{
key: "toString",
value: function toString() {
return "北宸";
}
}
]);
||
||
||
\/
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
||
||
||
\/
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);
}
}
复制代码
其实这段代码中,_createClass(Constructor, protoProps, staticProps)
是核心。在函数内部,依次对protoProps
,staticProps
进行_defineProperties
处理。分别目标类进行prototype
和静态属性进行设置
Note:
static
方法都是挂载在prototype
上的。咱们在class
中定义了一个constructor()
,可是这个方法被加载在protoProps
数组里面了。这是由于在ES5Function
的实例中的prototype
,都有一个默认属性constructor
指向构造函数。static
方法都是enumerable=false
也就是说,es6中定义的方法,是不会被for(let props in obj)
所察觉的,而ES5中prototype
是能够的。static
属性的"完整"类直接撸一个实例:(代码结构仍是沿用前面的例子)
class Test {
//新增static属性
static _count=1;
constructor(x,y){
this.x =x;
this.y = y;
}
toString(){
return '北宸'
}
//新增static方法
static getAllName(){
return '北宸南蓁'
}
}
复制代码
note:针对如今ES6的标准,是没法对class
新增static
属性的。只有在babel
的stage-0/1/2/3
的协助上才能够。
咱们也会将一些上面已经解释过的代码,简化:
"use strict";
function _instanceof(left, right) {}
function _classCallCheck(instance, Constructor) {}
function _defineProperties(target, props) {}
function _createClass(Constructor, protoProps, staticProps) {}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
var Test =
/*#__PURE__*/
(function() {
//新增static属性
function Test(x, y) {
_classCallCheck(this, Test);
this.x = x;
this.y = y;
}
_createClass(
Test,
[
{
key: "toString",
value: function toString() {
return "北宸";
} //新增static方法
}
],
[
{
key: "getAllName",
value: function getAllName() {
return "北宸南蓁";
}
}
]
);
return Test;
})();
_defineProperty(Test, "_count", 1);
复制代码
这里有几个地方须要提醒的是:
_createClass
中多了第三个参数staticProps
,也就是说,static
方法是和prototype
的信息存贮是分开的。_createClass
中针对staticProps
信息也是通过_defineProperties
处理的。也就是说,挂载在Test
上的方法也是enumerable=false
。不能被for(let propsName in Class)
被捕获。prototype
中的方法和属性是全局的。这也就证明了,主线情节中,Circle能根据挂载在类上的静态属性(circlesMade)计算被实例化了多少次static
是enumerable=true
的。这个特性和在class
中直接static classPropsName ='value'
同样,是须要babel
的stage-0
及以上才能够实现。
class Test {
count=count;
name =name;
}
复制代码
"use strict";
function _instanceof(left, right) {}
function _classCallCheck(instance, Constructor) {}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
var Test = function Test() {
_classCallCheck(this, Test);
_defineProperty(this, "count", count);
_defineProperty(this, "name", name);
};
复制代码
其实这段代码就是在每次进行一个实例属性的定义的时候,在脱糖以后,在构造函数中调用_defineProperty(this, key, value);
进行值的绑定。
因为是定义的是实例属性,因此如上代码中count
/name
都是在实例化的时候传入到类中的。new Test(1,'北宸')
。因此在_defineProperty
中判断if(key in obj)
的时候,是true
。因此,就会进行Object.defineProperty
的处理。同时在实例化的时候,this==实例对象
。最终的结果就是在实例对象
上新增了指定的属性。