41

JavaScript 编码规范

 4 years ago
source link: https://www.tuicool.com/articles/qQZBN3m
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

类型

基本类型

你可以直接获取到基本类型的值

string
number
boolean
null
undefined
symbol
const foo = 1;
let bar = foo;

bar = 9;

console.log(foo, bar); // => 1, 9

注意Symbols 不能被完整的 polyfill ,所以,在不支持 Symbols 的环境下中,不应该使用 symbol 类型。

复杂类型

复杂类型赋值就是获取到他的引用的值,相当于引用传递

object
array
function
const foo = [1, 2];
const bar = foo;

bar[0] = 9;
console.log(foo[0], bar[0]); // => 9, 9

参考

永远都使用 const

为了确保你不会改变你的初始值,重复引用会导致一些不可预见的 bug ,还会让代码难以理解,所有的赋值都应该使用 const ,避免使用 var

eslint

// bad
var a = 1;
var b = 2;

// good
const a = 1;
const b = 2;

可以使用 let

如果你一定要对参数重新赋值,那就使用 let ,而不是 varlet 是块级作用域,而 ver 是函数级作用域。

eslint

// bad
var count = 1;
if (true) {
  count += 1;
}

// good
let count = 1;
if (true) {
  count += 1;
}

注意 constlet 的块级作用域

constlet 声明的常量与变量都只存在于定义它们的那个块级作用域中。

{
  let a = 1;
  const b = 1;
}

console.log(a); // ReferenceError
console.log(b); // ReferenceError

对象

永远使用字面量创建对象

eslint

// bad
const obj = new Object();

// good
const obj = {};

使用计算属性名

当你需要创建一个带有 动态属性名 的对象时,请将所有的属性定义放在一起,可以使用 计算属性名

function getKey(key) {
  return `a key named ${key}`;
}

// bad
const obj = {
  id: 1,
  name: 'Parc MG',
};
obj[getKey('enabled')] = true;

// good
const obj = {
  id: 1,
  name: 'Parc MG',
  [getKey('enabled')]: true
};

对象方法简写

eslint

// bad
const atom = {
  value: 1,
  add: function (value) {
    return atom.value + value;
  }
};

// good
const atom = {
  value: 1,
  add(value) {
    return atom.value + value;
  }
};

属性值缩写

eslint

const name = 'Parc MG';

// bad
const org = {
  name: name,
};

// good
const org = {
  name,
};

将所有属性值缩写放在对象声明的最前面

const name = 'Parc MG';
const url = 'https://parcmg.com';

// bad
const org = {
  email: '[email protected]',
  name,
  created: new Date(),
  url,
};

// good
const org = {
  name,
  url,
  email: '[email protected]',
  created: new Date(),
};

若非必要,属性名不使用 ''

eslint

// bad
const bad = {
  'foo': 1,
  'bar': 2,
  'foo-bar': 3,
};

// good
const good = {
  foo: 1,
  bar: 2,
  'foo-bar': 3,
};

不直接调用对象原型上的方法

不直接调用一个对象的 hasOwnPropertypropertyIsEnumerableisPrototypeOf 等这些原型的方法,在某些情况下,这些方法可能会被屏蔽掉,比如 { hasOwnProperty: false } 或者是一个空对象 Object.create(null)

// bad
obj.hasOwnProperty(key);

// good
Object.prototype.hasOwnProperty.call(obj, key);

// best
const has = Object.prototype.hasOwnProperty;

has.call(obj, key);

积极使用扩展及解构运算 ...

  • 在对象的 浅拷贝 时,更推荐使用扩展运算 { ...obj } ,而不是 Object.assign
  • 在获取对象指定的几个属性时,使用解构运算 { foo, bar, ...rest } = obj

eslint

// very bad
const original = { a: 1, b: 2 };
const copied = Object.assign(original, { c: 3 }); // 这将导致 original 也被修改

delete copied.a; // 这样操作之后会导致 original 也被修改
console.log(original); // => {b: 2, c: 3}

// bad
const original = { a: 1, b: 2 };
const copied = Object.assign({}, original, { c: 3}};

// good
const original = { a: 1, b: 2 };
const copied = { ...original, c: 3 };

