前端JavaScript規範 非常詳細

破狼 Blog發表於2015-05-10

JavaScript規範

目錄

  1. 型別
  2. 物件
  3. 陣列
  4. 字串
  5. 函式
  6. 屬性
  7. 變數
  8. 條件表示式和等號
  9. 註釋
  10. 空白
  11. 逗號
  12. 分號
  13. 型別轉換
  14. 命名約定
  15. 存取器
  16. 構造器
  17. 事件
  18. 模組
  19. jQuery
  20. ES5 相容性
  21. HTML、CSS、JavaScript分離
  22. 使用jsHint
  23. 前端工具

型別

  • 原始值: 相當於傳值(JavaScript物件都提供了字面量),使用字面量建立物件。
    • string
    • number
    • boolean
    • null
    • undefined
var foo = 1,
    bar = foo;

bar = 9;

console.log(foo, bar); // => 1, 9

複雜型別: 相當於傳引用

  • object
  • array
  • function
var foo = [1, 2],
    bar = foo;

bar[0] = 9;

console.log(foo[0], bar[0]); // => 9, 9

物件

  • 使用字面值建立物件
    // bad
    var item = new Object();
    
    // good
    var item = {};
  • 不要使用保留字 reserved words 作為鍵
    // bad
    var superman = {
      class: 'superhero',
      default: { clark: 'kent' },
      private: true
    };
    
    // good
    var superman = {
      klass: 'superhero',
      defaults: { clark: 'kent' },
      hidden: true
    };

陣列

  • 使用字面值建立陣列
    // bad
    var items = new Array();
    
    // good
    var items = [];
  • 如果你不知道陣列的長度,使用push
    var someStack = [];
    
    // bad
    someStack[someStack.length] = 'abracadabra';
    
    // good
    someStack.push('abracadabra');
  • 當你需要拷貝陣列時使用slice. jsPerf
    var len = items.length,
        itemsCopy = [],
        i;
    
    // bad
    for (i = 0; i < len; i++) {
      itemsCopy[i] = items[i];
    }
    
    // good
    itemsCopy = items.slice();
  • 使用slice將類陣列的物件轉成陣列.
    function trigger() {
      var args = [].slice.apply(arguments);
      ...
    }

