diff --git a/docs/class.md b/docs/class.md index 70f0800..07345c6 100644 --- a/docs/class.md +++ b/docs/class.md @@ -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。 diff --git a/docs/function.md b/docs/function.md index ed88a00..558ee60 100644 --- a/docs/function.md +++ b/docs/function.md @@ -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"] - ``` 如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。 diff --git a/docs/object.md b/docs/object.md index c789f62..cec9c77 100644 --- a/docs/object.md +++ b/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 // 失败时抛出错误 diff --git a/docs/reference.md b/docs/reference.md index e5f903e..487fb4b 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -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 diff --git a/docs/style.md b/docs/style.md index 6f7aeba..30634e7 100644 --- a/docs/style.md +++ b/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; ```