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


محمد بغات

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

الأنواع

الأنواع الأساسية

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

  • String
  • number
  • boolean
  • null
  • undefined
  • symbol
const foo = 1;
let bar = foo;

bar = 9;

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

الرموز Symbols لا يمكن أن تُدرج (polyfilled) بدقة، لذلك لا ينبغي استخدامها عند استهداف المتصفحات/البيئات التي لا تدعمها تلقائيًّا.

الأنواع المركبة (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.
لماذا؟ لأنّ هذا سيضمن لك ألّا تعيد تعيين مراجعك، والذي يمكن أن يؤدي إلى أخطاء، ويُصعّب فهم الكود البرمجي.

// bad
var a = 1;
var b = 2;

// good
const a = 1;
const b = 2;

إن كنت مضطرًّا لإعادة تعيين المراجع، استخدم let بدلاً من var.
لماذا؟ لأن مدى let محدود في الكتلة البرمجية (block-scoped) وليس محدودًا داخل الدالة (function-scoped) كما هو الحال مع var.

// bad
var count = 1;
if (true) {
  count += 1;
}

// good, use the let.
let count = 1;
if (true) {
  count += 1;
}

تذكر أن مدى كل من let و constمحدود في الكتلة (block-scoped).

// const and let only exist in the blocks they are defined in.
{
  let a = 1;
  const b = 1;
}
console.log(a); // ReferenceError
console.log(b); // ReferenceError

الكائنات Objects

استخدم الأسلوب الحرفي لإنشاء الكائن.

// bad
const item = new Object();

// good
const item = {};

استخدم أسماءً محسوبةً للخصائص computed property names عند إنشاء كائنات ذات أسماء خصائص ديناميكية.
لماذا؟ لأن هذا سيسمح لك بتعريف جميع خصائص الكائن في مكان واحد.

function getKey(k) {
  return `a key named ${k}`;
}

// bad
const obj = {
  id: 5,
  name: 'San Francisco',
};
obj[getKey('enabled')] = true;

// good
const obj = {
  id: 5,
  name: 'San Francisco',
  [getKey('enabled')]: true,
};

استخدم أسلوب التعريف المختصر لوظائف الكائن.

// bad
const atom = {
  value: 1,

  addValue: function (value) {
    return atom.value + value;
  },
};

// good
const atom = {
  value: 1,

  addValue(value) {
    return atom.value + value;
  },
};

استخدم التعريف المختصر لقيمة الخاصية.

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

const lukeSkywalker = 'Luke Skywalker';

// bad
const obj = {
  lukeSkywalker: lukeSkywalker,
};

// good
const obj = {
  lukeSkywalker,
};

اجمع الخصائص المُختصرة في بداية التصريح بالكائن object declaration.
لماذا؟ لأنه من السهل هكذا معرفة أي الخصائص تستخدم الاختصار.

const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';

// bad
const obj = {
  episodeOne: 1,
  twoJediWalkIntoACantina: 2,
  lukeSkywalker,
  episodeThree: 3,
  mayTheFourth: 4,
  anakinSkywalker,
};

// good
const obj = {
  lukeSkywalker,
  anakinSkywalker,
  episodeOne: 1,
  twoJediWalkIntoACantina: 2,
  episodeThree: 3,
  mayTheFourth: 4,
};

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

// bad
const bad = {
  'foo': 3,
  'bar': 4,
  'data-blah': 5,
};

// good
const good = {
  foo: 3,
  bar: 4,
  'data-blah': 5,
};

لا تستدعي وظائف Object.prototype مباشرة، مثل hasOwnProperty، propertyIsEnumerable، و isPrototypeOf.
لماذا؟ تلك الوظائف قد تُظلّل shadowed من قبل خصائص الكائن المَعني – مثلًا {hasOwnProperty: false} –أو قد يكون الكائن معدومًا (Object.create(null)).

// bad
console.log(object.hasOwnProperty(key));

// good
console.log(Object.prototype.hasOwnProperty.call(object, key));

// best
const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope.
/* or */
import has from 'has'; // https://www.npmjs.com/package/has
// ...
console.log(has.call(object, key));

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

// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
delete copy.a; // so does this

// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }

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

استخدم التعبير الحرفي لإنشاء الصف.

// bad
const items = new Array();

// good
const items = [];

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

const someStack = [];

// bad
someStack[someStack.length] = 'abracadabra';

// good
someStack.push('abracadabra');

استخدم تناقل الصفوف (array spreads) لنسخ الصفوف.

// bad
const len = items.length;
const itemsCopy = [];
let i;

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

// good
const itemsCopy = [...items];

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

const foo = document.querySelectorAll('.foo');

// good
const nodes = Array.from(foo);

// best
const nodes = [...foo];

استخدم Array.from بدلًا من التناقل لأجل تطبيق إجراء على المكرّرات mapping over iterables، وذلك لتجنب خلق صف مؤقت.

// bad
const baz = [...foo].map(bar);

// good
const baz = Array.from(foo, bar);

استخدم العبارةreturn في استدعاءات وظائف الصف array method callbacks. لا ضير في حذفreturn إن كان متن الدالة function body يتكون من عبارة واحدة من دون آثار جانبية.

// good
[1, 2, 3].map((x) => {
  const y = x + 1;
  return x * y;
});

// good
[1, 2, 3].map(x => x + 1);

// bad - no returned value means `memo` becomes undefined after the first iteration
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
  const flatten = memo.concat(item);
  memo[index] = flatten;
});

// good
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
  const flatten = memo.concat(item);
  memo[index] = flatten;
  return flatten;
});

// bad
inbox.filter((msg) => {
  const { subject, author } = msg;
  if (subject === 'Mockingbird') {
    return author === 'Harper Lee';
  } else {
    return false;
  }
});

