وضع Netscape’s Brendan Eich أسس جافا سكريبت سنة 1995. وكان الغرض منها أن تكون لغة ترميز سهلة خاصة بالمواقع ومكملة للجافا في تطبيقات الويب المعقدة، ولكن سهولة دمج جافا سكريبت والدعم الذاتي لها مع المتصفحات جعلها أكثر شيوعًا من لغة الجافا الأصلية في واجهات الويب.
لا يقتصر استخدام جافا سكريبت مقتصرة على المتصفحات، ف Node.js مشروع قائم بذاته ويقدم إمكانية بناء تطبيقات إنترنت قائمة بذاتها.
صيغة الشفرة البرمجية الخاصة بجافا سكريبت شبيهة بطريقة كتابة لغة C، فإذا كنت قد تعاملت مع لغة البرمجة C قبل ذلك أو جافا، ستكون الكثير من الأساسيات مألوفة لك. على الرغم من ذلك، وعلى الرغم من سهولة الاسم، إلا أن النموذج الكائني في جافا سكريبت مختلف تماماً عن الموجود في الجافا.
سنتناول في هذا المقال المواضيع التالية:
- التعليقات.
- الأرقام، النصوص والعمليات.
- المتغيرات، المصفوفات والكائنات.
- جمل التحكم والمنطق.
- الدوال، نطاق الوصول و Closures.
- المشيّدات Constructors والنماذج الأولية Prototypes
التعليقات
لكتابة تعليق من سطر واحد نبدأ السطر بعلامتي / كما في السطر التالي:
// Single-line comments start with two slashes.
لكتابة تعليق من أكثر من سطر، نستخدم /* و */ في إحاطة الأسطر التي نريدها كما في الأسطر التالية:
/* Multiline comments start with slash-star،
and end with star-slash */
تنتهي الجمل في جافا سكريبت بفاصلة منقوطة، ولكن هذا الأمر غير ضروري، حيث يتم إضافة الفاصلة المنقوطة تلقائيا عند وجود سطر جديد وعدم وجود الفاصلة، وهذا الأمر مستثنى في بعض الحالات:
doStuff();
بدون فاصلة:
doStuff()
سنعتمد في هذا الدرس استخدام الفاصلة المنقوطة.
الأرقام، النصوص والعمليات
تحتوي جافا سكريبت على نوع رقمي واحد (64-bit IEEE 754 double).
الأرقام من نوع Double (الأعداد الحقيقة) تحتوي على 52 بت من الأساس العشري، بما يكفي لتخزين الأعداد الصحيحة Integers حتى 9✕10¹⁵ بدقة.
3; // = 3
1.5; // = 1.5
بعض العمليات الحسابية الأساسية:
1 + 1; // = 2
0.1 + 0.2; // = 0.30000000000000004
8 - 1; // = 7
10 * 2; // = 20
35 / 5; // = 7
5 / 2; // = 2.5
10 % 2; // = 0
30 % 4; // = 2
18.5 % 7; // = 4.5
العمليات الثنائية متاحة أيضا، فعند إجراءك لعملية ثنائية، فإن الأعداد العشرية Float يتم تحويله إلى أعداد طبيعية Int حتى 32 بت:
1 << 2; // = 4
ترتيب العمليات يتم بواسطة استخدام الأقواس:
(1 + 3) * 2; // = 8
توجد ثلاثة قيم أرقام غير حقيقية كالتالي:
Infinity; // ناتجة عن قسمة رقم موجب على صفر
-Infinity; // ناتجة عن قسمة رقم سالب على صفر
NaN; //تشير إلى قيمة "غير رقم"
القيم المنطقية:
true;
false;
يتم استخدام علامة التنصيص المنفردة أو المزدوجة لبناء النصوص:
'abc';
"Hello، world";
لعكس القيمة نستخدم علامة التعجب:
!true; // = false
!false; // = true
لفحص المساواة:
1 === 1; // = true
2 === 1; // = false
لفحص عدم المساواة:
1 !== 1; // = false
2 !== 1; // = true
عمليات المقارنة:
1 < 10; // = true
1 > 10; // = false
2 <= 2; // = true
2 >= 2; // = true
دمج النصوص يتم بواسطة عملية + :
"Hello " + "world!"; // = "Hello world!"
وعملية الدمج + لا تعمل فقط مع النصوص، بل مع الأرقام أيضا والتراكيب مثل المصفوفات:
"1، 2، " + 3; // = "1، 2، 3"
"Hello " + ["world"، "!"] // = "Hello world،!"
من الممكن مقارنة النصوص:
"a" < "b"; // = true
لإجراء عملية فحص المساواة باعتبار تحويل أنواع البيانات (في حالة اختلافها) نستخدم عملية = مرتين:
"5" == 5; // = true
null == undefined; // = true
في حالة استخدام = ثلاثة مرات، لا تتم عملية التحويل:
"5" === 5; // = false
null === undefined; // = false
لابد من الانتباه من التحويل التلقائي للنوع تجنبا لبعض الحالات غير المرغوبة:
13 + !0; // 14
"13" + !0; // '13true'
نستخدم charAt للوصول لمحرف Character معين في النصوص بتعيين مكانها في سلسلة المحارف:
"This is a string".charAt(0); // = 'T'
أو نستخدم substring للحصول على أجزاء أكبر من النصوص:
"Hello world".substring(0, 5); // = "Hello"
length تعتبر خاصية، لذلك لا تستخدم الأقواس في النهاية:
"Hello".length; // = 5
نستخدم null للإشارة للفارغ أو غير الموجود، بينما نستخدم undefined للإشارة لقيمة غير منشأة أو غير موجودة حاليا (مثل تعريف متغير وعدم إعطائه قيمة).
القيم false، null، undefined، NaN، 0 ،”” كلها قيم خاطئة (تستخدم كقيمة منطقية خطأ) والباقي صحيح.
المتغيرات، المصفوفات، والكائنات
يتم تعريف المتغيرات باستخدام كلمة var. جافا سكريبت ديناميكية النوع، حيث لا يجب عليك تحديد نوع المتغير عند تعريفه، ولإعطاء قيمة للمتغير نستخدم = كما في المثال التالي:
var someVar = 5;
عند عدم استخدام كلمة var لن يظهر لك خطأ، ولكن في هذه الحالة فإن المتغير يكون مستواه على نطاق الوصول العام Global scope ولن يكون على المستوى الذي تم تعريفه فقط.
someOtherVar = 10;
المتغيرات التي لا تأخذ قيمة عند تعريفها تكون بقيمة undefined تلقائيا:
var someThirdVar; // = undefined
لتعريف أكثر من متغير في نفس السطر نفصل بين المتغيرات بفاصلة عادية:
var someFourthVar = 2، someFifthVar = 4;
نستطيع اختصار كتابة العمليات الحسابية بالشكل التالي:
someVar += 5; // هذا يعادل someVar = someVar + 5; someVar is 10 now
someVar *= 10; // someVar = 100
someVar++; // someVar = 101
someVar--; // back = 100
المصفوفات عبارة عن قائمة من القيم المرتبة من أي نوع:
var myArray = ["Hello"، 45، true];
نستطيع الوصول لمحتويات المصفوفة باستخدام الأقواس المعكوفة والفهرس.
فهرس المصفوفات يبدأ من صفر:
myArray[1]; // = 45
المصفوفات غير ثابتة وذات حجم متغير:
myArray.push("World");
myArray.length; // = 4
للتعديل أو الإضافة في موقع معين في المصفوفة:
myArray[3] = "Hello";
لتعريف قاموس (Hash):
var myObj = {key1: "Hello"، key2: "World"};
المفاتيح في القاموس عبارة عن نص، أو مُعرف صحيح، والقيم تأخذ أي نوع:
var myObj = {myKey: "myValue"، "my other key": 4};
للوصول إلى قيمة باستخدام مفتاح والأقواس المعكوفة:
myObj["my other key"]; // = 4
أو باستخدام صيغة النقطة والمُعِرف الذي يمثل المفتاح:
myObj.myKey; // = "myValue"
الكائنات في جافا سكريبت غير ثابتة وقابلة للتعديل:
myObj.myThirdKey = true;
إذا حاولت الوصول لقيمة غير موجودة في القاموس، ستكون النتيجة المرجعة undefined:
myObj.myFourthKey; // = undefined
جمل التحكم والمنطق
جملة if:
var count = 1;
if (count == 3){
// ستُنفَّذ هذه الشفرةإذا كانت قيمة المتغير تساوي 3
} else if (count == 4){
// ستُنفَّذ هذه الشفرةإذا كانت قيمة المتغير تساوي 4
} else {
// ستُنفَّذ هذه الشفرة في حالة عدم تحقق أي شرط سابق
}
جملة while:
while (true){
// جملة تكرار غير منتهية
}
جملة do تشبه جملة while إلا أنها تُكرَّر مرة واحدة على الأقل:
var input;
do {
input = getInput();
} while (!isValid(input))
جملة For تشبه الموجودة في لغة سي وجافا:
for (var i = 0; i < 5; i++){
// ستُنفَّذ هذه الشفرة خمس مرات
}
توقف جملة التكرار باستخدام break مع تحديد اسم جملة التكرار التي نريد وقفها:
outer:
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
if (i == 5 && j ==5) {
break outer;
}
}
}
تسمح لنا جملة for/in بالمرور على خصائص ومحتويات الكائن. في المثال التالي نقوم بالمرور على محتوى قاموس وحفظ النتيجة في متغير:
var description = "";
var person = {fname:"Paul"، lname:"Ken"، age:18};
for (var x in person){
description += person[x] + " ";
} // description = 'Paul Ken 18 '
العملية المنطقية and تُمَثَلْ ب && والعملية or تُمَثَلْ ب ||:
if (house.size == "big" && house.colour == "blue"){
house.contains = "bear";
}
if (colour == "red" || colour == "blue"){
// colour is either red or blue
}
نستفيد من && و || في تحديد القيم التلقائية كما في المثال التالي:
var name = otherName || "default";
جملة switch تفحص المساواة باستخدام ===، استخدم break بعد كل حالة فحص وإلا سيتم تنفيذ حالة case الصحيحة التالية أيضا:
grade = 'B';
switch (grade) {
case 'A':
console.log("Great job");
break;
case 'B':
console.log("OK job");
break;
case 'C':
console.log("You can do better");
break;
default:
console.log("Oy vey");
break;
}
الدوال، نطاق الوصول وClosures
تُعرَّف الدوال في جافا سكريبت باستخدام كلمة function:
function myFunction(thing){
return thing.toUpperCase();
}
myFunction("foo"); // = "FOO"
لابد الانتباه أن تضع القيمة المرجعة في نفس السطر الموجودة به كلمة return، إذا لم يكن كذلك، ستكون النتيجة المرجعة undefined بسبب الإضافة التلقائية للفاصلة المنقوطة عند كل سطر جديد (وقد نوهنا لهذه النقطة في البداية).
function myFunction(){
return // الفاصلة المنقوطة مضافة تلقائيا هنا
{thisIsAn: 'object literal'}
}
myFunction(); // = undefined
يُتعامل مع الدوال في جافا سكريبت بوصفها كائنات، وهذا يعني أنك تستطيع تمرير الدالة معاملا لدالة أخرى، أو قيمة لمتغير.
تُستخدَم الدالة myFunction في معالجة حدث في المثال التالي، حيث سيتم تنفيذها بعد فترة زمنية محددة:
function myFunction(){
}
// ينتُج عن السطر التالي تنفيذ الدالة أعلاه بعد 5 ثوان
setTimeout(myFunction، 5000);
ملاحظة: الدالة setTimeout ليست جزءًا من جافا سكريبت، ولكنها مقدمة من قبل المتصفحات و Node.js.
وظيفة setInterval أيضا مقدمة من قبل المتصفحات.
function myFunction(){
}
setInterval(myFunction، 5000);
ليس من الشرط تحديد اسم الدالة، ونستطيع كتابة الدالة دون اسم في المكان الذي يتم تمرير قيمتها المُرجعة فيه بالطريقة التالية:
setTimeout(function(){
}, 5000);
تمتلك جافا سكريبت نطاقاً وظيفياً، حيث لكل دالة نطاقها الخاص، بينما الكتل الأخرى لا تشاركها هذا النطاق.
if (true){
var i = 5;
}
i; // = 5
إن كتبنا الشفرة في المثال السابق داخل دالة فإن قيمة المتغير i تساوي 5 على عكس ما تتوقعه في النطاق الكتلي، بمعنى أن المتغيرات مُشاهدَة ونستطيع الوصول إليها على مستوى الدالة بغض النظر عن مكان تعريفها داخل هذه الدالة.
وهذا يشير إلى نمط متعارف عليه يمنع المتغيرات المؤقتة من الظهور في نطاق الوصول العام.
وللتوضيح على ما سبق، في المثال التالي، يبقى المتغير temporary داخل نطاق الدالة المُعرف فيها، أما المتغيرات في النطاق العام مثل permanent فنستطيع الوصول إليه باستخدام الكائن العام والمسمى في كل المتصفحات ب window وتكون صيغة الوصول للمتغير هكذا window.permanent.
الكائن ذو النطاق العام يختلف اسمه في البيئات التي لا علاقة لها بالمتصفحات مثل Node.js.
(function(){
var temporary = 5;
window.permanent = 10;
})();
temporary; // raises ReferenceError
permanent; // = 10
من أقوى خصائص لغة جافا سكريبت وجود ما يسمى بclosures ، حيث إذا كانت دالة مُعرفة داخل دالة أخرى، فإن الدالة الداخلية تمتلك الوصول لكافة المتغيرات الخاصة بالدالة الخارجية حتى بعد خروجها وانتهائها.
في المثال التالي، فإن استدعاء الدالة setTimeout سيتم تنفيذها مباشرة بعد استدعاء الدالة الخارجية sayHelloInFiveSeconds والتي ستنتهي مباشرة. ومن ثم سوف يبدأ العد حتى 5 ثوان لاستدعاء الوظيفة الداخلية، وعند انتهاء المدة، وعلى الرغم من خروج وانتهاء الدالة الخارجية، إلا أنه سيتم تنفيذ الداخلية بنجاح وسيتم الوصول للمتغير prompt دون مشاكل.
function sayHelloInFiveSeconds(name){
var prompt = "Hello، " + name + "!";
function inner(){
alert(prompt);
}
setTimeout(inner، 5000);
}
// سيتم طباعة "مرحبا أدم" بعد 5 ثواني
sayHelloInFiveSeconds("Adam");
المشيّدات Constructors والنماذج الأولية Prototypes
يمكن للكائنات أن تحتوي على دوال، كما في المثال التالي:
var myObj = {
myFunc: function(){
return "Hello world!";
}
};
myObj.myFunc(); // = "Hello world!"
عندما يتم استدعاء دوال معرَّفة في كائن، فإن هذه الدوال تستطيع الوصول للكائن التي عُرِّفت فيه باستخدام كلمة this كما في المثال التالي:
myObj = {
myString: "Hello world!"،
myFunc: function(){
return this.myString;
}
};
myObj.myFunc(); // = "Hello world!"
الدالة myFunc لا تعمل إذا لم يتم استدعاؤها في سياق الكائن الذي تتصل به، لاحظ في المثال التالي:
var myFunc = myObj.myFunc;
myFunc(); // = undefined
نستطيع ربط دالة بكائن والوصول لمتغيرات هذا الكائن بواسطة this على الرغم من أن هذه الدالة لم تُعرَّف مع تعريف بالكائن.
var myOtherFunc = function(){
return this.myString.toUpperCase();
}
myObj.myOtherFunc = myOtherFunc;
myObj.myOtherFunc(); // = "HELLO WORLD!"
نستطيع أيضا تحديد سياق الدالة لتنفيذها من خلاله وذلك عن طريق استدعاء الوظيفة باستخدام call او apply.
var anotherFunc = function(s){
return this.myString + s;
}
anotherFunc.call(myObj، " And Hello Moon!"); // = "Hello World! And Hello Moon!"
استخدمنا في المثال السابق الدالة call. تؤدّي الدالة apply نفس الغرض ولكننا نمرر لها مصفوفة معاملات، وهذا يفيدنا في حالة التعامل مع دالة تقبل مجموعة من المعاملات.
anotherFunc.apply(myObj، [" And Hello Sun!"]); // = "Hello World! And Hello Sun!"
Math.min(42، 6، 27); // = 6
Math.min([42، 6، 27]); // = NaN (uh-oh!)
Math.min.apply(Math، [42، 6، 27]); // = 6
لاحظ أننا عند استخدام apply و call قمنا بتمرير السياق الذي نريده من خلال myObj.
إذا أردنا أن نُثبت السياق الذي نريد تنفيذ الدالة من خلاله، فإننا نستخدم bind عوضا عن ذلك.
var boundFunc = anotherFunc.bind(myObj);
boundFunc(" And Hello Saturn!"); // = "Hello World! And Hello Saturn!"
نستطيع استخدام bind لتطبيق دالة جزئيا، انظر المثال التالي:
var product = function(a، b){ return a * b; }
var doubler = product.bind(this، 2);
doubler(8); // = 16
عند استدعاء دالة بواسطة الكلمة new فإن كائناً جديداً يتم إنشاؤه وسوف يكون متاحا للدالة بواسطة كلمة this. الدوال التي صُممت للاستدعاء بهذه الطريقة تسمى المشيّدات constructors.
var MyConstructor = function(){
this.myNumber = 5;
}
myNewObj = new MyConstructor(); // = {myNumber: 5}
myNewObj.myNumber; // = 5
على خلاف لغات البرمجة الكائنية الأخرى، جافا سكريبت لا تحتوي على مفهوم العيّنة Instance أو “الكائن المتولد من الفئة عند التشغيل”.
تُقدم جافا سكريبت المفاهيم الكائنية مثل التوليد والوراثة من خلال مفهوم واحد يسمى النموذج الأولي Prototype.
كل كائن في الجافا سكريبت يحتوي على نموذج أولي. عندما تقوم بمحاولة استخدام لخاصية غير موجودة في كائن معين، فإن مفسر جافا سكريبت سوف ينظر في النموذج الأولي للكائن.
تجعلك بعض تطبيقات الجافا سكريبت تصل لنموذج الكائن بواسطة الخاصية “proto“. على الرغم من أن هذه الطريقة مفيدة في شرح مفهوم النموذج الأولي، إلا أنها ليست الطريقة المعيارية لذلك، وسوف نشرح الطريقة الصحيحة لهذا الأمر لاحقا.
var myObj = {
myString: "Hello world!"
};
var myPrototype = {
meaningOfLife: 42،
myFunc: function(){
return this.myString.toLowerCase()
}
};
myObj.__proto__ = myPrototype;
myObj.meaningOfLife; // = 42
myObj.myFunc(); // = "hello world!"
في حال لم تكن الخاصية موجودة في النموذج الأولي، فإن المفسر يبحث في نموذج النموذج وهكذا.
myPrototype.__proto__ = {
myBoolean: true
};
myObj.myBoolean; // = true
لا يوجد نُسَخْ عند استخدام النموذج، حيث إن كل كائن يقوم بالتأشير للنموذج الخاص به، وهذا يعني أن أي تغيير على النموذج سوف ينعكس في كل مكان آخر.
myPrototype.meaningOfLife = 43;
myObj.meaningOfLife; // = 43
جملة for/in تسمح بالمرور على خصائص كائن، مرورا بسلسلة النماذج الأولية حتى الوصول إلى نموذج فارغ.
for (var x in myObj){
console.log(myObj[x]);
}
///prints:
// Hello world!
// 42
// [Function: myFunc]
للمرور على خصائص الكائن دون النموذج، نستخدم وظيفة hasOwnProperty كما في المثال التالي:
for (var x in myObj){
if (myObj.hasOwnProperty(x)){
console.log(myObj[x]);
}
}
///prints:
// Hello world!
كما ذكرنا سابقا، فإن استخدام “proto” في تعريف نموذج كائن هي طريقة غير معيارية، ولا يوجد طريقة لتغيير نموذج أولي لكائن موجود.
على الرغم من ذلك، توجد طريقتان لإنشاء كائن مع نموذج مُعطى.
الأولى هي استخدام Object.create:
var myObj = Object.create(myPrototype);
myObj.meaningOfLife; // = 43
الطريقة الثانية – مضمونة أكثر - باستخدام المشيّدات.
تمتلك المشيّدات خاصية تسمى prototype تُحدَّد عند إنشاء كائن جدي باستخدام كلمة new، المثال التالي يشرح هذا الأمر:
MyConstructor.prototype = {
myNumber: 5،
getMyNumber: function(){
return this.myNumber;
}
};
var myNewObj2 = new MyConstructor();
myNewObj2.getMyNumber(); // = 5
myNewObj2.myNumber = 6
myNewObj2.getMyNumber(); // = 6
توجد لدى أنواع البيانات مثل النصوص والأرقام مشيّدات تقوم بإنشاء كائنات تعادل الكائنات المنشأة بطريقة عادية. عدا أنها ليست متماثلة تماما!
var myNumber = 12;
var myNumberObj = new Number(12);
myNumber == myNumberObj; // = true
typeof myNumber; // = 'number'
typeof myNumberObj; // = 'object'
myNumber === myNumberObj; // = false
if (0){
//لن تُنفَّذ هذه الشفرة لأن قيمة الصفر خاطئة
}
if (new Number(0)){
//سوف تُنفَّذ هذه الشفرة لأن الرقم في الشرط عبارة عن كائن وليس نوع رقم، والكائنات دائما ذات قيمة منطقية صحيحة
}
الكائنات المغلفة أو العادية تتشارك في النموذج الأولي الخاص بنوعها، فمثلا، نستطيع إضافة خاصية على النموذج الخاص بنوع string بهدف الحصول على الحرف الأول من النص، كما في المثال التالي:
String.prototype.firstCharacter = function(){
return this.charAt(0);
}
"abc".firstCharacter(); // = "a"
تُستخدَم الخاصية السابقة غالباً في ما يُعرَف بالملْء المتعدّد Polyfilling والتي تُطَبِقْ مميزات أحدث من جافا سكريبت في مجموعة قديمة من نُسخ جافا سكريبت بهدف استخدام هذه المميزات الحديثة في بيئات قديمة مثل المتصفحات المنتهية تاريخا.
ملاحظة: تنفيذ Object.create قد يكون غير متاح في بعض التطبيقات، ولكننا نستطيع استخدام الملْء المتعدّد لتعويض هذا الغياب كالتالي:
if (Object.create === undefined){ //في حالة كانت موجودة لا تعدل عليها
Object.create = function(proto){
// أنشئ مشيّدًا مؤقتا باستخدام النموذج الأولي المناسب
var Constructor = function(){};
Constructor.prototype = proto;
return new Constructor();
}
}
ترجمة – بتصرّف – للمقال Learn X in Y minutes Where X=javascript.
أفضل التعليقات
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.