前端小技巧:JavaScript 编码规范

前端小技巧:JavaScript 编码规范

1. 类型

1.1【可选】 基本类型: 当你访问一个基本类型时,直接操作它的值。

string
number
boolean
null
undefined
symbol

符号(Symbols)不能完全的被 polyfill,因此在不能原生支持symbol类型的浏览器或环境中,不应该使用symbol类型。

1.2 【可选】 复杂类型: 当你访问一个复杂类型时,直接操作其值的引用。

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

bar[0] = 9;

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

2. 引用

2.1 【必须】 使用 

const

定义你的所有引用;避免使用

var

原因? 这样能够确保你不能重新赋值你的引用,否则可能导致错误或者产生难以理解的代码。

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

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

2.2 【必须】 如果你必须重新赋值你的引用, 使用 

let

代替

var

原因? let 是块级作用域,而不像 var 是函数作用域

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

// good, use the let.
let count = 1;
if (true) {
  count += 1;
}

2.3 【可选】 注意,let 和 const 都是块级作用域。

// const 和 let 只存在于他们定义的块级作用域中。
{
  let a = 1;
  const b = 1;
}
console.log(a); // ReferenceError
console.log(b); // ReferenceError

3. 对象

3.1 【必须】 使用字面量语法创建对象。

// bad
const item = new Object();

// good
const item = {};

3.2 【推荐】 在创建具有动态属性名称的对象时使用计算属性名。原因? 它允许你在一个地方定义对象的所有属性。

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

// bad
const obj = {
  id: 5,
  name: 'San Francisco',
};
obj[getKey('enabled')] = true;

// good
const obj = {
  id: 5,
  name: 'San Francisco',
  [getKey('enabled')]: true,
};

3.3 【推荐】 用对象方法简写。

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

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

3.4 【推荐】 用属性值简写。

const ScreenBody = 'Screen Body';

// bad
const obj = {
  ScreenBody: ScreenBody,
};

// good
const obj = {
  ScreenBody,
};

3.5 【推荐】 声明对象时,将简写的属性放在前面。原因? 这样更容易的判断哪些属性使用的简写。

const ScreenBody = 'Screen Body';
const ModalText = 'Modal Text';

// bad
const obj = {
  One: 1,
  two: 2,
  ScreenBody,
  Three: 3,
  Fourth: 4,
  ModalText,
};

// good
const obj = {
  ScreenBody,
  ModalText,
  One: 1,
  two: 2,
  Three: 3,
  Fourth: 4,
};

3.6 【必须】 只使用引号标注无效标识符的属性。

// bad
const bad = {
  'foo': 3,
  'bar': 4,
  'data-blah': 5,
};

// good
const good = {
  foo: 3,
  bar: 4,
  'data-blah': 5,
};

3.7 【推荐】 不能直接调用 

Object.prototype的方法,如: hasOwnProperty,propertyIsEnumerable,isPrototypeOf 

原因? 这些方法可能被有问题的对象上的属性覆盖 - 如 { hasOwnProperty: false } - 或者,对象是一个空对象 Object.create(null)

// bad
console.log(object.hasOwnProperty(key));

// good
console.log(Object.prototype.hasOwnProperty.call(object, key));

// best
const has = Object.prototype.hasOwnProperty; // 在模块范围内的缓存中查找一次
console.log(has.call(object, key));

/* or */
import has from 'has'; // https://www.npmjs.com/package/has
console.log(has(object, key));

3.8 【推荐】 使用对象扩展操作符(spread operator)浅拷贝对象,而不是用 Object.assign方法。 使用对象的剩余操作符(rest operator)来获得一个新对象,该对象省略了某些属性。

// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // 变异的 `original` ಠ_ಠ
delete copy.a; // 这....

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

// good
const 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 }

4. 数组

4.1 【必须】 使用字面量语法创建数组。

// bad
const items = new Array();

// good
const items = [];

 4.2 【必须】 使用 Array#push 代替直接赋值来给数组添加项。

const someStack = [];

// bad
someStack[someStack.length] = 'abracadabra';

// good
someStack.push('abracadabra');

4.3 【必须】 使用数组展开符 ... 来拷贝数组。

// bad
const len = items.length;
const itemsCopy = [];
let i;

for (i = 0; i < len; i += 1) {
  itemsCopy[i] = items[i];
}

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

4.4 【推荐】 使用展开符 ... 代替Array.from,将一个可迭代对象转换成一个数组。

const foo = document.querySelectorAll('.foo');

// good
const nodes = Array.from(foo);

// best
const nodes = [...foo];

4.5 【必须】 使用 Array.from 将一个类数组(array-like)对象转换成一个数组。

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

// bad
const arr = Array.prototype.slice.call(arrLike);

// good
const arr = Array.from(arrLike);

4.6 【必须】 使用 Array.from 代替展开符 ... 映射迭代器,因为它避免了创建一个中间数组。

// bad
const baz = [...foo].map(bar);

// good
const baz = Array.from(foo, bar);

4.7 【推荐】 在数组回调函数中使用 return 语句。 如果函数体由单个语句的返回表达式组成,并且无副作用,那么可以省略返回值, 具体查看 8.2。

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

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

// bad - 没有返回值,意味着在第一次迭代后 `acc` 没有被定义
[[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
inbox.filter((msg) => {
  const { subject, author } = msg;
  if (subject === 'Mockingbird') {
    return author === 'Harper Lee';
  } else {
    return false;
  }
});

// good
inbox.filter((msg) => {
  const { subject, author } = msg;
  if (subject === 'Mockingbird') {
    return author === 'Harper Lee';
  }

  return false;
});

4.8 【推荐】 如果数组有多行,则在数组开始括号 [ 的时候换行,然后在数组结束括号 ] 的时候换行。

// bad
const arr = [
  [0, 1], [2, 3], [4, 5],
];

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

const numberInArray = [
  1, 2,
];

// good
const arr = [[0, 1], [2, 3], [4, 5]];

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

const numberInArray = [
  1,
  2,
];

5. 解构

5.1 【推荐】 在访问和使用对象的多个属性时使用对象解构。

原因? 解构可以避免为这些属性创建临时引用。

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

5.2 【推荐】 使用数组解构。

const arr = [1, 2, 3, 4];

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

// good
const [first, second] = arr;

5.3 【必须】 在有多个返回值时, 使用对象解构,而不是数组解构。原因? 你可以随时添加新的属性或者改变属性的顺序,而不用修改调用方法。

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

6. 字符

6.1 【推荐】 使用单引号 

// bad
const name = "Capt. Janeway";

// bad - 模板文字应该包含插值或换行。
const name = `Capt. Janeway`;

// good
const name = 'Capt. Janeway';

6.2 【必须】 不应该用字符串跨行连接符的格式来跨行编写,这样会使当前行长度超过100个字符。原因? 断开的字符串维护起来很痛苦,并且会提高索引难度。

// bad
const errorMessage = 'This is a super long error that was thrown because \
of Batman. When you stop to think about how Batman had anything to do \
with this, you would get nowhere \
fast.';

// bad
const errorMessage = 'This is a super long error that was thrown because ' +
  'of Batman. When you stop to think about how Batman had anything to do ' +
  'with this, you would get nowhere fast.';

// good
const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.';

6.3 【必须】 构建字符串时,使用字符串模板代替字符串拼接。

原因? 字符串模板为您提供了一种可读的、简洁的语法,具有正确的换行和字符串插值特性。

// bad
function sayHi(name) {
  return 'How are you, ' + name + '?';
}

// bad
function sayHi(name) {
  return ['How are you, ', name, '?'].join();
}

// bad
function sayHi(name) {
  return `How are you, ${ name }?`;
}

// good
function sayHi(name) {
  return `How are you, ${name}?`;
}

6.4 【必须】 永远不要使用 eval()执行放在字符串中的代码,它导致了太多的漏洞。

6.5 【必须】 不要在字符串中转义不必要的字符。

原因? 反斜杠损害了可读性,因此只有在必要的时候才可以出现。

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

// good
const foo = '\'this\' is "quoted"';
const foo = `my name is '${name}'`;

7. 函数

7.1 【可选】 使用命名的函数表达式代替函数声明。

原因? 函数声明时作用域被提前了,这意味着在一个文件里函数很容易(太容易了)在其定义之前被引用。这样伤害了代码可读性和可维护性。如果你发现一个函数又大又复杂,并且它干扰了对这个文件其他部分的理解,那么是时候把这个函数单独抽成一个模块了!别忘了给表达式显式的命名,不用管这个名字是不是由一个确定的变量推断出来的(这在现代浏览器和类似babel编译器中很常见)。这消除了由匿名函数在错误调用栈产生的所有假设。 * (Discussion)

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

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

// good
const short = function longUniqueMoreDescriptiveLexicalFoo() {
  // ...
};

7.2 【必须】 把立即执行函数包裹在圆括号里。

原因? 立即调用的函数表达式是个独立的单元 - 将它和它的调用括号还有入参包装在一起可以非常清晰的表明这一点。请注意,在一个到处都是模块的世界中,您几乎用不到 IIFE。

// immediately-invoked function expression (IIFE) 立即调用的函数表达式
(function () {
  console.log('Welcome to the Internet. Please follow me.');
}());

7.3 【必须】 切记不要在非功能块中声明函数 (if, while)等,请将函数赋值给变量。 浏览器允许你这样做, 但是不同浏览器会有不同的行为, 这并不是什么好事。

7.4 【必须】 ECMA-262 将 block 定义为语句列表。 而函数声明并不是语句。

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

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

7.5 【必须】 永远不要给一个参数命名为 arguments,这将会覆盖函数默认的arguments

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

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

7.6 【推荐】 使用 rest 语法 ...代替 arguments,

原因? ... 明确了你想要拉取什么参数。 而且, rest 参数是一个真正的数组,而不仅仅是类数组的arguments

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

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

7.7 【推荐】 使用默认的参数语法,而不是改变函数参数。

// really bad
function handleThings(opts) {
  // 不!我们不应该修改参数。
  // 更加错误的是: 如果 opts 是一个 "非正值"(falsy)它将被设置成一个对象
  // 这或许正是你想要的,但它可能会导致一些难以察觉的错误。
  opts = opts || {};
  // ...
}

// still bad
function handleThings(opts) {
  if (opts === void 0) {
    opts = {};
  }
  // ...
}

// good
function handleThings(opts = {}) {
  // ...
}

7.8 【必须】 使用默认参数时避免副作用。原因? 他们很容易混淆。

var b = 1;
// bad
function count(a = b++) {
  console.log(a);
}
count();  // 1
count();  // 2
count(3); // 3
count();  // 3

7.9 【推荐】 总是把默认参数放在最后。

// bad
function handleThings(opts = {}, name) {
  // ...
}

// good
function handleThings(name, opts = {}) {
  // ...
}

7.10 【推荐】 永远不要使用函数构造器来创建一个新函数。

原因? 以这种方式创建一个函数跟 eval() 差不多,将会导致漏洞。

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

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

7.11 【必须】 函数声明语句中需要空格。

原因? 一致性很好,在删除或添加名称时不需要添加或删除空格。

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

// good
const x = function () {};
const y = function a() {};

7.12 【推荐】 不要改变入参。
原因? 操作入参对象会导致原始调用位置出现意想不到的副作用。

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

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

7.13 【推荐】 不要对入参重新赋值,也不要给入参的属性赋值。部分要求修改入参的常用库(如 Koa、Vuex)可以豁免。

原因? 重新赋值参数会导致意外的行为,尤其是在访问 arguments 对象的时候。 它还可能导致性能优化问题,尤其是在 V8 中。

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

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

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

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

7.14 【推荐】 优先使用扩展运算符 ... 来调用可变参数函数。

原因? 它更加清晰,你不需要提供上下文,并且能比用 apply 来执行可变参数的 new 操作更容易些。

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

7.15 【推荐】 调用或者书写一个包含多个参数的函数应该像这个指南里的其他多行代码写法一样: 每行值包含一个参数,并且最后一行也要以逗号结尾。

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

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

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

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

8. 箭头函数

 

posted @ 2022-02-10 11:48  smallbore  阅读(174)  评论(0编辑  收藏  举报
回顶部