Skip to content

Latest commit

 

History

History
1726 lines (1284 loc) · 63.1 KB

README.md

File metadata and controls

1726 lines (1284 loc) · 63.1 KB

![Gitter](https://badges.gitter.im/Join Chat.svg)

Airbnb JavaScript Style Guide() {

คู่มือแนะนำการเขียนจาวาสคริปต์ที่เข้าท่ามากที่สุด โดย Airbnb

คู่มือนี้ผมแปลโดยใส่คำอธิบายและตัวอย่างเพิ่มเติม (ไม่แปลตรงตัว) เพื่อให้ผู้อ่านสามารถเข้าใจเนื้อหาต่าง ๆ ได้ดียิ่งขึ้น ในกรณีที่เจอข้อผิดพลาดใด ๆ กรุณา Fork และ PR ถ้ามีคำถามสามารถเปิด Issue ได้เลยครับ หวังว่าคู่มือนี้จะมีประโยชน์ต่อผู้อ่านไม่มากก็น้อย 🙏

  1. Types
  2. Objects
  3. Arrays
  4. Strings
  5. Functions
  6. Properties
  7. Variables
  8. Hoisting
  9. Comparison Operators & Equality
  10. Blocks
  11. Comments
  12. Whitespace
  13. Commas
  14. Semicolons
  15. Type Casting & Coercion
  16. Naming Conventions
  17. Accessors
  18. Constructors
  19. Events
  20. Modules
  21. jQuery
  22. ECMAScript 5 Compatibility
  23. Testing
  24. Performance
  25. Resources
  26. In the Wild
  27. Translation
  28. The JavaScript Style Guide Guide
  29. Chat With Us About Javascript
  30. Contributors
  31. License

Types

  • Primitives: เมื่อใช้งานตัวแปรพื้นฐาน (ตัวแปรที่อ้างอิงด้วยค่า) สามารถเข้าใช้งานได้โดยอ้างอิงค่าของตัวแปร

    • string
    • number
    • boolean
    • null
    • undefined
    var foo = 1;
    var bar = foo; // bar เก็บค่า 1 โดยจะไม่เกี่ยวข้องกับ foo อีกต่อไป
    
    bar = 9;
    
    console.log(foo, bar); // => 1, 9
  • Complex: เมื่อใช้งานตัวแปรที่มีความซับซ้อน (ตัวแปรที่อ้างอิงไปยังค่าที่อยู่ของตัวแปรอื่น) สามารถเข้าใช้งานได้โดยอ้างอิงค่าที่อยู่ของตัวแปรนั้น ๆ

    • object
    • array
    • function
    var foo = [1, 2];
    var bar = foo; // bar ไม่ได้เก็บค่า [1,2] แต่ bar ชี้ไปยังที่อยู่ของอาร์เรย์ ซึ่งเป็นที่ ๆ เดียวกันกับที่ foo ชี้ไป
    
    bar[0] = 9; // เมื่อเปลี่ยนแปลง bar, foo ก็จะถูกเปลี่ยนแปลงด้วย
    
    console.log(foo[0], bar[0]); // => 9, 9

[⬆ กลับไปด้านบน]

Objects

  • ควรใช้ปีกกา {} ในการประกาศออบเจ็กต์

    // ไม่ดี
    var item = new Object();
    
    // ดี
    var item = {};
  • อย่าใช้คำสงวน เป็นคีย์ เพราะมันจะใช้ไม่ได้ใน IE8. อ่านเพิ่มเติม.

    // ไม่ดี
    var superman = {
      default: { clark: 'kent' }, // default เป็นคำสงวน
      private: true
    };
    
    // ดี
    var superman = {
      defaults: { clark: 'kent' },
      hidden: true
    };
  • ใช้คำที่มีความหมายเหมือนกันแทนคำสงวน

    // ไม่ดี
    var superman = {
      class: 'alien' // class เป็นคำสงวน
    };
    
    // ไม่ดี
    var superman = {
      klass: 'alien' // แปลงคำไม่ใช่สิ่งดี เพราะจะทำให้เดาความหมายได้ยาก
    };
    
    // ดี
    var superman = {
      type: 'alien'
    };

[⬆ กลับไปด้านบน]

Arrays

  • ใช้วงเล็บก้ามปู [] ในการประกาศอาร์เรย์

    // ไม่ดี
    var items = new Array();
    
    // ดี
    var items = [];
  • ใช้ฟังก์ชัน Array#push ในการใส่ค่าเข้าไปในอาร์เรย์แทนการใส่ค่าโดยตรง

    var someStack = [];
    
    // ไม่ดี
    someStack[someStack.length] = 'abracadabra';
    
    // ดี
    someStack.push('abracadabra');
  • ใช้ฟังก์ชัน Array#slice ในการทำสำเนาอาร์เรย์ - jsPerf

    var len = items.length;
    var itemsCopy = [];
    var i;
    
    // ไม่ดี
    for (i = 0; i < len; i++) {
      itemsCopy[i] = items[i];
    }
    
    // ดี
    itemsCopy = items.slice();
  • ใช้ฟังก์ชัน Array#slice ใช้การแปลงอาร์เรย์ให้เป็นออบเจ็กต์ (ต้องเป็นอาร์เรย์ที่จัดวางเหมือนออบเจ็กต์เท่านั้นถึงจะทำการแปลงได้)

    function trigger() {
      var args = Array.prototype.slice.call(arguments);
      ...
    }

[⬆ กลับไปด้านบน]

Strings

  • ใช้เขี้ยวเดียว (Single quotes) ''สำหรับสตริง

    // ไม่ดี
    var name = "Bob Parr";
    
    // ดี
    var name = 'Bob Parr';
    
    // ไม่ดี
    var fullName = "Bob " + this.lastName;
    
    // ดี
    var fullName = 'Bob ' + this.lastName;
  • สตริงที่ยาวกว่า 80 ตัวอักษร ควรจะแยกเขียนในหลายบรรทัด และค่อยทำการเชื่อมต่อกัน

  • หมายเหตุ: ไม่ควรใช้สตริงที่ยาวมากเกินไป เพราะจะมีผลต่อประสิทธิภาพของแอพพลิเคชั่น - jsPerf & Discussion.

    // ไม่ดี
    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.';
    
    // ไม่ดี
    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.';
    
    // ดี
    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.';
  • เมื่อต้องการสร้างสตริง ควรใช้ฟังก์ชัน Array#join แทนการเชื่อมต่อโดยใช้เครื่องหมายบวก + โดยเฉพาะสำหรับ IE - jsPerf.

    var items;
    var messages;
    var length;
    var 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;
    
    // ไม่ดี
    function inbox(messages) {
      items = '<ul>';
    
      for (i = 0; i < length; i++) {
        items += '<li>' + messages[i].message + '</li>';
      }
    
      return items + '</ul>';
    }
    
    // ดี
    function inbox(messages) {
      items = [];
    
      for (i = 0; i < length; i++) {
        items[i] = '<li>' + messages[i].message + '</li>';
      }
    
      return '<ul>' + items.join('') + '</ul>';
    }

[⬆ กลับไปด้านบน]

Functions

  • Function expressions - การประกาศฟังก์ชันและใช้ตัวแปรในการอ้างอิงฟังก์ชันดังกล่าว ดังตัวอย่างต่อไปนี้

    // anonymous function expression
    var anonymous = function() {
      return true;
    };
    
    // named function expression
    var named = function named() {
      return true;
    };
    
    // immediately-invoked function expression (IIFE)
    (function() {
      console.log('Welcome to the Internet. Please follow me.');
    })();
  • อย่าประกาศฟังก์ชันประเภท Function Declaration ไว้ภายใน if, else, while, และอื่น ๆ เพราะบราวเซอร์จะตีความหมายผิด ถ้าจำเป็นต้องประกาศ ให้ประกาศในรูปแบบของ Function Expression

  • หมายเหตุ: ECMA-262 บอกไว้ว่าใน if, else, while, และอื่น ๆ จะต้องประกอบไปด้วย statements เท่านั้น ซึ่งการประกาศฟังก์ชันประเภท Function Declaration ไม่ใช่ statement อ่านเพิ่มเติมเกี่ยวกับ ECMA-262.

    // ไม่ดี
    if (currentUser) {
      function test() {
        console.log('Nope.');
      }
    }
    
    // ดี
    var test;
    if (currentUser) {
      test = function test() {
        console.log('Yup.');
      };
    }
  • อย่าตั้งชื่อพารามิเตอร์ว่า arguments เพราะมันจะไปทับออบเจ็กต์ arguments ที่จาวาสคริปต์มีให้ในทุก ๆ ฟังก์ชัน

    // ไม่ดี
    function nope(name, options, arguments) {
      // ...stuff...
    }
    
    // ดี
    function yup(name, options, args) {
      // ...stuff...
    }

[⬆ กลับไปด้านบน]

Properties

  • ใช้จุด . ในการเข้าถึงพรอพเพอร์ตี้ (properties).

    var luke = {
      jedi: true,
      age: 28
    };
    
    // ไม่ดี
    var isJedi = luke['jedi'];
    
    // ดี
    var isJedi = luke.jedi;
  • ใช้วงเล็บก้ามปู [] ในการเข้าถึงพรอพเพอร์ตี้ โดยการใช้ตัวแปร

    var luke = {
      jedi: true,
      age: 28
    };
    
    function getProp(prop) {
      return luke[prop]; //  เข้าถึงพรอพเพอร์ตี้ี้ของ luke โดยใช้ตัวแปร prop
    }
    
    var isJedi = getProp('jedi');

[⬆ กลับไปด้านบน]

Variables

  • ใช้ var ในการประกาศตัวแปรเสมอ ถ้าไม่ใช้จะมีผลให้ตัวแปรที่ประกาศขึ้นใหม่เป็นตัวแปรแบบ global ซึ่งอาจมีผลต่อไฟล์หรือโมดูลอื่น ๆ

    // ไม่ดี
    superPower = new SuperPower();
    
    // ดี
    var superPower = new SuperPower();
  • ใช้หนึ่ง var ต่อหนึ่งตัวแปร เพราะดูง่ายกว่า และป้องกันข้อผิดพลาดได้ อย่างเช่น บางครั้งใส่สลับกันระหว่าง ; และ , ซึ่งทำให้ได้ผลลัพธ์ที่ผิด

    // ไม่ดี
    var items = getItems(),
        goSportsTeam = true,
        dragonball = 'z';
    
    // ไม่ดี
    var items = getItems(),
        goSportsTeam = true; // ตัวอย่างข้อผิดพลาดที่ใส่ ; แทนที่จะเป็น ,
        dragonball = 'z';
    
    // ดี
    var items = getItems();
    var goSportsTeam = true;
    var dragonball = 'z';
  • ตัวแปรที่ยังไม่มีค่า ให้ประกาศไว้ข้างหลังสุดของการประกาศตัวแปรทั้งหมด ซึ่งจะมีประโยชน์เมื่อเรามาใส่ค่าให้ตัวแปรเหล่านั้นในภายหลัง โดยที่ตัวแปรเหล่านั้นจะต้องใช้ค่าของตัวแปรอื่น ๆ ที่เราประกาศไว้ก่อนหน้า

    // ไม่ดี
    var i, len, dragonball,
        items = getItems(),
        goSportsTeam = true;
    
    // ไม่ดี
    var i;
    var items = getItems();
    var dragonball;
    var goSportsTeam = true;
    var len;
    
    // ดี
    var items = getItems();
    var goSportsTeam = true;
    var dragonball;
    var length;
    var i;
  • ประกาศตัวแปรทั้งหมดไว้ข้างบนสุดของฟังก์ชัน ซึ่งจะทำให้เราไม่สับสนและยังป้องกันการ hoisting ของจาวาสคริปต์ได้อีกด้วย

    // ไม่ดี
    function() {
      test();
      console.log('doing stuff..');
    
      //..other stuff..
    
      var name = getName();
    
      if (name === 'test') {
        return false;
      }
    
      return name;
    }
    
    // ดี
    function() {
      var name = getName();
    
      test();
      console.log('doing stuff..');
    
      //..other stuff..
    
      if (name === 'test') {
        return false;
      }
    
      return name;
    }
    
    // ไม่ดี
    function() {
      var name = getName();
    
      if (!arguments.length) {
        return false;
      }
    
      return true;
    }
    
    // ดี
    function() {
      if (!arguments.length) {
        return false;
      }
    
      var name = getName();
    
      return true;
    }

[⬆ กลับไปด้านบน]

Hoisting

  • เวลาคอมไพล์จาวาสคริปต์จะอ่านตัวแปรที่ประกาศไว้ก่อนหน้าสิ่งอื่น ๆ ในสโคป แต่ค่าที่ใส่ให้ตัวแปรจะยังไม่ถูกอ่าน

    // สมมุติว่าเราไม่ได้ประกาศตัวแปร notDefined
    function example() {
      console.log(notDefined); // => throws a ReferenceError
    }
    
    // ประกาศตัวแปรหลังจากใช้งาน ในจาวาสคริปต์นั้นทำได้ (ไม่มี error)
    // เพราะว่าตัวแปรจะถูกคอมไพล์และดึงขึ้นมาไว้ข้างบนสโคป
    // แต่ค่าของตัวแปรไม่ได้ถูกดึงขึ้นมาด้วย จึงทำให้ค่าของตัวแปรนั้นเป็น undefined
    function example() {
      console.log(declaredButNotAssigned); // => undefined (ไม่ error แต่ยังไม่มีค่า)
      var declaredButNotAssigned = true;
    }
    
    // ตัวอย่างเมื่อคอมไพล์เลอร์ทำงานในตัวอย่างข้างต้น
    // คอมไพล์เลอร์จะอ่านตัวแปรและดึงขึ้นมาไว้ด้านบนของสโคป
    function example() {
      var declaredButNotAssigned;
      console.log(declaredButNotAssigned); // => undefined
      declaredButNotAssigned = true;
    }
  • Anonymous function expressions - การประกาศฟังก์ชันโดยไม่ใส่ชื่อฟังก์ชัน คอมไพล์เลอร์จะอ่านตัวแปรและดึงขึ้นไปด้านบนของสโคป แต่จะยังไม่อ่านฟังก์ชัน

    function example() {
      console.log(anonymous); // => undefined
    
      anonymous(); // => TypeError anonymous is not a function
    
      var anonymous = function() {
        console.log('anonymous function expression');
      };
    }
  • Named function expressions - การประกาศฟังก์ชันโดยใส่ชื่อฟังก์ชัน ได้ผลลัพธ์เหมือนตัวอย่างก่อนหน้า

    function example() {
      console.log(named); // => undefined
    
      named(); // => TypeError named is not a function
    
      superPower(); // => ReferenceError superPower is not defined
    
      var named = function superPower() {
        console.log('Flying');
      };
    }
    
    // ประกาศฟังก์ชันชื่อเดียวกับตัวแปร ก็ได้ผลลัพธ์เช่นเดียวกันกับตัวอย่างก่อนหน้า
    function example() {
      console.log(named); // => undefined
    
      named(); // => TypeError named is not a function
    
      var named = function named() {
        console.log('named');
      }
    }
  • Function declarations - การประกาศฟังก์ชันโดยไม่ได้ใส่ค่าฟังก์ชันให้ตัวแปร คอมไพล์เลอร์จะอ่านทั้งชื่อและฟังก์ชัน

    function example() {
      superPower(); // => Flying
    
      function superPower() { // ถึงจะประกาศทีหลังใช้แต่ คอมไพล์เลอร์จะอ่านทั้งชื่อและตัวฟังก์ชัน
        console.log('Flying');
      }
    }
  • อ่านเพิ่มเติมได้ที่ JavaScript Scoping & Hoisting โดย Ben Cherry.

[⬆ กลับไปด้านบน]

Comparison Operators & Equality

  • ใช้ === และ !== แทน == และ !=

  • การเปรียบเทียบโอเปอเรเตอร์ จาวาสคริปต์จะแปลงค่าเหล่านั้นเป็น boolean โดยใช้ฟังก์ชัน ToBoolean และใช้กฏต่าง ๆ ดังต่อไปนี้:

    • Objects ได้ผลลัพธ์เป็น true
    • Undefined ได้ผลลัพธ์เป็น false
    • Null ได้ผลลัพธ์เป็น false
    • Booleans ได้ผลลัพธ์ ขึ้นอยู่กับค่าของ boolean
    • Numbers ได้ผลลัพธ์เป็น false ถ้า +0, -0, or NaN, นอกนั้นได้ true
    • Strings ได้ผลลัพธ์เป็น false ถ้า '', นอกนั้นได้ true
    if ([0]) {
      // true
      // เพราะ array คือออบเจ็กต์
    }
  • ใช้ Shortcuts.

    // ไม่ดี
    if (name !== '') {
      // ...stuff...
    }
    
    // ดี
    if (name) {
      // ...stuff...
    }
    
    // ไม่ดี
    if (collection.length > 0) {
      // ...stuff...
    }
    
    // ดี
    if (collection.length) {
      // ...stuff...
    }
  • อ่านเพิ่มเติมได้ที่ Truth Equality and JavaScript โดย Angus Croll.

[⬆ กลับไปด้านบน]

Blocks

  • ใช้วงเล็บปีกกา {} ในกรณีที่ประกาศบล็อกมากกว่าหนึ่งบรรทัด

    // ไม่ดี
    if (test)
      return false;
    
    // ดี
    if (test) return false; // วางไว้บรรทัดเดียวกันจะอ่านง่ายกว่า
    
    // ดี
    if (test) {
      return false;
    }
    
    // ไม่ดี
    function() { return false; }
    
    // ดี
    function() { // ถ้ามีวงเล็บปีกกาให้วางไว้คนละบรรทัดจะอ่านง่ายกว่า
      return false;
    }
  • ถ้าประกาศโดยมีทั้ง if และ else ให้ใส่ else ไว้บรรทัดเดียวกับวงเล็บปีกกาปิดของ if

    // ไม่ดี
    if (test) {
      thing1();
      thing2();
    }
    else {
      thing3();
    }
    
    // ดี
    if (test) {
      thing1();
      thing2();
    } else {
      thing3();
    }

[⬆ กลับไปด้านบน]

Comments

  • ใช้ /** ... */ สำหรับคอมเม้นต์ที่มากกว่าหนึ่งบรรทัด และควรจะบอกประเภทและค่าของพารามิเตอร์พร้อมทั้งค่าที่จะรีเทิร์น

    // ไม่ดี
    // make() returns a new element
    // based on the passed in tag name
    //
    // @param {String} tag
    // @return {Element} element
    function make(tag) {
    
      // ...stuff...
    
      return element;
    }
    
    // ดี
    /**
     * make() returns a new element
     * based on the passed in tag name
     *
     * @param {String} tag
     * @return {Element} element
     */
    function make(tag) {
    
      // ...stuff...
    
      return element;
    }
  • ใช้ // สำหรับคอมเม้นต์บรรทัดเดียว โดยใส่ไว้บรรทัดบนของสิ่งที่ต้องการคอมเม้นต์ และเพิ่มบรรทัดว่างไว้ด้านบนคอมเม้นต์ด้วย

    // ไม่ดี
    var active = true;  // is current tab
    
    // ดี
    // is current tab
    var active = true;
    
    // ไม่ดี
    function getType() {
      console.log('fetching type...');
      // set the default type to 'no type'
      var type = this._type || 'no type';
    
      return type;
    }
    
    // ดี
    function getType() {
      console.log('fetching type...');
    
      // set the default type to 'no type'
      var type = this._type || 'no type';
    
      return type;
    }
  • ใส่ FIXME หรือ TODO ไว้ด้านหน้าคอมเม้นต์ ซึ่งจะช่วยให้ผู้พัฒนาระบบท่านอื่น ๆ ทราบได้ว่าสิ่งเหล่านั้นอาจจะต้องแก้ไข หรือยังไม่ได้ทำ (IDE บางตัวสามารถค้นหาคอมเม้นต์เหล่านี้อัตโนมัติ และบอกถึงสิ่งที่ควรจะแก้ไขหรือทำเพิ่ม)

  • ใช้ // FIXME: เพื่อบอกปัญหา

    function Calculator() {
    
      // FIXME: shouldn't use a global here
      total = 0;
    
      return this;
    }
  • ใช้ // TODO: เพื่อบอกแนวทางในกาแก้ไขปัญหา (แต่ยังไม่ได้ทำ)

    function Calculator() {
    
      // TODO: total ควรจะใส่เป็นพารามิเตอร์ โดยมีค่าหรือไม่มีค่าก็ได้ (ถ้าไม่มีค่าใส่มา ให้ค่าเริ่มต้นเป็น 0)
      this.total = 0;
    
      return this;
    }

[⬆ กลับไปด้านบน]

Whitespace

  • ควรตั้งค่าหนึ่งแท็บเท่ากับสองช่องว่าง (สามารถตั้งค่าใน Editor หรือ IDE ได้)

    // ไม่ดี
    function() {
    ∙∙∙∙var name;
    }
    
    // ไม่ดี
    function() {
    ∙var name;
    }
    
    // ดี
    function() {
    ∙∙var name;
    }
  • ใส่ช่องว่างก่อนวงเล็บปีกกาเปิด

    // ไม่ดี
    function test(){
      console.log('test');
    }
    
    // ดี
    function test() {
      console.log('test');
    }
    
    // ไม่ดี
    dog.set('attr',{
      age: '1 year',
      breed: 'Bernese Mountain Dog'
    });
    
    // ดี
    dog.set('attr', {
      age: '1 year',
      breed: 'Bernese Mountain Dog'
    });
  • ใส่ช่องว่างก่อนเปิดวงเล็บสำหรับ control statements (if, else, while, และอื่น ๆ) แต่สำหรับพารามิเตอร์ไม่ต้องใส่ช่องว่าง

    // ไม่ดี
    if(isJedi) {
      fight ();
    }
    
    // ดี
    if (isJedi) {
      fight();
    }
    
    // ไม่ดี
    function fight () {
      console.log ('Swooosh!');
    }
    
    // ดี
    function fight() {
      console.log('Swooosh!');
    }
  • ใส่ช่องว่างเวลาประกาศตัวแปร

    // ไม่ดี
    var x=y+5;
    
    // ดี
    var x = y + 5;
  • ลงท้ายไฟล์ด้วยการขึ้นบรรทัดใหม่เสมอ (แค่หนึ่งบรรทัดเท่านั้น)

    // ไม่ดี
    (function(global) {
      // ...stuff...
    })(this);
    // ไม่ดี
    (function(global) {
      // ...stuff...
    })(this);
    
    // ดี
    (function(global) {
      // ...stuff...
    })(this);
  • ใส่ย่อหน้าเวลาเรียกใช้เมท็อตแบบต่อเนื่อง (method chaining) ให้วางจุด . ไว้ด้านหน้าเสมอ เพื่อบอกว่าเป็นการเรียกเมท็อต

    // ไม่ดี
    $('#items').find('.selected').highlight().end().find('.open').updateCount();
    
    // ไม่ดี
    $('#items').
      find('.selected').
        highlight().
        end().
      find('.open').
        updateCount();
    
    // ดี
    $('#items')
      .find('.selected')
        .highlight()
        .end()
      .find('.open')
        .updateCount();
    
    // ไม่ดี
    var leds = stage.selectAll('.led').data(data).enter().append('svg:svg').classed('led', true)
        .attr('width',  (radius + margin) * 2).append('svg:g')
        .attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')')
        .call(tron.led);
    
    // ดี
    var leds = stage.selectAll('.led')
        .data(data)
      .enter().append('svg:svg')
        .classed('led', true)
        .attr('width',  (radius + margin) * 2)
      .append('svg:g')
        .attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')')
        .call(tron.led);
  • ใส่บรรทัดว่างหลังจากบล็อก และก่อนที่จะขึ้น statement ใหม่

    // ไม่ดี
    if (foo) {
      return bar;
    }
    return baz;
    
    // ดี
    if (foo) {
      return bar;
    }
    
    return baz;
    
    // ไม่ดี
    var obj = {
      foo: function() {
      },
      bar: function() {
      }
    };
    return obj;
    
    // ดี
    var obj = {
      foo: function() {
      },
    
      bar: function() {
      }
    };
    
    return obj;

