[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)