ES2015 classes(一)
一、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 的基本语法:简介
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);
存在的问题:
- 首先就是与传统的面向对象语言(如C++、Java )差异很大,一般通过 class 来构造一个实例对象,而不是通过构造函数
- 方法在构造函数 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
我们可以看到:
- ES5 的构造函数对应的就是 ES6 Class 的构造器/方法
- this 关键字即代表实例对象
- 定义 ES6 Class 方法的时候,前面不需添加 function 关键字
- 类的数据类型就是函数,类本身就指向构造函数 xx.prototype.constructor
- 事实上类的所有方法都定义在类的 prototype 属性上,在类的实例上调用方法,其实就是调用 prototype 上的方法
- 类的所有实例共享一个原型对象: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(); // "张三"
上一篇: ES6知识点整理——symbol
下一篇: es6零碎知识点整理