1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-24 18:32:22 +00:00

修改class

This commit is contained in:
Ruan Yifeng 2015-02-16 17:08:54 +08:00
parent 1f7cb6cf7a
commit 4ee9fa07d9
2 changed files with 356 additions and 15 deletions

View File

@ -2,6 +2,10 @@
## Class
### 基本语法
**1概述**
ES5通过构造函数定义并生成新对象。下面是一个例子。
```javascript
@ -17,7 +21,7 @@ Point.prototype.toString = function () {
```
ES6引入了Class这个概念作为对象的模板。通过class关键字可以定义类。基本上class可以看作只是一个语法糖没有引进任何ES5做不到的新功能只是让对象原型的写法更加清晰而已。上面的代码用“类”改写,就是下面这样。
ES6引入了Class这个概念作为对象的模板。通过class关键字可以定义类。基本上class可以看作只是一个语法糖它的绝大部分功能ES5都可以做到新的class写法只是让对象原型的写法更加清晰而已。上面的代码用“类”改写,就是下面这样。
```javascript
@ -37,14 +41,69 @@ class Point {
```
上面代码定义了一个“类”可以看到里面有一个constructor函数这就是构造函数而this关键字则代表实例对象。这个类除了构造方法还定义了一个toString方法。注意定义方法的时候前面不需要加上function这个保留字直接把函数定义放进去了就可以了。
上面代码定义了一个“类”可以看到里面有一个constructor方法这就是构造方法而this关键字则代表实例对象。Point类除了构造方法还定义了一个toString方法。注意定义方法的时候前面不需要加上function这个保留字直接把函数定义放进去了就可以了。
生成实例对象的写法与ES5完全一样也是使用new命令。
**2constructor方法**
constructor方法是类的默认方法通过new命令生成对象实例时自动调用该方法。一个类必须有constructor方法如果没有显式定义该方法会被默认添加代码如下。
```javascript
constructor() {}
```
constructor方法默认返回实例对象即this完全可以指定返回另外一个对象。
```javascript
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
// false
```
上面代码中constructor函数返回一个全新的对象结果导致实例对象不是Foo类的实例。
**3实例对象**
生成实例对象的写法与ES5完全一样也是使用new命令。如果忘记加上new像函数那样调用Class将会报错。
```javascript
// 报错
var point = Point(2, 3);
// 正确
var point = new Point(2, 3);
```
与ES5一样实例的属性除非显式定义在其本身即定义在this对象上否则都是定义在原型上即定义在class上
```javascript
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '('+this.x+', '+this.y+')';
}
}
var point = Point(2, 3);
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
@ -54,7 +113,7 @@ point.__proto__.hasOwnProperty('toString') // false
```
上面代码中x和y都是point自身的属性所以hasOwnProperty方法返回true而toString是原型对象的属性所以hasOwnProperty方法返回false。这些都与ES5的行为保持一致。
上面代码中x和y都是实例对象point自身的属性因为定义在this变量上所以hasOwnProperty方法返回true而toString是原型对象的属性因为定义在Point类上所以hasOwnProperty方法返回false。这些都与ES5的行为保持一致。
```javascript
@ -85,7 +144,77 @@ p3.printName() // "Oops"
```
上面代码在p1的原型上添加了一个printName方法由于p1的原型就是p2的原型因此p2也可以调用这个方法。而且新建的实例p3也可以调用这个方法。这意味着使用实例的\__proto__属性改写原型必须相当谨慎不推荐使用因为这会不可逆转地改变Class。
上面代码在p1的原型上添加了一个printName方法由于p1的原型就是p2的原型因此p2也可以调用这个方法。而且此后新建的实例p3也可以调用这个方法。这意味着使用实例的\__proto\__属性改写原型必须相当谨慎不推荐使用因为这会改变Class的原始定义影响到所有实例。
**4name属性**
由于本质上ES6的Class只是ES5的构造函数的一层包装所以函数的许多特性都被Class继承包括name属性。
```javascript
class Point {}
Point.name // "Point"
```
name属性总是返回紧跟在class关键字后面的类名。
**5Class表达式**
与函数一样Class也可以使用表达式的形式定义。
```javascript
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
```
上面代码使用表达式定义了一个类。需要注意的是这个类的名字是MyClass而不是MeMe只在Class的内部代码可用指代当前类。
```javascript
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined
```
上面代码表示Me只在Class内部有定义。
如果Class内部没用到的话可以省略Me也就是可以写成下面的形式。
```javascript
const MyClass = class { /* ... */ };
```
**6不存在变量提升**
Class不存在变量提升hoist这一点与ES5完全不同。
```javascript
new Foo(); // ReferenceError
class Foo {}
```
上面代码中Foo类使用在前定义在后这样会报错因为ES6不会把变量声明提升到代码头部。这种规定的原因与下文要提到的继承有关必须保证子类在父类之后定义。
**7严格模式**
类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。考虑到未来所有的代码其实都是运行在模块之中所以ES6实际上把整个语言升级到了严格模式。
### Class的继承
**1基本用法**
Class之间可以通过extends关键字实现继承这比ES5的通过修改原型链实现继承要清晰和方便很多。
@ -116,19 +245,121 @@ class ColorPoint extends Point {
上面代码中constructor方法和toString方法之中都出现了super关键字它指代父类的同名方法。在constructor方法内super指代父类的constructor方法在toString方法内super指代父类的toString方法。
父类和子类的\__proto__属性指向是不一样的。
子类必须在constructor方法中调用super方法否则新建实例时会报错。
```javascript
class Point { /* ... */ }
class ColorPoint extends Foo {
constructor() {
}
}
let cp = new ColorPoint(); // ReferenceError
```
如果子类没有定义constructor方法这个方法会被默认添加代码如下。也就是说不管有没有显式定义任何一个子类都有constructor方法。
```javascript
constructor(...args) {
super(...args);
}
```
另一个需要注意的地方是只有调用super方法之后才可以使用this关键字否则会报错。
```javascript
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方法之后就是正确的。
下面是生成子类实例的代码。
```javascript
let cp = new ColorPoint(25, 8, 'green');
cp instanceof ColorPoint // true
cp instanceof Point // true
```
上面代码中实例对象cp同时是ColorPoint和Point两个类的实例这与ES5的行为完全一致。
**2prototype属性**
Class作为构造函数的升级也有自己的prototype属性其规则与ES5构造函数的prototype属性一致。
```javascript
class A {
}
A.prototype === Function.prototype
// true
class B extends A {
}
B.prototype === C
// true
class C extends Object {
}
C.prototype === Object
// true
```
上面代码中子类的prototype属性都指向父类。如果一个类是基类即不存在任何继承那么它的原型指向`Function.prototype`
**3Object.getPrototypeOf方法**
Object.getPrototypeOf方法可以用来从子类上获取父类。
```javascript
Object.getPrototypeOf(ColorPoint) === Point
// true
```
**4\__proto\__属性**
父类和子类的\__proto\__属性指向是不一样的。
```javascript
var p1 = new Point(2, 3);
var p2 = new ColorPoint(2,3,red);
var p2 = new ColorPoint(2, 3, 'red');
p2.__proto__ === p1.__proto // false
p2.__proto__.__proto__ === p1.__proto__ // true
```
通过子类的\__proto__属性可以修改父类。
通过子类的\__proto\__属性可以修改父类。
```javascript
@ -136,12 +367,14 @@ p2.__proto__.__proto__.printName = function () {
console.log('Ha');
};
p1.printName() // Ha
p1.printName() // "Ha"
```
上面代码在ColorPoint的实例p2上向Point类添加方法结果影响到了Point的实例p1。
**5构造函数的继承**
下面是一个继承原生的Array构造函数的例子。
```javascript
@ -157,13 +390,119 @@ arr[1] = 12;
```
上面代码定义了一个MyArray的类继承了Array构造函数。因此就可以从MyArray生成数组的实例
上面代码定义了一个MyArray继承了Array构造函数因此就可以从MyArray生成数组的实例。这意味着ES6可以自定义原生数据结构比如Array、String等的子类这是ES5无法做到的
有一个地方需要注意,类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。考虑到未来所有的代码其实都是运行在模块之中所以ES6实际上把整个语言升级到了严格模式
上面这个例子也说明extends关键字不仅可以用来继承类还可以用来继承构造函数。下面是一个自定义Error子类的例子
## Module的基本用法
```javascript
JavaScript没有模块module体系无法将一个大程序拆分成互相依赖的小文件再用简单的方法拼装起来。其他语言都有这项功能比如Ruby的require、Python的import甚至就连CSS都有@import但是JavaScript任何这方面的支持都没有这对开发大型的、复杂的项目形成了巨大障碍。
class MyError extends Error {
}
throw new MyError('Something happened!');
```
### 取值函数getter和存值函数setter
与ES5一样在Class内部可以使用get和set关键字对某个属性设置存值函数和取值函数。
```javascript
class MyClass {
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
}
let inst = new MyClass();
inst.prop = 123;
// setter: 123
inst.prop
// 'getter'
```
上面代码中prop属性有对应的存值函数和取值函数因此赋值和读取行为都被自定义了。
### Generator方法
如果某个方法之前加上星号(*就表示该方法是一个Generator函数。
```javascript
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循环会自动调用这个遍历器。
### 静态方法
类相当于实例的原型所有在类中定义的方法都会被实例继承。如果在一个方法前加上static关键字就表示该方法不会被实例继承而是直接通过类来调用这就称为“静态方法”。
```javascript
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: undefined is not a function
```
上面代码中Foo类的classMethod方法前有static关键字表明该方法是一个静态方法可以直接在Foo类上调用`Foo.classMethod()`而不是在Foo类的实例上调用。如果在实例上调用静态方法会抛出一个错误表示不存在该方法。
父类的静态方法,可以被子类继承。
```javascript
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod(); // 'hello'
```
上面代码中父类Foo有一个静态方法子类Bar可以调用这个方法。
## Module
ES6的Class只是面向对象编程的语法糖升级了ES5的对象定义的写法并没有解决模块化问题。Module功能就是为了解决这个问题而提出的。
历史上JavaScript一直没有模块module体系无法将一个大程序拆分成互相依赖的小文件再用简单的方法拼装起来。其他语言都有这项功能比如Ruby的require、Python的import甚至就连CSS都有@import但是JavaScript任何这方面的支持都没有这对开发大型的、复杂的项目形成了巨大障碍。
在ES6之前社区制定了一些模块加载方案最主要的有CommonJS和AMD两种。前者用于服务器后者用于浏览器。ES6在语言规格的层面上实现了模块功能而且实现得相当简单完全可以取代现有的CommonJS和AMD规范成为浏览器和服务器通用的模块解决方案。

View File

@ -34,6 +34,7 @@
- Axel Rauschmayer, [ECMAScript 6s new array methods](http://www.2ality.com/2014/05/es6-array-methods.html): 对ES6新增的数组方法的全面介绍
- Dmitry Soshnikov, [ES6 Notes: Default values of parameters](http://dmitrysoshnikov.com/ecmascript/es6-notes-default-values-of-parameters/): 介绍参数的默认值
- Mozilla Developer Network, [WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet)介绍WeakSet数据结构
- Dwayne Charrington, [What Are Weakmaps In ES6?](http://ilikekillnerds.com/2015/02/what-are-weakmaps-in-es6/): WeakMap数据结构介绍
- Axel Rauschmayer, [ECMAScript 6: maps and sets](http://www.2ality.com/2015/01/es6-maps-sets.html): Set和Map结构的详细介绍
- Ragan Wald, [Destructuring and Recursion in ES6](http://raganwald.com/2015/02/02/destructuring.html): rest参数和扩展运算符的详细介绍
@ -89,6 +90,7 @@
- Axel Rauschmayer, [ECMAScript 6 modules: the final syntax](http://www.2ality.com/2014/09/es6-modules-final.html): ES6模块的介绍以及与CommonJS规格的详细比较
- Dave Herman, [Static module resolution](http://calculist.org/blog/2012/06/29/static-module-resolution/): ES6模块的静态化设计思想
- Axel Rauschmayer, [ECMAScript 6: new OOP features besides classes](http://www.2ality.com/2014/12/es6-oop.html)
- Axel Rauschmayer, [Classes in ECMAScript 6 (final semantics)](http://www.2ality.com/2015/02/es6-classes-final.html): Class语法的详细介绍和设计思想分析
## 工具