Skip to content

Latest commit

 

History

History
2276 lines (1712 loc) · 90.1 KB

README.md

File metadata and controls

2276 lines (1712 loc) · 90.1 KB

Airbnb JavaScript Style Guide() {

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

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

คู่มือแนะนำอื่น ๆ

  1. Types
  2. References
  3. Objects
  4. Arrays
  5. Destructuring
  6. Strings
  7. Functions
  8. Arrow Functions
  9. Classes & Constructors
  10. Modules
  11. Iterators and Generators
  12. Properties
  13. Variables
  14. Hoisting
  15. Comparison Operators & Equality
  16. Blocks
  17. Control Statements
  18. Comments
  19. Whitespace
  20. Commas
  21. Semicolons
  22. Type Casting & Coercion
  23. Naming Conventions
  24. Accessors
  25. Events
  26. jQuery
  27. ECMAScript 5 Compatibility
  28. ECMAScript 6+ (ES 2015+) Styles
  29. Standard Library
  30. Testing
  31. Performance
  32. Resources
  33. In the Wild
  34. Translation
  35. The JavaScript Style Guide Guide
  36. Chat With Us About Javascript
  37. Contributors
  38. License
  39. Amendments

Types

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

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

    • object
    • array
    • function
    const foo = [1, 2];
    const bar = foo;
    
    bar[0] = 9;
    
    console.log(foo[0], bar[0]); // => 9, 9

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

References

  • 2.1 ใช้ const สำหรับค่าคงที่ และหลีกเลี่ยงการใช้ var

    เพราะว่าการใช้งาน const จะทำให้เราไม่สามารถเปลี่ยนแปลงค่าได้อีก ซึ่งป้องกันข้อผิดพลาดต่าง ๆ ที่อาจจะเกิดขึ้น (ในกรณีที่เราลืมไปเปลี่ยนแปลงค่าของตัวแปร หรือมีไลบรารี่อื่น ๆ ที่เราใช้มาเปลี่ยนแปลงค่าตัวแปรของเรา)

    // ไม่ดี
    var a = 1;
    var b = 2;
    
    // ดี
    const a = 1;
    const b = 2;
  • 2.2 ถ้าต้องการตัวแปรที่เปลี่ยนแปลงค่าได้ให้ใช้ let และหลีกเลี่ยงการใช้ var

    เพราะว่า let จะมีค่าอยู่แค่ในปีกกาที่ประกาศ (Block-scoped) ซึ่งไม่เหมือน var ที่มีค่าอยู่ในฟังก์ชันที่ประกาศ (Function-scoped)

    // ไม่ดี
    var count = 1;
    if (true) {
      count += 1;
    }
    
    // ดี
    let count = 1;
    if (true) {
      count += 1;
    }
  • 2.3 let และ const จะมีค่าอยู่แค่ในปีกกาที่ประกาศ (Block-scoped) เท่านั้น

    {
      let a = 1;
      const b = 1;
    }
    console.log(a); // ReferenceError เมื่อออกนอกปีกกาที่ประกาศจะไม่สามารถเรียกใช้งานตัวแปรได้
    console.log(b); // ReferenceError เมื่อออกนอกปีกกาที่ประกาศจะไม่สามารถเรียกใช้งานตัวแปรได้

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

Objects

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

    // ไม่ดี
    const item = new Object();
    
    // ดี
    const item = {};
  • 3.2 อย่าใช้คำสงวน เป็นคีย์ เพราะมันจะใช้ไม่ได้ใน IE8. อ่านเพิ่มเติม แต่ถ้าเราสร้างโมดูลของตัวเองก็สามารถใช้คำเหล่านี้ได้ (แต่ไม่ใช้จะดีกว่าในความเห็นของผมนะครับ)

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

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

  • 3.4 ถ้าต้องการสร้างพรอพเพอร์ตี้ของออบเจ็กต์จากตัวแปร (Dynamic property) ให้สร้างตอนที่ประกาศออบเจ็กต์โดยใช้ []

    เพราะจะทำให้พรอพเพอร์ตี้ทั้งหมดถูกสร้างไว้ในที่เดียว ซึ่งทำให้ดูได้ง่ายกว่าการสร้างแยกกัน

    function getKey(k) {
      return `a key named ${k}`;
    }
    
    // ไม่ดี
    const obj = {
      id: 5,
      name: 'San Francisco',
    };
    obj[getKey('enabled')] = true; // สร้างหลังจากประกาศออบเจ็กต์เสร็จแล้ว ทำให้มองยากกว่า
    
    // ดี
    const obj = {
      id: 5,
      name: 'San Francisco',
      [getKey('enabled')]: true, // สร้างตอนประกาศออบเจ็กต์ ทำให้มองเห็นพรอพเพอร์ตี้ของออบเจ็กต์ทั้งหมดในที่เดียว
    };

  • 3.5 สร้างเมท็อตโดยใช้วิธีการประกาศแบบย่อ (Object method shorthand)

    // ไม่ดี
    const atom = {
      value: 1,
    
      addValue: function (value) { // การประกาศแบบปกติ
        return atom.value + value;
      },
    };
    
    // ดี
    const atom = {
      value: 1,
    
      addValue(value) { // การประกาศแบบย่อ ซึ่งตัดคีย์เวิร์ดฟังก์ชันออกไป ทำให้โค้ดอ่านง่ายขึ้น
        return atom.value + value;
      },
    };

  • 3.6 สร้องพรอพเพอร์ตี้โดยใช้วิธีการประกาศแบบย่อ (Property value shorthand)

    เพราะว่าทำให้อ่านง่ายขึ้น และเข้าใจได้ง่ายกว่า

    const lukeSkywalker = 'Luke Skywalker';
    
    // ไม่ดี
    const obj = {
      lukeSkywalker: lukeSkywalker,
    };
    
    // ดี
    const obj = {
      lukeSkywalker, // มีค่าเท่ากับด้านบนเพียงแต่ทำให้อ่านง่ายขึ้น (ถ้าต้องการให้ชื่อตัวแปรและชื่อพรอพเพอร์ตี้ต่างกัน ต้องใช้วิธีการประกาศแบบด้านบน)
    };
  • 3.7 พรอพเพอร์ตี้ที่ประกาศโดยใช้วิธีการประกาศแบบย่อ ให้ใส่ไว้ด้านบนสุดของการประกาศออบเจ็กต์

    เพราะทำให้รู้ได้ว่าพรอพเพอร์ตี้ใด ที่ประกาศโดยใช้วิธีการประกาศแบบย่อ

    const anakinSkywalker = 'Anakin Skywalker';
    const lukeSkywalker = 'Luke Skywalker';
    
    // ไม่ดี
    const obj = {
      episodeOne: 1,
      twoJediWalkIntoACantina: 2,
      lukeSkywalker,
      episodeThree: 3,
      mayTheFourth: 4,
      anakinSkywalker,
    };
    
    // ดี
    const obj = {
      lukeSkywalker,
      anakinSkywalker,
      episodeOne: 1,
      twoJediWalkIntoACantina: 2,
      episodeThree: 3,
      mayTheFourth: 4,
    };

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

Arrays

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

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

    const someStack = [];
    
    // ไม่ดี
    someStack[someStack.length] = 'abracadabra';
    
    // ดี
    someStack.push('abracadabra');

  • 4.3 ใช้ ... (Spreads) ในการทำสำเนาอาร์เรย์.

    // ไม่ดี
    const len = items.length;
    const itemsCopy = [];
    let i;
    
    for (i = 0; i < len; i++) {
      itemsCopy[i] = items[i];
    }
    
    // ดี
    const itemsCopy = [...items];
  • 4.4 ใช้ฟังก์ชัน Array#from ในการแปลงอ็อบเจ็กต์เป็นอาร์เรย์

    const foo = document.querySelectorAll('.foo');
    const nodes = Array.from(foo);

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

Destructuring

  • 5.1 ใช้รูปแบบของ Destructuring เมื่อต้องการแปลงพรอพเพอร์ตี้ของอ็อบเจ็กต์ให้เป็นตัวแปร

    เพราะจะได้ไม่ต้องสร้างตัวแปรชั่วคราวมารับพรอพเพอร์ตี้เหล่านั้น

    // ไม่ดี
    function getFullName(user) {
      const firstName = user.firstName; // ต้องสร้างตัวแปรทั่วคราวมารับค่า
      const lastName = user.lastName; // ต้องสร้างตัวแปรทั่วคราวมารับค่า
    
      return `${firstName} ${lastName}`;
    }
    
    // ดี
    function getFullName(obj) {
      const { firstName, lastName } = obj; // ใช้ Destructuring ในการแปลงค่าอ็อบเจ็กต์ให้เป็นตัวแปร
      return `${firstName} ${lastName}`;
    }
    
    // ดีที่สุด
    function getFullName({ firstName, lastName }) { // รับค่าโดยใช้ Destructuring
      return `${firstName} ${lastName}`;
    }
  • 5.2 ใช้รูปแบบของ Destructuring เมื่อต้องการแปลงอิลีเม้นท์ของอาร์เรย์ให้เป็นตัวแปร

    const arr = [1, 2, 3, 4];
    
    // ไม่ดี
    const first = arr[0];
    const second = arr[1];
    
    // ดี
    const [first, second] = arr; // ใช้ Destructuring ในการแปลงค่าอาร์เรย์ให้เป็นตัวแปร
  • 5.3 ใช้ Destructuring ในรูปแบบออบเจ็กต์ในการส่งค่าหลายค่ากลับไปจากฟังก์ชัน (อย่าใช้ Destructuring ในรูปแบบอาร์เรย์ )

    เพราะ Destructuring ในรูปแบบออบเจ็กต์จะทำให้ลำดับการส่งค่าไม่สำคัญ (สามารถสลับที่กันได้) เผื่อว่าในอนาคตเราอาจจะเพิ่มค่าเข้าไป ก็จะไม่ต้องกังวลเรื่องลำดับ

    // ไม่ดี
    function processInput(input) {
      return [left, right, top, bottom];
    }
    
    // คนที่เรียกใช้งานฟังก์ชันจะต้องคำนึงถึงลำดับของตัวแปรที่จะส่งไป
    const [left, __, top] = processInput(input);
    
    // ดี
    function processInput(input) {
      return { left, right, top, bottom };
    }
    
    // คนที่เรียกใช้งานฟังก์ชันสามารถส่งเฉพาะค่าที่ตนเองต้องการ โดยมันจะจับคู่ให้อัตโนมัติ
    const { left, right } = processInput(input);

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

Strings

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

    // ไม่ดี
    const name = "Capt. Janeway";
    
    // ดี
    const name = 'Capt. Janeway';
  • 6.2 สตริงที่ยาวกว่า 80 ตัวอักษร ควรจะแยกเขียนในหลายบรรทัด และค่อยทำการเชื่อมต่อกัน

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

    // ไม่ดี
    const 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.';
    
    // ไม่ดี
    const 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.';
    
    // ดี
    const 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.';

  • 6.4 เมื่อต้องการสร้างสตริงที่มีตัวแปร ให้ใช้เทมเพลตสตริง (Template strings) ซึ่งดีกว่าการเชื่อมสตริงด้วยตนเอง

    เพราะว่าเทมเพลตสตริงจะทำให้อ่านง่ายกว่า

    // ไม่ดี
    function sayHi(name) {
      return 'How are you, ' + name + '?';
    }
    
    // ไม่ดี
    function sayHi(name) {
      return ['How are you, ', name, '?'].join();
    }
    
    // ดี
    function sayHi(name) {
      return `How are you, ${name}?`;
    }

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

Functions

  • 7.1 ทุกครั้งที่จะประกาศฟังก์ชันให้ประกาศในรูปแบบ Function declarations (อย่าประกาศแบบ Function expressions)

    เพราะ Function declarations มีชื่อให้เห็นชัดเจน เมื่อทำการดีบัคโค้ดจะสามารถเห็นชื่อฟังก์ชันใน Call stacks นอกจากนั้นจาวาสคริปต์จะ Hoisting ฟังก์ชันที่ประกาศแบบ Function declarations ทำให้สามารถเรียกใช้ฟังก์ชันได้ทุกที่ เมื่อใดที่ต้องการใช้งาน Function expressions ให้ใช้ Arrow Functions แทนเสมอ

    // ไม่ดี
    const foo = function () {
    };
    
    // ดี
    function foo() {
    }
  • 7.2 Function expressions - การประกาศฟังก์ชันและใช้ตัวแปรในการอ้างอิงฟังก์ชันดังกล่าว (อาจจะไม่ใช้ตัวแปรในกรณีที่เป็น Anonymous function) ดังตัวอย่างต่อไปนี้

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

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

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

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

  • 7.6 ให้ใช้ ... (Rest) แทนการใช้พารามิเตอร์ arguments

    เพราะ ... สามารถทำให้รู้ว่าฟังก์ชันนั้นมีการรับค่าพารามิเตอร์ อีกทั้ง ... จะได้ค่าอาร์เรย์จริง ๆ ไม่ใช่ค่าออบเจ็กต์เหมือน arguments

    // ไม่ดี
    function concatenateAll() {
      const args = Array.prototype.slice.call(arguments);
      return args.join('');
    }
    
    // ดี
    function concatenateAll(...args) { // ทำให้รู้ว่าฟังก์ชันนี้รับพารามิเตอร์
      return args.join('');
    }

  • 7.7 ใส่ค่าเริ่มต้นให้กับพารามิเตอร์ทุกตัว (Default parameter)

    // แย่มาก
    function handleThings(opts) {
      // ไม่ควรเปลี่ยนค่าของพารามิเตอร์ อ่านเพิ่มเติมได้ที่ http://spin.atomicobject.com/2011/04/10/javascript-don-t-reassign-your-function-arguments
      // นอกจากนั้นถ้า opts เป็นเท็จ จะได้ค่าที่เป็นออบเจ็กต์ ซึ่งดูเหมือนว่า
      // จะเป็นค่าที่เราต้องการ แต่ความจริงนั้นจะทำให้เกิดบัค
      opts = opts || {};
      // ...
    }
    
    // แย่
    function handleThings(opts) {
      if (opts === void 0) {
        opts = {};
      }
      // ...
    }
    
    // ดี
    function handleThings(opts = {}) {
      // ...
    }
  • 7.8 หลีกเลี่ยงการตั้งค่าที่ยาก ๆ เป็นค่าเริ่มต้นของพารามิเตอร์

    เพราะจะทำให้สับสนได้ง่าย

    var b = 1;
    // ไม่ดี
    function count(a = b++) {
      console.log(a);
    }
    count();  // 1
    count();  // 2
    count(3); // 3 เพราะว่ามีการกำหนดอาร์กิวเมนต์เป็น 3 ดังนั้นค่าเริ่มต้นจะไม่ถูกเรียก (= b++ ไม่ถูกเรียก)
    count();  // 3

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

Arrow Functions

  • 8.1 ทุกครั้งที่ต้องการใช้งาน Function expressions (รวมถึง Anonymous functions) ให้ใช้ Arrow Functions แทน

    เพราะค่าของ this ใน Arrow functions จะมีค่าเท่ากับค่า this ของฟังก์ชันที่ห่อหุ้ม Arrow functions อยู่

    แต่ถ้าฟังก์ชันยาวมาก ๆ ให้แยกออกมาเป็น Function declarations แทน

    // ไม่ดี
    [1, 2, 3].map(function (x) {
      return x * x;
    });
    
    // ดี
    [1, 2, 3].map((x) => {
      return x * x;
    });
  • 8.2 ถ้าฟังก์ชันมีแค่บรรทัดเดียวและมีแค่อาร์กิวเมนต์เดียวให้ลบ {} และ () ออกได้ แต่ในกรณีอื่น ๆ ให้ใช้ {}, () และ return คีย์เวิร์ด

    เพราะอ่านง่ายกว่า โดยเฉพาะเวลาที่มีการเรียกใช้เมท็อตแบบต่อเนื่อง (Method chaining)

    แต่ถ้าต้องการส่งค่ากลับไปแบบออบเจ็กต์ ก็ให้ใช้ {} และ () ตามปกติ

    // ดี
    [1, 2, 3].map(x => x * x); // จะสังเกตว่า Arrow functions ที่สั้น ๆ แบบนี้เมื่อไม่ใส่ {} และ () แล้วทำให้ดูง่ายขึ้น
    
    // ดี
    [1, 2, 3].reduce((total, n) => { // Arrow functions ที่ซับซ้อนมากขึ้นควรใส่ {} และ ()
      return total + n;
    }, 0);

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

Classes & Constructors

  • 9.1 ใช้ class และหลีกเลี่ยงการเรียกใช้ prototype โดยตรง

    เพราะว่า class อ่านง่ายกว่า และง่ายต่อการเข้าใจ

    // ไม่ดี
    function Queue(contents = []) {
      this._queue = [...contents];
    }
    Queue.prototype.pop = function() {
      const value = this._queue[0];
      this._queue.splice(0, 1);
      return value;
    }
    
    // ดี
    class Queue {
      constructor(contents = []) {
        this._queue = [...contents];
      }
      pop() {
        const value = this._queue[0];
        this._queue.splice(0, 1);
        return value;
      }
    }
  • 9.2 ใช้ extends ในการสืบทอดคลาส (Inheritance)

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

    // ไม่ดี
    const inherits = require('inherits');
    function PeekableQueue(contents) {
      Queue.apply(this, contents);
    }
    inherits(PeekableQueue, Queue);
    PeekableQueue.prototype.peek = function() {
      return this._queue[0];
    }
    
    // ดี
    class PeekableQueue extends Queue {
      peek() {
        return this._queue[0];
      }
    }
  • 9.3 เมท็อตควรคืนค่าเป็นออบเจ็ค this เพื่อช่วยให้สามารถทำ Method chaining

    // ไม่ดี
    Jedi.prototype.jump = function() {
      this.jumping = true;
      return true;
    };
    
    Jedi.prototype.setHeight = function(height) {
      this.height = height;
    };
    
    const luke = new Jedi();
    luke.jump(); // => true
    luke.setHeight(20); // => undefined
    
    // ดี
    class Jedi {
      jump() {
        this.jumping = true;
        return this;
      }
    
      setHeight(height) {
        this.height = height;
        return this;
      }
    }
    
    const luke = new Jedi();
    
    luke.jump()
      .setHeight(20);
  • 9.4 สามารถทำการ Overwrite เมท็อต toString() ได้แต่ควรจะตรวจสอบให้มั่นใจว่าจะไม่เกิดข้อผิดพลาดขึ้นได้ในอนาคต

    class Jedi {
      constructor(options = {}) {
        this.name = options.name || 'no name';
      }
    
      getName() {
        return this.name;
      }
    
      toString() {
        return `Jedi - ${this.getName()}`;
      }
    }
  • 9.5 โดยปกติ class จะมี default constructor ถ้าไม่มีการระบุ constructor ใหม่ ดังนั้นการใส่ constructor ที่ว่างเปล่าจึงไม่มีความจำเป็น

    // ไม่ดี
    class Jedi {
      constructor() {}
    
      getName() {
        return this.name;
      }
    }
    
    // ไม่ดี
    class Rey extends Jedi {
      constructor(...args) {
        super(...args);
      }
    }
    
    // ดี
    class Rey extends Jedi {
      constructor(...args) {
        super(...args);
        this.name = 'Rey';
      }
    }
  • 9.6 หลีกเลี้ยงการมีเมท็อตของคลาสที่มีชื่อเดียวกัน

    การที่มีเมท็อตที่มีชื่อซ้ำกันในคลาสเดียวกันอาจทำให้เกิดบัค

    // ไม่ดี
    class Foo {
      bar() { return 1; }
      bar() { return 2; }
    }
    
    // ดี
    class Foo {
      bar() { return 1; }
    }
    
    // ดี
    class Foo {
      bar() { return 2; }
    }

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

Modules

  • 10.1 ควรใช้งานโมดูลในรูปแบบที่ ES6 มีให้ (import/export) แทนการใช้งานโมดูลรูปแบบอื่น ๆ เนื่องจากเราสามารถที่จะคอมไพล์ไฟล์เป็นโมดูลในระบบอื่น ๆ ในภายหลังได้

    เพราะว่าโมดูลจะเป็นรูปแบบที่ถูกใช้อย่างแพร่หลายในอนาคต

    // ไม่ดี
    const AirbnbStyleGuide = require('./AirbnbStyleGuide');
    module.exports = AirbnbStyleGuide.es6;
    
    // ok
    import AirbnbStyleGuide from './AirbnbStyleGuide';
    export default AirbnbStyleGuide.es6;
    
    // ดีที่สุด
    import { es6 } from './AirbnbStyleGuide';
    export default es6;
  • 10.2 หลีกเลี่ยงการใช้ * ในการอิมพอร์ต

    // ไม่ดี
    import * as AirbnbStyleGuide from './AirbnbStyleGuide';
    
    // ดี
    import AirbnbStyleGuide from './AirbnbStyleGuide';
  • 10.3 หลีกเลี่ยงการเอ็กพอร์ต โดยตรงจากอิมพอร์ต

    เพราะว่ามันจะทำให้ดูยากขึ้น แล้วไม่ค่อยเคลียร์ ถึงแม้ว่ามันจะสั้นกว่าก็ตาม

    // ไม่ดี
    // filename es6.js
    export { es6 as default } from './airbnbStyleGuide';
    
    // ดี
    // filename es6.js
    import { es6 } from './AirbnbStyleGuide';
    export default es6;

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

Iterators and Generators

  • 11.1 หลีกเลี่ยงการใช้ Iterators และหันมาใช้ Higher-order functions เช่น map() และ reduce() แทนการใช้งานลูปอย่าง for-of

    เพราะว่าการทำงานกับ Pure functions นั้นดูง่ายกว่า และเพื่อเป็นการลดผลกระทบอื่น ๆ ที่อาจจะตามมาได้

    const numbers = [1, 2, 3, 4, 5];
    
    // ไม่ดี
    let sum = 0;
    for (let num of numbers) {
      sum += num;
    }
    
    sum === 15;
    
    // ดี
    let sum = 0;
    numbers.forEach((num) => sum += num);
    sum === 15;
    
    // ดีที่สุด (use the functional force)
    const sum = numbers.reduce((total, num) => total + num, 0);
    sum === 15;
  • 11.2 หลีกเลี่ยงการใช้งาน Generators (ณ ปัจจุบัน)

    เพราะว่ายังไม่สามารถคอมไพล์กลับไปเป็น ES5 ได้อย่างสมบูรณ์

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

Properties

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

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

    const luke = {
      jedi: true,
      age: 28,
    };
    
    function getProp(prop) {
      return luke[prop];
    }
    
    const isJedi = getProp('jedi');

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

Variables

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

    // ไม่ดี
    superPower = new SuperPower();
    
    // ดี
    const superPower = new SuperPower();
  • 13.2 ใช้หนึ่ง const ต่อหนึ่งตัวแปร

    เพราะดูง่ายกว่า และป้องกันข้อผิดพลาดได้ อย่างเช่น บางครั้งใส่สลับกันระหว่าง ; และ , ซึ่งทำให้ได้ผลลัพธ์ที่ผิด

    // ไม่ดี
    const items = getItems(),
        goSportsTeam = true,
        dragonball = 'z';
    
    // ไม่ดี
    // (compare to above, and try to spot the mistake)
    const items = getItems(),
        goSportsTeam = true;
        dragonball = 'z';
    
    // ดี
    const items = getItems();
    const goSportsTeam = true;
    const dragonball = 'z';
  • 13.3 ประกาศ const ไว้ที่เดียวกัน จากนั้นตามด้วยการประกาศ let ไว้ที่เดียวกัน (อย่าสลับไปมา) และใส่ตัวแปรที่ยังไม่ได้กำหนดค่าไว้ด้านล่างเสมอ

    เพราะเมื่อต้องการที่จะกำหนดค่าให้กับตัวแปรโดยใช้ตัวแปรที่ประกาศไปก่อนหน้านั้น จะสามารถทำได้ง่ายกว่า

    // ไม่ดี
    let i, len, dragonball,
        items = getItems(),
        goSportsTeam = true;
    
    // ไม่ดี
    let i;
    const items = getItems();
    let dragonball;
    const goSportsTeam = true;
    let len;
    
    // ดี
    const goSportsTeam = true;
    const items = getItems();
    let dragonball;
    let i;
    let length;
  • 13.4 ประกาศตัวแปรในที่ ๆ เหมาะสม (จากวิจารณญาณของเราเอง โดยคิดว่าจะทำให้โค้ดมีระเบียบและอ่านง่ายมากขึ้น)

    เพราะว่า let และ const จะมีค่าอยู่แค่ในปีกกาที่ประกาศ (Block-scoped) เท่านั้น จึงปลอดภัย

    // ดี
    function() {
      test();
      console.log('doing stuff..');
    
      //..other stuff..
    
      const name = getName();
    
      if (name === 'test') {
        return false;
      }
    
      return name;
    }
    
    // ไม่ดี - unnecessary function call
    function(hasName) {
      const name = getName();
    
      if (!hasName) {
        return false;
      }
    
      this.setFirstName(name);
    
      return true;
    }
    
    // ดี
    function(hasName) {
      if (!hasName) {
        return false;
      }
    
      const name = getName();
      this.setFirstName(name);
    
      return true;
    }

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

Hoisting

  • 14.1 เวลาคอมไพล์จาวาสคริปต์จะอ่านตัวแปร var ที่ประกาศไว้ก่อนหน้าสิ่งอื่น ๆ ในสโคป แต่ค่าที่ใส่ให้ตัวแปรจะยังไม่ถูกอ่าน ส่วนการประกาศ const และ let จะใช้วิธีการใหม่ที่เรียกว่า Temporal Dead Zones (TDZ) อ่านเหตุผลของวิธีการใหม่นี้ได้จาก typeof is no longer safe

    ผมขอสรุปคร่าว ๆ จากลิ๊งทางข้างต้นเผื่อคนที่ไม่มีเวลาอ่านนะครับ สำหรับ let และ const นั้น ภายในปีกกาเดียวกันจะไม่สามารถประกาศซ้ำกันสองครั้งได้ นอกจากนั้นตัวแปรจะมีค่าก็ต่อเมื่อคอมไพล์เลอร์อ่านถึงบรรทัดที่ประกาศตัวแปร ดังนั้น typeof foo (ถ้ายังไม่มีการประกาศตัวแปร foo) จะได้ผลลัพธ์เป็น ReferenceError แทนที่จะได้ undefined เหมือนใน ES5

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

    function example() {
      console.log(anonymous); // => undefined
    
      anonymous(); // => TypeError anonymous is not a function
    
      var anonymous = function() {
        console.log('anonymous function expression');
      };
    }
  • 14.3 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');
      }
    }
  • 14.4 Function declarations - การประกาศฟังก์ชันโดยไม่ได้ใส่ค่าฟังก์ชันให้ตัวแปร คอมไพล์เลอร์จะอ่านทั้งชื่อและฟังก์ชัน

    function example() {
      superPower(); // => Flying
    
      function superPower() {
        console.log('Flying');
      }
    }
  • อ่านเพิ่มเติมได้ที่ JavaScript Scoping & Hoisting โดย Ben Cherry

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