[⬆ กลับไปด้านบน]

Commas

  • อย่าวางจุลภาค , ไว้ด้านหน้า

    // ไม่ดี
    var story = [
        once
      , upon
      , aTime
    ];
    
    // ดี
    var story = [
      once,
      upon,
      aTime
    ];
    
    // ไม่ดี
    var hero = {
        firstName: 'Bob'
      , lastName: 'Parr'
      , heroName: 'Mr. Incredible'
      , superPower: 'strength'
    };
    
    // ดี
    var hero = {
      firstName: 'Bob',
      lastName: 'Parr',
      heroName: 'Mr. Incredible',
      superPower: 'strength'
    };
  • อย่าใส่จุลภาค ถ้าไม่มีค่าอื่น ๆ ต่อท้ายแล้ว เพราะอาจจะทำให้เกิดปัญหาใน IE6/7 และ IE9 นอกจากนั้นใน ES3 จะเพิ่มความยาวของอาร์เรย์ถ้าเจอจุลภาคอยู่ด้านหลังสุดซึ่งเป็นสิ่งที่ผิด อ่านเพิ่มเติมที่ (ES5):

    Edition 5 ได้แก้ไขข้อผิดพลาดนี้ โดยไม่เพิ่มความยาวของอาร์เรย์จากจุลภาคที่อยู่หลังสุด ซึ่งเป็นข้อผิดพลาดใน ES3

    // ไม่ดี
    var hero = {
      firstName: 'Kevin',
      lastName: 'Flynn',
    };
    
    var heroes = [
      'Batman',
      'Superman',
    ];
    
    // ดี
    var hero = {
      firstName: 'Kevin',
      lastName: 'Flynn'
    };
    
    var heroes = [
      'Batman',
      'Superman'
    ];

[⬆ กลับไปด้านบน]

Semicolons

  • ควรใส่ ; เมื่อจบ statement

    // ไม่ดี
    (function() {
      var name = 'Skywalker'
      return name
    })()
    
    // ดี
    (function() {
      var name = 'Skywalker';
      return name;
    })();
    
    // ดี (เป็นการป้องกันไม่ให้ฟังก์ชันถูกตีความเป็น argument เมื่อทำการต่อไฟล์สองไฟล์ที่ใช้ IIFEs)
    ;(function() {
      var name = 'Skywalker';
      return name;
    })();

    อ่านเพิ่มเติม

[⬆ กลับไปด้านบน]

Type Casting & Coercion

  • เวลาแปลงค่าสตริงให้ใส่ '' ไว้ด้านหน้า เพราะเวลาอ่านจะทราบได้ทันที่ว่าค่าที่จะได้ จะเป็นชนิดสตริง

  • Strings:

    //  => this.reviewScore = 9;
    
    // ไม่ดี
    var totalScore = this.reviewScore + '';
    
    // ดี
    var totalScore = '' + this.reviewScore;
    
    // ไม่ดี
    var totalScore = '' + this.reviewScore + ' total score';
    
    // ดี
    var totalScore = this.reviewScore + ' total score';
  • เวลาใช้ parseInt ในการแปลงค่าให้เป็นตัวเลข ควรจะใส่เลขฐานที่ต้องการแปลงด้วย เพราะถ้าไม่ใส่อาจจะมีข้อผิดพลาดได้ถ้าค่าที่แปลงเป็นสตริงที่ไม่ได้ประกอบไปด้วยตัวเลขทั้งหมด

    var inputValue = '4';
    
    // ไม่ดี
    var val = new Number(inputValue);
    
    // ไม่ดี
    var val = +inputValue;
    
    // ไม่ดี
    var val = inputValue >> 0;
    
    // ไม่ดี
    var val = parseInt(inputValue);
    
    // ดี
    var val = Number(inputValue);
    
    // ดี
    var val = parseInt(inputValue, 10);
  • ในบางกรณีที่ต้องการให้ได้ประสิทธิภาพสูงสุดด้วยการใช้ Bitshift แทนการแปลงค่าโดยใช้ parseInt สามารถอ่านเพิ่มเติมได้ที่ performance reasons, มีคอมเม้นต์ต่าง ๆ ที่อธิบายถึงเรื่องประสิทธิภาพ

    // ดี
    /**
     * ถ้า parseInt ทำให้โค้ดช้า ให้ใช้
     * Bitshifting เพื่อแปลงค่าเป็นตัวเลขแทน
     * ซึ่งทำให้โค้ดสามารถทำงานได้เร็วขึ้นอย่างมาก
     */
    var val = inputValue >> 0;
  • หมายเหตุ: ควรระวังการใช้งาน bitshift เพราะตัวเลขปกติจะเป็น 64-bit values, แต่ Bitshift จะคืนค่าเป็น 32-bit เสมอ (ที่มา) Bitshift อาจทำให้ค่าผิดแปลกไปถ้าค่าของตัวเลขใหญ่กว่า 32 bits. ดูการพูดคุยในเรื่องนี้ ตัวเลขที่มากที่สุดของ 32-bit Int คือ 2,147,483,647:

    2147483647 >> 0 //=> 2147483647
    2147483648 >> 0 //=> -2147483648 เกินค่ามากที่สุดของ 32-bit Int จึงทำให้เกิดข้อผิดพลาด
    2147483649 >> 0 //=> -2147483647 เกินค่ามากที่สุดของ 32-bit Int จึงทำให้เกิดข้อผิดพลาด
  • Booleans:

    var age = 0;
    
    // ไม่ดี
    var hasAge = new Boolean(age);
    
    // ดี
    var hasAge = Boolean(age);
    
    // ดี
    var hasAge = !!age;

