JS拾荒のes6类全解读与实现

本文深入探讨ES6中的类和继承机制,包括类的基本用法、静态方法、继承特性及其实现细节,并对比ES5的实现方式。
  • es6类功能测试
    • es5和es6比较
    • 关于继承
  • 关于类的构造函数的指向
  • 使用es5实现es6的类
    • 源码

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值