دليل Airbnb لنمط جافا سكريبت


محمد الميداوي

ملاحظة: يفترض هذا الدليل أنك تستخدم مصرّف Babel، كما يتطلّب استخدام إعدادات babel-preset-airbnb المسبقة أو ما يماثلها. ويفترض أيضًا أنّك ثبّت ترقيعات متعدّدة (Polyfills/Shims)، عبر airbnb-browser-shims أو ما يماثلها.

أنواع البيانات

الأنواع البدائية (Primitives)

عندما تتعامل مع نوع بدائي فأنت تعمل مباشرةً على قيمته.

  • سلاسل المحارف string،
  • الأعداد number،
  • القيم المنطقية boolean،
  • القيمة المعدومة null،
  • القيمة غير المعرَّفة undefined،
  • الرموز symbol،
  • الأعداد الصحيحة الكبيرة bigint.
const foo = 1;
let bar = foo;

bar = 9;

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

لا يمكن ترقيع النوعين symbol وbigint بدقّة، لذا ينبغي ألّا تُستخدَم عند استهداف المتصفحات/البيئات التي لا تدعمها تلقائيّا.

الأنواع المركبة (Complex)

عند التعامل مع نوع مركّب فأنت تعمل على مرجعٍ لقيمته.

  • الكائنات object،
  • المصفوفات array،
  • الدوال function.
const foo = [1, 2];
const bar = foo;

bar[0] = 9;

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

المراجع References

  • استخدم const لجميع مراجعك. وتجنب استخدام var. استخدم قاعدتي prefer-const وno-const-assign في ESlint.

    لماذا؟ لأنّ هذا سيضمن لك ألّا تعيد تعيين مراجعك، وهو ما يمكن أن يؤدي إلى أخطاء، ويُصعّب فهم الشفرة البرمجية.

    // سيئ
    var a = 1;
    var b = 2;
    
    // جيّد
    const a = 1;
    const b = 2;

     

  • إن كنت مضطرًّا لإعادة تعيين المراجع، استخدم let بدلاً من var. استخدم قاعدة no-var في ESlint.

    لماذا؟ لأن نطاق تعريف let محدود بالكتلة البرمجية (Block-scoped) وليس محدودًا داخل الدالة (Function-scoped) كما هو الحال مع var.

    // سيئ
    var count = 1;
    if (true) {
      count += 1;
    }
    
    // جيد، استخدم let.
    let count = 1;
    if (true) {
      count += 1;
    }

     

  • تذكر أن نطاق كل من let و constمحدود بالكتلة.

    // لا توجد المتغيّرات المصرَّح عنها ب let وconst إلّا بداخل الكتل المُصرَّح فيها
    {
      let a = 1;
      const b = 1;
    }
    console.log(a); // خطأ في المرجع ReferenceError
    console.log(b); // خطأ في المرجع ReferenceError

     

الكائنات Objects

  • استخدم صياغة تصنيف النوع (Literal syntax) لإنشاء الكائنات. استخدم قاعدة no-new-object في ESLint.

    // سيئ
    const item = new Object();
    
    // جيّد
    const item = {};

     

  • استخدم أسماء محسوبة للخاصيّات عند إنشاء كائنات بأسماء خاصيّات ديناميكية.

    لماذا؟ لأن ذلك سيسمح لك بتعريف جميع خاصيّات الكائن في مكان واحد.

    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,
    };

     

  • استخدم أسلوب التعريف المختصر لتوابع الكائن. (قاعدة object-shorthand في ESLint).

    // سيئ
    const atom = {
      value: 1,
    
      addValue: function (value) {
        return atom.value + value;
      },
    };
    
    // جيّد
    const atom = {
      value: 1,
    
      addValue(value) {
        return atom.value + value;
      },
    };

     

  • استخدم التعريف المختصر لقيمة الخاصية (قاعدة object-shorthand في ESLint).

    لماذا؟ لأنه أقصر وأوضح.

    const lukeSkywalker = 'Luke Skywalker';
    
    // سيئ
    const obj = {
      lukeSkywalker: lukeSkywalker,
    };
    
    // جيّد
    const obj = {
      lukeSkywalker,
    };

     

  • اجمع الخاصيّات المُختصرة في بداية التصريح بالكائن (Object declaration).

    لماذا؟ لأنّ هذه الطريقة تسهّل معرفة أي الخاصيّات تستخدم الاختصار.

    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,
    };

     

  • لا تضع بين علامات التنصيص إلّا الخاصيّات التي لها أسماء غير صالحة.

    لماذا؟ بشكل عام، لأنّه أسهل للقراءة ويُحسّن وضوح الكود، كما أنّه يسهُل استخدامه من قبل محركات الجافا سكريبت.

    // سيئ
    const سيئ = {
      'foo': 3,
      'bar': 4,
      'data-blah': 5,
    };
    
    // جيّد
    const جيّد = {
      foo: 3,
      bar: 4,
      'data-blah': 5,
    };

     

  • لا تستدع توابع Object.prototype ، مثل hasOwnProperty، propertyIsEnumerable، و isPrototypeOf، لا تستدعها مباشرة. استخدم قاعدة no-prototype-builtins في ESLint.

    لماذا؟ لأنّ خاصيّات الكائن قد تغطّي تلك التوابع - انظر مثلًا إلى {hasOwnProperty: false} – علاوة على أن الكائن قد يكون معدومًا (Object.create(null)).

    // سيئ
    console.log(object.hasOwnProperty(key));
    
    // جيّد
    console.log(Object.prototype.hasOwnProperty.call(object, key));
    
    // أفضل
    const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope.
    /* أو */
    import has from 'has'; // https://www.npmjs.com/package/has
    // ...
    console.log(has.call(object, key));

     

  • يفضل تطبيق عامل التمديد (Spread operator) على الكائن بدلًا من استخدام التابع Object.assign إن كنت تريد النسخ السطحي (Shallow-copy) للكائنات. أو يمكنك استخدام عامل الاستناد (Rest operator) للحصول على كائن جديد مع حذف خصائص معينة.

    // سيئ جدًّا
    const original = { a: 1, b: 2 };
    const copy = Object.assign(original, { c: 3 }); // يتسبّب في التعديل على الكائن `original`
    delete copy.a; // الأمر نفسه هنا
    
    // سيئ
    const original = { a: 1, b: 2 };
    const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
    
    // جيّد
    const original = { a: 1, b: 2 };
    const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
    
    const { a, ...noA } = copy; // noA => { b: 2, c: 3 }

     

المصفوفات Arrays

  • استخدم صياغة تصنيف النوع (Literal syntax) لإنشاء المصفوفة (قاعدة no-array-constructor في ESLint).

    // سيئ
    const items = new Array();
    
    // جيّد
    const items = [];

     

  • استخدم Array#push بدلًا من الإسناد المباشر لإضافة عناصر إلى المصفوفة.

    const someStack = [];
    
    // سيئ
    someStack[someStack.length] = 'abracadabra';
    
    // جيّد
    someStack.push('abracadabra');

     

استخدم تمديد المصفوفات ... (Array spreads) لنسخ المصفوفات.

// سيئ
const len = items.length;
const itemsCopy = [];
let i;

for (i = 0; i < len; i += 1) {
  itemsCopy[i] = items[i];
}

// جيّد
const itemsCopy = [...items];
  • استخدم عامل التمديد ... بدلًا من التابع Array.from لتحويل كائن مُكرَّر (Iterable) إلى مصفوفة.

    const foo = document.querySelectorAll('.foo');
    
    // جيّد
    const nodes = Array.from(foo);
    
    // أفضل
    const nodes = [...foo];

     

  • استخدم التابع Array.from بدلًا من عامل التمديد ... لتحويل كائن شبيه بالمصفوفات إلى مصفوفة.

const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };

// سيئ
const arr = Array.prototype.slice.call(arrLike);

// جيّد
const arr = Array.from(arrLike);
  • استخدم التابع Array.from بدلًا من عامل التمديد ... لتطبيق الدالة map على الكائنات المُكرّرة، بهدف تجنّب خلق مصفوفة مؤقتة.

    // سيئ
    const baz = [...foo].map(bar);
    
    // جيّد
    const baz = Array.from(foo, bar);

     

  • استخدم التعليمة return في رد نداء توابع المصفوفات (Method callbacks). لا ضير في حذف التعليمة return إن كان متن الدالة يتكون من تعليمة واحدة من دون آثار جانبية. استخدم قاعدة array-callback-return في ESLint.

    // جيّد
    [1, 2, 3].map((x) => {
      const y = x + 1;
      return x * y;
    });
    
    // جيّد
    [1, 2, 3].map(x => x + 1);
    
    // سيئ -عدم وجود قيمة مُرجَعة يعني أن "acc" يصبح غير معرّف بعد عملية التكرار الأولى
    [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
      const flatten = memo.concat(item);
      memo[index] = flatten;
    });
    
    // جيّد
    [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
      const flatten = memo.concat(item);
      memo[index] = flatten;
      return flatten;
    });
    
    // سيئ
    inbox.filter((msg) => {
      const { subject, author } = msg;
      if (subject === 'Mockingbird') {
        return author === 'Harper Lee';
      } else {
        return false;
      }
    });
    
    // جيّد
    inbox.filter((msg) => {
      const { subject, author } = msg;
      if (subject === 'Mockingbird') {
        return author === 'Harper Lee';
      }
    
      return false;
    });

     

  • استخدم سطرًا جديدًا بعد معقوفة الفتح وقبل معقوفة إغلاق المصفوفة إذا كان المصفوفة متعددة الأسطر.

    // سيئ
    const arr = [
      [0, 1], [2, 3], [4, 5],
    ];
    
    const objectInArray = [{
      id: 1,
    }, {
      id: 2,
    }];
    
    const numberInArray = [
      1, 2,
    ];
    
    // جيّد
    const arr = [[0, 1], [2, 3], [4, 5]];
    
    const objectInArray = [
      {
        id: 1,
      },
      {
        id: 2,
      },
    ];
    
    const numberInArray = [
      1,
      2,
    ];

     

التفكيك Destructuring

  • استخدم تفكيك الكائن عند التعامل مع عدة خاصيّات للكائن.

    لماذا؟ التفكيك يُعفيك من الحاجة إلى إنشاء مراجع مؤقتة لتلك الخصائص.

    // سيئ
    function getFullName(user) {
      const firstName = user.firstName;
      const lastName = user.lastName;
    
      return `${firstName} ${lastName}`;
    }
    
    // جيّد
    function getFullName(user) {
      const { firstName, lastName } = user;
      return `${firstName} ${lastName}`;
    }
    
    // أفضل
    function getFullName({ firstName, lastName }) {
      return `${firstName} ${lastName}`;
    }

     

  • استخدم تفكيك المصفوفات.

    const arr = [1, 2, 3, 4];
    
    // سيئ
    const first = arr[0];
    const second = arr[1];
    
    // جيّد
    const [first, second] = arr;

     

  • استخدم تفكيك الكائن لأجل إرجاع أكثر من قيمة، وليس تفكيك المصفوفات. لماذا؟ يمكنك إضافة خاصيات جديدة مع الوقت وتغيير الترتيب دون مشاكل.

    // سيئ
    function processInput(input) {
      // then a miracle occurs
      return [left, right, top, bottom];
    }
    // تحتاج إلى أخذ ترتيب البيانات المُرجَعة في الحسبان
    
    const [left, __, top] = processInput(input);
    
    // جيّد
    function processInput(input) {
      // then a miracle occurs
      return { left, right, top, bottom };
    }
    
    // تختار البيانات التي تحتاجها فقط
    const { left, top } = processInput(input);

     

