mirror of
https://github.com/ruanyf/es6tutorial.git
synced 2025-05-24 18:32:22 +00:00
edit class/decorator
This commit is contained in:
parent
5c73894118
commit
6339feb01b
261
docs/class.md
261
docs/class.md
@ -13,7 +13,7 @@ function Point(x,y){
|
|||||||
}
|
}
|
||||||
|
|
||||||
Point.prototype.toString = function () {
|
Point.prototype.toString = function () {
|
||||||
return '('+this.x+', '+this.y+')';
|
return '(' + this.x + ', ' + this.y + ')';
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -41,6 +41,18 @@ class Point {
|
|||||||
|
|
||||||
Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个保留字,直接把函数定义放进去了就可以了。
|
Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个保留字,直接把函数定义放进去了就可以了。
|
||||||
|
|
||||||
|
ES6的类,完全可以看作构造函数的另一种写法。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Class Point{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
typeof Point // "function"
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码表明,类的数据类型就是函数。
|
||||||
|
|
||||||
构造函数的prototype属性,在ES6的“类”上面继续存在。事实上,除了constructor方法以外,类的方法都定义在类的prototype属性上面。
|
构造函数的prototype属性,在ES6的“类”上面继续存在。事实上,除了constructor方法以外,类的方法都定义在类的prototype属性上面。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
@ -161,7 +173,7 @@ p1.__proto__ === p2.__proto__
|
|||||||
|
|
||||||
上面代码中,p1和p2都是Point的实例,它们的原型都是Point,所以\_\_proto\_\_属性是相等的。
|
上面代码中,p1和p2都是Point的实例,它们的原型都是Point,所以\_\_proto\_\_属性是相等的。
|
||||||
|
|
||||||
这也意味着,可以通过\_\_proto\_\_属性为Class添加方法。
|
这也意味着,可以通过实例的\_\_proto\_\_属性为Class添加方法。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var p1 = new Point(2,3);
|
var p1 = new Point(2,3);
|
||||||
@ -183,11 +195,8 @@ p3.printName() // "Oops"
|
|||||||
由于本质上,ES6的Class只是ES5的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。
|
由于本质上,ES6的Class只是ES5的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class Point {}
|
class Point {}
|
||||||
|
|
||||||
Point.name // "Point"
|
Point.name // "Point"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
name属性总是返回紧跟在class关键字后面的类名。
|
name属性总是返回紧跟在class关键字后面的类名。
|
||||||
@ -197,23 +206,19 @@ name属性总是返回紧跟在class关键字后面的类名。
|
|||||||
与函数一样,Class也可以使用表达式的形式定义。
|
与函数一样,Class也可以使用表达式的形式定义。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
const MyClass = class Me {
|
const MyClass = class Me {
|
||||||
getClassName() {
|
getClassName() {
|
||||||
return Me.name;
|
return Me.name;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是MyClass而不是Me,Me只在Class的内部代码可用,指代当前类。
|
上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是MyClass而不是Me,Me只在Class的内部代码可用,指代当前类。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
let inst = new MyClass();
|
let inst = new MyClass();
|
||||||
inst.getClassName() // Me
|
inst.getClassName() // Me
|
||||||
Me.name // ReferenceError: Me is not defined
|
Me.name // ReferenceError: Me is not defined
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码表示,Me只在Class内部有定义。
|
上面代码表示,Me只在Class内部有定义。
|
||||||
@ -221,9 +226,7 @@ Me.name // ReferenceError: Me is not defined
|
|||||||
如果Class内部没用到的话,可以省略Me,也就是可以写成下面的形式。
|
如果Class内部没用到的话,可以省略Me,也就是可以写成下面的形式。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
const MyClass = class { /* ... */ };
|
const MyClass = class { /* ... */ };
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**(6)不存在变量提升**
|
**(6)不存在变量提升**
|
||||||
@ -231,23 +234,18 @@ const MyClass = class { /* ... */ };
|
|||||||
Class不存在变量提升(hoist),这一点与ES5完全不同。
|
Class不存在变量提升(hoist),这一点与ES5完全不同。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
new Foo(); // ReferenceError
|
new Foo(); // ReferenceError
|
||||||
|
|
||||||
class Foo {}
|
class Foo {}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,Foo类使用在前,定义在后,这样会报错,因为ES6不会把变量声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。
|
上面代码中,Foo类使用在前,定义在后,这样会报错,因为ES6不会把变量声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
{
|
{
|
||||||
let Foo = class {};
|
let Foo = class {};
|
||||||
class Bar extends Foo {
|
class Bar extends Foo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
如果存在Class的提升,上面代码将报错,因为let命令也是不提升的。
|
如果存在Class的提升,上面代码将报错,因为let命令也是不提升的。
|
||||||
@ -263,36 +261,31 @@ class Foo {}
|
|||||||
Class之间可以通过extends关键字,实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。
|
Class之间可以通过extends关键字,实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class ColorPoint extends Point {}
|
class ColorPoint extends Point {}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类。下面,我们在ColorPoint内部加上代码。
|
上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类。下面,我们在ColorPoint内部加上代码。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class ColorPoint extends Point {
|
class ColorPoint extends Point {
|
||||||
|
|
||||||
constructor(x, y, color) {
|
constructor(x, y, color) {
|
||||||
super(x, y); // 等同于parent.constructor(x, y)
|
super(x, y); // 调用父类的constructor(x, y)
|
||||||
this.color = color;
|
this.color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return this.color + ' ' + super.toString(); // 等同于parent.toString()
|
return this.color + ' ' + super.toString(); // 调用父类的toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,constructor方法和toString方法之中,都出现了super关键字,它指代父类的实例(即父类的this对象)。
|
上面代码中,constructor方法和toString方法之中,都出现了super关键字,它指代父类的实例(即父类的this对象)。
|
||||||
|
|
||||||
子类必须在constructor方法中调用super方法,否则新建实例时会报错。
|
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class Point { /* ... */ }
|
class Point { /* ... */ }
|
||||||
|
|
||||||
class ColorPoint extends Point {
|
class ColorPoint extends Point {
|
||||||
@ -301,23 +294,21 @@ class ColorPoint extends Point {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let cp = new ColorPoint(); // ReferenceError
|
let cp = new ColorPoint(); // ReferenceError
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
上面代码中,ColorPoint继承了父类Point,但是它的构造函数没有调用super方法,导致新建实例时报错。
|
||||||
|
|
||||||
如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。
|
如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为没有调用父类的构造函数,就无法子类实例的构建。
|
另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class Point {
|
class Point {
|
||||||
constructor(x, y) {
|
constructor(x, y) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
@ -332,7 +323,6 @@ class ColorPoint extends Point {
|
|||||||
this.color = color; // 正确
|
this.color = color; // 正确
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的。
|
上面代码中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的。
|
||||||
@ -340,12 +330,10 @@ class ColorPoint extends Point {
|
|||||||
下面是生成子类实例的代码。
|
下面是生成子类实例的代码。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
let cp = new ColorPoint(25, 8, 'green');
|
let cp = new ColorPoint(25, 8, 'green');
|
||||||
|
|
||||||
cp instanceof ColorPoint // true
|
cp instanceof ColorPoint // true
|
||||||
cp instanceof Point // true
|
cp instanceof Point // true
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,实例对象cp同时是ColorPoint和Point两个类的实例,这与ES5的行为完全一致。
|
上面代码中,实例对象cp同时是ColorPoint和Point两个类的实例,这与ES5的行为完全一致。
|
||||||
@ -359,85 +347,61 @@ cp instanceof Point // true
|
|||||||
(2)子类prototype属性的`__proto__`属性,表示方法的继承,总是指向父类的prototype属性。
|
(2)子类prototype属性的`__proto__`属性,表示方法的继承,总是指向父类的prototype属性。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
class A {
|
||||||
|
}
|
||||||
|
|
||||||
class B extends A {
|
class B extends A {
|
||||||
}
|
}
|
||||||
|
|
||||||
B.__proto__ === A // true
|
B.__proto__ === A // true
|
||||||
B.prototype.__proto__ === A.prototype // true
|
B.prototype.__proto__ === A.prototype // true
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,子类A的`__proto__`属性指向父类B,子类A的prototype属性的__proto__属性指向父类B的prototype属性。
|
上面代码中,子类A的`__proto__`属性指向父类B,子类A的prototype属性的__proto__属性指向父类B的prototype属性。
|
||||||
|
|
||||||
第一条继承链,实质如下。
|
这两条继承链,可以这样理解:作为一个对象,子类(B)的原型(`__proto__属性`)是父类(A);作为一个构造函数,子类(B)的原型(prototype属性)是父类的实例。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class B extends A {
|
|
||||||
constructor() {
|
|
||||||
return A.call(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 等同于
|
|
||||||
|
|
||||||
class B extends A {
|
|
||||||
constructor() {
|
|
||||||
return B.__proto__.call(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
第二条继承链,实质如下。
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
|
|
||||||
B.prototype = new A();
|
B.prototype = new A();
|
||||||
// 等同于
|
// 等同于
|
||||||
B.prototype.__proto__ = A.prototype;
|
B.prototype.__proto__ = A.prototype;
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
此外,还有三种特殊情况。
|
此外,考虑三种特殊情况。第一种特殊情况,子类继承Object类。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class A extends Object {
|
class A extends Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
A.__proto__ === Object // true
|
A.__proto__ === Object // true
|
||||||
A.prototype.__proto__ === Object.prototype // true
|
A.prototype.__proto__ === Object.prototype // true
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
第一种特殊情况,子类A继承Object。这种情况下,A其实就是构造函数Object的复制,A的实例就是Object的实例。
|
这种情况下,A其实就是构造函数Object的复制,A的实例就是Object的实例。
|
||||||
|
|
||||||
|
第二种特性情况,不存在任何继承。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class A {
|
class A {
|
||||||
}
|
}
|
||||||
|
|
||||||
A.__proto__ === Function.prototype // true
|
A.__proto__ === Function.prototype // true
|
||||||
A.prototype.__proto__ === Object.prototype // true
|
A.prototype.__proto__ === Object.prototype // true
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
第二种特殊情况,A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承`Funciton.prototype`。但是,A调用后返回一个空对象(即Object实例),所以`A.prototype.__proto__`指向构造函数(Object)的prototype属性。
|
这种情况下,A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承`Funciton.prototype`。但是,A调用后返回一个空对象(即Object实例),所以`A.prototype.__proto__`指向构造函数(Object)的prototype属性。
|
||||||
|
|
||||||
|
第三种特殊情况,子类继承null。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class A extends null {
|
class A extends null {
|
||||||
}
|
}
|
||||||
|
|
||||||
A.__proto__ === Function.prototype // true
|
A.__proto__ === Function.prototype // true
|
||||||
A.prototype.__proto__ === null // true
|
A.prototype.__proto__ === null // true
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
第三种特殊情况,与第二种情况非常像。A也是一个普通函数,所以直接继承`Funciton.prototype`。但是,A调用后返回的对象不继承任何方法,所以它的`__proto__`指向`Function.prototype`,即实质上执行了下面的代码。
|
这种情况与第二种情况非常像。A也是一个普通函数,所以直接继承`Funciton.prototype`。但是,A调用后返回的对象不继承任何方法,所以它的`__proto__`指向`Function.prototype`,即实质上执行了下面的代码。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
class C extends null {
|
class C extends null {
|
||||||
@ -450,46 +414,39 @@ class C extends null {
|
|||||||
Object.getPrototypeOf方法可以用来从子类上获取父类。
|
Object.getPrototypeOf方法可以用来从子类上获取父类。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
Object.getPrototypeOf(ColorPoint) === Point
|
Object.getPrototypeOf(ColorPoint) === Point
|
||||||
// true
|
// true
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 实例的\_\_proto\_\_属性
|
### 实例的\_\_proto\_\_属性
|
||||||
|
|
||||||
父类和子类的\_\_proto\_\_属性,指向是不一样的。
|
父类实例和子类实例的\_\_proto\_\_属性,指向是不一样的。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
var p1 = new Point(2, 3);
|
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__ === p1.__proto // false
|
||||||
p2.__proto__.__proto__ === p1.__proto__ // true
|
p2.__proto__.__proto__ === p1.__proto__ // true
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
通过子类的\_\_proto\_\_属性,可以修改父类。
|
通过子类实例的\_\_proto\_\_属性,可以修改父类实例的行为。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
p2.__proto__.__proto__.printName = function () {
|
p2.__proto__.__proto__.printName = function () {
|
||||||
console.log('Ha');
|
console.log('Ha');
|
||||||
};
|
};
|
||||||
|
|
||||||
p1.printName() // "Ha"
|
p1.printName() // "Ha"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码在ColorPoint的实例p2上向Point类添加方法,结果影响到了Point的实例p1。
|
上面代码在ColorPoint的实例p2上向Point类添加方法,结果影响到了Point的实例p1。
|
||||||
|
|
||||||
### 构造函数的继承
|
### 原生构造函数的继承
|
||||||
|
|
||||||
下面是一个继承原生的Array构造函数的例子。
|
下面是一个继承原生的Array构造函数的例子。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class MyArray extends Array {
|
class MyArray extends Array {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
@ -498,20 +455,17 @@ class MyArray extends Array {
|
|||||||
|
|
||||||
var arr = new MyArray();
|
var arr = new MyArray();
|
||||||
arr[1] = 12;
|
arr[1] = 12;
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码定义了一个MyArray类,继承了Array构造函数,因此就可以从MyArray生成数组的实例。这意味着,ES6可以自定义原生数据结构(比如Array、String等)的子类,这是ES5无法做到的。
|
上面代码定义了一个MyArray类,继承了Array构造函数,因此就可以从MyArray生成数组的实例。这意味着,ES6可以自定义原生数据结构(比如Array、String等)的子类,这是ES5无法做到的。
|
||||||
|
|
||||||
上面这个例子也说明,extends关键字不仅可以用来继承类,还可以用来继承构造函数。下面是一个自定义Error子类的例子。
|
上面这个例子也说明,extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数。下面是一个自定义Error子类的例子。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class MyError extends Error {
|
class MyError extends Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new MyError('Something happened!');
|
throw new MyError('Something happened!');
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## class的取值函数(getter)和存值函数(setter)
|
## class的取值函数(getter)和存值函数(setter)
|
||||||
@ -519,7 +473,6 @@ throw new MyError('Something happened!');
|
|||||||
与ES5一样,在Class内部可以使用get和set关键字,对某个属性设置存值函数和取值函数。
|
与ES5一样,在Class内部可以使用get和set关键字,对某个属性设置存值函数和取值函数。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class MyClass {
|
class MyClass {
|
||||||
get prop() {
|
get prop() {
|
||||||
return 'getter';
|
return 'getter';
|
||||||
@ -536,7 +489,6 @@ inst.prop = 123;
|
|||||||
|
|
||||||
inst.prop
|
inst.prop
|
||||||
// 'getter'
|
// 'getter'
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,prop属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。
|
上面代码中,prop属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。
|
||||||
@ -546,7 +498,6 @@ inst.prop
|
|||||||
如果某个方法之前加上星号(*),就表示该方法是一个Generator函数。
|
如果某个方法之前加上星号(*),就表示该方法是一个Generator函数。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class Foo {
|
class Foo {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
this.args = args;
|
this.args = args;
|
||||||
@ -563,7 +514,6 @@ for (let x of new Foo('hello', 'world')) {
|
|||||||
}
|
}
|
||||||
// hello
|
// hello
|
||||||
// world
|
// world
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,Foo类的Symbol.iterator方法前有一个星号,表示该方法是一个Generator函数。Symbol.iterator方法返回一个Foo类的默认遍历器,for...of循环会自动调用这个遍历器。
|
上面代码中,Foo类的Symbol.iterator方法前有一个星号,表示该方法是一个Generator函数。Symbol.iterator方法返回一个Foo类的默认遍历器,for...of循环会自动调用这个遍历器。
|
||||||
@ -573,7 +523,6 @@ for (let x of new Foo('hello', 'world')) {
|
|||||||
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
|
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class Foo {
|
class Foo {
|
||||||
static classMethod() {
|
static classMethod() {
|
||||||
return 'hello';
|
return 'hello';
|
||||||
@ -585,7 +534,6 @@ Foo.classMethod() // 'hello'
|
|||||||
var foo = new Foo();
|
var foo = new Foo();
|
||||||
foo.classMethod()
|
foo.classMethod()
|
||||||
// TypeError: undefined is not a function
|
// TypeError: undefined is not a function
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(`Foo.classMethod()`),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。
|
上面代码中,Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(`Foo.classMethod()`),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。
|
||||||
@ -593,7 +541,6 @@ foo.classMethod()
|
|||||||
父类的静态方法,可以被子类继承。
|
父类的静态方法,可以被子类继承。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class Foo {
|
class Foo {
|
||||||
static classMethod() {
|
static classMethod() {
|
||||||
return 'hello';
|
return 'hello';
|
||||||
@ -604,7 +551,6 @@ class Bar extends Foo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bar.classMethod(); // 'hello'
|
Bar.classMethod(); // 'hello'
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,父类Foo有一个静态方法,子类Bar可以调用这个方法。
|
上面代码中,父类Foo有一个静态方法,子类Bar可以调用这个方法。
|
||||||
@ -612,7 +558,6 @@ Bar.classMethod(); // 'hello'
|
|||||||
静态方法也是可以从super对象上调用的。
|
静态方法也是可以从super对象上调用的。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
class Foo {
|
class Foo {
|
||||||
static classMethod() {
|
static classMethod() {
|
||||||
return 'hello';
|
return 'hello';
|
||||||
@ -626,7 +571,145 @@ class Bar extends Foo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bar.classMethod();
|
Bar.classMethod();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 修饰器
|
||||||
|
|
||||||
|
修饰器(Decorator)用于修改类的行为。这是ES7的一个[提案](https://github.com/wycats/javascript-decorators),目前Babel转码器已经支持。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function testable(target) {
|
||||||
|
target.isTestable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@testable
|
||||||
|
class MyTestableClass () {}
|
||||||
|
|
||||||
|
console.log(MyTestableClass.isTestable) // true
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,`@testable`就是一个修饰器。它修改了MyTestableClass这个类的行为,为它加上了静态属性isTestable。
|
||||||
|
|
||||||
|
修饰器函数的参数,就是所要修饰的目标对象。比如上面代码中,testable函数的参数target,就是所要修饰的对象。如果希望修饰器的行为,能够根据目标对象的不同而不同,就要在外面再封装一层函数。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function testable(isTestable) {
|
||||||
|
return function(target) {
|
||||||
|
target.isTestable = isTestable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@testable(true) class MyTestableClass () {}
|
||||||
|
console.log(MyTestableClass.isTestable) // true
|
||||||
|
|
||||||
|
@testable(false) class MyClass () {}
|
||||||
|
console.log(MyClass.isTestable) // false
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,修饰器testable可以接受参数,这就等于可以修改修饰器的行为。
|
||||||
|
|
||||||
|
如果想要为类的实例添加方法,可以在修饰器函数中,为目标类的prototype属性添加方法。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function testable(target) {
|
||||||
|
target.prototype.isTestable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@testable
|
||||||
|
class MyTestableClass () {}
|
||||||
|
|
||||||
|
let obj = new MyClass();
|
||||||
|
|
||||||
|
console.log(obj.isTestable) // true
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,修饰器函数testable是在目标类的prototype属性添加属性,因此就可以在类的实例上调用添加的属性。
|
||||||
|
|
||||||
|
下面是另外一个例子。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// mixins.js
|
||||||
|
export function mixins(...list) {
|
||||||
|
return function (target) {
|
||||||
|
Object.assign(target.prototype, ...list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// main.js
|
||||||
|
import { mixins } from './mixins'
|
||||||
|
|
||||||
|
const Foo = {
|
||||||
|
foo() { console.log('foo') }
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixins(Foo)
|
||||||
|
class MyClass {}
|
||||||
|
|
||||||
|
let obj = new MyClass()
|
||||||
|
|
||||||
|
obj.foo() // 'foo'
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码通过修饰器mixins,可以为类添加指定的方法。
|
||||||
|
|
||||||
|
修饰器可以用`Object.assign()`模拟。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const Foo = {
|
||||||
|
foo() { console.log('foo') }
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyClass {}
|
||||||
|
|
||||||
|
Object.assign(MyClass.prototype, Foo);
|
||||||
|
|
||||||
|
let obj = new MyClass();
|
||||||
|
obj.foo() // 'foo'
|
||||||
|
```
|
||||||
|
|
||||||
|
修饰器不仅可以修饰类,还可以修饰类的属性。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class Person {
|
||||||
|
@readonly
|
||||||
|
name() { return `${this.first} ${this.last}` }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,修饰器readonly用来修饰”类“的name方法。
|
||||||
|
|
||||||
|
此时,修饰器函数一共可以接受三个参数,第一个参数是所要修饰的目标对象,第二个参数是所要修饰的属性名,第三个参数是该属性的描述对象。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
readonly(Person.prototype, 'name', descriptor);
|
||||||
|
|
||||||
|
function readonly(target, name, descriptor){
|
||||||
|
// descriptor对象原来的值如下
|
||||||
|
// {
|
||||||
|
// value: specifiedFunction,
|
||||||
|
// enumerable: false,
|
||||||
|
// configurable: true,
|
||||||
|
// writable: true
|
||||||
|
// };
|
||||||
|
descriptor.writable = false;
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(Person.prototype, 'name', descriptor);
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码说明,修饰器(readonly)会修改属性的描述对象(descriptor),然后被修改的描述对象再用来定义属性。下面是另一个例子。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class Person {
|
||||||
|
@nonenumerable
|
||||||
|
get kidCount() { return this.children.length; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function nonenumerable(target, name, descriptor) {
|
||||||
|
descriptor.enumerable = false;
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Module
|
## Module
|
||||||
|
@ -674,7 +674,7 @@ function f(x){
|
|||||||
|
|
||||||
上面代码中,函数f的最后一步是调用函数g,这就叫尾调用。
|
上面代码中,函数f的最后一步是调用函数g,这就叫尾调用。
|
||||||
|
|
||||||
以下两种情况,都不属于尾调用。
|
以下三种情况,都不属于尾调用。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// 情况一
|
// 情况一
|
||||||
@ -687,9 +687,21 @@ function f(x){
|
|||||||
function f(x){
|
function f(x){
|
||||||
return g(x) + 1;
|
return g(x) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 情况三
|
||||||
|
function f(x){
|
||||||
|
g(x);
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,情况一是调用函数g之后,还有别的操作,所以不属于尾调用,即使语义完全一样。情况二也属于调用后还有操作,即使写在一行内。
|
上面代码中,情况一是调用函数g之后,还有别的操作,所以不属于尾调用,即使语义完全一样。情况二也属于调用后还有操作,即使写在一行内。情况三等同于下面的代码。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function f(x){
|
||||||
|
g(x);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
尾调用不一定出现在函数尾部,只要是最后一步操作即可。
|
尾调用不一定出现在函数尾部,只要是最后一步操作即可。
|
||||||
|
|
||||||
|
@ -38,7 +38,9 @@ ECMA的第39号技术专家委员会(Technical Committee 39,简称TC39)负
|
|||||||
|
|
||||||
各大浏览器的最新版本,对ES6的支持可以查看[kangax.github.io/es5-compat-table/es6/](http://kangax.github.io/es5-compat-table/es6/)。随着时间的推移,支持度已经越来越高了,ES6的大部分特性都实现了。
|
各大浏览器的最新版本,对ES6的支持可以查看[kangax.github.io/es5-compat-table/es6/](http://kangax.github.io/es5-compat-table/es6/)。随着时间的推移,支持度已经越来越高了,ES6的大部分特性都实现了。
|
||||||
|
|
||||||
Node.js和io.js(一个部署新功能更快的Node分支)对ES6的支持度,比浏览器更高。通过它们,可以体验更多ES6的特性。建议使用版本管理工具[nvm](https://github.com/creationix/nvm),来安装Node.js和io.js。不过,nvm不支持Windows系统,下面的操作可以改用[nvmw](https://github.com/hakobera/nvmw)或[nvm-windows](https://github.com/coreybutler/nvm-windows)代替。
|
Node.js和io.js(一个部署新功能更快的Node分支)是JavaScript语言的服务器运行环境。它们对ES6的支持度,比浏览器更高。通过它们,可以体验更多ES6的特性。
|
||||||
|
|
||||||
|
建议使用版本管理工具[nvm](https://github.com/creationix/nvm),来安装Node.js和io.js。不过,nvm不支持Windows系统,下面的操作可以改用[nvmw](https://github.com/hakobera/nvmw)或[nvm-windows](https://github.com/coreybutler/nvm-windows)代替。
|
||||||
|
|
||||||
安装nvm需要打开命令行窗口,运行下面的命令。
|
安装nvm需要打开命令行窗口,运行下面的命令。
|
||||||
|
|
||||||
@ -105,6 +107,13 @@ $ node --v8-options | grep harmony
|
|||||||
|
|
||||||
上面命令的输出结果,会因为版本的不同而有所不同。
|
上面命令的输出结果,会因为版本的不同而有所不同。
|
||||||
|
|
||||||
|
我写了一个[ES-Checker](https://github.com/ruanyf/es-checker)模块,用来检查各种运行环境对ES6的支持情况。访问[ruanyf.github.io/es-checker](http://ruanyf.github.io/es-checker),可以看到您的浏览器支持ES6的程度。运行下面的命令,可以查看本机支持ES6的程度。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install -g es-checker
|
||||||
|
$ es-checker
|
||||||
|
```
|
||||||
|
|
||||||
## Babel转码器
|
## Babel转码器
|
||||||
|
|
||||||
[Babel](https://babeljs.io/)是一个广泛使用的ES6转码器,可以ES6代码转为ES5代码,从而在浏览器或其他环境执行。这意味着,你可以用ES6的方式编写程序,又不用担心现有环境是否支持。它的安装命令如下。
|
[Babel](https://babeljs.io/)是一个广泛使用的ES6转码器,可以ES6代码转为ES5代码,从而在浏览器或其他环境执行。这意味着,你可以用ES6的方式编写程序,又不用担心现有环境是否支持。它的安装命令如下。
|
||||||
|
47
docs/let.md
47
docs/let.md
@ -58,12 +58,10 @@ a[6](); // 6
|
|||||||
let不像var那样,会发生“变量提升”现象。
|
let不像var那样,会发生“变量提升”现象。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
function do_something() {
|
function do_something() {
|
||||||
console.log(foo); // ReferenceError
|
console.log(foo); // ReferenceError
|
||||||
let foo = 2;
|
let foo = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码在声明foo之前,就使用这个变量,结果会抛出一个错误。
|
上面代码在声明foo之前,就使用这个变量,结果会抛出一个错误。
|
||||||
@ -71,12 +69,10 @@ function do_something() {
|
|||||||
这也意味着typeof不再是一个百分之百安全的操作。
|
这也意味着typeof不再是一个百分之百安全的操作。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
if (1) {
|
if (1) {
|
||||||
typeof x; // ReferenceError
|
typeof x; // ReferenceError
|
||||||
let x;
|
let x;
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,由于块级作用域内typeof运行时,x还没有值,所以会抛出一个ReferenceError。
|
上面代码中,由于块级作用域内typeof运行时,x还没有值,所以会抛出一个ReferenceError。
|
||||||
@ -84,14 +80,12 @@ if (1) {
|
|||||||
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
|
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
var tmp = 123;
|
var tmp = 123;
|
||||||
|
|
||||||
if (true) {
|
if (true) {
|
||||||
tmp = 'abc'; // ReferenceError
|
tmp = 'abc'; // ReferenceError
|
||||||
let tmp;
|
let tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
|
上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
|
||||||
@ -101,7 +95,6 @@ ES6明确规定,如果区块中存在let和const命令,这个区块对这些
|
|||||||
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称TDZ)。
|
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称TDZ)。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
if (true) {
|
if (true) {
|
||||||
// TDZ开始
|
// TDZ开始
|
||||||
tmp = 'abc'; // ReferenceError
|
tmp = 'abc'; // ReferenceError
|
||||||
@ -113,7 +106,6 @@ if (true) {
|
|||||||
tmp = 123;
|
tmp = 123;
|
||||||
console.log(tmp); // 123
|
console.log(tmp); // 123
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,在let命令声明变量tmp之前,都属于变量tmp的“死区”。
|
上面代码中,在let命令声明变量tmp之前,都属于变量tmp的“死区”。
|
||||||
@ -121,13 +113,11 @@ if (true) {
|
|||||||
有些“死区”比较隐蔽,不太容易发现。
|
有些“死区”比较隐蔽,不太容易发现。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
function bar(x=y, y=2) {
|
function bar(x=y, y=2) {
|
||||||
return [x, y];
|
return [x, y];
|
||||||
}
|
}
|
||||||
|
|
||||||
bar(); // 报错
|
bar(); // 报错
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,调用bar函数之所以报错,是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于”死区“。
|
上面代码中,调用bar函数之所以报错,是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于”死区“。
|
||||||
@ -135,7 +125,6 @@ bar(); // 报错
|
|||||||
需要注意的是,函数的作用域是其声明时所在的作用域。如果函数A的参数是函数B,那么函数B的作用域不是函数A。
|
需要注意的是,函数的作用域是其声明时所在的作用域。如果函数A的参数是函数B,那么函数B的作用域不是函数A。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
let foo = 'outer';
|
let foo = 'outer';
|
||||||
|
|
||||||
function bar(func = x => foo) {
|
function bar(func = x => foo) {
|
||||||
@ -144,7 +133,6 @@ function bar(func = x => foo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bar();
|
bar();
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,函数bar的参数func,默认是一个匿名函数,返回值为变量foo。这个匿名函数的作用域就不是bar。这个匿名函数声明时,是处在外层作用域,所以内部的foo指向函数体外的声明,输出outer。它实际上等同于下面的代码。
|
上面代码中,函数bar的参数func,默认是一个匿名函数,返回值为变量foo。这个匿名函数的作用域就不是bar。这个匿名函数声明时,是处在外层作用域,所以内部的foo指向函数体外的声明,输出outer。它实际上等同于下面的代码。
|
||||||
@ -167,7 +155,6 @@ bar();
|
|||||||
let不允许在相同作用域内,重复声明同一个变量。
|
let不允许在相同作用域内,重复声明同一个变量。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
// 报错
|
// 报错
|
||||||
{
|
{
|
||||||
let a = 10;
|
let a = 10;
|
||||||
@ -179,13 +166,11 @@ let不允许在相同作用域内,重复声明同一个变量。
|
|||||||
let a = 10;
|
let a = 10;
|
||||||
let a = 1;
|
let a = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
因此,不能在函数内部重新声明参数。
|
因此,不能在函数内部重新声明参数。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
function func(arg) {
|
function func(arg) {
|
||||||
let arg; // 报错
|
let arg; // 报错
|
||||||
}
|
}
|
||||||
@ -195,7 +180,6 @@ function func(arg) {
|
|||||||
let arg; // 不报错
|
let arg; // 不报错
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 块级作用域
|
## 块级作用域
|
||||||
@ -259,7 +243,6 @@ function f() { console.log('I am outside!'); }
|
|||||||
const也用来声明变量,但是声明的是常量。一旦声明,常量的值就不能改变。
|
const也用来声明变量,但是声明的是常量。一旦声明,常量的值就不能改变。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
const PI = 3.1415;
|
const PI = 3.1415;
|
||||||
PI // 3.1415
|
PI // 3.1415
|
||||||
|
|
||||||
@ -268,7 +251,6 @@ PI // 3.1415
|
|||||||
|
|
||||||
const PI = 3.1;
|
const PI = 3.1;
|
||||||
PI // 3.1415
|
PI // 3.1415
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码表明改变常量的值是不起作用的。需要注意的是,对常量重新赋值不会报错,只会默默地失败。
|
上面代码表明改变常量的值是不起作用的。需要注意的是,对常量重新赋值不会报错,只会默默地失败。
|
||||||
@ -276,24 +258,20 @@ PI // 3.1415
|
|||||||
const的作用域与let命令相同:只在声明所在的块级作用域内有效。
|
const的作用域与let命令相同:只在声明所在的块级作用域内有效。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
if (true) {
|
if (true) {
|
||||||
const MAX = 5;
|
const MAX = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 常量MAX在此处不可得
|
// 常量MAX在此处不可得
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
const命令也不存在提升,只能在声明的位置后面使用。
|
const命令也不存在提升,只能在声明的位置后面使用。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
if (true) {
|
if (true) {
|
||||||
console.log(MAX); // ReferenceError
|
console.log(MAX); // ReferenceError
|
||||||
const MAX = 5;
|
const MAX = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码在常量MAX声明之前就调用,结果报错。
|
上面代码在常量MAX声明之前就调用,结果报错。
|
||||||
@ -301,20 +279,17 @@ if (true) {
|
|||||||
const声明的常量,也与let一样不可重复声明。
|
const声明的常量,也与let一样不可重复声明。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
var message = "Hello!";
|
var message = "Hello!";
|
||||||
let age = 25;
|
let age = 25;
|
||||||
|
|
||||||
// 以下两行都会报错
|
// 以下两行都会报错
|
||||||
const message = "Goodbye!";
|
const message = "Goodbye!";
|
||||||
const age = 30;
|
const age = 30;
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
由于const命令只是指向变量所在的地址,所以将一个对象声明为常量必须非常小心。
|
由于const命令只是指向变量所在的地址,所以将一个对象声明为常量必须非常小心。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|
||||||
const foo = {};
|
const foo = {};
|
||||||
foo.prop = 123;
|
foo.prop = 123;
|
||||||
|
|
||||||
@ -322,7 +297,6 @@ foo.prop
|
|||||||
// 123
|
// 123
|
||||||
|
|
||||||
foo = {} // 不起作用
|
foo = {} // 不起作用
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
|
上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
|
||||||
@ -360,6 +334,27 @@ var constantize = (obj) => {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 跨模块常量
|
||||||
|
|
||||||
|
上面说过,const声明的常量只在当前代码块有效。如果想设置跨模块的常量,可以采用下面的写法。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// constants.js 模块
|
||||||
|
export const A = 1;
|
||||||
|
export const B = 3;
|
||||||
|
export const C = 4;
|
||||||
|
|
||||||
|
// test1.js 模块
|
||||||
|
import * as constants from './constants';
|
||||||
|
console.log(constants.A); // 1
|
||||||
|
console.log(constants.B); // 3
|
||||||
|
|
||||||
|
// test2.js 模块
|
||||||
|
import {A, B} from './constants';
|
||||||
|
console.log(A); // 1
|
||||||
|
console.log(B); // 3
|
||||||
|
```
|
||||||
|
|
||||||
## 全局对象的属性
|
## 全局对象的属性
|
||||||
|
|
||||||
全局对象是最顶层的对象,在浏览器环境指的是window对象,在Node.js指的是global对象。在JavaScript语言中,所有全局变量都是全局对象的属性。
|
全局对象是最顶层的对象,在浏览器环境指的是window对象,在Node.js指的是global对象。在JavaScript语言中,所有全局变量都是全局对象的属性。
|
||||||
|
@ -113,6 +113,7 @@
|
|||||||
- Dave Herman, [Static module resolution](http://calculist.org/blog/2012/06/29/static-module-resolution/): ES6模块的静态化设计思想
|
- 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, [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语法的详细介绍和设计思想分析
|
- Axel Rauschmayer, [Classes in ECMAScript 6 (final semantics)](http://www.2ality.com/2015/02/es6-classes-final.html): Class语法的详细介绍和设计思想分析
|
||||||
|
- Maximiliano Fierro, [Declarative vs Imperative](http://elmasse.github.io/js/decorators-bindings-es7.html): Decorators介绍
|
||||||
|
|
||||||
## 工具
|
## 工具
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user