Comparison Operators & Equality

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

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

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

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

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

Blocks

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

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

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

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

Control Statements

  • 17.1 ในกรณีที่มีการใช้ control statement เช่น if, while และอื่น ๆ มีความยาวเกินความยาวต่อบรรทัดสูงสุด ให้รวมกรุ๊ปแต่ละเงื่อนไขและขึ้นเป็นบรรทัดใหม่

    // ไม่ดี
    if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) {
      thing1();
    }
    
    // ไม่ดี
    if (foo === 123 &&
      bar === 'abc') {
      thing1();
    }
    
    // ไม่ดี
    if (foo === 123
      && bar === 'abc') {
      thing1();
    }
    
    // ไม่ดี
    if (
      foo === 123 &&
      bar === 'abc'
    ) {
      thing1();
    }
    
    // ดี
    if (
      foo === 123
      && bar === 'abc'
    ) {
      thing1();
    }
    
    // ดี
    if (
      (foo === 123 || bar === "abc")
      && doesItLookGoodWhenItBecomesThatLong()
      && isThisReallyHappening()
    ) {
      thing1();
    }
    
    // ดี
    if (foo === 123 && bar === 'abc') {
      thing1();
    }

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

Comments

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

    // ไม่ดี
    // 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;
    }
  • 18.2 ใช้ // สำหรับคอมเม้นต์บรรทัดเดียว โดยใส่ไว้บรรทัดบนของสิ่งที่ต้องการคอมเม้นต์ และเพิ่มบรรทัดว่างไว้ด้านบนคอมเม้นต์ด้วย

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

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

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

    class Calculator {
      constructor() {
        // TODO: total should be configurable by an options param
        this.total = 0;
      }
    }

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

