写点什么

前端 JS 规范

作者:默默的成长
  • 2022-10-16
    山东
  • 本文字数:10417 字

    阅读完需:约 1 分钟



工程中的配置

在我们现在所有的工程中都配置了 eslint 校验命令:


npm run lint / npm run lintfix, 区别只是一个只做 eslint 验证,一个是会主动 fix 部分问题


引用

  • 请记得 const 和 let 都是块级作用域,var 是函数级作用域


// const and let only exist in the blocks they are defined in.{  let a = 1  const b = 1}console.log(a) // ReferenceErrorconsole.log(b) // ReferenceError
复制代码


对所有引用都使用 const,不要使用 var,eslint: prefer-const, no-const-assign 原因:这样做可以确保你无法重新分配引用,以避免出现错误和难以理解的代码


// badvar a = 1var b = 2
// goodconst a = 1const b = 2
复制代码


如果引用是可变动的,使用 let 代替 var,eslint: no-var 原因:let 是块级作用域的,而不像 var 属于函数级作用域


// badvar count = 1if (count < 10) {  count += 1}
// goodlet count = 1if (count < 10) { count += 1}
复制代码

对象

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


// badconst a = new Object{}
// goodconst a = {}别使用保留字作为对象的键值,这样在 IE8 下不会运行
// badconst a = { default: {}, // default 是保留字 common: {}}
// goodconst a = { defaults: {}, common: {}}
复制代码


当使用动态属性名创建对象时,请使用对象计算属性名来进行创建 原因:因为这样做就可以让你在一个地方定义所有的对象属性


function getKey(k) {  return `a key named ${k}`}
// badconst obj = { id: 5, name: 'San Francisco'};obj[getKey('enabled')] = true
// goodconst obj = { id: 5, name: 'San Francisco', [getKey('enabled')]: true};
复制代码


请使用对象方法的简写方式,eslint: object-shorthand


// badconst item = {  value: 1,
addValue: function (val) { return item.value + val }}
// goodconst item = { value: 1,
addValue (val) { return item.value + val }}
复制代码


请使用对象属性值的简写方式,eslint: object-shorthand 原因:这样更简短且描述更清楚


const job = 'FrontEnd'
// badconst item = { job: job}
// goodconst item = { job}
复制代码


将简写的对象属性分组后统一放到对象声明的开头 原因:这样更容易区分哪些属性用了简写的方式


const job = 'FrontEnd'const department = 'JDC'
// badconst item = { sex: 'male', job, age: 25, department}
// goodconst item = { job, department, sex: 'male', age: 25}
复制代码


只对非法标识符的属性使用引号,eslint: quote-props 原因:因为通常来说我们认为这样主观上会更容易阅读,这样会带来代码高亮上的提升,同时也更容易被主流 JS 引擎优化


// badconst bad = {  'foo': 3,  'bar': 4,  'data-blah': 5}
// goodconst good = { foo: 3, bar: 4, 'data-blah': 5}
复制代码


不要直接使用 Object.prototype 的方法, 例如 hasOwnProperty, propertyIsEnumerable 和 isPrototypeOf 方法,eslint: no-prototype-builtins 原因:这些方法可能会被对象自身的同名属性覆盖 - 比如 { hasOwnProperty: false } 或者对象可能是一个 null 对象(Object.create(null))


// badconsole.log(object.hasOwnProperty(key))
// goodconsole.log(Object.prototype.hasOwnProperty.call(object, key))
// bestconst has = Object.prototype.hasOwnProperty // cache the lookup once, in module scope.console.log(has.call(object, key))
/* or */import has from 'has' // https://www.npmjs.com/package/hasconsole.log(has(object, key))
复制代码


优先使用对象展开运算符 ... 来做对象浅拷贝而不是使用 Object.assign,使用对象剩余操作符来获得一个包含确定的剩余属性的新对象


// very badconst original = { a: 1, b: 2 }const copy = Object.assign(original, { c: 3 }) // this mutates `original` ಠ_ಠdelete copy.a // so does this
// badconst original = { a: 1, b: 2 }const copy = Object.assign({}, original, { c: 3 }) // copy => { a: 1, b: 2, c: 3 }
// goodconst original = { a: 1, b: 2 }const copy = { ...original, c: 3 } // copy => { a: 1, b: 2, c: 3 }
const { a, ...noA } = copy // noA => { b: 2, c: 3 }
复制代码

