OPY-bbt / OPY-bbt.github.io

my webpage
https://opy-bbt.github.io/
0 stars 0 forks source link

ES6-class #18

Open OPY-bbt opened 4 years ago

OPY-bbt commented 4 years ago

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

OPY-bbt commented 4 years ago

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 commented 4 years ago

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

OPY-bbt commented 4 years ago

constructor

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

OPY-bbt commented 4 years ago

Prototype methods

定义在原型上的方法

OPY-bbt commented 4 years ago

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

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

OPY-bbt commented 4 years ago

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

OPY-bbt commented 4 years ago
class Animal { 
  constructor() {
    this.name = 'animal';
  }
  speak() {
    console.log('speak');
  }
  static eat() {
    console.log('eat');
  }
}
Animal.staticValue = 1;
Animal.prototype.prototypeWidth = 2;
OPY-bbt commented 4 years ago
      "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 commented 4 years ago
OPY-bbt commented 4 years ago

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

OPY-bbt commented 4 years ago

修改demo里的代码如下:

class Animal { 
  constructor() {
    this.name = 'animal';
  }
  speak1() {
    console.log('speak');
  }
  speak2 = () => {
    console.log('speak2');
  }
  speak3 = function (){
    console.log('speak3');
  }
}
OPY-bbt commented 4 years ago

经过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 commented 4 years ago

为什么不为了性能,把箭头函数也绑定到原型上呢?这和箭头中的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 commented 4 years ago

extends 创建子类

OPY-bbt commented 4 years ago

继续扩展原来的例子, 新建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 commented 4 years ago

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 commented 4 years ago

假如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 commented 4 years ago

继承的几种方式

OPY-bbt commented 4 years ago

原型链

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 commented 4 years ago

借用构造函数

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

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

OPY-bbt commented 4 years ago

组合继承

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

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

function Child() {
     Parent.call(this);
}
// 这里存在多余属性
Child.prototype = new Parent();
OPY-bbt commented 4 years ago

原型式继承

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

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

function object(o) {
    return Object.create(o);
}
OPY-bbt commented 4 years ago

寄生式继承

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

function createChild(o) {
    var child = object.create(o);
    // 增强对象
    child.sayHi = function() {};
    reutrn child;
}
OPY-bbt commented 4 years ago

寄生组合式继承

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

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