Whitespace

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

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

    // ไม่ดี
    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',
    });
  • 19.3 ใส่ช่องว่างก่อนเปิดวงเล็บสำหรับ control statements (if, else, while, และอื่น ๆ) แต่สำหรับพารามิเตอร์ไม่ต้องใส่ช่องว่าง

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

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

    // ไม่ดี
    (function(global) {
      // ...stuff...
    })(this);
    // ไม่ดี
    (function(global) {
      // ...stuff...
    })(this);
    
    // ดี
    (function(global) {
      // ...stuff...
    })(this);
  • 19.5 ใส่ย่อหน้าเวลาเรียกใช้เมท็อตแบบต่อเนื่อง (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();
    
    // ไม่ดี
    const 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);
    
    // ดี
    const 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);
  • 19.6 ใส่บรรทัดว่างหลังจากจบบล็อก และก่อนที่จะขึ้น statement ใหม่

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

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

Commas

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

    // ไม่ดี
    const story = [
        once
      , upon
      , aTime
    ];
    
    // ดี
    const story = [
      once,
      upon,
      aTime,
    ];
    
    // ไม่ดี
    const hero = {
        firstName: 'Ada'
      , lastName: 'Lovelace'
      , birthYear: 1815
      , superPower: 'computers'
    };
    
    // ดี
    const hero = {
      firstName: 'Ada',
      lastName: 'Lovelace',
      birthYear: 1815,
      superPower: 'computers',
    };
  • 20.2 ควรใส่จุลภาค , ต่อท้ายพรอพเพอร์ตี้ตัวสุดท้าย

    เพราะว่าเวลาดูใน git diff จะเป็นการเพิ่มบรรทัดอย่างเดียว โดยไม่มีการลบบรรทัดก่อนหน้า นอกจากนั้น Transpilers เช่น Babel จะลบตัวจุลภาคนี้ออกเองเวลาคอมไพล์ ทำให้ไม่ต้องกังวลเกี่ยวกับ ปัญหาจุลภาคที่เกินมา ในบราวเซอร์เวอร์ชันเก่า

    // ไม่ดี - git diff เมื่อไม่มีจุลภาคต่อท้าย
    const hero = {
         firstName: 'Florence',
    -    lastName: 'Nightingale' // ลบตัวสุดท้ายออก
    +    lastName: 'Nightingale',
    +    inventorOf: ['coxcomb graph', 'modern nursing']
    }
    
    // ดี - git diff เมื่อมีจุลภาคต่อท้าย
    const hero = {
         firstName: 'Florence',
         lastName: 'Nightingale',
    +    inventorOf: ['coxcomb chart', 'modern nursing'], // เพิ่มบรรทัดอย่างเดียว
    }
    
    // ไม่ดี
    const hero = {
      firstName: 'Dana',
      lastName: 'Scully'
    };
    
    const heroes = [
      'Batman',
      'Superman'
    ];
    
    // ดี
    const hero = {
      firstName: 'Dana',
      lastName: 'Scully',
    };
    
    const heroes = [
      'Batman',
      'Superman',
    ];

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

