Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ES6-class #18

Open
OPY-bbt opened this issue Nov 17, 2019 · 25 comments
Open

ES6-class #18

OPY-bbt opened this issue Nov 17, 2019 · 25 comments

Comments

@OPY-bbt
Copy link
Owner

OPY-bbt commented Nov 17, 2019

No description provided.

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

class 没有变量名提升,无法在定义之前使用。

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

named or unnamed

// unnamed
let Rectangle = class {};
console.log(Rectangle.name);
// output: "Rectangle"

// named
let Rectangle = class Rectangle2 {}
console.log(Rectangle.name);
// output: "Rectangle2"

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

class 的内容定义在大括号中,并且其中开启了严格模式

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

constructor

constructor 用于创建和初始化类,一个类中有多个constructor时,会报语法错误(SyntaxError: 解析代码时,Javascript引擎发现了不符合语法规范的tokens或token顺序时抛出SyntaxError)。

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

Prototype methods

定义在原型上的方法

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

静态属性值和原型上的属性值必须定义在class外部

Rectangle.staticWidth = 20;
Rectangle.prototype.prototypeWidth = 25;

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

基本的类的定义到此为止。看一下babel是如何处理的

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

class Animal { 
  constructor() {
    this.name = 'animal';
  }
  speak() {
    console.log('speak');
  }
  static eat() {
    console.log('eat');
  }
}
Animal.staticValue = 1;
Animal.prototype.prototypeWidth = 2;

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

      "use strict";

      function _classCallCheck(instance, Constructor) {
        if (!(instance instanceof Constructor)) {
          throw new TypeError("Cannot call a class as a 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);
        }
      }

      function _createClass(Constructor, protoProps, staticProps) {
        if (protoProps) _defineProperties(Constructor.prototype, protoProps);
        if (staticProps) _defineProperties(Constructor, staticProps);
        return Constructor;
      }

      var Animal =
        /*#__PURE__*/
        (function() {
          function Animal() {
            _classCallCheck(this, Animal);

            this.name = "animal";
          }

          _createClass(
            Animal,
            [
              {
                key: "speak",
                value: function speak() {
                  console.log("speak");
                }
              }
            ],
            [
              {
                key: "eat",
                value: function eat() {
                  console.log("eat");
                }
              }
            ]
          );

          return Animal;
        })();

      Animal.staticValue = 1;
      Animal.prototype.prototypeWidth = 2;

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

  • 将constructor里的内容放到构造函数内,并判断是否使用new调用
  • 将class上定义的静态方法和原型方法分为两个数组,遍历数组,分别通过Object.defineProperty添加到构造函数和原型上。

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

ES6中是不支持在类中直接声明字段的,所有的声明必须在构造函数内。虽然新的草案还没有正式通过,我们可以使用babel-plugin-proposal-class-properties.先来体验一下。

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

修改demo里的代码如下:

class Animal { 
  constructor() {
    this.name = 'animal';
  }
  speak1() {
  	console.log('speak');
  }
  speak2 = () => {
  	console.log('speak2');
  }
  speak3 = function (){
  	console.log('speak3');
  }
}

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

经过babel之后,可以看出,声明的字段方法全部放到了构造函数内,而不是原型上。如果有多个实例,性能相比原型方法就会下降。

var Animal =
/*#__PURE__*/
function () {
  function Animal() {
    _classCallCheck(this, Animal);

    this.speak2 = function () {
      console.log('speak2');
    };

    this.speak3 = function () {
      console.log('speak3');
    };

    this.name = 'animal';
  }

  _createClass(Animal, [{
    key: "speak1",
    value: function speak1() {
      console.log('speak');
    }
  }]);

  return Animal;
}();

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

为什么不为了性能,把箭头函数也绑定到原型上呢?这和箭头中的this不会创建自己的this,而是从自己的作用域链的上一层继承this有关。例如:

class Animal { 
  constructor() {
    this.name = 'animal';
  }
  speak1() {
  	console.log(this);
  }
  speak2 = () => {
  	console.log(this);
  }
  speak3 = function (){
  	console.log(this);
  }
}

var a = new Animal();
var speak1 = a.speak1;
var speak2 = a.speak2;
var speak3 = a.speak3;
speak1(); //undefined
speak2(); //Animal{}
speak3(); //undefined

为了实现这一点,babel需要将两种声明方式做区分。为了做到这一点,做了不少hack,JS真的是太男了。babel转换后代码如下:

var Animal =
/*#__PURE__*/
function () {
  function Animal() {
    var _this = this;

    _classCallCheck(this, Animal);

    this.speak2 = function () {
      console.log(_this);
    };

    this.speak3 = function () {
      console.log(this);
    };

    this.name = 'animal';
  }

  _createClass(Animal, [{
    key: "speak1",
    value: function speak1() {
      console.log(this);
    }
  }]);

  return Animal;
}();

没有什么岁月静好,只是有人替你负重前行, 感谢babel

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

extends 创建子类

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

继续扩展原来的例子, 新建Animal的子类

class Animal { 
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(this.name + ' makes a noise.');
  }
}

class Dog extends Animal {
  constructor() {
  	super();
  }
  speak() {
    console.log(this.name + ' barks.');
  }
}

var d = new Dog('Mitzie');
d.speak();

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 17, 2019

