التصريح عن المتغيّرات في TypeScript

من موسوعة حسوب


التصريح عن المتغيّرات (Variable Declarations)

الكلمتان المفتاحيتان ‎let‎ و‎const‎ نوعان جديدان من طرق التصريح عن المتغيرات في لغة JavaScript. الكلمة المفتاحية let مُشابهة للكلمة المفتاحية var من عدّة نواحي، لكنّ let تسمح للمُطورين بتجنب بعض المشاكل التي تحدث عادةً في لغة JavaScript. أمّا const فهي طريقة لمنع إعادة تعيين القيم للمتغيرات.

ولأنّ TypeScript مجموعة عليا من JavaScript، فاللغة تدعم ‎let‎ و‎const‎. وسنشرح طرق التصريح عن المتغيرات هذه ولمَ يُفضّل استعمالها عوضًا عن ‎var‎.

إن كانت لديك خبرة قليلة في لغة JavaScript، فستفيدك الفقرة التالية لتقويم معلوماتك. أما إن كانت لك دراية شاملة بكيفية عمل تصريحات ‎var‎ في لغة JavaScript، فيُمكنك تجاهل الفقرة التالية.

تصريحات ‎‎var

لطالما صُرِّح عن المُتغيّرات في JavaScript بالكلمة المفتاحية ‎‎var‎.

var a = 10;

صرحنا في الشيفرة أعلاه عن مُتغيّر باسم ‎a‎ وعيّنا له القيمة ‎10‎.

يُمكننا كذلك التصريح عن مُتغيّر داخل دالة كما يلي:

function f() {
    var message = "Hello, world!";

    return message;
}

ويُمكننا كذلك الوصول إلى هذه المتغيرات داخل دوال أخرى كما يلي:

function f() {
    var a = 10;
    return function g() {
        var b = a + 1;
        return b;
    }
}

var g = f();
g(); // returns '11'
// يُعيد الاستدعاء أعلاه القيمة
// '11'

في المثال أعلاه، التقطت (capture) الدالة g المُتغيّر a المُعرَّف في الدالة f. عندما تُستدعى الدالة g فقيمة a ستكون مرتبطة بقيمة a داخل الدالة f. وحتى ولو استُدعيَت الدالة g في الوقت الذي تتوقف فيه الدالة f عن التنفيذ، فستتمكن رغم ذلك من الوصول إلى المتغير a وتغييره.

function f() {
    var a = 1;

    a = 2;
    var b = g();
    a = 3;

    return b;

    function g() {
        return a;
    }
}

f(); // returns '2'
// يُعيد الاستدعاء أعلاه القيمة
// '2'

قواعد المجال (Scoping rules)

قواعد المجال في تصريحات ‎var‎ غريبة وغير مألوفة لمن اعتاد على العمل بلغات برمجة أخرى. مثلًا:

function f(shouldInitialize: boolean) {
    if (shouldInitialize) {
        var x = 10;
    }

    return x;
}

f(true);  // returns '10'
f(false); // returns 'undefined'

قد يتعجّب البعض من هذا المثال، إذ صُرِّح عن المتغيّر ‎xداخل كتلة if، لكن رغم ذلك تمكّنا من الوصول إليه من خارج هذه الكتلة. هذا لأنّه من الممكن الوصول إلى تصريحات ‎var‎ من أي مكان داخل الدالة، أو الوحدة (module)، أو مجال الأسماء (namespace)، أو المجال العمومي (global scope) الذي يحتوي على الشيفرة، وذلك بغضّ النظر عن الكتلة التي تحتوي على الشيفرة. يُسمّى هذا الأمر عادةً بمجال ‎var‎ ‎(var-scoping‎)‎ أو مجال الدوال (function-scoping). والمُعاملات (Parameters) تكون في مجال الدالة كذلك.

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

function sumMatrix(matrix: number[][]) {
    var sum = 0;
    for (var i = 0; i < matrix.length; i++) {
        var currentRow = matrix[i];
        for (var i = 0; i < currentRow.length; i++) {
            sum += currentRow[i];
        }
    }

    return sum;
}

لاحظ أنّ حلقة ‎for‎ الداخلية ستكتب وتغطّي على (overwrite) قيمة المتغيّر i بدون قصد، وذلك لأنّ المتغيّر i يُشير إلى نفس المتغير الموجود داخل مجال الدالة، ما سيُخلخل طريقة عمل البرنامج. يُمكن لمثل هذه العلل (bugs) أن تتجاوز عمليات مراجعة الشيفرة (code reviews) ما سيُصعّب من عمليّة إصلاح الأخطاء.

مراوغات التقاط المتغيّرات (Variable capturing quirks)

حاول تخمين مُخرج (output) الشيفرة التالية:

for (var i = 0; i < 10; i++) {
    setTimeout(function() { console.log(i); }, 100 * i);
}

تُحاول الدالة ‎setTimeout‎ تنفيذ دالة بعد عدد معين من أجزاء من ألف جزء من الثانية milliseconds (لكنها تنتظر إلى أن تنتهي العمليات الأخرى من التنفيذ).

هذا هو المُخرج:

10
10
10
10
10
10
10
10
10
10

إن فاجأك المُخرج، فأنت لست وحيدًا، مُعظم الناس سيتوقعون المُخرج التالي:

0
1
2
3
4
5
6
7
8
9

تذكّر ما قلناه سابقًا عن التقاط المتغيرات، كل تعبير دالة نُمرّره إلى ‎setTimeout‎ سيُشير إلى نفس المتغيّر ‎i‎ داخل نفس المجال.

هذا يعني بأنّ الدالة setTimeout ستُشغّل الدالة بعد عدد من أجزاء من ألف جزء من الثانية، لكن فقط بعد الانتهاء من تنفيذ حلقة ‎for‎؛ عندما تتوقف حلقة for، فقيمة المتغيّر i تكون 10. لذا ستُطبع القيمة 10 في كل مرّة تُستدعَى فيها الدالة!

عادةً ما تُستعمل تعابير IIFE (تعبير دالة يُعتمد عليها فورًا، اختصارًا للعبارة Immediately Invoked Function Expression) لحل هذه المشكلة عبر التقاط المتغيّر i عند كل تكرار:

for (var i = 0; i < 10; i++) {
    // capture the current state of 'i'
    // by invoking a function with its current value
    // التقاط حالة المتغيّر
    // عبر الاعتماد على دالةٍ تُمرّر لها قيمته الحاليّة
    (function(i) {
        setTimeout(function() { console.log(i); }, 100 * i);
    })(i);
}

هذه الشيفرة التي تبدو غريبةً شائعةٌ في تطبيقات JavaScript. المعامل ‎i‎ الموجود في قائمة المعاملات يغطّي على المتغيّر المُصرَّح عنه في حلقة ‎for‎، ولكنّنا لم نغيّر محتويات الحلقة كثيرًا لأننا سمّيناهما بنفس الاسم.

تصريحات ‎‎let

أضيف التصريح ‎‎let‎ لحلّ المشاكل التي تُسبّبها تصريحات ‎‎var‎. وطريقة كتابة جمل let شبيهة بطريقة كتابة جمل var:

let hello = "Hello!";

الاختلاف الحقيقي موجود في طريقة العمل والدلالات (semantics)، وليس في البنية العامّة (syntax).

مجالات الكتلة (Block-scoping)

عند التصريح عن مُتغيّر باستخدام ‎let‎، فهذا يعني أنه يَستخدم ما يُصطلح عليه بالمجالات المعجميّة (lexical-scoping) أو مجالات الكتلة (block-scoping). وعلى النقيض من المتغيّرات المُصرّح عنها بالكلمة المفتاحية ‎var‎ التي تتسرّب مجالاتها إلى الدالة التي تحتوي عليها، فالمتغيّرات المصرح عنها في مجال الكتلة لا يُمكن الوصول إليها خارج أقرب كتلة محتويّة أو حلقة for.

function f(input: boolean) {
    let a = 100;

    if (input) {
        // الإشارة إلى المتغيّر
        // 'a'
        // مسموح بها
        let b = a + 1;
        return b;
    }
    // خطأ، المتغيّر
    // 'b'
    // غير موجود هنا، لأننا عرفناه في كتلة الشرط
    return b;
}

هنا لدينا متغيّران محليّان، المتغيّر ‎a‎ والمتغيّر ‎b‎. مجال ‎a‎ محدود في جسم الدالة ‎f‎ ومجال ‎b‎ محدود في كتلة جملة ‎if‎ المحتوية.

تملك المتغيرات المُصرّح عنها في كتلة catch قواعدَ مجالات مُشابهة كذلك:

try {
    throw "oh no!";
}
catch (e) {
    console.log("Oh well.");
}
// خطأ، المتغيّر
// 'e'
// غير موجود هنا
console.log(e);

لا يُمكن قراءة المتغيّرات (الحصول على قيمتها) أو الكتابة إليها (إسناد قيمة لها) قبل التصريح عنها، وهذه من مزايا المتغيّرات المحدودة في مجالات الكتل. ورغم أن هذه المتغيرات موجودة في كامل مجالها، لكن جميع الأماكن التي تسبق مكان التصريح عنها تُعدّ جزءًا من نطاق الموت المؤقّت (temporal dead zone) الخاص بها. وهذه العبارة تعني ببساطة بأنك لا تستطيع الوصول إلى المتغيّرات قبل جملة ‎let‎ التي تُصرِّح عنها، ولحسن الحظّ، ستخبرك TypeScript بذلك لتجنّب الأخطاء.

a++; // لا يجوز استعمال المتغير قبل التصريح عنه
let a;

ملحوظة: لا يزال بإمكانك التقاط متغيّر مجال كتلة قبل التصريح عنه. لكن لا يجوز استدعاء الدالة المحتوية قبل التصريح عن المتغيّر. إن كنت تعتمد على النسخة ES2015، فسيرمي زمن التّنفيذ (runtime) استثناءً؛ لكن رغم ذلك، تسمح TypeScript حاليًّا بذلك ولن تُخبرك بأنه خطأ.

function foo() {
    // التقاط المتغيّر
    // 'a'
    // مسموح به هنا
    return a;
}

// لا يجوز استدعاء الدالة قبل التصريح عن المتغيّر
// سيُرمى خطأ في زمن التّنفيذ هنا
foo();

let a;

للاستزادة حول نطاق الموت المؤقّت، انظر هذه الصّفحة.

إعادة التصريحات (Re-declarations) والتغطية (Shadowing)

عند استخدام تصريحات ‎var‎، يُمكن إعادة التصريح عن المتغيّر بعدد لا نهائي من المرّات؛ وستحصل في النهاية على متغيّر واحد فقط:

function f(x) {
    var x;
    var x;

    if (true) {
        var x;
    }
}

في الشيفرة أعلاه، جميع التصريحات عن ‎x‎ تُشير إلى نفس المتغيّر x، وهذا صحيح وسليم. لكنّه ينتهي أحيانًا كمصدر للعلل. لكن تصريحات ‎let‎ تحلّ هذه المشاكل، إذ لا تكون مسامحة بنفس الشّكل:

let x = 10;
let x = 20; // خطأ، لا يمكن إعادة التصريح عن المتغيّر في نفس المجال

ليس من الضروري أن يكون كلا المتغيرين محدودي المجال في الكتلة لتُخبرنا TypeScript بأنّ هناك مشكلة ما.

function f(x) {
    let x = 100; // خطأ، هذا يتعارض مع التصريح عن معامل الدالة
}

function g() {
    let x = 100;
    var x = 100; // خطأ، لا يُمكن التصريح عن المتغيّر بكلا الطريقتين
}

هذا لا يعني بأنّه لا يمكن التصريح عن متغيّرات مجال الكتلة (block-scoped variable) مع متغيّر مجال دالّة (function-scoped variable). إذ يحتاج متغيّر مجال الكتلة فقط إلى التصريح عنه داخل كتلة مختلفة اختلافًا واضحًا.

function f(condition, x) {
    if (condition) {
        let x = 100;
        return x;
    }

    return x;
}

f(false, 0); // returns '0'
f(true, 0);  // returns '100'

إقحام اسم جديد في مجال داخليّ يُسمّى بالتغطيّة (shadowing). وهو سلاح ذو حديّن نوعًا ما، لأنّه قد يجلب عللًا أخرى في حالة التغطيّة بغير قصد، في نفس الوقت الذي يحمي فيه من علل أخرى كذلك. على سبيل المثال، تخيّل لو كتبنا الدالة ‎sumMatrix‎ السابقة باستخدام متغيّرات ‎let‎.

function sumMatrix(matrix: number[][]) {
    let sum = 0;
    for (let i = 0; i < matrix.length; i++) {
        var currentRow = matrix[i];
        for (let i = 0; i < currentRow.length; i++) {
            sum += currentRow[i];
        }
    }

    return sum;
}

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

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

التقاط متغيّرات مجالات الكتلة (Block-scoped variable capturing)

عندما تطرّقنا إلى فكرة التقاط المتغيرات عند استعمال تصريحات ‎var‎، ألقينا نظرة على كيفية تصرّف المتغيّرات عندما تُلتقط. لتوضيح الفكرة أكثر، ضع في ذهنك أنّه في كل مرة يُنفّذ فيها مجال مُعيّن، سيقوم بإنشاء "بيئة” من المتغيرات. يُمكن لهذه البيئة والمتغيرات الملتقطة فيها أن تكون متوفّرة حتى بعد انتهاء تنفيذ كل شيء موجود في مجالها.

function theCityThatAlwaysSleeps() {
    let getCity;

    if (true) {
        let city = "Seattle";
        getCity = function() {
            return city;
        }
    }

    return getCity();
}

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

تذكّر مثال ‎setTimeout‎ السابق، الذي احتجنا فيه تعبير IIFE لالتقاط حالة المتغيّر لكل تكرار في حلقة ‎for‎. إذ كنّا نُنشئُ بيئة متغيّرات جديدة للمتغيرات الملتقطة الخاصّة بنا. وكان ذلك معقدًا نوعًا ما، لكن ولحسن الحظ، لن تحتاج إلى القيام بذلك أبدًا في TypeScript.

تمتلك تصريحات ‎let‎ آلية عمل مختلفة تمامًا عندما تُستعمل داخل حلقات التكرار. فعوضًا عن إنشاء بيئة جديدة في الحلقة، تُنشئُ التصريحات مجالًا جديدًا لكل تكرار. ولأن هذا هو الهدف من تعبير IIFE الذي استعملناه، فنستطيع تغيير مثال ‎setTimeout‎ السابق لاستعمال تصريحات let فقط.

for (let i = 0; i < 10 ; i++) {
    setTimeout(function() { console.log(i); }, 100 * i);
}

والمخرج هذه المرّة سيكون كما هو متوقّع:

0
1
2
3
4
5
6
7
8
9

تصريحات ‎‎const

تصريحات ‎‎const‎ طريقةٌ أخرى للتصريح عن المتغيّرات.

const numLivesForCat = 9;

وهي مشابهة لتصريحات ‎let‎‎، لكن وكما يُشير اسم التصريح (من constant، أي ثابت)، فقيم المتغيرات المصرح عنها لا تكون قابلة للتغيير بعد ربطها. بعبارة أخرى، تصريحات ‎ const‎تتبع نفس قواعد المجال التي تتبعها تصريحات const‎، لكنّك لا تستطيع إعادة تعيين قيمة لها.

هذا لا يعني بأن القيم التي تشير إليها المتغيرات غير قابلة للتغيير (immutable):

const numLivesForCat = 9;
const kitty = {
    name: "Aurora",
    numLives: numLivesForCat,
}

// خطأ، لا يمكن تغيير القيمة إلى قيمة أخرى
kitty = {
    name: "Danielle",
    numLives: numLivesForCat
};

// لا بأس بتغيير محتويات القيمة التي يشير إليها المتغيّر
kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;

لاحظ أنّه لا يزال بإمكانك تغيير الحالة الداخلية لمتغيّرات ‎const‎، إلا إن اتّخذت إجراءات خاصّة لمنع ذلك.

ولحسن الحظ، تسمح لك TypeScript بتحديد عناصر من كائن معيّن لتكون قابلة للقراءة فقط (readonly). يُمكنك الاطلاع على صفحة الواجهات للمزيد من التفاصيل.

الفرق بين تصريحات ‎‎let‎ وتصريحات ‎‎const

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

بتطبيق مبدأ الامتيازات الدنيا (principle of least privilege)، يجب على جميع التصريحات التي لا تُخطّط لتغييرها أن تستخدم ‎‎const‎‎‎. إن لم يكن متغيّر ما بحاجة إلى تعيين قيمة جديدة له، فمن يعمل على الشيفرة لا يجب أن يتمكن من فعل ذلك تلقائيًّا، وسيُفكّر من يريد تغيير قيمة المتغيّر في الأمر مليًّا قبل فعله. استعمال ‎const‎ يجعل كذلك من قراءة وشرح الشيفرة وتحليل تدفق البيانات سهلًا.

فكّر مليًّا في الأمر، وإن كنت تعمل ضمن فريق فاستشر زملاءك.

يستخدم هذا التوثيق تصريحات ‎let‎ في معظم الأحيان.

التفكيك (Destructuring)

التفكيك من المزايا الجديدة في ECMAScript 2015 المتوفّرة في TypeScript. لمرجع كامل حول التفكيك، انظر هذه الصّفحة.

تفكيك المصفوفات (Array destructuring)

تفكيك المصفوفات وإسناد عناصرها إلى عدّة متغيّرات من أبسط أشكال التفكيك:

let input = [1, 2];
let [first, second] = input;
console.log(first); // 1
console.log(second); // 2

يُنشئ هذا متغيرين جديدين باسم ‎first‎ و‎second‎. هذا مكافئ لاستخدام الفهرسة (indexing)، لكنّه أكثر وضوحًا وبساطة من الفهرسة:

first = input[0];
second = input[1];

يعمل التفكيك مع المتغيرات المصرح عنها مسبقًا كذلك:

// مبادلة المتغيرات، بحيث يحمل كل واحد قيمة الآخر
[first, second] = [second, first];

ويمكن استعماله كذلك مع معاملات الدوال:

function f([first, second]: [number, number]) {
    console.log(first);
    console.log(second);
}
f([1, 2]);

يمكنك إنشاء متغيّر يحمل بقيّة عناصر القائمة باستخدام البنية ‎...‎ كما يلي:

let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1
console.log(rest); // [ 2, 3, 4 ]

ولأننا نتعامل مع لغة JavaScript، فيُمكنك كذلك تجاهل العناصر المتبقيّة غير المرغوبة:

let [first] = [1, 2, 3, 4];
console.log(first); // 1

أو تجاهل عناصر أخرى:

let [, second, , fourth] = [1, 2, 3, 4];

تفكيك الكائنات (Object destructuring)

يُمكنك تفكيك الكائنات كذلك:

let o = {
    a: "foo",
    b: 12,
    c: "bar"
};
let { a, b } = o;

يُنشئ هذا متغيّرين جديدين، ‎a‎ و‎b‎ من ‎o.a‎ و‎o.b‎. لاحظ أنّك تستطيع تخطي ‎c‎ إن لم تحتج إليه.

ومثل تفكيك المصفوفات، يُمكنك كذلك التعيين دون التصريح عن المتغيرات:

({ a, b } = { a: "baz", b: 101 });

لاحظ أنّه توجّب علينا إحاطة هذه الجملة بقوسين. وهذا لأنّ JavaScript تعتبر الرمز ‎{‎ على أنّه بداية كتلة (block).

يمكنك إنشاء متغيّر يحمل بقيّة عناصر الكائن باستخدام البنية ‎...‎ كما يلي:

let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;

إعادة تسمية الخاصيات (Property renaming)

يُمكنك تسميّة الخاصيات كذلك:

let { a: newName1, b: newName2 } = o;

البنية العامة هنا معقدة قليلًا، يُمكنك قراءة المقطع ‎a: newName1‎ وكأنّك تقول: خذ قيمة ‎a‎ من الكائن وضَعها في المتغيّر ‎newName1‎. الاتجاه يكون من اليسار إلى اليمين، وكأنّك كتبت ما يلي:

let newName1 = o.a;
let newName2 = o.b;

ما يجعل الأمر مُشوّشًا ومُحيّرًا هنا هو أن النقطتان (:) لا تُعبّران عن النوع. إن حُدِّد النوع يجب أن يُحدَّد بعد كامل التفكيك كما يلي:

let { a, b }: { a: string, b: number } = o;

القيم الافتراضيّة (Default values)

تسمح القيم الافتراضية بتحديد قيمة افتراضيّة لخاصيةٍ في حالة لم تُعرَّف:

function keepWholeObject(wholeObject: { a: string, b?: number }) {
    let { a, b = 1001 } = wholeObject;
}

في هذا المثال، أصبحت الدالة ‎keepWholeObject‎ تمتلك متغيّرًا ‎wholeObject‎ إضافة إلى الخاصيتين ‎a‎ و‎b‎، حتى ولو لم تُعرّف الخاصيّة ‎b‎. إذ ستكون قيمتها الافتراضيّة العدد ‎1001‎.

التصريح عن الدوال (Function declarations)

يعمل التفكيك عند التصريح عن الدوال كذلك. وهذا واضح في الحالات البسيطة:

type C = { a: string, b?: number }
function f({ a, b }: C): void {
    // ...
}

تحديد القيم الافتراضية للمعاملات شائع جدًّا، وجمعه مع التفكيك قد يكون صعبًا. أولًا، عليك أن تتذكر تحديد النمط (pattern) قبل القيمة الافتراضيّة:

function f({ a, b } = { a: "", b: 0 }): void {
    // ...
}
f();
// القيمة الافتراضية هي
// { a: "", b: 0 }

ملاحظة: المقطع أعلاه مثال على استنتاج الأنواع (type inference).

بعد ذلك ستحتاج إلى أن تتذكّر تحديد القيم الافتراضية للخاصيات الاختيارية على الخاصيّة المُفكّكة عوضًا عن الجزء الرئيسيّ, تذكّر بأنّ ‎C‎ قد عُرّف مع ‎b‎ كخاصية اختيارية:

function f({ a, b = 0 } = { a: "" }): void {
    // ...
}
// القيمة الافتراضية هي
// b = 0
f({ a: "yes" });

// القيمة الافتراضية الأولى هي
// { a: "" }
// ما يُؤدي إلى القيمة الافتراضيّة
// b = 0
f();

// خطأ لأنّ 
// 'a'
// خاصيّة مطلوبة عندما نُمرّر قيمة للمعامل
f({});

استعمل التفكيك بحذر. فكما يوضح المثال أعلاه، يُمكن لتعابير التفكيك أن تتحول إلى شيفرة مُعقّدة بسهولة. والتفكيك المتداخل يزيد من هذا التعقيد، ما قد يجعل فهم الشيفرة أمرًا صعبًا وشاقًّا حتى دون استعمال إعادة التسميّة (renaming) والقيم الافتراضيّة (default values) وحواشي الأنواع (type annotations). حاول إبقاء تعابير التفكيك صغيرةً وبسيطة. إذ يُمكنك دائمًا كتابة التعيينات التي سيُولّدها التفكيك بنفسك، ما سيُسهّل قراءة وفهم الشيفرة.

معامل النشر Spread

معامل النشر (spread operator) هو المقابل للتفكيك. إذ يسمح بنشر مصفوفة إلى مصفوفة أخرى، أو كائن إلى كائن آخر. على سبيل المثال:

let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];

يمنح هذا القيمةَ ‎[0, 1, 2, 3, 4, 5]‎ للمصفوفة ‎bothPlus‎. يقوم النشر بنسخ سطحي لكل من first وsecond، لذا فالنشر لا يُغيّرهما.

يُمكنك نشر الكائنات (objects) كذلك:

let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { ...defaults, food: "rich" };

بهذا يُصبح الكائن ‎search‎ كما يلي:

{ food: "rich", price: "$$", ambiance: "noisy" }

نشر الكائنات أعقد من نشر المصفوفات. وكما في نشر المصفوفات، فالعملية تجري من اليسار إلى اليمين، لكنّ النتيجة تبقى كائنًا. هذا يعني بأن الخاصيات التي تكون في آخر كائن النشر تكتب وتغطّي على (overwrite) الخاصيات التي تكون في بدايته. لذا لو عدّلنا المثال أعلاه ونشرنا في آخر الكائن:

let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { food: "rich", ...defaults };

فستكتب الخاصية ‎food‎ الموجودة في الكائن ‎defaults‎ على الخاصية ‎‎food: "rich"‎‎، وهذه النتيجة غير مرغوبة في هذه الحالة، لذا خذ حذرك عند نشر الكائنات.

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

class C {
  p = 12;
  m() {
  }
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // خطأ، التابع لا ينضم إلى النسخة الجديدة المنشأة عن طريق النشر

ثانيًّا، لا يسمح مترجم (compiler) لغة Typescript بنشر معاملات الأنواع (type parameters) من الدوال العموميّة (generic functions). لكن من المتوقع أن تتاح هذه الميزة في نسخ مستقبلية من اللغة.

مصادر