Semicolons

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

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

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

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

Type Casting & Coercion

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

  • 22.2 สตริง:

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

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

    // ดี
    /**
     * ถ้า parseInt ทำให้โค้ดช้า ให้ใช้
     * Bitshifting เพื่อแปลงค่าเป็นตัวเลขแทน
     * ซึ่งทำให้โค้ดสามารถทำงานได้เร็วขึ้นอย่างมาก
     */
    const val = inputValue >> 0;
  • 22.5 ควรระวังการใช้งาน 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 จึงทำให้เกิดข้อผิดพลาด
  • 22.6 Booleans:

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

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

Naming Conventions

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

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

    // ไม่ดี
    const OBJEcttsssss = {};
    const this_is_my_object = {};
    function c() {}
    
    // ดี
    const thisIsMyObject = {};
    function thisIsMyFunction() {}
  • 23.3 ใช้ PascalCase (ขึ้นต้นทุกคำด้วยตัวใหญ่) เมื่อต้องการตั้งชื่อ constructor หรือ class

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

    // ไม่ดี
    this.__firstName__ = 'Panda';
    this.firstName_ = 'Panda';
    
    // ดี
    this._firstName = 'Panda';
  • 23.5 อย่าบันทึกค่า this ไว้ใช้ ให้ใช้ Arrow functions หรือ Function#bind.

    // ไม่ดี
    function foo() {
      const self = this;
      return function() {
        console.log(self);
      };
    }
    
    // ไม่ดี
    function foo() {
      const that = this;
      return function() {
        console.log(that);
      };
    }
    
    // ดี
    function foo() {
      return () => {
        console.log(this);
      };
    }
  • 23.6 ถ้าในไฟล์มีแค่หนึ่งคลาส ให้ตั้งชื่อไฟล์ให้เป็นชื่อเดียวกับชื่อคลาส

    // file contents
    class CheckBox {
      // ...
    }
    export default CheckBox;
    
    // in some other file
    // ไม่ดี
    import CheckBox from './checkBox';
    
    // ไม่ดี
    import CheckBox from './check_box';
    
    // ดี
    import CheckBox from './CheckBox';
  • 23.7 ใช้ camelCase เมื่อต้องการเอ็กพอร์ตฟังก์ชัน ชื่อไฟล์ควรเป็นชื่อเดียวกับชื่อฟังก์ชัน

    function makeStyleGuide() {
    }
    
    export default makeStyleGuide;
  • 23.8 ใช้ PascalCase เมื่อเอ็กพอร์ต Singleton / Function library / หรือ Bare object

    const AirbnbStyleGuide = {
      es6: {
      }
    };
    
    export default AirbnbStyleGuide;

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

Accessors

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

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

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

    // ไม่ดี
    if (!dragon.age()) {
      return false;
    }
    
    // ดี
    if (!dragon.hasAge()) {
      return false;
    }
  • 24.4 ความจริงแล้วตั้งชื่อ get() และ set() ก็ไม่เสียหายอะไร แต่ต้องตั้งให้เหมือนกันในทุก ๆ ที่

    class Jedi {
      constructor(options = {}) {
        const lightsaber = options.lightsaber || 'blue';
        this.set('lightsaber', lightsaber);
      }
    
      set(key, val) {
        this[key] = val;
      }
    
      get(key) {
        return this[key];
      }
    }

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

Events

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

    // ไม่ดี
    $(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
    });

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

jQuery

  • 26.1 ใส่สัญลักษณ์ $ ไว้ด้านหน้าตัวแปรทุกตัวที่เป็น jQuery Object

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

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

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

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

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

ECMAScript 5 Compatibility

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

ECMAScript 6+ (ES 2015+) Styles

  • 28.1 อ่านเพิ่มเติมเกี่ยวกับฟีเจอร์ต่าง ๆ ของ ES6:
  1. Arrow Functions
  2. Classes
  3. Object Shorthand
  4. Object Concise
  5. Object Computed Properties
  6. Template Strings
  7. Destructuring
  8. Default Parameters
  9. Rest
  10. Array Spreads
  11. Let and Const
  12. Iterators and Generators
  13. Modules

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

