语言规范

语言规范

JavaScript 是一种客户端脚本语言,这里列出了编写 JavaScript 时需要遵守的规则。

类型

  • 原始类型: 存取原始类型直接作用于值本身

    • 布尔类型
    • Null 类型
    • Undefined 类型
    • 数字类型
    • BigInt 类型
    • 字符串类型
    • 符号类型 Symbol
    1. const foo = 1
    2. let bar = foo
    3. bar = 9
    4. console.log(foo, bar) // 1, 9
  • 复杂类型: 访问复杂类型作用于值的引用

    • object
    • array
    • function
    1. const foo = [1, 2, 3]
    2. const bar = foo
    3. bar[0] = 9
    4. console.log(foo[0], bar[0]) // 9, 9

引用

  • 请记得 constlet 都是块级作用域,var 是函数级作用域
  1. // const and let only exist in the blocks they are defined in.
  2. {
  3. let a = 1
  4. const b = 1
  5. }
  6. console.log(a) // ReferenceError
  7. console.log(b) // ReferenceError

原因:这样做可以确保你无法重新分配引用,以避免出现错误和难以理解的代码

  1. // bad
  2. var a = 1
  3. var b = 2
  4. // good
  5. const a = 1
  6. const b = 2
  • 如果引用是可变动的,使用 let 代替 var,eslint: no-var

原因:let 是块级作用域的,而不像 var 属于函数级作用域

  1. // bad
  2. var count = 1
  3. if (count < 10) {
  4. count += 1
  5. }
  6. // good
  7. let count = 1
  8. if (count < 10) {
  9. count += 1
  10. }

对象

  • 请使用字面量值创建对象,eslint: no-new-object

    1. // bad
    2. const a = new Object{}
    3. // good
    4. const a = {}
  • 别使用保留字作为对象的键值,这样在 IE8 下不会运行

    1. // bad
    2. const a = {
    3. default: {}, // default 是保留字
    4. common: {}
    5. }
    6. // good
    7. const a = {
    8. defaults: {},
    9. common: {}
    10. }
  • 当使用动态属性名创建对象时,请使用对象计算属性名来进行创建

原因:因为这样做就可以让你在一个地方定义所有的对象属性

  1. function getKey(k) {
  2. return `a key named ${k}`
  3. }
  4. // bad
  5. const obj = {
  6. id: 5,
  7. name: 'San Francisco'
  8. };
  9. obj[getKey('enabled')] = true
  10. // good
  11. const obj = {
  12. id: 5,
  13. name: 'San Francisco',
  14. [getKey('enabled')]: true
  15. };
  • 请使用对象方法的简写方式,eslint: object-shorthand

    1. // bad
    2. const item = {
    3. value: 1,
    4. addValue: function (val) {
    5. return item.value + val
    6. }
    7. }
    8. // good
    9. const item = {
    10. value: 1,
    11. addValue (val) {
    12. return item.value + val
    13. }
    14. }
  • 请使用对象属性值的简写方式,eslint: object-shorthand

    原因:这样更简短且描述更清楚

    1. const job = 'FrontEnd'
    2. // bad
    3. const item = {
    4. job: job
    5. }
    6. // good
    7. const item = {
    8. job
    9. }
  • 将简写的对象属性分组后统一放到对象声明的开头

原因:这样更容易区分哪些属性用了简写的方式

  1. const job = 'FrontEnd'
  2. const department = 'JDC'
  3. // bad
  4. const item = {
  5. sex: 'male',
  6. job,
  7. age: 25,
  8. department
  9. }
  10. // good
  11. const item = {
  12. job,
  13. department,
  14. sex: 'male',
  15. age: 25
  16. }
  • 只对非法标识符的属性使用引号,eslint: quote-props

