今回は、カスタマイズ可能でメンテナンス可能な JS コードの書き方と、カスタマイズ可能でメンテナンス可能な JS コードを書く際の注意点について説明します。以下は実際のケースです。見てみましょう。
1.1 書式設定
インデントレベルについて: 「タブかスペースか」対「2 か 4 か 6 か 8 スペース」の議論を始めたくありません。このトピックは、インデントに関連するものであっても、何時間も議論することができます。プログラマーの価値観に合わせて。次の 3 つの点を覚えておく必要があります:
コードはインデントされ、位置が揃えられている必要があります。
同じプロジェクト内でタブとスペースを混在させないでください。
チームのスタイルに一貫性を保ちましょう。
末尾のセミコロンについて: パーサーの自動セミコロン挿入 (ASI) メカニズムに依存して、JS コードはセミコロンが省略されても正常に動作します。 ASI は、コード内でセミコロンを使用する必要があるが存在しない場所を自動的に検索し、セミコロンを挿入します。ほとんどのシナリオでは、ASI はエラーを引き起こすことなくセミコロンを正しく挿入しますが、ASI のセミコロン挿入規則は非常に複雑で覚えにくいため、セミコロンを省略しないことをお勧めします。ほとんどのスタイル ガイド (JavaScript 標準スタイルを除く) では、セミコロンを省略しないことを推奨しています。
行の長さについて: ほとんどの言語と JS コーディング スタイル ガイドでは、行の長さが 80 文字であると指定されています。この値は、昔のテキスト エディターの 1 行の最大文字数制限に基づいています。エディターでは 1 行に表示できる最大文字数は 80 文字のみです。80 文字を超える行は折り返されるか非表示になりますが、これは望ましくありません。また、私は行の長さを 80 文字に制限する傾向があります。
改行について: 行の長さが 1 行の最大文字数制限に達した場合は、1 行を手動で 2 行に分割する必要があります。通常、演算子の後の行を折り返し、次の行で 2 レベルのインデントを追加します (個人的には 1 レベルのインデントは問題ないと思いますが、インデントなしは絶対にだめです)。例:
callFunc(document, element, window, 'test', 100, true);
この例では、カンマは演算子であり、前の行の終わりとして使用する必要があります。シナリオによっては ASI メカニズムが行末にセミコロンを挿入するため、この改行位置は非常に重要です。常に行末に演算子を配置することで、ASI が自らセミコロンを挿入することがなくなり、エラーが回避されます。このルールには例外が 1 つあります。値を変数に代入するときは、2 行目の位置を代入演算子の位置と揃える必要があります。例:
var result = something + anotherThing + yetAnotherThing + somethingElse + anotherSomethingElse;
このコードでは、コードの読みやすさを確保し、折り返されたテキストのコンテキストが一目で明確にわかるように、変数 anotherSomethingElse と行頭の something が左揃えになっています。
空白行について: プログラミング仕様では、空白行は無視されることが多い側面です。一般に、コードは 1 つの大きな連続したテキスト ブロックではなく、一連の読みやすい段落のように見える必要があります。場合によっては、コードの一部のセマンティクスが別のコードの部分に関連していない場合、意味的に関連するコードが一緒に表示されるように、空白行を使用してコードを区切る必要があります。一般に、次のシナリオでは空行を追加することをお勧めします:
メソッド間に。
メソッド内のローカル変数と最初のステートメントの間。
複数行または単一行のコメントの前。
読みやすさを向上させるために、メソッド内の論理フラグメントの間に空行を挿入します。
1.2 命名
命名は、変数、定数、関数、コンストラクターの 4 つのカテゴリに分類されます。変数と関数では小さなキャメル ケースの命名 (最初の文字が小文字) が使用され、コンストラクターでは大きなキャメル ケース (最初の文字が大文字) が使用されます。および定数はすべて大文字を使用し、単語をアンダースコアで区切ります。
let myAge; // 変数: CamelCase の名前付け const PAGE_SIZE; // 定数: すべて大文字、単語の区切りにはアンダースコアを使用 function getAge() {} // 通常の関数: CamelCase の名前付け function Person() {} // コンストラクター : Camel大文字と小文字の名前付け
変数と関数を区別するために、変数名には名前を接頭辞として付け、関数名には動詞を接頭辞として付ける必要があります (コンストラクター名は通常名詞です)。次の例を見てください:
let count = 10; // Goodlet getCount = 10; // Bad, look like functionfunction getName() {} // Goodfunction theName() {} // Bad, look like variable
ネーミングは科学であるだけでなくテクノロジーでもありますが、一般的に言えば、ネーミングの長さはできるだけ短く、要点を押さえておく必要があります。値のデータ型を変数名に反映するようにしてください。たとえば、count、length、size という名前はデータ型が数値であることを示し、name、title、message という名前はデータ型が文字列であることを示します。ただし、i、j、k などの 1 文字で名前が付けられた変数は通常、ループ内で使用されます。データ型を反映したこれらの名前を使用すると、他の人や自分自身がコードを読みやすくなります。
foo、bar、tmp などの無意味な名前の使用は避けてください。関数とメソッドの名前付けでは、最初の単語は動詞にする必要があります。 動詞の使用に関する一般的な規則をいくつか示します。
can 関数はブール値を返します
has 関数はブール値を返します
is関数はブール値を返します
get 関数は非ブール値を返します
set 関数は値を保存するために使用されます
1.3 直接数量
JS中包含一些类型的原始值:字符串、数字、布尔值、null和undefined。同样也包含对象直接量和数组直接量。这其中,只有布尔值是自解释(self-explanatory)的,其他的类型或多或少都需要思考一下它们如何才能更精确地表示出来。
关于字符串:字符串可以用双引号也可以用单引号,不同的JS规范推荐都不同, 但切记不可在一个项目中混用单引号和双引号。
关于数字:记住两点建议:第一,为了避免歧义,请不要省略小数点之前或之后的数字;第二,大多数开发者对八进制格式并不熟悉,也很少用到,所以最好的做法是在代码中禁止八进制直接量。
// 不推荐的小数写法:没有小数部分let price = 10.;// 不推荐的小数写法:没有整数部分let price = .1;// 不推荐的写法:八进制写法已经被弃用了let num = 010;
关于null:null是一个特殊值,但我们常常误解它,将它和undefined搞混。在下列场景中应当使用null。
用来初始化一个变量,这个变量可能赋值为一个对象。
用来和一个已经初始化的变量比较,这个变量可以是也可以不是一个对象。
当函数的参数期望是对象时,用作参数传入。
当函数的返回值期望是对象时,用作返回值传出。
还有下面一些场景不应当使用null。
不要使用null来检测是否传入了某个参数。
不要用null来检测一个未初始化的变量。
理解null最好的方式是将它当做对象的占位符(placeholder)。这个规则在所有的主流编程规范中都没有提及,但对于全局可维护性来说至关重要。
关于undefined:undefined是一个特殊值,我们常常将它和null搞混。其中一个让人颇感困惑之处在于null == undefined结果是true。然而,这两个值的用途却各不相同。那些没有被初始化的变量都有一个初始值,即undefined,表示这个变量等待被赋值。比如:
let person; // 不好的写法console.log(person === undefined); // true
尽管这段代码能正常工作,但我建议避免在代码中使用undefined。这个值常常和返回"undefined"的typeof运算符混淆。事实上,typeof的行为也很让人费解,因为不管是值是undefined的变量还是未声明的变量,typeof运算结果都是"undefined"。比如:
// foo未被声明let person;console.log(typeof person); // "undefined"console.log(typeof foo); // "undefined"
这段代码中,person和foo都会导致typeof返回"undefined",哪怕person和foo在其他场景中的行为有天壤之别(在语句中使用foo会报错,而使用person则不会报错)。
通过禁止使用特殊值undefined,可以有效地确保只在一种情况下typeof才会返回"undefined":当变量为声明时。如果你使用了一个可能(或者可能不会)赋值为一个对象的变量时,则将其赋值为null。
// 好的做法let person = null;console.log(person === null); // true
将变量初始值赋值为null表明了这个变量的意图,它最终很可能赋值为对象。typeof运算符运算null的类型时返回"object", 这样就可以和undefined区分开了。
关于对象直接量和数组直接量: 请直接使用直接量语法来创建对象和数组,避免使用Object和Array构造函数来创建对象和数组。
1.4 注释
注释是代码中最常见的组成部分。它们是另一种形式的文档,也是程序员最后才舍得花时间去写的。但是,对于代码的总体可维护性而言,注释是非常重要的一环。JS支持两种注释:单行注释和多行注释。
很多人喜欢在双斜线后敲入一个空格,用来让注释文本有一定的偏移(我非常推荐你这么做)。单行注释有三种使用方法:
独占一行的注释,用来解释下一行代码。这行注释之前总是有一个空行,且缩进层级和下一行代码保持一致。
在代码行的尾部的注释。代码结束到注释之间至少有一个缩进。注释(包括之前的代码部分)不应当超过最大字符数限制,如果超过了,就将这条注释放置于当前代码行的上方。
被注释的大段代码(很多编辑器都可以批量注释掉多行代码)。
单行注释不应当以连续多行注释的形式出现,除非你注释掉一大段代码。只有当需要注释一段很长的文本时才使用多行注释。
虽然多行注释也可以用于注释单行,但是我还是推荐仅在需要使用多行注释的时候,才使用多行注释。多行注释一般用于以下场景:
模块、类、函数开头的注释
需要使用多行注释
我十分推荐你使用Java风格的多行注释,看起来十分美观,而且很多编辑器支持自动生成,见如下示例:
/** * Java风格的注释,注意*和注释之间 * 有一个空格,并且*左边也有一个空格。 * 你甚至可以加上一些@参数来说明一些东西。 * 例如: * * @author 作者 * @param Object person */
何时添加注释是程序员经常争论的一个话题。一个通行的指导原则是, 当代码不够清晰时添加注释,而当代码很明了时不应当添加注释。 基于这个原则,我推荐你在下面几种情况下添加注释:
难以理解的代码: 难以理解的代码通常都应当加注释。根据代码的用途,你可以用单行注释、多行注释,或者混用这两种注释。关键是让其他人更容易读懂这段代码。
可能被误认为错误的代码: 例如这段代码while(el && (el = el.next)) {}。在团队开发中,总是会有一些好心的开发者在编辑代码时发现他人的代码错误,就立即将它修复。有时这段代码并不是错误的源头,所以“修复”这个错误往往会制造其他错误,因此本次修改应当是可追踪的。当你写的代码有可能会被别的开发者认为有错误时,则需要添加注释。
浏览器特性hack: 这个写过前端的都知道,有时候你不得不写一些低效的、不雅的、彻头彻尾的肮脏代码,用来让低版本浏览器正常工作。
1.5 语句和表达式
关于 花括号的对齐方式 ,有两种主要的花括号对齐风格。第一种风格是,将左花括号放置在块语句中第一句代码的末尾,这种风格继承自Java;第二种风格是将左花括号放置于块语句首行的下一行,这种风格是随着C#流行起来的,因为Visual Studio强制使用这种对齐方式。当前并无主流的JS编程规范推荐这种风格,Google JS风格指南明确禁止这种用法,以免导致错误的分号自动插入。我个人也推荐使用第一种花括号对齐格式。
// 第一种花括号对齐风格if (condition) { }// 第二种花括号对齐风格if (condition) { }
关于块语句间隔: 有下面三种风格,大部分的代码规范都推荐使用第二种风格:
// 第一种风格if(condition){ doSomething(); }// 第二种风格if (condition) { doSomething(); }// 第三种风格if ( condition ) { doSomething(); }
关于switch语句,很多JS代码规范都没有对此做详细的规定,一个是而实际工作中你也会发现使用场景比较少。因为你只有在有很多条件判断的情况下才会用switch(短条件就直接用if语句了),但是熟练的程序员面对很多的判断条件一般都会用对象表查询来解决这个问题。看如下推荐的风格代码:
switch (condition) { case 'cond1': case 'cond2': doCond1(); break; case 'cond3': doCond3(); break; default: doDefault(); }
推荐你遵循如下的风格:
switch后的条件括号需要前后各一个空格;
case语句需要相对switch语句缩进一个层级;
允许多个case语句共用一个处理语句;
如果没有默认执行代码,可以不用加default
关于with:JS引擎和压缩工具无法对有with语句的代码进行优化,因为它们无法猜出代码的正确含义。在严格模式中,with语句是被明确禁止的,如果使用则报语法错误。这表明ECMAScript委员会确信with不应当继续使用。我也强烈推荐避免使用with语句。
关于for循环:for循环有两种,一种是传统的for循环,是JS从C和Java中继承而来,主要用于遍历数组成员;另外一种是for-in循环,用来遍历对象的属性。
针对for循环, 我推荐尽可能避免使用continue,但也没有理由完全禁止使用,它的使用应当根据代码可读性来决定。
for-in循环是用来遍历对象属性的。不用定义任何控制条件,循环将会有条不紊地遍历每个对象属性,并返回属性名而不是值。for-in循环有一个问题,就是它不仅遍历对象的实例属性(instance property),同样还遍历从原型继承来的属性。当遍历自定义对象的属性时,往往会因为意外的结果而终止。出于这个原因,最好使用hasOwnProperty()方法来为for-in循环过滤出实例属性。我也推荐你这么做,除非你确实想要去遍历对象的原型链,这个时候你应该加上注释说明一下。
// 包含对原型链的遍历for (let prop in obj) { console.log(`key: ${prop}; value: ${obj[prop]}`); }for (let prop in obj) { if (obj.hasOwnProperty(prop)) { console.log(`key: ${prop}; value: ${obj[prop]}`); } }
关于for-in循环,还有一点需要注意,即for-in循环是用来遍历对象的。一个常见的错误用法是使用for-in循环来遍历数组成员,它的结果可能不是你想要的(得到的是数组下标),你应该使用ES6的for-of循环来遍历数组。
let arr = ['a', 'b', 'c'];for (let i in arr) { console.log(i); // 0, 1, 2}for (let v of arr) { console.log(v); // 'a', 'b', 'c'}
1.6 变量声明
我们知道JS中var声明的变量存在变量提升,对变量提升不熟悉的同学写代码的时候就会产生不可意料的Bug。例如:
function func () { var result = 10 + result; var value = 10; return result; // return NaN}// 实际被解释成function func () { var result; var value; result = 10 + result; value = 10; return result; }
在某些场景中,开发者往往会漏掉变量提升,for语句就是其中一个常见的例子(因为ES5之前没有块级作用域):
function func (arr) { for (var i = 0, len = arr.length; i < len; i += 1) {} }// 实际被解释成function func (arr) { var i, len; for (i = 0, len = arr.length; i < len; i += 1) {} }
变量声明提前意味着:在函数内部任意地方定义变量和在函数顶部定义变量是完全一样的。 因此,一种流行的风格是将你所有变量声明放在函数顶部而不是散落在各个角落。简言之,依照这种风格写出的代码逻辑和JS引擎解析这段代码的习惯是非常相似的。我也建议你总是将局部变量的定义作为函数内第一条语句。
function func (arr) { var i, len; var value = 10; var result = value + 10; for (i = 0; len = arr.length; i < len; i += 1) { console.log(arr[i]); } }
当然,如果你有机会使用ES6,那我强烈推荐你完全抛弃var,直接用let和const来定义变量。相信我,抛弃var绝对值得的,let和const提供了块级作用域,比var更安全可靠,行为更可预测。
1.7 函数声明与调用
和变量声明一样,函数声明也会被JS引擎提升。因此,在代码中函数的调用可以出现在函数声明之前。但是,我们推荐总是先声明JS函数然后使用函数。此外,函数声明不应当出现在语句块之内。例如,这段代码就不会按照我们的意图来执行:
// 不好的写法if (condition) { function func () { alert("Hi!"); } } else { function func () { alert("Yo!"); } }
这段代码在不同浏览器中的运行结果也是不尽相同的。不管condition的计算结果如何,大多数浏览器都会自动使用第二个声明。而Firefox则根据condition的计算结果选用合适的函数声明。这种场景是ECMAScript的一个灰色地带,应当尽可能地避免。函数声明应当在条件语句的外部使用。这种模式也是Google的JS风格指南明确禁止的。
一般情况下,对于函数调用写法推荐的风格是,在函数名和左括号之间没有空格。这样做是为了将它和块语句区分开发。
// 好的写法callFunc(params);// 不好的写法,看起来像一个块语句callFunc (params);// 用来做对比的块语句while (condition) {}
1.8 立即调用的函数
IIFE(Immediately Invoked Function Expression),意为立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数。ES6中很少使用了,因为有模块机制,而IIFE最主要的用途就是来模拟模块隔离作用域的。下面有一些推荐的IIFE写法:
// 不好的写法:会让人误以为将一个匿名函数赋值给了这个变量var value = function () { return { msg: 'Hi' }; }();// 为了让IIFE能够被一眼看出来,可以将函数用一对圆括号包裹起来// 好的写法var value = (function () { return { msg: 'Hi' }; }());// 好的写法var value = (function () { return { msg: 'Hi' }; })();
1.9 严格模式
如果你在写ES5代码,推荐总是使用严格模式。不推荐使用全局的严格模式,可能会导致老的代码报错。推荐使用函数级别的严格模式,或者在IIFE中使用严格模式。
1.10 相等
关于JS的强制类型转换机制,我们不得不承认它确实很复杂,很难全部记住(主要是懒)。所以我推荐你,任何情况下,做相等比较请用===和!==。
1.11 eval
动态执行JS字符串可不是一个好主意,在下面几种情况中,都可以动态执行JS,我建议你应该避免这么做,除非你精通JS,并且知道自己在做什么。
eval("alert('bad')");const func = new Function("alert bad('bad')"); setTimeout("alert('bad')", 1000); setInterval("alert('bad')", 1000);
1.12 原始包装类型
JS装箱和拆箱了解下,原始值是没有属性和方法的,当我们调用一个字符串的方法时,JS引擎会自动把原始值装箱成一个对象,然后调用这个对象的方法。但这并不意味着你应该使用原始包装类型来创建对应的原始值,因为开发者的思路常常会在对象和原始值之间跳来跳去,这样会增加出bug的概率,从而使开发者陷入困惑。你也没有理由自己手动创建这些对象。
// 自动装箱const name = 'Nicholas';console.log(name.toUpperCase());// 好的写法const name = 'Nicholas';const author = true;const count = 10;// 不好的写法const name = new String('Nicholas');const author = new String(true);const count = new Number(10);
1.13 工具
团队开发中,为了保持风格的统一,Lint工具必不可少。因为即使大家都明白要遵守统一的编程风格,但是写代码的时候总是不经意就违背风格指南的规定了(毕竟人是会犯错的)。这里我推荐你使用ESLint工具进行代码的风格检查,你没必要完全重新写配置规则,你可以继承已有的业内优秀的JS编码规范来针对你团队做微调。我这里推荐继承自Airbnb JavaScript Style Guide,当然,你也可以继承官方推荐的配置或者Google的JS编码风格,其实在编码风格上,三者在大部分的规则上是相同的,只是在一部分细节上不一致而已。
当然,如果你实在是太懒了,那了解一下JavaScript Standard Style,它是基于ESLint的一个JS风格检查工具,有自己的一套风格,强制你必须遵守。可配置性没有直接引入ESLint那么强,如果你很懒并且能够接受它推荐的风格,那使用StandardJS倒也无妨。
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
以上がカスタマイズ可能で保守可能な JS コードの書き方の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。