// 解构运算与 `rest` 赋值运算
const obj = { a: 1, b: 2, c: 3 };
const { a, b } = obj; // 从对象 obj 中解构出 a, b 两个属性的值,并赋值给名为 a,b 的常量
const { a, ...rest } = obj; // 从对象 obj 中解构出 a 的值,并赋值给名为 a 的常量,同时,创建一个由所有其它属性组成的名为 `rest` 的新对象
console.log(rest); // => { b: 2, c: 3 }

// bad
function getFullName(user) {
  const firstName = user.firstName;
  const lastName = user.lastName;
  
  return `${firstName} ${lastName}`;
}

// good
function getFullName(user) {
  const { firstName, lastName } = user;
  return `${firstName} ${lastName}`;
}

// best
function getFullName({ firstName, lastName}) {
  return `${firstName} ${lastName}`;
}

// the most best
const getFullName = ({ firstName, lastName }) => `${firstName} ${lastName}`;

返回多值时,使用对象解构,而非数组结构

由于 JavaScript 不支持多值返回,当一个函数或者方法有多个值需要创建时,请为每一个值命名,并以所有值组成的对象为单一值返回,而不是以数组的形式返回。

// bad
function processInput(input) {
  return [left, right, top, bottom];
}

const [left, _, top] = processInput(input); // 调用者需要在调用时,明确的知道每一个索引上的值是什么 ,且无法跳越前面的值取后面的值

// good
function processInput(input) {
  return { left, right, top, bottom };
}

const { left, top } = processInput(input); // 调用者可以明确的指定需要哪个值,而且不需要创建多余的变量

数组

使用字面量赋值

eslint

// bad
const items = new Array();

// good
const items = [];

使用 .push 方法代替直接索引赋值

const items = [];

// bad
items[items.length] = 'new item';

// good
items.push('new item');

使用扩展运算符进行浅拷贝

const items = [1, 2, 3, 4, 5];

// bad
const length = items.length;
const copied = [];
let index;

for (index = 0; index < length; index += 1) {
  copied[index] = items[index];
}

// good
const copied = [ ...items ];

使用 ... 运算符代替 Array.from

当需要将一个可迭代的对象转换成数组时,推荐使用 ... 操作符。

const elements = document.querySelectorAll('.foobar');

// not bad
const nodes = Array.from(elements);

// good
const nodes = [ ...elements ];

使用 ... 解构数组

const array = [1, 2, 3, 4, 5];

// bad
const first = array[0];
const second = array[1];

// good
const [first, second, ...rest] = array;
console.log(rest); // => [3, 4, 5]

使用 Array.from 将类数组对象转成数组

参考: Typed Arrays

const arrayLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 }

// bad
const array = Array.prototype.slice.call(arrayLike);

// good
const array = Array.from(arrayLike);

使用 Array.from 对类数组对象进行遍历

Array.from(arrayLike[, mapFn[, thisArg]]) 方法,参考 Array.from

const arrayLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 }

// bad
const array = [...arrayLike].map(mapFn);

// good
const array = Array.from(arrayLike, mapFn);

在数组方法的回调函数中,永远返回正确的值

// bad - 当第一次迭代完成之后, acc 就变成了 undefined 了
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
  const flatten = acc.concat(item);
  acc[index] = flatten;
});

// good
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
  const flatten = acc.concat(item);
  acc[index] = flatten;
  return flatten;
});

// bad
messages.filter(msg => {
  const { subject, author } = msg;
  if (subject === 'ParcMG') {
    return author === 'MG';
  } else {
    return false;
  }
});

// good
messages.filter(msg => {
  const { subject, author } = msg;
  if (subject === 'ParcMG') {
    return author === 'MG';
  }
  return false;
});

// bad
[1, 2, 3].map(x => {
  const y = x + 1;
  return x * y;
}

// good
[1, 2, 3].map(x => x * (x + 1));

一个数组有多行时,在 [] 处断行

// bad
const array = [
  [0, 1], [2, 3], [4, 5], [6, 7]
];

const objectArray = [{
  id: 1,
}, {
  id: 2,
}];

const numberArray = [
  1, 2,
];

// good
const array = [[0, 1], [2, 3], [4, 5], [6, 7]];

const objectArray = [
  {
    id: 1,
  },
  {
    id: 2,
  }
];

const numberArray = [1, 2];

const numberArray = [
  1,
  2,
];

字符串

string 永远使用单引号 ''

eslint

// bad
const name = "Parc M.G";

const name = `Parc M.G`;

// good
const name = 'Parc M.G';

超长的字符串,不应该使用多行串联

// bad
const content = '《学而》是《论语》第一篇的篇名。《论语》中各篇一般都是以第\
一章的前二三个字作为该篇的篇名。《学而》一篇包括16章,内容涉及诸多方面。其中重\
点是「吾日三省吾身」;「节用而爱人,使民以时」;「礼之用,和为贵」以及仁、孝、\
信等道德范畴。';

const content = '《学而》是《论语》第一篇的篇名。《论语》中各篇一般都是以第' +
'一章的前二三个字作为该篇的篇名。《学而》一篇包括16章,内容涉及诸多方面。其中重' +
'点是「吾日三省吾身」;「节用而爱人,使民以时」;「礼之用,和为贵」以及仁、孝、' +
'信等道德范畴。';

// good
const content = '《学而》是《论语》第一篇的篇名。《论语》中各篇一般都是以第\一章的前二三个字作为该篇的篇名。《学而》一篇包括16章,内容涉及诸多方面。其中重点是「吾日三省吾身」;「节用而爱人,使民以时」;「礼之用,和为贵」以及仁、孝、信等道德范畴。';

使用模板而非拼接来组织可编程字符串

eslint

// bad
function hello(name) {
  return '你好,' + name + '!';
}

function hello(name) {
  return ['你好,', name, '!'].join('');
}

function hello(name) {
  return `你好,${ name }!`;
}

// good
function hello(name) {
  return `你好,${name}!`;
}

永远不使用 eval()

eslint

若非必要,不使用转义字符

eslint

// bad
const foo = '\'this\' \i\s \"quoted\"';

// good
const foo = '\this\' is "quoted"';

// best
const foo = `'this' is "quoted"`;

函数

使用命名函数表达式,而不是函数声明

eslint

使用函数声明,它的作用域会被提前,这意味着很容易在一个文件里面引用一个还未被定义的函数,这样大大伤害了代码的可读性和可维护性,若一个函数很大很复杂,那么应该考虑将该函数单独提取到一个文件中,抽离成一个模块,同时不要忘记给表达式显示的命名,这消除了由匿名函数在错误调用栈中产生的所有假设。

// bad
function foo() {
  // ...
}

// bad
const foo = function () {
  // ...
}

// good
const foo = function foo() {
  // ...
}

// best
const foo = function longUniqueMoreDescriptiveLexicalFoo() {
  // ...
}

把立即执行函数包裹在圆括号里

eslint

(function () {
  console.log('Welcome to the ParcMG world.');
}());

不要在非函数块内声明函数

虽然运行环境允许你这样做,但是不同环境的解析方式不一样。

eslint

//bad 
for (var i=10; i; i--) {
    (function() { return i; })();
}

while(i) {
    var a = function() { return i; };
    a();
}

do {
    function a() { return i; };
    a();
} while (i);

let foo = 0;
for (let i = 0; i < 10; ++i) {
    // Bad, `foo` is not in the loop-block's scope and `foo` is modified in/after the loop
    setTimeout(() => console.log(foo));
    foo += 1;
}

for (let i = 0; i < 10; ++i) {
    // Bad, `foo` is not in the loop-block's scope and `foo` is modified in/after the loop
    setTimeout(() => console.log(foo));
}
foo = 100;

// good
var a = function() {};

for (var i=10; i; i--) {
    a();
}

for (var i=10; i; i--) {
    var a = function() {}; // OK, no references to variables in the outer scopes.
    a();
}

for (let i=10; i; i--) {
    var a = function() { return i; }; // OK, all references are referring to block scoped variables in the loop.
    a();
}

var foo = 100;
for (let i=10; i; i--) {
    var a = function() { return foo; }; // OK, all references are referring to never modified variables.
    a();
}

注意:在 ECMA-262 中, 块( block 的定义是: 一系列语句 ,但函数声明不是一个语句,命名函数表达式是一个语句。

// bad
if (currentUser) {
  function test() {
    console.log('Nope.');
  }
}

// good
let test;
if (currentUser) {
  test = () => {
    console.log('Yup.');
  };
}

不允许使用 arguments 命名参数

arguments 的优先级高于高于每个函数作用域自带的 arguments 对象,这会导致函数自带的 arguments 值被覆盖。

// bad
function foo(name, options, arguments) {
  // ...
}

// good
function foo(name, options, args) {
  // ...
}

不要在函数体内使用 arguments ,使用 ...rest 代替

eslint

... 明确出你想用那个参数,同时, rest 是一个真数组,而不是一个类数组的 arguments

// bad
function concatenateAll() {
  const args = Array.prototype.slice.call(arguments);
  return args.join('');
}

// good
function concatenateAll(...args) {
  return args.join('');
}

使用默认参数,而不是在函数体内对参数重新赋值

// really bad
function handleThings(options) {
  options = options || {};
}

// still bad
function handleTings(options) {
  if (options === void 0) {
    options = {};
  }
}

// good
function handleThings(options = {}) {
}

默认参数要避免副作用

// bad
let v = 1;
const count = function count(a = v++) {
  console.log(a);
}

count();   // => 1
count();   // => 2
count(3);  // => 3
count();   // => 3

// maybe
const v = 1;
const count = function count(a = v) {
  console.log(a);
}

把默认参数放在最后

// bad
function handleTings(options = {}, name) {
  // ...
}

// good
function handleTings(name, options = {}) {
  // ...
}

不要使用函数构造器构造函数

eslint

// bad
var add = new Function('a', 'b', 'return a + b');

// still bad
var subtract = Function('a', 'b', 'return a - b');

// good
const subtract = (a, b) => a + b;

函数签名部分要有空格

eslint

// bad
const f = function(){};
const g = function (){};
const h = function() {};

// good
const f = function a() {};

不修改参数

eslint

函数签名时定义的参数,在函数体内不允许被重新赋值(包含参数本身,若参数为对象,还包括该对象所有属性的值),

一个函数应该是没有任何副作用的。

// bad
function f1 (obj) {
  obj.key = 1;
};

function f2 (a) {
  a = 1;
  // ...
}

function f3 (a) {
  if (!a) { a = 1; }
  // ...
}

// good
function f4(obj) {
  const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
};

function f5(a) {
  const b = a || 1;
  // ...
}

function f6(a = 1) {
  // ...
}

使用 spread 操作符 ... 调用多变参数函数

eslint

// bad
const x = [1, 2, 3, 4, 5];
console.log.apply(console, x);

// good
const x = [1, 2, 3, 4, 5];
console.log(...x);

// bad
new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));

// good
new Date(...[2016, 8, 5]);

若函数签名包含多个参数需要使用多行,那就每行有且仅有一个参数

// bad
function foo(bar,
             baz,
             quux) {
  // ...
}

// good 缩进不要太过分
function foo(
  bar,
  baz,
  quux,
) {
  // ...
}

// bad
console.log(foo,
  bar,
  baz);

// good
console.log(
  foo,
  bar,
  baz,
);

箭头函数

当你一定要用函数表达式的时候,就使用箭头函数

eslint

// 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

eslint

// 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
}));

// 表达式有副作用就不要用隐式return
function foo(callback) {
  const val = callback();
  if (val === true) {
    // Do something if callback returns true
  }
}

let bool = false;

// bad
// 这种情况会return bool = true, 不好
foo(() => bool = true);

// good
foo(() => {
  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

// 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 don’t want it to take up space on the .map line!`
));

// 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;
});

避免箭头函数(=>)和比较操作符(<=, >=)混淆.

eslint

// bad
const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize;

// bad
const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize;

// good
const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize);

// good
const itemHeight = (item) => {
  const { height, largeSize, smallSize } = item;
  return height > 256 ? largeSize : smallSize;
};

在隐式return中强制约束函数体的位置, 就写在箭头后面

eslint

// bad
(foo) =>
  bar;

(foo) =>
  (bar);

// good
(foo) => bar;
(foo) => (bar);
(foo) => (
   bar
)

类与构造器

使用构造器,而不是 prototype

// 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

// 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];
  }
}

方法可以返回 this 实现方法链

// bad
Jedi.prototype.jump = function () {
  this.jumping = true;
  return true;
};

Jedi.prototype.setHeight = function (height) {
  this.height = height;
};

