12-ES6-Class类

本文详细解析ES6中Class的概念,包括基本语法、构造方法、实例对象、静态方法、继承等核心特性,以及new.target、super关键字的使用,帮助读者全面掌握Class的使用。

1、class的基本语法

· constructor() 是构造方法
· this 代表实例对象
· 定义 “类” 的方法时,前面不需要加上 function 这个保留字,直接定义函数即可。
· 方法之间不需要用逗号分隔, 加了会报错

  class Point {
    constructor( x, y ){
      this.x = x;
      this.y = y;
    }
    toString(){
      return `${ this.x }, ${ this.y }`;
    }
  };

2)类的数据类型就是函数, 类本身就指向构造函数

 class Point {
   // ...
 };

 typeof Point;   // function
 Point = Point.prototype.constructor;  // true

3)构造函数的 prototype 属性在 “类” 上继续存在。类的所有方法都定义在类的 prototype 属性上

 class Point {
   constructor(){}
   toString(){}
   toValue(){}
 };
 // 等同于
 Point.prototype = {
   constructor(){}
   toString(){}
   toValue(){}
 };

4)在类的实例上调用方法, 其实就是调用原型上的方法

  class Point {};
  let point = new Point();
  console.log( point.constructor === Point.prototype.constructor ); // true

5)类的方法(除 constructor 以外)都定义在 prototype 对象上( constructor 是包含在prorotype 中的不可被枚举的属性 ),所以类的新方法都可以添加在prototype 对象上。Object.assign()可以向类中添加多个方法。

     class Point {
       constructor(){}
     };
     Object.assign( Point.prorotype, {
       toString(){},
       toValue(){}
     });
     
     Point.prototype.constructor === Point; // true
     prototype 对象上的 constructor 属性直接指向 "类"(本例是 Point )本身

6)ES6规定类的内部定义的所有方法都是不可被枚举的( non-enumerable ),ES5 是可枚举的

    class Point {
      constructor(){}
      toString(){}
    };
    Object.keys( Point );   // []
    Object.getOwnPropertyNames( Point.prototype ); // [ "constructor", "toString"]

7)类的属性名可以采用表达式

    let methodName = 'getArea';
    class Square {
      constructor(){}
      [methodName](){}
    }

2、constructor 方法

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

      class Point {};
      // 等同于
      class Point{
        constructor(){}
      };

2)constructor() 默认返回实例对象( 即 this ),不过完全可以指定返回另外一个对象

    class Foo {
      constructor(){
        return Object.create( null );  // 默认  return this;
      }
    };
    new Foo instanceof Foo; // fasle

3)类必须使用 new 来调用, 否则会报错。

    class Point {
      constructor(){}
    };
    let point = new Point();

3、类的实例对象

1)生成实例对象的写法与ES5完全一样,也是使用 new 命令。如果忘记加上 new ,像函数
那样调用 Class 将会报错。

       class Point {
         constrcutor(){}
       };

       let point = new Point();

2)constructor中定义的属性可以称为实例属性(即定义在this对象上),constructor外声明的属性都是定义在原型上的,可以称为原型属性(即定义在class上)。hasOwnProperty()函数用于判断属性是否是实例属性。其结果是一个布尔值,true说明是实例属性,false说明不是实例属性。in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。

        // 定义类
        class Point {
          constructor( x, y ){    // 显式定义在 this 对象上
            this.x = x;
            this.y = y;
          },
          toString(){}           // 定义在原型上 Class Point{}
        };

3)类的所有实例共享一个原型对象,它们的原型都是Person.prototype,所以proto属性是相等的

        let p1 = new Point(2,3);
        let p2 = new Point(4,5);
        p1.__proto__ === p2.__proto__;  // true

4、Class 表达式

1)与函数一样, Class 也可以使用表达式的形式定义

         const MyClass = class Me {
           getClassName(){
             return Me.name
           }
         };
         let inst = new MyClass();
         inst.getClassName(); // Me
         
        这个类的名字是 MyClass 而不是 Me, Me 只在 Class 内部可用,指代当前类

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

        let person = new class {
          costructor( name ){
            this.name = name;
          }
          sayName(){
            console.log( this.name );
          }
        }('hello');
        perosn.sayName(); // hello

5、不存在变量提升

1)类不存在变量提升, 这一点与 ES5 完全不同。因为 ES6 不会把变量提升到代码头部。这种规定与继承有关,必须保证子类在父类之后定义。

        // 不允许变量
        new Foo();   // ReferenceError
        class Foo{};

        // 继承
        {
          let Foo = class {};
          class Bar extends Foo{}
        }

6、this 指向

类的方法内部如果含有 this , 它将默认指向类的实例。一旦单独使用该方法,可能报错。
有三种解决方法:
· constructor(){ this.method = this.method.bind( this ) }
· constructor(){ this.method = () => { } }
· Proxy, 获取方法的时候自动绑定 this

          class Logger {
            constrcutor(){
              this.printName = this.printName.bind( this );
              this.print = this.print.bind( this );
            }
            printName( name ){
              this.print(`Hello ${ name }`);
            }
            print( text ){
              console.log( text );
            }
          };
          let logger = new Logger();
          const { printName } = logger;

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

1)在 “类” 的内部可以使用 get 和 set 关键字对某个属性设置存值函数和取值函数拦截该属性的行为。

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

            prop 属性有对应的存值函数和取值函数, 因此赋值和读取行为都被自定义了

2)存值函数和取值函数是设置在属性的 Descriptor 对象上的

            class Custom {
              constructor( el ){
                this.el = el;
              }
              get html(){
                return this.el.innerHTML'
              }
              set html( value ){
                this.el.innerHTML = value;
              }
            };
            let descriptor = Object.gerOwnPropertyDescriptor( Custom.prototype, 'html' );
            "get" in descriptor; // true
            "set" in descriptor; // true

8、Class 的 Generator 方法

1)如果某个方法之前加上星号( * ), 就表示该方法是一个 Generator 函数

 class Foo {
   constructor( ...args ){
     this.age = args;
   }
   * [Symbol.iterator](){
     for( let arg of this.args ){
       yield arg;
     }
   }
 };
 for( let x of new Foo( 'hello', 'world' ) ){
   console.log( x );
 };

9、Class 的静态方法

1)类相当于实例的原型, 所有在类中定义的方法都会被实例继承。如果在一个方法前加上 static 关键字, 就表示该方法不会被实例继承, 而是直接通过类调用,称为静态方法

 class Foo {
   static classMethod(){
     return 'hello';
   }
 };

 Foo.classMethod(); // hello

2)父类的静态方法可以被子类继承

 class Foo {
   static classMethod(){
     return 'hello';
   }
 };

 class barChild extends Foo {};

 barChild.classMethod(); // hello

3)静态方法也可以从 super 对象上调用

  class Foo {
    static classMethod(){
      return 'hello';
    }
  };

  class barChild extends Foo {
    static classMethodChild(){
      return super.classMethod() + 'world';
    }
  };

10、Class 静态属性和实例属性

1)Class 的实例属性可以用等式写入类的定义之中

  class MyClass {
     myprop = 1;
     constructor(){
       console.log( this.myprop );
     }
   }

2)静态属性指的是 Class 本身的属性, Class.propname, 而不是定义在实例 对象( this )的属性, 只要在实例属性写法前面加上 static 关键字就可以了。
· 静态属性也是可以被子类继承的
· 也可以被 super 对象上调用

添加静态属性

  class Foo {
    static prop = 1;
  };

静态属性被继承

  class Foo {
    static prop = 1;
  };
  class Bar extends Foo {};
  Bar.prop;

super对象调用继承的静态属性

  class Foo {
    static prop = 1;
  };
  class Bar extends Foo {
    barChild(){
      return super.prop;
    }
  };
  Bar.barChild();  // 1

