当前位置 博文首页 > 文章内容

    ECMAScript 6-Class

    作者: 栏目:未分类 时间:2020-09-01 14:01:00

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    在这里插入图片描述

    如果对构造函数、原型对象、实例对象还不清楚,建议先看这里原型基础部分

    Class基本概念

    传统方法是通过构造函数生成新对象

    function Point(x, y) {
      this.x = x;
      this.y = y;
    }
    Point.prototype.toString = function () {
      return '(' + this.x + ', ' + this.y + ')';
    };
    

    class是构造器的另一种写法

    class Point {
      constructor(x, y) {	
        this.x = x;
        this.y = y;
      }
      toString() {
        return '(' + this.x + ', ' + this.y + ')';
      }
    } 
    
    • 类的所有方法都定义在类的prototype属性上,即原型对象上(静态方法除外)

      class Point {
        constructor(){...}
        toString(){...}
        toValue(){...}
      }
      

      等同于

      class Point {
        constructor(){...}
      }
          
      Point.prototype = {
        toString(){},
        toValue(){}
      };
      
    • 通过类创建实例

      var b = new Point();
      b.toString() //在实例上调用的方法其实就是原型上的方法
      
    • 严格模式

      类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式


    其他知识点

    • Object.assign方法可以很方便地一次向类添加多个原型方法。

      class Point {
        constructor(){...}
      }
      
      Object.assign(Point.prototype, {
        toString(){},
        toValue(){}
      });
      
    • 类的内部所有定义的方法,都是不可枚举的(non-enumerable)

    • 类的属性名,可以采用表达式

      let methodName = "getArea";
      class Square{
        constructor(length) {...}
        [methodName]() {...}
      }
      
    • 不存在变量提升

    Class的实例对象

    1.如何创建

    使用new命令生成类的实例对象

    var point = new Point(2, 3);
    

    2.constructor方法

    constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加

    • constructor方法默认返回实例对象(即this

    • constructor方法也可以指定返回另外一个对象

      class Foo {
        constructor() {return Object.create(null);}	//返回一个全新的对象
      }
      
      new Foo() instanceof Foo	// false  //导致实例对象不是Foo类的实例
      

    3.实例上的方法

    • 实例的方法都是定义在原型上(通过在class上添加)

    • 实例的属性和方法可以定义在实例对象本身(通过在this上添加)

      class Point {
        //通过this将实例的属性和方法定义在实例对象本身
        constructor(x, y) {
          this.x = x;
          this.y = y;
          this.fun=function (){return 1}
        }
          
        //通过在class将实例的属性和方法定义在原型上
        toString() {return '(' + this.x + ', ' + this.y + ')';}
      }
      

      验证:

      var point = new Point(2, 3)
      
      //由于x和y定义在实例对象本身,所以实例有这个属性
      point.hasOwnProperty('x') // true
      point.hasOwnProperty('y') // true
      
      point.hasOwnProperty('fun') // true
      //由于toString定义在原型上,所以实例对象本身没有这个方法,而实例对象的原型有这个方法
      point.hasOwnProperty('toString') // false
      point.__proto__.hasOwnProperty('toString') // true
      
    • 类的所有实例共享一个原型对象

      var p1 = new Point(2,3);
      var p2 = new Point(3,2);
      p1.__proto__ === p2.__proto__	//true
      
    • 可以通过实例的__proto__属性为类的原型对象添加方法

      var p1 = new Point(2,3);
      var p2 = new Point(3,2);
      p1.__proto__.printName = function () { return 'Oops' };	//添加
      p1.printName() // "Oops"
      p2.printName() // "Oops"	//共享类的原型对象的方法
      

    4.实例上的属性

    以前:在类的constructor方法上的定义实例的属性

    class Point {
      constructor(myProp) {
        this.myProp= myProp;
      }
    }
    

    现在:可以在类中用等式定义实例的属性

    class MyClass {
      myProp = 42;
    }
    
    var myclass=new MyClass()
    myclass.myProp//42
    

    5.this的指向

    类的方法内部的this默认指向类的实例

    class Logger {
      printName(name = 'there') {
        this.print(`Hello ${name}`); //this指向Logger类的实例
      }
      print(text) {
        console.log(text);
      }
    }
    
    //创建实例
    const logger = new Logger();
    //单独从实例中取出方法
    const { printName } = logger;	
    //单独使用该方法
    printName(); // TypeError: Cannot read property 'print' of undefined
    //解析:该函数内部有个this是指向实例对象的。单独取出来后this会指向该方法运行时所在的环境,然后因为找不到print方法而导致报错
    
    • 解决1:在构造方法中加上绑定this的语句

      class Logger {
        constructor() {
          this.printName = this.printName.bind(this);
        }
        // ...
      }
      
    • 解决2:在构造方法中使用箭头函数

      class Logger {
        constructor() {
          this.printName = (name = 'there') => {
            this.print(`Hello ${name}`);
          };
        }
      
        // ...
      }
      
    • 解决3:使用Proxy

    Class的继承

    1. Extends

    • 类相当于实例的原型,所有在类中定义的方法,都会被实例继承

    • 类之间可以通过extends关键字实现继承

      class ColorPoint extends Point {} //ColorPoint类继承了Point类的所有属性和方法
      
      class B extends A {} //只要是一个有prototype属性的函数,就能被B继承
      

      三种特殊情况:

      • 子类继承Object类

        class A extends Object {}
        A.__proto__ === Object // true
        A.prototype.__proto__ === Object.prototype // true
        //A其实就是构造函数Object的复制,A的实例就是Object的实例。
        
      • 不存在任何继承

        class A {}
        A.__proto__ === Function.prototype // true
        A.prototype.__proto__ === Object.prototype // true
        //A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承Funciton.prototype。但是,A调用后返回一个空对象(即Object实例),所以A.prototype.__proto__指向构造函数(Object)的prototype属性。
        
      • 子类继承null

        class A extends null {}
        A.__proto__ === Function.prototype // true
        A.prototype.__proto__ === undefined // true
        //A也是一个普通函数,所以直接继承Funciton.prototype。但是,A调用后返回的对象不继承任何方法,所以它的__proto__指向Function.prototype
        

    2.class的静态方法

    静态方法不会被实例继承,而是直接通过类来调用,所谓静态方法,就是方法前面加上了static关键字

    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    //直接在Foo类上调用
    Foo.classMethod() // 'hello' 
    
    //不可在Foo类的实例中调用
    var foo = new Foo();
    foo.classMethod()// TypeError: foo.classMethod is not a function 
    
    

    3.class的静态属性

    静态属性指的是Class本身的属性,即Class.propname,而不是定义在实例对象(this)上的属性。同理,静态属性不会被继承

    以前的写法

    class Foo {}
    Foo.prop = 1; //定义一个静态属性
    Foo.prop // 1
    

    现在的写法

    class MyClass {
      static myStaticProp = 1; //在类中定义一个静态属性
    }
    MyClass.myStaticProp//1
    

    4.super 关键字

    • super作为函数调用时,代表父类的构造函数。super()只能用在子类的构造函数之中

      class ColorPoint extends Point {
        constructor(x, y, color) {
          super(x, y); //调用父类的constructor(x, y)
          this.color = color;
        }
      }
      

      子类构造函数的this

      • ES6 要求:子类的构造函数必须执行一次super函数。因为子类的this对象(实例对象)是继承父类的,所以子类必须在constructor方法中调用super方法

        class Point { /* ... */ }
        class ColorPoint extends Point {
          //子类本身是没有this的,只能通过super继承父类的this
          constructor() {}
        }
        let cp = new ColorPoint(); // ReferenceError//没有this无法创建实例
        
      • 子类实例是基于父类实例加工的

        class Point {
          constructor(x, y) {
            this.x = x;
            this.y = y;
          }
        }
        
        class ColorPoint extends Point {
          constructor(x, y, color) {//基于父类实例加上了color
            this.color = color; // 没有继承父类this:ReferenceError
            super(x, y);	//继承父类this后返回父类实例:
            this.color = color; // 正确
          }
        }
        

      其他

      • 实例对象既是子类的实例对象又是父类的实例对象

          let cp = new ColorPoint(25, 8, 'green');
          cp instanceof ColorPoint // true
        cp instanceof Point // true
        
      • Object.getPrototypeOf方法可以用来从子类上获取父类

        Object.getPrototypeOf(ColorPoint) === Point  // true
        
      • super虽然代表了父类的构造函数,但是返回的是子类的实例

        class Parent {
            constructor() {
                console.log(new.target.name);
          }
        }
        
        class Children extends Parent {
            constructor() {
                super();
            }
        }
        new A() // A
        new B() // B
        //注意,super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B,因此super()在这里相当于A.prototype.constructor.call(this)
        
    • super在普通方法中作为对象时,指向父类的原型对象

      class A {
        p() {
          return 2;
        }
      }
      
      class B extends A {
        constructor() {
          super();
          //这里super被当做一个对象使用了,指向A.prototype(父类原型),相当于A.prototype.p()
          console.log(super.p()); // 2 
        }
      }
      

      由于super指向父类的原型对象,所以定义在父类实例上(构造函数上)的方法或属性,是无法通过super调用的

      class A {
        constructor() {
          this.p = 2;	//1. 构造方法内定义的p(即实例上的p)
        }
      }
      
      class B extends A {
        get m() {
          return super.p;//2. 这里返回父类的p,相当于A.prototype.p
        }
      }
      
      let b = new B();
      b.m // undefined //3. 由于A.prototype找不到p,报错(因为p是在实例上定义的)
      
      //解决:这时如果把父类的p定义在父类的原型对象上,子类super.p就能访问到。比如:
      A.prototype.p = 2
      b.m //2
      

      ES6 规定,通过super调用父类的方法时,super会绑定子类的this

      class A {
        constructor() {
          this.x = 1;
        }
        print() {
          console.log(this.x);
        }
      }
      
      class B extends A {
        constructor() {
          super();
          this.x = 2;
        }
        m() {
          super.print();//super被当做对象,指向的是父类的原型对象,所以可以找到print
        }
      }
      
      let b = new B();
      b.m() // 2
      //super.print()虽然调用的是A.prototype.print(),但是A.prototype.print()会绑定子类B的this,导致输出的是2,而不是1。也就是说,实际上执行的是super.print.call(this)
      

      如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性

      class A {
        constructor() {
          this.x = 1;
        }
      }
      
      class B extends A {
        constructor() {
          super();	
          this.x = 2;
          super.x = 3;	//由于会绑定子类的this,这里相当于this.x=3
          console.log(super.x); // undefined	//当读取super.x的时候,读的是A.prototype.x,所以返回undefined
          console.log(this.x); // 3
        }
      }
      
    • super在静态方法中作为对象时,指向父类

      class Parent {
        //父类
        static myMethod(msg) {
          console.log('static', msg);
        }
        //父类的原型对象
        myMethod(msg) {
          console.log('instance', msg);
        }
      }
      
      class Child extends Parent {
        static myMethod(msg) {
          super.myMethod(msg);	//super在静态方法之中指向父类
        }
        myMethod(msg) {
          super.myMethod(msg);	//在普通方法之中指向父类的原型对象
        }
      }
       
      Child.myMethod(1); // static 1 //这里调用的是父类的静态方法
      var child = new Child(); //实例化
      child.myMethod(2); // instance 2 //这里调用的是父类的原型对象
      
    • 由于对象总是继承其他对象的,所以可以在任意一个对象中,使用super关键字

      var obj = {
        toString() {
          return "MyObject: " + super.toString();
        }
      };
      
      obj.toString(); // MyObject: [object Object]
      

    Class表达式

    类可以使用表达式的形式定义

    const MyClass = class Me {
      getClassName() {
        return Me.name;
      }
    };
    let inst = new MyClass();
    inst.getClassName() // Me
    Me.name // ReferenceError: Me is not defined
    //上面代码表示,Me只在Class内部有定义
    //如果类的内部没用到的话,可以省略Me,也就是可以写成下面的形式
    const MyClass = class { /* ... */ };
    

    采用Class表达式,可以写出立即执行的Class

    let person = new class {
      constructor(name) {
        this.name = name;
      }
      sayName() {console.log(this.name);}
    }('张三');
    person.sayName(); // "张三"
    

    其他

    私有方法

    • 利用call方法

      class Widget {
        //公有方法
        foo (baz) {
          //私有方法:利用call使bar成为了当前模块的私有方法
          bar.call(this, baz);
        }
      }
      
      function bar(baz) {
        return this.snaf = baz;
      }
      
    • 利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值

      const bar = Symbol('bar');
      const snaf = Symbol('snaf');
      
      class myClass{
        // 公有方法
        foo(baz) {
          this[bar](baz);
        }
        // 私有方法
        [bar](baz) {
          return this[snaf] = baz;
        }
      };
      

    class的私有属性

    私有属性即在类之外是读取不到的属性,通过在属性名前使用#来添加

    class的prototype属性和__proto__属性

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

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

      class A {}
      class B extends A {}
      B.__proto__ === A // true
      B.prototype.__proto__ === A.prototype // true
      

    实例的_proto_属性

    子类实例的_proto_属性的_proto_属性,指向父类实例的_proto_属性。也就是说,子类的原型的原型,是父类的原型

    var p1 = new Point(2, 3);
    var p2 = new ColorPoint(2, 3, 'red'); //继承Point
    
    p2.__proto__ === p1.__proto__ // false
    p2.__proto__.__proto__ === p1.__proto__ // true
    

    因此,通过子类实例的__proto__.__proto__属性,可以修改父类实例的行为

    p2.__proto__.__proto__.printName = function () {
      console.log('Ha');
    };
    p1.printName() // "Ha"
    

    原生构造函数的继承

    原生构造函数是指语言内置的构造函数,通常用来生成数据结构。以前,这些原生构造函数是无法继承的

    ES6允许继承原生构造函数定义子类,因为ES6是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承。

    Class的取值函数(getter)和存值函数(setter)

    与ES5一样,在Class内部可以使用getset关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为

    class MyClass {
      constructor() {
        // ...
      }
      get prop() {
        return 'getter';
      }
      set prop(value) {
        console.log('setter: '+value);
      }
    }
    
    let inst = new MyClass();
    
    inst.prop = 123;
    // setter: 123
    
    inst.prop
    // 'getter'