ECMAScript 6-Class

ECMAScript 6-Class

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

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'
上一篇:【js面向对象ES6】-学习笔记


下一篇:JavaScript 复习07