[TOC]
## es6類功能測試
### es5和es6比較
關鍵詞:`靜態方法`、`static`
**注意事項**:
1. 我們仍然可以使用es5 `類.prototype` 的方式綁定公用方法和屬性
2. es6雖然不支持靜態屬性,但可以通過 `類.x` 的方式達到靜態屬性的效果。
3. 我們無法像es5構造函數一樣將`類`當做函數執行
```
// 1)
class Child{
constructor(){
}
//靜態方法
static echo(){
console.log('這是一個靜態方法');
}
}
Child.a = 1; //相當于靜態屬性
console.log(Child.a); //1
Child.echo(); //這是一個靜態方法
Child.prototype.b = 2;
let c1 = new Child();
console.log(c1.b); //2
Child(); //TypeError: Class constructor Child cannot be invoked without 'new'
```
### 關于繼承
- 當不填` constructor` 時,使用了繼承的子類可以順利得到父類給實例的 `私有屬性和方法`。
- 子類可以繼承到父類原型上的方法以及父類的靜態方法
```
// 1)
class Parent{
constructor(){
this.a = 1; //私有屬性
}
echo(){
console.log('我是Parent中的方法');
}
}
class Child extends Parent{
//沒有填寫constructor
}
let c1 = new Child();
console.log(c1) //{a:1}
console.log(c1.constructor) // [Function:Child]
c1.echo(); //我是Parent中的方法
```
- 當填了 `constructor`,必須調用 `super` 方法,否則會報以下錯誤。
```
// 2)
class Parent{
constructor(){}
}
class Child extends Parent{
constructor(){}
}
let c1 = new Child();
<<<
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
```
- 當父類在構造函數中返回一個對象時,子類的實例將會是這個對象。
- 這個對象不會包含父類以及子類原型上的方法(但子類仍然可以繼承到父類的靜態方法)
- 這個對象的 `constructor` 指向的是 `Object構造函數`,而不是子類或父類的構造函數
```
// 3)
class Parent{
constructor(){
this.a = 2;
return {
a:1
};
}
echo(){
console.log('我是Parent中的方法');
}
static echo(){
console.log('我是Parent中的靜態方法');
}
}
class Child extends Parent{
constructor(){
super();
this.b = 2;
}
notice(){
console.log('我是子類中的方法')
}
}
let c1 = new Child();
console.log(c1); //{a:1,b:2}
console.log(c1.constructor); //Function Object
console.log(c1 instanceof Child); //false
console.log(c1 instanceof Parent); //false
console.log(c1 instanceof Object); //true
Child.echo(); //我是Parent中的靜態方法
c1.notice(); //c1.notice is not a function
c1.echo(); //TypeError: c1.echo is not a function
```
## 關于類的構造函數的指向
構造函數的指向(`__proto__`)是指向 `Function` 構造函數的原型的
```
console.log(Object.getPrototypeOf(a)===Function.prototype); //true
```
**注意**:
> 這里說的是構造函數的指向,而不是構造函數原型的指向
## 使用es5實現es6的類
首先我們先定義兩個構造函數
```
function Parent(){};
function Child(){};
```
如果是es6,我們要實現繼承的話只需要`Child extends Parent`即可,但es5顯然是不存在這種語法糖的,我們需要通過把構造函數包裝進一個函數中(這個函數其實就是所謂的類),通過函數自執行時傳入的參數來決定這個類繼承自誰。
**這也是通過class創建的類,去掉語法糖的皮,編譯過后,真正的運行時的樣子。**
```
var Child = function(Parent){
_inherits(Child,Parent);
function Child(){}
return Child;
}(Parent)
var Parent = function(){
function Parent(){};
return Parent;
}()
```
注意我們在匿名函數自執行時使用了一個方法 `_inherits` 來具體實現類的繼承。
```
function _inherits(subCon,superCon){
// 繼承父類prototype上的方法(公有方法)
let subCon.prototype = Object.create(superCon.prototype,{constructor:{value:subCon}});
// 繼承父類的static方法(靜態方法)
subCon.__proto__ = superCon;
}
```
除此之外子類還需要繼承父類的私有屬性和方法
```
var Child = function(Parent){
...
function Child(){
Object.getPrototypeOf(Child).call(this); //在上面的_inheris方法中我們已經將Child__proto__ = Parent,故這里的getPrototypeOf 即為 Parent
}
return Child;
}(Parent)
```
并且當父類返回的是一個對象時,我們子類實例化時返回的對象也要變成這個父類返回的對象。
```
var Child = function(Parent){
...
function Child(){
let ret = this;
let o = Object.getPrototype(Child).call(this);
if(typeof o === 'object'){
ret = o;
// 還可以在這里進行一些子類的私有屬性和方法的掛載
}
return ret;
}
return Child;
}(Parent)
```
除此之外,我們需要確保類只能用 `new ` 來實例化,而不能單獨執行。(我們不能像es5一樣讓構造函數像普通函數一樣執行)
So我們在構造函數調用時候使用了一個 `__classCallCheck` 方法來檢查類
這個方法之所以有效的原因在于,如果是像調用普通函數一樣調用類,那么此時的 `this` 應該指向的是 `window ` or `undefined` ,這兩貨顯然不是Child的實例。
```
function _classCallCheck(instance,constructor){ //檢查當前類 有沒有使用new
if(!(instance instanceof constructor)) throw Error('Without new');
}
...
function Child(){
_classCallCheck(this,Child);
...
}
...
```
另外當我們在 `class` 中聲明一個公共方法或則靜態方法時,內部其實調用的是 `defineProperty` 來給構造函數的原型和構造函數本身上添加屬性來實現的。
```
...
function Parent(){
...
_createClass(Parent,[
//公共方法
{key:'publicFn',value:function(){
console.log(1);
}}
...
],[
//靜態方法
{key:'staticFn',value:function(){
console.log(2);
}}
])
}
...
function _createClass(target,protoProperties,staticProperties){
if(protoProperties){
defineProperties(target.prototype,protoProperties);
}
if(staticProperties){
defineProperties(target,staticProperties);
}
}
function defineProperties(target,properties){
var conf = {configurable:true,writable:true,enumerable:true}
for(var i=0;i<properties.length;++i){
conf.value = properties[i].value;
Object.defineProperty(target,properties[i].key,conf);
}
}
```
### 源碼
> 點擊獲取源碼 [github](https://github.com/fancierpj0/es6-class)