亲自上手es6的class

作者: physihan | 来源:发表于2016-11-28 17:53 被阅读354次

    新一代标准已经提出有些日子了,最近在写react的时候,react用了大量的class的语法去写组件,对于class的理解不是很深刻,于是这里把从babel转换的代码分享给大家。

    类=构造函数+原型

    es6标准的类和其他语言的类很相似,但是这只是一种语法糖,底层还是通过原型继承实现的,首先看一个简单的类的形式

     class A {
       constructor(){
         this.name='xiaoming'
       }
       sayHello(){
         console.log('hello')
       }
     }
    

    包含一个构造函数,和一个类里面的函数,如果大家对原型继承有所了解的话,这种形式可以近似写成

    function A(){
      this.name='xiaoming';
    }
    A.prototype={
      sayHello:function(){
        console.log('hello')
      }
    }
    

    这就是类的雏形,但是实际操作上还是有些不同的,下面就是babel翻译的es5的语法

    'use strict';
    
    var _createClass = function () { 
    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);
       } 
    }
    return function (Constructor, protoProps, staticProps) {
      if (protoProps) 
        defineProperties(Constructor.prototype, protoProps);
      if (staticProps) 
        defineProperties(Constructor, staticProps); 
    return Constructor; 
    }}();
    
    function _classCallCheck(instance, Constructor) { 
      if (!(instance instanceof Constructor))  {
        throw new TypeError("Cannot call a class as a function");
      } 
    }
    
    var A = function () {
      function A() {
        _classCallCheck(this, A);
        this.name = 'xiaoming';
      }
    
      _createClass(A, [{
        key: 'sayHello',
        value: function sayHello() {
          console.log('hello');
        }
      }]);
    
      return A;
    }();
    

    这个代码感兴趣大家可以看看,其中有几个地方需要注意,这个类A不能当成函数去调用,A()这种方法调用会报错,可以

    let a = new A();
    

    这样去实例化一个类,当然类都是需要继承的,新版本中继承用extends来实现,考虑这样一个类B继承类A

     class A {
      constructor(){
        this.name='xiaoming'
      }
      sayHello(){
        console.log('hello')
      }
    }
      class B extends A{
      constructor(){
        super() 
        this.age=12
      }
      sayHello(){
        console.log('hello')
      }
    }
    

    这是加完继承后的代码

    'use strict';
    
    var _createClass = function () { 
    function defineProperties(target, props) { 
    ...//和前面一样
     }();
    
    function _possibleConstructorReturn(self, call) { if (!self) {
      throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } 
      return call && (typeof call === "object" || typeof call === "function") ? call : self; 
    }
    
    function _inherits(subClass, superClass) { 
      if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function,not " + typeof superClass); 
      } 
      subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
        value: subClass, 
        enumerable: false,
        writable: true,
        configurable: true 
      }
     }); 
    if (superClass) 
      Object.setPrototypeOf ?
      Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
    
    function _classCallCheck(instance, Constructor) {
      if (!(instance instanceof Constructor)) { 
      throw new TypeError("Cannot call a class as a function");
      } 
    }
    
    var A = function () {
      function A() {
        _classCallCheck(this, A);
    
        this.name = 'xiaoming';
        this.say = function () {
          console.log("123");
        };
      }
    
      _createClass(A, [{
        key: 'sayHello',
        value: function sayHello() {
          console.log('hello');
        }
      }]);
    
      return A;
    }();
    
    var B = function (_A) {
      _inherits(B, _A);
    
      function B() {
        _classCallCheck(this, B);
    
        var _this = _possibleConstructorReturn(this, (B.__proto__ || Object.getPrototypeOf(B)).call(this));
    
        _this.age =12;
        return _this;
      }
    
      _createClass(B, [{
        key: 'sayHello',
        value: function sayHello() {
          console.log('hello');
        }
      }]);
    
      return B;
    }(A);
    

    代码很长,都不用看,只看_inherits这个函数,从这一句

    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: { 
        value: subClass, 
        enumerable: false, 
        writable: true, 
        configurable: true 
      }
    });
    

    这里有个小技巧,&&当计算前面为false时,就不计算后面的表达式了,当然返回的是false或者最后一个值,这是从左向右计算的,这里意思就是如果 superClass 存在,那就计算 superClass.prototype,当然也就是存在的,这一句就是将B的原型设为一个以 A 的 prototype 为原型的对象,也就是说

    B.prototype.__proto__=A.prototype
    

    proto VS prototype

    这里__proto__这个属性是每个对象都有的,就是因为这个属性的存在,对象可以继承很多不是他自己的属性或方法,比如toString()。虽然toString()不是这个对象自己的方法,但是去调用一个对象的这个方法时,这个对象自己没有,就会去找这个对象的__proto__,在它的__proto__所指向的对象中去找,如果找不到,就会继续去在这个__proto____proto__中去找,这就形成了一个原型链,直到找到为止,找不到就会报错。
    prototype__proto__之间的区别很明显,prototype是函数对象所特有的,他作为一个属性指向另一个对象,即这个函数的原型对象,它存在的目的只是为了生产对象,通过这个函数new出来的对象都有一个__proto__属性指向这个函数的原型对象,从下面代码就可以看出来

    function A(){
      this.name='xiaoming'
    }
    A.prototype={
      sayhi:function(){
        console.log('hi')
      }
    }
    var a=new A();
    console.log(a.__proto__===A.prototype)
    // true
    

    也就是说,对象的__proto__属性指向那个制造这个对象的构造函数的原型对象,通过对象字面量形式创建的对象的__proto__就是Object.prototype,

    o = {};// 以字面量方式创建的空对象就相当于:
    o = Object.create(Object.prototype);
    

    那么继续之前的话题,_inherits函数中有这一句

    if (superClass) 
    Object.setPrototypeOf ? 
    Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
    

    这样子类的__proto__就指向了父类,就将原型链头指向了父类,也就是在B中可以使用A的方法和属性,注意是在B这个构造函数内,B的原型对象在之前的代码已经解释了,通过Object.create方法把B的原型对象绑定到了A的原型上,B的原型对象可以通过原型链原型继承使用A的原型对象的属性和方法。
    总之最后的情况是这样的

    B.__proto__=A
    B.prototype=对象xx
    对象xx:{
    __proto__:A.prototype
    constructor:B
    ...
    }
    

    当使用new去创建一个B的实例b时会发生这样的过程
    在constructor中会得到以this.xx,比如this.age this.age...
    等等还有一个不能忘了在B的构造函数中会有个super(),这样A的构造函数也会执行了,不然没有name属性
    然后就是将这个对象的__proto__指向那个对象xx,也就是B这个构造函数的原型对象,这样就能访问这个原型链上的属性和方法了。

    总结

    新版本的类也是基于原型继承的,所以只要把基础打好了,遇到新的东西也理解的比较清楚,class中constructor对应的还是以前的构造函数,整个类里面的内容就是这个构造函数的原型对象的内容,如果有继承还要加上继承的对象的内容,我们依然可以用类名xx来指代以前的构造函数,xx.prototype来指代原型对象。新的语法形式,对外隐藏了实现的细节,写起来更加简洁,还有会在不正当时使用时的错误提示。

    相关文章

      网友评论

        本文标题:亲自上手es6的class

        本文链接:https://www.haomeiwen.com/subject/bjunpttx.html