let في JavaScript

من موسوعة حسوب
مراجعة 04:19، 17 يناير 2018 بواسطة عبد اللطيف ايمش (نقاش | مساهمات)
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)

تعبير let يُصرِّح عن متغير محلي للقسم الكتلي، ويمكن تهيئة قيمته الابتدائية اختياريًا.

البنية العامة

let var1 [= value1] [, var2 [= value2]] [, ..., varN [= valueN]];

varnameN

اسم المتغير، ويمكن أن يكون أيّ معرِّف صالح في JavaScript.

valueN

القيمة الابتدائية للمتغير، ويمكن استخدام أيّ تعبير (expression) صالح في JavaScript.

الوصف

التعبير let يسمح بالتصريح عن متغيرات يكون مجالها (scope) محدودًا إلى القسم الكتلي (block statement)، أو إلى التعبير (expression) الذي اُستخدِمَ فيه؛ وهو على النقيض من الكلمة المحجوزة var التي تُعرِّف متغيرًا عامًا، أو محليًا إلى الدالة الحالة بغض النظر عن مجال القسم الكتلي (block scope).

قواعد المجالات

المتغيرات المُصرَّح عنها عبر let سيكون مجالها هو القسم الكتلي الذي عُرِّفَت فيه إضافةً إلى أيّة أقسام فرعية محتواة داخله؛ وهذا يعني أنَّ المتغيرات المُصرَّح عنها عبر let تشبه كثيرًا var؛ لكن الاختلاف الرئيسي بينهما هو أنَّ مجال متغيرات var سيكون كامل الدالة التي تحتويها:

function varTest() {
  var x = 1;
  if (true) {
    var x = 2;  // المتغير نفسه
    console.log(x);  // 2
  }
  console.log(x);  // 2
}

function letTest() {
  let x = 1;
  if (true) {
    let x = 2;  // متغير مختلف
    console.log(x);  // 2
  }
  console.log(x);  // 1
}

شيفرة أوضح في الدوال الداخلية

تسمح المتغيرات المُعرَّفة عبر let بإنشاء شيفرة أكثر وضوحًا عند استخدام الدوال الداخلية (inner functions):

var list = document.getElementById('list');

for (let i = 1; i <= 5; i++) {
  let item = document.createElement('li');
  item.appendChild(document.createTextNode('Item ' + i));

  item.onclick = function(ev) {
    console.log('Item ' + i + ' is clicked.');
  };
  list.appendChild(item);
}

// لإنشاء نفس التأثير لكن مع استخدام var
// فعلينا إنشاء سياق مختلف
// باستخدام تعبير مغلق للحفاظ على القيمة closure
for (var i = 1; i <= 5; i++) {
  var item = document.createElement('li');
  item.appendChild(document.createTextNode('Item ' + i));

  (function(i){
    item.onclick = function(ev) {
      console.log('Item ' + i + ' is clicked.');
    };
  })(i);
  list.appendChild(item);
}

عمِلَ المثال السابق كما ينبغي لأن النسخ الخمس من الدالة الداخلية المجهولة (anonymous inner function) تُشير إلى خمس نسخ مختلفة من المتغير i، لاحظ أنَّ المثال السابق لن يعمل إذا وضعت var بدلًا من let لأنَّ جميع الدوال الداخلية ستُعيد آخر قيمة للمتغير i وهي 6؛ لاحظ أننا أبقينا المجال حول حلقة التكرار واضحًا بنقلنا لجميع الشيفرات التي تُنشِئ العناصر الجديدة إلى مجال كل تكرار. يجدر بالذكر أنَّ let (على النقيض من var) لن تُنشِئ خاصيةً في الكائن العام (global object)، مثلًا:

var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined

محاكاة المتغيرات الخاصة

عند التعامل مع الدوال البانية، فمن الممكن استخدام let لمشاركة عنصر أو أكثر دون استخدام التعابير المغلقة (closures):

var Thing;

{
  let privateScope = new WeakMap();
  let counter = 0;

  Thing = function() {
    this.someProperty = 'foo';
    
    privateScope.set(this, {
      hidden: ++counter,
    });
  };

  Thing.prototype.showPublic = function() {
    return this.someProperty;
  };

  Thing.prototype.showPrivate = function() {
    return privateScope.get(this).hidden;
  };
}

console.log(typeof privateScope);
// "undefined"

var thing = new Thing();

console.log(thing);
// Thing {someProperty: "foo"}

thing.showPublic();
// "foo"

thing.showPrivate();
// 1

المناطق الميتة زمنيًا والأخطاء مع let

إعادة التصريح عن نفس المتغير ضمن الدالة أو القسم نفسه سيؤدي إلى SyntaxError:

if (x) {
  let foo;
  let foo; // SyntaxError
}

في ECMAScript 2015 لن تنتقل التصريحات عن المتغيرات عبر let إلى أعلى سياق التنفيذ الحالي (current execution context)، والإشارة إلى المتغيرات في القسم الكتلي قبل تهيئتها سيؤدي إلى حدوث ReferenceError (على النقيض من المتغيرات المُصرَّح عنها عبر var، والتي ستملك القيمة undefined). أي أنَّ المتغير سيكون في «منطقة ميتة زمنيًا» (temporal dead zone) بدءًا من بداية القسم الكتلي حتى تتم تهيئة المتغير.

function do_something() {
  console.log(bar); // undefined
  console.log(foo); // ReferenceError
  var bar = 1;
  let foo = 2;
}

قد تواجه أخطاء في تعابير switch لأنَّها تُشكِّل قسمًا كتليًا وحيدًا:

let x = 1;
switch(x) {
  case 0:
    let foo;
    break;
    
  case 1:
    let foo; // SyntaxError بسبب إعادة التصريح
    break;
}

لكن من المهم الإشارة إلى أنَّ الأقسام الكتلية المتشعبة ضمن قسم case ستؤدي إلى إنشاء مجال كتلي جديد، ولن تظهر هنا أخطاء تتعلق بإعادة التصريح عن المتغيرات:

let x = 1;

switch(x) {
  case 0: {
    let foo;
    break;
  }  
  case 1: {
    let foo;
    break;
  }
}

المجالات الكتلية

عند استخدام الكلمة المحجوزة let داخل كتلة (block) فسيكون مجال المتغير محدودًا إلى تلك الكتلة، لاحظ الاختلافات بينها وبين var التي يكون مجالها هو كامل الدالة التي عُرِّفَت فيها:

var a = 1;
var b = 2;

if (a === 1) {
  var a = 11; // المجال عام
  let b = 22; // المجال هو داخل التعبير الشرطي فقط

  console.log(a);  // 11
  console.log(b);  // 22
} 

console.log(a); // 11
console.log(b); // 2

دعم المتصفحات

الميزة Chrome Firefox Internet Explorer Opera Safari
الدعم الأساسي 41 44 11 17 10

مصادر ومواصفات