// good
inbox.filter((msg) => {
  const { subject, author } = msg;
  if (subject === 'Mockingbird') {
    return author === 'Harper Lee';
  }

  return false;
});

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

// bad
const arr = [
  [0, 1], [2, 3], [4, 5],
];

const objectInArray = [{
  id: 1,
}, {
  id: 2,
}];

const numberInArray = [
  1, 2,
];

// good
const arr = [[0, 1], [2, 3], [4, 5]];

const objectInArray = [
  {
    id: 1,
  },
  {
    id: 2,
  },
];

const numberInArray = [
  1,
  2,
];

التفكيك Destructuring

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

// bad
function getFullName(user) {
  const firstName = user.firstName;
  const lastName = user.lastName;

  return `${firstName} ${lastName}`;
}

// good
function getFullName(user) {
  const { firstName, lastName } = user;
  return `${firstName} ${lastName}`;
}

// best
function getFullName({ firstName, lastName }) {
  return `${firstName} ${lastName}`;
}

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

const arr = [1, 2, 3, 4];

// bad
const first = arr[0];
const second = arr[1];

// good
const [first, second] = arr;

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

// bad
function processInput(input) {
  // then a miracle occurs
  return [left, right, top, bottom];
}

// the caller needs to think about the order of return data
const [left, __, top] = processInput(input);

// good
function processInput(input) {
  // then a miracle occurs
  return { left, right, top, bottom };
}

// the caller selects only the data they need
const { left, top } = processInput(input);

النصوص Strings

استخدم علامات التنصيص المفردة ’ لأجل النصوص.

// bad
const name = "Capt. Janeway";

// bad - template literals should contain interpolation or newlines
const name = `Capt. Janeway`;

// good
const name = 'Capt. Janeway';

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

// bad
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.';

// bad
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.';

// good
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.';

عند بناء النصوص برمجيا، حاول استخدام قوالب النصوص بدلًا من الضمّ concatenation.

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

// bad
function sayHi(name) {
  return 'How are you, ' + name + '?';
}

// bad
function sayHi(name) {
  return ['How are you, ', name, '?'].join();
}

// bad
function sayHi(name) {
  return `How are you, ${ name }?`;
}

// good
function sayHi(name) {
  return `How are you, ${name}?`;
}

لا تستخدم أبدًا ()eval على النصوص، لأنها تفتح الكثير من نقاط الضعف.
تجنب تحريف الأحرف (escape characters) في النصوص قدر الإمكان.
لماذا؟ الشرطة المائلة Backslashes تضر بالمقروئية، وبالتالي يجب ألًا تُستخدم إلا عند الضرورة.

// bad
const foo = '\'this\' \i\s \"quoted\"';

// good
const foo = '\'this\' is "quoted"';
const foo = `my name is '${name}'`;

الدوال

استخدم عبارات الدالة المُسماة named function expression بدلًا من تصريحات الدوال function declarations
لماذا؟ تصريحات الدوال تُرفَّع hoisted إلى الأعلى، ما يعني أنه سيكون من السهل الإحالة إلى الدالة قبل أن يتم تعريفها في الملف. وهذا يضر المقروئية والصيانة. إذا وجدت أن تعريف دالة ما كبير أو معقد بشكل مربك، فربما حان الوقت لوضعه في وحدة خاصة به! لا تنسى أن تُسمي التعبير صراحة، بغض النظر عما إذا كان الاسم مستنتجًا من المتغير الحاوي inferred from the containing variable (كما هو الحال غالبًا في المتصفحات الحديثة أو عند استخدام متراجمات Babel). هذا سيلغي أي افتراضات حول مكدّس الخطأ Error’s call stack

// bad
function foo() {
  // ...
}

// bad
const foo = function () {
  // ...
};

// good
// lexical name distinguished from the variable-referenced invocation(s)
const short = function longUniqueMoreDescriptiveLexicalFoo() {
  // ...
};

ضع الدوال المستدعاة فورًا بين قوسين.
لماذا؟ الدوال المستدعاة فورًا هي عبارةٌ واحدة – لذلك وضعها هي وعبارة استدعائها بين قوسين يجعل الكود واضحًا. وإن كان من المستبعد جدّا أن تحتاج الدوال المستدعاة فورًا IIFE)).

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

لا تصرّح أبدًا بالدوال في كتلة غير خاصة بالدوال non-function block (مثل if, while …). قم بإحالة الدالة إلى متغير بدلًا من ذلك. المتصفحات ستسمح لك بفعل ذلك، ولكنّها ستفسرها بطرق مختلفة، وهو أمر لا يحبه المبرمجون.
ملاحظة: ECMA-262 يٌعرّف الكتلة block كقائمة من التعليمات. وتعريف الدالة ليس تعليمةً.

// bad
if (currentUser) {
  function test() {
    console.log('Nope.');
  }
}

// good
let test;
if (currentUser) {
  test = () => {
    console.log('Yup.');
  };
}

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

// bad
function foo(name, options, arguments) {
  // ...
}

// good
function foo(name, options, args) {
  // ...
}

لا تستخدم أبدًا arguments، واستخدم الأسلوب الساكن rest syntax بدلًا من ذلك.
لماذا؟ واضحة في تحديد المعاملات arguments التي تريد سحبها. بالإضافة إلى ذلك، المعاملات الساكنة هي في الأصل صف، وليست شبيهة بالصفوف مثل arguments.

// bad
function concatenateAll() {
  const args = Array.prototype.slice.call(arguments);
  return args.join('');
}

// good
function concatenateAll(...args) {
  return args.join('');
}

استخدم أسلوب المعاملات الافتراضية بدلًا من المعاملات المتحوّلة mutating function arguments.

// really bad
function handleThings(opts) {
  // No! We shouldn’t mutate function arguments.
  // Double bad: if opts is falsy it'll be set to an object which may
  // be what you want but it can introduce subtle bugs.
  opts = opts || {};
  // ...
}

// still bad
function handleThings(opts) {
  if (opts === void 0) {
    opts = {};
  }
  // ...
}

// good
function handleThings(opts = {}) {
  // ...
}

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

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

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

// bad
function handleThings(opts = {}, name) {
  // ...
}

// good
function handleThings(name, opts = {}) {
  // ...
}

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

// bad
var add = new Function('a', 'b', 'return a + b');

// still bad
var subtract = Function('a', 'b', 'return a - b');

ضع مسافة في توقيع الدالة.
لماذا؟ الاتساق أمر جيد، كما لن تكون مضطرًا لإضافة أو إزالة مسافة عند إضافة أو إزالة اسم.

// bad
const f = function(){};
const g = function (){};
const h = function() {};

// good
const x = function () {};
const y = function a() {};

لا تحوًل mutate المعاملات أبدًا.
لماذا؟ يمكن للتعامل مع الكائنات التي مُرُرت كمعاملات parameters أن يتسبب في آثار جانبية غير مرغوب فيها في المستدعي الأصلي.

// bad
function f1(obj) {
  obj.key = 1;
}

// good
function f2(obj) {
  const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
}

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

// bad
function f1(a) {
  a = 1;
  // ...
}

function f2(a) {
  if (!a) { a = 1; }
  // ...
}

// good
function f3(a) {
  const b = a || 1;
  // ...
}

function f4(a = 1) {
  // ...
}

من الأفضل استخدام العملية التناقلية لاستدعاء الدوال المرنةvariadic functions .
لماذا؟ لأنها أوضح، فلست مضطرًّا لتجهيز السياق، كما لا يمكنك أن تجمع بسهولة new مع apply.

// bad
const x = [1, 2, 3, 4, 5];
console.log.apply(console, x);

// good
const x = [1, 2, 3, 4, 5];
console.log(...x);

// bad
new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));

// good
new Date(...[2016, 8, 5]);

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

// bad
function foo(bar,
             baz,
             quux) {
  // ...
}

// good
function foo(
  bar,
  baz,
  quux,
) {
  // ...
}

// bad
console.log(foo,
  bar,
  baz);

// good
console.log(
  foo,
  bar,
  baz,
);

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

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

// bad
[1, 2, 3].map(function (x) {
  const y = x + 1;
  return x * y;
});

// good
[1, 2, 3].map((x) => {
  const y = x + 1;
  return x * y;
});

إن كان متن الدالة function body يتكون من تعليمة واحدة تُرجِع تعبيرًا دون آثار جانبية، فاحذف اللّامّتين واستخدم return الضمنية. خلافًا لذلك، أبق على الأقواس واستخدم العبارة return.
لماذا؟ تٌسهل قراءة الكود عند استخدام دوال متعددة معًا.

// bad
[1, 2, 3].map(number => {
  const nextNumber = number + 1;
  `A string containing the ${nextNumber}.`;
});

// good
[1, 2, 3].map(number => `A string containing the ${number}.`);

// good
[1, 2, 3].map((number) => {
  const nextNumber = number + 1;
  return `A string containing the ${nextNumber}.`;
});

// good
[1, 2, 3].map((number, index) => ({
  [index]: number,
}));

// No implicit return with side effects
function foo(callback) {
  const val = callback();
  if (val === true) {
    // Do something if callback returns true
  }
}

let bool = false;

// bad
foo(() => bool = true);

// good
foo(() => {
  bool = true;
});

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

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

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

إن كان للدالة معامل واحد فقط ولا تستخدم اللامّتين، فقم بحذف الأقواس. خلافًا ذلك، دائمًا ضع أقواسًا حول المعاملات من أجل مزيد من الوضوح والاتساق. ملاحظة: من المقبول أيضًا أن تستخدم الأقواس دائمًا، وفي هذه الحالة استخدم الخيارalways"".
لماذا؟ لأجل وضوح أكبر.

// bad
[1, 2, 3].map((x) => x * x);

// good
[1, 2, 3].map(x => x * x);

// good
[1, 2, 3].map(number => (
  `A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!`
));

// bad
[1, 2, 3].map(x => {
  const y = x + 1;
  return x * y;
});

// good
[1, 2, 3].map((x) => {
  const y = x + 1;
  return x * y;
});

تجنب الخلط بين عبارات الدوال السهمية(=>) وبين عوامل المقارنة (<= , >=).

// bad
const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize;

// bad
const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize;

// good
const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize);

// good
const itemHeight = (item) => {
  const { height, largeSize, smallSize } = item;
  return height > 256 ? largeSize : smallSize;
};

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

استخدم دائمًا class. وتجنب التعامل مع prototype مباشرةً.
لماذا؟ العبارة class أكثر إيجازا ووضوحًا.

// bad
function Queue(contents = []) {
  this.queue = [...contents];
}
Queue.prototype.pop = function () {
  const value = this.queue[0];
  this.queue.splice(0, 1);
  return value;
};

// good
class Queue {
  constructor(contents = []) {
    this.queue = [...contents];
  }
  pop() {
    const value = this.queue[0];
    this.queue.splice(0, 1);
    return value;
  }
}

استخدم extends لأجل الوراثة.
لماذا؟ لأنها وسيلة مدمجة لوراثة نموذج الوظائف extends دون التسبب بمشاكل مع instanceof.

// bad
const inherits = require('inherits');
function PeekableQueue(contents) {
  Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function () {
  return this.queue[0];
};

// good
class PeekableQueue extends Queue {
  peek() {
    return this.queue[0];
  }
}

يمكن للوظائف أن تُرجع this للمساعدة بخصوص تسلسل الوظائف method chaining.

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

// good
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()}`;
  }
}

الأصناف لديها منشئ افتراضي إذا لم يتم تحديد واحد سلفًا. لذلك فالمنشئات الفارغة أو التي تنوب عن الصنف الأب وحسب ليست ضرورية.

// bad
class Jedi {
  constructor() {}

  getName() {
    return this.name;
  }
}

// bad
class Rey extends Jedi {
  constructor(...args) {
    super(...args);
  }
}

// good
class Rey extends Jedi {
  constructor(...args) {
    super(...args);
    this.name = 'Rey';
  }
}

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

// bad
class Foo {
  bar() { return 1; }
  bar() { return 2; }
}

// good
class Foo {
  bar() { return 1; }
}

// good
class Foo {
  bar() { return 2; }
}

الوحدات Modules

دائمًا استخدم (import/export) لأجل الوحدات بدلًا من الطرق الأخرى غير القياسية. يمكنك الترجمة transpile دائمًا نحو نظام الوحدات المفضل لديك.
لماذا؟ الوحدات هي المستقبل، دعونا نُدشن المستقبل منذ الآن.

// bad
const AirbnbStyleGuide = require('./AirbnbStyleGuide');
module.exports = AirbnbStyleGuide.es6;

// ok
import AirbnbStyleGuide from './AirbnbStyleGuide';
export default AirbnbStyleGuide.es6;

// best
import { es6 } from './AirbnbStyleGuide';
export default es6;

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

// bad
import * as AirbnbStyleGuide from './AirbnbStyleGuide';

// good
import AirbnbStyleGuide from './AirbnbStyleGuide';

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

// bad
// filename es6.js
export { es6 as default } from './AirbnbStyleGuide';

// good
// filename es6.js
import { es6 } from './AirbnbStyleGuide';
export default es6;

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

// bad
import foo from 'foo';
// … some other imports … //
import { named1, named2 } from 'foo';

// good
import foo, { named1, named2 } from 'foo';

// good
import foo, {
  named1,
  named2,
} from 'foo';

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

// bad
let foo = 3;
export { foo };

// good
const foo = 3;
export { foo };

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

// bad
export function foo() {}

// good
export default function foo() {}

ضع كل العبارات import فوق عبارات الاستيراد الأخرى.
لماذا؟ لأن import يتم ترفيعهاhoisted ، فوضعها كلها في الجزء العلوي يمنع أي سلوك غير متوقع.

// bad
import foo from 'foo';
foo.init();

import bar from 'bar';

// good
import foo from 'foo';
import bar from 'bar';

foo.init();

الاستيراد المتعدد يجب أن توضع قبله مسافة بادئة مثله مثل الصفوف متعددة الأسطر والتصريح الحرفي للكائنات object literals.
لماذا؟ اللامّتان تتبعان نفس قواعد المسافة البادئة كما هو الحال في كل كتل اللامّات في هذا الدليل، وكذلك مثل الفواصل الزائدة trailing commas.

// bad
import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';

// good
import {
  longNameA,
  longNameB,
  longNameC,
  longNameD,
  longNameE,
} from 'path';

تجنب تعابير مُحمّلات الحُزم Webpack loader في عبارات import.
لماذا؟ بما أن مُحمّلات الحزم تستخدم معدات الوحدات module bundler. فمن الأفضل استخدام عبارة المُحمّل في webpack.config.js.

// bad
import fooSass from 'css!sass!foo.scss';
import barCss from 'style!css!bar.css';

// good
import fooSass from 'foo.scss';
import barCss from 'bar.css';

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

لا تستخدم المكررات. من الأفضل استخدام دوال الدرجات العليا higher-order functions لجافا سكريبت بدلًا من الحلقات مثلfor-in أو for-of.
لماذا؟ هذا يتماشى مع قاعدة التحول immutable rule الخاصة بنا. التعامل مع الدوال التي تُرجع قيمًا أسهل للفهم مقارنة بالدوال ذات الآثار الجانبية.
استخدم ()map / every()  / filter()  / find() / findIndex()  / reduce() / some / للتمرير iterate على الصفوف، و ()Object.keys / Object.values() / Object.entries لإنتاج صف حتى تتمكن من التمرير على عناصره.

const numbers = [1, 2, 3, 4, 5];

// bad
let sum = 0;
for (let num of numbers) {
  sum += num;
}
sum === 15;

// good
let sum = 0;
numbers.forEach((num) => {
  sum += num;
});
sum === 15;

// best (use the functional force)
const sum = numbers.reduce((total, num) => total + num, 0);
sum === 15;

// bad
const increasedByOne = [];
for (let i = 0; i < numbers.length; i++) {
  increasedByOne.push(numbers[i] + 1);
}

// good
const increasedByOne = [];
numbers.forEach((num) => {
  increasedByOne.push(num + 1);
});

// best (keeping it functional)
const increasedByOne = numbers.map(num => num + 1);

لا تستخدم المولدات في الوقت الراهن.
لماذا؟ لأنها لا تُترجم transpile جيدًا في ES5.
إذا كان عليك استخدام المُولدات، أو إذا أردت تجاهل نصيحتنا، تأكد من أن تواقيع دوالها متباعدة بشكل صحيح.
لماذا؟ function و * هما كلمتان مفتاحيتان من نفس المفهوم conceptual keyword – * ليست تعديلًا لـ function و function* مختلفة عن function.

// bad
function * foo() {
  // ...
}

// bad
const bar = function * () {
  // ...
};

// bad
const baz = function *() {
  // ...
};

// bad
const quux = function*() {
  // ...
};

// bad
function*foo() {
  // ...
}

// bad
function *foo() {
  // ...
}

// very bad
function
*
foo() {
  // ...
}

// very bad
const wat = function
*
() {
  // ...
};

// good
function* foo() {
  // ...
}

// good
const foo = function* () {
  // ...
};

الخصائص

استخدم أسلوب النقطة dot notation عند الدخول إلى الخصائص.

const luke = {
  jedi: true,
  age: 28,
};

// bad
const isJedi = luke['jedi'];

// good
const isJedi = luke.jedi;

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

const luke = {
  jedi: true,
  age: 28,
};

function getProp(prop) {
  return luke[prop];
}

const isJedi = getProp('jedi');

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

// bad
const binary = Math.pow(2, 10);

// good
const binary = 2 ** 10;

المتغيرات

استخدم دومًا const أو let لتعريف المتغيرات. عدم القيام بذلك سيؤدي إلى إنشاء متغيرات كلية global variables. ونحن نريد تجنب تلويث مجال الأسماء ((namespace العام.

// bad
superPower = new SuperPower();

// good
const superPower = new SuperPower();

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

// bad
const items = getItems(),
    goSportsTeam = true,
    dragonball = 'z';

// bad
// (compare to above, and try to spot the mistake)
const items = getItems(),
    goSportsTeam = true;
    dragonball = 'z';

// good
const items = getItems();
const goSportsTeam = true;
const dragonball = 'z';

قم بتجميع كل التعابير const ثم بعدها جمّع التعابير let.
لماذا؟ سيكون هذا مفيدًا لاحقًا عندما تحتاج إلى تعيين متغير اعتمادًا على متغير معيّن سابقًا.

// bad
let i, len, dragonball,
    items = getItems(),
    goSportsTeam = true;

// bad
let i;
const items = getItems();
let dragonball;
const goSportsTeam = true;
let len;

// good
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;

عيّن المتغيرات حيث ما أردت، ولكن ضعها في مكان مناسب.
لماذا؟ let و const كتلية المدى block scoped وليست دوالية المدى function scoped.

// bad - unnecessary function call
function checkName(hasName) {
  const name = getName();

  if (hasName === 'test') {
    return false;
  }

  if (name === 'test') {
    this.setName('');
    return false;
  }

  return name;
}

// good
function checkName(hasName) {
  if (hasName === 'test') {
    return false;
  }

  const name = getName();

  if (name === 'test') {
    this.setName('');
    return false;
  }

  return name;
}

تجنب الإحالات المتسلسلة للمتغيرات chain variable assignments.
لماذا؟ الإحالات المتسلسة للمتغيرات تُنشئ متغيرات كلية.

// bad
(function example() {
  // JavaScript interprets this as
  // let a = ( b = ( c = 1 ) );
  // The let keyword only applies to variable a; variables b and c become
  // global variables.
  let a = b = c = 1;
}());

console.log(a); // throws ReferenceError
console.log(b); // 1
console.log(c); // 1

// good
(function example() {
  let a = 1;
  let b = a;
  let c = a;
}());

console.log(a); // throws ReferenceError
console.log(b); // throws ReferenceError
console.log(c); // throws ReferenceError

// the same applies for `const`

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

// bad

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

// good

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;

التّرفيع Hoisting

يتم ترفيع عبارات التصريح var إلى الأعلى داخل مدى الدالة المحتوية، ولكنّ إحالة قيم على تلك المتغيرات لا يُرفّع. const و let لديهما ميزة جديدة تُسمى Temporal Dead Zones (TDZ). لذلك من المهم أن تعرف لماذا استخدامtypeof لم يعد آمنًا.

// we know this wouldn’t work (assuming there
// is no notDefined global variable)
function example() {
  console.log(notDefined); // => throws a ReferenceError
}

// creating a variable declaration after you
// reference the variable will work due to
// variable hoisting. Note: the assignment
// value of `true` is not hoisted.
function example() {
  console.log(declaredButNotAssigned); // => undefined
  var declaredButNotAssigned = true;
}

// the interpreter is hoisting the variable
// declaration to the top of the scope,
// which means our example could be rewritten as:
function example() {
  let 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;
}

الدوال المجهولة يُرفّع اسم المتغير الخاص بها، بخلاف إحالة الدالة.

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

// the same is true when the function name
// is the same as the variable name.
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، ودائمًا تتبع القواعد البسيطة التالية.

  • Objects يتم إعطاؤها القيمة true
  • Undefined يتم إعطاؤه القيمة false
  • Null يتم إعطاؤه القيمة false
  • Booleans يتم إعطاؤه قيمة العنصر Boolean
  • Numbers يتم إعطاؤه القيمة false إن كان يساوي 0+ أو 0- أو NaN وإلا يُعطى القيمة true
  • Strings يتم إعطاؤها القيمة false إن كانت تساوي "" وإلا تٌعطى القيمة true
if ([0] && []) {
  // true
  // an array (even an empty one) is an object, objects will evaluate to true
}

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

// bad
if (isValid === true) {
  // ...
}

// good
if (isValid) {
  // ...
}

// bad
if (name) {
  // ...
}

// good
if (name !== '') {
  // ...
}

// bad
if (collection.length) {
  // ...
}

// good
if (collection.length > 0) {
  // ...
}

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

// bad
switch (foo) {
  case 1:
    let x = 1;
    break;
  case 2:
    const y = 2;
    break;
  case 3:
    function f() {
      // ...
    }
    break;
  default:
    class C {}
}

// good
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 وعادةً ينبغي أن تكون على سطر واحد

// bad
const foo = maybe1 > maybe2
  ? "bar"
  : value1 > value2 ? "baz" : null;

// split into 2 separated ternary expressions
const maybeNull = value1 > value2 ? 'baz' : null;

// better
const foo = maybe1 > maybe2
  ? 'bar'
  : maybeNull;

// best
const foo = maybe1 > maybe2 ? 'bar' : maybeNull;

تجنب استخدام التعابير الثلاثية التي لا لزوم لها.

// bad
const foo = a ? a : b;
const bar = c ? true : false;
const baz = c ? false : true;

// good
const foo = a || b;
const bar = !!c;
const baz = !c;

عند مزج العمليات، استخدم الأقواس. والاستثناء الوحيد هي العمليات الحسابية القياسية (+، -، *، /) بما أنّ الأسبقية فيها مفهومة بشكل جيد.
لماذا؟ هذا يحسّن المقروئية.

// bad
const foo = a && b < 0 || c > 0 || d + 1 === 0;

// bad
const bar = a ** b - 5 % d;

// bad
// one may be confused into thinking (a || b) && c
if (a || b && c) {
  return d;
}

// good
const foo = (a && b < 0) || c > 0 || (d + 1 === 0);

// good
const bar = (a ** b) - (5 % d);

// good
if (a || (b && c)) {
  return d;
}

// good
const bar = a + b / c * d;

الكتل Blocks

استخدم اللامّات في حال الكتل متعدد الأسطر.

// bad
if (test)
  return false;

// good
if (test) return false;

// good
if (test) {
  return false;
}

// bad
function foo() { return false; }

// good
function bar() {
  return false;
}

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

// bad
if (test) {
  thing1();
  thing2();
}
else {
  thing3();
}

// good
if (test) {
  thing1();
  thing2();
} else {
  thing3();
}

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

// bad
function foo() {
  if (x) {
    return x;
  } else {
    return y;
  }
}

// bad
function cats() {
  if (x) {
    return x;
  } else if (y) {
    return y;
  }
}

// bad
function dogs() {
  if (x) {
    return x;
  } else {
    if (y) {
      return y;
    }
  }
}

// good
function foo() {
  if (x) {
    return x;
  }

  return y;
}

// good
function cats() {
  if (x) {
    return x;
  }

  if (y) {
    return y;
  }
}

//good
function dogs(x) {
  if (x) {
    if (z) {
      return y;
    }
  } else {
    return z;
  }
}

عبارات التحكم Control Statements

في حال تجاوزت عبارات التحكم (if, while …) الحد الأقصى لطول السطر، فيمكن وضع كل شرط في سطر جديد. كما يجب أن يكون العامل المنطقي في بداية السطر.
● لماذا؟ وضع العوامل operators في بداية السطر يحافظ على محاذاة العوامل ويتبع نمطًا مماثلا لتسلسل الوظائف method chaining. كما يحسن القراءة من خلال توضيح العمليات المنطقية المعقدة.

// bad
if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) {
  thing1();
}

// bad
if (foo === 123 &&
  bar === 'abc') {
  thing1();
}

// bad
if (foo === 123
  && bar === 'abc') {
  thing1();
}

// bad
if (
  foo === 123 &&
  bar === 'abc'
) {
  thing1();
}

// good
if (
  foo === 123
  && bar === 'abc'
) {
  thing1();
}

// good
if (
  (foo === 123 || bar === "abc")
  && doesItLookGoodWhenItBecomesThatLong()
  && isThisReallyHappening()
) {
  thing1();
}

// good
if (foo === 123 && bar === 'abc') {
  thing1();
}

التعليقات

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

// bad
// make() returns a new element
// based on the passed in tag name
//
// @param {String} tag
// @return {Element} element
function make(tag) {

  // ...

  return element;
}

// good
/**
 * make() returns a new element
 * based on the passed-in tag name
 */
function make(tag) {

  // ...

  return element;
}

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

// bad
const active = true;  // is current tab

// good
// is current tab
const active = true;

// bad
function getType() {
  console.log('fetching type...');
  // set the default type to 'no type'
  const type = this.type || 'no type';

  return type;
}

// good
function getType() {
  console.log('fetching type...');

  // set the default type to 'no type'
  const type = this.type || 'no type';

  return type;
}

// also good
function getType() {
  // set the default type to 'no type'
  const type = this.type || 'no type';

  return type;
}

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

// bad
//is current tab
const active = true;

// good
// is current tab
const active = true;

// bad
/**
 *make() returns a new element
 *based on the passed-in tag name
 */
function make(tag) {

  // ...

  return element;
}

// good
/**
 * 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

استخدم مسافتين فارغتين space character لتعيين المسافات.

// bad
function foo() {
∙∙∙∙let name;
}

// bad
function bar() {
∙let name;
}

// good
function baz() {
∙∙let name;
}

ضع مسافة قبل اللامّة الأولى.

// bad
function test(){
  console.log('test');
}

// good
function test() {
  console.log('test');
}

// bad
dog.set('attr',{
  age: '1 year',
  breed: 'Bernese Mountain Dog',
});

// good
dog.set('attr', {
  age: '1 year',
  breed: 'Bernese Mountain Dog',
});

ضع مسافةً قبل القوس الفاتح في عبارات التحكم (if, while …). لا تضع أي مسافات في لائحة المعاملاتarguments واسم الدالة عند استدعاء الدالة أو تعريفها.

// bad
if(isJedi) {
  fight ();
}

// good
if (isJedi) {
  fight();
}

// bad
function fight () {
  console.log ('Swooosh!');
}

// good
function fight() {
  console.log('Swooosh!');
}

ضع مسافات بين العوامل operators.

// bad
const x=y+5;

// good
const x = y + 5;

أنهِ الملفات بحرف الرجوع إلى السطر.

// bad
import { es6 } from './AirbnbStyleGuide';
  // ...
export default es6;
// bad
import { es6 } from './AirbnbStyleGuide';
  // ...
export default es6;↵
↵
// good
import { es6 } from './AirbnbStyleGuide';
  // ...
export default es6;↵

استخدم المسافة البادئة indentation عند استخدام سلسة طويلة من الوظائف (أكثر من اثنتين). استخدم نقطة في البداية، لكي تبين أنّ السطر هو استدعاء لوظيفة، وليس تعليمةً جديدةً.

// bad
$('#items').find('.selected').highlight().end().find('.open').updateCount();

// bad
$('#items').
  find('.selected').
    highlight().
    end().
  find('.open').
    updateCount();

// good
$('#items')
  .find('.selected')
    .highlight()
    .end()
  .find('.open')
    .updateCount();

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

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

// good
const leds = stage.selectAll('.led').data(data);

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

// bad
if (foo) {
  return bar;
}
return baz;

// good
if (foo) {
  return bar;
}

return baz;

// bad
const obj = {
  foo() {
  },
  bar() {
  },
};
return obj;

// good
const obj = {
  foo() {
  },

  bar() {
  },
};

return obj;

// bad
const arr = [
  function foo() {
  },
  function bar() {
  },
];
return arr;

// good
const arr = [
  function foo() {
  },

  function bar() {
  },
];

return arr;

لا تبدأ الكتل بأسطر فارغة.

// bad
function bar() {

  console.log(foo);

}

// bad
if (baz) {

  console.log(qux);
} else {
  console.log(foo);

}

// bad
class Foo {

  constructor(bar) {
    this.bar = bar;
  }
}

// good
function bar() {
  console.log(foo);
}

// good
if (baz) {
  console.log(qux);
} else {
  console.log(foo);
}

لا تقم بإضافة مساحات داخل الأقواس.

// bad
function bar( foo ) {
  return foo;
}

// good
function bar(foo) {
  return foo;
}

// bad
if ( foo ) {
  console.log(foo);
}

// good
if (foo) {
  console.log(foo);
}

لا تقم بإضافة مساحات داخل المعقوفات.

// bad
const foo = [ 1, 2, 3 ];
console.log(foo[ 0 ]);

// good
const foo = [1, 2, 3];
console.log(foo[0]);

قم بإضافة مساحات داخل اللامّات.

// bad
const foo = {clark: 'kent'};

// good
const foo = { clark: 'kent' };

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

// bad
const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy;

// bad
$.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.'));

// good
const foo = jsonData
  && jsonData.foo
  && jsonData.foo.bar
  && jsonData.foo.bar.baz
  && jsonData.foo.bar.baz.quux
  && jsonData.foo.bar.baz.quux.xyzzy;

// good
$.ajax({
  method: 'POST',
  url: 'https://airbnb.com/',
  data: { name: 'John' },
})
  .done(() => console.log('Congratulations!'))
  .fail(() => console.log('You have failed this city.'));

الفواصل

تجنب الفواصل في البداية.

// bad
const story = [
    once
  , upon
  , aTime
];

// good
const story = [
  once,
  upon,
  aTime,
];

// bad
const hero = {
    firstName: 'Ada'
  , lastName: 'Lovelace'
  , birthYear: 1815
  , superPower: 'computers'
};

// good
const hero = {
  firstName: 'Ada',
  lastName: 'Lovelace',
  birthYear: 1815,
  superPower: 'computers',
};

استخدم فاصلة زائدة إضافية.
لماذا؟ هذا يؤدي إلى توضيح الاختلافات بين الأكواد فيgit . أيضًا، المترجمات من أمثال Babel ستزيل الفاصلة الزائدة الإضافية في الكود المترجم، ممّا يعني أنه لا داعي للقلق من مشكلة الفاصلة الزائدة في المتصفحات القديمة.

// bad - git diff without trailing comma
const hero = {
     firstName: 'Florence',
-    lastName: 'Nightingale'
+    lastName: 'Nightingale',
+    inventorOf: ['coxcomb chart', 'modern nursing']
};

// good - git diff with trailing comma
const hero = {
     firstName: 'Florence',
     lastName: 'Nightingale',
+    inventorOf: ['coxcomb chart', 'modern nursing'],
};
// bad
const hero = {
  firstName: 'Dana',
  lastName: 'Scully'
};

const heroes = [
  'Batman',
  'Superman'
];

// good
const hero = {
  firstName: 'Dana',
  lastName: 'Scully',
};

const heroes = [
  'Batman',
  'Superman',
];

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

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

// good (note that a comma must not appear after a "rest" element)
function createHero(
  firstName,
  lastName,
  inventorOf,
  ...heroArgs
) {
  // does nothing
}

// bad
createHero(
  firstName,
  lastName,
  inventorOf
);

// good
createHero(
  firstName,
  lastName,
  inventorOf,
);

// good (note that a comma must not appear after a "rest" element)
createHero(
  firstName,
  lastName,
  inventorOf,
  ...heroArgs
);

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

نعم

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

// bad - raises exception
const luke = {}
const leia = {}
[luke, leia].forEach(jedi => jedi.father = 'vader')

// bad - raises exception
const reaction = "No! That's impossible!"
(async function meanwhileOnTheFalcon(){
  // handle `leia`, `lando`, `chewie`, `r2`, `c3p0`
  // ...
}())

// bad - returns `undefined` instead of the value on the next line - always happens when `return` is on a line by itself because of ASI!
function foo() {
  return
    'search your feelings, you know it to be foo'
}

// good
const luke = {};
const leia = {};
[luke, leia].forEach((jedi) => {
  jedi.father = 'vader';
});

// good
const reaction = "No! That's impossible!";
(async function meanwhileOnTheFalcon(){
  // handle `leia`, `lando`, `chewie`, `r2`, `c3p0`
  // ...
}());

// good
function foo() {
  return 'search your feelings, you know it to be foo';
}

تحويل الأنواع Type Casting & Coercion

قم بتحويل الأنواع في بداية التعليمة.
النصوص

// => this.reviewScore = 9;

// bad
const totalScore = new String(this.reviewScore); // typeof totalScore is "object" not "string"

// bad
const totalScore = this.reviewScore + ''; // invokes this.reviewScore.valueOf()

// bad
const totalScore = this.reviewScore.toString(); // isn’t guaranteed to return a string

// good
const totalScore = String(this.reviewScore);

بالنسبة للأعداد: استخدم Number لأجل التحويل type casting، أما لتحليل النصوص فاستخدم parseInt دائمًا مع أساس radix .

const inputValue = '4';

// bad
const val = new Number(inputValue);

// bad
const val = +inputValue;

// bad
const val = inputValue >> 0;

// bad
const val = parseInt(inputValue);

// good
const val = Number(inputValue);

// good
const val = parseInt(inputValue, 10);

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

// good
/**
 * 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 بتة.
أكبر قيمة للأعداد ذات الإشارة signed numbers الممثلة على 32 بتة هو 2,147,483,647:

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

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

const age = 0;

// bad
const hasAge = new Boolean(age);

// good
const hasAge = Boolean(age);

// best
const hasAge = !!age;

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

تجنب الأسماء المكونة من حرف واحد. استخدم أسماءً معبّرة.

// bad
function q() {
  // ...
}

// good
function query() {
  // ...
}

استخدم أسلوب التسمية camelCase عند تسمية الأشياء، الدوال، والعينات instances.

// bad
const OBJEcttsssss = {};
const this_is_my_object = {};
function c() {}

// good
const thisIsMyObject = {};
function thisIsMyFunction() {}

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

// bad
function user(options) {
  this.name = options.name;
}

const bad = new user({
  name: 'nope',
});

// good
class User {
  constructor(options) {
    this.name = options.name;
  }
}

const good = new User({
  name: 'yup',
});

لا تستخدم العارضة السفلية في البداية أو النهاية.

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

// bad
this.__firstName__ = 'Panda';
this.firstName_ = 'Panda';
this._firstName = 'Panda';

// good
this.firstName = 'Panda';

لا تحفظ مرجعًا لـ this. واستخدم الدوال السهمية أو Function#bind .

// bad
function foo() {
  const self = this;
  return function () {
    console.log(self);
  };
}

// bad
function foo() {
  const that = this;
  return function () {
    console.log(that);
  };
}

// good
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
// bad
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

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

// good
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 عندما تصدّر منشئ / صنف / فردsingleton / مكتبة دوال / أو كائن عارٍ bare object.

const AirbnbStyleGuide = {
  es6: {
  },
};

export default AirbnbStyleGuide;

المختصرات والكلمات المنحوتة initialisms ينبغي أن تُكتب كلها بأحرف كبيرة أو صغيرة.
لماذا؟ الأسماء ينبغي أن تكون واضحة للإنسان، وليس لاسترضاء خوارزميات الكمبيوتر.

// bad
import SmsContainer from './containers/SmsContainer';

// bad
const HttpRequests = [
  // ...
];

// good
import SMSContainer from './containers/SMSContainer';

// good
const HTTPRequests = [
  // ...
];

// also good
const httpRequests = [
  // ...
];

// best
import TextMessageContainer from './containers/TextMessageContainer';

// best
const requests = [
  // ...
];

دالّات الدخول Accessors

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

// bad
class Dragon {
  get age() {
    // ...
  }

  set age(value) {
    // ...
  }
}

// good
class Dragon {
  getAge() {
    // ...
  }

  setAge(value) {
    // ...
  }
}

إذا كانت قيمة الخاصية / الوظيفة boolean، استخدم()isVal أو ()hasVal.

// bad
if (!dragon.age()) {
  return false;
}

// good
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)، قم بتمرير كائن حرفي object literal (معروف أيضًا باسم “hash”) بدلًا من قيمة خام raw value. هذا سيسمح لاحقًا بإضافة المزيد من البيانات إلى حَمولة الحدث دون الحاجة إلى إيجاد وتحديث كل معالجات الحدث. على سبيل المثال، بدلًا من:

// bad
$(this).trigger('listingUpdated', listing.id);

// ...

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

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

// good
$(this).trigger('listingUpdated', { listingID: listing.id });

// ...

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

jQuery

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

// bad
const sidebar = $('.sidebar');

// good
const $sidebar = $('.sidebar');

// good
const $sidebarBtn = $('.sidebar-btn');

خزّن عمليات البحث المؤقت لـ jQuery.

// bad
function setSidebar() {
  $('.sidebar').hide();

  // ...

  $('.sidebar').css({
    'background-color': 'pink',
  });
}

// good
function setSidebar() {
  const $sidebar = $('.sidebar');
  $sidebar.hide();

  // ...

  $sidebar.css({
    'background-color': 'pink',
  });
}

بالنسبة لاستعلامات DOM استخدم $('.sidebar ul') أو parent > child $('.sidebar > ul').
استخدم find مع استعلامات jQuery المحدّدة scoped jQuery object queries)).

// bad
$('ul', '.sidebar').hide();

// bad
$('.sidebar').find('ul').hide();

// good
$('.sidebar ul').hide();

// good
$('.sidebar > ul').hide();

// good
$sidebar.find('ul').hide();

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

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

// bad
isNaN('1.2'); // false
isNaN('1.2.3'); // true

// good
Number.isNaN('1.2.3'); // false
Number.isNaN(Number('1.2.3')); // true

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

// bad
isFinite('2e3'); // true

// good
Number.isFinite('2e3'); // false
Number.isFinite(parseInt('2e3', 10)); // true

الاختبار

نعم

function foo() {
  return true;
}
  • مهما كان إطار الاختبار الذي تستخدمه، يجب أن تكتب الاختبارات بنفسك!
  • احرص على كتابة العديد من الدوال البسيطة والصغيرة، وقلّل من استخدام البيانات المتحوّلةmutations .
  • كن حذرًا عند استخدام أصناف stubs و mocks – لأنها يمكن أن تجعل اختباراتك أكثر هشاشة.
  • محاولة اختبار 100٪ من الكود هو هدف جيد، حتى لو لم يكن دائمًا عمليًّا.
  • كلما أصلحت خللًا، قم بكتابة اختبار تقييم regression test. فبدونه من المؤكد أنّ الثغرات ستعود مجدّدًا.

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





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


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



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

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

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


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

تسجيل الدخول

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


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