const luke = new Jedi();
luke.jump(); // => true
luke.setHeight(20); // => undefined

// good
class Jedi {
  jump() {
    this.jumping = true;
    return this;
  }

  setHeight(height) {
    this.height = height;
    return this;
  }
}

const luke = new Jedi();

luke.jump()
  .setHeight(20);

只要保证可以正常工作且没有副作用,可以自已定制 toString 方法

class Jedi {
  constructor(options = {}) {
    this.name = options.name || 'no name';
  }

  getName() {
    return this.name;
  }

  toString() {
    return `Jedi - ${this.getName()}`;
  }
}

不要写无用的构造函数

eslint

// bad
class Jedi {
  constructor() {}

  getName() {
    return this.name;
  }
}

// bad
class Rey extends Jedi {
  // 这种构造函数是不需要写的
  constructor(...args) {
    super(...args);
  }
}

// good
class Rey extends Jedi {
  constructor(...args) {
    super(...args);
    this.name = 'Rey';
  }
}

避免重复类成员

eslint

// bad
class Foo {
  bar() { return 1; }
  bar() { return 2; }
}

// good
class Foo {
  bar() { return 1; }
}

// good
class Foo {
  bar() { return 2; }
}

模块

使用 import / export

// bad
const Button = require('./Button');
module.exports = Button.es6;

// ok
import Button from './Button';
export default Button.es6;

// best
import { es6 } from './Button';
export default es6;

不要 import 通配符

// bad
import * as Component from './Component';

// good
import Component from './Component';

不要直接从 importexport

虽然一行是简洁的,有一个明确的方式进口和一个明确的出口方式来保证一致性。

// bad
export { es6 as default } from './Component';

// good
import { es6 } from './Component';
export default es6;

一个路径只 import 一次

eslint

从同一个路径下import多行会使代码难以维护

// bad
import foo from 'foo';
// … some other imports … //
import { named1, named2 } from 'foo';

// good
import foo, { named1, named2 } from 'foo';

// good
import foo, {
  named1,
  named2,
} from 'foo';

若非必要,不要 export 可变量

eslint

变化通常都是需要避免,特别是当你要输出可变的绑定。虽然在某些场景下可能需要这种技术,但总的来说应该导出常量。

// bad
let foo = 3;
export { foo }

// good
const foo = 3;
export { foo }

在一个单一导出模块里,使用 export default

eslint

鼓励使用更多文件,每个文件只做一件事情并导出,这样可读性和可维护性更好。

// bad
export function foo() {}

// good
export default function foo() {}

import 应该放在所有其它语句之前

eslint

// bad
import foo from 'foo';
foo.init();

import bar from 'bar';

// good
import foo from 'foo';
import bar from 'bar';

foo.init();

多行import应该缩进,就像多行数组和对象字面量

花括号与样式指南中每个其他花括号块遵循相同的缩进规则,逗号也是。

// bad
import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';

// good
import {
  longNameA,
  longNameB,
  longNameC,
  longNameD,
  longNameE,
} from 'path';

若使用 webpack ,不允许在 import 中使用 webpack loader 语法

eslint

一旦用 Webpack 语法在 import 里会把代码耦合到模块绑定器。最好是在 webpack.config.js 里写 webpack loader 语法

// bad
import fooSass from 'css!sass!foo.scss';
import barCss from 'style!css!bar.css';

// good
import fooSass from 'foo.scss';
import barCss from 'bar.css';

迭代器与生成器

不要使用遍历器

eslint

用JavaScript高级函数代替 for-infor-of

  • 这强调了我们不可变的规则。 处理返回值的纯函数比副作用更容易。
  • 用数组的这些迭代方法: map()every()filter()find()findIndex()reduce()some() ……
  • 用对象的这些方法 Object.keys()Object.values()Object.entries 去产生一个数组, 这样你就能去遍历对象了。
const numbers = [1, 2, 3, 4, 5];

// bad
let sum = 0;
for (let num of numbers) {
  sum += num;
}
sum === 15;

// good
let sum = 0;
numbers.forEach(num => sum += num);
sum === 15;

// best (use the functional force)
const sum = numbers.reduce((total, num) => total + num, 0);
sum === 15;

// bad
const increasedByOne = [];
for (let i = 0; i < numbers.length; i++) {
  increasedByOne.push(numbers[i] + 1);
}

// good
const increasedByOne = [];
numbers.forEach(num => increasedByOne.push(num + 1));

// best (keeping it functional)
const increasedByOne = numbers.map(num => num + 1);

不要用 generator

eslint

它在es5上支持的不好

如果一定要用,那么一定需要注意一点: function* 是同一概念关键字, * 并不是 function 的修饰符, function* 是一个与 function 完全不一样的独特结构。

// bad
function * foo() {
  // ...
}

// bad
const bar = function * () {
  // ...
}

// bad
const baz = function *() {
  // ...
}

// bad
const quux = function*() {
  // ...
}

// bad
function*foo() {
  // ...
}

// bad
function *foo() {
  // ...
}

// very bad
function
*
foo() {
  // ...
}

// very bad
const wat = function
*
() {
  // ...
}

// good
function* foo() {
  // ...
}

// good
const foo = function* () {
  // ...
}

属性

访问属性使用 .

eslint

这条,涉及一个曾经阿里出过一个看似简单,实则很难的面试题,你就算猜对一个,你也不一定能说出原理:

a.b.c.d和a'b'['d'],哪个性能更高

到这里,突然想起这个梗,有兴趣的可以翻看一下 这里

const luke = {
  jedi: true,
  age: 28,
};

// bad
const isJedi = luke['jedi'];

// good
const isJedi = luke.jedi;

当获取属性名称本身是一个变量是,使用 [] 访问

const luke = {
  jedi: true,
  age: 28,
};

function getProp(prop) {
  return luke[prop];
}

const isJedi = getProp('jedi');

幂等使用 ** 操作符

eslint

// bad
const binary = Math.pow(2, 10);

// good
const binary = 2 ** 10;

变量

永远使用 const 或者 let ,不使用 var

eslint

// bad
superPower = new SuperPower();

// good
const superPower = new SuperPower();

每一个变量都用一个 const 或者 let

eslint

扯蛋的理由:这种方式很容易去声明新的变量,你不用去考虑把;调换成,,或者引入一个只有标点的不同的变化。

真正的理由:做法也可以是你在调试的时候单步每个声明语句,而不是一下跳过所有声明。

// bad
const items = getItems(),
    goSportsTeam = true,
    dragonball = 'z';

const items = getItems(),
    goSportsTeam = true;
    dragonball = 'z';

// good
const items = getItems();
const goSportsTeam = true;
const dragonball = 'z';

尘归尘,土归土

在同一个块中,所有的 const 放在一起,所有的 let 放在一起

// bad
let i, len, dragonball,
    items = getItems(),
    goSportsTeam = true;

// bad
let i;
const items = getItems();
let dragonball;
const goSportsTeam = true;
let len;

// good
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;

在你需要的地方声明变量,但要放在合理的位置

// bad
function checkName(hasName) {
  const name = getName();

  if (hasName === 'test') {
    return false;
  }

  if (name === 'test') {
    this.setName('');
    return false;
  }

  return name;
}

// good
function checkName(hasName) {
  if (hasName === 'test') {
    return false;
  }

  // 在需要的时候分配
  const name = getName();

  if (name === 'test') {
    this.setName('');
    return false;
  }

  return name;
}

不使用链接变量分配

eslint

链接变量分配会隐匿创建全局变量

// bad
(function example() {
  // JavaScript 将这一段解释为
  // let a = ( b = ( c = 1 ) );
  // let 只对变量 a 起作用; 变量 b 和 c 都变成了全局变量
  let a = b = c = 1;
}());

console.log(a); // undefined
console.log(b); // 1
console.log(c); // 1

// good
(function example() {
  let a = 1;
  let b = a;
  let c = a;
}());

console.log(a); // undefined
console.log(b); // undefined
console.log(c); // undefined

// `const` 也是如此