原因:因为通常来说我们认为这样主观上会更容易阅读,这样会带来代码高亮上的提升,同时也更容易被主流 JS 引擎优化

  1. // bad
  2. const bad = {
  3. 'foo': 3,
  4. 'bar': 4,
  5. 'data-blah': 5
  6. }
  7. // good
  8. const good = {
  9. foo: 3,
  10. bar: 4,
  11. 'data-blah': 5
  12. }
  • 不要直接使用 Object.prototype 的方法, 例如 hasOwnProperty, propertyIsEnumerableisPrototypeOf 方法,eslint: no-prototype-builtins

    原因:这些方法可能会被对象自身的同名属性覆盖 - 比如 { hasOwnProperty: false } 或者对象可能是一个 null 对象(Object.create(null))

  1. // bad
  2. console.log(object.hasOwnProperty(key))
  3. // good
  4. console.log(Object.prototype.hasOwnProperty.call(object, key))
  5. // best
  6. const has = Object.prototype.hasOwnProperty // cache the lookup once, in module scope.
  7. console.log(has.call(object, key))
  8. /* or */
  9. import has from 'has' // https://www.npmjs.com/package/has
  10. console.log(has(object, key))
  • 优先使用对象展开运算符 ... 来做对象浅拷贝而不是使用 Object.assign,使用对象剩余操作符来获得一个包含确定的剩余属性的新对象
  1. // very bad
  2. const original = { a: 1, b: 2 }
  3. const copy = Object.assign(original, { c: 3 }) // this mutates `original` ಠ_ಠ
  4. delete copy.a // so does this
  5. // bad
  6. const original = { a: 1, b: 2 }
  7. const copy = Object.assign({}, original, { c: 3 }) // copy => { a: 1, b: 2, c: 3 }
  8. // good
  9. const original = { a: 1, b: 2 }
  10. const copy = { ...original, c: 3 } // copy => { a: 1, b: 2, c: 3 }
  11. const { a, ...noA } = copy // noA => { b: 2, c: 3 }

数组

  • 请使用字面量值创建数组,eslint: no-array-constructor

    1. // bad
    2. const items = new Array()
    3. // good
    4. const items = []
  • 向数组中添加元素时,请使用 push 方法

    1. const items = []
    2. // bad
    3. items[items.length] = 'test'
    4. // good
    5. items.push('test')
  • 使用展开运算符 ... 复制数组

    1. // bad
    2. const items = []
    3. const itemsCopy = []
    4. const len = items.length
    5. let i
    6. // bad
    7. for (i = 0; i < len; i++) {
    8. itemsCopy[i] = items[i]
    9. }
    10. // good
    11. itemsCopy = [...items]
  • 把一个可迭代的对象转换为数组时,使用展开运算符 ... 而不是 Array.from

  1. const foo = document.querySelectorAll('.foo')
  2. // good
  3. const nodes = Array.from(foo)
  4. // best
  5. const nodes = [...foo]
  • 使用 Array.from 来将一个类数组对象转换为数组
  1. const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 }
  2. // bad
  3. const arr = Array.prototype.slice.call(arrLike)
  4. // good
  5. const arr = Array.from(arrLike)
  • 遍历迭代器进行映射时使用 Array.from 代替扩展运算符 ..., 因为这可以避免创建中间数组
  1. // bad
  2. const baz = [...foo].map(bar)
  3. // good
  4. const baz = Array.from(foo, bar)
  • 使用数组的 map 等方法时,请使用 return 声明,如果是单一声明语句的情况,可省略 return

    1. // good
    2. [1, 2, 3].map(x => {
    3. const y = x + 1
    4. return x * y
    5. })
    6. // good
    7. [1, 2, 3].map(x => x + 1)
    8. // bad
    9. const flat = {}
    10. [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
    11. const flatten = memo.concat(item)
    12. flat[index] = flatten
    13. })
    14. // good
    15. const flat = {}
    16. [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
    17. const flatten = memo.concat(item)
    18. flat[index] = flatten
    19. return flatten
    20. })
    21. // bad
    22. inbox.filter((msg) => {
    23. const { subject, author } = msg
    24. if (subject === 'Mockingbird') {
    25. return author === 'Harper Lee'
    26. } else {
    27. return false
    28. }
    29. })
    30. // good
    31. inbox.filter((msg) => {
    32. const { subject, author } = msg
    33. if (subject === 'Mockingbird') {
    34. return author === 'Harper Lee'
    35. }
    36. return false
    37. })
  • 如果一个数组有多行则要在数组的开括号后和闭括号前使用新行

  1. // bad
  2. const arr = [
  3. [0, 1], [2, 3], [4, 5]
  4. ]
  5. const objectInArray = [{
  6. id: 1
  7. }, {
  8. id: 2
  9. }]
  10. const numberInArray = [
  11. 1, 2
  12. ]
  13. // good
  14. const arr = [[0, 1], [2, 3], [4, 5]]
  15. const objectInArray = [
  16. {
  17. id: 1
  18. },
  19. {
  20. id: 2
  21. }
  22. ]
  23. const numberInArray = [
  24. 1,
  25. 2
  26. ]

解构赋值

愿意:解构可以避免创建属性的临时引用

  1. // bad
  2. function getFullName (user) {
  3. const firstName = user.firstName
  4. const lastName = user.lastName
  5. return `${firstName} ${lastName}`
  6. }
  7. // good
  8. function getFullName (user) {
  9. const { firstName, lastName } = user
  10. return `${firstName} ${lastName}`
  11. }
  12. // better
  13. function getFullName ({ firstName, lastName }) {
  14. return `${firstName} ${lastName}`
  15. }
  • 当需要使用数组的多个值时,请同样使用解构赋值,eslint: prefer-destructuring

    1. const arr = [1, 2, 3, 4]
    2. // bad
    3. const first = arr[0]
    4. const second = arr[1]
    5. // good
    6. const [first, second] = arr
  • 函数需要回传多个值时,请使用对象的解构,而不是数组的解构

原因:可以非破坏性地随时增加或者改变属性顺序

  1. // bad
  2. function doSomething () {
  3. return [top, right, bottom, left]
  4. }
  5. // 如果是数组解构,那么在调用时就需要考虑数据的顺序
  6. const [top, xx, xxx, left] = doSomething()
  7. // good
  8. function doSomething () {
  9. return { top, right, bottom, left }
  10. }
  11. // 此时不需要考虑数据的顺序
  12. const { top, left } = doSomething()

字符串

  • 字符串统一使用单引号的形式 '',eslint: quotes

    1. // bad
    2. const department = "JDC"
    3. // good
    4. const department = 'JDC'
  • 字符串太长的时候,请不要使用字符串连接符换行 \,而是使用 +

    1. const str = '凹凸实验室 凹凸实验室 凹凸实验室' +
    2. '凹凸实验室 凹凸实验室 凹凸实验室' +
    3. '凹凸实验室 凹凸实验室'
  • 程序化生成字符串时,请使用模板字符串,eslint: prefer-template template-curly-spacing

    1. const test = 'test'
    2. // bad
    3. const str = ['a', 'b', test].join()
    4. // bad
    5. const str = 'a' + 'b' + test
    6. // good
    7. const str = `ab${test}`
  • 不要对字符串使用eval(),会导致太多漏洞, eslint: no-eval

  • 不要在字符串中使用不必要的转义字符, eslint: no-useless-escape

  1. // bad
  2. const foo = '\'this\' \i\s \"quoted\"'
  3. // good
  4. const foo = '\'this\' is "quoted"'
  5. const foo = `my name is '${name}'`

函数

  • 不要使用Function构造函数创建函数, eslint: no-new-func

原因:此方式创建函数和对字符串使用 eval() 一样会产生漏洞

  1. // bad
  2. const add = new Function('a', 'b', 'return a + b')
  3. // still bad
  4. const subtract = Function('a', 'b', 'return a - b')
  1. const f = function(){}
  2. const g = function (){}
  3. const h = function() {}
  4. // good
  5. const x = function b () {}
  6. const y = function a () {}
  • 使用具名函数表达式而非函数声明,eslint: func-style

原因:这样做会导致函数声明被提升,这意味着很容易在文件中定义此函数之前引用它,不利于可读性和可维护性。如果你发现函数定义既庞大又复杂以至于不能理解文件的其他部分,或许你应该将它拆分成模块!别忘记要显式命名表达式,而不用管名字是否是从包含的变量(通常出现在现代浏览器中或者使用 Babel 编译器的时候)中推断的。这样会消除错误调用堆栈中的任何假设。 (讨论)

  1. // bad
  2. function foo () {
  3. // ...
  4. }
  5. // bad
  6. const foo = function () {
  7. // ...
  8. }
  9. // good
  10. // lexical name distinguished from the variable-referenced invocation(s)
  11. const short = function longUniqueMoreDescriptiveLexicalFoo () {
  12. // ...
  13. }
  • 用圆括号包裹自执行匿名函数,eslint:wrap-iife