数组

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


// badconst items = new Array()
// goodconst items = []
复制代码


向数组中添加元素时,请使用 push 方法


const items = []
// baditems[items.length] = 'test'
// gooditems.push('test')
复制代码


使用展开运算符 ... 复制数组


// badconst items = []const itemsCopy = []const len = items.lengthlet i
// badfor (i = 0; i < len; i++) { itemsCopy[i] = items[i]}
// gooditemsCopy = [...items]
复制代码


把一个可迭代的对象转换为数组时,使用展开运算符 ... 而不是 Array.from


const foo = document.querySelectorAll('.foo')
// goodconst nodes = Array.from(foo)
// bestconst nodes = [...foo]使用 Array.from 来将一个类数组对象转换为数组const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 }
// badconst arr = Array.prototype.slice.call(arrLike)
// goodconst arr = Array.from(arrLike)
复制代码


遍历迭代器进行映射时使用 Array.from 代替扩展运算符 ..., 因为这可以避免创建中间数组


// badconst baz = [...foo].map(bar)
// goodconst baz = Array.from(foo, bar)
复制代码


使用数组的 map 等方法时,请使用 return 声明,如果是单一声明语句的情况,可省略 return


// good[1, 2, 3].map(x => {  const y = x + 1  return x * y})
// good[1, 2, 3].map(x => x + 1)
复制代码


// badconst flat = {}[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {  const flatten = memo.concat(item)  flat[index] = flatten})
// goodconst flat = {}[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => { const flatten = memo.concat(item) flat[index] = flatten return flatten})
复制代码


// badinbox.filter((msg) => {  const { subject, author } = msg  if (subject === 'Mockingbird') {    return author === 'Harper Lee'  } else {    return false  }})
// goodinbox.filter((msg) => { const { subject, author } = msg if (subject === 'Mockingbird') { return author === 'Harper Lee' }
return false})
复制代码


如果一个数组有多行则要在数组的开括号后和闭括号前使用新行


// badconst arr = [  [0, 1], [2, 3], [4, 5]]
const objectInArray = [{ id: 1}, { id: 2}]
const numberInArray = [ 1, 2]
// goodconst arr = [[0, 1], [2, 3], [4, 5]]
const objectInArray = [ { id: 1 }, { id: 2 }]
const numberInArray = [ 1, 2]
复制代码

解构赋值

当需要使用对象的多个属性时,请使用解构赋值,eslint: prefer-destructuring 愿意:解构可以避免创建属性的临时引用


// badfunction getFullName (user) {  const firstName = user.firstName  const lastName = user.lastName
return `${firstName} ${lastName}`}
// goodfunction getFullName (user) { const { firstName, lastName } = user
return `${firstName} ${lastName}`}
// betterfunction getFullName ({ firstName, lastName }) { return `${firstName} ${lastName}`}
复制代码


当需要使用数组的多个值时,请同样使用解构赋值,eslint: prefer-destructuring


const arr = [1, 2, 3, 4]
// badconst first = arr[0]const second = arr[1]
// goodconst [first, second] = arr
复制代码


函数需要回传多个值时,请使用对象的解构,而不是数组的解构 原因:可以非破坏性地随时增加或者改变属性顺序


// badfunction doSomething () {  return [top, right, bottom, left]}
// 如果是数组解构,那么在调用时就需要考虑数据的顺序const [top, xx, xxx, left] = doSomething()
// goodfunction doSomething () { return { top, right, bottom, left }}// 此时不需要考虑数据的顺序const { top, left } = doSomething()
复制代码

字符串

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


// badconst department = "JDC"
// goodconst department = 'JDC'
复制代码


字符串太长的时候,请不要使用字符串连接符换行 \,而是使用 + 程序化生成字符串时,请使用模板字符串,eslint: prefer-template template-curly-spacing


const test = 'test'
// badconst str = ['a', 'b', test].join()
// badconst str = 'a' + 'b' + test
// goodconst str = `ab${test}`
复制代码


不要对字符串使用 eval(),会导致太多漏洞, eslint: no-eval


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


// badconst foo = ''this' \i\s "quoted"'
// goodconst foo = ''this' is "quoted"'const foo = `my name is '${name}'`
复制代码

函数

不要使用 Function 构造函数创建函数, eslint: no-new-func 原因:此方式创建函数和对字符串使用 eval() 一样会产生漏洞


// badconst add = new Function('a', 'b', 'return a + b')
// still badconst subtract = Function('a', 'b', 'return a - b')在函数签名中使用空格,eslint: space-before-function-paren space-before-blocksconst f = function(){}const g = function (){}const h = function() {}
// goodconst x = function b () {}const y = function a () {}
复制代码


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


// badfunction foo () {  // ...}
// badconst foo = function () { // ...}
// good// lexical name distinguished from the variable-referenced invocation(s)const short = function longUniqueMoreDescriptiveLexicalFoo () { // ...}
复制代码


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


// immediately-invoked function expression (IIFE)(function () {  console.log('Welcome to the Internet. Please follow me.')}())不要在非函数代码块(if , while 等)中声明函数,eslint:no-loop-func
// badif (isUse) { function test () { // do something }}
// goodlet testif (isUse) { test = () => { // do something }}
复制代码


不要将参数命名为 arguments,会导致该参数的优先级高于每个函数作用域内原先存在的 arguments 对象


// badfunction foo (name, options, arguments) {  // ...}
// goodfunction foo (name, options, args) { // ...}
复制代码


不要使用 arguments,使用 剩余运算符 ...


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


// badfunction test () {  const args = Array.prototype.slice.call(arguments)  return args.join('')}
// goodfunction test (...args) { return args.join('')}
复制代码


使用参数默认值语法而不是修改函数参数


// really badfunction handleThings (opts) {  // No! We shouldn't mutate function arguments.  // Double bad: if opts is falsy it'll be set to an object which may  // be what you want but it can introduce subtle bugs.  opts = opts || {}  // ...}
// still badfunction handleThings (opts) { if (opts === void 0) { opts = {} } // ...}
// goodfunction handleThings (opts = { }) { // ...}
复制代码


避免参数默认值的副作用


let b = 1// badfunction count (a = b++) {  console.log(a)}count()  // 1count()  // 2count(3) // 3count()  // 3
复制代码


将参数默认值放在最后


// badfunction handleThings (opts = {}, name) {  // ...}
// goodfunction handleThings (name, opts = {}) { // ...}
复制代码


不要更改参数,eslint: no-param-reassign 原因:操作作为参数传入的对象可能在原始调用中造成意想不到的变量副作用


// badfunction f1 (obj) {  obj.key = 1}
// goodfunction f2 (obj) { const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1}
复制代码


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


// badfunction f1 (a) {  a = 1}
function f2 (a) { if (!a) { a = 1 }}
// goodfunction f3 (a) { const b = a || 1}
function f4 (a = 1) {}
复制代码


调用可变参数函数时建议使用展开运算符 ...., eslint: prefer-spread 原因:显然你无需使用上下文,很难结合 new 和 apply


// badconst x = [1, 2, 3, 4, 5]console.log.apply(console, x)
// goodconst x = [1, 2, 3, 4, 5]console.log(...x)
// badnew (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]))
// goodnew Date(...[2016, 8, 5])
复制代码

箭头函数

当你必须使用函数表达式(传递匿名函数)时,使用箭头函数标记. eslint: prefer-arrow-callback, arrow-spacing 原因:它将创建在 this 上下文中执行的函数版本,通常是您想要的,并且语法更简洁


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


// bad[1, 2, 3].map(function (x) {  const y = x + 1  return x * y})
// good[1, 2, 3].map((x) => { const y = x + 1 return x * y})
复制代码


如果函数体只包含一条没有副作用的返回表达式的语句,可以省略花括号并使用隐式的 return, 否则保留花括号并使用 return 语句,eslint: arrow-parens, arrow-body-style


// bad[1, 2, 3].map(number => {  const nextNumber = number + 1  `A string containing the ${nextNumber}.`})
// good[1, 2, 3].map(number => `A string containing the ${number}.`)
// good[1, 2, 3].map((number) => { const nextNumber = number + 1 return `A string containing the ${nextNumber}.`})
// good[1, 2, 3].map((number, index) => ({ index: number}))
// No implicit return with side effectsfunction foo(callback) { const val = callback() if (val === true) { // Do something if callback returns true }}
let bool = false
// badfoo(() => bool = true)
// goodfoo(() => { bool = true})
复制代码


一旦表达式跨多行,使用圆括号包裹以便更好阅读


// bad['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call(    httpMagicObjectWithAVeryLongName,    httpMethod  ))
// good['get', 'post', 'put'].map(httpMethod => ( Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod )))
复制代码


函数如果只接收一个参数并且没使用用花括号,则省略圆括号,否则为了清晰明确则使用圆括号包裹参数,注意:总是使用圆括号也是可以接受的,eslint 中的 “always” 选项,eslint: arrow-parens


// bad[1, 2, 3].map((x) => x * x)
// good[1, 2, 3].map(x => x * x)
// good[1, 2, 3].map(number => ( `A long string with the ${number}. It’s so long that we’ve broken it ` + 'over multiple lines!'))
// bad[1, 2, 3].map(x => { const y = x + 1 return x * y})
// good[1, 2, 3].map((x) => { const y = x + 1 return x * y})
复制代码

类&构造函数

使用 class,避免直接操作 prototype


// badfunction Queue (contents = []) {  this._queue = [..contents]}Queue.prototype.pop = function () {  const value = this._queue[0]  this._queue.splice(0, 1)  return value}
// goodclass Queue { constructor (contents = []) { this._queue = [...contents] }
pop () { const value = this._queue[0] this._queue.splice(0, 1) return value }}
复制代码


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


// badconst inherits = require('inherits')function PeekableQueue(contents) {  Queue.apply(this, contents)}inherits(PeekableQueue, Queue)PeekableQueue.prototype.peek = function () {  return this.queue[0]}
// goodclass PeekableQueue extends Queue { peek () { return this.queue[0] }}
复制代码


如果未声明构造函数,则类会有一个默认的构造函数,没必要用空的构造函数或者将其委托给父类,eslint: no-useless-constructor


// badclass Jedi {  constructor () {}
getName() { return this.name }}
// badclass Rey extends Jedi { constructor (...args) { super(...args) }}
// goodclass Rey extends Jedi { constructor (...args) { super(...args) this.name = 'Rey' }}
复制代码


避免类成员重复,eslint: no-dupe-class-members 原因:重复的类成员声明会默认使用最后声明的,通常会导致 bug


// badclass Foo {  bar () { return 1 }  bar () { return 2 }}
// goodclass Foo { bar () { return 1 }}
// goodclass Foo { bar () { return 2 }}
复制代码

模块

使用标准的 ES6 模块语法 import 和 export 原因:模块是未来,让我们现在开始使用未来的特性


// badconst util = require('./util')module.exports = util
// goodimport Util from './util'export default Util
// betterimport { Util } from './util'export default Util
复制代码


不要使用 import 的通配符 *,这样可以确保你只有一个默认的 export


// badimport * as Util from './util'
// goodimport Util from './util'
复制代码


同个文件每个模块只允许 import 一次,有多个 import 请书写在一起,eslint: no-duplicate-imports 原因:这样可以让代码更易于维护


// badimport foo from 'foo'// … some other imports … //import { named1, named2 } from 'foo'
// goodimport foo, { named1, named2 } from 'foo'
// goodimport foo, { named1, named2} from 'foo'
复制代码


将所有 import 语句放在文件最前方,eslint: import/imports-first


// badimport foo from 'foo'foo.init()
import bar from 'bar'
// goodimport foo from 'foo'import bar from 'bar'
foo.init()
复制代码


多行导入应该像多行数组和对象文字一样缩进


// badimport { longNameA, longNameB, longNameC, longNameD, longNameE } from 'path'
// goodimport { longNameA, longNameB, longNameC, longNameD, longNameE} from 'path'
复制代码


在模块 import 声明中禁止使用 Webpack 的 loader 语法,eslint: import/no-webpack-loader-syntax


// badimport fooSass from 'css!sass!foo.scss'import barCss from 'style!css!bar.css'
// goodimport fooSass from 'foo.scss'import barCss from 'bar.css'
复制代码

迭代器

建议使用 JS 更高优先级的函数代替 for-in 或 for-of 循环,eslint: no-iterator no-restricted-syntax


