البحث في الموقع
المحتوى عن 'الصفوف'.
-
كوتلن (Kotlin) هي لغة برمجة مخصّصة لمنصّة جافا الافتراضية (Java Virtual Machine أو اختصارًا JVM)، الأندرويد، والمتّصفح. تنتمي للغات statically typed (التي تفحص الأنواع وقت الترجمة). وهي متوافقة مع جافا 100%. // (//)التعليقات على سطر واحد تبدأ بـ/* التعليقات المؤلفة من عدة أسطر تبدو كهذه */ تعمل الكلمة المفتاحية package بنفس طريقة جافا package com.learnxinyminutes.kotlin نقطة الإدخال لبرامج Kotlin هي دالة(تابع) تسمى main ،يمرر التّابع مصفوفة تحتوي على وسطاء arguments لسطر الأوامر. fun main(args: Array<String>) { التّصريح عن القيم يتم باستخدام إما var أو val ،تصريح val لا يمكن إعادة تعيينه، في حين يمكن ذلك في var val fooVal =10 لا يمكننا لاحقًا إعادة تعيين قيمة fooVal لقيمة أخرى. var fooVar =10 fooVar =20 من الممكن إعادة تعيين قيمة fooVar. في أغلب الحالات، يمكن لكوتلن أن تحدّد ما هو نوع المتغير، لذلك لا داعي لتحديده صراحة في كل مرّة. يمكننا أن نصرّح بنوع المتغير بوضوح كالتالي: val foo:Int=7 يمكن تمثيل السلاسل بطريقة مماثلة في java. يتم الهروب باستخدام backslash. val fooString ="My String Is Here!" val barString ="Printing on a new line?\nNo Problem!" val bazString ="Do you want to add a tab?\tNo Problem!" println(fooString) println(barString) println(bazString) تُحدد السلسلة الخام string raw باستخدام triple quote ("""). ويمكن أن تحتوي أسطر جديدة و أية محارف أخرى. val fooRawString =""" fun helloWorld(val name :String){ println("Hello, world!")}""" println(fooRawString) السلاسل ممكن أن تحتوي تعابير القالب template expressions. تبدأ تعابير القالب بالرمز $. val fooTemplateString ="$fooString has ${fooString.length} characters" println(fooTemplateString)// => My String Is Here! has 18 characters من أجل المتغيرات التي تحوي قيمة فارغة null يجب تحديد ذلك صراحة nullable. يمكن تحديد المتغير قابلاً للقيمة null بإلحاق? بنوعه. ويمكننا الوصول إلى المتحولات القابلة لـ null باستخدام مُعامل التشغيل? ،ويمكننا استخدام عامل التشغيل :? لتحديد قيمة بديلة للاستخدام إذا كان المتغير فارغ null. var fooNullable:String?="abc" println(fooNullable?.length)// => 3 println(fooNullable?.length ?:-1)// => 3 fooNullable = null println(fooNullable?.length)// => null println(fooNullable?.length ?:-1)// => -1 يمكن التصريح عن الدوال باستخدام الكلمة المفتاحية fun. وتحدد وسطاء الدالة بين قوسين بعد اسم الدالة. ويمكن لوسطاء الدالة اختياريًا الاحتواء على قيمة افتراضية. ويحدّد نوع إرجاع الدالة، إذا لزم الامر بعد المعطيات. fun hello(name:String="world"):String{return"Hello, $name!"} println(hello("foo"))// => Hello, foo! println(hello(name ="bar"))// => Hello, bar! println(hello())// => Hello, world! يمكن لبارامتر الدالة أن يوسم بالكلمة المفتاحية vararg للسماح بتمرير عدد متغير من المعطيات إلى الدالة. fun varargExample(vararg names:Int){ println("Argument has ${names.size} elements")} varargExample()// => لا يحوي الوسيط أية عناصر varargExample(1)// => يحوي الوسيط عنصر واحد varargExample(1,2,3)// => يحوي الوسيط 3 عناصر عندما تتكوّن الدّالة من تعبير واحد فقط، يمكن حذف الأقواس المنحنية { } ويتم تحديد الجسم(العملية) بعد رمز = fun odd(x:Int):Boolean= x %2==1 println(odd(6))// => false println(odd(7))// => true إذا كان نوع الاسترجاع يمكن استنتاجه فلسنا بحاجة لتحديده. fun even(x:Int)= x %2==0 println(even(6))// => true println(even(7))// => false الدوال يمكن أن تأخذ دوال كوسطاء وترجع دالة. fun not(f:(Int)->Boolean):(Int)->Boolean{return{n ->!f.invoke(n)}} يمكن للدوال المسمّاة أن تحدّد كوسائط باستخدام معامل التشغيل val notOdd = not(::odd) val notEven = not(::even) يمكن تحديد تعابير Lambda كوسائط val notZero = not {n -> n ==0} إذا احتوت lambda على بارامتر واحد فقط يمكن حذف التصريح عنه (جنبًا إلى جنب مع<- )وسيكون اسم البارامتر الوحيد it. val notPositive = not {it >0}for(i in 0..4){ println("${notOdd(i)} ${notEven(i)} ${notZero(i)} ${notPositive(i)}")} تستخدم الكلمة المفتاحية class للتّصريح عن الأصناف. class ExampleClass(val x:Int){ fun memberFunction(y:Int):Int{return x + y } infix fun infixMemberFunction(y:Int):Int{return x * y }} لإنشاء حالة instance جديدة نستدعي الباني. ولاحظ أن Kotlin لا تحوي الكلمة المفتاحية new. val fooExampleClass =ExampleClass(7) يمكن استدعاء دوال المستخدم باستخدام التنويت النقطي . println(fooExampleClass.memberFunction(4))// => 11 إذا وُسمت الدالة بالكلمة المفتاحية infix يمكن عندها أن يستدعى باستخدام التنويت الداخلي println(fooExampleClass infixMemberFunction 4)// => 28 أصناف البيانات Data classes هي طريقة مختصرة لإنشاء الأصناف التي تحتوي بيانات فقط وتنشئ دوال hashCode،equals و toString تلقائيًا. data classDataClassExample(val x:Int, val y:Int, val z:Int) val fooData =DataClassExample(1,2,4) println(fooData)// => DataClassExample(x=1, y=2, z=4) أصناف البيانات لديها دالة copy val fooCopy = fooData.copy(y =100) println(fooCopy)// => DataClassExample(x=1, y=100, z=4) يمكن أن تفكَك الكائنات Objects في متغيرات متعددة val (a, b, c)= fooCopy println("$a $b $c")// => 1 100 4 التفكيك باستخدام حلقة for for((a, b, c) in listOf(fooData)){ println("$a $b $c")// => 1 100 4} val mapData = mapOf("a" to 1,"b" to 2) Map.Entry قابل للتفكيك كذلك for((key, value) in mapData){ println("$key -> $value")} الدالة with مشابهة لعبارة with في جافا data classMutableDataClassExample(var x:Int, var y:Int, var z:Int) val fooMutableData =MutableDataClassExample(7,4,9) with (fooMutableData){ x -=2 y +=2 z--} println(fooMutableData)// => MutableDataClassExample(x=5, y=6, z=8) يمكننا إنشاء قائمة (لائحة) باستخدام الدالة listOf. ستكون القائمة غير قابلة للتغيير ولا يمكن إضافة عناصر أو إزالتها. val fooList = listOf("a","b","c") println(fooList.size)// => 3 println(fooList.first())// => a println(fooList.last())// => c// index يمكن الوصول إلى عناصر القائمة من خلال فهرسها println(fooList[1])// => b يمكن إنشاء قائمة قابلة للتعديل باستخدام الدالة mutableListOf val fooMutableList = mutableListOf("a","b","c") fooMutableList.add("d") println(fooMutableList.last())// => d println(fooMutableList.size)// => 4 يمكن إنشاء تعيين set باستخدام الدّالة setOf val fooSet = setOf("a","b","c") println(fooSet.contains("a"))// => true println(fooSet.contains("z"))// => false يمكننا إنشاء خريطة map باستخدام الدّالة mapOf val fooMap = mapOf("a" to 8,"b" to 7,"c" to 9) يمكن الوصول لقيم الـ Map من خلال مفاتيحها println(fooMap["a"])// => 8 تُمثل المتتالية Sequences مجموعات تقييمها مؤجل إلى حين الحاجة lazily-evaluated collections. ويمكننا إنشاء متتالية باستخدام الدّالة generateSequence. val fooSequence = generateSequence(1,{ it +1}) val x = fooSequence.take(10).toList() println(x)// => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] مثال لاستخدام متتالية لتوليد أرقام فيبانوتشي fibonacci fun fibonacciSequence():Sequence<Long>{ var a =0L var b =1L fun next():Long{ val result = a + b a = b b = result return a }return generateSequence(::next)} val y = fibonacciSequence().take(10).toList() println(y)// => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] تزوّد Kotlin دوال عالية الرتبة للعمل مع المجموعات collections val z =(1..9).map{it *3}.filter {it <20}.groupBy {it %2==0}.mapKeys {if(it.key)"even"else"odd"} println(z)// => {odd=[3, 9, 15], even=[6, 12, 18]} يمكن استخدام حلقة for مع أي شيء يؤمن التكرار. for(c in "hello"){ println(c)} تعمل حلقات while بشكل مشابه لعملها في اللغات الأخرى var ctr =0while(ctr <5){ println(ctr) ctr++}do{ println(ctr) ctr++}while(ctr <10) يمكن استخدام if كتعبير يرجع القيم. لهذا السبب ليس هناك حاجة لمعامل التشغيل :? في Kotlin. val num =5 val message =if(num %2==0)"even"else"odd" println("$num is $message")// => 5 is odd يمكن استخدام when كبديل لسلاسل if-else if val i =10 when { i <7-> println("first block") fooString.startsWith("hello")-> println("second block")else-> println("else block")} يمكن استخدام when مع المعطيات. when (i){0,21-> println("0 or 21") in 1..20-> println("in the range 1 to 20")else-> println("none of the above")} يمكن استخدام when كدالة ترجع القيم. var result = when (i){0,21->"0 or 21" in 1..20->"in the range 1 to 20"else->"none of the above"} println(result) يمكننا التحقق فيما إذا كان الكائن object من نوع محدّد باستخدام معامل التشغيل is. إذا مرر الكائن فحص النوع type check عندها يمكن استخدام هذا النوع دون الموائمة بشكل صريح. fun smartCastExample(x:Any):Boolean{if(x is Boolean){ X توائم أوتوماتيكيًا إلى Boolean return x }elseif(x is Int){ X توائم أوتوماتيكيًا إلى Int return x >0}elseif(x is String){ X توائم أوتوماتيكيًا إلى String return x.isNotEmpty()}else{returnfalse}} println(smartCastExample("Hello, world!"))// => true println(smartCastExample(""))// => false println(smartCastExample(5))// => true println(smartCastExample(0))// => false println(smartCastExample(true))// => true تعمل الموائمة الذكيّة smartcast أيضًا مع كتلة when fun smartCastWhenExample(x:Any)= when (x){ is Boolean-> x is Int-> x >0 is String-> x.isNotEmpty()else->false} الملحقات Extensions هي طريقة لإضافة وظائف جديدة للصنف class. وهي مشابهة لدوال ملحقات #C fun String.remove(c:Char):String{returnthis.filter {it != c}} println("Hello, world!".remove('l'))// => Hello, word! println(EnumExample.A)// => A println(ObjectExample.hello())// => hello أصناف Enum مشابهة لأنواع enum في جافا. enumclassEnumExample{ A, B, C } يمكن استخدام الكلمة المفتاحية object لإنشاء كائنات وحيدة. لا يمكننا تمثيلها ولكن يمكن أن نشير لحالتها الفريدة من خلال اسمها. وهذا مشابه لكائنات Scala singleton object ObjectExample{ fun hello():String{return"hello"}} fun useObject(){ObjectExample.hello() val someRef:Any=ObjectExample//كماهي تمامًا objects نستخدم أسماء الكائنات } ترجمة -وبتصرّف- للمقال Learn kotlin in Y Minutes
-
ملاحظة: يفترض هذا الدليل أنك تستخدم مصرّف 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}. It’s so long that we don’t want it to take up space on the .map line!` )); // جيّد [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!` )); // سيّئ [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
-
جافا هي لغة برمجة حاسوبية للأغراض العامة، المتزامنة، المعتمدة على الصفوف، وغرضية التوجه // يبدأ التعليق المكتوب على خط واحد ب .// /* يبدو التعليق المكتوب على عدة سطور بهذا الشكل. */ /** *تبدو التعليقات في ملفات لغة البرمجة جافا بهذا الشكل *و تستخدم لوصف الصف أو الصفات المختلفة لكائن معين. *:الصفات الرئيسية * *@author- اسم مؤلف الكود. *ويحتوي على معلومات الاتصال كالبريد الالكتروني لمؤلف الكود أو للمؤلفين. *@version- النسخة الحالية من البرنامج. *@since -الوقت الذي تم فيه إضافة هذا الجزء من البرنامج. *@param -من أجل وصف البارامترات المختلفة للمنهج (method). *@return -لوصف القيمة التي يرجعها المنهج. *@deprecated -لإظهار انتهاء صلاحية الكود أو عدم وجوب استخدامه. *@see - روابط إلى جزء آخر من المستندات */ استورد الصف ArrayList بدلاً من استيراد الرزمة java.util كلّها import java.util.ArrayList; استورد جميع الصفوف الموجود داخل الرزمة import java.security.*; يحوي أي ملف جافا على صف عام، على المستوى الخارجي، له نفس اسم الملف public class LearnJava { ليعمل برنامج جافا يجب أن يحوي على تابع رئيسي بمثابة نقطة البدء public static void main(String[] args) { الدخل/الخرج دورة تطوير التطبيقات باستخدام لغة Python احترف تطوير التطبيقات مع أكاديمية حسوب والتحق بسوق العمل فور انتهائك من الدورة اشترك الآن الخرج استخدم ()System.out.println لطباعة السطور النصيّة System.out.println("Hello World!"); System.out.println( "Integer: " + 10 + " Double: " + 3.14 + " Boolean: " + true); استخدم ()System.out.print للطباعة بدون سطر جديد System.out.print("Hello "); System.out.print("World"); استخدم ()System.out.printf لتنسيق الطباعة بسهولة System.out.printf("pi = %.5f", Math.PI); // => pi = 3.14159 الدخل استخدم scanner لقراءة الدخل، يجب استيراد الصف ;java.util.Scanner Scanner scanner = new Scanner(System.in); لقراءة السلاسل المحرفية المُدخلة String name = scanner.next(); لقراءة البايتات المُدخلة byte numByte = scanner.nextByte(); لقراءة العدد الصحيح المُدخل int numInt = scanner.nextInt(); لقراءة العدد الحقيقي المٌدخل float numFloat = scanner.nextFloat(); لقراءة العدد الحقيقي مضاعف الدقة المُدخل double numDouble = scanner.nextDouble(); لقراءة القيمة المنطقية المُدخلة boolean bool = scanner.nextBoolean(); المتغيرات التصريح عن المتغيرات يتم التصريح عن متغير باستخدام // <type> <name> int fooInt; يتم التصريح عن مجموعة من المتغيرات من نفس النوع // <type> <name1>, <name2>, <name3> int fooInt1, fooInt2, fooInt3; تهيئة المتغير يتم تهيئة متغير باستخدام // <type> <name> = <val> int barInt = 1; يتم تهيئة مجموعة من المتغيرات من نفس النوع بنفس القيمة باستخدام // <type> <name1>, <name2>, <name3> // <name1> = <name2> = <name3> = <val> int barInt1, barInt2, barInt3; barInt1 = barInt2 = barInt3 = 1; أنواع المتغير البايت (Byte) : وهو 8bit ويستخدم لترميز الأعداد الصحيحة بين -128 و 127 byte fooByte = 100; إذ كنت ترغب بتفسير البايت كعدد صحيح موجب (بدون إشارة). فإن هذه العملية البسيطة من الممكن أن تساعد int unsignedIntLessThan256 = 0xff & fooByte; هذا يناقض عمل cast الذي من الممكن أن يعطي عدد سالب int signedInt = (int) fooByte; القصير (Short): وهو 16bit ويستخدم لترميز الأعداد الصحيحة بين -32,768 و 32,767 short fooShort = 10000; الصحيح (Integer): وهو 32bit ويستخدم لترميز الأعداد الصحيحة بين -2,147,483,648 و 2,147,483,647 int bazInt = 1; الطويل (Long): وهو 64bit ويستخدم لترميز الأعداد الصحيحة بين -9,223,372,036,854,775,808 و 9,223,372,036,854,775,807 long fooLong = 100000L; يستخدم المحرف L للدلالة على أن قيمة المتحول هي من النوع Long. و أي قيمة مُسندة للمتحول بدون استخدام L هي عبارة عن عدد صحيح int بشكل افتراضي. ملاحظة: إن الأنواع byte، short، int، long، هي أنواع ذات إشارة signed. أي من الممكن أن تحوي على قيم موجبة أو قيم سالبة. لا يوجد متغيرات بقيمة موجبة فحسب. ولكن المحارف تعتبر من نوعية unsigned ذات القيمة الموجبة فقط ذات 16bit. العائم (Float): وهو ذو دقة أحادية 32 bit IEEE 754 ويستخدم لترميز الأعداد الحقيقية ذات الفاصلة العائمة بين 149-^2 و 127^2 - (23-^2-2) float fooFloat = 234.5f; يستخدم المحرف f أو F للدلالة على أن قيمة المتحول هي من النوع Float. و إلا سيعتبر المتغير من النوع الحقيقي المضاعف. المضاعف (Double): وهو ذو دقة مضاعفة 64 bit IEEE 754 ويستخدم لترميز الأعداد الحقيقية ذات الفاصلة العائمة بين 1074-^2 و 1023^2 - (52-^2-2) double fooDouble = 123.4; القيم المنطقية (Boolean) ذات القيمة: true و false boolean fooBoolean = true; boolean barBoolean = false; Char هو محرف يونيكود 16بت أحادي char fooChar = 'A'; لا يمكن أعادة تهيئة المعطيات من النوع final final int HOURS_I_WORK_PER_WEEK = 9001; ولكن من الممكن تهيئتها بعد عملية التصريح عنها final double E; E = 2.71828; BigInteger -عبارة عن نوع الأعداد الصحيحة الثابتة بمستويات دقّة مختلفة. يستخدم نوع المعطيات BigInteger للسماح للمبرمج بالتعامل مع الأعداد الصحيحة التي هي أكبر من 64-بت. حيث تُخزّن الأعداد الصحيحة في مصفوفة من البايتات، التي يمكن التلاعب بها باستخدام التوابع المبنيّة في الصف BigInteger . يمكن تهيئة المتغير من النوع BigInteger بمصفوفة من البايتات أو بمصفوفة من السلاسل المحرفية. BigInteger fooBigInteger = new BigInteger(fooByteArray); BigDecimal عبارة عن نوع الأعداد الحقيقية الثابتة بمستويات دقة مختلفة. ويأخذ المتغير من النوع BigDecimal معاملين: عدد صحيح ذو حجم غير مقّيّد، يمثل مستوى الدقّة، وعدد صحيح أخر بحجم 32 بت. يسمح النوع BigDecimal بالتحكم بتقريب الأعداد الحقيقية. عندما يكون مطلوب مستوى دقة محدد للعدد الحقيقي يُنصح باستخدام BigDecimal . من الممكن تهيئة المتغير من النوع BigDecimal بأحد الأنواع التالية: int ،long ،double ،String ،BigInteger BigDecimal fooBigDecimal = new BigDecimal(fooBigInteger, fooInt); كُن حذرًا عند استخدامك للأنواع float ,double لأن عدم الدقّة في تحديد النوع سوف تُنسخ في الـ BigDecimal ويُفضل استخدام سلسلة محرفية ثابتة عندما تحتاج إلى قيمة دقيقة. BigDecimal tenCents = new BigDecimal("0.1"); String -السلاسل المحرفية String fooString = "My String Is Here!"; الحرف الخاص n\ يُعرف بحرف السطر الجديد الذي يحرك مؤشر الكتابة إلى بداية السطر التالي. String barString = "Printing on a new line?\nNo Problem!"; الحرف الخاص t\ يُعرف بالمسافة الأفقية، يقوم بتحريك مؤشر الكتابة مسافة معينة إلى النقطة التالية في السطر. String bazString = "Do you want to add a tab?\tNo Problem!"; System.out.println(fooString); System.out.println(barString); System.out.println(bazString); بناء السلاسل المحرفية: باستخدام عامل الجمع (+) تُعتبر الطريقة الأساسية (الأمثل) للقيام ببناء السلاسل المحرفية. String plusConcatenated = "Strings can " + "be concatenated " + “via + operator.”; System.out.println(plusConcatenated); // Output: Strings can be concatenated via + operator. باستخدام الصف StringBuilder لا تشكل هذه الطريقة أية سلاسل محرفية وسيطة، فقط تقوم بتخزين قطع السلاسل المحرفية و ربطها مع بعضها عندما يتم استدعاء التابع ()toString. تلميح: لا يعتبر الصف StringBuilder إجرائية آمنة. يوجد صف بديل آمن StringBuffer (مع تأثير بسيط على الأداء). StringBuilder builderConcatenated = new StringBuilder(); builderConcatenated.append("You "); builderConcatenated.append("can use "); builderConcatenated.append("the StringBuilder class."); System.out.println(builderConcatenated.toString()); // فقط الآن تمّ بناء السلسة المحرفية // Output: You can use the StringBuilder class. تكون StringBuilder فعاله عندما السلسلة المحرفية الكاملة المبنيّة مطلوبة في نهاية عملية ما. StringBuilder stringBuilder = new StringBuilder(); String inefficientString = ""; for (int i = 0 ; i < 10; i++) { stringBuilder.append(i).append(" "); inefficientString += i + " "; } System.out.println(inefficientString); System.out.println(stringBuilder.toString()); تتطلب inefficientString عمل أكثر، حيث أنها تنتج سلسلة محرفية عند كل دورة للحلقة. يتم ترجمة تجميع السلاسل المحرفية البسيطة بالعامل (+) إلى: toString() و StringBuilder تجنب استخدام تجميع السلاسل المحرفية داخل الحلقات. لاستخدام منسق السلاسل المحرفية طريقة أخرى بديلة لتوليد السلاسل المحرفية سريعة و قابلة للقراءة. String.format("%s may prefer %s.", “Or you”, “String.format()”); // Output: Or you may prefer String.format(). المصفوفات يجب تحديد حجم المصفوفة بشكل فوري بمجرد التصريح عنها. تستخدم الصيغة التالية للتصريح عن مصفوفة. // <datatype>[] <var name> = new <datatype>[<array size>]; // <datatype> <var name>[] = new <datatype>[<array size>]; int[] intArray = new int[10]; String[] stringArray = new String[1]; boolean boolArray[] = new boolean[100]; طريقة أخرى للتصريح عن مصفوفة و تهيئتها int[] y = {9000, 1000, 1337}; String names[] = {"Bob", "John", "Fred", "Juan Pedro"}; boolean bools[] = {true, false, false}; فهرسة المصفوفة - الوصول إلى عنصر فيها System.out.println("intArray @ 0: " + intArray[0]); تبدأ المصفوفات بالفهرس (0)، و هي قابله للتغيير . intArray[1] = 1; System.out.println("intArray @ 1: " + intArray[1]) ArrayLists; // => 1 مجموعة من أنواع المعطيات الأخرى، التي تستحق التدقيق فيها: ArrayLists : مشابهة للمصفوفات، إلا أنها تحوي على وظائف إضافية، وحجمها قابل للتعديل. LinkedLists : عبارة عن تنفيذ لقائمة مترابطة بشكل مضاعف، التي تنفذ جميع العمليات التي من المتوقع لقائمة مترابطة بشكل مضاعف أن تنفذها. Maps : عبارة عن وصل كائنات المفتاح إلى كائنات القيمة. وهي عبارة عن واجهة، و بالتالي لا يمكن أن يتم تشكيل كائنات منها. يجب أن يتم تحديد نوع المفاتيح و القيم بناءً على الكائن المُشكل من الصف. من الممكن ان يُربط كل مفتاح إلى قيمة واحدة فقط، ومن الممكن أن يظهر كل مفتاح مرة واحدة فقط. (لا يوجد نسخ) لتنفيذ الخريطة/الواجهة Map hashtable : يُستخدم هذا الصف الجدول ، HashMaps وينفذ الخريطة Map ، هذا يسمح بتثبيت زمن العمليات الأساسية، مثل الحصول على عنصر أو إدخال عنصر، حتى في المجموعات الكبيرة. TreeMap: عبارة عن Map ، مصنفة حسب مفاتيحها. يُحافظ كل تعديل على ترتيبه، إما باستخدام المقارن المزود عند عملية توليد الكائن، أو باستخدام المقارنات لكل كائن، اذا كان ينفذ واجهة قابلة للمقارنة. سوف يؤدي الفشل المدمج في تنفيذ واجهة قابلة للمقارنة، مع الفشل في تزويد مقارن إلى رمي ClassCastExceptions تأخذ عمليات إدخال و حذف عناصر زمن من الدرجة ((O(log(n ، لذلك تجنب استخدام بنى المعطيات هذه، إلا أذا كنت ترغب من الاستفادة من ميزة الترتيب. العمليات System.out.println("\n->Operators"); int i1 = 1, i2 = 2; // الطريقة المختصرة للتصريح عن عدة متغيرات في نفس الوقت العمليات الحسابية بسيطة System.out.println("1+2 = " + (i1 + i2)); // => 3 System.out.println("2-1 = " + (i2 - i1)); // => 1 System.out.println("2*1 = " + (i2 * i1)); // => 2 System.out.println("1/2 = " + (i1 / i2)); // => 0 (int يعيد int/int) System.out.println("1/2.0 = " + (i1 / (double)i2)); // => 0.5 باقي القسمة System.out.println("11%3 = "+(11 % 3)); // => 2 عمليات المقارنة System.out.println("3 == 2? " + (3 == 2)); // => false System.out.println("3 != 2? " + (3 != 2)); // => true System.out.println("3 > 2? " + (3 > 2)); // => true System.out.println("3 < 2? " + (3 < 2)); // => false System.out.println("2 <= 2? " + (2 <= 2)); // => true System.out.println("2 >= 2? " + (2 >= 2)); // => true العمليات المنطقية System.out.println("3 > 2 && 2 > 3? " + ((3 > 2) && (2 > 3))); // => false System.out.println("3 > 2 || 2 > 3? " + ((3 > 2) || (2 > 3))); // => true System.out.println("!(3 == 2)? " + (!(3 == 2))); // => true العمليات على مستوى البت ~ عامل إيجاد المتمم على مستولى البتات. << عامل الإزاحة الحسابية نحو اليسار (مع الأخذ بعين الاعتبار بت الإشارة). >> عامل الإزاحة الحسابية نحو اليمين (مع الأخذ بعين الاعتبار بت الإشارة). >>> عامل الإزاحة المنطقية نحو اليمين (بدون الأخذ بعين الاعتبار بت الإشارة). & على مستوى البتّات and العملية المنطقية ^ على مستوى البتّات xor العملية المنطقية | على مستوى البتّات or العملية المنطقية عمليات الزيادة int i = 0; System.out.println("\n->Inc/Dec-rementation"); العامل ++ يقوم بالزيادة بمقدار واحد. العامل – يقوم بالإنقاص بمقدار واحد. إذا تم وضع هذين العاملين قبل المتغير، فإنهن يقومان بالزيادة أو الإنقاص، ومن ثم يعيدان قيمة المتحول الجديدة. أما إذا تم وضع هذين العاملين بعد المتغير، فإنهما يقومان بإعادة قيمة المتحول، ومن ثم يقومان بالزيادة أو الإنقاص. System.out.println(i++); // i = 1, يطبع 0 (زياده لاحقة). System.out.println(++i); // i = 2, يطبع 2 (زيادة سابقة). System.out.println(i--); // i = 1, يطبع 2 (إنقاص لاحق) System.out.println(--i); // i = 0, يطبع 0 (إنقاص سابق) بنى التحكم System.out.println("\n->Control Structures"); عبارة if مشابهة لتلك الموجودة في لغة البرمجة C int j = 10; if (j == 10) { System.out.println("I get printed"); } else if (j > 10) { System.out.println("I don't"); } else { System.out.println("I also don't"); } حلقة While int fooWhile = 0; while(fooWhile < 100) { System.out.println(fooWhile); //زيادة العدّاد //(في المجال (0،1،..99 foowhile مكررة 100 مرة ، حيث أن قيم المتغير fooWhile++; } System.out.println("fooWhile Value: " + fooWhile); حلقة Do While int fooDoWhile = 0; do { System.out.println(fooDoWhile); //زيادة العدّاد //في المجال 0-99 foowhile مكررة 99 مرة ، حيث أن قيم المتغير fooDoWhile++; } while(fooDoWhile < 100); System.out.println("fooDoWhile Value: " + fooDoWhile); حلقة For : هيكلية الحلقة على الشكل التالي: for(<start_statement>; <conditional>; <step>) مثال for (int fooFor = 0; fooFor < 10; fooFor++) { System.out.println(fooFor); // fooFor 0->9 مكررة 10 مرات، حيث } System.out.println("fooFor Value: " + fooFor); //outerيتم الخروج من الحلقة الداخلية باستخدام جملة الهروب المعنونّة بـ outer: for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { if (i == 5 && j ==5) { break outer; //يخرج من الحلقة الخارجية أيضا، بدلاً من الخروج فقط من الحلقة الداخلية } } } حلقة For Each : قادرة على العمل مع المصفوفات و الكائنات for إن حلقة التي تنفذ الواجهات القابلة للتكرار. int[] fooList = {1, 2, 3, 4, 5, 6, 7, 8, 9}; هيكلية الحلقة على الشكل التالي: for (<object> : <iterable>) تقرأ هكذا: لكل عنصر في ال iterable ملاحظة: يجب أن يتطابق نوع الكائن مع نوع عناصر ال iterable for (int bar : fooList) { System.out.println(bar); //سوف تتكرر 9 مرات و يطبع 1-9 على سطور جديدة. } Switch تعمل Switch مع أنواع المعطيات: int، char، short ،byte وأيضًا من الممكن أن تعمل مع النوع Enum و String و مجموعة من الصفوف الخاصة التي تجمع الأنواع البدائية: Character, Byte, Short, Integer. بدءًا من جافا 7، من الممكن استخدام النوع String. ملاحظة: تذكر عدم استخدام التعليمة "break" في نهاية حالة جزئية إذا كان هناك يوجد حالة أخرى تستوفي الشرط أيضًا. int month = 3; String monthString; switch (month) { case 1: monthString = "January"; break; case 2: monthString = "February"; break; case 3: monthString = "March"; break; default: monthString = "Some other month"; break; } System.out.println("Switch Case Result: " + monthString); عبارة (Try-with-resources) في Java الإصدار +7 تُستخدم في جافا عبارات Try-catch-finally، و لكن في جافا +7 أيضا ً مُتاح استخدام try-with-resources إن عبارات try-with-resources تُبسط عمل عبارات try-catch-finally عن طريق إغلاق المصادر بشكل أتوماتيكي. لاستخدام try-with-resources ضمّن كائن من صف في تعليمة try. try (BufferedReader br = new BufferedReader(new FileReader("foo.txt"))) { // يمكنك أن تجرّب شيئًا يحتاج إلى استثناء System.out.println(br.readLine()); // في جافا 7 المصادر مغلقة دومًا حتى لو كان هناك استثناء } catch (Exception ex) { //catch سوف يتم إغلاق المصدر قبل أن تنفذ عبارة System.out.println("readLine() failed."); } في هذه الحالة، لا يوجد حاجة إلى عبارة finally حيث أن BufferedReader تم إغلاقه للتو. يتم استخدام هذا لتجنب الحالات الحرجة، عندما عبارة finally لا يمكن استدعاءها. الاختزال الشرطي يمكنك استخدام العامل "?" لسرعة الأسناد . تُقرأ كما يلي: "إذا كانت العبارة صحيحة ، استخدم <أول قيمة> و إلا استخدم <القيمة الثانية> int foo = 5; String bar = (foo < 10) ? "A" : "B"; System.out.println("bar : " + bar); // "bar : A"تطبع // صحيحة (foo < 10) لأن العبارة //أو بشكل مبسط: System.out.println("bar : " + (foo < 10 ? "A" : "B")); التحويل بين أنواع المعطيات تحويل المعطيات تحويل السلاسل المحرفية إلى عدد صحيح Integer.parseInt("123");//يعيد نسخة العدد الصحيح من السلسة المحرفية "123" تحويل العدد الصحيح إلى سلسلة محرفية Integer.toString(123);//يعيد النسخة المحرفية من العدد الصحيح 123 راجع تحويل المعطيات للأنواع التالية من الصفوف: Double Long String الصفوف و التوابع للتصريح عن كائن من الصف Bicycle استخدم الكلمة المفتاحية new Bicycle trek = new Bicycle(); استدعاء توابع الكائن trek.speedUp(3); // setter , getter يجب عليك استخدام توابع الواضع و الآخذ trek.setCadence(100); يُعيد التابع ()toString التمثيل النصي للكائن. System.out.println("trek info: " + trek.toString()); التهيئة مزدوجة الأقواس لا تملك جافا تراكيب لتشكيل مجموعات ساكنة من المعطيات بطريقة سهلة عادةً يتم استخدام الطريقة التالية: private static final Set<String> COUNTRIES = new HashSet<String>(); static { COUNTRIES.add("DENMARK"); COUNTRIES.add("SWEDEN"); COUNTRIES.add("FINLAND"); } ولكن يوجد طريقة أنيقة لإنجاز الشيء نفسه بشكل أسهل، تُدعى هذه الطريقة مزدوجة الأقواس private static final Set<String> COUNTRIES = new HashSet<String>() {{ add("DENMARK"); add("SWEDEN"); add("FINLAND"); }} يولد القوس الاول { } صف داخلي بدون اسم AnonymousInnerClass ويعرّف القوس الثاني{ } كتلة مهيئ الحالة instance. يتم استدعاء هذه الكتلة، عندما يتم توليد الصف الداخلي لا يعمل هذا فقط للمجموعات، إنما يعمل أيضًا لكل الصفوف غير النهائي non-final يمكنك تضمين صفوف أخرى خارجية غير عامة في ملف جافا ، ولكنها ليست بممارسة جيدة. بدلاً من ذلك يفضّل فصل الصفوف في ملفات جافا منفصلة صيغة التصريح عن صف: // <public/private/protected> class <class name> { // } حقول البيانات, أنماط الباني, الوظائف كلها في الداخل تستدعى الوظائف كالدوال في جافا class Bicycle { حقول و متغيرات الصف Bicycle Public: يمكن الوصول إليه من أي مكان public int cadence; Private: يمكن الوصول إليه فقط من داخل الصف private int speed; Protected: يمكن الوصول إليه فقط من داخل الصف و الصفوف الفرعية protected int gear; default: يمكن الوصول إليه فقط من داخل الرزمة. String name; Static متغير صف ساكن static String className; البلوك الساكن Static لا تملك جافا تنفيذ للبواني الساكنة، ولكن جافا تملك كتل برمجية لتعريف متغيرات الصف (المتغيرات لساكنة) سيتم استدعاء هذا البلوك عند تحميل الصف. static { className = "Bicycle"; } الباني هي الطريقة التي يتم فيها توليد الصفوف. هذا هو الباني public Bicycle() { //يمكنك أيضا استدعاء باني آخر: // this(1, 50, 5, "Bontrager"); gear = 1; cadence = 50; speed = 5; name = "Bontrager"; } هذا عبارة عن باني ذو معاملات public Bicycle(int startCadence, int startSpeed, int startGear, String name) { this.gear = startGear; this.cadence = startCadence; this.speed = startSpeed; this.name = name; } صيغة الدالة // <public/private/protected> <return type> <function name>(<args>) أصناف الجافا غالبًا ما تنفّذ getters وsetters في حقولها صيغة التصريح عن الدالة // <access modifier> <return type> <method name>(<args>) public int getCadence() { return cadence; } دوال void لا تحتاج تصريح عودة public void setCadence(int newValue) { cadence = newValue; } public void setGear(int newValue) { gear = newValue; } public void speedUp(int increment) { speed += increment; } public void slowDown(int decrement) { speed -= decrement; } public void setName(String newName) { name = newName; } public String getName() { return name; } دالة لتوليد قيم الصفات لهذا الكائن @Override // موروثة من الصف لهذا الكائن. public String toString() { return "gear: " + gear + " cadence: " + cadence + " speed: " + speed + " name: " + name; } الصف PennyFarthing عبارة عن صنف فرعي من Bicycle class PennyFarthing extends Bicycle { -Penny Farthings //هي عباره عن نوع من الدراجات بعجلات أمامية كبيرة. public PennyFarthing(int startCadence, int startSpeed) { // super استدعي الباني الأب باستخدام الكلمة المفتاحية super(startCadence, startSpeed, 0, "PennyFarthing"); } يجب عليك تعليم المنهج الذي تُعيد كتابته بـ annotation@. @Override public void setGear(int gear) { this.gear = 0; } Object casting بما أن الصف PennyFarthing يرث الصف Bicycle : فمن الممكن القول أن: Bicycle هو PennyFarthing، و يمكن أن نكتب: // Bicycle bicycle = new PennyFarthing(); هذا يُدعى"object casting" حيث يتم تشكيل كائن من كائن آخر. الواجهات صيغة التصريح عن واجهة // <access-level> interface <interface-name> extends <super-interfaces> { // // Constants // // Method declarations // } مثال - الطعام: public interface Edible { public void eat(); //يجب على أي صف ينفذ هذه الواجهة // ان ينفذ هذه الدالة } public interface Digestible { public void digest(); // منذ جافا 8، من الممكن أن تملك الواجهات دالة افتراضية. public default void defaultMethod() { System.out.println("Hi from default method ..."); } } الآن يمكنك تشكيل صف يُنفذ كلا من هاتين الواجهتين. public class Fruit implements Edible, Digestible { @Override public void eat() { // ... } @Override public void digest() { // ... } } في جافا، يمكنك أن تمدد(ترث) صف واحد فقط، ولكن يمكنك أن تنفذ عدّة واجهات . على سبيل المثال: public class ExampleClass extends ExampleClassParent implements InterfaceOne, InterfaceTwo { @Override public void InterfaceOneMethod() { } @Override public void InterfaceTwoMethod() { } } الصفوف التجريدية صيغة التصريح عن صف تجريدي: // <access-level> abstract class <abstract-class-name> extends // <super-abstract-classes> { // // Constants and variables // // Method declarations // } لا يمكن تشكيل كائنات من الصفوف المجردة (تمثيل الصفوف المجردة) يمكن للصفوف المجردة أن تعرف عن دوال مجرّدة. المناهج المجردة لا تحوي على جسم ويجب تعليمها بالكلمة المفتاحية abstract . يجب على الصفوف الأبناء غير المجردة أن تعيد كتابة Override@ جميع الدوال المجردة في صفوفها العليا. من الممكن أن تكون الصفوف المجرّدة مفيدة عند الجمع بين منطق التكرار و السلوك المخصص، ولكن بما أن الصف المجرد يتطلب الوراثة، فإن الصفوف المجردة تنتهك منطق "التركيب عبر الوراثة". لذلك خُذ بعين الاعتبار الطرق الأخرى التي تستخدم هذا التركيب public abstract class Animal { private int age; public abstract void makeSound(); من الممكن أن يحوي المنهج جسم public void eat() { System.out.println("I am an animal and I am Eating."); //(private)ملاحظة: من الممكن هنا الوصول للمتغيرات الخاصة age = 30; } public void printAge() { System.out.println(age); } يمكن أن تحوي الصفوف المجردة على دالة رئيسية public static void main(String[] args) { System.out.println("I am abstract"); } } class Dog extends Animal { لاحظ بأنه لا يزال هناك حاجة لإعادة كتابة الدوال التجريدية في الصف التجريدي // @Override public void makeSound() { System.out.println("Bark"); // age = 30; ==> ERROR! // Animal خاص بالنسبة للصف age خطأ- و ذلك لأن المتغير } ملاحظة: من الممكن أن تحصل على خطأ إذا قمت هنا باستخدام Override@ لأن جافا لا تسمح لك بإعادة كتابة الدوال الساكنة. ما يحدث هنا يُدعى إخفاء الدالة METHOD HIDING public static void main(String[] args) { Dog pluto = new Dog(); pluto.makeSound(); pluto.eat(); pluto.printAge(); } الصفوف النهائية final class صيغة التصريح عن الصفوف النهائية // <access-level> final <final-class-name> { // // Constants and variables // // Method declarations // } الصفوف النهائية هي عبارة عن الصفوف التي لا يمكن وراثتها، (أي هي الولد النهائي). بطريقة ما، تُعد الصفوف النهائية معاكس الصفوف المجردة. لأنه يجب توسيع (وراثة) الصفوف المجردة، أما الصفوف النهائية لا يمكن توسيعها. public final class SaberToothedCat extends Animal { لاحظ بأنه ما يزال يوجد حاجة لإعادة كتابة الدوال المجردة في الصف المجرد.// @Override public void makeSound() { System.out.println("Roar"); } } الدوال النهائية public abstract class Mammal() { // صيغة الدالة النهائية: // <access modifier> final <return type> <function name>(<args>) // لا يمكن إعادة كتابة الدوال النهائية بواسطة صف ابن، // وبالتالي الدالة النهائية هو أخر تنفيذ للدالة . public final boolean isWarmBlooded() { return true; } } النوع Enum еnum هي طريقة تنظيمية للثوابت (Constants) في الكود بحيث تجمع الثوابت التي لها علاقة ببعضها تحت فئة واحدة بطريقة تنظم الوصول إليها .بما أن هذه القيم هي عبارة عن ثوابت، لذلك فإن أسماء الحقول من النوع enum يجب أن تكتب بحروف كبيرة. في لغة جافا يتم تعريف هذا النوع باستخدام الكلمة المفتاحية enum. لتعريف متحول عن يوم من أيام الأسبوع، باستخدام النوع enum : public enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY } من الممكن استخدام متغيرنا Day من النوع enum كما يلي: public class EnumTest { //Enumالمتغير من النوع Day day; public EnumTest(Day day) { this.day = day; } public void tellItLikeItIs() { switch (day) { case MONDAY: System.out.println("Mondays are bad."); break; case FRIDAY: System.out.println("Fridays are better."); break; case SATURDAY: case SUNDAY: System.out.println("Weekends are best."); break; default: System.out.println("Midweek days are so-so."); break; } } public static void main(String[] args) { EnumTest firstDay = new EnumTest(Day.MONDAY); firstDay.tellItLikeItIs(); // => Mondays are bad. EnumTest thirdDay = new EnumTest(Day.WEDNESDAY); thirdDay.tellItLikeItIs(); // => Midweek days are so-so. } } الأنواع enum هي فعالة بشكل أكبر مما تم توضيحه فوق. من الممكن أن يحتوي جسم المتغير enum على دوال و حقول أخرى. ترجمة -وبتصرّف- للمقال Learn Java in Y Minutes