原因:一个立即执行匿名函数表达式是一个单一的单元,将其及其调用括号包装在括号中,能够清楚地表达这一点。注意,在到处都是模块的世界中几乎不需要 IIFE。

  1. // immediately-invoked function expression (IIFE)
  2. (function () {
  3. console.log('Welcome to the Internet. Please follow me.')
  4. }())
  • 不要在非函数代码块(if , while 等)中声明函数,eslint:no-loop-func

    1. // bad
    2. if (isUse) {
    3. function test () {
    4. // do something
    5. }
    6. }
    7. // good
    8. let test
    9. if (isUse) {
    10. test = () => {
    11. // do something
    12. }
    13. }
  • 不要将参数命名为 arguments,会导致该参数的优先级高于每个函数作用域内原先存在的 arguments 对象

  1. // bad
  2. function foo (name, options, arguments) {
  3. // ...
  4. }
  5. // good
  6. function foo (name, options, args) {
  7. // ...
  8. }
  • 不要使用 arguments,使用 剩余运算符 ...

    arguments 只是一个类数组,而 ... 是一个真正的数组

    1. // bad
    2. function test () {
    3. const args = Array.prototype.slice.call(arguments)
    4. return args.join('')
    5. }
    6. // good
    7. function test (...args) {
    8. return args.join('')
    9. }
  • 使用参数默认值语法而不是修改函数参数

  1. // really bad
  2. function handleThings (opts) {
  3. // No! We shouldn't mutate function arguments.
  4. // Double bad: if opts is falsy it'll be set to an object which may
  5. // be what you want but it can introduce subtle bugs.
  6. opts = opts || {}
  7. // ...
  8. }
  9. // still bad
  10. function handleThings (opts) {
  11. if (opts === void 0) {
  12. opts = {}
  13. }
  14. // ...
  15. }
  16. // good
  17. function handleThings (opts = { }) {
  18. // ...
  19. }
  • 避免参数默认值的副作用
  1. let b = 1
  2. // bad
  3. function count (a = b++) {
  4. console.log(a)
  5. }
  6. count() // 1
  7. count() // 2
  8. count(3) // 3
  9. count() // 3
  • 将参数默认值放在最后
  1. // bad
  2. function handleThings (opts = {}, name) {
  3. // ...
  4. }
  5. // good
  6. function handleThings (name, opts = {}) {
  7. // ...
  8. }

原因:操作作为参数传入的对象可能在原始调用中造成意想不到的变量副作用

  1. // bad
  2. function f1 (obj) {
  3. obj.key = 1
  4. }
  5. // good
  6. function f2 (obj) {
  7. const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1
  8. }

原因:参数重新赋值可能会导致无法预期的行为,尤其是当操作 arguments 对象时,也可能导致优化问题,尤其是在 V8 引擎中

  1. // bad
  2. function f1 (a) {
  3. a = 1
  4. }
  5. function f2 (a) {
  6. if (!a) { a = 1 }
  7. }
  8. // good
  9. function f3 (a) {
  10. const b = a || 1
  11. }
  12. function f4 (a = 1) {
  13. }
  • 调用可变参数函数时建议使用展开运算符 ...., eslint: prefer-spread

原因:显然你无需使用上下文,很难结合 newapply

  1. // bad
  2. const x = [1, 2, 3, 4, 5]
  3. console.log.apply(console, x)
  4. // good
  5. const x = [1, 2, 3, 4, 5]
  6. console.log(...x)
  7. // bad
  8. new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]))
  9. // good
  10. new Date(...[2016, 8, 5])

箭头函数

原因:它将创建在 this 上下文中执行的函数版本,通常是您想要的,并且语法更简洁

如果您有一个相当复杂的函数,则可以将该逻辑移到其自己的命名函数表达式中

  1. // bad
  2. [1, 2, 3].map(function (x) {
  3. const y = x + 1
  4. return x * y
  5. })
  6. // good
  7. [1, 2, 3].map((x) => {
  8. const y = x + 1
  9. return x * y
  10. })
  • 如果函数体只包含一条没有副作用的返回表达式的语句,可以省略花括号并使用隐式的 return, 否则保留花括号并使用 return 语句,eslint: arrow-parens, arrow-body-style
  1. // bad
  2. [1, 2, 3].map(number => {
  3. const nextNumber = number + 1
  4. `A string containing the ${nextNumber}.`
  5. })
  6. // good
  7. [1, 2, 3].map(number => `A string containing the ${number}.`)
  8. // good
  9. [1, 2, 3].map((number) => {
  10. const nextNumber = number + 1
  11. return `A string containing the ${nextNumber}.`
  12. })
  13. // good
  14. [1, 2, 3].map((number, index) => ({
  15. index: number
  16. }))
  17. // No implicit return with side effects
  18. function foo(callback) {
  19. const val = callback()
  20. if (val === true) {
  21. // Do something if callback returns true
  22. }
  23. }
  24. let bool = false
  25. // bad
  26. foo(() => bool = true)
  27. // good
  28. foo(() => {
  29. bool = true
  30. })
  • 一旦表达式跨多行,使用圆括号包裹以便更好阅读
  1. // bad
  2. ['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call(
  3. httpMagicObjectWithAVeryLongName,
  4. httpMethod
  5. )
  6. )
  7. // good
  8. ['get', 'post', 'put'].map(httpMethod => (
  9. Object.prototype.hasOwnProperty.call(
  10. httpMagicObjectWithAVeryLongName,
  11. httpMethod
  12. )
  13. ))
  • 函数如果只接收一个参数并且没使用用花括号,则省略圆括号,否则为了清晰明确则使用圆括号包裹参数,注意:总是使用圆括号也是可以接受的,eslint 中的 “always” 选项,eslint: arrow-parens
  1. // bad
  2. [1, 2, 3].map((x) => x * x)
  3. // good
  4. [1, 2, 3].map(x => x * x)
  5. // good
  6. [1, 2, 3].map(number => (
  7. `A long string with the ${number}. Its so long that weve broken it ` +
  8. 'over multiple lines!'
  9. ))
  10. // bad
  11. [1, 2, 3].map(x => {
  12. const y = x + 1
  13. return x * y
  14. })
  15. // good
  16. [1, 2, 3].map((x) => {
  17. const y = x + 1
  18. return x * y
  19. })

类&构造函数

  • 使用 class,避免直接操作 prototype

    1. // bad
    2. function Queue (contents = []) {
    3. this._queue = [..contents]
    4. }
    5. Queue.prototype.pop = function () {
    6. const value = this._queue[0]
    7. this._queue.splice(0, 1)
    8. return value
    9. }
    10. // good
    11. class Queue {
    12. constructor (contents = []) {
    13. this._queue = [...contents]
    14. }
    15. pop () {
    16. const value = this._queue[0]
    17. this._queue.splice(0, 1)
    18. return value
    19. }
    20. }
  • 使用 extends 来实现继承

原因:这是一个不会破坏 instanceof 的内建实现原型式继承的方式

  1. // bad
  2. const inherits = require('inherits')
  3. function PeekableQueue(contents) {
  4. Queue.apply(this, contents)
  5. }
  6. inherits(PeekableQueue, Queue)
  7. PeekableQueue.prototype.peek = function () {
  8. return this.queue[0]
  9. }
  10. // good
  11. class PeekableQueue extends Queue {
  12. peek () {
  13. return this.queue[0]
  14. }
  15. }
  • 如果未声明构造函数,则类会有一个默认的构造函数,没必要用空的构造函数或者将其委托给父类,eslint: no-useless-constructor
  1. // bad
  2. class Jedi {
  3. constructor () {}
  4. getName() {
  5. return this.name
  6. }
  7. }
  8. // bad
  9. class Rey extends Jedi {
  10. constructor (...args) {
  11. super(...args)
  12. }
  13. }
  14. // good
  15. class Rey extends Jedi {
  16. constructor (...args) {
  17. super(...args)
  18. this.name = 'Rey'
  19. }
  20. }

原因:重复的类成员声明会默认使用最后声明的,通常会导致 bug

  1. // bad
  2. class Foo {
  3. bar () { return 1 }
  4. bar () { return 2 }
  5. }
  6. // good
  7. class Foo {
  8. bar () { return 1 }
  9. }
  10. // good
  11. class Foo {
  12. bar () { return 2 }
  13. }

模块

  • 使用标准的 ES6 模块语法 importexport

原因:模块是未来,让我们现在开始使用未来的特性

  1. // bad
  2. const util = require('./util')
  3. module.exports = util
  4. // good
  5. import Util from './util'
  6. export default Util
  7. // better
  8. import { Util } from './util'
  9. export default Util
  • 不要使用 import 的通配符 *,这样可以确保你只有一个默认的 export

    1. // bad
    2. import * as Util from './util'
    3. // good
    4. import Util from './util'
  • 同个文件每个模块只允许 import 一次,有多个 import 请书写在一起,eslint: no-duplicate-imports

原因:这样可以让代码更易于维护

  1. // bad
  2. import foo from 'foo'
  3. // … some other imports … //
  4. import { named1, named2 } from 'foo'
  5. // good
  6. import foo, { named1, named2 } from 'foo'
  7. // good
  8. import foo, {
  9. named1,
  10. named2
  11. } from 'foo'
  1. // bad
  2. import foo from 'foo'
  3. foo.init()
  4. import bar from 'bar'
  5. // good
  6. import foo from 'foo'
  7. import bar from 'bar'
  8. foo.init()
  • 多行导入应该像多行数组和对象文字一样缩进
  1. // bad
  2. import { longNameA, longNameB, longNameC, longNameD, longNameE } from 'path'
  3. // good
  4. import {
  5. longNameA,
  6. longNameB,
  7. longNameC,
  8. longNameD,
  9. longNameE
  10. } from 'path'
  1. // bad
  2. import fooSass from 'css!sass!foo.scss'
  3. import barCss from 'style!css!bar.css'
  4. // good
  5. import fooSass from 'foo.scss'
  6. import barCss from 'bar.css'

迭代器

  • 不要使用 iterators,建议使用 JS 更高优先级的函数代替 for-in 或 for-of 循环,除非迫不得已,eslint: no-iterator no-restricted-syntax
  1. const numbers = [1, 2, 3, 4, 5]
  2. // bad
  3. let sum = 0
  4. for (let num of numbers) {
  5. sum += num
  6. }
  7. // good
  8. let sum = 0
  9. numbers.forEach(num => sum += num)
  10. // better
  11. const sum = numbers.reduce((total, num) => total + num, 0)

生成器

  • 现阶段请不要使用生成器 generator

原因:因为不能很好地翻译成 ES5 代码

对象属性

  • 使用 . 来访问对象属性

    1. const joke = {
    2. name: 'haha',
    3. age: 28
    4. }
    5. // bad
    6. const name = joke['name']
    7. // good
    8. const name = joke.name
  • 当访问的属性是变量时使用 []
  1. const luke = {
  2. jedi: true,
  3. age: 28,
  4. }
  5. function getProp (prop) {
  6. return luke[prop]
  7. }
  8. const isJedi = getProp('jedi')

变量声明

  • 声明变量时,请使用 constlet 关键字,如果没有写关键字,变量就会暴露在全局上下文中,这样很可能会和现有变量冲突,另外,也很难明确该变量的作用域是什么。这里推荐使用 const 来声明变量,我们需要避免全局命名空间的污染。eslint: no-undef prefer-const

    1. // bad
    2. demo = new Demo()
    3. // good
    4. const demo = new Demo()
  • 将所有的 constlet 分组

    1. // bad
    2. let a
    3. const b
    4. let c
    5. const d
    6. let e
    7. // good
    8. const b
    9. const d
    10. let a
    11. let c
    12. let e
  • 变量不要进行链式赋值

原因:变量链式赋值会创建隐藏的全局变量

  1. // bad
  2. (function example() {
  3. // JavaScript interprets this as
  4. // let a = ( b = ( c = 1 ) );
  5. // The let keyword only applies to variable a; variables b and c become
  6. // global variables.
  7. let a = b = c = 1
  8. }())
  9. console.log(a) // throws ReferenceError
  10. console.log(b) // 1
  11. console.log(c) // 1
  12. // good
  13. (function example() {
  14. let a = 1
  15. let b = a
  16. let c = a
  17. }())
  18. console.log(a) // throws ReferenceError
  19. console.log(b) // throws ReferenceError
  20. console.log(c) // throws ReferenceError
  21. // the same applies for `const`

原因:声明但未被使用的变量通常是不完全重构犯下的错误.这种变量在代码里浪费空间并会给读者造成困扰

  1. // bad
  2. var some_unused_var = 42
  3. // Write-only variables are not considered as used.
  4. var y = 10
  5. y = 5
  6. // A read for a modification of itself is not considered as used.
  7. var z = 0
  8. z = z + 1
  9. // Unused function arguments.
  10. function getX (x, y) {
  11. return x
  12. }
  13. // good
  14. function getXPlusY (x, y) {
  15. return x + y
  16. }
  17. const x = 1
  18. const y = a + 2
  19. alert(getXPlusY(x, y))
  20. // 'type' is ignored even if unused because it has a rest property sibling.
  21. // This is a form of extracting an object that omits the specified keys.
  22. const { type, ...coords } = data
  23. // 'coords' is now the 'data' object without its 'type' property.

Hoisting

  • var 存在变量提升的情况,即 var 声明会被提升至该作用域的顶部,但是他们的赋值并不会。而 constlet 并不存在这种情况,他们被赋予了 Temporal Dead Zones, TDZ, 了解 typeof 不再安全很重要

    1. function example () {
    2. console.log(notDefined) // => throws a ReferenceError
    3. }
    4. function example () {
    5. console.log(declareButNotAssigned) // => undefined
    6. var declaredButNotAssigned = true
    7. }
    8. function example () {
    9. let declaredButNotAssigned
    10. console.log(declaredButNotAssigned) // => undefined
    11. declaredButNotAssigned = true
    12. }
    13. function example () {
    14. console.log(declaredButNotAssigned) // => throws a ReferenceError
    15. console.log(typeof declaredButNotAssigned) // => throws a ReferenceError
    16. const declaredButNotAssigned = true
    17. }
  • 匿名函数的变量名会提升,但函数内容不会

    1. function example () {
    2. console.log(anonymous) // => undefined
    3. anonymous()
    4. var anonymous = function () {
    5. console.log('test')
    6. }
    7. }
  • 命名的函数表达式的变量名会被提升,但函数名和函数函数内容并不会

    1. function example() {
    2. console.log(named) // => undefined
    3. named() // => TypeError named is not a function
    4. superPower() // => ReferenceError superPower is not defined
    5. var named = function superPower () {
    6. console.log('Flying')
    7. }
    8. }
    9. function example() {
    10. console.log(named) // => undefined
    11. named() // => TypeError named is not a function
    12. var named = function named () {
    13. console.log('named')
    14. }
    15. }

比较运算符&相等

  • 使用 ===!== 而非 ==!=,eslint: eqeqeq

  • 条件声明例如 if 会用 ToBoolean 这个抽象方法将表达式转成布尔值并遵循如下规则

    • Objects 等于 true
    • Undefined 等于 false
    • Null 等于 false
    • Booleans 等于 布尔值
    • Numbers+0, -0, 或者 NaN 的情况下等于 false, 其他情况是 true
    • Strings'' 时等于 false, 否则是 true
      1. if ([0] && []) {
      2. // true
      3. // 数组(即使是空数组)也是对象,对象等于true
      4. }

分号

  • 我们遵循 Standard 的规范,不使用分号。

    关于应不应该使用分号的讨论有很多,本规范认为非必要的时候,应该不使用分号,好的 JS 程序员应该清楚场景下是一定要加分号的,相信你也是名好的开发者。

    1. // bad
    2. const test = 'good';
    3. (function () {
    4. const str = 'hahaha';
    5. })()
    6. // good
    7. const test = 'good'
    8. ;(() => {
    9. const str = 'hahaha'
    10. })();

标准特性

为了代码的可移植性和兼容性,我们应该最大化的使用标准方法,例如优先使用 string.charAt(3) 而不是 string[3]

eval()

由于 eval 方法比较 evil,所以我们约定禁止使用该方法

with() {}

由于 with 方法会产生神奇的作用域,所以我们也是禁止使用该方法的

修改内置对象的原型

不要修改内置对象,如 ObjectArray