mirror of
https://github.com/ruanyf/es6tutorial.git
synced 2025-05-24 10:22:23 +00:00
edit object/reflect
This commit is contained in:
parent
0dbfa7a33b
commit
e673693006
@ -250,7 +250,27 @@ class Foo {}
|
||||
|
||||
如果存在Class的提升,上面代码将报错,因为let命令也是不提升的。
|
||||
|
||||
**(7)严格模式**
|
||||
**(7)存取函数**
|
||||
|
||||
Class支持set和get方法,设置赋值函数和取值函数,拦截属性的存取行为。
|
||||
|
||||
```javascript
|
||||
class Jedi {
|
||||
constructor(options = {}) {
|
||||
// ...
|
||||
}
|
||||
|
||||
set(key, val) {
|
||||
this[key] = val;
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this[key];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**(8)严格模式**
|
||||
|
||||
类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际上把整个语言升级到了严格模式。
|
||||
|
||||
@ -741,12 +761,10 @@ import { stat, exists, readFile } from 'fs';
|
||||
ES6允许将独立的JS文件作为模块,也就是说,允许一个JavaScript脚本文件调用另一个脚本文件。该文件内部的所有变量,外部无法获取,必须使用export关键字输出变量。下面是一个JS文件,里面使用export关键字输出变量。
|
||||
|
||||
```javascript
|
||||
|
||||
// profile.js
|
||||
export var firstName = 'Michael';
|
||||
export var lastName = 'Jackson';
|
||||
export var year = 1958;
|
||||
|
||||
```
|
||||
|
||||
上面代码是profile.js文件,保存了用户信息。ES6将其视为一个模块,里面用export命令对外部输出了三个变量。
|
||||
@ -754,14 +772,12 @@ export var year = 1958;
|
||||
export的写法,除了像上面这样,还有另外一种。
|
||||
|
||||
```javascript
|
||||
|
||||
// profile.js
|
||||
var firstName = 'Michael';
|
||||
var lastName = 'Jackson';
|
||||
var year = 1958;
|
||||
|
||||
export {firstName, lastName, year};
|
||||
|
||||
```
|
||||
|
||||
上面代码在export命令后面,使用大括号指定所要输出的一组变量。它与前一种写法(直接放置在var语句前)是等价的,但是应该优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。
|
||||
@ -769,7 +785,6 @@ export {firstName, lastName, year};
|
||||
使用export命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块(文件)。
|
||||
|
||||
```javascript
|
||||
|
||||
// main.js
|
||||
|
||||
import {firstName, lastName, year} from './profile';
|
||||
@ -777,7 +792,6 @@ import {firstName, lastName, year} from './profile';
|
||||
function sfirsetHeader(element) {
|
||||
element.textContent = firstName + ' ' + lastName;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
上面代码属于另一个文件main.js,import命令就用于加载profile.js文件,并从中输入变量。import命令接受一个对象(用大括号表示),里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。
|
||||
@ -785,15 +799,12 @@ function sfirsetHeader(element) {
|
||||
如果想为输入的变量重新取一个名字,import语句中要使用as关键字,将输入的变量重命名。
|
||||
|
||||
```javascript
|
||||
|
||||
import { lastName as surname } from './profile';
|
||||
|
||||
```
|
||||
|
||||
ES6支持多重加载,即所加载的模块中又加载其他模块。
|
||||
|
||||
```javascript
|
||||
|
||||
import { Vehicle } from './Vehicle';
|
||||
|
||||
class Car extends Vehicle {
|
||||
@ -803,11 +814,22 @@ class Car extends Vehicle {
|
||||
}
|
||||
|
||||
export { Car }
|
||||
|
||||
```
|
||||
|
||||
上面的模块先加载Vehicle模块,然后在其基础上添加了move方法,再作为一个新模块输出。
|
||||
|
||||
如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。
|
||||
|
||||
```javascript
|
||||
export { es6 as default } from './someModule';
|
||||
|
||||
// 等同于
|
||||
import { es6 } from './someModule';
|
||||
export default es6;
|
||||
```
|
||||
|
||||
上面代码中,export和import语句可以结合在一起,写成一行。但是从可读性考虑,不建议采用这种写法,h应该采用标准写法。
|
||||
|
||||
**(2)模块的整体输入,module命令**
|
||||
|
||||
export命令除了输出变量,还可以输出方法或类(class)。下面是一个circle.js文件,它输出两个方法area和circumference。
|
||||
|
@ -345,7 +345,6 @@ arr1.push(...arr2);
|
||||
扩展运算符还可以用于数组的赋值。
|
||||
|
||||
```javascript
|
||||
|
||||
var a = [1];
|
||||
var b = [2, 3, 4];
|
||||
var c = [6, 7];
|
||||
@ -353,15 +352,17 @@ var d = [0, ...a, ...b, 5, ...c];
|
||||
|
||||
d
|
||||
// [0, 1, 2, 3, 4, 5, 6, 7]
|
||||
|
||||
```
|
||||
|
||||
上面代码其实也提供了,将一个数组拷贝进另一个数组的便捷方法。
|
||||
|
||||
```javascript
|
||||
const arr2 = [...arr1];
|
||||
```
|
||||
|
||||
扩展运算符也可以与解构赋值结合起来,用于生成数组。
|
||||
|
||||
```javascript
|
||||
|
||||
const [first, ...rest] = [1, 2, 3, 4, 5];
|
||||
first // 1
|
||||
rest // [2, 3, 4, 5]
|
||||
@ -381,7 +382,6 @@ rest // ["bar"]
|
||||
const [first, ...rest] = ["foo", "bar", "baz"];
|
||||
first // "foo"
|
||||
rest // ["bar","baz"]
|
||||
|
||||
```
|
||||
|
||||
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
|
||||
|
182
docs/object.md
182
docs/object.md
@ -772,7 +772,14 @@ Proxy可以理解成在目标对象之前,架设一层“拦截”,外界对
|
||||
ES6原生提供Proxy构造函数,用来生成Proxy实例。
|
||||
|
||||
```javascript
|
||||
var proxy = new Proxy(target, handler)
|
||||
```
|
||||
|
||||
Proxy对象的使用方法,都是上面这种形式。`new Proxy()`表示生成一个Proxy实例,它的target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
|
||||
|
||||
下面是一个使用实例。
|
||||
|
||||
```javascript
|
||||
var proxy = new Proxy({}, {
|
||||
get: function(target, property) {
|
||||
return 35;
|
||||
@ -782,17 +789,21 @@ var proxy = new Proxy({}, {
|
||||
proxy.time // 35
|
||||
proxy.name // 35
|
||||
proxy.title // 35
|
||||
|
||||
```
|
||||
|
||||
作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个设置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,设置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35。
|
||||
上面代码中,作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个设置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,设置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35。
|
||||
|
||||
注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。
|
||||
|
||||
一个技巧是将Proxy对象,设置到`object.proxy`属性,从而可以在object对象上调用。
|
||||
|
||||
```javascript
|
||||
var object = { proxy: new Proxy(target, handler) }.
|
||||
```
|
||||
|
||||
Proxy实例也可以作为其他对象的原型对象。
|
||||
|
||||
```javascript
|
||||
|
||||
var proxy = new Proxy({}, {
|
||||
get: function(target, property) {
|
||||
return 35;
|
||||
@ -800,41 +811,72 @@ var proxy = new Proxy({}, {
|
||||
});
|
||||
|
||||
let obj = Object.create(proxy);
|
||||
|
||||
obj.time // 35
|
||||
|
||||
```
|
||||
|
||||
上面代码中,proxy对象是obj对象的原型,obj对象本身并没有time属性,所有根据原型链,会在proxy对象上读取该属性,导致被拦截。
|
||||
|
||||
对于没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。
|
||||
同一个拦截器函数,可以设置拦截多个操作。
|
||||
|
||||
Proxy支持的拦截操作一览。
|
||||
```javascript
|
||||
var handler = {
|
||||
get: function(target, name) {
|
||||
if (name === 'prototype') return Object.prototype;
|
||||
return 'Hello, '+ name;
|
||||
},
|
||||
apply: function(target, thisBinding, args) { return args[0]; },
|
||||
construct: function(target, args) { return args[1]; }
|
||||
};
|
||||
|
||||
- defineProperty(target, propKey, propDesc):返回一个布尔值,拦截Object.defineProperty(proxy, propKey, propDesc)
|
||||
- deleteProperty(target, propKey) :返回一个布尔值,拦截delete proxy[propKey]
|
||||
- enumerate(target):返回一个遍历器,拦截for (x in proxy)
|
||||
- get(target, propKey, receiver):返回类型不限,拦截对象属性的读取
|
||||
- getOwnPropertyDescriptor(target, propKey) :返回属性的描述对象,拦截Object.getOwnPropertyDescriptor(proxy, propKey)
|
||||
- getPrototypeOf(target) :返回一个对象,拦截Object.getPrototypeOf(proxy)
|
||||
- has(target, propKey):返回一个布尔值,拦截propKey in proxy
|
||||
- isExtensible(target):返回一个布尔值,拦截Object.isExtensible(proxy)
|
||||
- ownKeys(target):返回一个数组,拦截Object.getOwnPropertyPropertyNames(proxy)、Object.getOwnPropertyPropertySymbols(proxy)、Object.keys(proxy)
|
||||
- preventExtensions(target):返回一个布尔值,拦截Object.preventExtensions(proxy)
|
||||
- set(target, propKey, value, receiver):返回一个布尔值,拦截对象属性的设置
|
||||
- setPrototypeOf(target, proto):返回一个布尔值,拦截Object.setPrototypeOf(proxy, proto)
|
||||
var fproxy = new Proxy(function(x,y) {
|
||||
return x+y;
|
||||
}, handler);
|
||||
|
||||
fproxy(1,2); // 1
|
||||
new fproxy(1,2); // 2
|
||||
fproxy.prototype; // Object.prototype
|
||||
fproxy.foo; // 'Hello, foo'
|
||||
```
|
||||
|
||||
Proxy支持的拦截操作一览。对于没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。
|
||||
|
||||
(1)get(target, propKey, receiver):拦截对象属性的读取,比如`proxy.foo`和`proxy['foo']`,返回类型不限。最后一个参数receiver可选,当target对象设置了propKey属性的get函数时,receiver对象会绑定get函数的this对象。
|
||||
|
||||
(2)set(target, propKey, value, receiver):拦截对象属性的设置,比如`proxy.foo = v`或`proxy['foo'] = v`,返回一个布尔值。
|
||||
|
||||
(3)has(target, propKey):拦截`propKey in proxy`的操作,返回一个布尔值。
|
||||
|
||||
(4)deleteProperty(target, propKey) :拦截`delete proxy[propKey]`的操作,返回一个布尔值。
|
||||
|
||||
(5)enumerate(target):拦截`for (var x in proxy)`,返回一个遍历器。
|
||||
|
||||
(6)hasOwn(target, propKey):拦截`proxy.hasOwnProperty('foo')`,返回一个布尔值。
|
||||
|
||||
(7)ownKeys(target):拦截`Object.getOwnPropertyNames(proxy)`、`Object.getOwnPropertySymbols(proxy)`、`Object.keys(proxy)`,返回一个数组。该方法返回对象所有自身的属性,而`Object.keys()`仅返回对象可遍历的属性。
|
||||
|
||||
(8)getOwnPropertyDescriptor(target, propKey) :拦截`Object.getOwnPropertyDescriptor(proxy, propKey)`,返回属性的描述对象。
|
||||
|
||||
(9)defineProperty(target, propKey, propDesc):拦截`Object.defineProperty(proxy, propKey, propDesc)`、`Object.defineProperties(proxy, propDescs)`,返回一个布尔值。
|
||||
|
||||
(10)preventExtensions(target):拦截`Object.preventExtensions(proxy)`,返回一个布尔值。
|
||||
|
||||
(11)getPrototypeOf(target) :拦截`Object.getPrototypeOf(proxy)`,返回一个对象。
|
||||
|
||||
(12)isExtensible(target):拦截`Object.isExtensible(proxy)`,返回一个布尔值。
|
||||
|
||||
(13)setPrototypeOf(target, proto):拦截`Object.setPrototypeOf(proxy, proto)`,返回一个布尔值。
|
||||
|
||||
如果目标对象是函数,那么还有两种额外操作可以拦截。
|
||||
|
||||
- apply方法:拦截Proxy实例作为函数调用的操作,比如proxy(···)、proxy.call(···)、proxy.apply(···)。
|
||||
- construct方法:拦截Proxy实例作为构造函数调用的操作,比如new proxy(···)。
|
||||
(14)apply(target, object, args):拦截Proxy实例作为函数调用的操作,比如`proxy(...args)`、`proxy.call(object, ...args)`、`proxy.apply(...)`。
|
||||
|
||||
### get
|
||||
(15)construct(target, args, proxy):拦截Proxy实例作为构造函数调用的操作,比如new proxy(...args)。
|
||||
|
||||
### get()
|
||||
|
||||
get方法用于拦截某个属性的读取操作。上文已经有一个例子,下面是另一个拦截读取操作的例子。
|
||||
|
||||
```javascript
|
||||
|
||||
var person = {
|
||||
name: "张三"
|
||||
};
|
||||
@ -851,7 +893,6 @@ var proxy = new Proxy(person, {
|
||||
|
||||
proxy.name // "张三"
|
||||
proxy.age // 抛出一个错误
|
||||
|
||||
```
|
||||
|
||||
上面代码表示,如果访问目标对象不存在的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性,只会返回undefined。
|
||||
@ -859,7 +900,6 @@ proxy.age // 抛出一个错误
|
||||
利用proxy,可以将读取属性的操作(get),转变为执行某个函数。
|
||||
|
||||
```javascript
|
||||
|
||||
var pipe = (function () {
|
||||
var pipe;
|
||||
return function (value) {
|
||||
@ -884,12 +924,11 @@ var reverseInt = function (n) { return n.toString().split('').reverse().join('')
|
||||
|
||||
pipe(3) . double . pow . reverseInt . get
|
||||
// 63
|
||||
|
||||
```
|
||||
|
||||
上面代码设置Proxy以后,达到了将函数名链式使用的效果。
|
||||
|
||||
### set
|
||||
### set()
|
||||
|
||||
set方法用来拦截某个属性的赋值操作。假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy对象保证age的属性值符合要求。
|
||||
|
||||
@ -923,7 +962,7 @@ person.age = 300 // 报错
|
||||
|
||||
上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误。利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新DOM。
|
||||
|
||||
### apply
|
||||
### apply()
|
||||
|
||||
apply方法拦截函数的调用、call和apply操作。
|
||||
|
||||
@ -945,12 +984,11 @@ p() === 'I am the proxy';
|
||||
|
||||
上面代码中,变量p是Proxy的实例,当它作为函数调用时(p()),就会被apply方法拦截,返回一个字符串。
|
||||
|
||||
### ownKeys
|
||||
### ownKeys()
|
||||
|
||||
ownKeys方法用来拦截Object.keys()操作。
|
||||
|
||||
```javascript
|
||||
|
||||
let target = {};
|
||||
|
||||
let handler = {
|
||||
@ -963,7 +1001,6 @@ let proxy = new Proxy(target, handler);
|
||||
|
||||
Object.keys(proxy)
|
||||
// [ 'hello', 'world' ]
|
||||
|
||||
```
|
||||
|
||||
上面代码拦截了对于target对象的Object.keys()操作,返回预先设定的数组。
|
||||
@ -973,7 +1010,6 @@ Object.keys(proxy)
|
||||
Proxy.revocable方法返回一个可取消的Proxy实例。
|
||||
|
||||
```javascript
|
||||
|
||||
let target = {};
|
||||
let handler = {};
|
||||
|
||||
@ -984,17 +1020,23 @@ proxy.foo // 123
|
||||
|
||||
revoke();
|
||||
proxy.foo // TypeError: Revoked
|
||||
|
||||
```
|
||||
|
||||
Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。
|
||||
|
||||
## Reflect
|
||||
|
||||
### 概述
|
||||
|
||||
Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新API。Reflect对象的设计目的有这样几个。
|
||||
|
||||
- 将Object对象的一些明显属于语言层面的方法,放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署。
|
||||
- Reflect对象的方法与Proxy对象的方法一一对应,这是为了让Proxy对象可以方便地有一个原始方法,作为修改行为基础。
|
||||
(1) 将Object对象的一些明显属于语言层面的方法,放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。
|
||||
|
||||
(2) 修改某些Object方法的返回结果,让其变得更合理。比如,`Object.defineProperty(obj, name, desc)`在无法定义属性时,会抛出一个错误,而`Reflect.defineProperty(obj, name, desc)`则会返回false。
|
||||
|
||||
(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如`name in obj`和`delete obj[name]`,而`Reflect.has(obj, name)`和`Reflect.deleteProperty(obj, name)`让它们变成了函数行为。
|
||||
|
||||
(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。
|
||||
|
||||
```javascript
|
||||
Proxy(target, {
|
||||
@ -1010,22 +1052,19 @@ Proxy(target, {
|
||||
|
||||
上面代码中,Proxy方法拦截target对象的属性赋值行为。它采用Reflect.set方法将值赋值给对象的属性,然后再部署额外的功能。
|
||||
|
||||
下面的例子中,Reflect对象方法基本与Object对象的对应方法一致。
|
||||
下面是get方法的例子。
|
||||
|
||||
```javascript
|
||||
var O = {a: 1};
|
||||
Object.defineProperty(O, 'b', {value: 2});
|
||||
O[Symbol('c')] = 3;
|
||||
|
||||
Reflect.ownKeys(O); // ['a', 'b', Symbol(c)]
|
||||
|
||||
function C(a, b){
|
||||
this.c = a + b;
|
||||
}
|
||||
var instance = Reflect.construct(C, [20, 22]);
|
||||
instance.c; // 42
|
||||
var loggedObj = new Proxy(obj, {
|
||||
get: function(target, name) {
|
||||
console.log("get", target, name);
|
||||
return Reflect.get(target, name);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 方法
|
||||
|
||||
Reflect对象的方法清单如下。
|
||||
|
||||
- Reflect.getOwnPropertyDescriptor(target,name)
|
||||
@ -1048,17 +1087,54 @@ Reflect对象的方法清单如下。
|
||||
- Reflect.apply(target,thisArg,args)
|
||||
- Reflect.construct(target,args)
|
||||
|
||||
上面这些方法中,有几点需要注意。
|
||||
上面这些方法的作用,大部分与Object对象的同名方法的作用都是相同的。下面是对其中几个方法的解释。
|
||||
|
||||
- Reflect.get(target,name,receiver) 查找target对象的name属性。如果name属性部署了读取函数,则读取函数的this绑定receiver。
|
||||
(1)Reflect.get(target,name,receiver)
|
||||
|
||||
- Reflect.set(target, name, value, receiver) 设置target对象的name属性等于value。如果name属性设置了赋值函数,则赋值函数的this绑定receiver。
|
||||
查找并返回target对象的name属性,如果没有该属性,则返回undefined。
|
||||
|
||||
- Reflect.construct(target, args)等同于`new target(...args)`。
|
||||
如果name属性部署了读取函数,则读取函数的this绑定receiver。
|
||||
|
||||
- Reflect.apply(fun,thisArg,args)等同于`Function.prototype.apply.call(fun,thisArg,args)`。
|
||||
```javascript
|
||||
var obj = {
|
||||
get foo() { return this.bar(); },
|
||||
bar: function() { ... }
|
||||
}
|
||||
|
||||
- Reflect.set()、Reflect.defineProperty()、Reflect.freeze()、Reflect.seal()和Reflect.preventExtensions()返回一个布尔值,表示操作是否成功。它们对应的Object方法,失败时都会抛出错误。
|
||||
// 下面语句会让 this.bar()
|
||||
// 变成调用 wrapper.bar()
|
||||
Reflect.get(obj, "foo", wrapper);
|
||||
```
|
||||
|
||||
(2)Reflect.set(target, name, value, receiver)
|
||||
|
||||
设置target对象的name属性等于value。如果name属性设置了赋值函数,则赋值函数的this绑定receiver。
|
||||
|
||||
(3)Reflect.has(obj, name)
|
||||
|
||||
等同于`name in obj`。
|
||||
|
||||
(4)Reflect.deleteProperty(obj, name)
|
||||
|
||||
等同于`delete obj[name]`。
|
||||
|
||||
(5)Reflect.construct(target, args)
|
||||
|
||||
等同于`new target(...args)`,这提供了一种不使用new,来调用构造函数的方法。
|
||||
|
||||
(6)Reflect.getPrototypeOf(obj)
|
||||
|
||||
读取对象的\_\_proto\_\_属性,等同于`Object.getPrototypeOf(obj)`。
|
||||
|
||||
(7)Reflect.setPrototypeOf(obj, newProto)
|
||||
|
||||
设置对象的\_\_proto\_\_属性。注意,Object对象没有对应这个方法的方法。
|
||||
|
||||
(8)Reflect.apply(fun,thisArg,args)
|
||||
|
||||
等同于`Function.prototype.apply.call(fun,thisArg,args)`。一般来说,如果要绑定一个函数的this对象,可以这样写`fn.apply(obj, args)`,但是如果函数定义了自己的apply方法,就只能写成`Function.prototype.apply.call(fn, obj, args)`,采用Reflect对象可以简化这种操作。
|
||||
|
||||
另外,需要注意的是,Reflect.set()、Reflect.defineProperty()、Reflect.freeze()、Reflect.seal()和Reflect.preventExtensions()返回一个布尔值,表示操作是否成功。它们对应的Object方法,失败时都会抛出错误。
|
||||
|
||||
```javascript
|
||||
// 失败时抛出错误
|
||||
|
@ -61,6 +61,10 @@
|
||||
- Axel Rauschmayer, [Symbols in ECMAScript 6](http://www.2ality.com/2014/12/es6-symbols.html): Symbol简介
|
||||
- Axel Rauschmayer, [Meta programming with ECMAScript 6 proxies](http://www.2ality.com/2014/12/es6-proxies.html): Proxy详解
|
||||
- Daniel Zautner, [Meta-programming JavaScript Using Proxies](http://dzautner.com/meta-programming-javascript-using-proxies/): 使用Proxy实现元编程
|
||||
- Tom Van Cutsem, [Harmony-reflect](https://github.com/tvcutsem/harmony-reflect/wiki): Reflect对象的设计目的
|
||||
- Tom Van Cutsem, [Proxy Traps](https://github.com/tvcutsem/harmony-reflect/blob/master/doc/traps.md):Proxy拦截操作一览
|
||||
- Tom Van Cutsem, [Reflect API](https://github.com/tvcutsem/harmony-reflect/blob/master/doc/api.md)
|
||||
- Tom Van Cutsem, [Proxy Handler API](https://github.com/tvcutsem/harmony-reflect/blob/master/doc/handler_api.md)
|
||||
|
||||
## Symbol
|
||||
|
||||
|
273
docs/style.md
273
docs/style.md
@ -1,6 +1,6 @@
|
||||
# 编程风格
|
||||
|
||||
本章探讨如何将ES6的新语法,运用到编码实践之中,与传统的JavaScript语法结合在一起,以及如何形成良好的编码风格。
|
||||
本章探讨如何将ES6的新语法,运用到编码实践之中,与传统的JavaScript语法结合在一起,写出合理的、易于阅读和维护的代码。多家公司和组织已经公开了它们的风格规范,具体可参阅[jscs.info](http://jscs.info/),下面的内容主要参考了[Airbnb](http://jscs.info/)的JavaScript风格规范。
|
||||
|
||||
## 块级作用域
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
ES6提出了两个新的声明变量的命令:let和const。其中,let完全可以取代var,因为两者语义相同,而且let没有副作用。
|
||||
|
||||
```javascript
|
||||
|
||||
"use strict";
|
||||
|
||||
if(true) {
|
||||
@ -19,7 +18,6 @@ if(true) {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
console.log(i);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
上面代码如果用var替代let,实际上就声明了一个全局变量,这显然不是本意。变量应该只在其声明的代码块内有效,var命令做不到这一点。
|
||||
@ -27,14 +25,12 @@ for (let i = 0; i < 10; i++) {
|
||||
var命令存在变量提升效用,let命令没有这个问题。
|
||||
|
||||
```javascript
|
||||
|
||||
"use strict";
|
||||
|
||||
if(true) {
|
||||
console.log(x); // ReferenceError
|
||||
let x = 'hello';
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
上面代码如果使用var替代let,console.log那一行就不会报错,而是会输出undefined,因为变量声明提升到代码块的头部。这违反了变量先声明后使用的原则。
|
||||
@ -46,7 +42,6 @@ if(true) {
|
||||
在let和const之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。这符合函数式编程思想,有利于将来的分布式运算。
|
||||
|
||||
```javascript
|
||||
|
||||
// bad
|
||||
var a = 1, b = 2, c = 3;
|
||||
|
||||
@ -57,7 +52,6 @@ const c = 3;
|
||||
|
||||
// best
|
||||
const [a, b, c] = [1, 2, 3];
|
||||
|
||||
```
|
||||
|
||||
const声明常量还有两个好处,一是阅读代码的人立刻会意识到不应该修改这个值,二是防止了无意间修改变量值所导致的错误。
|
||||
@ -75,7 +69,6 @@ V8引擎只在严格模式之下,支持let和const。结合前两点,这实
|
||||
静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
|
||||
|
||||
```javascript
|
||||
|
||||
// bad
|
||||
const a = "foobar";
|
||||
const b = 'foo' + a + 'bar';
|
||||
@ -87,7 +80,56 @@ const c = `foobar`;
|
||||
const a = 'foobar';
|
||||
const b = `foo${a}bar`;
|
||||
const c = 'foobar';
|
||||
```
|
||||
|
||||
## 解构赋值
|
||||
|
||||
使用数组成员对变量赋值,优先使用解构赋值。
|
||||
|
||||
```javascript
|
||||
const arr = [1, 2, 3, 4];
|
||||
|
||||
// bad
|
||||
const first = arr[0];
|
||||
const second = arr[1];
|
||||
|
||||
// good
|
||||
const [first, second] = arr;
|
||||
```
|
||||
|
||||
函数的参数如果是对象的成员,优先使用解构赋值。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
function getFullName(user) {
|
||||
const firstName = user.firstName;
|
||||
const lastName = user.lastName;
|
||||
}
|
||||
|
||||
// good
|
||||
function getFullName(obj) {
|
||||
const { firstName, lastName } = obj;
|
||||
}
|
||||
|
||||
// best
|
||||
function getFullName({ firstName, lastName }) {
|
||||
}
|
||||
```
|
||||
|
||||
如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
function processInput(input) {
|
||||
return [left, right, top, bottom];
|
||||
}
|
||||
|
||||
// good
|
||||
function processInput(input) {
|
||||
return { left, right, top, bottom };
|
||||
}
|
||||
|
||||
const { left, right } = processInput(input);
|
||||
```
|
||||
|
||||
## 对象
|
||||
@ -95,7 +137,6 @@ const c = 'foobar';
|
||||
单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。
|
||||
|
||||
```javascript
|
||||
|
||||
// bad
|
||||
const a = { k1: v1, k2: v2, };
|
||||
const b = {
|
||||
@ -109,13 +150,11 @@ const b = {
|
||||
k1: v1,
|
||||
k2: v2,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法。
|
||||
|
||||
```javascript
|
||||
|
||||
// bad
|
||||
const a = {};
|
||||
a.x = 3;
|
||||
@ -127,27 +166,108 @@ Object.assign(a, { x: 3 });
|
||||
// good
|
||||
const a = { x: null };
|
||||
a.x = 3;
|
||||
```
|
||||
|
||||
如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
const obj = {
|
||||
id: 5,
|
||||
name: 'San Francisco',
|
||||
};
|
||||
obj[getKey('enabled')] = true;
|
||||
|
||||
// good
|
||||
const obj = {
|
||||
id: 5,
|
||||
name: 'San Francisco',
|
||||
[getKey('enabled')]: true,
|
||||
};
|
||||
```
|
||||
|
||||
上面代码中,对象obj的最后一个属性名,需要计算得到。这时最好采用属性表达式,在新建obj的时候,将该属性与其他属性定义在一起。这样一来,所有属性就在一个地方定义了。
|
||||
|
||||
另外,对象的属性和方法,尽量采用简洁表达法,这样易于描述和书写。
|
||||
|
||||
```javascript
|
||||
var ref = 'some value';
|
||||
|
||||
// bad
|
||||
const atom = {
|
||||
ref: ref,
|
||||
|
||||
value: 1,
|
||||
|
||||
addValue: function (value) {
|
||||
return atom.value + value;
|
||||
},
|
||||
};
|
||||
|
||||
// good
|
||||
const atom = {
|
||||
ref,
|
||||
|
||||
value: 1,
|
||||
|
||||
addValue(value) {
|
||||
return atom.value + value;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## 数组
|
||||
|
||||
使用扩展运算符(...)拷贝数组。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
const len = items.length;
|
||||
const itemsCopy = [];
|
||||
let i;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
itemsCopy[i] = items[i];
|
||||
}
|
||||
|
||||
// good
|
||||
const itemsCopy = [...items];
|
||||
```
|
||||
|
||||
使用Array.from方法,将类似数组的对象转为数组。
|
||||
|
||||
```javascript
|
||||
const foo = document.querySelectorAll('.foo');
|
||||
const nodes = Array.from(foo);
|
||||
```
|
||||
|
||||
## 函数
|
||||
|
||||
使用匿名函数的场合,一律改为使用箭头函数。
|
||||
立即执行函数可以写成箭头函数的形式。
|
||||
|
||||
```javascript
|
||||
|
||||
// bad
|
||||
arr.reduce(function(x, y) { return x + y; }, 0);
|
||||
|
||||
// good
|
||||
arr.reduce((x, y) => x + y, 0);
|
||||
|
||||
(() => {
|
||||
console.log('Welcome to the Internet.');
|
||||
})();
|
||||
```
|
||||
|
||||
箭头函数取代Function.prototype.bind,不应再用 self / _this / that 绑定 this。
|
||||
那些需要使用函数表达式的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了this。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
[1, 2, 3].map(function (x) {
|
||||
return x * x;
|
||||
});
|
||||
|
||||
// good
|
||||
[1, 2, 3].map((x) => {
|
||||
return x * x;
|
||||
});
|
||||
```
|
||||
|
||||
箭头函数取代Function.prototype.bind,不应再用self/\_this/that绑定 this。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
const self = this;
|
||||
const boundMethod = function(...params) {
|
||||
@ -159,13 +279,11 @@ const boundMethod = method.bind(this);
|
||||
|
||||
// best
|
||||
const boundMethod = (...params) => method.apply(this, params);
|
||||
|
||||
```
|
||||
|
||||
所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数。
|
||||
|
||||
```bash
|
||||
|
||||
// bad
|
||||
function divide(a, b, option = false ) {
|
||||
}
|
||||
@ -173,7 +291,35 @@ function divide(a, b, option = false ) {
|
||||
// good
|
||||
function divide(a, b, { option = false } = {}) {
|
||||
}
|
||||
```
|
||||
|
||||
不要在函数体内使用arguments变量,使用rest运算符(...)代替。因为rest运算符显式表明你想要获取参数,而且arguments是一个类似数组的对象,而rest运算符可以提供一个真正的数组。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
function concatenateAll() {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
return args.join('');
|
||||
}
|
||||
|
||||
// good
|
||||
function concatenateAll(...args) {
|
||||
return args.join('');
|
||||
}
|
||||
```
|
||||
|
||||
使用默认值语法设置函数参数的默认值。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
function handleThings(opts) {
|
||||
opts = opts || {};
|
||||
}
|
||||
|
||||
// good
|
||||
function handleThings(opts = {}) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Map结构
|
||||
@ -181,7 +327,6 @@ function divide(a, b, { option = false } = {}) {
|
||||
注意区分Object和Map,只有模拟实体对象时,才使用Object。如果只是需要key:value的数据结构,使用Map。因为Map有内建的遍历机制。
|
||||
|
||||
```javascript
|
||||
|
||||
let map = new Map(arr);
|
||||
|
||||
for (let key of map.keys()) {
|
||||
@ -195,12 +340,60 @@ for (let value of map.values()) {
|
||||
for (let item of map.entries()) {
|
||||
console.log(item[0], item[1]);
|
||||
}
|
||||
```
|
||||
|
||||
## Class
|
||||
|
||||
总是用class,取代需要prototype操作。因为class的写法更简洁,更易于理解。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
function Queue(contents = []) {
|
||||
this._queue = [...contents];
|
||||
}
|
||||
Queue.prototype.pop = function() {
|
||||
const value = this._queue[0];
|
||||
this._queue.splice(0, 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
// good
|
||||
class Queue {
|
||||
constructor(contents = []) {
|
||||
this._queue = [...contents];
|
||||
}
|
||||
pop() {
|
||||
const value = this._queue[0];
|
||||
this._queue.splice(0, 1);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
使用extends实现继承,因为这样更简单,不会有破坏instanceof运算的危险。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
const inherits = require('inherits');
|
||||
function PeekableQueue(contents) {
|
||||
Queue.apply(this, contents);
|
||||
}
|
||||
inherits(PeekableQueue, Queue);
|
||||
PeekableQueue.prototype.peek = function() {
|
||||
return this._queue[0];
|
||||
}
|
||||
|
||||
// good
|
||||
class PeekableQueue extends Queue {
|
||||
peek() {
|
||||
return this._queue[0];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 模块
|
||||
|
||||
使用import取代require。
|
||||
首先,Module语法是JavaScript模块的标准写法,坚持使用这种写法。使用import取代require。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
@ -215,7 +408,6 @@ import { func1, func2 } from 'moduleA';
|
||||
使用export取代module.exports。
|
||||
|
||||
```javascript
|
||||
|
||||
// commonJS的写法
|
||||
var React = require('react');
|
||||
|
||||
@ -237,5 +429,34 @@ const Breadcrumbs = React.createClass({
|
||||
});
|
||||
|
||||
export default Breadcrumbs
|
||||
|
||||
```
|
||||
|
||||
不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default)。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
import * as myObject './importModule';
|
||||
|
||||
// good
|
||||
import myObject from './importModule';
|
||||
```
|
||||
|
||||
如果模块默认输出一个函数,函数名的首字母应该小写。
|
||||
|
||||
```javascript
|
||||
function makeStyleGuide() {
|
||||
}
|
||||
|
||||
export default makeStyleGuide;
|
||||
```
|
||||
|
||||
如果模块默认输出一个对象,对象名的首字母应该大写。
|
||||
|
||||
```javascript
|
||||
const StyleGuide = {
|
||||
es6: {
|
||||
}
|
||||
};
|
||||
|
||||
export default StyleGuide;
|
||||
```
|
||||
|
Loading…
x
Reference in New Issue
Block a user