const numbers = [1, 2, 3, 4, 5]
// badlet sum = 0for (let num of numbers) { sum += num}
// goodlet sum = 0numbers.forEach(num => sum += num)
// betterconst sum = numbers.reduce((total, num) => total + num, 0)
复制代码

对象属性

使用 . 来访问对象属性


const joke = {  name: 'haha',  age: 28}
// badconst name = joke['name']
// goodconst name = joke.name
复制代码


当访问的属性是变量时使用 []


const luke = {  jedi: true,  age: 28,}
function getProp (prop) { return luke[prop]}
const isJedi = getProp('jedi')
复制代码

变量声明

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


// baddemo = new Demo()
// goodconst demo = new Demo()
复制代码


将所有的 const 和 let 分组


// badlet aconst blet cconst dlet e
// goodconst bconst dlet alet clet e
复制代码


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


// bad(function example() {  // JavaScript interprets this as  // let a = ( b = ( c = 1 ) );  // The let keyword only applies to variable a; variables b and c become  // global variables.  let a = b = c = 1}())
console.log(a) // throws ReferenceErrorconsole.log(b) // 1console.log(c) // 1
// good(function example() { let a = 1 let b = a let c = a}())
console.log(a) // throws ReferenceErrorconsole.log(b) // throws ReferenceErrorconsole.log(c) // throws ReferenceError
// the same applies for `const`
复制代码


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


// badvar some_unused_var = 42
// Write-only variables are not considered as used.var y = 10y = 5
// A read for a modification of itself is not considered as used.var z = 0z = z + 1
// Unused function arguments.function getX (x, y) { return x}
// good
function getXPlusY (x, y) { return x + y}
const x = 1const y = a + 2
alert(getXPlusY(x, y))
// 'type' is ignored even if unused because it has a rest property sibling.// This is a form of extracting an object that omits the specified keys.const { type, ...coords } = data// 'coords' is now the 'data' object without its 'type' property.Hoisting
复制代码


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


function example () {  console.log(notDefined)   // => throws a ReferenceError}
function example () { console.log(declareButNotAssigned) // => undefined var declaredButNotAssigned = true}
function example () { let declaredButNotAssigned console.log(declaredButNotAssigned) // => undefined declaredButNotAssigned = true}
function example () { console.log(declaredButNotAssigned) // => throws a ReferenceError console.log(typeof declaredButNotAssigned) // => throws a ReferenceError const declaredButNotAssigned = true}
复制代码


匿名函数的变量名会提升,但函数内容不会


function example () {  console.log(anonymous)  // => undefined
anonymous()
var anonymous = function () { console.log('test') }}
复制代码


命名的函数表达式的变量名会被提升,但函数名和函数函数内容并不会


function example() {  console.log(named)  // => undefined
named() // => TypeError named is not a function
superPower() // => ReferenceError superPower is not defined
var named = function superPower () { console.log('Flying') }}
function example() { console.log(named) // => undefined
named() // => TypeError named is not a function
var named = function named () { console.log('named') }}
复制代码

比较运算符&相等

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


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


Objects 等于 trueUndefined 等于 falseNull 等于 falseBooleans 等于 布尔值Numbers 在 +0, -0, 或者 NaN 的情况下等于 false, 其他情况是 trueStrings 为 '' 时等于 false, 否则是 trueif ([0] && []) {  // true  // 数组(即使是空数组)也是对象,对象等于true}
复制代码

分号

Standard 的规范是不使用分号的,我建议统一使用分号,代码更加清晰 关于应不应该使用分号的讨论有很多,好的 JS 程序员应该清楚场景下是一定要加分号的,相信你也是名好的开发者。


// badconst test = 'good'(function () {  const str = 'hahaha'})()
// goodconst test = 'good';;(() => { const str = 'hahaha';})();
复制代码

标准特性

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

eval()

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

with() {}

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

修改内置对象的原型

不要修改内置对象,如 Object 和 Array

注释

为了代码的统一性,函数内部采用 单行注释,工程复杂注释采用多行 如果涉及 todo 类型的注释,采用 // TODO:

用户头像

还未添加个人签名 2022-10-11 加入

还未添加个人简介

评论

发布
暂无评论
前端JS规范_Vue_默默的成长_InfoQ写作社区