11、new.target 属性

1)返回 new 命令所作用的构造函数。如果构造函数不是通过 new 命令调用的, 那么 new.target 会返回 undefined , 因此这个属性可以确定函数是怎么调用的。

   function Person( name ){
     if( new.target !== undefined ){
       this.name = name;
     }else if( new.target === Person ){
       this.name = name;
     }else{
       throw new Error( '必须使用 new 生成实例' );
     }
   };

   let person = new Person( 'hello' );  // 正确
   let notPerson = Person.call( person, 'hello' ); // 报错

2)子类继承父类是 new.targe 会返回子类

   class Rectang {
     consiructor(){
       console.log( new.target === Rectang );
     }
   };

   class FooChild extends Rectang {
     constructor(){
       super();
     }
   };
   let obj = new FooChild();

1、Class 的继承

1)Class 可以通过 extends 关键字实现继承。

  class Point {};
  class ColorPoint extends Point{};

2)Object.getPrototypeOf()—从子类获取父类

   class Parent {};
   class SumChild extends Parent{};
   Object.getPrototypeOf( SumChild );  // class Parent {}
  1. super() 关键字: 可以当做函数使用,也可以当做对象使用。这两种情况下, 它的作用完全不同。
    1)第一种情况, super() 作为函数调用时代表父类的构造函数。子类的构造函数必须执行 一次 super() 函数,否则会报错。super() 虽然代表了父类的构造函数,但是返回的是子类的实例, 即 super 内部的 this 是子类,因此 super() 等于就是Point.prototype.constructor.call( this );作为函数时, super() 只能在子类的构造函数之中, 用在其他地方就会报错
  class Point {};
  class FooChild extends Point {
    constructor(){
      super();
    }
  };
  1. super() 关键字

· super()作为函数时, super() 只能在子类的构造函数之中, 用在其他地方就会报错
· super() 作为函数调用时代表父类的构造函数。子类的构造函数必须执行一次 super() 函数,否则会报错。
· super() 虽然代表了父类的构造函数,但是返回的是子类的实例, 即 super 内部的 this 是子类,因此
super() 等于就是 父类.prototype.constructor.call( this );

  class Point {};
  class FooChild extends Point {
    constructor(){
      super();
    }
  };

· super 作为对象时在普通方法中指向父类的原型对象。但ES6规定, 通过 super 调用父类的方法,super 会绑定子类的 this, 由于绑定子类的 this , 因此如果通过 super 对某个属性赋值,这时 super 就是 this , 赋值的属性会变成子类的实例属性

例1 指向父类的原型对象

 class Parent {
   init(){
     return 2;
   }
 };
 class Child extends Parent {
   constructor(){
     super();
     console.log( super.init() );
   }
 };

 子类中的 super.init() 将 super 当做一个对象使用。这时 super 在普通方法之中指向 
 Parent.prototype, 所以 super.init() 就相当于 Parent.prototype.init();
 由于prototype.init() 绑定了 Child 子类的 this, 实际上执行的是 super.init.call( this );

例2

  class P {
    constrcutor(){
      this.index = 0;
    }
  };
  class C extends P {
    constrcutor(){
      super();
      this.index = 1;
      super.index = 2;
      console.log( super.index ); // undefined
      console.log( this.index ); // 2
    }
  };

super.x 被赋值为 2, 等同于对 this.x 赋值为 2。当读取 super.x 时,相当于读取P.prototype.x, 
所以返回 undefined

· 由于 super 指向父类的原型对象, 所以定义在父类实例上的方法或属性是无法通过super 调用,如果属性定义在父类的原型(prototype)对象上, super 就可以取到

例1 父类实例方法或属性不能通过 super 调用

  class A {
    constrcutor(){
      this.y = 2;
    }
  };
  class B extends A {
    constrcutor(){
      get toValue(){
        return super.y;
      }
    }
  };
  let b = new B();
  b.toValue(); // undefined

因为 y 是父类 P 实例的属性, 因此 super.y 就引用不到它。

例2 属性定义在父类的原型( prototype )对象上, super 可以调用

   class A {};
   A.prototype.y = 2;
   class B extends A {
     constructor(){
       super();
       console.log( super.y ); // 2
     }
   };

· 如果 super 作为对象用在静态方法之中, 这时 super 将指向父类, 而不是父类的原型对象

   class Parent {
     static parentStaticMethod( msg ){
       console.log( `static, ${ msg }` );
     }
     ParentMethod( msg ){
       console.log( `instance, ${ msg }`);
     }
   };

   class Child extends Parent {
     static chdilStaticMethod( msg ){
       super.ParentMethod( msg );
     }
     childMethod( msg ){
       console.log( msg );
     }
   }

· 使用 super 的时候, 必须显式指定是作为函数还是作为对象使用, 否则会报错

   class A {};
   class B extends A {
     constructoe(){
       super();
       console.log( sunper );   // 报错
     }
   };

1、类的 prototype 属性和 proto 属性

1)每一个对象都有 proto 属性, 指向对应的构造函数的 prototype 属性。 Class 作为构造函数的语法糖, 同时拥有 prototype 属性和 proto 属性, 因此同时存在两条继承链。
· 子类的 proto 属性表示构造函数的继承, 总是指向父类的继承链
· 子类的 prototype 属性的 proto 属性表示方法的继承, 总是指向父类的prototype 属性

   class A {};
   class B extends A {};

   console.log( B.__proto__ === A.prototype ); // true
   console.log( B.prototype.__proto__ === A.protorype ); // true

2)类的继承的模式实现

   class A {};
   class B {};

   // B实例继承A的实例
   Object.setPrototypeOf( B.prototype, A.prototype );
   // 等同于
   B.prototype.__proto__ = A.prototype;

   // B实例继承A的静态属性
   Object.setPrototypeOf( B, A );
   // 等同于
   B.__proto__ = A;

   const b = new B();

   这两天继承链可以理解为: 作为一个对象, 子类( B )的原型( __proto__ 属性 )是父类( A );
   作为一个构造函数, 子类( B )的原型( prototype )是父类的实例

1、extends 的继承目标

  1. extends 关键字后面可以跟多种类型的值, 但是还有3特殊情况
        class B extends A {};

        只要 A 有 prototype 属性的函数, 就能被 B 继承。由于函数都有 prototype 属性
        (除了Function.prototype函数), 因此 A 可以是任意函数

· 子类继承 Object 类

    class A extends Object(){};
    console.log( A.__proto__ === Object );   // true
    console.log(A.prototype.__proto__  === Object.prototype); // true

    A 其实就是构造函数 Object 的复制, A 的实例就是 Object 的实例。

· 子类继承 null

    class A extends null {};
    console.log(A.__proto__ === Function.prototype);   // true
    console.log( A.prototype.__proto__ === undefined ); // true

· 不存在任何继承

    class A {};
    console.log(A.__proto__ === Function.prototype);   // true
    console.log( A.prototype.__proto__ === Object.prototype );  // true

1、实例的 proto 属性

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

    class Point {};
    class ColorPoint extends Point {};

    let point = new Point( 2, 3 );
    let colorPoint = new ColorPoint( 2, 3, 'red' );

    colorPoint.__proto__ === point.__proto__; // false
    colorPoint.__proto__.__proto__ === point.__proto__;  // true

    ColorPoint 继承了 Point, 导致前者原型的原型是后者的原型

· 子类实例可以通过 proto.proto 属性来修改父类实例的行为

   class Point {
     constrcutor(){
       this.name = 'hello';
     }
     printName(){
       alert( this.name );
     }
   };
   class ColorPoint extends Point {
     constrcutor(){
       super();
     }
   };


 colorPoint.__proto__.__proto__.printName = function(){
   this.name = 'world';
 };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值