JavaScript 代码示例编写指南

以下指南涵盖了如何为 MDN Web Docs 编写 JavaScript 示例代码。本文列出了编写简洁示例的规则,以期这些示例将被尽可能多的人理解。

JavaScript 代码示例常规指南

本节解释了编写 JavaScript 代码示例时要牢记的一般准则。后面的部分将涵盖更具体的细节。

选择格式

关于正确缩进、空格和行的长度方面的观点一直存在争议。对这些主题的讨论会分散创作和维护内容的注意力。

在 MDN Web Docs,我们使用 Prettier 作为代码格式化工具以保持代码样式的一致性(同时避免偏离主题的讨论)。你可以查阅我们的配置文件了解当前规则,并阅读 Prettier 文档

Prettier 格式化所有代码并保持风格一致。尽管如此,你还需要遵守一些额外的规则。

使用现代 JavaScript 特性

如果新的特性被每个主流的浏览器——Chrome、Edge、Firefox 和 Safari 所支持,你就可以使用它们。

数组

创建数组

请使用字面量而非构造函数来创建数组。

像这样来创建数组:

js
const visitedCities = [];

而不要这样创建数组:

js
const visitedCities = new Array(length);

添加元素

向数组添加元素时,请使用 push() 而非直接赋值。考虑有如下数组:

js
const pets = [];

像这样向数组添加元素:

js
pets.push("cat");

而不要像这样向数组添加元素:

js
pets[pets.length] = "cat";

异步方法

编写异步代码可以提高性能,应尽可能使用。特别是,你可以使用:

当两种语法都可行时,我们倾向于使用更简单的 async/await 语法。不幸的是,你不能在顶层使用 await,除非你在 ECMAScript 模块中。但 Node.js 使用 CommonJS 模块而不是 ES 模块。因此,如果你打算在所有地方使用该示例,请避免顶层的 await

注释

注释对于编写良好的代码示例非常重要。它们清楚地阐明了代码的意图,并帮助开发者理解它。因此要特别注意它们。

  • 如果代码的目的或逻辑不明显,请添加表明你的意图的注释,就像下面这样:

    js
    let total = 0;
    
    // 计算数组 arr 的前四个元素的和
    for (let i = 0; i < 4; i++) {
      total += arr[i];
    }
    

    另一方面,请不要用文字重述代码来作为注释:

    js
    let total = 0;
    
    // 从 1 到 4 的 for 循环
    for (let i = 0; i < 4; i++) {
      // 将值累加到总和(total)
      total += arr[i];
    }
    
  • 当函数的名称清楚地描述了它们的作用时,不需要再注释。如:

    js
    closeConnection();
    

    不要写像下面这样的注释:

    js
    closeConnection(); // 关闭连接
    

使用单行注释

单行注释用 // 标记,与包含在 /* … */ 之间的块注释相反。

通常情况下,使用单行注释来注释代码。编写者必须用 // 标记每一行的注释,这样就更容易直观地注意到注释掉的代码。此外,这种约定允许在调试时使用 /* … */ 注释掉部分代码。

  • 在斜杠和空格之间留一个空格。(英文的)注释以大写字母开头,就像句子一样,但不要在结尾加句号。

    js
    // 这是一个良好的单行注释
    
  • 如果一个注释没有在新缩进级别之后开始,请在空行后添加注释。这将创建一个代码块,使得注释与代码的关联更加清晰。此外,将你的注释放在它们所指的代码之前的单独一行上。如下例所示:

    js
    function checkout(goodsPrice, shipmentPrice, taxes) {
      // 计算总价
      const total = goodsPrice + shipmentPrice + taxes;
    
      // 创建一个文档并向其添加一个新的段落
      const para = document.createElement("p");
      para.textContent = `总价是 ${total}`;
      document.body.appendChild(para);
    }
    

打印的输出

  • 对于预期在生产环境中运行的代码,你很少会需要对打印语句进行注释。在代码示例中,我们经常使用 console.log()console.error() 或类似的函数来输出重要的值。为了帮助读者在不运行代码的情况下理解会发生什么,你可以在打印函数之后注释输出的内容。比如:

    js
    function exampleFunc(fruitBasket) {
      console.log(fruitBasket); // ['香蕉', '芒果', '橙子']
    }
    

    请不要这样写:

    js
    function exampleFunc(fruitBasket) {
      // 输出:['香蕉', '芒果', '橙子']
      console.log(fruitBasket);
    }
    
  • 如果这样会让行变得太长,请将注释放在函数之后,如下所示:

    js
    function exampleFunc(fruitBasket) {
      console.log(fruitBasket);
      // ['香蕉', '芒果', '橙子', '苹果', '梨', '榴莲', '柠檬']
    }
    

多行注释

简短的注释通常更好,所以尝试将他们保持在 60–80 个字符的单行中。如果这不可能,请在每行的开头使用 //

js
// 这是一个多行注释的示例。
// 假设下面的函数有一些我想指出的不寻常的局限性。
// 局限 1
// 局限 2

请不要使用 /* … */

js
/* 这是一个多行注释的示例。
  假设下面的函数有一些我想指出的不寻常的局限性。
  局限 1
  局限 2 */

使用注释来标记省略号

为了保持示例简短,我们需要使用省略号(…)来跳过冗余代码。尽管如此,编写者应该仔细考虑,因为开发者经常复制粘贴示例到他们的代码中,所以我们所有的示例代码都应该是有效的 JavaScript。

在 JavaScript 中,你应该将省略号()放在注释之中。可能的话,请指出重用此代码片段的人预计会添加什么操作。

对省略号(…)使用注释更加明确,可以防止开发人员复制和粘贴示例代码时出现错误。比如:

js
function exampleFunc() {
  // 在此处添加你的代码
  // …
}

请不要像这样这样使用省略号(…):

js
function exampleFunc() {
  …
}

注释掉参数

在编写代码时,你通常会省略你不需要的参数。但在某些代码示例中,你想要显式的说明你没有使用某些可能的参数。

为此,请在参数列表中使用 /* … */。这是只使用单行注释(//)的规则的一个例外。

js
array.forEach((value /* , index, array */) => {
  // …
});

函数

函数名称

对于函数名称,请使用以小写字母开头的小驼峰式命名。酌情使用简洁、可读的和语义化的名称。

以下是函数名称的正确示例:

js
function sayHello() {
  console.log("你好!");
}

请不要使用像这样的函数名称:

js
function SayHello() {
  console.log("你好!");
}

function doIt() {
  console.log("你好!");
}

函数声明

  • 在可能的情况下,请使用函数声明而不是函数表达式来定义函数。

    以下是声明函数的推荐方式:

    js
    function sum(a, b) {
      return a + b;
    }
    

    以下不是定义函数的好方法:

    js
    let sum = function (a, b) {
      return a + b;
    };
    
  • 当使用匿名函数作为回调(传递给另一个方法调用的函数),如果你不需要访问 this,使用箭头函数可以让代码更加简洁明了。

    以下是推荐的方式:

    js
    const array1 = [1, 2, 3, 4];
    const sum = array1.reduce((a, b) => a + b);
    

    而不是这样:

    js
    const array1 = [1, 2, 3, 4];
    const sum = array1.reduce(function (a, b) {
      return a + b;
    });
    
  • 考虑避免使用箭头函数将函数分配给标识符。尤其是,不要对方法使用箭头函数。使用带有关键字 function 的函数声明:

    js
    function x() {
      // …
    }
    

    不要这样:

    js
    const x = () => {
      // …
    };
    
  • 使用箭头函数时,尽可能使用隐式返回(也称为表达式主体):

    js
    arr.map((e) => e.id);
    

    而不是:

    js
    arr.map((e) => {
      return e.id;
    });
    

循环和条件语句

循环初始化

当需要循环时,从 for(;;)for...ofwhile 等中选择合适的。

  • 在遍历集合中的所有元素时,避免使用传统的 for (;;) 循环;优先使用 for...offorEach()。请注意,如果你使用的是一个不是 Array 的集合,你必须检查 for...of 是否真的被支持(它需要变量是可迭代的),或者 forEach() 方法是否存在。

    使用 for...of

    js
    const dogs = ["旺财", "来福"];
    for (const dog of dogs) {
      console.log(dog);
    }
    

    forEach()

    js
    const dogs = ["旺财", "来福"];
    dogs.forEach((dog) => {
      console.log(dog);
    });
    

    请不要使用 for (;;)——你不仅需要添加额外的索引 i,你还必须跟踪数组的长度。这对于初学者来说很容易出错。

    js
    const dogs = ["旺财", "来福"];
    for (let i = 0; i < dogs.length; i++) {
      console.log(dogs[i]);
    }
    
  • 确保你正确地定义了初始化器,对 for...of 使用 const 关键字,或对其他循环使用 let。注意不要遗漏。以下是正确的示例:

    js
    const cats = ["汤姆", "胖橘"];
    for (const cat of cats) {
      console.log(cat);
    }
    
    for (let i = 0; i < 4; i++) {
      result += arr[i];
    }
    

    下面的示例没有遵循推荐的初始化准则(它隐式创建了全局变量,并且在严格模式下会失败):

    js
    const cats = ["汤姆", "胖橘"];
    for (i of cats) {
      console.log(i);
    }
    
  • 但是需要同时范围值和索引时,你可以使用 .forEach() 而不是 for (;;)。例如:

    js
    const gerbils = ["舒克", "贝塔"];
    gerbils.forEach((gerbil, i) => {
      console.log(`沙鼠 ${i} 号:${gerbil}`);
    });
    

    请不要这样写:

    js
    const gerbils = ["舒克", "贝塔"];
    for (let i = 0; i < gerbils.length; i++) {
      console.log(`沙鼠 ${i} 号:${gerbil}`);
    }
    

警告:切勿使用 for...in 遍历数组和字符串。

备注:考虑完全不使用 for 循环。如果你正在使用 Array(或者在某些操作中使用 String),请考虑使用更具语义的迭代方法,比如 map()every()findIndex()find()includes() 等。

控制语句

对于 if...else 控制语句,有一个值得注意的情况。如果 if 语句以 return 结尾,则不要添加 else 语句。

if 语句后继续,例如:

js
if (test) {
  // 如果 test 为真则执行某些操作
  // …
  return;
}

// 如果 test 为假则执行某些操作
// …

请不要这样写:

js
if (test) {
  //  如果 test 为真则执行某些操作
  // …
  return;
} else {
  // 如果 test 为假则执行某些操作
  // …
}

将大括号与控制流语句和循环一起使用

虽然像 ifforwhile 的控制流语句不需要在内容中只有一个语句时使用大括号,但你总是应该使用大括号。比如:

js
for (const car of storedCars) {
  car.paint("红色");
}

不要这样写:

js
for (const car of storedCars) car.paint("红色");

这可以防止忘记在添加更多语句时添加大括号。

Switch 语句

switch 语句可能有点棘手。

  • 不要在 return 语句后添加 break 语句。相反,请像下面这样编写 return 语句:

    js
    switch (species) {
      case "chicken":
        return farm.shed;
      case "horse":
        return corral.entry;
      default:
        return "";
    }
    

    如果你添加了 break 语句,它会是不可达的。不要这样写:

    js
    switch (species) {
      case "chicken":
        return farm.shed;
        break;
      case "horse":
        return corral.entry;
        break;
      default:
        return "";
    }
    
  • 使用 default 作为最后一个 case,不要在 default 后面添加 break 语句。如果你需要做不同的事情,请添加注释来解释为什么。

  • 请记住,当你为一个 case 声明一个局部变量时,你需要使用大括号来定义作用域:

    js
    switch (fruits) {
      case "Orange": {
        const slice = fruit.slice();
        eat(slice);
        break;
      }
      case "Apple": {
        const core = fruit.extractCore();
        recycle(core);
        break;
      }
    }
    

错误处理

  • 如果程序的某些状态引发未捕获的错误,它们会停止执行,并可能降低示例的可用性。因此,你应该使用 try...catch 块来捕获错误,如下所示:

    js
    try {
      console.log(getResult());
    } catch (e) {
      console.error(e);
    }
    
  • 在你不需要 catch 语句的参数时,请省略它:

    js
    try {
      console.log(getResult());
    } catch {
      console.error("发生了一个错误!");
    }
    

备注:请记住,只有可恢复的错误才应该被捕获和处理。所有不可恢复的错误都应该被抛出,并在调用栈中逐级向上冒泡。

对象

对象名称

  • 当定义一个类时,使用大驼峰式(以大写字母开头)来命名类,使用小驼峰式(以小写字母开头)来命名对象属性和方法名。

  • 当定义一个对象实例时,使用字面量或构造函数,请使用小驼峰式(以小写字母开头)为实例命名。比如:

    js
    const hanSolo = new Person("Han Solo", 25, "he/him");
    
    const luke = {
      name: "Luke Skywalker",
      age: 25,
      pronouns: "he/him",
    };
    

创建对象

对于创建普通对象(即,当不涉及类时),请使用字面量而非构造函数。

例如,这样做:

js
const object = {};

不要像这样创建普通对象:

js
const object = new Object();

对象类

  • 使用新的 ES 类语法进行定义,而不是构造函数。

    例如,这是推荐的方式:

    js
    class Person {
      constructor(name, age, pronouns) {
        this.name = name;
        this.age = age;
        this.pronouns = pronouns;
      }
    
      greeting() {
        console.log(`你好!我是 ${this.name}`);
      }
    }
    
  • 使用 extends 进行继承:

    js
    class Teacher extends Person {
      // …
    }
    

方法

要定义方法,请使用方法定义语法:

js
const obj = {
  foo() {
    // …
  },
  bar() {
    // …
  },
};

而不是:

js
const obj = {
  foo: function () {
    // …
  },
  bar: function () {
    // …
  },
};

对象属性

  • Object.prototype.hasOwnProperty() 方法已被弃用,取而代之的是 Object.hasOwn()

  • 在可能的情况下,使用简写来避免属性标识符的重复。比如:

    js
    function createObject(name, age) {
      return { name, age };
    }
    

    不要这样写:

    js
    function createObject(name, age) {
      return { name: name, age: age };
    }
    

运算符

本节列出了我们对使用哪些运算符以及何时使用的建议。

条件运算符

当你想要根据条件将一个字面量存储到变量中时,请使用条件(三元)运算符而不是 if...else 语句。此规则也适用于返回值时。比如:

js
const x = condition ? 1 : 2;

而不要这样:

js
let x;
if (condition) {
  x = 1;
} else {
  x = 2;
}

条件运算符在创建用来打印新信息的字符串时很有帮助。在这种情况下,使用常规的 if...else 语句会导致像打印日志这样的副操作变成长代码块,会混淆示例的重点。

严格相等运算符

优先使用严格相等(三等号)和不等运算符,而不是相等运算符(双等号)和不等运算符。

像这样使用严格相等和不等运算符:

js
name === "Shilpa";
age !== 25;

不要像下面这样使用宽松相等和不等运算符:

js
name == "Shilpa";
age != 25;

如果你需要使用 ==!=,请记住 == null 是唯一可接受的情况。由于 TypeScript 在所有其他情况下都会失败,因此我们不希望在示例代码中使用。考虑添加注释来解释你为什么需要它。

布尔值测试的简便方式

优先使用简便布尔值测试。例如,使用 if (x)if (!x),而不是 if (x === true)if (x === false),除非以不同的方式处理不同种类的真值或假值。

字符串

字符串字面量可以用单引号包含起来,比如 'A string',也可以用双引号包含起来,比如 "A string"。不过,请不要在意需要选择使用哪个,Prettier 会保持一致。

模版字面量

要将值插入字符串,请使用模板字面量

  • 这里是一个推荐的使用模板字面量的例子。它们的使用可以防止大量的空格错误。

    js
    const name = "Shilpa";
    console.log(`你好!我是 ${name}!`);
    

    请不要像这样串联字符串:

    js
    const name = "Shilpa";
    console.log("你好!我是 " + name + "!"); // 你好!我是 Shilpa!
    
  • 请不要过度使用模版字面量。如果没有替换的话,请使用普通字符串。

变量

变量名称

好的变量名对理解代码至关重要。

  • 使用简短的标识符,并避免不常用的缩写。好的变量名通常在 3 到 10 个字符之间,但这只是一个提示。例如,accelerometer 比为了字符长度而减短的缩写 acclmtr 更具描述性。

  • 尝试使用现实世界相关的例子,其中每个变量都有清晰的语义。只有在示例简单且随意时才使用占位名称,比如 foobar

  • 不要使用匈牙利命名法命名约定。不要在变量名前面加上类型。比如,不要写成 bBought = oCar.sBuyer != nullsName = "John Doe",而是写成 bought = car.buyer !== nullsName = "John Doe"

  • 对于集合,避免在命名中添加例如列表、数组、队列这样的类型名称。使用复数形式的内容名称。例如,对于汽车数组,使用 cars 而不是 carArraycarList。有时会有例外,比如你想在没有特定应用程序的上下文中展示一个特性的抽象形式。

  • 对于原始值,使用小驼峰命名法,以小写字母开头。不要使用 _。在适当的情况下,使用简洁、可读性好且语义化的名称。比如,使用 currencyName 而不是 currency_name

  • 避免使用冠词和所有格,例如,使用 car 而不是 myCaraCar。有时会有例外,比如你想在没有特定应用程序的上下文中展示一个特性的抽象形式。

  • 使用变量名如下所示:

    js
    const playerScore = 0;
    const speed = distance / time;
    

    不要像这样命名变量:

    js
    const thisIsaveryLONGVariableThatRecordsPlayerscore345654 = 0;
    const s = d / t;
    

备注:唯一不允许使用可读性好的语义名称的地方是存在非常公认的惯例,例如将 ij 用于循环迭代器。

变量声明

在声明变量和常量时,使用 letconst 关键字,而不是 var。下面的例子展示了 MDN Web 文档上推荐的和不推荐的用法:

  • 如果一个变量不会被重新赋值,请使用 const,如下所示:

    js
    const name = "Shilpa";
    console.log(name);
    
  • 如果你会改变变量的值,请使用 let,如下所示:

    js
    let age = 40;
    age++;
    console.log("生日快乐!");
    
  • 下面的示例在应该使用 const 的地方使用了 let,代码仍然可以运行,但我们希望在 MDN Web 文档代码示例中避免这种用法。

    js
    let name = "Shilpa";
    console.log(name);
    
  • 下面的示例使用 const 声明了一个变量,该变量会被重新赋值。重新赋值会抛出一个错误。

    js
    const age = 40;
    age++;
    console.log("生日快乐!");
    
  • 下面的示例使用了 var,这会污染全局作用域:

    js
    var age = 40;
    var name = "Shilpa";
    
  • 一行声明一个变量,如下所示:

    js
    let var1;
    let var2;
    let var3 = "张三";
    let var4 = var3;
    

    不要在一行中通过逗号或链式声明来声明多个变量。避免像这样声明变量:

    js
    let var1, var2;
    let var3 = var4 = "张三"; // var4 被隐式创建为全局变量;这在严格模式下会失败
    

强制类型转换

避免隐式强制类型转换。特别是,避免 +val 以强制将值转换未为数字,以及 "" + val 来将其强制转换为字符串。改为在不使用 new 的情况下使用 Number()String()。例如:

js
class Person {
  #name;
  #birthYear;

  constructor(name, year) {
    this.#name = String(name);
    this.#birthYear = Number(year);
  }
}

不要这样写:

js
class Person {
  #name;
  #birthYear;

  constructor(name, year) {
    this.#name = "" + name;
    this.#birthYear = +year;
  }
}

要避免的 Web API

除了这些 JavaScript 语言特性之外,我们还建议你牢记一些与 Web API 相关的准则。

避免浏览器前缀

如果所有主流浏览器(Chrome、Edge、Firefox 和 Safari)都支持某个特性,请不要在该特性前面加上前缀。比如:

js
const context = new AudioContext();

避免增加前缀的复杂性,不要这样写:

js
const AudioContext = window.AudioContext || window.webkitAudioContext;
const context = new AudioContext();

同样的规则也适用于 CSS 前缀。

避免已弃用的 API

当一个方法、属性或一整个接口都已经弃用,不要使用它(除了它的文档)。相反,使用现代的 API。

以下是要避免的 Web API 以及用什么来替换它们的非详尽列表:

  • 使用 fetch() 来替代 XHR(XMLHttpRequest)。
  • 在 Web 音频 API 中使用 AudioWorklet 来替代 ScriptProcessorNode

使用安全可靠的 API

  • 请不要使用 Element.innerHTML 将纯文本内容插入到元素中;而是使用 Node.textContent 替代。innerHTML 属性可能会导致安全问题,如果开发人员不控制参数。作为编写者,我们越避免使用它,开发者复制和粘贴我们的代码时产生的安全漏洞就越少。

    下面的示例演示了 textContent 的用法。

    js
    const text = "父老乡亲们大家好";
    const para = document.createElement("p");
    para.textContent = text;
    

    不要使用 innerHTML纯文本插入到 DOM 节点中。

    js
    const text = "父老乡亲们大家好";
    const para = document.createElement("p");
    para.innerHTML = text;
    
  • alert() 函数是不可靠的。在 MDN Web 文档,它在嵌入到 <iframe> 中的实时示例内无法正常工作。此外,它是对整个窗口的模态提示,这很令人讨厌。在静态代码示例中,使用 console.log()console.error()。在实时示例中,避免使用 console.log()console.error(),因为它们不会显示。而是使用专用的 UI 元素。

使用合适的打印方法

  • 当打印一个消息时,使用 console.log()
  • 当打印一个错误时,使用 console.error()

参见

JavaScript 参考——浏览我们的 JavaScript 参考页面,查看一些优秀、简洁、有意义的 JavaScript 代码片段。