字串

  • 對字串使用單引號 ''(因為大多時候我們的字串。特別html會出現")
    // bad
    var name = "Bob Parr";
    
    // good
    var name = 'Bob Parr';
    
    // bad
    var fullName = "Bob " + this.lastName;
    
    // good
    var fullName = 'Bob ' + this.lastName;
  • 超過80(也有規定140的,專案具體可制定)個字元的字串應該使用字串連線換行
  • 注: 如果過度使用,長字串連線可能會對效能有影響. jsPerf & Discussion
    // bad
    var 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
    var 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
    var 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.';
  • 程式設計時使用join而不是字串連線來構建字串,特別是IE: jsPerf.
    var items,
        messages,
        length, i;
    
    messages = [{
        state: 'success',
        message: 'This one worked.'
    },{
        state: 'success',
        message: 'This one worked as well.'
    },{
        state: 'error',
        message: 'This one did not work.'
    }];
    
    length = messages.length;
    
    // bad
    function inbox(messages) {
      items = '<ul>';
    
      for (i = 0; i < length; i++) {
        items += '<li>' + messages[i].message + '</li>';
      }
    
      return items + '</ul>';
    }
    
    // good
    function inbox(messages) {
      items = [];
    
      for (i = 0; i < length; i++) {
        items[i] = messages[i].message;
      }
    
      return '<ul><li>' + items.join('</li><li>') + '</li></ul>';
    }

函式

  • 函式表示式:
    // 匿名函式表示式
    var anonymous = function() {
      return true;
    };
    
    // 有名函式表示式
    var named = function named() {
      return true;
    };
    
    // 立即呼叫函式表示式
    (function() {
      console.log('Welcome to the Internet. Please follow me.');
    })();
  • 絕對不要在一個非函式塊裡宣告一個函式,把那個函式賦給一個變數。瀏覽器允許你這麼做,但是它們解析不同。
  • 注: ECMA-262定義把定義為一組語句,函式宣告不是一個語句。閱讀ECMA-262對這個問題的說明.
    // bad
    if (currentUser) {
      function test() {
        console.log('Nope.');
      }
    }
    
    // good
    if (currentUser) {
      var test = function test() {
        console.log('Yup.');
      };
    }
  • 絕對不要把引數命名為 arguments, 這將會逾越函式作用域內傳過來的 arguments 物件.
    // bad
    function nope(name, options, arguments) {
      // ...stuff...
    }
    
    // good
    function yup(name, options, args) {
      // ...stuff...
    }

屬性

  • 當使用變數和特殊非法變數名時,訪問屬性時可以使用中括號(. 優先).
    var luke = {
      jedi: true,
      age: 28
    };
    
    function getProp(prop) {
      return luke[prop];
    }
    
    var isJedi = getProp('jedi');

變數

  • 總是使用 var 來宣告變數,如果不這麼做將導致產生全域性變數,我們要避免汙染全域性名稱空間。
    // bad
    superPower = new SuperPower();
    
    // good
    var superPower = new SuperPower();
  • 使用一個 var 以及新行宣告多個變數,縮排4個空格。
    // bad
    var items = getItems();
    var goSportsTeam = true;
    var dragonball = 'z';
    
    // good
    var items = getItems(),
        goSportsTeam = true,
        dragonball = 'z';
  • 最後再宣告未賦值的變數,當你想引用之前已賦值變數的時候很有用。
    // bad
    var i, len, dragonball,
        items = getItems(),
        goSportsTeam = true;
    
    // bad
    var i, items = getItems(),
        dragonball,
        goSportsTeam = true,
        len;
    
    // good
    var items = getItems(),
        goSportsTeam = true,
        dragonball,
        length,
        i;
  • 在作用域頂部宣告變數,避免變數宣告和賦值引起的相關問題。
    // bad
    function() {
      test();
      console.log('doing stuff..');
    
      //..other stuff..
    
      var name = getName();
    
      if (name === 'test') {
        return false;
      }
    
      return name;
    }
    
    // good
    function() {
      var name = getName();
    
      test();
      console.log('doing stuff..');
    
      //..other stuff..
    
      if (name === 'test') {
        return false;
      }
    
      return name;
    }
    
    // bad
    function() {
      var name = getName();
    
      if (!arguments.length) {
        return false;
      }
    
      return true;
    }
    
    // good
    function() {
      if (!arguments.length) {
        return false;
      }
    
      var name = getName();
    
      return true;
    }

條件表示式和等號

  • 合理使用 === 和 !== 以及 == 和 !=.
  • 合理使用表示式邏輯操作運算.
  • 條件表示式的強制型別轉換遵循以下規則:
    • 物件 被計算為 true
    • Undefined 被計算為 false
    • Null 被計算為 false
    • 布林值 被計算為 布林的值
    • 數字 如果是 +0, -0, or NaN 被計算為 false , 否則為 true
    • 字串 如果是空字串 '' 則被計算為 false, 否則為 true
      if ([0]) {
        // true
        // An array is an object, objects evaluate to true
      }
  • 使用快捷方式.
    // bad
    if (name !== '') {
      // ...stuff...
    }
    
    // good
    if (name) {
      // ...stuff...
    }
    
    // bad
    if (collection.length > 0) {
      // ...stuff...
    }
    
    // good
    if (collection.length) {
      // ...stuff...
    }
  • 閱讀 Truth Equality and JavaScript 瞭解更多

  • 給所有多行的塊使用大括號
    // bad
    if (test)
      return false;
    
    // good
    if (test) return false;
    
    // good
    if (test) {
      return false;
    }
    
    // bad
    function() { return false; }
    
    // good
    function() {
      return false;
    }

註釋

  • 使用 /** ... */ 進行多行註釋,包括描述,指定型別以及引數值和返回值
    // bad
    // make() returns a new element
    // based on the passed in tag name
    //
    // @param <String> tag
    // @return <Element> element
    function make(tag) {
    
      // ...stuff...
    
      return element;
    }
    
    // good
    /**
     * make() returns a new element
     * based on the passed in tag name
     *
     * @param <String> tag
     * @return <Element> element
     */
    function make(tag) {
    
      // ...stuff...
    
      return element;
    }
  • 使用 // 進行單行註釋,在評論物件的上面進行單行註釋,註釋前放一個空行.
    // bad
    var active = true;  // is current tab
    
    // good
    // is current tab
    var active = true;
    
    // bad
    function getType() {
      console.log('fetching type...');
      // set the default type to 'no type'
      var type = this._type || 'no type';
    
      return type;
    }
    
    // good
    function getType() {
      console.log('fetching type...');
    
      // set the default type to 'no type'
      var type = this._type || 'no type';
    
      return type;
    }
  • 如果你有一個問題需要重新來看一下或如果你建議一個需要被實現的解決方法的話需要在你的註釋前面加上 FIXME 或 TODO 幫助其他人迅速理解
    function Calculator() {
    
      // FIXME: shouldn't use a global here
      total = 0;
    
      return this;
    }
    function Calculator() {
    
      // TODO: total should be configurable by an options param
      this.total = 0;
    
      return this;
    }
  • 滿足規範的文件,在需要文件的時候,可以嘗試jsdoc.

空白

  • 縮排、格式化能幫助團隊更快得定位修復程式碼BUG.
  • 將tab設為4個空格
    // bad
    function() {
    ∙∙var name;
    }
    
    // bad
    function() {
    ∙var name;
    }
    
    // good
    function() {
    ∙∙∙∙var name;
    }
  • 大括號前放一個空格
    // bad
    function test(){
      console.log('test');
    }
    
    // good
    function test() {
      console.log('test');
    }
    
    // bad
    dog.set('attr',{
      age: '1 year',
      breed: 'Bernese Mountain Dog'
    });
    
    // good
    dog.set('attr', {
      age: '1 year',
      breed: 'Bernese Mountain Dog'
    });
  • 在做長方法鏈時使用縮排.
    // bad
    $('#items').find('.selected').highlight().end().find('.open').updateCount();
    
    // good
    $('#items')
      .find('.selected')
        .highlight()
        .end()
      .find('.open')
        .updateCount();
    
    // bad
    var leds = stage.selectAll('.led').data(data).enter().append('svg:svg').class('led', true)
        .attr('width',  (radius + margin) * 2).append('svg:g')
        .attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')')
        .call(tron.led);
    
    // good
    var leds = stage.selectAll('.led')
        .data(data)
      .enter().append('svg:svg')
        .class('led', true)
        .attr('width',  (radius + margin) * 2)
      .append('svg:g')
        .attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')')
        .call(tron.led);

逗號

  • 不要將逗號放前面
    // bad
    var once
      , upon
      , aTime;
    
    // good
    var once,
        upon,
        aTime;
    
    // bad
    var hero = {
        firstName: 'Bob'
      , lastName: 'Parr'
      , heroName: 'Mr. Incredible'
      , superPower: 'strength'
    };
    
    // good
    var hero = {
      firstName: 'Bob',
      lastName: 'Parr',
      heroName: 'Mr. Incredible',
      superPower: 'strength'
    };
  • 不要加多餘的逗號,這可能會在IE下引起錯誤,同時如果多一個逗號某些ES3的實現會計算多陣列的長度。
    // bad
    var hero = {
      firstName: 'Kevin',
      lastName: 'Flynn',
    };
    
    var heroes = [
      'Batman',
      'Superman',
    ];
    
    // good
    var hero = {
      firstName: 'Kevin',
      lastName: 'Flynn'
    };
    
    var heroes = [
      'Batman',
      'Superman'
    ];

分號

  • 語句結束一定要加分號
    // bad
    (function() {
      var name = 'Skywalker'
      return name
    })()
    
    // good
    (function() {
      var name = 'Skywalker';
      return name;
    })();
    
    // good
    ;(function() {
      var name = 'Skywalker';
      return name;
    })();

型別轉換

  • 在語句的開始執行型別轉換.
  • 字串:
    //  => this.reviewScore = 9;
    
    // bad
    var totalScore = this.reviewScore + '';
    
    // good
    var totalScore = '' + this.reviewScore;
    
    // bad
    var totalScore = '' + this.reviewScore + ' total score';
    
    // good
    var totalScore = this.reviewScore + ' total score';
  • 對數字使用 parseInt 並且總是帶上型別轉換的基數.,如parseInt(value, 10)
  • var inputValue = '4';
    
    // bad
    var val = new Number(inputValue);
    
    // bad
    var val = +inputValue;
    
    // bad
    var val = inputValue >> 0;
    
    // bad
    var val = parseInt(inputValue);
    
    // good
    var val = Number(inputValue);
    
    // good
    var val = parseInt(inputValue, 10);
    
    // good
    /**
     * parseInt was the reason my code was slow.
     * Bitshifting the String to coerce it to a
     * Number made it a lot faster.
     */
    var val = inputValue >> 0;
  • 布林值:
    var age = 0;
    
    // bad
    var hasAge = new Boolean(age);
    
    // good
    var hasAge = Boolean(age);
    
    // good
    var hasAge = !!age;

命名約定

  • 避免單個字元名,讓你的變數名有描述意義。
    // bad
    function q() {
      // ...stuff...
    }
    
    // good
    function query() {
      // ..stuff..
    }
  • 當命名物件、函式和例項時使用駝峰命名規則
    // bad
    var OBJEcttsssss = {};
    var this_is_my_object = {};
    var this-is-my-object = {};
    function c() {};
    var u = new user({
      name: 'Bob Parr'
    });
    
    // good
    var thisIsMyObject = {};
    function thisIsMyFunction() {};
    var user = new User({
      name: 'Bob Parr'
    });
  • 當命名建構函式或類時使用駝峰式大寫
    // bad
    function user(options) {
      this.name = options.name;
    }
    
    var bad = new user({
      name: 'nope'
    });
    
    // good
    function User(options) {
      this.name = options.name;
    }
    
    var good = new User({
      name: 'yup'
    });
  • 命名私有屬性時前面加個下劃線 _:
    // bad
    this.__firstName__ = 'Panda';
    this.firstName_ = 'Panda';
    
    // good
    this._firstName = 'Panda';
  • 當儲存對 this 的引用時使用 self(python 風格),避免this issue.Angular建議使用vm(MVVM模式中view-model):
    // good
    function() {
      var self = this;
      return function() {
        console.log(self);
      };
    }

存取器

  • 屬性的存取器函式不是必需的
  • 如果你確實有存取器函式的話使用getVal() 和 setVal(‘hello’),java getter、setter風格或者jQuery風格
  • 如果屬性是布林值,使用isVal() 或 hasVal()
    // bad
    if (!dragon.age()) {
      return false;
    }
    
    // good
    if (!dragon.hasAge()) {
      return false;
    }
  • 可以建立get()和set()函式,但是要保持一致
    function Jedi(options) {
      options || (options = {});
      var lightsaber = options.lightsaber || 'blue';
      this.set('lightsaber', lightsaber);
    }
    
    Jedi.prototype.set = function(key, val) {
      this[key] = val;
    };
    
    Jedi.prototype.get = function(key) {
      return this[key];
    };

構造器

  • 給物件原型分配方法,而不是用一個新的物件覆蓋原型,覆蓋原型會使繼承出現問題。
    function Jedi() {
      console.log('new jedi');
    }
    
    // bad
    Jedi.prototype = {
      fight: function fight() {
        console.log('fighting');
      },
    
      block: function block() {
        console.log('blocking');
      }
    };
    
    // good
    Jedi.prototype.fight = function fight() {
      console.log('fighting');
    };
    
    Jedi.prototype.block = function block() {
      console.log('blocking');
    };
  • 方法可以返回 this 幫助方法可鏈。
    // bad
    Jedi.prototype.jump = function() {
      this.jumping = true;
      return true;
    };
    
    Jedi.prototype.setHeight = function(height) {
      this.height = height;
    };
    
    var luke = new Jedi();
    luke.jump(); // => true
    luke.setHeight(20) // => undefined
    
    // good
    Jedi.prototype.jump = function() {
      this.jumping = true;
      return this;
    };
    
    Jedi.prototype.setHeight = function(height) {
      this.height = height;
      return this;
    };
    
    var luke = new Jedi();
    
    luke.jump()
      .setHeight(20);
  • 可以寫一個自定義的toString()方法,但是確保它工作正常並且不會有副作用。
    function Jedi(options) {
      options || (options = {});
      this.name = options.name || 'no name';
    }
    
    Jedi.prototype.getName = function getName() {
      return this.name;
    };
    
    Jedi.prototype.toString = function toString() {
      return 'Jedi - ' + this.getName();
    };

事件

  • 當給事件附加資料時,傳入一個雜湊而不是原始值,這可以讓後面的貢獻者加入更多資料到事件資料裡而不用找出並更新那個事件的事件處理器
    // bad
    $(this).trigger('listingUpdated', listing.id);
    
    ...
    
    $(this).on('listingUpdated', function(e, listingId) {
      // do something with listingId
    });

    更好:

    // good
    $(this).trigger('listingUpdated', { listingId : listing.id });
    
    ...
    
    $(this).on('listingUpdated', function(e, data) {
      // do something with data.listingId
    });

模組

  • 這個檔案應該以駝峰命名,並在同名資料夾下,同時匯出的時候名字一致
  • 對於公開API庫可以考慮加入一個名為noConflict()的方法來設定匯出的模組為之前的版本並返回它
  • 總是在模組頂部宣告 'use strict';,引入[JSHint規範](http://jshint.com/):
    // fancyInput/fancyInput.js
    
    (function(global) {
      'use strict';
    
      var previousFancyInput = global.FancyInput;
    
      function FancyInput(options) {
        this.options = options || {};
      }
    
      FancyInput.noConflict = function noConflict() {
        global.FancyInput = previousFancyInput;
        return FancyInput;
      };
    
      global.FancyInput = FancyInput;
    })(this);

jQuery

  • 對於jQuery物件以$開頭,以和原生DOM節點區分。
    // bad
    var menu = $(".menu");
    
    // good
    var $menu = $(".menu");
  • 快取jQuery查詢
    // bad
    function setSidebar() {
      $('.sidebar').hide();
    
      // ...stuff...
    
      $('.sidebar').css({
        'background-color': 'pink'
      });
    }
    
    // good
    function setSidebar() {
      var $sidebar = $('.sidebar');
      $sidebar.hide();
    
      // ...stuff...
    
      $sidebar.css({
        'background-color': 'pink'
      });
    }
  • 對DOM查詢使用級聯的 $('.sidebar ul') 或 $('.sidebar ul'),jsPerf
  • 對有作用域的jQuery物件查詢使用 find:
    // bad
    $('.sidebar', 'ul').hide();
    
    // bad
    $('.sidebar').find('ul').hide();
    
    // good
    $('.sidebar ul').hide();
    
    // good
    $('.sidebar > ul').hide();
    
    // good (slower)
    $sidebar.find('ul');
    
    // good (faster)
    $($sidebar[0]).find('ul');
  • 每個頁面只使用一次document的ready事件,這樣便於除錯與行為流跟蹤。
    $(function(){
       //do your page init.  
    });
  • 事件利用jQuery.on從頁面分離到JavaScript檔案。
    // bad
    <a id="myLink" href="#" onclick="myEventHandler();"></a>
    
    // good
    <a id="myLink" href="#"></a>
    
    $("#myLink").on("click", myEventHandler);
  • 對於Ajax使用promise方式。
     // bad
        $.ajax({
            ...
            success : function(){
            },
            error : function(){
            } 
        })
    
        // good
        $.ajax({.
            ..
        }).then( function( ){
            // success
        }, function( ){
            // error
        })
  • 利用promise的deferred物件解決延遲註冊問題。
    var dtd = $.Deferred(); // 新建一個deferred物件
      var wait = function(dtd){
        var tasks = function(){
          alert("執行完畢!");
          dtd.resolve(); // 改變deferred物件的執行狀態
        };
        setTimeout(tasks,5000);
        return dtd;
      };
  • HTML中Style、以及JavaScript中style移到CSS中class,在HTML、JavaScript中引入class,而不是直接style。

ECMAScript 5相容性

儘量採用ES5方法,特別陣列map、filter、forEach方法簡化日常開發。在老式IE瀏覽器中引入ES5-shim。或者也可以考慮引入underscorelodash 常用輔助庫.
- 參考Kangax的 ES5 compatibility table

HTML、CSS、JavaScript分離

  • 頁面DOM結構使用HTML,樣式則採用CSS,動態DOM操作JavaScript。不要混用在HTML中
  • 分離在不同型別檔案,檔案link。
  • HTML、CSS、JavaScript變數名都需要有業務價值。CSS以中劃線分割的全小寫命名,JavaScript則首字母小寫的駝峰命名。
  • CSS可引入Bootstrap、Foundation等出名響應式設計框架。以及SASS、LESS工具書寫CSS。
  • 對於CSS、JavaScript建議合併為單檔案,減少Ajax的連線數。也可以引入AMD(Require.js)載入方式。
  • 對於內部大部分企業管理系統,可以嘗試採用前端 MVC框架組織程式碼。如Angular、React + Flux架構、Knockout等。
  • 對於相容性可用Modernizr規範庫輔助。

使用jsHint

  • 前端專案中推薦引入jshint外掛來規範專案編碼規範。以及一套完善的IDE配置。
  • 注意:jshint需要引入nodejs 工具grunt或gulp外掛,建議企業級nodejs npm私服。

前端工具

  • 前端第三方JavaScript包管理工具bower(bower install jQuery),bower可以實現第三方庫的依賴解析、下載、升級管理等。建議建立企業級bower私服。
  • 前端構建工具,可以採用grunt或者gulp工具,可以實現html、css、js壓縮、驗證、測試,檔案合併、watch和liveload等所有前端任務。建議企業級nodejs npm私服。
  • 前端開發IDE: WebStorm( Idea )、Sublime為最佳 。專案組統一IDE。IDE統一配置很重要。

相關文章