欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ES2015 classes(一)

程序员文章站 2022-07-16 20:09:36
...

一、Classes

[原文]https://babeljs.io/docs/en/learn

1. a simple sugar over the prototype-based OO pattern. 基于原型的面向对象模式 ,可以看作只是一个语法糖,即写法只是让对象原型的写法更为清晰、更像面向对象编程的语法而已

2. have a single convenient declartive form. 简洁的声明格式

3. support prototype-based Inheritance, super calls, instance, constructors and static methods. 支持原型继承、super、构造实例、构造器和静态方法

 

示例:

class SkinnedMesh extends THREE.Mesh { //inheritance

  constructor(geometry, materials) { //constructor

    super(geometry, materials); //super

    this.idMatrix = SkinnedMesh.defaultMatrix(); //instance
    this.bones = [];
    this.boneMatrices = [];

    //...

  }

  update(camera) {
    //...
    super.update();
  }

  static defaultMatrix() { //static methods
    return new THREE.Matrix4();
  }

}

 

二、Class 的基本语法:简介

[原文]http://es6.ruanyifeng.com/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io#docs/class-extends

 

1. Class 的由来

  • 生成实例对象的传统方法:通过构造函数
function Point(x, y) {
    this.x = x;
    this.y = y;
}

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

var p = new Point(1, 2);

存在的问题:

  1. 首先就是与传统的面向对象语言(如C++、Java )差异很大,一般通过 class 来构造一个实例对象,而不是通过构造函数
  2. 方法在构造函数 function 定义之外,变量在内,不美观

 

  • ES6 提供的 Class :语法糖

通过 class 关键字定义类,可以作为对象的模板。

class Ponit {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return '('+this.x+','+this.y+')';
    }
}

var p = new Point(1, 2);
p.toString();

typeof Point //"function" 类的数据类型就是函数,类本身就指向构造函数
Point === Point.prototype.constructor //true, prototype.constructor就是构造函数
p.prototype === Point.prototype.constructor

我们可以看到:

  1. ES5 的构造函数对应的就是 ES6 Class 的构造器/方法
  2. this 关键字即代表实例对象
  3. 定义 ES6 Class 方法的时候,前面不需添加 function 关键字
  4. 类的数据类型就是函数,类本身就指向构造函数 xx.prototype.constructor
  5. 事实上类的所有方法都定义在类的 prototype 属性上,在类的实例上调用方法,其实就是调用 prototype 上的方法
  6. 类的所有实例共享一个原型对象:p1.__prototype__ === p2.__prototype__。注意,这也意味着可以通过实例的 __prototype__ 来为原型类添加方法。不过也会由于改写了原型影响到其他所有实例,所以不推荐使用。

__prototype__,并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。

生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。

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

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

 

  • ES2015 Class 的 Object.assign() 方法

由于 Class 的方法本质上都是定义在 prototype 对象上面,我们可以通过 Object.assign() 很方便地一次性向类添加多个方法

class Point {
  constructor(){ }
}

Object.assign(Point.prototype, {
  toString(){},
  toValue(){}
});

注意:类的内部的所有定义的方法,都是不可枚举(keys()方法)的,non-enumerable。这一点与 ES5 的行为是不一致的:

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


// ES5
var Point = function (x, y) { };
Point.prototype.toString = function() { };
Object.keys(Point.prototype)
// ["toString"]
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

 

2. Class constructor 方法

constructor 方法是 Class 的默认方法,通过 new 命令生成对象实例时,会自动调用该方法,所以开发者通常在 constructor 方法里面定义一个 init 方法来初始化操作逻辑。

  • 一个类必须有 constructor 方法,如果没有显示定义,一个空的 constructor 会被默认添加。
  • constructor 方法默认返回实例对象,即 this。并且,可以指定返回另外一个对象。如 constructor() { return Object.create(null) }; 所以 new Foo() instanceof Foo 会出现 false 结果,因为 Class Foo 返回的实例对象指向的不是 Foo 类的实例
  • class 必须通过 new 来调用,否则会报错 TypeError: Class constructor Foo cannot be invoked widthout 'new'。这是它跟普通构造函数的一个主要区别,后者不用 new 也可以执行。

 

3. 实例的属性是定义在 this 对象上(类实例上)还是在 class(类原型对象)上

这点与 ES5 是一致的,即实例的属性除显示的定义在其本身 this 对象上,否则都是定义在原型(即 class)上:

class Point {

  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }

}

var point = new Point(2, 3);

point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false,因为 toString 是原型对象Point的属性
point.__proto__.hasOwnProperty('toString') // true

所以要注意,我们在 模块化开发 中,将一个 class 定义在 js 文件里并 export default,并在其他 js 文件 import 引入使用时,需要注意属性和方法是定义在类的原型对象,还是定义在类的 this 对象(类实例)上。

 

4. ES2015 Class 的 get 、set

这点和 ES5 也是一致的,支持取之函数和存值函数:getters、setter。一般用来对某个属性的存取行为进行拦截:

class MyClass {
  constructor() { }

  // prop 属性定义对应的存值函数和取值函数
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log('setter: '+value);
  }
}

let inst = new MyClass();

inst.prop = 123;
// setter: 123

inst.prop
// 'getter'

注意:getter和setter是设置在属性的 Descriptor 对象(描述对象)上的。

 

5. 属性表达式(通过表达式设置属性名) 和 Class 表达式(使用表达式的形式定义类)

// 属性表达式 [methodname](){}
let methodName = 'getArea';
class Square {
  constructor(length) { }
  [methodName]() { }
}

// 下面代码使用表达式定义了一个类。并且这个类的名字是MyClass而不是Me,Me只在 Class 的内部代码可用,指代当前类
const MyClass = class Me {
  getClassName() {
    return Me.name;
  }
};

// 如果Class的内部没有使用到,可以省略内部class name
const MyClass = class { /* ... */ };

//立即执行的Class
let person = new class {
  constructor(name) {
    this.name = name;
  }
  sayName() {
    console.log(this.name);
  }
}('张三');
person.sayName(); // "张三"