编码规范
判空
- object -> true
- Undefined -> false
- Null -> false
- Booleans ->
- Number -> +0,-0,NaN:false, 其他值:true
- String -> 空字符串’’ : flase,其他值:true
简化判空操作
1 | // bad |
注意:空对象
{}
、空数组[]
均会返回true
let、var、const
- 变量用
let
声明,常量用const
声明,避免使用var
let
、const
都是块级作用域
避免不必要的三元表达式
1 | // bad |
不要直接调用Object.prototype
上的方法,比如,hasOwnProperty
,propertyIsEnumerable
和isPrototypeOf
Object.prototype
上的方法可能会被错误的覆盖
1 | // bad |
call
、apply
、bind
每个函数都包含两个非继承而来的方法:call()
和apply()
。这两个函数的用途都是在特定的作用域中调用函数,等同于设置函数体内this
对象的值。
apply()
方法接收两个参数,一个是函数运行的作用域,另一个是参数数组。第二个参数可以是Array
类型,也可以是arguments
对象。
1 | function sum (a, b) { |
call()
方法与apply()
的作用相同,区别在于传递给函数的参数必须逐个列举出来
1 | function callSum (a, b) { |
应用:
1 | window.color = 'red' |
bind()
函数在 ES5 中定义的,这个方法会创建一个函数的实例,其this
值会被绑定到传给bind()
函数的值。
1 | window.color = 'red' |
表达式解构
本质上,这种写法是一种”模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值,解构不成功变量的值等于 undefined。
- 数组解构赋值
1 | // 基本用法 |
指定默认值
1 | let [a, b] = [1] |
注意:ES6 中使用严格相等运算符
===
判断是否相等,所以只有当一个数组成员严格等于 undefined 时默认值才会生效。例如,有一个数组成员为 null 则默认值会生效,匹配后的值为 null。
对象解构赋值
对象解构与数组解构一个重要的不同在于,数组元素按顺序排列,变量的取值由位置决定,而对象的属性没有顺序,变量名必须与属性名相同,才能取到值。
1 | let {key1, key2} = {key1: 'first key', key2: 'second key'} |
嵌套对象解构
1 | let {a, b: {c}} = {a: 1, b: {c: 3}} |
- 函数参数解构赋值
1 | // bad |
- 函数需返回多个值且不在关心顺序时,使用对象解构返回,而不是数组。
1 | // bad |
箭头函数
- 几种常见写法
1 | // 只有一个参数可省略括号 |
- 箭头函数的上下文(
this
)会绑定为定义函数所在作用域的上下文
1 | let obj = { |
上面的代码等价于:
1 | let obj = { |
注意:箭头函数对上下文的绑定是强制性的,无法通过
apply
和call
改变其上下文。
- 使用匿名函数时,使用箭头函数定义。
箭头函数的上下文绑定特性,绑定的
this
通常都是我们希望的。
1 | // bad |
尽量使用高阶函数代替 for-in
和 for-of
高阶函数相比
for-in
和for-of
具有更高的可读性,且减少了一些边界条件的考虑。
1 | const numbers = [1, 2, 3, 4, 5] |
模块化编程
在 ES6 之前,javaScript 一直没有模块体系,无法将一个大程序拆分成互相依赖的小文件,因此javaScript 社区制定了一些模块化加载方案,最主要的就是 CommonJS
和 AMD
前者用于服务器,后者用于浏览器。ES6 在语言层面上实现了模块功能,而且相当简单完全可以取代 CommonJS
和 AMD
规范。
ES6 模块
ES6 模块自动采用严格模式,不管是否在模块头部加上 "use strict"
export 命令
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
一个模块就是一个单独的文件,文件内定义的所有变量都是私有的,外部无法获取。如果希望外部读取模块的某个变量,只能通过 export
导出改变量。
例:
1 | // moduleA.js |
除了上述写法一个一个导出变量,还有另外一种写法:
1 | let name = 'first name' |
export
命令除了导出变量,还可以导出函数或类
1 | export function sayHello () { |
一般情况导出的变量就是本来定义的名字,如果希望重命名可以通过 as
关键字实现
1 | let name = 'first name' |
import 命令
使用 export
定义了模块的接口后,其他 JS 文件可通过 import
命令引入这个模块
1 | // main.js |
import
命令接受一对大括号,在括号里指定要从其他模块导入的变量名,必须与模块中导出的名称相同。
如果想对 import
的变量重新命名,可通过 as
关键字实现:
1 | import { name as firstName } from './moduleA.js' |
import
命名是在编译阶段执行的,所以不能使用表达式和变量,这种在运行时才能得到结果的语法结构。
1 | // 错误 |
除了加载某个指定的输出值,还可通过 *
将模块的所有输出导出到一个对象上
1 | import * as obj from './moduleA.js' |
export default 命令
export default
为模块指定默认输出,一个文件或模块中只能有一个 export default
1 | // export-default.js |
import
引入模块默认输出是不需要大括号,且可以任意指定模块默认输出的名称,不需要知道原模块中输出的名称
1 | import say from './export-default.js' |
本质上 export default
就是输出一个叫做 default
的变量或者方法,所以export default
后面不能跟变量申明语句,直接导出变量表达式。
1 | // 正确 |
export 和 export default 区别
- export与export default均可用于导出常量、函数、文件、模块等
- 在一个文件或模块中,export、import可以有多个,export default仅有一个
- 通过export方式导出,在导入时要加{ },export default则不需要
- export能直接导出变量表达式,export default不行
CommonJS
CommonJS 是以在浏览器环境之外构建 JavaScript 生态系统为目标而产生的项目,比如服务器和桌面应用。
CommonJS 是一种规范,为了解决 JavaScript 作用域问题而定义的模块形式。可以时每个模块在自身的命名空间中执行。模块必须通过module.exports
到处对外的变量或接口,荣拓require()
来导入模块。node.js 模块系统即参照 CommonJS 规范实现的。
浏览器不兼容 CommonJS 的根本原因是缺少四个 Node.js 的环境变量:
- module
- exports
- require
- global
要在浏览器环境兼容 CommonJS 可以使用一些转换工作,如 Browserify
, Browserify
是最常用的 CommonJS 格式转换工具。
例:
1 | // moduleA.js |
1 | // moduleB.js |
CommonJS 是同步加载模块
AMD
AMD 是为浏览器环境设计的,定义了一套 JavaScript 模块依赖异步加载标准,来解决同步加载的问题。
模块通过 define
函数定义在闭包中,格式如下:
1 | define(id?: String, dependencies?: String[], factory: Function|Object) |
id
是模块的名字,可选参数。dependencies
指定了所需要依赖的模块列表,是一个数组,也是可选参数。每个模块的输出将作为参数一次传入 factory
。如果没有指定 dependencies
它的默认值是 ["require", "exports", "module"]
factory
是模块的具体实现,可以是一个函数或一个对象。如果是函数,那么函数的返回值就是模块的输出接口或值。
定义一个模块,依赖jQuery
,如下:
1 | define('myModule', ["jQuery"], function ($) { |
在模块内部引用依赖
1 | define('myModule', function (require) { |
具体应用可看 RequireJS
模块化编码建议
- 总是使用
import/export
,不再使用CommonJS
、AMD
等模块化方案。 不用通配符
import
1
2
3
4
5// bad
import * as Module from './moduleA'
// good
import Module from './moduleA'从同一个 module
import
多个值写在一行1
2
3
4
5
6
7
8
9
10
11
12// bad
import foo from 'foo'
import { named1, named2 } from 'foo'
// good
import foo, { named1, named2 } from 'foo'
// good
import foo, {
named1,
named2,
} from 'foo'
只导出常量,不要导出可变绑定。
1
2
3
4
5
6
7// bad
let foo = 3;
export { foo }
// good
const foo = 3;
export { foo }
Promise
Promise
是 JavaScript
异步编程的一种解决方案,ES6 将其写入了语言标准,提供了原生的Promise
对象。
Promise
设计上具有原子性,即只有两种状态,未开始和结束(成功和失败均是结束),也就是说 Promise
一但开始便不能中途取消,会得到一个结束状态成功或失败。未开始、成功、失败这三个状态分别用 pending
、fulfilled
和rejected
表示。
基本用法
Promise
构造函数接收一个函数参数,函数有两个参数resolve
和reject
,resolve
用于将Promise
对象的状态从未开始 变为 成功,reject
用于将Promise
对象的状态从 未开始 变为 失败。要创建一个异步函数只需返回一个Promise
对象即可。
如下,创建一个异步函数:
1 | function fetchData() { |
Promise
的参数建议用箭头函数来定义,利用箭头函数的上下文绑定将this
绑定为函数所在的作用域,这通常是我们所期望的。
创建异步函数后,调用时可用.then()
来添加resolve
时的回调函数,.catch
添加reject
时的回调函数。resolve
中的参数会传递给.then()
添加的回调函数,同理reject
的参数亦会传递给.catch()
添加的回调函数。
1 | function fetchData() { |
注:
resolve
和reject
并不会终止Promise
的函数的执行。
1 | new Promise((resolve, reject) => { |
链式调用
.then()
方法返回的是一个新的Promise
对象(注意不是原来的那个),因此我们可以用链式写法添加多个.then()
处理函数,多个.then()
会添加顺序依次调用。.then()
中传递参数给下一个.then()
只需 return
对应的值即可。
1 | function fetchData() { |
同理,可以链式添加多个.catch()
,.catch()
中可以抛出错误被后续的.catch()
捕获。
1 | function fetchData() { |
注:
.catch()
不仅会捕获reject
的错误信息,.then()
中出现错误一会被.catch()
捕获。
Promise.prototype.finally()
.finally()
方法用于添加不管Promise
状态如何都会执行的操作。.finally()
回调函数不接受任何参数,即无法知道Promise
的状态是fulfilled
还是rejected
,故.finally()
中的操作不应该包含于状态有关的逻辑。
1 | function fetchData() { |
Promise.all()
Promise.all()
方法用于将多个 Promise
实例包装成一个Promise
实例。Promise.all()
接收一个数组作为参数,数组中的元素必须是Promise
实例,如果不是会调用Promise.resolve
方法将参数转换为Promise
实例,再做进一步的处理。
1 | const p = Promise.all([p1, p2, p3]) |
一个简单的例子如上,p1、p2、p3均为Promise
实例,p的状态有以下两种情况:
p1、p2、p3 的状态均为
fulfilled
时,p 的状态才会变成fulfilled
,此时p1、p2、p3的返回值组成一个数组传给 p 的.then()
回调函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const p1 = new Promise((resolve, reject) => {
resolve(1)
}).then((res) => {
return `message from p1 ${res}`
})
const p2 = new Promise((resolve, reject) => {
resolve(2)
}).then((res) => {
return `message from p2 ${res}`
})
const p3 = new Promise((resolve, reject) => {
resolve(3)
}).then((res) => {
return `message from p1 ${res}`
})
Promise.all([p1, p2, p3]).then((res) => {
console.log(res)
})
// [ 1, 2, 3]p1、p2、p3 中只要有一个状态为
rejected
,p 的状态就会变成rejected
,此时第一个reject
的实例的返回值会传递给 p 的回调函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24const p1 = new Promise((resolve, reject) => {
resolve(1)
}).then((res) => {
return res
})
const p2 = new Promise((resolve, reject) => {
reject(2)
}).then((res) => {
return res
})
const p3 = new Promise((resolve, reject) => {
reject(3)
}).then((res) => {
return res
})
Promise.all([p1, p2, p3]).then((res) => {
console.log(res)
}).catch((error) => {
console.log(error)
})
// 2
注:只有p1、p2、p3中
reject
的按个实例没有.catch()
方法时才会调用Promise.all()
的catch
方法。
1 | const p1 = new Promise((resolve, reject) => { |
Promise.race()
Promise.race()
与 Promise.all()
类似,将多个 Promise 实例,包装成一个新的 Promise
实例。
不同之处在于,只要参数中有一个实例的状态改变了,Promise.race()
的状态就会耕者改变,率先改变的 Promise
实例返回值传递给Promise.race()
的回调函数。
1 | const p1 = new Promise((resolve, reject) => { |