Skip to content

wwwy3y3/idiomatic.js

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

撰寫一個一致、慣用的 JavaScript

這個文件永遠歡迎大家給予新想法提昇這份文件。貢獻方式:fork, clone, branch, commit, push, pull request.

所有的程式庫應該要像是同一個人所寫出來的,不論有多少人一起貢獻程式

下面所用的實例是我用在我的程式中,所有在我的 project 中的貢獻者都要遵守這些原則。

我不會強制用我的程式風格在別人的程式碼或專案中。如果有一個一般性的風格我會尊重。

"Arguments over style are pointless. There should be a style guide, and you should follow it"

Rebecca Murphey

 

"Part of being a good steward to a successful project is realizing that writing code for yourself is a Bad Idea™. If thousands of people are using your code, then write your code for maximum clarity, not your personal preference of how to get clever within the spec."

Idan Gazit

翻譯

重要、非常通用的資源

程式品質工具、資料與文獻

變聰明

下列的資源: 1)並不完全, 2) 必讀。雖然我不是每次都認同下面的撰寫風格。但是他們都具有一致性。此外,這些都具有權威性。

建構和部屬流程

project 應該都要有一般的程式碼檢驗,測試和壓縮準備給發佈使用。關於這個任務現在已經有由 Ben Alman 所開發的 grunt 屬於第一,已經替代了這個 repo "kits/" 目錄做為官方工具。

測試工具

專案 必須 包含一些 unit, reference, implementation 或是 functional 測試。寫一些 demos 並不代表 "測試"。下面列了一系列的 frameworks, 沒有哪個 framework 比另一個還好。

目錄


前言

這個下面的章節提出了一些 合理的 風格指導給 JavaScript 開發,但這些都不是規定。最重要的是 一致的程式碼撰寫風格。不論你選擇哪種風格在你的專案中使用都需要有些原則。這個文件是用來建立一個專案一致性、易讀性、可維持性的共識

慣用的風格

  1. Whitespace
  • 永遠不要把 spaces 和 tabs 混著用。

  • 在開始專案前,請先選擇要用 spaces 或是用 real tabs,根據這個 規則

    • 為了易讀性,我一直建議設定你的編輯器到縮排兩個字元 &mdash,這代表兩個 spaces 或是兩個空格代表一個 tab。
  • 如果你的編輯器,開著 "顯示不可見字符" 這個設定的好處是:

    • 一致性
    • 去掉行末的空白
    • 去掉空行的空格
    • 提交或比對更易讀
  1. Beautiful Syntax A. 小括號、大括號、換行

    // if/else/for/while/try 同常有小括號、大括號和多行
    // 有助於易讀性
    
    // 2.A.1.1
    // 難辨是的語法
    
    if(condition) doSomething();
    
    while(condition) iterating++;
    
    for(var i=0;i<100;i++) someIterativeFn();
    
    
    // 2.A.1.1
    // 用空白來增進易讀性
    
    if ( condition ) {
      // statements
    }
    
    while ( condition ) {
      // statements
    }
    
    for ( var i = 0; i < 100; i++ ) {
      // statements
    }
    
    // 更好的方式:
    
    var i,
      length = 100;
    
    for ( i = 0; i < length; i++ ) {
      // statements
    }
    
    // 或是:
    
    var i = 0,
      length = 100;
    
    for ( ; i < length; i++ ) {
      // statements
    }
    
    var prop;
    
    for ( prop in object ) {
      // statements
    }
    
    
    if ( true ) {
      // statements
    } else {
      // statements
    }

    B. 賦值、宣告、函式 ( 命名, 表達式, 建構式 )

    // 2.B.1.1
    // 變數
    var foo = "bar",
      num = 1,
      undef;
    
    // 字元符號:
    var array = [],
      object = {};
    
    
    // 2.B.1.2
    // 只用一個 `var` 在一個 function 中增進易讀性。
    // 並且讓你的宣告更清楚(也減少了一些 keystrokes)
    
    // 不好
    var foo = "";
    var bar = "";
    var qux;
    
    // 好
    var foo = "",
      bar = "",
      quux;
    
    // or..
    var // Comment on these
    foo = "",
    bar = "",
    quux;
    
    // 2.B.1.3
    // var 的宣告應一直在一開始的 scope。
    // 同樣的應用來自於 ECMAScript 6 的變數
    
    // 不好
    function foo() {
    
      // some statements here
    
      var bar = "",
        qux;
    }
    
    // 好
    function foo() {
      var bar = "",
        qux;
    
      // all statements after the variables declarations.
    }
    // 2.B.2.1
    // 命名函式宣告
    function foo( arg1, argN ) {
    
    }
    
    // 用法
    foo( arg1, argN );
    
    
    // 2.B.2.2
    // 命名函式宣告
    function square( number ) {
      return number * number;
    }
    
    // 用法
    square( 10 );
    
    // Really contrived continuation passing style
    function square( number, callback ) {
      callback( number * number );
    }
    
    square( 10, function( square ) {
      // callback statements
    });
    
    
    // 2.B.2.3
    // 函式表達式
    var square = function( number ) {
      // 返回有值的和相關的
      return number * number;
    };
    
    // Function Expression with Identifier
    // This preferred form has the added value of being
    // able to call itself and have an identity in stack traces:
    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
    // 函式與 callback 函式
    foo(function() {
      // 注意第一個函式的小括號和 `function` 沒有空格
    });
    
    // 函式接受 `array` 作為一個參數,沒有空格
    foo([ "alpha", "beta" ]);
    
    // 2.C.1.2
    // 函式接受 `object` 作為一個參數,沒有空格
    foo({
      a: "alpha",
      b: "beta"
    });
    
    // 單一一個變數,沒有空格
    foo("bar");
    
    // 分組用的小括號內部,沒有空格
    if ( !("foo" in obj) ) {
    
    }

    D. 一致性

    在 2.A-2.C 中, 空白建議用四個,用來簡化和更高的目的:一致性。 這是非常重要的規格喜好,像是 "inner whitespace" 應該是個 optional,但是應該只有一個風格橫跨所有的企劃。

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

    E. 引號

    不論你使用的是雙引號或是單引號都可以,在 JavaScript 中沒有差別。最重要的是要保持一致性。 絕對不要同時混著兩個用,選擇了一個就保持下去。

    F. 行末和空行

    留白會破壞區別使得更困難閱讀。考慮包含一個 pre-commit hook 來移除行末的空白和空白行。

    1. Type Checking (Courtesy jQuery Core Style Guidelines)

    A. 實際類型

    String:

     typeof variable === "string"
    

    Number:

     typeof variable === "number"
    

    Boolean:

     typeof variable === "boolean"
    

    Object:

     typeof variable === "object"
    

    Array:

     Array.isArray( arrayLikeObject )
     (wherever possible)
    

    Node:

     elem.nodeType === 1
    

    null:

     variable === null
    

    null or undefined:

     variable == null
    

    undefined:

    Global Variables:

     typeof variable === "undefined"
    

    Local Variables:

     variable === undefined
    

    Properties:

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

    B. 轉換類型

    思考下面這些的含意

    給定 HTML:

    <input type="text" id="foo-input" value="1">
    // 3.B.1.1
    
    // `foo` 被賦予值 `0` 他的類別為 `number`
    var foo = 0;
    
    // typeof foo;
    // "number"
    ...
    
    // 後續的程式中,你要更新 `foo`
    // 賦予他一個新的值
    
    foo = document.getElementById("foo-input").value;
    
    // 如果你現在測試 `typeof foo` ,結果應該是 `string`
    // 這代表如果你要測試 `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.4
    
    
    var num = 2.5;
    
    parseInt( num, 10 );
    
    // 同等...
    
    ~~num;
    
    num >> 0;
    
    num >>> 0;
    
    // 結果 2
    
    
    // 記得負數的結果會完全不一樣...
    
    var neg = -2.5;
    
    parseInt( neg, 10 );
    
    // 同等於...
    
    ~~neg;
    
    neg >> 0;
    
    // All result in -2
    // 然而...
    
    neg >>> 0;
    
    // Will result in 4294967294
    
    
    
  2. Conditional Evaluation

    // 4.1.1
    // 判斷一個 array 有沒有 length,相對於下面的:
    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
    // 判斷一個 sting 是空值,相對於這個
    if ( string === "" ) ...
    
    // 判斷真假,請使用這個
    if ( !string ) ...
    
    
    // 4.1.5
    // 當只是判斷一個參數是真
    // 與其用這個:
    if ( foo === true ) ...
    
    // ...判斷像你所想的,更好的是用:
    if ( foo ) ...
    
    
    // 4.1.6
    // 當要判斷他為假
    // 與其用這個:
    if ( foo === false ) ...
    
    // 用 negation 來強迫用 ture 的方式定義
    if ( !foo ) ...
    
    // ...注意,這些都會符合: 0, "", null, undefined, NaN
    // 你 _必須_ 測試 boolean false,的時候用:
    if ( foo === false ) ...
    
    
    // 4.1.7
    // 如果要確定一個參數可能為 null 或是 undefined, 但 NOT false, "" 或 0,
    // 與其用這個:
    if ( foo === null || foo === undefined ) ...
    
    // ...建議用 == type 來強制,像是:
    if ( foo == null ) ...
    
    // 記得使用 == 這會符合 `null` 同時在 `null` 和 `undefined` 的情形下
    // 但不會在 `false`, "" or 0
    null == undefined

    ALWAYS evaluate for the best, most accurate result - the above is a guideline, not a dogma.

    永遠 evaluate 最好是越精確、越明確越好 - 以上都是 guideline,不是教條。

    // 4.2.1
    // 強迫轉型和值的比較
    
    //  `===` 較 `==` 好 (除非你需要的是更寬鬆的比較)
    
    // === 不會強迫轉型,這代表
    
    "1" === 1;
    // false
    
    // == 做強迫轉型,這代表:
    
    "1" == 1;
    // true
    
    
    // 4.2.2
    // Booleans, Truthies & Falsies
    
    // Booleans:
    true, false
    
    // Truthy:
    "foo", 1
    
    // Falsy:
    "", 0, null, undefined, NaN, void 0
  3. Practical Style

    // 5.1.1
    // 一個實用的模組
    
    (function( global ) {
      var Module = (function() {
    
        var data = "secret";
    
        return {
          // This is some boolean property
          bool: true,
          // Some string value
          string: "a string",
          // An array property
          array: [ 1, 2, 3, 4 ],
          // An object property
          object: {
            lang: "en-Us"
          },
          getData: function() {
            // get the current value of `data`
            return data;
          },
          setData: function( value ) {
            // set the value of `data` and return it
            return ( data = value );
          }
        };
      })();
    
      // Other things might happen here
    
      // expose our module to the global object
      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 );
      };
    
    
      // To call constructor's without `new`, you might do this:
      var ctor = function( foo ) {
        return new Ctor( foo );
      };
    
    
      // expose our constructor to the global object
      global.ctor = ctor;
    
    })( this );
  4. Naming

    A. 人不是 compiler/comperessor,所以不要試著變成機器。

    下面的 code 是在示範過度的命名方式:

    // 6.A.1.1
    // Example of code with poor names
    
    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
    // Example of code with improved names
    
    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
    // Naming strings
    
    `dog` is a string
    
    
    // 6.A.3.2
    // Naming arrays
    
    `dogs` is an array of `dog` strings
    
    
    // 6.A.3.3
    // 命名 functions, objects, instances, etc
    
    camelCase; function and var declarations
    
    
    // 6.A.3.4
    // 命名 constructors, prototypes, etc.
    
    PascalCase; constructor function
    
    
    // 6.A.3.5
    // 命名 regular expressions
    
    rDesc = //;
    
    
    // 6.A.3.6
    // 從 Google Closure Library 程式風格指導:
    
    functionNamesLikeThis;
    variableNamesLikeThis;
    ConstructorNamesLikeThis;
    EnumNamesLikeThis;
    methodNamesLikeThis;
    SYMBOLIC_CONSTANTS_LIKE_THIS;

    B. 面對 this

    除了大家都知道的 callapply外,總是優先選擇 .bind( this ) 或是一個功能上等於他的。 創一個 BoundFunction 的宣告給予後續的調度。當沒有比較好的選擇的時候才使用別名。

    // 6.B.1
    function Device( opts ) {
    
      this.value = null;
    
      // 開啟一個非同步的 stream,
      // 這會被持續的呼叫
      stream.read( opts.path, function( data ) {
    
        // Update this instance's current value
        // with the most recent value from the
        // data stream
        this.value = data;
    
      }.bind(this) );
    
      // Throttle the frequency of events emitted from
      // this Device instance
      setInterval(function() {
    
        // Emit a throttled event
        this.emit("event");
    
      }.bind(this), opts.freq || 100 );
    }
    
    // Just pretend we've inherited EventEmitter ;)

    當不能運行,同樣功能的 .bind 在不同的 JavaScript 函式庫中都有出現

    When unavailable, functional equivalents to .bind exist in many modern JavaScript libraries.

    // 6.B.2
    
    // eg. 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 );
    }
    
    // eg. 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 );
    }
    
    // eg. 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 );
    }

    As a last resort, create an alias to this using self as an Identifier. This is extremely bug prone and should be avoided whenever possible.

    提供一個候選,創一個 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 prototype 的方法都內建了一個特殊的 thisArg 標記, 盡可能的避免使用他

    // 6.C.1
    
    var obj;
    
    obj = { f: "foo", b: "bar", q: "qux" };
    
    Object.keys( obj ).forEach(function( key ) {
    
      // |this| now refers to `obj`
    
      console.log( this[ key ] );
    
    }, obj ); // <-- the last arg is `thisArg`
    
    // Prints...
    
    // "foo"
    // "bar"
    // "qux"

    thisArg 可以被用在 Array.prototype.every, Array.prototype.forEach, Array.prototype.some, Array.prototype.map, Array.prototype.filter

  5. Misc

    這個部分會說明一些想法和概念,但這些都不是教條。相反的鼓勵對現在的用法保持好奇,且嘗試完成用更好了 JavaScript 的程式任務。

    A. 避免使用 switch,modern method tracing 會把 switch 的表達是列為黑名單。

    目前似乎對新版本的 Firefox 和 chrome 對 switch 表達式都有改進。 http://jsperf.com/switch-vs-object-literal-vs-module

    值得注意的是,改進的可以在這裡看到: rwaldron#13

    // 7.A.1.1
    // An example switch statement
    
    switch( foo ) {
      case "alpha":
        alpha();
        break;
      case "beta":
        beta();
        break;
      default:
        // something to default to
        break;
    }
    
    // 7.A.1.2
    // 一個支持組合和重複使用的方法是用 object 儲存 "cases" 和一個 function 來調用。
    
    
    var cases, delegator;
    
    // Example returns for illustration only.
    cases = {
      alpha: function() {
        // statements
        // a return
        return [ "Alpha", arguments.length ];
      },
      beta: function() {
        // statements
        // a return
        return [ "Beta", arguments.length ];
      },
      _default: function() {
        // statements
        // a return
        return [ "Default", arguments.length ];
      }
    };
    
    delegator = function() {
      var args, key, delegate;
    
      // Transform arguments list into an array
      args = [].slice.call( arguments );
    
      // shift the case key from the arguments
      key = args.shift();
    
      // Assign the default case handler
      delegate = cases._default;
    
      // Derive the method to delegate operation to
      if ( cases.hasOwnProperty( key ) ) {
        delegate = cases[ key ];
      }
    
      // The scope arg could be set to something specific,
      // in this case, |null| will suffice
      return delegate.apply( null, args );
    };
    
    // 7.A.1.3
    // Put the API in 7.A.1.2 to work:
    
    delegator( "alpha", 1, 2, 3, 4, 5 );
    // [ "Alpha", 5 ]
    
    // Of course, the `case` key argument could easily be based
    // on some other arbitrary condition.
    
    var caseKey, someUserInput;
    
    // Possibly some kind of form input?
    someUserInput = 9;
    
    if ( someUserInput > 10 ) {
      caseKey = "alpha";
    } else {
      caseKey = "beta";
    }
    
    // or...
    
    caseKey = someUserInput > 10 ? "alpha" : "beta";
    
    // And then...
    
    delegator( caseKey, someUserInput );
    // [ "Beta", 1 ]
    
    // And of course...
    
    delegator();
    // [ "Default", 0 ]
    

    B. 提早的 returns 提昇了程式的易讀性和些微的效能差別

    // 7.B.1.1
    // Bad:
    function returnLate( foo ) {
      var ret;
    
      if ( foo ) {
        ret = "foo";
      } else {
        ret = "quux";
      }
      return ret;
    }
    
    // Good:
    
    function returnEarly( foo ) {
    
      if ( foo ) {
        return "foo";
      }
      return "quux";
    }
  6. Native & Host Objects

    最基本的原則:

    別做蠢事,所有的東西都是可以接受的

    為了加強這個概念,請觀看下列的演說:

    “Everything is Permitted: Extending Built-ins” by Andrew Dupont (JSConf2011, Portland, Oregon)

    <iframe src="http://blip.tv/play/g_Mngr6LegI.html" width="480" height="346" frameborder="0" allowfullscreen></iframe>

    http://blip.tv/jsconf/jsconf2011-andrew-dupont-everything-is-permitted-extending-built-ins-5211542

  7. Comments

    如果是一行的話,就在那行的上面 comment

    多行 comment 很好

    在最後一行做 comment 是禁止的。

    JSDoc 的 comment 是好的,但是需要花費比較多的時間

  8. One Language Code

    程式應該都要用同一個語言編寫,不論是哪一種語言,那個決定權決定於維護者。

Appendix

Comma First.

所有只用這個風格指南的 project 都不允許用前置的逗號代碼格式,除非是明確的指定或作者要求。


Creative Commons License
Principles of Writing Consistent, Idiomatic JavaScript by Rick Waldron and Contributors is licensed under a Creative Commons Attribution 3.0 Unported License.
Based on a work at github.com/rwldrn/idiomatic.js.

About

Principles of Writing Consistent, Idiomatic JavaScript

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published