【翻译】Airbnb JavaScript Style Guide

乜烨霖
2023-12-01

Airbnb JavaScript Style Guide

译者的话:文章中有一些词句翻译的可能不是很准确,如果有错误的话欢迎大家及时指正。

文中的深蓝色字体是我的个人见解,可略过。

原文地址戳这里:Airbnb JavaScript Style Guide,里边作者还列出了一些其他的规范,可以参考看看。

1. 变量类型

  • 1.1 原始类型(Primitives)

    当你访问一个原始类型时,直接改变他的值。

    • string
    • number
    • boolean
    • null
    • undefined
    const foo = 1;
    let bar = foo;
    
    bar = 9;
    
    console.log(foo, bar); // => 1, 9
  • 1.2 复杂类型(Complex)

    当你访问一个复杂类型时,将对其进行引用。

    • 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
    eslint:
    prefer-const,
    no-const-assign

    为什么要这么用呢?因为这保证了你不能再次你更改引用变量,而更改引用变量可能会导致bug和代码理解困难。

    //bad
    var count = 1;
    if(true) {
    count += 1;
    }
    
    //good, use the let.
    let count = 1;
    if(true) {
    count += 1;
    }
  • 2.2 如果必须要改变变量的值,用let替代var
    eslint: no-var jscs: disallowVar

    为啥?因为let是块级作用域,好过使用函数作用域的var

    //bad
    var count = 1;
    if(true){
        count += 1;
    }
    
    //good, use the let.
    let count = 1;
    if(true) {
        count += 1; 
    }
  • 2.3 注意!letconst声明的变量都拥有块级作用域

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

3. 对象

  • 3.1 创建对象时,尽量使用字面量方法。
    eslint: no-new-object

    //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 使用对象快速创建法。
    eslint: object-shorthand
    jscs: requireEnhancedObjectLiterals

    // bad
    const atom = {
        value: 1;
    
        addValue: function(value) {
            return atom.value + value;
        },
    };
    
    //good
    const atom = {
        value: 1,
    
        addValue(value) {
            return atom.value + value;
        },
    };
  • 3.4 属性值简写。
    eslint: object-shorthand
    jscs: requireEnhancedObjectLiterals

    Why? => 简洁

    const lukeSkywalker = 'Luke Skywalker';
    
    //bad
    const obj = {
        lukeSkywalker: lukeSkywalker,
    };
    
    //good
    const obj = {
        lukeSkywalker,
    };
  • 3.5 将简写属性全部写在对象声明的开头

    Why? => 方便看出哪个简写了

    const anakinSkywalker = 'Anakin Skywalker';
    const lukeSkywalker = 'Luke Skywalker';
    
    // bad
    const obj = {
        episodeOne: 1,
        twoJediWalkIntoACantina: 2,
        lukeSkywalker,
        episodeThree: 3,
        mayTheFourth: 4,
        anakinSkywalker,
    };
    
    // good
    const obj = {
        lukeSkywalker,
        anakinSkywalker,
        episodeOne: 1,
        twoJediWalkIntoACantina: 2,
        episodeThree: 3,
        mayTheFourth: 4,
    };
  • 3.6 仅把含无效标示的属性用引号括起来。
    eslint: quote-props
    jscs: disallowQuoteKeyInObjects

    Why? 我们一般认为主观上更容易阅读,能帮助凸显语法,并且更容易被JS引擎优化。

    // 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`propertyIsEnmerable,and isPrototypeof

    Why? 这些属性有可能被对象属性掩盖了,如{ hasOwnProperty: false },或者可能是一个null对象(Object.create(null))

    // bad
    console.log(object.hasOwnProperty(key));
    
    // good
    console.log(Object.prototype.hasOwnProperty.call(object, key));
    
    // best
    const has = Object.prototype.hasOwnProperty; //     cache the lookup once, in module scope.
    /* or */
    import has from 'has';
    // ...
    console.log(has.call(object, key));
  • 3.8 Prefer the object spread operator over Object.assign to shallow-copy objects. Use the object rest operator to get a new object with certain properties omitted.

    // very bad
    const original = { a: 1, b: 2 };
    const copy = Object.assign(original, { c: 3 }); // this     mutates `original` ಠ_ಠ
    delete copy.a; // so does this
    
    // 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 创建时使用字面量方法。
    eslint: no-array-constructor

    //bad
    const items = new Array();
    
    //good
    const items = [];
  • 4.2 使用Array#push代替直接添加项到数组中。

    const someStack = [];
    
    // bad
    someStack[someStack.length] = 'despacito';
    
    // good
    someStack.push('despacito');
  • 4.3 拷贝数组时使用...

    //bad
    const len = items.length;
    const itemsCopy = [];
    let i;
    
    for(i=0; i<len; i+=1) {
        itemsCopy[i] = items[i];
    }
    
    //good
    const itemsSopy = [...items];
  • 4.4 把一个类数组的对象转换为数组时,用Array.from

    const foo document.querySelectorAll('.foo');
    const nodes = Array.from(foo);
  • 4.5 数组方法回调时使用return。如果function中只包含一些简单的逻辑,并且无副作用的话可以省略。
    following: 8.2
    eslint: array-callback-return

    // good
    [1, 2, 3].map((x) => {
        const y = x + 1;
        return x * y;
    });
    
    // good
    [1, 2, 3].map(x => x + 1);
    
    // bad
    const flat = {};
    [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
        const flatten = memo.concat(item);
        flat[index] = flatten;
    });
    
    // good
    const flat = {};
    [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
        const flatten = memo.concat(item);
        flat[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.6 如果一个数组中有很多行的话,在开始数组的括号后和结束数组的括号前使用换行符。

    // 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 访问和使用多属性对象时,使用对象解构。
    jscs: requireObjectDestructuring

    Why? 解构能把你从创建属性的临时引用中解放出来

    // 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 使用数组解构。jscs: requireArrayDestructuring

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

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

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

  • 5.3 要返回多个值的时候,不适用数组解构,而是使用对象解构。

    Why? 可以随时添加新的属性或者改变顺序,不必担心中断了请求。

    // bad
    function processInput(input) {
        // then a miracle occurs
        return [left, right, top, bottom];
    }
    
    // the caller needs to think about the order of return data
    const [left, __, top] = processInput(input);
    
    // good
    function processInput(input) {
        // then a miracle occurs
        return { left, right, top, bottom };
    }
    
    // the caller selects only the data they need
    const { left, top } = processInput(input);

6. 字符串

  • 6.1 字符串用单引号''
    eslint: quote
    jscs: validateQuoteMarks

    //bad
    const name = "Capt. Janeway";
    
    //bad - template literals should contain interpolation or newlines
    const name = `Capt.janway`;
    
    //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 以编程方式构建字符串时,使用模板字符串而不是直接串联起来。

    Why? 模板字符串可读性强,语法简介,还方便完成字符串插值。

    // 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(),漏洞多。
    eslint: no-eval
    JavaScript中的eval()不安全,可能会被利用做XSS攻击,eval也存在一个安全问题,因为它可以执行传给它的任何字符串,所以永远不要传入字符串或者来历不明和不受信任源的参数。

  • 6.5 字符串中不需要没必要的转移字符。
    eslint: no-useless-escape

    Why? 反斜线有碍可读性,所以应该在万不得已时才用。

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

7. 函数

  • 7.1 使用命名函数表达式而不是函数声明。
    eslint: func-style
    jscs: disallowFunctionDeclarations

    Why? 为什么?函数声明被挂起,这意味着在函数在文件中定义之前很容易引用函数。这会损害可读性和可维护性。如果您发现函数的定义足够大或足够复杂,它会干扰理解文件的其余部分,那么可能是时候将其解压缩到自己的模块中了!不要忘记给表达式命名——匿名函数会使查找错误的调用堆栈中的问题变得更加困难。(Discussion)

    // bad
    function foo(){
        //...
    }
    
    // bad
    const foo = function (){
        //...
    };
    
    // good
    const foo = function bar(){
        //...
    };
  • 7.2 把立即调用的表达式放入括号中。
    eslint: wrap-iife
    jscs: requireParentheseAroundIIFE

一个立即调用函数表达式是一个单独的单元。值得注意的是,在这个世界上,模块无处不在,你几乎从不需要IIFE

```
//immediately-invoked function expression(IIFE)
(function(){
    console.log('Welcome to the Internet. Please follow me');
})
```

* 7.3 永远都不要再一个非函数作用域(if, while, etc.)中声明函数。浏览器允许你这样做,但是他们各自都有不同的解释。
eslint: no-loop-func

  • 7.4 注意:ECMA-262中定义了block语句列表。函数声明不算是语句。Read ECMA-262’s note on this issue

    // 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, option, args) {
        //...
    }
  • 7.6 永远不要使用arguments,选择使用rest语法的...替代。
    eslint: prefer-rest-params

    Why? ...是明确表达你想要拉哪些参数,另外,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(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 bad
    function handleThings(opts) {
      if (opts === void 0) {
        opts = {};
      }
      // ...
    }
    
    // good
    function handleThings(opts = {}) {
      // ...
    }
  • 7.8 避免默认参数的副作用

    Why? 容易混淆

    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(opt = {}, name) {
        //...
    }
    
    //good
    function handleThings(name, opt = {}) {
        //...
    }
  • 7.10 永远不要使用函数构造函数来创建一个新函数。
    eslint: no-new-func

    Why?使用这种方式创建函数相当于字符串和eval()一样,会有漏洞。

    //bad
    var add = new Function('a', 'b', 'return a+b');
    
    //still bad
    var subtract = Function('a', 'b', 'return a-b');
  • 7.11 函数中使用空格。
    eslint: space-before-function-paren
    space-before-blocks

    Why? 可以保持一致性,而且添加或删除名字时不用添加或删除空格。

    //bad
    const f = function(){};
    const g = function (){};
    const h = function() {};
    
    //good
    const x = function () {};
    const y = function a() {};
  • 7.12 不要改写参数。
    eslint: no-param-ressign

    Why? 改写过的对象作为参数传递可能会导致原始调用函数出现不必要的问题。

    //bad
    function f1(obj) {
        obj.key = 1;
    }
    
    //good
    function f2(obj) {
        const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1
    }
  • 7.13 不要给参数再赋值。
    eslint: no-param-reassign

    Why? 参数再赋值会导致意外的行为发生,特别是当访问argument对象时。它还可能导致优化问题,特别是在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 调用可变参数函数时,使用扩展操作符...
    eslint: prefer-spread

    Why? 你不需要提供上下文,但是你也不能很轻易地用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.1 当您必须使用函数表达式(如传递匿名函数时),请使用箭头函数表示法。
    eslint: prefer-arrow-callback, arrow-spacing
    jscs: requireArrowFunctions

    Why? 它创建了的函数,有让可以执行this的上下文。这是一种很简洁的语法

    Why not? 如果具有相当复杂的函数,则可以将该逻辑移到自己的函数声明中。

    // 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;
    })
  • 8.2 如果函数体由一个语句组成,返回一个没有副作用的表达式,省略括号并使用隐式返回。或者,保留括号并使用返回语句。
    eslint: arrow-parens, arrow-body-style
    jscs: disallowParentheseAroundArrowParam, requireShorthandArrowFunctions

    Why? 语法糖。当多个函数连接在一起时,可读性比较强。

    // 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 effects
    function foo(callback) {
      const val = callback();
      if (val === true) {
        // Do something if callback returns true
      }
    }
    
    let bool = false;
    
    // bad
    foo(() => bool = true);
    
    // good
    foo(() => {
      bool = true;
    });
  • 8.3 如果表达式跨越多行,则用圆括号括起来以提高可读性。

    Why? 使函数开头和结尾都很明晰。

    // bad
    ['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call(
        httpMagicObjectWithAVeryLongName,
        httpMethod,
      )
    );
    
    // good
    ['get', 'post', 'put'].map(httpMethod => (
      Object.prototype.hasOwnProperty.call(
        httpMagicObjectWithAVeryLongName,
        httpMethod,
      )
    ));
  • 8.4 如果函数只有一行并且没有使用圆括号,可以直接省略括号。否则,始终将圆括号括在参数中以确保清楚性和一致性。注意:一直使用圆括号也可以,这种情况下使用“always” option的eslint,jscs则不使用disallowParenthesesAroundArrowParam
    eslint: arrow-parens
    jscs: disallowParenthesesAroundArrowParam

    Why? 减少视觉混乱

    // 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;
    });
  • 8.5 避免将箭头函数语法( => )与比较运算符混淆( <=, >= )。
    eslint: no-confusing-arrow

    // 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;
    };
 类似资料: