這是一篇番外篇,其實他的主線是由一篇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==實例對象
。最終的結果就是在實例對象
上新增了指定的屬性。