不使用一元自增自减运算( ++--

eslint

根据 eslint 文档,一元增量和减量语句受到自动分号插入的影响,并且可能会导致应用程序中的值递增或递减的无声错误。 使用 num + = 1 而不是 num ++num ++ 语句来表达你的值也是更有表现力的。 禁止一元增量和减量语句还会阻止您无意地预增/预减值,这也会导致程序出现意外行为。

// bad

let array = [1, 2, 3];
let num = 1;
num++;
--num;

let sum = 0;
let truthyCount = 0;
for(let i = 0; i < array.length; i++){
  let value = array[i];
  sum += value;
  if (value) {
    truthyCount++;
  }
}

// good

let array = [1, 2, 3];
let num = 1;
num += 1;
num -= 1;

const sum = array.reduce((a, b) => a + b, 0);
const truthyCount = array.filter(Boolean).length;

赋值时不换行

eslint

如果赋值语句超出了 max-len 配置,那么给值前面加上括号。

// bad
const foo =
  superLongLongLongLongLongLongLongLongFunctionName();

// bad
const foo
  = 'superLongLongLongLongLongLongLongLongString';

// good
const foo = (
  superLongLongLongLongLongLongLongLongFunctionName()
);

// good
const foo = 'superLongLongLongLongLongLongLongLongString';

不允许声明不使用的变量

eslint

// bad

var some_unused_var = 42;

// 写了没用
var y = 10;
y = 5;

// 变量改了自己的值,也没有用这个变量
var z = 0;
z = z + 1;

// 参数定义了但未使用
function getX(x, y) {
    return x;
}

// good
function getXPlusY(x, y) {
  return x + y;
}

var x = 1;
var y = a + 2;

alert(getXPlusY(x, y));

// 'type' 即使没有使用也可以可以被忽略, 因为这个有一个 rest 取值的属性。
// 这是从对象中抽取一个忽略特殊字段的对象的一种形式
var { type, ...coords } = data;
// 'coords' 现在就是一个没有 'type' 属性的 'data' 对象

变量提升

永远不要使用 var

var 声明会将变量声明提升到作用域的最前面,但是他的值却只有在运行到代码行时才会被赋值,永远都使用 constlet ,了解 时效区(Temporal Dead Zones) 的相关知识,也还要知道为什么 typeof 不再安全

// 我们知道这个不会工作,假设没有定义全局的notDefined
function example() {
  console.log(notDefined); // => throws a ReferenceError
}

// 在你引用的地方之后声明一个变量,他会正常输出是因为变量作用域上升。
// 注意: declaredButNotAssigned的值没有上升
function example() {
  console.log(declaredButNotAssigned); // => undefined
  var declaredButNotAssigned = true;
}

// 解释器把变量声明提升到作用域最前面,
// 可以重写成如下例子, 二者意义相同
function example() {
  let declaredButNotAssigned;
  console.log(declaredButNotAssigned); // => undefined
  declaredButNotAssigned = true;
}

// 用 const, let就不一样了
function example() {
  console.log(declaredButNotAssigned); // => throws a ReferenceError
  console.log(typeof declaredButNotAssigned); // => throws a ReferenceError
  const declaredButNotAssigned = true;
}

匿名函数表达式与 var 的情况一样

function example() {
  console.log(anonymous); // => undefined

  anonymous(); // => TypeError anonymous is not a function

  var anonymous = function () {
    console.log('anonymous function expression');
  };
}

已命名函数表达式只提升变量名,而不是函数名或者函数体

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');
  };
}

函数声明则提升了函数名和函数体

function example() {
  superPower(); // => Flying

  function superPower() {
    console.log('Flying');
  }
}

比较操作符

永远使用 ===!== ,而不是 ==!=

eslint

if 条件语句的强制 toBoolean

if 条件语句的强制 toBoolean 总是遵循以下规则:

  • Objects 总是计算成 true
  • Undefined 总是计算 成 false
  • Null 总是计算成 false
  • Booleans 计算成它本身的布尔值
  • Numbers

    • +0-0 或者 NaN 总是计算成 false
    • 其它的全部为 true
  • Strings

    false
    true
    

注意: NaN 是不等于 NaN 的,请使用 isNaN() 检测。

if ([0] && []) {
  // true
  // 数组(即使是空数组)是对象,对象会计算成true
}

console.log(NaN === NaN) // => false
console.log(isNaN(NaN))  // => true

布尔值要使用缩写,但是字符串与数字要明确比较对象

// bad
if (isValid === true) {
  // ...
}

// good
if (isValid) {
  // ...
}

// bad
if (name) {
  // ...
}

// good
if (name !== '') {
  // ...
}

// bad
if (collection.length) {
  // ...
}

// good
if (collection.length > 0) {
  // ...
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK