书写具备一致风格、通俗易懂 JavaScript 的原则

原文出处 https://github.com/rwaldron/idiomatic.js/blob/master/readme.md

 另一篇jQuery官网上的js代码规范 http://contribute.jquery.org/style-guide/js/#type-checks

前言

下面的章节描述的是一个 合理 的现代 JavaScript 开发风格指南,并非硬性规定。其想送出的核心理念是高度统一的代码风格(the law of code style consistency)。你为项目所择风格都应为最高准则。作为一个描述放置于你的项目中,并链接到这个文档作为代码风格一致性、可读性和可维护性的保证。

Idiomatic 风格宣言

  1. 空白

    • 永远都不要混用�空格和Tab。
    • 开始一个项目,在写代码之前,选择软缩进(空格)或者 Tab(作为缩进方式),并将其作为最高准则
      • 为了可读, 我总是推荐在你的编辑中设计2个字母宽度的缩进 — 这等同于两个空格或者两个空格替代一个 Tab。
    • 如果你的编辑器支持,请总是打开 “显示不可见字符” 这个设置。好处是:
      • 保证一致性
      • 去掉行末的空格
      • 去掉空行的空格
      • 提交和对比更具可读性
  2. 美化语法

    A. 小括号, 花括号, 换行

    // if/else/for/while/try 通常都有小括号、花括号和多行
    // 这有助于可读
    
    // 2.A.1.1
    // 难辨语法(cramped syntax)的例子
    
    if(condition) doSomething(); while(condition) iterating++; for(var i=0;i<100;i++) someIterativeFn(); // 2.A.1.1 // 使用空格来提升可读性 if ( condition ) { // 语句 } while ( condition ) { // 语句 } for ( var i = 0; i < 100; i++ ) { // 语句 } // 更好的做法: var i, length = 100; for ( i = 0; i < length; i++ ) { // 语句 } // 或者... var i = 0, length = 100; for ( ; i < length; i++ ) { // 语句 } var prop; for ( prop in object ) { // 语句 } if ( true ) { // 语句 } else { // 语句 }

    B. 赋值, 声明, 函数 ( 命名函数, 函数表达式, 构建函数 )

    // 2.B.1.1
    // 变量
    var foo = "bar", num = 1, undef; // 字面量标识: var array = [], object = {}; // 2.B.1.2 // 在一个作用域(函数)内只使用一个 `var` 有助于提升可读性 // 并且让你的声明列表变得有条不紊 (还帮你省了几次键盘敲击) // 不好 var foo = ""; var bar = ""; var qux; // 好 var foo = "", bar = "", quux; // 或者.. var // 对这些变量的注释 foo = "", bar = "", quux; // 2.B.1.3 // `var` 语句必须总是在各自作用域(函数)顶部 // 同样适应于来自 ECMAScript 6 的常量 // 不好 function foo() { // 在变量前有语句 var bar = "", qux; } // 好 function foo() { var bar = "", qux; // 所有语句都在变量之后 }
    // 2.B.2.1
    // 命名函数声明
    function foo( arg1, argN ) { } // 使用方法 foo( arg1, argN ); // 2.B.2.2 // 命名函数声明 function square( number ) { return number * number; } // 使用方法 square( 10 ); // 非常不自然的连带传参(continuation passing)风格 function square( number, callback ) { callback( number * number ); } square( 10, function( square ) { // 回调内容 }); // 2.B.2.3 // 函数表达式 var square = function( number ) { // 返回有价值的、相关的内容 return number * number; }; // 带标识符的函数表达式 // 这种首选形式有附加的功能让其可以调用自身 // 并且在堆栈中有标识符 var factorial = function factorial( number ) { if ( number < 2 ) { return 1; } return number * factorial( number-1 ); }; // 2.B.2.4 // 构造函数声明 function FooBar( options ) { this.options = options; } // 使用方法 var fooBar = new FooBar({ a: "alpha" }); fooBar.options; // { a: "alpha" } 

    C. 异常, 细节

    // 2.C.1.1
    // 带回调的函数
    foo(function() {
      // 注意:在第一函数调用的小括号和 `function` 处并没有空格
    });
    
    // 函数接受 `array` 作为参数,没有空格 foo([ "alpha", "beta" ]); // 2.C.1.2 // 函数接受 `object` 作为参数,没有空格 foo({ a: "alpha", b: "beta" }); // 函数接受 `string` 字面量作为参数,没有空格 foo("bar"); // 分组用的小括号内部,没有空格 if ( !("foo" in obj) ) { } 

    D. 一致性(统一)总是笑到最后的(Consistency Always Wins)

    在 2.A-2.C 节,留白作为一个推荐方式被提出,基于单纯的、更高的目的:统一。值得注意的是格式化偏好,像“内部留白”必须是可选的,但在整个项目的源码中必须只存在着一种。

    // 2.D.1.1
    
    if (condition) {
      // 语句
    }
    
    while (condition) {
      // 语句
    }
    
    for (var i = 0; i < 100; i++) { // 语句 } if (true) { // 语句 } else { // 语句 } 

    E. 引号

    无论你选择单引号还是双引号都无所谓,在 JavaScript 中它们在解析上没有区别。而绝对需要强制的是一致性。 永远不要在同一个项目中混用两种引号,选择一种,并保持一致

    F. 行末和空行

    留白会破坏区别并使用变更不可读。考虑包括一个预提交的 hook 自动删除行末和空行中的空格。

  3. 类型检测 (来源于 jQuery Core Style Guidelines)

    A. 直接类型(实际类型,Actual Types)

    String:

    typeof variable === "string"
    

    Number:

    typeof variable === "number"
    

    Boolean:

    typeof variable === "boolean"
    

    Object:

    typeof variable === "object"
    

    Array:

    Array.isArray( arrayLikeObject )
    (如果可能的话)
    

    Node:

    elem.nodeType === 1
    

    null:

    variable === null
    

    null or undefined:

    variable == null
    

    undefined:

    全局变量:

    typeof variable === "undefined"
    

    局部变量:

    variable === undefined
    

    属性:

    object.prop === undefined
    object.hasOwnProperty( prop )
    "prop" in object
    

    B. 转换类型(强制类型,Coerced Types)

    考虑下面这个的含义...

    给定的 HTML:

    <input type="text" id="foo-input" value="1"> 
    // 3.B.1.1
    
    // `foo` 已经被赋予值 `0`,类型为 `number`
    var foo = 0;
    
    // typeof foo; // "number" ... // 在后续的代码中,你需要更新 `foo`,赋予在 input 元素中得到的新值 foo = document.getElementById("foo-input").value; // 如果你现在测试 `typeof foo`, 结果将是 `string` // 这意味着你在 if 语句检测 `foo` 有类似于此的逻辑: if ( foo === 1 ) { importantTask(); } // `importantTask()` 将永远不会被执行,即使 `foo` 有一个值 "1" // 3.B.1.2 // 你可以巧妙地使用 + / - 一元运算符强制转换类型以解决问题: foo = +document.getElementById("foo-input").value; // ^ + 一元运算符将它右边的运算对象转换为 `number` // typeof foo; // "number" if ( foo === 1 ) { importantTask(); } // `importantTask()` 将被调用

    对于强制类型转换这里有几个例子:

    // 3.B.2.1
    
    var number = 1,
      string = "1", bool = false; number; // 1 number + ""; // "1" string; // "1" +string; // 1 +string++; // 1 string; // 2 bool; // false +bool; // 0 bool + ""; // "false"
    // 3.B.2.2
    
    var number = 1,
      string = "1", bool = true; string === number; // false string === number + ""; // true +string === number; // true bool === number; // false +bool === number; // true bool === string; // false bool === !!string; // true
    // 3.B.2.3
    
    var array = [ "a", "b", "c" ]; !!~array.indexOf("a"); // true !!~array.indexOf("b"); // true !!~array.indexOf("c"); // true !!~array.indexOf("d"); // false // 值得注意的是上述都是 "不必要的聪明" // 采用明确的方案来比较返回的值 // 如 indexOf: if ( array.indexOf( "a" ) >= 0 ) { // ... }
    // 3.B.2.3
    
    
    var num = 2.5;
    
    parseInt( num, 10 ); // 等价于... ~~num; num >> 0; num >>> 0; // 结果都是 2 // 时刻牢记心底, 负值将被区别对待... var neg = -2.5; parseInt( neg, 10 ); // 等价于... ~~neg; neg >> 0; // 结果都是 -2 // 但是... neg >>> 0; // 结果即是 4294967294 
  4. 对比运算

    // 4.1.1
    // 当只是判断一个 array 是否有长度,相对于使用这个:
    if ( array.length > 0 ) ... // ...判断真伪, 请使用这种: if ( array.length ) ... // 4.1.2 // 当只是判断一个 array 是否为空,相对于使用这个: if ( array.length === 0 ) ... // ...判断真伪, 请使用这种: if ( !array.length ) ... // 4.1.3 // 当只是判断一个 string 是否为空,相对于使用这个: if ( string !== "" ) ... // ...判断真伪, 请使用这种: if ( string ) ... // 4.1.4 // 当只是判断一个 string 是为空,相对于使用这个: if ( string === "" ) ... // ...判断真伪, 请使用这种: if ( !string ) ... // 4.1.5 // 当只是判断一个引用是为真,相对于使用这个: if ( foo === true ) ... // ...判断只需像你所想,享受内置功能的好处: if ( foo ) ... // 4.1.6 // 当只是判断一个引用是为假,相对于使用这个: if ( foo === false ) ... // ...使用叹号将其转换为真 if ( !foo ) ... // ...需要注意的是:这个将会匹配 0, "", null, undefined, NaN // 如果你 _必须_ 是布尔类型的 false,请这样用: if ( foo === false ) ... // 4.1.7 // 如果想计算一个引用可能是 null 或者 undefined,但并不是 false, "" 或者 0, // 相对于使用这个: if ( foo === null || foo === undefined ) ... // ...享受 == 类型强制转换的好处,像这样: if ( foo == null ) ... // 谨记,使用 == 将会令 `null` 匹配 `null` 和 `undefined` // 但不是 `false`,"" 或者 0 null == undefined 

    总是判断最好、最精确的值,上述是指南而非教条。

    // 4.2.1
    // 类型转换和对比运算说明
    
    // 首次 `===`,`==` 次之 (除非需要松散类型的对比)
    
    // `===` 总不做类型转换,这意味着:
    
    "1" === 1; // false // `==` 会转换类型,这意味着: "1" == 1; // true // 4.2.2 // 布尔, 真 & 伪 // 布尔: true, false // 真: "foo", 1 // 伪: "", 0, null, undefined, NaN, void 0 
  5. 实用风格

    // 5.1.1
    // 一个实用的模块
    
    (function( global ) {
      var Module = (function() { var data = "secret"; return { // 这是一个布尔值 bool: true, // 一个字符串 string: "a string", // 一个数组 array: [ 1, 2, 3, 4 ], // 一个对象 object: { lang: "en-Us" }, getData: function() { // 得到 `data` 的值 return data; }, setData: function( value ) { // 返回赋值过的 `data` 的值 return ( data = value ); } }; })(); // 其他一些将会出现在这里 // 把你的模块变成全局对象 global.Module = Module; })( this ); 
    // 5.2.1
    // 一个实用的构建函数
    
    (function( global ) {
    
      function Ctor( foo ) { this.foo = foo; return this; } Ctor.prototype.getFoo = function() { return this.foo; }; Ctor.prototype.setFoo = function( val ) { return ( this.foo = val ); }; // 不使用 `new` 来调用构建函数,你可能会这样做: var ctor = function( foo ) { return new Ctor( foo ); }; // 把我们的构建函数变成全局对象 global.ctor = ctor; })( this ); 
  6. 命名

    A. 你并不是一个人肉 编译器/压缩器,所以尝试去变身为其一。

    下面的代码是一个极糟命名的典范:

    // 6.A.1.1
    // 糟糕命名的示例代码
    
    function q(s) {
      return document.querySelectorAll(s); } var i,a=[],els=q("#foo"); for(i=0;i<els.length;i++){ a.push(els[i]);}

    毫无疑问,你写过这样的代码 —— 希望从今天它不再出现。

    这里有一份相同逻辑的代码,但拥有更健壮、贴切的命名(和一个可读的结构):

    // 6.A.2.1
    // 改善过命名的示例代码
    
    function query( selector ) {
      return document.querySelectorAll( selector ); } var idx = 0, elements = [], matches = query("#foo"), length = matches.length; for ( ; idx < length; idx++ ) { elements.push( matches[ idx ] ); } 

    一些额外的命名提示:

    // 6.A.3.1
    // 命名字符串
    
    `dog` 是一个 string
    
    
    // 6.A.3.2 // 命名 arrays `['dogs']` 是一个包含 `dog 字符串的 array   // 6.A.3.3 // 命名函数、对象、实例,等  camlCase; function 和 var 声明   // 6.A.3.4 // 命名构建器、原型,等  PascalCase; 构建函数   // 6.A.3.5 // 命名正则表达式  rDesc = //;   // 6.A.3.6 // 来自 Google Closure Library Style Guide  functionNamesLikeThis; variableNamesLikeThis; ConstructorNamesLikeThis; EnumNamesLikeThis; methodNamesLikeThis; SYMBOLIC_CONSTANTS_LIKE_THIS; 

    B. 面对 this

    除使用众所周知的 call 和 apply 外,总是优先选择 .bind( this ) 或者一个功能上等价于它的。创建 BoundFunction声明供后续调用,当没有更好的选择时才使用别名。

    // 6.B.1
    function Device( opts ) {
    
      this.value = null; // 新建一个异步的 stream,这个将被持续调用 stream.read( opts.path, function( data ) { // 使用 stream 返回 data 最新的值,更新实例的值 this.value = data; }.bind(this) ); // 控制事件触发的频率 setInterval(function() { // 发出一个被控制的事件 this.emit("event"); }.bind(this), opts.freq || 100 ); } // 假设我们已继承了事件发送器(EventEmitter) ;) 

    当不能运行时,等价于 .bind 的功能在多数现代 JavaScript 库中都有提供。

    // 6.B.2
    
    // 示例:lodash/underscore,_.bind()
    function Device( opts ) {
    
      this.value = null; stream.read( opts.path, _.bind(function( data ) { this.value = data; }, this) ); setInterval(_.bind(function() { this.emit("event"); }, this), opts.freq || 100 ); } // 示例:jQuery.proxy function Device( opts ) { this.value = null; stream.read( opts.path, jQuery.proxy(function( data ) { this.value = data; }, this) ); setInterval( jQuery.proxy(function() { this.emit("event"); }, this), opts.freq || 100 ); } // 示例:dojo.hitch function Device( opts ) { this.value = null; stream.read( opts.path, dojo.hitch( this, function( data ) { this.value = data; }) ); setInterval( dojo.hitch( this, function() { this.emit("event"); }), opts.freq || 100 ); } 

    提供一个候选,创建一个 this 的别名,以 self 作为标识符。这很有可能出 bug,应尽可能避免。

    // 6.B.3
    
    function Device( opts ) {
      var self = this; this.value = null; stream.read( opts.path, function( data ) { self.value = data; }); setInterval(function() { self.emit("event"); }, opts.freq || 100 ); } 

    C. 使用 thisArg

    好几个 ES 5.1 中的原型的方法都内置了一个特殊的 thisArg 标记,尽可能多地使用它

    // 6.C.1
    
    var obj;
    
    obj = { f: "foo", b: "bar", q: "qux" }; Object.keys( obj ).forEach(function( key ) { // |this| 现在是 `obj` console.log( this[ key ] ); }, obj ); // <-- 最后的参数是 `thisArg` // 打印出来... // "foo" // "bar" // "qux" 

    thisArg 在 Array.prototype.every、 Array.prototype.forEach、 Array.prototype.some、 Array.prototype.mapArray.prototype.filter 中都可以使用。

  7. Misc

    这个部分将要说明的想法和理念都并非教条。相反更鼓励对现存实践保持好奇,以尝试提供完成一般 JavaScript 编程任务的更好方案。

    A. 避免使用 switch,现代方法跟踪(method tracing)将会把带有 switch 表达式的函数列为黑名单。

    似乎在最新版本的 Firefox 和 Chrome 都对 switch 语句有重大改进。http://jsperf.com/switch-vs-object-literal-vs-module

    值得注意的是,改进可以这里看到: https://github.com/rwldrn/idiomatic.js/issues/13

    // 7.A.1.1
    // switch 语句示例
    
    switch( foo ) {
      case "alpha": alpha(); break; case "beta": beta(); break; default: // 默认分支 break; } // 7.A.1.2 // 一个可支持组合、重用的方法是使用一个对象来存储 “cases”, // 使用一个 function 来做委派: var cases, delegator; // 返回值仅作说明用 cases = { alpha: function() { // 语句 // 一个返回值 return [ "Alpha", arguments.length ]; }, beta: function() { // 语句 // 一个返回值 return [ "Beta", arguments.length ]; }, _default: function() { // 语句 // 一个返回值 return [ "Default", arguments.length ]; } }; delegator = function() { var args, key, delegate; // 把 `argument` 转换成数组 args = [].slice.call( arguments ); // 从 `argument` 中抽出最前一个值 key = args.shift(); // 调用默认分支 delegate = cases._default; // 从对象中对方法进行委派操作 if ( cases.hasOwnProperty( key ) ) { delegate = cases[ key ]; } // arg 的作用域可以设置成特定值, // 这种情况下,|null| 就可以了 return delegate.apply( null, args ); }; // 7.A.1.3 // 使用 7.A.1.2 中的 API: delegator( "alpha", 1, 2, 3, 4, 5 ); // [ "Alpha", 5 ] // 当然 `case` key 的值可以轻松地换成任意值 var caseKey, someUserInput; // 有没有可能是某种形式的输入? someUserInput = 9; if ( someUserInput > 10 ) { caseKey = "alpha"; } else { caseKey = "beta"; } // 或者... caseKey = someUserInput > 10 ? "alpha" : "beta"; // 然后... delegator( caseKey, someUserInput ); // [ "Beta", 1 ] // 当然还可以这样搞... delegator(); // [ "Default", 0 ] 

    B. 提前返回值提升代码的可读性并且没有太多性能上的差别

    // 7.B.1.1
    // 不好:
    function returnLate( foo ) {
      var ret; if ( foo ) { ret = "foo"; } else { ret = "quux"; } return ret; } // 好: function returnEarly( foo ) { if ( foo ) { return "foo"; } return "quux"; } 
  8. 原生 & 宿主对象(注:其实一直觉得 Host Objects 真不应该翻译过来,这是就按一般书的写法翻出来吧)

    最基本的原则是:

    不要干任何蠢事,事情总会变好的。

    为了加强这个观念,请观看这个演示:

    “一切都被允许: 原生扩展” by Andrew Dupont (JSConf2011, Portland, Oregon)

    http://www.everytalk.tv/talks/441-JSConf-Everything-is-Permitted-Extending-Built-ins

  9. 注释

    • 单行注释放于代码上方为首选
    • 多行也可以
    • 行末注释应被避免!
    • JSDoc 的方式也不错,但需要比较多的时间

转载于:https://www.cnblogs.com/xyyz2014/p/5136371.html

你可能感兴趣的:(书写具备一致风格、通俗易懂 JavaScript 的原则)