zoukankan      html  css  js  c++  java
  • Class(类)和 继承

    ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

    //定义类
    class Point {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    
      toString() {
        return '(' + this.x + ', ' + this.y + ')';
      }
    }

    上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。

    Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。

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

    使用的时候,直接对类使用new命令

    class Bar {
      doStuff() {
        console.log('stuff');
      }
    }
    
    var b = new Bar();
    b.doStuff() // "stuff"

    类的所有方法都定义在类的prototype属性上面

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

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

    class B {}
    let b = new B();
    
    b.constructor === B.prototype.constructor // true

    上面代码中,b是B类的实例,它的constructor方法就是B类原型的constructor方法。

    另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。

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

    constructor方法

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

    constructor() {}

    类的构造函数,不使用new是没法调用的,会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

    class Foo {
      constructor() {
        return Object.create(null);
      }
    }
    
    Foo()
    // TypeError: Class constructor Foo cannot be invoked without 'new'

    类的实例对象

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

    不存在变量提升

    Class不存在变量提升(hoist),这一点与ES5完全不同。

    new Foo(); // ReferenceError
    class Foo {}

    Class表达式

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

    const MyClass = class Me {
      getClassName() {
        return Me.name;
      }
    };

    上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是MyClass而不是MeMe只在Class的内部代码可用,指代当前类。

    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(); // "张三"

    上面代码中,person是一个立即执行的类的实例。

    私有方法

    一种方法是将私有方法移出模块,因为模块内部的所有方法都是对外可见的

    class Widget {
      foo (baz) {
        bar.call(this, baz);
      }
    
      // ...
    }
    
    function bar(baz) {
      return this.snaf = baz;
    }

    上面代码中,foo是公有方法,内部调用了bar.call(this, baz)。这使得bar实际上成为了当前模块的私有方法。

    还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。

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

    上面代码中,barsnaf都是Symbol值,导致第三方无法获取到它们,因此达到了私有方法和私有属性的效果。

    this的指向

    类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。

    class Logger {
      printName(name = 'there') {
        this.print(`Hello ${name}`);
      }
    
      print(text) {
        console.log(text);
      }
    }
    
    const logger = new Logger();
    const { printName } = logger;
    printName(); // TypeError: Cannot read property 'print' of undefined

    上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境,因为找不到print方法而导致报错。

    一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了。

    class Logger {
      constructor() {
        this.printName = this.printName.bind(this);
      }
    
      // ...
    }

    另一种解决方法是使用箭头函数。

    class Logger {
      constructor() {
        this.printName = (name = 'there') => {
          this.print(`Hello ${name}`);
        };
      }
    
      // ...
    }

    还有一种解决方法是使用Proxy,获取方法的时候,自动绑定this

    function selfish (target) {
      const cache = new WeakMap();
      const handler = {
        get (target, key) {
          const value = Reflect.get(target, key);
          if (typeof value !== 'function') {
            return value;
          }
          if (!cache.has(value)) {
            cache.set(value, value.bind(target));
          }
          return cache.get(value);
        }
      };
      const proxy = new Proxy(target, handler);
      return proxy;
    }
    
    const logger = selfish(new Logger());

    严格模式

    类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。

    name属性

    class Point {}
    Point.name // "Point"
    

    name属性总是返回紧跟在class关键字后面的类名。

    Class的继承

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

    class ColorPoint extends Point {}

    上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类。下面,我们在ColorPoint内部加上代码。

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

    上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

    子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

    class Point { /* ... */ }
    
    class ColorPoint extends Point {
      constructor() {
      }
    }
    
    let cp = new ColorPoint(); // ReferenceError

    上面代码中,ColorPoint继承了父类Point,但是它的构造函数没有调用super方法,导致新建实例时报错。

    另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。

    class Point {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    }
    
    class ColorPoint extends Point {
      constructor(x, y, color) {
        this.color = color; // ReferenceError
        super(x, y);
        this.color = color; // 正确
      }
    }

    上面代码中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的。

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

    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'

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

    Class 的 Generator 方法

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

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

    上面代码中,Foo类的Symbol.iterator方法前有一个星号,表示该方法是一个 Generator 函数。Symbol.iterator方法返回一个Foo类的默认遍历器,for...of循环会自动调用这个遍历器。

    Class 的静态方法

    如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    Foo.classMethod() // 'hello'
    
    var foo = new Foo();
    foo.classMethod()
    // TypeError: foo.classMethod is not a function

    上面代码中,Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(Foo.classMethod()),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。

    父类的静态方法,可以被子类继承。

    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    class Bar extends Foo {
    }
    
    Bar.classMethod(); // 'hello'

    上面代码中,父类Foo有一个静态方法,子类Bar可以调用这个方法。

    静态方法也是可以从super对象上调用的。

    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    class Bar extends Foo {
      static classMethod() {
        return super.classMethod() + ', too';
      }
    }
    
    Bar.classMethod();

    Class的静态属性和实例属性

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

    class Foo {
    }
    
    Foo.prop = 1;
    Foo.prop // 1

    上面的写法为Foo类定义了一个静态属性prop

    目前,只有这种写法可行,因为ES6明确规定,Class内部只有静态方法,没有静态属性。

    // 以下两种写法都无效
    class Foo {
      // 写法一
      prop: 2
    
      // 写法二
      static prop: 2
    }
    
    Foo.prop // undefined

    ES7有一个静态属性的提案,目前Babel转码器支持。

    ..........

    Mixin模式的实现

    ...............

    原文:http://jsrun.net/tutorial/SZKKp

  • 相关阅读:
    HBase权威指南
    Mapreduce编程
    Hive内部表和外部表的区别
    Android Volley全然解析(四),带你从源代码的角度理解Volley
    codeforces 448CPainting Fence
    2.maven 安装配置
    cocos2d-x 3.0正式版创建project笔记
    C. DZY Loves Sequences
    spring 基础回想 tips01
    spring 配置属性细节
  • 原文地址:https://www.cnblogs.com/caozhuzi/p/10963737.html
Copyright © 2011-2022 走看看