[⬆ กลับไปด้านบน]

Naming Conventions

  • ควรจะตั้งชื่อให้สื่อความหมาย

    // ไม่ดี
    function q() {
      // ...stuff...
    }
    
    // ดี
    function query() {
      // ..stuff..
    }
  • ใช้ camelCase (ขึ้นต้นด้วยตัวเล็กและคำต่อไปขึ้นต้นด้วยตัวใหญ่) เมื่อต้องการตั้งชื่อออบเจ็กต์, ฟังก์ชัน, และ instance

    // ไม่ดี
    var OBJEcttsssss = {};
    var this_is_my_object = {};
    function c() {}
    var u = new user({
      name: 'Bob Parr'
    });
    
    // ดี
    var thisIsMyObject = {};
    function thisIsMyFunction() {}
    var user = new User({
      name: 'Bob Parr'
    });
  • ใช้ PascalCase (ขึ้นต้นทุกคำด้วยตัวใหญ่) เมื่อต้องการตั้งชื่อ constructor หรือ class

    // ไม่ดี
    function user(options) {
      this.name = options.name;
    }
    
    var ไม่ดี = new user({
      name: 'nope'
    });
    
    // ดี
    function User(options) {
      this.name = options.name;
    }
    
    var ดี = new User({
      name: 'yup'
    });
  • ขึ้นต้นด้วยขีดล่าง (_) เมื่อต้องการตั้งชื่อพรอพเพอร์ตี้ที่เป็น Private

    // ไม่ดี
    this.__firstName__ = 'Panda';
    this.firstName_ = 'Panda';
    
    // ดี
    this._firstName = 'Panda';
  • เมื่อต้องการบันทึกค่า this ไว้ใช้ ให้ใส่ไว้ในตัวแปรชื่อ _this

    // ไม่ดี
    function() {
      var self = this;
      return function() {
        console.log(self);
      };
    }
    
    // ไม่ดี
    function() {
      var that = this;
      return function() {
        console.log(that);
      };
    }
    
    // ดี
    function() {
      var _this = this;
      return function() {
        console.log(_this);
      };
    }
  • ตั้งชื่อฟังก์ชันเสมอ ซึ่งจะเป็นประโยชน์เวลาดู Stack traces เมื่อทำการ Debug

    // ไม่ดี
    var log = function(msg) {
      console.log(msg);
    };
    
    // ดี
    var log = function log(msg) {
      console.log(msg);
    };
  • หมายเหตุ สำหรับบราวเซอร์ที่ต่ำกว่า IE8 อาจจะมีข้อผิดพลาดถ้าตั้งชื่อให้กับ Function expression อ่านเพิ่มเติมได้ที่ http://kangax.github.io/nfe/

  • ถ้าในไฟล์มีแค่หนึ่งคลาส ให้ตั้งชื่อไฟล์ให้เป็นชื่อเดียวกับชื่อคลาส

    // file contents
    class CheckBox {
      // ...
    }
    module.exports = CheckBox;
    
    // in some other file
    // ไม่ดี
    var CheckBox = require('./checkBox');
    
    // ไม่ดี
    var CheckBox = require('./check_box');
    
    // ดี
    var CheckBox = require('./CheckBox');