السلاسل النصية Strings

  • استخدم علامات التنصيص المفردة '' لتحديد السلاسل النصيّة (القاعدة quotes في ESLint).

    // سيئ
    const name = "Capt. Janeway";
    
    // سيئ - يجب أن تحتوي القوالب مصنَّفة النوع على حشو (Interpolation) أو أسطر جديدة.
    const name = `Capt. Janeway`;
    
    // جيّد
    const name = 'Capt. Janeway';

     

  • ينبغي ألّا تُكتَب النصوص التي يتجاوز طولها 100 حرف على أسطر متعددة باستخدام ضمّ النصوص (Concatenation). لماذا؟ النصوص التي فيها أخطاء تكون مزعجةً وتجعل الكود أقل قابلية للبحث.

    // سيئ
    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.';

     

  • عند بناء النصوص برمجيًّا، استخدم القوالب النصيّة بدلًا من ضمّ النصوص (قاعدة prefer-template template-curly-spacing في ESLint).

    لماذا؟ تجعل القوالب النصيّة الشفرة أكثر مقروئية وإيجازًا، مع أسطر جديدة مناسبة وميزات حشو النصوص.

    // سيئ
    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 }?`;
    }
    
    // جيّد
    function sayHi(name) {
      return `How are you, ${name}?`;
    }

     

  • لا تطبّق أبدًا ()eval على النصوص، لأنها تتسبّب في الكثير من الثغرات. استخدم قاعدة no-eval في ESLint. تجنب تخليص الأحرف (Escape characters) في النصوص قدر الإمكان. لماذا؟ الخط المائل العكسي يضر بالمقروئية، وبالتالي يجب ألًا يُستخدم إلا عند الضرورة.

    // سيئ
    const foo = '\'this\' \i\s \"quoted\"';
    
    // جيّد
    const foo = '\'this\' is "quoted"';
    const foo = `my name is '${name}'`;

     

الدوال

  • استخدم الدوال العبارات (Function expressions) المُسماة بدلًا من التصريح بالدوال (قاعدة func-style في ESLint). لماذا؟ يضع محرَك جافاسكريبت الدوال المُصرَّح بها في بداية النطاق (تُعرَف هذه العملية بالرفع Hoist)، وبالتالي يمكن استخدام الدالة قبل تعريفها في الملف. يعني ذلك أنه سيكون من السهل الإحالة إلى الدالة قبل أن تُعرَّف في الملف، وهو ما يضر المقروئية وقابلية الصيانة. إذا وجدت أن تعريف دالة ما كبير أو معقد حدّ الإرباك، فربما حان الوقت لوضعه في وحدة خاصة به! لا تنس أن تُسمّي العبارة صراحة، بغض النظر عما إذا كان الاسم مستنتجًا من المتغير الحاوي (كما هو الحال غالبًا في المتصفحات الحديثة أو عند استخدام مصرّفات مثل Babel) أم لا. تلغي هذه الطريقة أي افتراضات حول مكدس النداء إلى الخطأ Error’s call stack

    // سيئ
    function foo() {
      // ...
    }
    
    // سيئ
    const foo = function () {
      // ...
    };
    
    // جيّد
    // اسم العبارة الدالة مغاير لاسم الاستدعاء الذي يحيل إلى المتغيّر
    const short = function longUniqueMoreDescriptiveLexicalFoo() {
      // ...
    };

     

  • ضع الدوال العبارات فورية الاستدعاء (Immediately-invoked Function Expression، أو IIFE اختصارًا)، ضعها بين قوسين. استخدم قاعدة wrap-iife في ESLint. لماذا؟ الدوال العباراة فورية الاستدعاء هي وحدة منفردة، لذلك وضعها، هي وعبارة استدعائها، بين قوسين يجعل الشفرة واضحة. وإن كان من المستبعد جدّا أن تحتاج الدوال فورية الاستدعاء (IIFE) في مشاريع تكثر من استخدام الوحدات الوظيفية (Modules).

    // immediately-invoked function expression (IIFE)
    (function () {
      console.log('Welcome to the Internet. Please follow me.');
    }());

     

  • لا تصرّح أبدًا بالدوال في كتلة غير خاصة بالدوال (مثل if، وwhile، …إلخ). أسند الدالة إلى متغير بدلًا من ذلك. تسمح المتصفحات بالتصريح بالدالة في تلك الكتل، إلّا أنها ستفسرها بطرق مختلفة، وهو أمر لا يحبه المبرمجون. استخدم قاعدة no-loop-func في ESLint.

  • ملحوظة: يعرّف معيار ECMA-262 الكتلة (Block) بأنها قائمة من التعليمات البرمجية (Statements). والتصريح بدالة (Function decleration) ليس تعليمة

    // سيئ
    if (currentUser) {
      function test() {
        console.log('Nope.');
      }
    }
    
    // جيّد
    let test;
    if (currentUser) {
      test = () => {
        console.log('Yup.');
      };
    }

     

  • لا تسمّي معاملًا بالاسم arguments. لأنه سيأخذ الأسبقية على الكائن arguments الذي يُحدد تلقائيّا في نطاق كل دالة.

    // سيئ
    function foo(name, options, arguments) {
      // ...
    }
    
    // جيّد
    function foo(name, options, args) {
      // ...
    }

     

  • لا تستخدم أبدًا المعامل arguments، واستخدم بدلًا منه عامل التمديد (...). لماذا؟ عامل التمديد واضح في تحديد الوسائط التي تريد سحبها. بالإضافة إلى ذلك، وسائط عامل التمديد هي مصفوفة حقيقة، وليست شبيهة بالمصفوفة مثل الكائن arguments. استخدم قاعدة prefer-rest-params في ESLint.

    // سيئ
    function concatenateAll() {
      const args = Array.prototype.slice.call(arguments);
      return args.join('');
    }
    
    // جيّد
    function concatenateAll(...args) {
      return args.join('');
    }

     

  • استخدم الصيغة الافتراضية للمعاملات بدلًا من التعديل على وسائط الدالة.

    // سيئ جدّا
    function handleThings(opts) {
      // لا! يجب ألّا نعدّل على وسائط الدالة.
      // أمر سيئ آخر: إذا كانت قيمة الوسيط opts تساوي القيمة المنطقية false فسيُسنَد كائن إلى الوسيط opts، وهو ما قد تريده إلّا أنه يتسبب في ثغرات تصعب ملاحظتها
      opts = opts || {};
      // ...
    }
    
    // سيئ أيضا
    function handleThings(opts) {
      if (opts === void 0) {
        opts = {};
      }
      // ...
    }
    
    // جيّد
    function handleThings(opts = {}) {
      // ...
    }

     

  • تجنب الآثار الجانبية في المعاملات الافتراضية. لماذا؟ لأنّها مربكة.

    var b = 1;
    // سيئ
    function count(a = b++) {
      console.log(a);
    }
    count();  // 1
    count();  // 2
    count(3); // 3
    count();  // 3

     

  • ضع دائمًا المعاملات الافتراضية في الأخير.

    // سيئ
    function handleThings(opts = {}, name) {
      // ...
    }
    
    // جيّد
    function handleThings(name, opts = {}) {
      // ...
    }

     

  • لا تستخدم أبدًا منشئ الدوال (Function constructor) لإنشاء دالة جديدة. استخدم قاعدة no-new-func في ESLint. لماذا؟ إنشاء دالة بهذه الطريقة يتضمّن تقييم نص كما تفعل ()eval، وهو ما يفتح نقاط ضعف.

    // سيئ
    var add = new Function('a', 'b', 'return a + b');
    
    // سيئ كذلك
    var subtract = Function('a', 'b', 'return a - b');

     

  • ضع مسافات في توقيع الدالة. استخدم قاعدتي space-before-function-paren وspace-before-blocks في ESLint. لماذا؟ الاتساق أمر جيد، كما لن تكون مضطرًا لإضافة أو إزالة مسافة عند إضافة أو إزالة اسم.

// سيئ
const f = function(){};
const g = function (){};
const h = function() {};

// جيّد
const x = function () {};
const y = function a() {};
  • لا تعدّل أبدًا على المعاملات. استخدم قاعدة no-param-reassign في ESLint. لماذا؟ يمكن للتعديل على الكائنات التي مُرُرت كمعاملات أن يتسبب في آثار جانبية غير مرغوب فيها في المستدعي الأصلي.

    // سيئ
    function f1(obj) {
      obj.key = 1;
    }
    
    // جيّد
    function f2(obj) {
      const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
    }

     

  • لا تُعد إسناد المعاملات. استخدم قاعدة no-param-reassign في ESLint. لماذا؟ إعادة إسناد المعاملات يمكن أن يؤدي إلى سلوك غير متوقع، خصوصًا عند التعامل مع الكائن arguments. كما يمكن أن يسبب مشاكل في الأداء، خصوصا في V8.

    // سيئ
    function f1(a) {
      a = 1;
      // ...
    }
    
    function f2(a) {
      if (!a) { a = 1; }
      // ...
    }
    
    // جيّد
    function f3(a) {
      const b = a || 1;
      // ...
    }
    
    function f4(a = 1) {
      // ...
    }

     

  • من الأفضل استخدام استخدام عامل التمديد (...) لاستدعاء الدوال متغيرة عدد الوسائط (Variadic functions). استخدم قاعدة prefer-spread في ESLint. لماذا؟ لأنها أوضح، فلست مضطرًّا لتجهيز السياق، كما لا يمكنك أن تجمع بسهولة new مع apply.

    // سيئ
    const x = [1, 2, 3, 4, 5];
    console.log.apply(console, x);
    
    // جيّد
    const x = [1, 2, 3, 4, 5];
    console.log(...x);
    
    // سيئ
    new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));
    
    // جيّد
    new Date(...[2016, 8, 5]);

     

  • الدوال التي لها توقيعات أو استدعاءات متعددة الأسطر، ينبغي أن تكون مسافاتها البادئة تمامًا مثل كل القوائم متعددة الأسطر الأخرى في هذا الدليل، بمعنى أن كل عنصر في سطر، مع فاصلة زائدة بعد العنصر الأخير.

    // سيئ
    function foo(bar,
                 baz,
                 quux) {
      // ...
    }
    
    // جيّد
    function foo(
      bar,
      baz,
      quux,
    ) {
      // ...
    }
    
    // سيئ
    console.log(foo,
      bar,
      baz);
    
    // جيّد
    console.log(
      foo,
      bar,
      baz,
    );

     

الدوال السهمية Arrow Functions

  • استخدم صيغة الدالة السهمية عندما تكون مضطرًّا لاستخدام دالة مجهولة (Anonymous function)، مثلًا عند تمرير ردّ نداء (Callback) على السطر. استخدم قاعدتي prefer-arrow-callback وarrow-spacing في ESlint.

    لماذا؟ لأنها تخلق نسخة من الدالة تُنفَّذ في السياق this، وهو عادةً ما ترغب فيه، كما أنها أكثر إيجازا.

    متى تتخلّى عنها؟ إذا كانت لديك دالة معقدة، فيمكنك نقل وظيفتها إلى دالة عبارة مُسمّاة.

    // سيئ
    [1, 2, 3].map(function (x) {
      const y = x + 1;
      return x * y;
    });
    
    // جيّد
    [1, 2, 3].map((x) => {
      const y = x + 1;
      return x * y;
    });

     

  • إن كان متن الدالة يتكون من تعليمة واحدة تُرجِع تعبيرًا دون آثار جانبية، فاحذف القوسين المعقوصيْن ({}) واستخدم الإرجاع الضمني (بدون التعليمة return). خلافًا لذلك، أبق على الأقواس المعقوصة واستخدم التعليمة return. استعن بقاعدتي arrow-parens و arrow-body-style في ESLint.

    لماذا؟ تسهيل قراءة الشفرة عند استخدام دوال بالتسلسل.

    // سيئ
    [1, 2, 3].map(number => {
      const nextNumber = number + 1;
      `A string containing the ${nextNumber}.`;
    });
    
    // جيّد
    [1, 2, 3].map(number => `A string containing the ${number}.`);
    
    // جيّد
    [1, 2, 3].map((number) => {
      const nextNumber = number + 1;
      return `A string containing the ${nextNumber}.`;
    });
    
    // جيّد
    [1, 2, 3].map((number, index) => ({
      [index]: number,
    }));
    
    // لا يوجد إرجاع ضمني لكن توجد آثار جانبية
    function foo(callback) {
      const val = callback();
      if (val === true) {
        //  افعل شيئًا هنا إذا كان رد النداء إيجابيا
      }
    }
    
    let bool = false;
    
    // سيئ
    foo(() => bool = true);
    
    // جيّد
    foo(() => {
      bool = true;
    });

     

  • في حال امتد التعبير عبر عدة أسطر، ضعه بين قوسين لمقروئية أكبر. لماذا؟ لتوضيح أين تبدأ وأين تنتهي الدالة.

// سيئ
['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call(
    httpMagicObjectWithAVeryLongName,
    httpMethod,
  )
);

// جيّد
['get', 'post', 'put'].map(httpMethod => (
  Object.prototype.hasOwnProperty.call(
    httpMagicObjectWithAVeryLongName,
    httpMethod,
  )
));
  • أضف دائمًا أقواسًا حول الوسائط من أجل الوضوح والتناسق. استعن بقاعدة arrow-parens في ESlint.

    لماذا؟ تقليل الأخطاء عند إضافة وسائط أو حذفها.

    // سيّئ
    [1, 2, 3].map(x => x * x);
    
    // جيّد
    [1, 2, 3].map((x) => x * x);
    
    // سيّئ
    [1, 2, 3].map(number => (
      `A long string with the ${number}. Its so long that we dont want it to take up space on the .map line!`
    ));
    
    // جيّد
    [1, 2, 3].map((number) => (
      `A long string with the ${number}. Its so long that we dont want it to take up space on the .map line!`
    ));
    
    // سيّئ
    [1, 2, 3].map(x => {
      const y = x + 1;
      return x * y;
    });
    
    // جيّد
    [1, 2, 3].map((x) => {
      const y = x + 1;
      return x * y;
    });

     

  • تجنب الخلط بين صياغة الدوال السهمية (=>) وبين عوامل المقارنة (<= , >=). استعن بقاعدة no-confusing-arrow.

    // سيئ
    const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize;
    
    // سيئ
    const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize;
    
    // جيّد
    const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize);
    
    // جيّد
    const itemHeight = (item) => {
      const { height, largeSize, smallSize } = item;
      return height > 256 ? largeSize : smallSize;
    };

     

  • افرض موقع متن الدوال السهميّة عن طريق الإرجاع الضمني (Implicit return). استعن بقاعدة implicit-arrow-linebreak.

    // سيّئ
    (foo) =>
      bar;
    
    (foo) =>
      (bar);
    
    // جيّد
    (foo) => bar;
    (foo) => (bar);
    (foo) => (
       bar
    )

     

الأصناف والمُنشِئات Classes & Constructors

  • استخدم دائمًا الكلمة المفتاحية 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;
      }
    }

     

  • استخدم الكلمة المفتاحية extends للتوارث بين الأصناف.

    لماذا؟ لأنها وسيلة مدمجة لوارثة الوظائف من النموذج الأولي دون التسبب بمشاكل عند استخدام العامل 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];
      }
    }

     

  • يمكن للتوابع أن تُرجع الكائن this للمساعدة استخدام التوابع بالتسلسل.

    // سيئ
    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);

     

  • لا بأس في تخصيص التابع ()toString، لكن تأكد من أنه يعمل بسلاسة ولا تتسبب في أي آثار جانبية.

    class Jedi {
      constructor(options = {}) {
        this.name = options.name || 'no name';
      }
    
      getName() {
        return this.name;
      }
    
      toString() {
        return `Jedi - ${this.getName()}`;
      }
    }

     

  • توجد منشئات افتراضية للأصناف يُلجَا إليها إنْ لم يُحدّد منشئ سلفا. لذا، فلا حاجة لمنشئات فارغة أو منشئات تقتصر على الإنابة عن الصنف الأب. استعن بقاعدة no-useless-constructor في ESLint.

    // سيئ
    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';
      }
    }

     

  • تجنب تكرار عناصر الصنف. استعن بقاعدة no-dupe-class-members في ESLint.

    لماذا؟ في حال التصريح المكرر لعنصر من عناصر الصنف، فالقيمة الأخيرة فقط هي التي ستُعتمَد. وجود التكرار يعني بالضرورة وجود علّة في الشفرة البرمجية.

    // سيئ
    class Foo {
      bar() { return 1; }
      bar() { return 2; }
    }
    
    // جيّد
    class Foo {
      bar() { return 1; }
    }
    
    // جيّد
    class Foo {
      bar() { return 2; }
    }

     

  • يجب أن تستخدم توابع الصنف الكائن this أو تُدرَج في تابع ثابت (Static) إلّا إذا استدعت مكتبة خارجية أو إطار عمل استخدام توابع غير ثابتة. كون التابع مرتبطًا بنظير كائن (Instance) يجب أن يشير إلى أنه يتصرف بسلوك مختلف حسب خاصيّات الكائن المستقبل. استعن بالقاعدة class-methods-use-this.

    // سيّئ
    class Foo {
      bar() {
        console.log('bar');
      }
    }
    
    // جيّد، استخدام this
    class Foo {
      bar() {
        console.log(this.bar);
      }
    }
    
    // جيّد، المنشئ مستثنى من القاعدة
      constructor() {
        // ...
      }
    }
    
    // جيّد، يفترض ألا تستخدم التوابع الثابتة الكائن this
    class Foo {
      static bar() {
        console.log('bar');
      }
    }

     

الوحدات Modules

  • استخدم دائمًا الكلمتيْن المفتاحيتيْن (import/export) لاستيراد أو تصدير الوحدات، بدلًا من الطرق الأخرى غير القياسية. يمكنك دائمًا تصريف الوحدات المفضلة لديك إلى شفرة جافاسكريبت (Transpile).

    لماذا؟ الوحدات هي المستقبل، دعونا نُدشن المستقبل منذ الآن.

    // سيئ
    const AirbnbStyleGuide = require('./AirbnbStyleGuide');
    module.exports = AirbnbStyleGuide.es6;
    
    // مقبول
    import AirbnbStyleGuide from './AirbnbStyleGuide';
    export default AirbnbStyleGuide.es6;
    
    // أفضل
    import { es6 } from './AirbnbStyleGuide';
    export default es6;

     

  • لا تعتمد على محارف البدل (Wild card) في استيراد الوحدات (الاستيراد بالجملة).

    لماذا؟ لتتأكد من أنّ لديك تصديرًا افتراضيّا واحدا.

    // سيئ
    import * as AirbnbStyleGuide from './AirbnbStyleGuide';
    
    // جيّد
    import AirbnbStyleGuide from './AirbnbStyleGuide';

     

  • لا تُصدّر مباشرةً من الاستيراد.

    لماذا؟ على الرغم من أن الكتابة في سطر واحد تكون أوجز، إلّا أن وجود طريقة واضحة واحدة للاستيراد وأخرى للتصدير يجعل الأمور متسقة.

    // سيئ
    // filename es6.js
    export { es6 as default } from './AirbnbStyleGuide';
    
    // جيّد
    // filename es6.js
    import { es6 } from './AirbnbStyleGuide';
    export default es6;

     

  • استورد من مسار معيّن في مكان واحد فقط. استعن بقاعدة no-duplicate-imports.

    لماذا؟ وجود عدة أسطر تستورد من المسار نفسه يمكن أن يجعل الشفرة أقل قابلية للصيانة.

    // سيئ
    import foo from 'foo';
    // … some other imports … //
    import { named1, named2 } from 'foo';
    
    // جيّد
    import foo, { named1, named2 } from 'foo';
    
    // جيّد
    import foo, {
      named1,
      named2,
    } from 'foo';

     

  • لا تُصدر الارتباطات المتحوّلة (Mutable bindings). استعن بقاعدة no-duplicate-imports.

    لماذا؟ يُفضّل عمومًا تجنب العناصر المتحولة، ولكن على وجه الخصوص عند تصدير الارتباطات المتحولة. على الرغم من أن هذا الأسلوب قد يكون ضروريّا في حالات خاصة، إلّا أنّ القاعدة الأساسية هي ألّا تُصدرَ إلا المراجع الثابتة.

    // سيئ
    let foo = 3;
    export { foo };
    
    // جيّد
    const foo = 3;
    export { foo };

     

  • في الوحدات التي فيها تصدير واحد فقط، يفضل استخدام التصدير الافتراضي بدلًا من التصدير المسمى named export. استعن بقاعدة import/prefer-default-export.

    لماذا؟ لتشجيع استخدام الملفات التي لا تصدر إلًا شيئًا واحدًا فقط، لأن ذلك أفضل وأسهل للقراءة والصيانة.

    // سيئ
    export function foo() {}
    
    // جيّد
    export default function foo() {}

     

  • ضع كل عبارات import فوق عبارات الاستيراد الأخرى. استعن بقاعدة import/first في ESLint.

    لماذا؟ بما أن محرّك جافاسكريبت يرفع تعليمات import إلى أعلى النطاق (Hoisted)، فوضعها كلها في الجزء العلوي يمنع أي سلوك غير متوقع.

    // سيئ
    import foo from 'foo';
    foo.init();
    
    import bar from 'bar';
    
    // جيّد
    import foo from 'foo';
    import bar from 'bar';
    
    foo.init();

     

  • يجب أن توضع مسافة بادئة قبل عناصر الاستيراد ذي الأسطر المتعدّدة، مثله مثل المصفوفات متعددة الأسطر والكائنات مصنّفة النوع (Object literals). استعن بالقاعدة object-curly-newline.

    لماذا؟ تتبع الأقواس المعقوصة قواهد المسافات البادئة ذاتها التي تتبعها كتل الأقواس المعقوصة الأخرى في هذا الدليل. الأمر نفسه ينطبق على الفواصل الزائدة (Trailing commas).

// سيئ
import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';

// جيّد
import {
  longNameA,
  longNameB,
  longNameC,
  longNameD,
  longNameE,
} from 'path';
  • لا تسمح بصيغة Webpack للتحميل في تعليمات استيراد الوحدات. استعن بالقاعدة import/no-webpack-loader-syntax.

    لماذا؟ لأن استخدام صيغة Webpack في الاستيراد تجعل الشفرة معتمدة على محزّم (Bundler). من الأفضل استخدام صيغة Webpack في الملف webpack.config.js.

    // سيئ
    import fooSass from 'css!sass!foo.scss';
    import barCss from 'style!css!bar.css';
    
    // جيّد
    import fooSass from 'foo.scss';
    import barCss from 'bar.css';

     

  • لا تُضمّن امتداد ملفات جافاسكريبت في الاستيراد. استعن بالقاعدة import/extensions.

    لماذا؟ لأن تضمين الامتدادات يصعّب إعادة كتابة الشفرة، ويضع لدى جميع العملاء تفاصيل غير مناسبة عن الوحدة التي تستوردها.

    // سيّئ 
    import foo from './foo.js';
    import bar from './bar.jsx';
    import baz from './baz/index.jsx';
    
    // جيّد
    import foo from './foo';
    import bar from './bar';
    import baz from './baz';

     

المُكرّرات والمولّدات

  • لا تستخدم المكررات (Iterators). من الأفضل استخدام دوال الدرجات العليا لجافاسكريبت بدلًا من الحلقات مثلfor-in أو for-of.

    لماذا؟ هذا يتماشى مع قاعدة انعدام التحول (Immutable rule) التي ننتهجها. التعامل مع الدوال التي تُرجع قيمًا أسهل للفهم مقارنة بالدوال ذات الآثار الجانبية. استعن بقاعدتي no-iterator وno-restricted-syntax.

    استخدم الدوال ()map / every() / filter()، find() / findIndex() / reduce() / some / للمرور على المصفوفات، والتوابع ()Object.keys / Object.values() / Object.entries لإنتاج مصفوفات حتى تتمكن من المرور على عناصر الكائن.

    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;
    
    // أفضل (استخدام فعالية الدوال، برمجة وظيفية)
    const sum = numbers.reduce((total, num) => total + num, 0);
    sum === 15;
    
    // سيئ
    const increasedByOne = [];
    for (let i = 0; i < numbers.length; i++) {
      increasedByOne.push(numbers[i] + 1);
    }
    
    // جيّد
    const increasedByOne = [];
    numbers.forEach((num) => {
      increasedByOne.push(num + 1);
    });
    
    // أفضل (الاعتماد على التصور الوظيفي)
    const increasedByOne = numbers.map(num => num + 1);

     

  • لا تستخدم المولدات (Generators) في الوقت الراهن.

    لماذا؟ لأنها لا تُصرَّف على نحو جيّد إلى ES5.

  • إذا كان عليك استخدام المُولدات، أو إذا أردت تجاهل نصيحتنا، تأكد من أن تواقيع دوالها متباعدة بشكل صحيح. استعن بالقاعدة generator-star-spacing.

    لماذا؟ function و * هما كلمتان مفتاحيتان من نفس المفهوم، فالكلمة المفتاحية * ليست تعديلًا للكلمة المفتاحية function، و function* مختلفة عن function.

    // سيئ
    function * foo() {
      // ...
    }
    
    // سيئ
    const bar = function * () {
      // ...
    };
    
    // سيئ
    const baz = function *() {
      // ...
    };
    
    // سيئ
    const quux = function*() {
      // ...
    };
    
    // سيئ
    function*foo() {
      // ...
    }
    
    // سيئ
    function *foo() {
      // ...
    }
    
    // سيئ جدّا
    function
    *
    foo() {
      // ...
    }
    
    // سيئ جدّا
    const wat = function
    *
    () {
      // ...
    };
    
    // جيّد
    function* foo() {
      // ...
    }
    
    // جيّد
    const foo = function* () {
      // ...
    };

     

الخصائص

  • استخدم أسلوب الترميز بالنقطة (Dot notation) عند الدخول إلى الخصائص.

    const luke = {
      jedi: true,
      age: 28,
    };
    
    // سيئ
    const isJedi = luke['jedi'];
    
    // جيّد
    const isJedi = luke.jedi;

    استخدام المعقوفتين [] عند الدخول إلى الخاصيّات بواسطة متغير.

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

     

  • استخدام العامل الأسي ** عند حساب الأسّ.

    // سيئ
    const binary = Math.pow(2, 10);
    
    // جيّد
    const binary = 2 ** 10;

     

المتغيرات

  • استخدم دومًا const أو let للتصريح بالمتغيرات. عدم القيام بذلك سيؤدي إلى إنشاء متغيرات عامة، في حين نريد تجنب تلويث فضاء الأسماء العام (Global namespace). استعن بقاعدتيْ no-undef وprefer-const.

    // سيئ
    superPower = new SuperPower();
    
    // جيّد
    const superPower = new SuperPower();
    
  • صرّح باستخدام const أو let لكل متغيّر على حدة. استعن بقاعدة one-var في ESlint.

    لماذا؟ من الأسهل إضافة متغيرات جديدة بهذه الطريقة، ولن تكون مضطرا لإبدال الفاصلة المنقوطة ; بفاصلة , كل ما أردت إضافة متغيّر جديد، وستتخلّص من التعديلات المقتصرة على علامات التنقيط. ستتمكّن كذلك من المرور خلال التنقيح (Debugging) على كل متغيّر على حدة، بدلًا من القفز عليها كلها في وقت واحد.

    // سيئ
    const items = getItems(),
        goSportsTeam = true,
        dragonball = 'z';
    
    // سيئ (قارن بالتصريح فوقه لتعثر على الخطأ)
    
    const items = getItems(),
        goSportsTeam = true;
        dragonball = 'z';
    
    // جيّد
    const items = getItems();
    const goSportsTeam = true;
    const dragonball = 'z';

     

  • جمّع كل التصريحات بالكلمة المفتاحية 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;

     

  • أسند المتغيرات حيث تحتاج لذلك، لكن ضعها في مكان مناسب.

    لماذا؟ المتغيّرات المصرّح عنها بـ let أو const ذات نطاق كتلي (Block scoped) وليست ذات نطاق دالّي (Function scoped).

    // سيئ، لا حاجة لنداء الدالة
    
    function checkName(hasName) {
      const name = getName();
    
      if (hasName === 'test') {
        return false;
      }
    
      if (name === 'test') {
        this.setName('');
        return false;
      }
    
      return name;
    }
    
    // جيّد
    function checkName(hasName) {
      if (hasName === 'test') {
        return false;
      }
    
      const name = getName();
    
      if (name === 'test') {
        this.setName('');
        return false;
      }
    
      return name;
    }

     

  • تجنب الإسناد المتسلسل للمتغيّرات. استعن no-multi-assign بقاعدة في ESLint.

    لماذا؟ لأن الإسناد المتسلسل ينتج متغيرات عامة.

    // سيئ
    (function example() {
      // يفسّر جافاسكريبت التعليمة أدناه على النحو التالي
      // let a = ( b = ( c = 1 ) );
      // لا يُطبّق التصريح بـ let إلا على المتغيّر a،
      // المتغيّران b وc يصبحان عامين.
      let a = b = c = 1;
    }());
    
    console.log(a); // يتسبّب في خطأ في المرجع ReferenceError
    console.log(b); // 1
    console.log(c); // 1
    
    // جيّد
    (function example() {
      let a = 1;
      let b = a;
      let c = a;
    }());
    
    console.log(a); // يتسبّب في خطأ في المرجع ReferenceError
    console.log(b); // يتسبّب في خطأ في المرجع ReferenceError
    console.log(c); // يتسبّب في خطأ في المرجع ReferenceError
    
    // الأمر نفسه ينطبق على `const`

     

  • تجنب استخدام عمليات الزيادة والإنقاص الأحادية (++, --). استعن بقاعدة no-plusplus.

    لماذا؟ تخضع عمليات الزيادة والإنقاص الأحادية، حسب وثائق ESLint، لقاعدة الإدراج التلقائي للفواصل المنقوطة، ويمكن أن تتسبب في حدوث أخطاء صامتة مع زيادة أو إنقاص قيمة ضمن التطبيق. كما أنه من الأكثر وضوحًا استخدام num += 1 بدلًا من num++ أو num ++ لتغيير قيم المتغيّرات. تجنُّب استخدام عامل الزيادة والإنقاص الأحادي سيجنبك الزيادة (أو الإنقاص) قبل إجراء عملية، وهو ما يمكن أيضًا أن يسبب سلوكًا غير متوقع في برامجك.

    // سيئ
    
    const array = [1, 2, 3];
    let num = 1;
    num++;
    --num;
    
    let sum = 0;
    let truthyCount = 0;
    for (let i = 0; i < array.length; i++) {
      let value = array[i];
      sum += value;
      if (value) {
        truthyCount++;
      }
    }
    
    // جيّد
    
    const array = [1, 2, 3];
    let num = 1;
    num += 1;
    num -= 1;
    
    const sum = array.reduce((a, b) => a + b, 0);
    const truthyCount = array.filter(Boolean).length;

     

  • تجنّب إدراج الأسطر قبل علامة الإسناد = أو بعدها. أحط القيمة بقوسيْن إنْ كان الإسناد يخرق قاعدة max-len (طول السطر). استعن بقاعدة operator-linebreak.

    لماذا لأن الأسطر حول = يمكن أن تعتّم على قيمة الإسناد.

    // سيّئ
    const foo =
      superLongLongLongLongLongLongLongLongFunctionName();
    
    // سيّئ
    const foo
      = 'superLongLongLongLongLongLongLongLongString';
    
    // جيّد
    const foo = (
      superLongLongLongLongLongLongLongLongFunctionName()
    );
    
    // جيّد
    const foo = 'superLongLongLongLongLongLongLongLongString';

     

  • لا تسمح بمتغيّرات غير مستخدمة. استعن بالقاعدة no-unused-vars.

    لماذا؟ المتغيّرات المُصرّح بها غير المستخدمة في أي جزء من الشفرة هي في الغالب خطأ ناتج عن إعادة هيكلة غير مكتملة. تأخذ هذه المتغيّرات مساحة من الشفرة ويمكن أن تتسبّب في خلط لدى من يقرأه.

// سيّئ

var some_unused_var = 42;

// متغيّرات يقتصر استخدامها على تغيير قيمتها لا تعد مستخدمة.
var y = 10;
y = 5;

// قراءة المتغيّر فقط من أجل التعديل عليه لا يعدّ استخداما
var z = 0;
z = z + 1;

// وسائط غير مستخدمة في الدالة
function getX(x, y) {
    return x;
}

// جيّد

function getXPlusY(x, y) {
  return x + y;
}

var x = 1;
var y = a + 2;

alert(getXPlusY(x, y));

// لا تنطبق القاعدة هنا على المتغيّر type الذي على الرغم من أنه غير مستخدم إلا أن له علاقة بالمتغيّر المُفكَّك
// هذه طريقة لاستخراج كائن بإسقاط المفاتيح المحدّدة.
var { type, ...coords } = data;
// يطابق الكائنُ coords الكائنَ data، غير أنه لا توجد فيه الخاصيّة type التي استخرجناها منه.

الرفع إلى أعلى النطاق Hoisting

  • تُرفَع المتغيّرات المُصرَّح عنها بالكلمة المفتاحية var إلى أعلى نطاق الدالة المحتوية الأقرب، إلّا أن هذا الأمر لا ينطبق على عمليّات الإسناد. للمتغيّرات المُعرَّفة بالكلمتيْن المفتاحيتيْن const و let ميزة جديدة تُسمى المناطق الميتة الظرفية (Temporal dead zones)؛ لذا من المهم أن تعرف لماذا لم يعد استخدام الدالة typeof آمنا.

    // نعرف أن التعليمات التالية لن تعمل (على فرض
    // أنه لا يوجد متغيّر باسم notDefined)
    
    function example() {
      console.log(notDefined); // يتسبب في خطأ ReferenceError
    }
    
    // تستطيع التصريح بمتغيّر بعد الإحالة إليه
    // لأن التصريح سيُرفَع إلى أعلى النطاق
    // ملحوظة: إسناد القيمة true إلى المتغيّر لا يُرفَع
    // إلى أعلى النطاق.
    
    function example() {
      console.log(declaredButNotAssigned); // => undefined
      var declaredButNotAssigned = true;
    }
    
    // يرفع مفسّر جافاسكريبت التصريح بالمتغيّر 
    // إلى أعلى النطاق، وهو ما يعني أنه بإمكاننا
    // إعادة كتابة المثال السابق على النحو التالي
    
    function example() {
      let declaredButNotAssigned;
      console.log(declaredButNotAssigned); // => undefined
      declaredButNotAssigned = true;
    }
    
    // استخدام const وlet
    function example() {
      console.log(declaredButNotAssigned); // تتسبّب في خطأ ReferenceError
      console.log(typeof declaredButNotAssigned);  // تتسبّب في خطأ ReferenceError
      const declaredButNotAssigned = true;
    }

     

  • يُرفَع اسم متغيّر الدوال العبارات غير المُسمّاة إلى أعلى النطاق، بخلاف إسناد الدالة.

    function example() {
      console.log(anonymous); // => undefined
    
      anonymous(); // => TypeError anonymous is not a function
    
      var anonymous = function () {
        console.log('anonymous function expression');
      };
    }

     

  • في الدوال العبارات المُسماة يُرفع اسم المتغير، بخلاف اسم الدالة أو متنها.

    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 example() {
      superPower(); // => Flying
    
      function superPower() {
        console.log('Flying');
      }
    }

     

عوامل المقارنة والمساواة

  • استخدم === و !== بدلًا من == و !=.

  • تقيّم التعليمات الشرطية مثل العبارة if العبارات عبر آلية الإجبار (Coercion) عن طريق الدالة المجرَّدة ToBoolean والتي تتبع دائمًا القواعد البسيطة التالية:

  • تُمنَح للكائنات القيمة true

  • تُعيَّن للنوع Undefined القيمة false

  • تُحدَّد للعبارة Null القيمة false

  • البيانات من النوع المنطقي (Booleans) تُعطى لها القيمة المنطقية الموافقة

  • إذا كانت قيمة العدد تساوي 0+ أو 0- أو NaN تحدّد قيمته المنطقية بـ false، وإلّا يأخذ القيمة true

  • إذا كانت سلسلة المحارف (String) خاوية ("") تُعيَّن قيمتها إلى false، وإلاّ تأخذ القيمة true.

    if ([0] && []) {
      // true
      // المصفوفة (حتى وإنْ كانت فارغة) هي كائن، والكائنات تُقيّم بالقيمة true
    }

     

  • استخدم الاختصارات عند التعامل مع قيم منطقية، بالمقابل استخدم المقارنات الصريحة للنصوص والأرقام.

    // سيئ
    if (isValid === true) {
      // ...
    }
    
    // جيّد
    if (isValid) {
      // ...
    }
    
    // سيئ
    if (name) {
      // ...
    }
    
    // جيّد
    if (name !== '') {
      // ...
    }
    
    // سيئ
    if (collection.length) {
      // ...
    }
    
    // جيّد
    if (collection.length > 0) {
      // ...
    }

     

  • استخدم الأقواس المعقوصة ({}) لإنشاء الكتل في البنود case و default التي تحتوي التصريح بمتغيّرات (على سبيل المثال let، const ، function ، وclass). استعن بالقاعدة no-case-declarations.

    لماذا؟ التصريحات مرئية في كامل كتلة switch، ولكن لا يُعاد تعيينها إلّا عندما تُسنَد لها قيمة، وهو ما لا يحدث إلا عند بلوغ بند case، وهو ما يسبب مشاكل عندما يحاول أكثر من بند case تعريف الشيء نفسه.

    // سيئ
    switch (foo) {
      case 1:
        let x = 1;
        break;
      case 2:
        const y = 2;
        break;
      case 3:
        function f() {
          // ...
        }
        break;
      default:
        class C {}
    }
    
    // جيّد
    switch (foo) {
      case 1: {
        let x = 1;
        break;
      }
      case 2: {
        const y = 2;
        break;
      }
      case 3: {
        function f() {
          // ...
        }
        break;
      }
      case 4:
        bar();
        break;
      default: {
        class C {}
      }
    }

     

  • لا ينبغي أن تتداخل عوامل المقارنة الثلاثية Ternaries، وفي الغالب ينبغي أن تكون على سطر واحد. استعن بقاعدة no-nested-ternary.

    // سيئ
    const foo = maybe1 > maybe2
      ? "bar"
      : value1 > value2 ? "baz" : null;
    
    // التقسيم إلى عبارتين تستخدمان مقارنات ثلاثية
    const maybeNull = value1 > value2 ? 'baz' : null;
    
    // أفضل
    const foo = maybe1 > maybe2
      ? 'bar'
      : maybeNull;
    
    // الأفضل
    const foo = maybe1 > maybe2 ? 'bar' : maybeNull;

     

  • تجنب استخدام المقارنات الثلاثية التي لا لزوم لها. استعن بقاعدة no-unneeded-ternary.

    // سيئ
    const foo = a ? a : b;
    const bar = c ? true : false;
    const baz = c ? false : true;
    
    // جيّد
    const foo = a || b;
    const bar = !!c;
    const baz = !c;

     

  • استخدم الأقواس عند مزج العمليات، والاستثناء الوحيد هي العمليات الحسابية القياسية (+، -، *، /) بما أنّ الأسبقية فيها مفهومة جيّدا. ننصح بوضع / و* بين قوسين، لأنّ الأسبقية بينهما قد تكون غير واضحة عند المزج بينهما. استعن بقاعدة no-mixed-operators.

    لماذا؟ هذا يحسّن المقروئية ويوضّح قصد المطوّر.

    // سيئ
    const foo = a && b < 0 || c > 0 || d + 1 === 0;
    
    // سيئ
    const bar = a ** b - 5 % d;
    
    // سيئ
    // one may be confused into thinking (a || b) && c
    if (a || b && c) {
      return d;
    }
    
    // جيّد
    const foo = (a && b < 0) || c > 0 || (d + 1 === 0);
    
    // جيّد
    const bar = (a ** b) - (5 % d);
    
    // جيّد
    if (a || (b && c)) {
      return d;
    }
    
    // جيّد
    const bar = a + b / c * d;

     

الكتل Blocks

  • استخدم الأقواس المعقوصة للكتل متعددة الأسطر.

    // سيئ
    if (test)
      return false;
    
    // جيّد
    if (test) return false;
    
    // جيّد
    if (test) {
      return false;
    }
    
    // سيئ
    function foo() { return false; }
    
    // جيّد
    function bar() {
      return false;
    }

     

  • إذا كنت تستخدم الكتل متعددة الأسطر مع if و else ، ضع else على السطر نفسه الذي يوجد عليه القوس المعقوص الذي يغلق كتلة if.

    // سيئ
    if (test) {
      thing1();
      thing2();
    }
    else {
      thing3();
    }
    
    // جيّد
    if (test) {
      thing1();
      thing2();
    } else {
      thing3();
    }

     

  • إذا كانت كتلة if تُرجع قيمة دائمًا (أي تنتهي بالتعليمة return)، فكتلة else المتعلّقة بها غير ضرورية. تعليمة return في كتلة else if موالية لكتلة if تحتوي على return يمكن تقسيمها إلى عدة كتل if. استعن بقاعدة no-else-return.

    // سيئ
    function foo() {
      if (x) {
        return x;
      } else {
        return y;
      }
    }
    
    // سيئ
    function cats() {
      if (x) {
        return x;
      } else if (y) {
        return y;
      }
    }
    
    // سيئ
    function dogs() {
      if (x) {
        return x;
      } else {
        if (y) {
          return y;
        }
      }
    }
    
    // جيّد
    function foo() {
      if (x) {
        return x;
      }
    
      return y;
    }
    
    // جيّد
    function cats() {
      if (x) {
        return x;
      }
    
      if (y) {
        return y;
      }
    }
    
    //جيّد
    function dogs(x) {
      if (x) {
        if (z) {
          return y;
        }
      } else {
        return z;
      }
    }

     

تعليمات التحكم Control Statements

  • إذا تجاوزت عبارات التحكم (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")
      && doesItLookجيّدWhenItBecomesThatLong()
      && isThisReallyHappening()
    ) {
      thing1();
    }
    
    // جيّد
    if (foo === 123 && bar === 'abc') {
      thing1();
    }

     

  • لا تستخدم عوامل الاختيار بدلًا من تعليمات التحكم

    // سيّئ
    !isRunning && startRunning();
    
    // جيّد
    if (!isRunning) {
      startRunning();
    }

     

التعليقات

  • استخدم /** ... */ للتعليقات متعددة الأسطر.

    // سيئ
    // make() returns a new element
    // based on the passed in tag name
    //
    // @param {String} tag
    // @return {Element} element
    function make(tag) {
    
      // ...
    
      return element;
    }
    
    // جيّد
    /**
     * make() returns a new element
     * based on the passed-in tag name
     */
    function make(tag) {
    
      // ...
    
      return element;
    }

     

  • استخدم // لأجل التعليقات ذات السطر الواحد. ضع التعليقات ذات السطر الواحد على سطر جديد فوق التعليمة البرمجية موضوع التعليق. وضع سطرًا فارغًا قبل التعليق إلا إذا كان على السطر الأول من كتلة.

    // سيئ
    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;
    }
    
    // جيّد أيضا
    function getType() {
      // set the default type to 'no type'
      const type = this.type || 'no type';
    
      return type;
    }

     

  • ابدأ كل التعليقات بمسافة لجعلها أسهل للقراءة. استعت بقاعدة spaced-comment.

    // سيئ
    //is current tab
    const active = true;
    
    // جيّد
    // is current tab
    const active = true;
    
    // سيئ
    /**
     *make() returns a new element
     *based on the passed-in tag name
     */
    function make(tag) {
    
      // ...
    
      return element;
    }
    
    // جيّد
    /**
     * make() returns a new element
     * based on the passed-in tag name
     */
    function make(tag) {
    
      // ...
    
      return element;
    }

     

  • ابدأ التعليقات التي تهدف إلى لفت انتباه المطورين الآخرين إلى أنّ هناك مشكلةً تحتاج إلى المراجعة بالكلمات FIXME أو TODO، أو إذا كنت تقترح حلّا لتنفيذه. تختلف هذه عن التعليقات العادية لأنها قابلة للتنفيذ. الإجراءات قد تكون: FIXME: -- need to figure this out أو TODO: -- need to implement مثلا.

  • استخدم // FIXME: للإشارة إلى مشكلة.

    class Calculator extends Abacus {
      constructor() {
        super();
        // FIXME: shouldn’t use a global here total = 0; 
    	} 
    }

     

  • استخدم // TODO: لاقتراح حل لمشكلة.

class Calculator extends Abacus {
  constructor() {
    super();

    // TODO: total should be configurable by an options param
    this.total = 0;
  }
}

المسافات Whitespace

  • أضف فراغيْن بسيطيْن (زر المسافة مرتيْن) للمسافات البادئة. استعن بقاعدة indent.

    // سيئ
    function foo() {
    ∙∙∙∙let name;
    }
    
    // سيئ
    function bar() {
    let name;
    }
    
    // جيّد
    function baz() {
    ∙∙let name;
    }

     

  • ضع مسافة واحدة قبل القوس المعقوص الأول. استعن بالقاعدة space-before-blocks.

    // سيئ
    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',
    });

     

  • ضع مسافةً قبل القوس الفاتح في تعليمات التحكم (if, while …). لا تضع أي مسافات بين لائحة الوسائط Arguments واسم الدالة عند استدعاء دالة أو التصريح بها. استعن بقاعدة keyword-spacing.

    // سيئ
    if(isJedi) {
      fight ();
    }
    
    // جيّد
    if (isJedi) {
      fight();
    }
    
    // سيئ
    function fight () {
      console.log ('Swooosh!');
    }
    
    // جيّد
    function fight() {
      console.log('Swooosh!');
    }

     

  • ضع مسافات بين العوامل. استعن بقاعدة space-infix-ops.

    // سيئ
    const x=y+5;
    
    // جيّد
    const x = y + 5;

     

  • أنهِ الملفات بمحرف الرجوع إلى السطر. استعن بقاعدة eol-last

    // سيئ
    import { es6 } from './AirbnbStyleGuide';
      // ...
    export default es6;
    // سيئ
    import { es6 } from './AirbnbStyleGuide';
      // ...
    export default es6;↵
    
    // جيّد
    import { es6 } from './AirbnbStyleGuide';
      // ...
    export default es6;↵

     

  • استخدم المسافة البادئة عند استخدام سلسة طويلة من التوابع (أكثر من اثنتين). استخدم نقطة في البداية، لكي تبين أنّ السطر هو استدعاء لتابع، وليس تعليمةً جديدة. استعن بالقاعدتيْن newline-per-chained-call وno-whitespace-before-property.

    // سيئ
    $('#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').classed('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);
    
    // جيّد
    const leds = stage.selectAll('.led').data(data);

     

  • ضع سطرًا فارغًا بعد الكتل وقبل التعليمة الموالية.

    // سيئ
    if (foo) {
      return bar;
    }
    return baz;
    
    // جيّد
    if (foo) {
      return bar;
    }
    
    return baz;
    
    // سيئ
    const obj = {
      foo() {
      },
      bar() {
      },
    };
    return obj;
    
    // جيّد
    const obj = {
      foo() {
      },
    
      bar() {
      },
    };
    
    return obj;
    
    // سيئ
    const arr = [
      function foo() {
      },
      function bar() {
      },
    ];
    return arr;
    
    // جيّد
    const arr = [
      function foo() {
      },
    
      function bar() {
      },
    ];
    
    return arr;

     

  • لا تحش الكتل بأسطر فارغة. استعن بالقاعدة padded-blocks.

    // سيئ
    function bar() {
    
      console.log(foo);
    
    }
    
    // سيئ
    if (baz) {
    
      console.log(qux);
    } else {
      console.log(foo);
    
    }
    
    // سيئ
    class Foo {
    
      constructor(bar) {
        this.bar = bar;
      }
    }
    
    // جيّد
    function bar() {
      console.log(foo);
    }
    
    // جيّد
    if (baz) {
      console.log(qux);
    } else {
      console.log(foo);
    }

     

  • لا تستخدم عدة أسطر فارغة لحشو شفرتك البرمجية. استعن بالقاعدة no-multiple-empty-lines.

    	// سيّئ
    	class Person {
    	  constructor(fullName, email, birthday) {
    	    this.fullName = fullName;
    	
    	
    	    this.email = email;
    	
    	
    	    this.setAge(birthday);
    	  }
    	
    	
    	  setAge(birthday) {
    	    const today = new Date();
    	
    	
    	    const age = this.getAge(today, birthday);
    	
    	
    	    this.age = age;
    	  }
    	
    	
    	  getAge(today, birthday) {
    	    // ..
    	  }
    	}
    	
    	// جيّد
    	class Person {
    	  constructor(fullName, email, birthday) {
    	    this.fullName = fullName;
    	    this.email = email;
    	    this.setAge(birthday);
    	  }
    	
    	  setAge(birthday) {
    	    const today = new Date();
    	    const age = getAge(today, birthday);
    	    this.age = age;
    	  }
    	
    	  getAge(today, birthday) {
    	    // ..
    	  }
    	}
  • لا تضف مسافات داخل الأقواس. استعن بالقاعدة space-in-parens.
// سيئ
function bar( foo ) {
  return foo;
}

// جيّد
function bar(foo) {
  return foo;
}

// سيئ
if ( foo ) {
  console.log(foo);
}

// جيّد
if (foo) {
  console.log(foo);
}
  • لا تضف مسافات داخل الأقواس المعكوفة ([]). استعن بالقاعدة array-bracket-spacing.

    // سيئ
    const foo = [ 1, 2, 3 ];
    console.log(foo[ 0 ]);
    
    // جيّد
    const foo = [1, 2, 3];
    console.log(foo[0]);

     

  • أضف مساحات داخل الأقواس المعقوصة. استعن بالقاعدة object-curly-spacing.

    // سيئ
    const foo = {clark: 'kent'};
    
    // جيّد
    const foo = { clark: 'kent' };

     

  • تجنب التعليمات البرمجية التي يتجاوز طولها 100 محرف (باحتساب المسافات). النصوص الطويلة - حسب قاعدة مذكورة أعلاه - مستثناة من هذه القاعدة، وينبغي ألّا تُفكك. استعن بالقاعدة max-len.

    لماذا؟ لضمان سهولة القراءة والصيانة.

    // سيئ
    const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy;
    
    // سيئ
    $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.'));
    
    // جيّد
    const foo = jsonData
      && jsonData.foo
      && jsonData.foo.bar
      && jsonData.foo.bar.baz
      && jsonData.foo.bar.baz.quux
      && jsonData.foo.bar.baz.quux.xyzzy;
    
    // جيّد
    $.ajax({
      method: 'POST',
      url: 'https://airbnb.com/',
      data: { name: 'John' },
    })
      .done(() => console.log('Congratulations!'))
      .fail(() => console.log('You have failed this city.'));

     

  • افرض مسافات متناسقة بعد القوس المعقوص البادئ لكتلة وبعد القوس المعقوص البادئ لكتلة موالية لها على السطر نفسه. الأمر نفسه ينطبق على الأقواس المعقوصة المكمّلة للكتلتيْن. استعن بقاعدة block-spacing.

    // سيّئ
    function foo() {return true;}
    if (foo) { bar = 0;}
    
    // جيّد
    function foo() { return true; }
    if (foo) { bar = 0; }

     

  • تجنّب المسافات قبل الفواصل وافرض مسافة بعد الفاصلة. استعن بالقاعدة comma-spacing.

    // جيّد
    var foo = 1,bar = 2;
    var arr = [1 , 2];
    
    // سيّئ
    var foo = 1, bar = 2;
    var arr = [1, 2];

     

  • افرض عدم استخدام المسافات داخل الأقواس المعكوفة لخاصيّة محسوبة. استعن بالقاعدة computed-property-spacing.

    // سيّئ
    obj[foo ]
    obj[ 'foo']
    var x = {[ b ]: a}
    obj[foo[ bar ]]
    
    // جيّد
    obj[foo]
    obj['foo']
    var x = { [b]: a }
    obj[foo[bar]]

     

  • تجنّب المسافات بين دالة واستدعائها. استعن بالقاعدة func-call-spacing.

    // سيئ
    func ();
    
    func
    ();
    
    // جيّد
    func();

     

  • افرض المسافات بين المفاتيح والقيم في الخاصيّات المصنّفة النوع في الكائنات. استعن بالقاعدة key-spacing.

    // سيّئ
    var obj = { foo : 42 };
    var obj2 = { foo:42 };
    
    // جيّد
    var obj = { foo: 42 };

     

  • تجنّب المسافات الباقية بعد نهاية الأسطر. استعن بالقاعدة no-trailing-spaces.

  • تجنّب عدة أسطر فارغة، ولاتسمح إلا بسطر واحد جديد في نهاية الملفات. تجنّب كذلك وجود سطر جديد في بداية الملفات. استعن بالقاعدة no-multiple-empty-lines.

    // سيّئ - عدة أسطر فارغة.
    var x = 1;
    
    
    var y = 2;
    
    // سيّئ - سطران جديدان بعد نهاية الملف
    var x = 1;
    var y = 2;
    
    
    // سيّئ - سطر جديد في بداية الملف
    
    var x = 1;
    var y = 2;
    
    // جيّد
    var x = 1;
    var y = 2;

     

الفواصل

  • تجنب الفواصل في البداية. استعن بالقاعدة comma-style.

    // سيئ
    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',
    };

     

  • استخدم فاصلة إضافية. استعن بالقاعدة comma-dangle.

    لماذا؟ هذا يؤدي إلى توضيح الاختلافات بين الشفرات البرمجية في إيداعات Git . علاوة على ذلك، تحذف مصرّفات مثل Babel الفواصل الزائدة في الشفرة الناتجة عن التصريف، ممّا يعني أنه لا داعي للقلق من مشكلة الفاصلة المُجرجَرة (Trailing comma) في المتصفحات القديمة.

// سيئ، فرق بين إيداعين في Git بدون فاصلة إضافية

const hero = {
     firstName: 'Florence',
-    lastName: 'Nightingale'
+    lastName: 'Nightingale',
+    inventorOf: ['coxcomb chart', 'modern nursing']
};

// جيّد، فرق بين إيداعين في Git بوجود فاصلة إضافية

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',
];

// سيئ
function createHero(
  firstName,
  lastName,
  inventorOf
) {
  // does nothing
}

// جيّد
function createHero(
  firstName,
  lastName,
  inventorOf,
) {
  // does nothing
}

// جيّد (انتبه إلى أنه يجب ألا تظهر فاصلة بعد عامل الاستناد Rest element)

function createHero(
  firstName,
  lastName,
  inventorOf,
  ...heroArgs
) {
  // does nothing
}

// سيئ
createHero(
  firstName,
  lastName,
  inventorOf
);

// جيّد
createHero(
  firstName,
  lastName,
  inventorOf,
);

// جيّد (انتبه إلى أنه يجب ألا تظهر فاصلة بعد عامل الاستناد Rest element)

createHero(
  firstName,
  lastName,
  inventorOf,
  ...heroArgs
);

الفاصلة المنقوطة Semicolons

  • استخدم الفاصلة المنقوطة. استعن بالقاعدة semi.

    لماذا؟ عندما يصادف مفسّر جافاسكريبت عودة إلى السطر بدون فاصلة منقوطة، فإنه يستخدم مجموعة من القواعد تسمى الإدراج التلقائي للفاصلة المنقوطة (Automatic Semicolon Insertion) لتحديد ما إذا كان يجب احتساب نهاية السطر على أنها نهاية للتعليمة البرمجية، ويُدرِج، كما يوحي الاسم، فاصلة منقوطة في الشفرة البرمجية قبل نهاية السطر إذا رأى أن التعليمة قد انتهت. الإدراج التلقائي للفاصلة المنقوطة ترافقه سلوكيات شاذة، قد تتسبّب في إساءة فهم الشفرة البرمجية. تصبح هذه القواعد أكثر تعقيدًا مع إضافة ميزات جديدة إلى جافاسكريبت. سيساعد الوضوح في إنهاء التعليمات البرمجية وإعداد أداة جودة الشفرة (مثل ESLint) لتحديد الفواصل المنقوطة المفقودة في تجنب تلك المشاكل.

    // سيئ، تنتج عنه استثناءات (Exceptions)
    const luke = {}
    const leia = {}
    [luke, leia].forEach(jedi => jedi.father = 'vader')
    
    // سيئ، تنتج عنه استثناءات (Exceptions)
    const reaction = "No! That's impossible!"
    (async function meanwhileOnTheFalcon(){
      // handle `leia`, `lando`, `chewie`, `r2`, `c3p0`
      // ...
    }())
    
    // سيّئ، يُرجع `undefined` بدلًا من القيمة الموجودة في السطر الموالي
    // يحدث هذا دائمًا عندما تكون التعليمة return مفصولة بسطر عن القمية المرجَعة
    // وذلك بسبب حدوث الإدراج التلقائي للفاصلة المنقوطة
    
    function foo() {
      return
        'search your feelings, you know it to be foo'
    }
    
    // جيّد
    const luke = {};
    const leia = {};
    [luke, leia].forEach((jedi) => {
      jedi.father = 'vader';
    });
    
    // جيّد
    const reaction = "No! That's impossible!";
    (async function meanwhileOnTheFalcon(){
      // handle `leia`, `lando`, `chewie`, `r2`, `c3p0`
      // ...
    }());
    
    // جيّد
    function foo() {
      return 'search your feelings, you know it to be foo';
    }

     

التحويل بين أنواع البيانات وفرض نوع معيّن Type Casting & Coercion

  • حوّل نوع البيانات في بداية التعليمة.

  • سلاسل المحارف:

    // => this.reviewScore = 9;
    
    // سيئ
    const totalScore = new String(this.reviewScore); //  المتغيّر totalScore كائن "object" وليس سلسلة محارف "string"
    
    // سيئ
    const totalScore = this.reviewScore + ''; 
    //  يستدعي التابع this.reviewScore.valueOf()
    
    // سيئ
    const totalScore = this.reviewScore.toString(); // لا تضمن إرجاع سلسلة محارف
    
    // جيّد
    const totalScore = String(this.reviewScore);

     

  • بالنسبة للأعداد: استخدم Number لأجل التحويل، أما لتحليل النصوص فاستخدم دائمًا التابع parseInt مع أساس radix . استعن بالقاعدتيْن radix وno-new-wrappers.

    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);

     

  • ضع تعليقًا يشرح ما الذي تفعله ولماذا إذا كنت مضطرّا لاستعمال parseInt واحتجت إلى استخدام Bitshift لأسباب تتعلق بالأداء.

    // جيّد
    /**
     * parseInt was the reason my code was slow.
     * Bitshifting the String to coerce it to a
     * Number made it a lot faster.
     */
    const val = inputValue >> 0;

     

  • احذر عند استخدام عمليات الإزاحة bitshift ، فالأعداد مُمثلة بقيم من 64 بتا. ولكنّ عمليات الإزاحة دائما تُرجع أعدادًا ممثلة بقيم من 32 بت (المرجع). يمكن أن تؤدي الإزاحة إلى نتائج غير متوقعة عند استخدام قيم عددية أكبر من 32 بتا. أكبر قيمة للأعداد ذات الإشارة الممثلة على 32 بتا هي 2,147,483,647. (نقاش).

    2147483647 >> 0; // => 2147483647
    2147483648 >> 0; // => -2147483648
    2147483649 >> 0; // => -2147483647

     

  • القيم المنطقية:

    const age = 0;
    
    // سيئ
    const hasAge = new Boolean(age);
    
    // جيّد
    const hasAge = Boolean(age);
    
    // best
    const hasAge = !!age;

     

اصطلاحات التسمية

  • تجنب الأسماء المكونة من حرف واحد. استخدم أسماء معبّرة. استعن بالقاعدة id-length.

    // سيئ
    function q() {
      // ...
    }
    
    // جيّد
    function query() {
      // ...
    }

     

  • استخدم أسلوب camelCase لتسمية الكائنات، والدوال، والنظائر (Instances). استعن بالقاعدة camelCase.

    // سيئ
    const OBJEcttsssss = {};
    const this_is_my_object = {};
    function c() {}
    
    // جيّد
    const thisIsMyObject = {};
    function thisIsMyFunction() {}

     

  • لا تستخدم أسلوب التسمية PascalCase إلا عند تسمية المُنشئات أو الأصناف. استعن بالدالة new-cap.

    // سيئ
    function user(options) {
      this.name = options.name;
    }
    
    const سيئ = new user({
      name: 'nope',
    });
    
    // جيّد
    class User {
      constructor(options) {
        this.name = options.name;
      }
    }
    
    const جيّد = new User({
      name: 'yup',
    });

     

  • لا تستخدم العارضة السفلية في البداية أو النهاية. استعن بالقاعدة camelcno-underscore-danglease.

    لماذا؟ لا يوجد في جافاسكريبت مفهوم الخصوصية عندما يتعلّق الأمر بالخاصيّات أو التوابع. على الرغم من أن الكثيرين ينظرون إلى وضع العارضة السفلية في بداية الاسم على أنه اصطلاح يعني “خاص”، إلّا أن هذه الخاصيّات عامة كلها، وعلى هذا النحو، فهي جزء من الواجهة البرمجية API العمومية. هذا الاصطلاح قد يؤدي بالمطورين للاعتقاد خطأً بأن التغيير لن يؤثّر سلبًا على الشفرة البرمجية، أو أنه ليست هناك حاجة للاختبار. إن بدا لك هذا الشرح طويلًا، فتذكر هذه الجملة: إذا كنت تريد لشيء أن يكون “خاصّا”، فيجب ألا تضعه في مكان ملحوظ.

    // سيئ
    this.__firstName__ = 'Panda';
    this.firstName_ = 'Panda';
    this._firstName = 'Panda';
    
    // جيّد
    this.firstName = 'Panda';
    // جيّد، في البيئات التي يكون WeakMaps متوفّرًا فيها، راجع الرابط التالي
    // see https://kangax.github.io/compat-table/es6/#test-WeakMap
    
    const firstNames = new WeakMap();
    firstNames.set(this, 'Panda');

     

  • لا تحفظ مرجعًا إلى this. واستخدم الدوال السهمية أو التابع 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);
      };
    }

     

  • يجب أن يتطابق اسم الملف القاعدي (Base filename) تمامًا مع اسم التصدير الافتراضي.

    // file 1 contents
    class CheckBox {
      // ...
    }
    export default CheckBox;
    
    // file 2 contents
    export default function fortyTwo() { return 42; }
    
    // file 3 contents
    export default function insideDirectory() {}
    
    // in some other file
    // سيئ
    import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename
    import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export
    import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export
    
    // سيئ
    import CheckBox from './check_box'; // PascalCase import/export, snake_case filename
    import forty_two from './forty_two'; // snake_case import/filename, camelCase export
    import inside_directory from './inside_directory'; // snake_case import, camelCase export
    import index from './inside_directory/index'; // requiring the index file explicitly
    import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly
    
    // جيّد
    import CheckBox from './CheckBox'; // PascalCase export/import/filename
    import fortyTwo from './fortyTwo'; // camelCase export/import/filename
    import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index"
    // ^ supports both insideDirectory.js and insideDirectory/index.js

     

  • استخدم أسلوب التسمية camelCase عندما التصدير الافتراضي لدالة. يجب أن يكون اسم الملف الخاص بك مطابقًا لاسم دالتك.

    function makeStyleGuide() {
      // ...
    }
    
    export default makeStyleGuide;

     

  • استخدم أسلوب التسمية PascalCase عندما تصدّر منشئًا أو صنفًا أو صنفًا أو مكتبة دوال أو كائن مجرّدا.

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

     

  • يجب أن تكون كل حروف المختصرات والكلمات المنحوتة إما مكتوبة بأحرف كبيرة وإما بأحرف صغيرة. لماذا؟ تهدف التسميات لتسهيل قراءة الشفرة على الإنسان، وليس لاسترضاء خوارزميات الكمبيوتر.

    // سيئ
    import SmsContainer from './containers/SmsContainer';
    
    // سيئ
    const HttpRequests = [
      // ...
    ];
    
    // جيّد
    import SMSContainer from './containers/SMSContainer';
    
    // جيّد
    const HTTPRequests = [
      // ...
    ];
    
    // جيّد أيضا
    const httpRequests = [
      // ...
    ];
    
    // أفضل
    import TextMessageContainer from './containers/TextMessageContainer';
    
    // أفضل
    const requests = [
      // ...
    ];

     

  • اختياريًّا، يمكنك كتابة ثابت بحروف كبيرة إذا تحقّقت الشروط التالية: 1) التصدير، 2) التصريح بالكلمة const ، أي أنه لا يمكن إعادة إسناده، 3) يمكن للمبرمج أن يثق أنه لم يتغيّر لا هو ولا الخاصيّات المتفرّعة عنه.

    لماذا؟ هذه أداة إضافية للحالات التي يكون المبرمج فيها غير متأكد من أن المتغيّر ستتغيّر قيمته. تخبر المتغيّرات المكتوبة بحروف كبيرة (مثل UPPERCASE_VARIABLES) المبرمج أن بإمكانه الوثوق من أن تلك الثوابت (وخاصيّاتها) لن تتغيّر قيمتها.

    • هل ينطبق الأمر على كل المتغيّرات المعرّفة بالكلمة const؟ لا حاجة لذلك، وبالتالي يجب ألا تُستخدَم الحروف الكبيرة في تسمية الثوابت داخل ملف، ولكنها يجب أن تُستخدَم للثوابت المُصدَّرة.
    • ماذا عن الكائنات المُصدَّرة؟ استخدم الحروف الكبيرة في المستوى الأعلى من التصدير (مثلًا EXPORTED_OBJECT.key) وتأكّد من أن الخاصيّات المتفرّعة كلها لا تتغيّر.
    // سيّئ
    const PRIVATE_VARIABLE = 'should not be unnecessarily uppercased within a file';
    
    // سيّئ
    export const THING_TO_BE_CHANGED = 'should obviously not be uppercased';
    
    // سيّئ
    export let REASSIGNABLE_VARIABLE = 'do not use let with uppercase variables';
    
    // ---
    
    // مرخَّص به، لكنه لا يضيف قيمة دلالية
    export const apiKey = 'SOMEKEY';
    
    // أفضل في أغلب الحالات
    export const API_KEY = 'SOMEKEY';
    
    // ---
    
    // سيّء، حروف كبيرة غير ضرورية في الاسم مع انعدام القيمة الدلالية
    
    export const MAPPING = {
      KEY: 'value'
    };
    
    // جيّد
    export const MAPPING = {
      key: 'value'
    };

     

المسترجعات (Accessors)

  • ليس مفروضًا وجود توابع الاسترجاع للوصول إلى الخاصيّات.

  • لا تستخدم توابع الاسترجاع أو التعديل التي توفّرها جافاسكريبت لأنّ لها آثارًا جانبيةً غير متوقعة، ويصعب اختبارها وصيانتها والتعامل معها. إنْ أردت استخدام المسترجعات (أو المعدّلات) فمن الجيّد استخدام التوابع ()getVal و('setVal('hello لهذا الغرض.

    // سيئ
    class Dragon {
      get age() {
        // ...
      }
    
      set age(value) {
        // ...
      }
    }
    
    // جيّد
    class Dragon {
      getAge() {
        // ...
      }
    
      setAge(value) {
        // ...
      }
    }

     

  • استخدم()isVal أو ()hasVal للخاصيّات والتوابع المنطقية.

    // سيئ
    if (!dragon.age()) {
      return false;
    }
    
    // جيّد
    if (!dragon.hasAge()) {
      return false;
    }

     

  • لا ضير في إنشاء دوال ()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];
      }
    }

     

الأحداث

  • عند ربط حمولات البيانات بالأحداث (سواء كانت أحداث DOM أوأحداث مكتبات خاصّة مثل Backbone)، مرّر كائنًا مصنّف النوع (معروف أيضًا باسم “hash”) بدلًا من قيمة خام. سيسمح ذلك لاحقًا بإضافة المزيد من البيانات إلى حمولة الحدث دون الحاجة إلى إيجاد وتحديث كل معالجات الحدث. على سبيل المثال، بدلًا من:

    // سيئ
    $(this).trigger('listingUpdated', listing.id);
    
    // ...
    
    $(this).on('listingUpdated', (e, listingID) => {
      // do something with listingID
    });

     

من الأفضل استخدام:

// جيّد
$(this).trigger('listingUpdated', { listingID: listing.id });

// ...

$(this).on('listingUpdated', (e, data) => {
  // do something with data.listingID
});

jQuery

  • ضع السابقة $ قبل متغيرات jQuery .

    // سيئ
    const sidebar = $('.sidebar');
    
    // جيّد
    const $sidebar = $('.sidebar');
    
    // جيّد
    const $sidebarBtn = $('.sidebar-btn');

     

  • أضف عمليات البحث المؤقت في jQuery إلى التخبئة (Cache).

    // سيئ
    function setSidebar() {
      $('.sidebar').hide();
    
      // ...
    
      $('.sidebar').css({
        'background-color': 'pink',
      });
    }
    
    // جيّد
    function setSidebar() {
      const $sidebar = $('.sidebar');
      $sidebar.hide();
    
      // ...
    
      $sidebar.css({
        'background-color': 'pink',
      });
    }

     

  • بالنسبة لاستعلامات DOM استخدم ‎$('.sidebar ul')‎ أو parent > child $('.sidebar > ul')‎. راجع jsperf.

  • استخدم التابع find في الاستعلام عن الكائنات في نطاق jQuery.

    // سيئ
    $('ul', '.sidebar').hide();
    
    // سيئ
    $('.sidebar').find('ul').hide();
    
    // جيّد
    $('.sidebar ul').hide();
    
    // جيّد
    $('.sidebar > ul').hide();
    
    // جيّد
    $sidebar.find('ul').hide();

     

المكتبة القياسية

تحوي المكتبة القياسية أدوات مساعدة لم تعد تُستخدم ولكن يُيقى عليها للتوافق مع المتصفحات القديمة.

  • استخدم التابع Number.isNaN بدلًا من التابع العام isNaN. استعن بالقاعدة no-restricted-globals.

    لماذا؟ يحوّل التابع isNaN القيم غير العددية إلى أعداد، ويُرجع القيمة true لأي شيء يتحوّل إلى NaN. إذا كان السلوك هو ما ترغب فيه، فكن صريحًا في ذلك.

    // سيئ
    isNaN('1.2'); // false
    isNaN('1.2.3'); // true
    
    // جيّد
    Number.isNaN('1.2.3'); // false
    Number.isNaN(Number('1.2.3')); // true

     

  • استخدم التابع Number.isFinite بدلًا من التابع العام isFinite. استعن بالقاعدة no-restricted-globals.

    لماذا؟ isFinite تحوّل القيم غير العددية إلى أعداد، وتُرجع القيمة true لأي شيء يتحوّل إلى عدد منته. إذا كان السلوك هو ما ترغب فيه، فكن صريحًا في ذلك.

    // سيئ
    isFinite('2e3'); // true
    
    // جيّد
    Number.isFinite('2e3'); // false
    Number.isFinite(parseInt('2e3', 10)); // true

     

الاختبار

  • يجب أن تكتب اختبارات، وليس أساسيًّا الإطار الذي تستخدمه لذلك. المهم أن تكتبها.
  • احرص على كتابة العديد من الدوال البسيطة والصغيرة، وقلّل من استخدام البيانات المتحوّلة .
  • كن حذرًا عند استخدام أصناف stubs و mocks لأنها يمكن أن تجعل اختباراتك أكثر هشاشة.
  • نستخدم mocha وjest في Airbnb. يُستخدَم tape كذلك من حين لآخر في وحدات صغيرة ومعزولة.
  • محاولة اختبار 100٪ من الشفرة هو هدف جيد، حتى لو لم يكن دائمًا عمليّا.
  • كلما أصلحت خللًا، قم بكتابة اختبار ارتداد (Regression test). فمن المؤكد أنّه بدونه ستعود الثغرات مجدّدا.

ترجمة - وبتصرّف - للمقال Airbnb JavaScript Style Guide



1 شخص أعجب بهذا


تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن