edit object/reflect

This commit is contained in:
Ruan Yifeng
2015-07-03 16:50:09 +08:00
parent 0dbfa7a33b
commit e673693006
5 changed files with 416 additions and 93 deletions
+33 -11
View File
@@ -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。
+4 -4
View File
@@ -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"]
```
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
+129 -53
View File
@@ -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支持的拦截操作一览。对于没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。
1get(target, propKey, receiver):拦截对象属性的读取,比如`proxy.foo``proxy['foo']`,返回类型不限。最后一个参数receiver可选,当target对象设置了propKey属性的get函数时,receiver对象会绑定get函数的this对象。
2set(target, propKey, value, receiver):拦截对象属性的设置,比如`proxy.foo = v``proxy['foo'] = v`,返回一个布尔值。
3has(target, propKey):拦截`propKey in proxy`的操作,返回一个布尔值。
4deleteProperty(target, propKey) :拦截`delete proxy[propKey]`的操作,返回一个布尔值。
5enumerate(target):拦截`for (var x in proxy)`,返回一个遍历器。
6hasOwn(target, propKey):拦截`proxy.hasOwnProperty('foo')`,返回一个布尔值。
7ownKeys(target):拦截`Object.getOwnPropertyNames(proxy)``Object.getOwnPropertySymbols(proxy)``Object.keys(proxy)`,返回一个数组。该方法返回对象所有自身的属性,而`Object.keys()`仅返回对象可遍历的属性。
8getOwnPropertyDescriptor(target, propKey) :拦截`Object.getOwnPropertyDescriptor(proxy, propKey)`,返回属性的描述对象。
9defineProperty(target, propKey, propDesc):拦截`Object.defineProperty(proxy, propKey, propDesc``Object.defineProperties(proxy, propDescs)`,返回一个布尔值。
10preventExtensions(target):拦截`Object.preventExtensions(proxy)`,返回一个布尔值。
11getPrototypeOf(target) :拦截`Object.getPrototypeOf(proxy)`,返回一个对象。
12isExtensible(target):拦截`Object.isExtensible(proxy)`,返回一个布尔值。
13setPrototypeOf(target, proto):拦截`Object.setPrototypeOf(proxy, proto)`,返回一个布尔值。
如果目标对象是函数,那么还有两种额外操作可以拦截。
- apply方法:拦截Proxy实例作为函数调用的操作,比如proxy(···)、proxy.call(···)、proxy.apply(···)
- construct方法:拦截Proxy实例作为构造函数调用的操作,比如new proxy(···)。
14apply(target, object, args):拦截Proxy实例作为函数调用的操作,比如`proxy(...args)``proxy.call(object, ...args)``proxy.apply(...)`
### get
15construct(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)`让它们变成了函数行为。
4Reflect对象的方法与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。
1Reflect.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);
```
2Reflect.set(target, name, value, receiver)
设置target对象的name属性等于value。如果name属性设置了赋值函数,则赋值函数的this绑定receiver。
3Reflect.has(obj, name)
等同于`name in obj`
4Reflect.deleteProperty(obj, name)
等同于`delete obj[name]`
5Reflect.construct(target, args)
等同于`new target(...args)`,这提供了一种不使用new,来调用构造函数的方法。
6Reflect.getPrototypeOf(obj)
读取对象的\_\_proto\_\_属性,等同于`Object.getPrototypeOf(obj)`
7Reflect.setPrototypeOf(obj, newProto)
设置对象的\_\_proto\_\_属性。注意,Object对象没有对应这个方法的方法。
8Reflect.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
// 失败时抛出错误
+4
View File
@@ -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
+246 -25
View File
@@ -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;
```