[⬆ กลับไปด้านบน]

Accessors

  • Accessor functions (ฟังก์ชันที่ใช้ในการเข้าถึงพรอพเพอร์ตี้) ไม่จำเป็นต้องมีก็ได้

  • แต่ถ้ามีควรจะตั้งชื่อในรูปแบบ getVal() และ setVal('hello')

    // ไม่ดี
    dragon.age();
    
    // ดี
    dragon.getAge();
    
    // ไม่ดี
    dragon.age(25);
    
    // ดี
    dragon.setAge(25);
  • ถ้าพรอพเพอร์ตี้เป็นค่าบูลีน (boolean) ให้ใช้ isVal() หรือ hasVal().

    // ไม่ดี
    if (!dragon.age()) {
      return false;
    }
    
    // ดี
    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];
    };

[⬆ กลับไปด้านบน]

Constructors

  • ควรเพิ่มเมท็อตของออบเจ็คต์ ผ่านทาง Prototype ด้วยการใช้จุด . เพราะจะเป็นการเพิ่มพรอพเพอร์ตี้ ไม่ใช่การสร้างออบเจ็คต์ใหม่ ถ้าสร้างออบเจ็คต์ใหม่ จะไม่สามารถทำ Inheritance ได้อีก

    function Jedi() {
      console.log('new jedi');
    }
    
    // ไม่ดี
    Jedi.prototype = {
      fight: function fight() {
        console.log('fighting');
      },
    
      block: function block() {
        console.log('blocking');
      }
    };
    
    // ดี
    Jedi.prototype.fight = function fight() {
      console.log('fighting');
    };
    
    Jedi.prototype.block = function block() {
      console.log('blocking');
    };
  • เมท็อตควรคืนค่าเป็นออบเจ็ค this เพื่อช่วยให้สามารถทำ Method chaining.

    // ไม่ดี
    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
    
    // ดี
    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);
  • สามารถทำการ Overwrite เมท็อต 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();
    };

[⬆ กลับไปด้านบน]

Events

  • เมื่อทำการเชื่อมต่ออีเว้นต์ ให้ส่งค่าที่เป็นออบเจ็คต์ไป ซึ่งจะดีกว่าการส่งค่าแบบธรรมดา เพราะจะช่วยให้ตัวเมท็อตที่รับค่าสามารถแก้ไขค่าและเพิ่มพรอพเพอร์ตี้ได้ง่ายขึ้น

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

[⬆ กลับไปด้านบน]

Modules

  • โมดูล (Module) ควรเริ่มต้นไฟล์ด้วยเครื่องหมายอัศเจรีย์ ! เพื่อให้แน่ใจว่าถ้ามีโมดูลอื่นที่ลืมใส่ semicolon ; ในบรรทัดสุดท้าย และนำไฟล์มาต่อกับโมดูลนี้็จะไม่ทำให้เกิดข้อผิดพลาดขึ้น (ปกติถ้าใช้ uglify ในการต่อไฟล์ จะใส่ ! ให้อัตโนมัติ) คำอธิบาย

  • ไฟล์ขึ้นจะตั้งชื่อแบบ camelCase และใส่ไว้ในโฟลเดอร์ชื่อเดียวกัน นอกจากนั้นฟังก์ชันที่นำออกมาจากไฟล์ควรจะเป็นชื่อเดียวกับชื่อไฟล์

  • เพิ่มเมท็อตชื่อ noConflict() เพื่อใช้ในการนำออกโมดูลเวอร์ชันก่อนหน้าที่จะทำการเปลี่ยนแปลง

  • ใส่ 'use strict'; ใช้ที่บรรทัดบนสุดของโมดูลเสมอ

    // 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 Object

    // ไม่ดี
    var sidebar = $('.sidebar');
    
    // ดี
    var $sidebar = $('.sidebar');
  • ในกรณีที่ต้องค้นหา DOM โดยใช้ jQuery ควรจะเก็บแคช (Cache) ไว้เสมอ เพราะการค้นหา DOM ซ้ำ ๆ หลายรอบจะส่งผลต่อประสิทธิภาพของโค้ด

    // ไม่ดี
    function setSidebar() {
      $('.sidebar').hide();
    
      // ...stuff...
    
      $('.sidebar').css({
        'background-color': 'pink'
      });
    }
    
    // ดี
    function setSidebar() {
      var $sidebar = $('.sidebar'); // เก็บแคชในการค้นหาไว้ในตัวแปร เพื่อนำไปใช้ต่อไป
      $sidebar.hide();
    
      // ...stuff...
    
      $sidebar.css({
        'background-color': 'pink'
      });
    }
  • เวลาค้นหา DOM ให้ใช้รูปแบบของ Cascading เช่น $('.sidebar ul') หรือ parent > child $('.sidebar > ul') - jsPerf

  • ใช้ find ร่วมกับ jQuery object (ที่เราแคชไว้ก่อนหน้านี้)

    // ไม่ดี
    $('ul', '.sidebar').hide();
    
    // ไม่ดี
    $('.sidebar').find('ul').hide();
    
    // ดี
    $('.sidebar ul').hide();
    
    // ดี
    $('.sidebar > ul').hide();
    
    // ดี
    $sidebar.find('ul').hide();

[⬆ กลับไปด้านบน]

ECMAScript 5 Compatibility

[⬆ กลับไปด้านบน]

Testing

  • Yup.

    function() {
      return true;
    }

[⬆ กลับไปด้านบน]

Performance

อ่านเพิ่มเติมจากข้อมูลต่อไปนี้

[⬆ กลับไปด้านบน]

Resources

อ่านเพิ่มเติม

เครื่องมือต่าง ๆ

ข้อมูลแนะนำการเขียนจาวาสคริปต์อื่น ๆ

ข้อมูลแนะนำสไตล์อื่น ๆ

อ่านเพิ่มเติม

หนังสือ

บล็อก

พอดคาสต์ (Podcasts)

[⬆ กลับไปด้านบน]

In the Wild

รายชื่อองกรค์ที่ทำตามคู่มือแนะนำการเขียนจาวาสคริปต์นี้ ถ้าองค์กรของคุณทำตามคู่มือนี้เช่นกัน กรุณาส่ง Pull request หรือเปิด Issue แล้วเราจะเพิ่มคุณเข้าไปในรายชื่อต่อไปนี้

Translation

คู่มือแนะนำการเขียนจาวาสคริปต์นี้ได้ถูกแปลเป็นภาษาต่าง ๆ มากมายดังต่อไปนี้:

คู่มือแนะนำการเขียนจาวาสคริปต์

พูดคุยกับพวกเราเกี่ยวกับจาวาสคริปต์

  • ติดต่อเราบน gitter.

ผู้ที่มีส่วนช่วย

License

(The MIT License)

Copyright (c) 2014 Airbnb

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

[⬆ กลับไปด้านบน]

};