es6 Class内容略解(详略得当,包含extends关键字的理解

提前声明:以下的内容是基于阮一峰大大的es6教程进行说明自己的理解的,当然主题内容还是以阮一峰大大的教程作为参考(下面部分只说明自己之前遗漏或者不太懂的地方,不会全部将文档照搬过来,详细内容见阮一峰大大文档es6入门

一、 Class基本内容

  • Class是语法糖,实际上其绝大部分功能,es5都可以实现
  • Class中定义的所有方法实际上都定义在了该类的原型上,在constructor内定义的方法和属性是定义在对象本身。
class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        } 
        getName() {
            return this.name;
        }
        getAge() {
            return this.age;
        }
    }

    //相当于

    function Person() {}
    Person.prototype = {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        },
        getName() {
            return this.name;
        },
        getAge() {
            return this.age;
        }
    }
  • 类内部的所有定义的方法都是不可枚举的。在类内部定义的属性都会被默认加到各个通过该类创建的对象上。但在constructor中定义的属性和方法都是属于通过该类创建的对象上。对象的所有属性和方法都是可枚举的。(通过es5的方式,定义在构造函数prototype上的方法都是可枚举的
	class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
            this.getAll = function (){
                return this.name + this.age
            }
        } 
        height = 200
        getName() {
            return this.name;
        }
        getAge() {
            return this.age;
        }
    }

    let my = new Person('zyy',20);
    console.log("Object.keys(Person.prototype)====",Object.keys(Person.prototype))


//es5
 function Person() {  }
    Person.prototype.getAll = function (){
        console.log("zyy")
    }
    console.log(Object.keys(Person.prototype))

在这里插入图片描述
在这里插入图片描述

  • class中的constructor方法是必备的,如果没有,则会被添加新的constructor方法。如果constructor方法返回的其他类的实例,则通过该类创建出来的对象并不属于该类。
class Foo {
  constructor() {
    return Object.create(null);
  }
}

new Foo() instanceof Foo//false
  • 所有对象的__proto__都为类的prototype,故可以通过obj1.__proto__给类的原型添加属性和方法。
  • 在类内部可以使用getset关键词,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。需要注意的是:get以及set后面的名称不要和其内部操作的对象属性的名称一样,否则会出现无限循环的情况
 class Person {
        constructor(name, age) {
            this.name = name;
             this._age = age;
            this.getAll = function (){
                return this.name + this.age
            }
        } 
        height = 200
        getName() {
            return this.name;
        }
        get age() {
            return this._age;
        }
        set age(ageEle){
            this._age = ageEle;
        }
    }

    let my = new Person('zyy',20);
    my.__proto__.weight = 110;
    console.log(my.age)//20
  • 类的属性名可以采用表达式。值得注意的是,在调用方法时必须要与变量的取值相同,而不能与变量名相同。(eg:下面在调用获取高度的方法时必须调用的是getHeightA,而非getHeight
let getHeight = "getHeightA"
    class Person {
        constructor(name, age) {
            this.name = name;
             this._age = age;
            this.getAll = function (){
                return this.name + this.age
            }
        } 
        height = 200
        getName() {
            return this.name;
        }
        get age() {
            return this._age;
        }
        set age(ageEle){
            this._age = ageEle;
        }
        [getHeight]() {
            return this.height
        }
    }

    let my = new Person('zyy',20);
    my.__proto__.weight = 110;
    console.log(my.age)
    console.log(my.getHeightA())
  • es6将整个语言升级到了严格模式,故在类中的所有代码都会处于严格模式下。
  • 不存在变量提升。即在使用类时,该类已经声明。原因其实是与继承有关,必须保证子类在父类之后定义。
  • this的指向(这我可得好好说下):

    由于this是在运行时确定的(除了箭头函数,箭头函数是在定义时便确认了this,再提一嘴,作用域是在函数定义时确定的),故当我们把类内部的方法在外部单独使用时(由于class内部是严格模式,故在外部使用时this实际指向的是undefined,不是window),便可能会报错。

class Logger {
        constructor() {
            //this.printName = this.printName.bind(this);//相当于把原型上的方法赋给了对象
        }
        printName() {
            return this;
        }
    }
    let logger = new Logger();
    console.log("logger.printName()=====",logger.printName())//Logger类的一个实例
    let {printName} = logger
    console.log("printName()=====",printName())//undefined。如果使用返回结果调用方法/属性时便会报错

在这里插入图片描述
有了这个问题后,该怎么解决? 我们知道,箭头函数的this是在创建时确认,我们也可以动态地改变方法在运行时的内部this指向,那么解决方法就来了:
① 使用箭头函数定义方法(使用箭头函数实际上会将原型上的方法赋值给对象本身,无论是下面第一种还是第二种都是这样)

//第一种
  class Logger {
      constructor() {
          // this.printName = this.printName.bind(this);//相当于把原型上的方法赋给了对象
          this.printName = () => this;
      }
      printName() {
          return this;
      }
  }
  let logger = new Logger();
  console.log("logger.printName()=====",logger.printName())
  let {printName} = logger
  console.log("printName()=====",printName())

//第二种
class Logger {
      constructor() {
          // this.printName = this.printName.bind(this);//相当于把原型上的方法赋给了对象
          //this.printName = () => this;
      }
      printName = () => {
          return this;
      }
  }
  let logger = new Logger();
  console.log("logger.printName()=====",logger.printName())
  let {printName} = logger
  console.log("printName()=====",printName())

在这里插入图片描述
② 使用bind改变方法内部this指向(callapply都会立即执行,所以不要用callapply),该方法实际上也会将原型上的方法赋值给对象本身(实际上无论是在原型还是在对象本身,方法内部this都是对象本身,所以无差异),在将printName方法内部的this绑定为实例本身后,在每次调用该方法(无论是通过对象调用还是通过外部单独调用),内部this都是Logger类的实例。

class Logger {
        constructor() {
             this.printName = this.printName.bind(this);//相当于把原型上的方法赋给了对象
        }
        printName() {
            return this;
        }
    }
    let logger = new Logger();
    console.log("logger.printName()=====",logger.printName())
    let {printName} = logger
    console.log("printName()=====",printName())

在这里插入图片描述

  • static关键字来声明一个属性/方法为静态属性/静态方法。直接在类上调用。如果在静态方法中使用this关键字,这个this指向的为类,不是实例。父类的静态方法可以被子类继承
  • 私有属性和私有方法用#做前缀标识,只能在类内的方法使用,不可通过对象/类在外部使用(文档中可以直接不使用this调用#count,但是实际操作了一波,好像不可?在写私有方法时,只能将其赋值给实例,不可写在原型上,会报错(实践得知,猜测是觉得没有什么意义…))
 class IncreasingCounter {
        #count = 0;
        increment() {
            this.#count++;
            return this.#getCount()
        }
        #getCount = ()=>{
            return this.#count
        }
    }
    console.log(new IncreasingCounter().increment())//1
    console.log(new IncreasingCounter().#getCount())//Uncaught SyntaxError: Private field '#getCount' must be declared in an enclosing class,外部不可访问私有属性/方法
    console.log(new IncreasingCounter())

在这里插入图片描述

二、Class的继承

  • 子类必须在constructor中调用super方法,否则新建实例时会报错(调用super代表调用父类的构造函数)因为子类的this对象必须要先通过父类的构造函数完成给塑造,得到与父类实例同样的属性和方法,然后再向this进行加工,添加自己的属性和方法。不过不调用super方法,子类便无法得到this对象。(如果子类中没有contructor方法,会为其默认添加一个,并在内部默认调用super方法)
  • 当调用完super方法后才能够访问this(规定先完成父类实例的属性和方法的构造,再对this进行加工)。super虽然调用的是父类的构造函数,但实际上返回的是子类的实例。
  • 作为函数时,super只能在子类的构造函数中调用。
	class Parent{
        constructor(name) {
            console.log('在Parent中调用new.target.name方法====', new.target.name)
            this.name = name;
        }
    }

    class Student extends Parent{
        constructor(name, age) {
        	// console.log(this)//报错
            super(name);
            console.log('Parent 构造方法调用完输出this', this)
            this.age = age
            console.log('Student 构造方法的底部输入this', this)
        }
    }

    console.log(new Student('zyy', 21))

在这里插入图片描述

  • super作为对象时,在普通方法中,指向父类的原型对象(故定义在父类实例上的属性和方法是无法通过super调用的);在静态方法中指向父类。虽然super在子类的普通方法中指向父类的原型对象,但是方法内部的this是指向子类实例的。故如果通过super对属性赋值,实际上是在为子类实例赋值,如果子类没有这个属性,则该赋值属性会变为子类实例的属性。
class Parent{
        constructor(name) {
            console.log('在Parent中调用new.target.name方法====', new.target.name)
            this.name = name;
        }
        getName() {
            return this.name;
        }
    }

    class Student extends Parent{
        constructor(name, age) {
            super(name);
            console.log('Parent 构造方法调用完输出this', this)
            this.name = name + 'zyy';
            this.age = age
            console.log('Student 构造方法的底部输入this', this)
        }
        getName() {
            super.x = 1;
            console.log('父类的原型对象属性 super.x', super.x)//父类原型对象上没有name属性,故输出undefined
            console.log(super.getName())//zyyzyy,返回的为子类实例上的name属性值。
        }
    }

    let stu = new Student('zyy', 21);
    stu.getName()
    console.log(stu)

在这里插入图片描述

  • 在子类的静态方法中使用super,其指向父类。通过super调用属性和方法时,调用的都为静态属性和静态方法。

classprototype__proto__属性

  • 子类的__proto__属性,表示构造函数的继承,总是指向父类
  • 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性

理解:作为一个对象,子类的原型(__proto__属性)是父类;作为一个构造函数,子类的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例。

为什么是上面那样? 接下来我们来看被编译后的继承代码:
1. 先来看class关键字会被如何编译:
原始es6代码:
class Parent {
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    speakSomething(){
        console.log("I can speek chinese");
    }
}
被编译后:
"use strict";

var _createClass = function () {
    function defineProperties(target, props) {
    //props为需要定义在` target`原型上的方法/属性集合
        for (var i = 0; i < props.length; i++) {
            var descriptor = props[i];
            descriptor.enumerable = descriptor.enumerable || false;//是否可枚举(原型上的方法不可枚举,故如果`target`为某构造函数原型,则取值为false)
            descriptor.configurable = true;
            if ("value" in descriptor) descriptor.writable = true;//如果当前元素上有value属性,则允许重新为其重新赋值
            Object.defineProperty(target, descriptor.key, descriptor);//将当前元素定义到target上
        }
    }

    return function (Constructor, protoProps, staticProps) {
        if (protoProps) defineProperties(Constructor.prototype, protoProps);  //protoProps为需要定义在`Parent`原型上的方法/属性集合
        if (staticProps) defineProperties(Constructor, staticProps);//staticProps为需要定义在`Parent`的静态方法/属性集合
        return Constructor;
    };
}();

function _classCallCheck(instance, Constructor) {
//主要是为了检查是否通过new操作符调用构造函数,而非直接通过调用函数的方式调用。
    if (!(instance instanceof Constructor)) {
    //如果instance不是`Constructor`的实例。则报错
        throw new TypeError("Cannot call a class as a function");
    }
}

var Parent = function () {
    function Parent(name, age) {
        _classCallCheck(this, Parent);

        this.name = name;
        this.age = age;
    }

    _createClass(Parent, [{
        key: "speakSomething",
        value: function speakSomething() {
            console.log("I can speek chinese");
        }
    }]);

    return Parent;
}();

理解:在Parent自执行函数中创建了Parent构造函数,并调用_createClass方法。该方法是将传入的元素集合定义至Parent原型上且将传入的元素集合定义到Parent上,作为其静态属性/方法。当通过new操作符创建Parent类的对象时,会和es5一样,调用Parent构造函数,首先会取检查是否通过new操作符调用Parent构造函数,而不是通过()直接调用。在通过检查后,再为当前实例添加属性/方法。

2. 再看含extends后会被如何编译:
原始es6代码:
class Parent {
    static height = 12
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    speakSomething(){
        console.log("I can speek chinese");
    }
}
Parent.prototype.color = 'yellow'


//定义子类,继承父类
class Child extends Parent {
    static width = 18
    constructor(name,age){
        super(name,age);
    }
    coding(){
        console.log("I can code JS");
    }
}

var c = new Child("job",30);
c.coding()

在这里插入图片描述

被编译后:
"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 _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) {//父类必须是函数/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;//设置子类的__proto__属性指向父类,子类的__proto__属性,表示构造函数的继承,总是指向父类。
}

function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}

var Parent = function () {
    function Parent(name, age) {
        _classCallCheck(this, Parent);//检测是否通过Parent()方式调用,如果是,报错
        this.name = name;
        this.age = age;
    }

    _createClass(Parent, [{
        key: "speakSomething",
        value: function speakSomething() {
            console.log("I can speek chinese");
        }
    }]);//为Parent原型添加了speakSomething方法

    return Parent;
}();

function _defineProperty(obj, key, value) {
    if (key in obj) {
        Object.defineProperty(
            obj,
            key,
            {
                value: value,
                enumerable: true,
                configurable: true,
                writable: true
            }
        );
    } else { 
        obj[key] = value;
    }
    return obj;
}

_defineProperty(Parent, "height", 12);//为Parent添加静态属性height

Parent.prototype.color = 'yellow';//为其原型添加方法

//定义子类,继承父类

var Child = function (_Parent) {
    _inherits(Child, _Parent);//实现继承

    function Child(name, age) {
        _classCallCheck(this, Child);
        return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name, age));//如果父类有返回的对象,则返回父类返回的对象。否则返回this
    }

    _createClass(Child, [{
        key: "coding",
        value: function coding() {
            console.log("I can code JS");
        }
    }]);//为child函数原型添加coding方法

    return Child;
}(Parent);

_defineProperty(Child, "width", 18);//为Child添加静态属性width


var c = new Child("job", 30);
console.log("c=========", c)

理解

  • Parent类编译出来的内容与上面一致,我们来说一下不一致的地方:在执行自执行函数Child时,首先调用了_inherits实现了继承。_inherits方法内部首先对父类进行出错判断,确保父类必须时函数/null,如果出错判断没有问题,则将superClassprototype设为subClassprototype的原型,并为subClassprototype设置constructor方法,完成继承中的第一条链,即方法的继承(如果传入的父类为null,则直接将null作为subClass.prototype的原型)。if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;完成继承中的第二条链,即构造函数继承。
  • 完成两条继承链的构造后,调用_possibleConstructorReturn方法(让我百思不得其解的一个方法,这个方法一定要先仔细看其传入的参数!!!),传入两个参数:this(Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name, age),第二个参数首先调用了父类构造函数,并将其内部this设为当前子类实例,此时也便是之前继承说到的super方法,调用了父类的构造函数,但实际内部this为子类实例。如果父类构造函数返回一个新的对象,那么最终通过子类构造方法创建出来的对象为父类工造函数返回的对象(见例3),否则返回的为子类实例。
//例3
class Parent {
    static height = 12
    constructor(name,age){
        this.name = name;
        this.age = age;
        return new Object()
    }
    speakSomething(){
        console.log("I can speek chinese");
    }
}
Parent.prototype.color = 'yellow'


//定义子类,继承父类
class Child extends Parent {
    static width = 18
    constructor(name,age){
        super(name,age);
    }
    coding(){
        console.log("I can code JS");
    }
}

    var c = new Child("job", 30);
    console.log('c===============', c)//返回的即为父类构造函数的返回对象

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43314846/article/details/106850803
今日推荐