Standard Library

ตัว Standard Library มี Utilities หลายตัวที่อาจจะทำงานผิดพลาดหรือไม่ถูกต้องแต่ยังมีให้ใช้อยู่ด้วยเหตุผลทางด้าน Legacy ดังนั้นให้เลือกใช้ตัวที่เหมาะสม

  • 29.1 ใช้ Number.isNaN แทนที่จะใช้ isNaN ที่เป็นฟังก์ชัน Global

    เพราะว่าฟังก์ชั่น isNaN ที่เป็น Global จะแปลงค่าที่ไม่ได้เป็น numbers ให้กลายเป็น numbers และ return ค่า true

    // ไม่ดี
    isNaN('1.2'); // false
    isNaN('1.2.3'); // true
    
    // ดี
    Number.isNaN('1.2.3'); // false
    Number.isNaN(Number('1.2.3')); // true
  • 29.2 ใช้ Number.isFinite แทนที่จะใช้ isFinite ที่เป็นฟังก์ชั่น Global

    เพราะว่าฟังก์ชั่น isFinite ที่เป็น Global จะแปลงค่าที่ไม่ได้เป็น numbers ให้กลายเป็น numbers และ return ค่า true

    // ไม่ดี
    isFinite('2e3'); // true
    
    // ดี
    Number.isFinite('2e3'); // false
    Number.isFinite(parseInt('2e3', 10)); // true

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

Testing

  • 30.1 Yup.

    function() {
      return true;
    }

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

Performance

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

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

Resources

อ่านเพิ่มเติมเกี่ยวกับ ES6

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

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

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

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

หนังสือ

บล็อก

พอดคาสต์ (Podcasts)

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

In the Wild

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

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

Translation

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

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

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

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

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.

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

Amendments

เราแนะนำให้คุณ fork ตัวไกด์นี้และเปลี่ยนกฏให้เหมาะกับสไตล์การเขียนของทีมคุณ คุณสามารถเพิ่มรายชื่อการแก้ไขกฏต่าง ๆ ข้างล่างนี้เพื่อให้สามารถปรับสไตล์เป็นสไตล์ของคุณเองโดยที่ไม่ต้องเจอกับปัญหา merge conflicts

};