babel编译之后的代码,省略了部分无关代码。

      "use strict";

      function _typeof(obj) { }

      function _possibleConstructorReturn(self, call) {
        if (
          call &&
          (_typeof(call) === "object" || typeof call === "function")
        ) {
          return call;
        }
        return _assertThisInitialized(self);
      }
     
      // 判断是否先调用了父类构造函数。
      //  构造函数内不调用super就会报错??? 和我想的不太一样
      function _assertThisInitialized(self) {
        if (self === void 0) {
          throw new ReferenceError(
            "this hasn't been initialised - super() hasn't been called"
          );
        }
        return self;
      }

      function _getPrototypeOf(o) {
        return Object.getPrototypeOf(o);
      }

      // 组合寄生式继承
      function _inherits(subClass, superClass) {
        if (typeof superClass !== "function" && superClass !== null) {
          throw new TypeError(
            "Super expression must either be null or a function"
          );
        }
        subClass.prototype = Object.create(superClass && superClass.prototype, {
          constructor: { value: subClass, writable: true, configurable: true }
        });
        // 设置子类构造函数的原型对象为父类
        if (superClass) _setPrototypeOf(subClass, superClass);
      }

      function _setPrototypeOf(o, p) {
        return  Object.setPrototypeOf(o, p);
      }

      function _classCallCheck(instance, Constructor) { }

      function _defineProperties(target, props) {}

      function _createClass(Constructor, protoProps, staticProps) {}

      var Animal =
        /*#__PURE__*/
        (function() {
          function Animal(name) {
            _classCallCheck(this, Animal);

            this.name = name;
          }

          _createClass(Animal, [
            {
              key: "speak",
              value: function speak() {
                console.log(this.name + " makes a noise.");
              }
            }
          ]);

          return Animal;
        })();

      var Dog =
        /*#__PURE__*/
        (function(_Animal) {
          _inherits(Dog, _Animal);

          function Dog() {
            _classCallCheck(this, Dog);

            return _possibleConstructorReturn(
              this,
              _getPrototypeOf(Dog).call(this)
            );
          }

          _createClass(Dog, [
            {
              key: "speak",
              value: function speak() {
                console.log(this.name + " barks.");
              }
            }
          ]);

          return Dog;
        })(Animal);

      var d = new Dog("Mitzie");
      d.speak();

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 18, 2019

假如Animal 和 Dog都有static speak方法,并且在Dog的speak方法中还调用了Animal的speak方法,如下:

  static speak() {
  	super.speak();
        console.log('dog speak');
  }

这时super应该指向的是构造函数,而不是实例了。经过babel转化为下:

          _createClass(Dog, null, [
            {
              key: "speak",
              value: function speak() {
                // 获取构造函数原型对象即构造函数Animal上的speak方法。
                _get(_getPrototypeOf(Dog), "speak", this).call(this);

                console.log("dog speak");
              }
            }
          ]);

重点在_get 方法

      function _get(target, property, receiver) {
        if (typeof Reflect !== "undefined" && Reflect.get) {
         // 类似于target[property],只不过是通过调用函数来实现,最关键的是第三个参数,如果target对象中指定了getter,receiver则为getter调用时的this值。
          _get = Reflect.get;
        } else {
          _get = function _get(target, property, receiver) {
            //  按照原型链向上寻找,直到找到对应的property
            var base = _superPropBase(target, property);
            if (!base) return;
            var desc = Object.getOwnPropertyDescriptor(base, property);
            if (desc.get) { // getter,执行getter并且指定其中this值
              return desc.get.call(receiver);
            }
            return desc.value; // 普通方法
          };
        }
        return _get(target, property, receiver || target);
      }

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 19, 2019

继承的几种方式

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 19, 2019

原型链

Child.prototype = new Parent();
缺点:引用类型的原型会被所有实例共享, 例如

function Parent() {
    this.colors = [1, 2];
}
function Child(){}
Child.prototype = new Parent();
var c1 = new Child();
var c2 = new Child();
c1.colors.push(3);
console.log(c2.colors); // [1, 2, 3]

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 19, 2019

借用构造函数

function Child() {
    Parent.call(this);
}

缺点:无法复用原型上的函数

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 19, 2019

组合继承

使用借用构造函数继承属性,原型链继承方法

缺点:会调用两次父类构造函数,并且在子类原型上有多余的属性(来自原型链继承),例如

function Child() {
     Parent.call(this);
}
// 这里存在多余属性
Child.prototype = new Parent();

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 19, 2019

原型式继承

这种方法没有严格意义上的构造函数,借助已有的对象创建新对象

function object(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

function object(o) {
    return Object.create(o);
}

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 19, 2019

寄生式继承

创建一个仅用于封装继承过程的函数

function createChild(o) {
    var child = object.create(o);
    // 增强对象
    child.sayHi = function() {};
    reutrn child;
}

@OPY-bbt
Copy link
Owner Author

OPY-bbt commented Nov 19, 2019

寄生组合式继承

是对组合继承的一种优化,优化组合继承中的原型链部分,少调用一次父类构造函数

function inherit(child, parent) {
    // 创建父类原型副本
    var prototype = Object.create(parent.prototype);
    prototype.constructor = child;
    child.prototype = prototype;
}
function Parent() {}
function Child() {
     Parent.call(this);
}
inherit(Child, Parent);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant