Help us learn about your current experience with the documentation. Take the survey.

JavaScript 风格指南

我们使用 Airbnb JavaScript 风格指南 及其配套的 linter 来管理我们的大部分 JavaScript 风格规范。

除了 Airbnb 设定的风格指南外,我们还有一些特定的规则 如下所述。

你可以通过运行 yarn run lint:eslint:allyarn run lint:eslint $PATH_TO_FILE 来本地运行 ESLint。

避免使用 forEach

在修改数据时避免使用 forEach。在修改数据时,使用 mapreducefilter 替代 forEach。 这可以最小化函数中的修改,符合 Airbnb 风格指南 的要求。

// bad
users.forEach((user, index) => {
  user.id = index;
});

// good
const usersWithId = users.map((user, index) => {
  return Object.assign({}, user, { id: index });
});

限制参数数量

如果你的函数或方法有超过 3 个参数,请使用对象作为参数 替代。

// bad
function a(p1, p2, p3, p4) {
  // ...
};

// good
function a({ p1, p2, p3, p4 }) {
  // ...
};

避免使用类处理 DOM 事件

如果类的唯一目的是绑定 DOM 事件和处理回调,优先 使用函数。

// bad
class myClass {
  constructor(config) {
    this.config = config;
  }

  init() {
    document.addEventListener('click', () => {});
  }
}

// good

const myFunction = () => {
  document.addEventListener('click', () => {
    // 在这里处理回调
  });
}

将元素容器传递给构造函数

当你的类操作 DOM 时,将元素容器作为参数接收。 这样更易于维护且性能更好。

// bad
class a {
  constructor() {
    document.querySelector('.b');
  }
}

// good
class a {
  constructor(options) {
    options.container.querySelector('.b');
  }
}

将字符串转换为整数

将字符串转换为整数时,Number 在语义上更清晰且可读性更好。两者都可以使用,但 Number 有轻微的可维护性优势。

parseInt 必须包含 基数参数

// bad (缺少基数参数)
parseInt('10');

// good
parseInt("106", 10);

// good
Number("106");
// bad (缺少基数参数)
things.map(parseInt);

// good
things.map(Number);

如果字符串可能表示非整数(包含小数点的数字),不要使用 parseInt。考虑使用 NumberparseFloat

CSS 选择器 - 使用 js- 前缀

如果 CSS 类仅在 JavaScript 中用作元素的引用,请为 类名添加 js- 前缀。

// bad
<button class="add-user"></button>

// good
<button class="js-add-user"></button>

ES 模块语法

对于大多数 JavaScript 文件,使用 ES 模块语法来导入或导出模块。 优先使用命名导出,因为它们可以提高名称一致性。

// bad (有例外,见下文)
export default SomeClass;
import SomeClass from 'file';

// good
export { SomeClass };
import { SomeClass } from 'file';

在少数特定情况下可以使用默认导出:

  • Vue 单文件组件 (SFCs)
  • Vuex mutation 文件

更多信息请参见 RFC 20

CommonJS 模块语法

我们的 Node 配置要求使用 CommonJS 模块语法。优先使用命名导出。

// bad
module.exports = SomeClass;
const SomeClass = require('./some_class');

// good
module.exports = { SomeClass };
const { SomeClass } = require('./some_class');

模块的绝对路径与相对路径

如果导入的模块在向上不超过两个层级,请使用相对路径。

// bad
import GitLabStyleGuide from '~/guides/GitLabStyleGuide';

// good
import GitLabStyleGuide from '../GitLabStyleGuide';

如果导入的模块在向上两个或更多层级,请使用绝对路径:

// bad
import GitLabStyleGuide from '../../../guides/GitLabStyleGuide';

// good
import GitLabStyleGuide from '~/GitLabStyleGuide';

此外,不要添加到全局命名空间

不要在非页面模块中使用 DOMContentLoaded

导入的模块每次加载时应该表现相同。DOMContentLoaded 事件只允许在 /pages/* 目录中加载的模块上使用,因为那些 模块是使用 webpack 动态加载的。

避免 XSS

不要使用 innerHTMLappend()html() 来设置内容。这会带来太多 安全漏洞。

ESLint

ESLint 的行为可以在我们的 工具指南 中找到。

IIFEs

避免使用 IIFEs(立即调用函数表达式)。尽管 我们有很多将内容包装在 IIFEs 中的文件示例, 但在从 Sprockets 迁移到 webpack 后,这已不再必要。 不要再使用它们,在重构遗留代码时可以放心移除它们。

全局命名空间

避免添加到全局命名空间。

// bad
window.MyClass = class { /* ... */ };

// good
export default class MyClass { /* ... */ }

副作用

顶层副作用

任何包含 export 的脚本中禁止顶层副作用:

// bad
export default class MyClass { /* ... */ }

document.addEventListener("DOMContentLoaded", function(event) {
  new MyClass();
}

避免在构造函数中使用副作用

避免在 constructor 中进行异步调用、API 请求或 DOM 操作。 将它们移到单独的函数中。这样更容易编写测试, 并且避免违反 单一职责原则

// bad
class myClass {
  constructor(config) {
    this.config = config;
    axios.get(this.config.endpoint)
  }
}

// good
class myClass {
  constructor(config) {
    this.config = config;
  }

  makeRequest() {
    axios.get(this.config.endpoint)
  }
}
const instance = new myClass();
instance.makeRequest();

纯函数和数据修改

努力编写许多小的纯函数,并最小化修改发生的位置

// bad
const values = {foo: 1};

function impureFunction(items) {
  const bar = 1;

  items.foo = items.a * bar + 2;

  return items.a;
}

const c = impureFunction(values);

// good
var values = {foo: 1};

function pureFunction (foo) {
  var bar = 1;

  foo = foo * bar + 2;

  return foo;
}

var c = pureFunction(values.foo);

将常量作为基本类型导出

优先使用通用命名空间导出常量基本类型,而不是导出对象。这允许更好的编译时引用检查,并有助于避免运行时意外出现 undefined。此外,它有助于减少包大小。

只有当需要迭代常量时(例如,用于属性验证器),才将常量作为集合(数组或对象)导出。

// bad
export const VARIANT = {
  WARNING: 'warning',
  ERROR: 'error',
};

// good
export const VARIANT_WARNING = 'warning';
export const VARIANT_ERROR = 'error';

// good, 如果需要迭代这些常量
export const VARIANTS = [VARIANT_WARNING, VARIANT_ERROR];

错误处理

对于服务器返回 500 的内部服务器错误,你应该返回 一个通用的错误消息。

当后端返回错误时,这些错误应该 适合向用户显示。

如果由于某种原因难以做到这一点,作为最后的手段,你可以 通过添加前缀来选择特定的错误消息:

  1. 确保后端为要显示的错误消息添加前缀:

    Gitlab::Utils::ErrorMessage.to_user_facing('Example user-facing error-message')
  2. 使用 app/assets/javascripts/lib/utils/error_message.js 中包含的错误消息工具函数。

该工具接受两个参数:从服务器响应接收的错误对象和 默认错误消息。该工具检查错误对象中的消息是否有前缀, 指示该消息是否面向用户。如果消息面向用户,工具将按原样返回它。 否则,它返回作为参数传递的默认错误消息。

import { parseErrorMessage } from '~/lib/utils/error_message';

onError(error) {
  const errorMessage = parseErrorMessage(error, genericErrorText);
}

请注意,此前缀不能用于 API 响应。相反,请遵循 REST APIGraphQL 指南 来了解如何使用错误对象。