إليك بعض الميزات الجديدة في JavaScript

تم التحديث: ٥ مايو ٢٠٢٦

Here are Some new Features in Javascript

ملخص

قدمت إصدارات ES2024 و ES2025 إضافات جوهرية: Promise.withResolvers()، و Object.groupBy / Map.groupBy، وعمليات نظرية المجموعات (union، intersection، difference)، ومساعدات المكررات (Iterator Helpers) (مثل .map الكسول، و .filter، و .take، و .toArray على المكررات) والتي تم شحنها جميعًا إلى كل المتصفحات الحديثة الحالية. يستعرض هذا المنشور ما تم توحيده اليوم، وما لا يزال في الطريق، والمقترحات التي تم سحبها منذ مسودة عام 2023 الأصلية.

تطور JavaScript لا يتوقف أبدًا. في كل عام، تقوم لجنة TC39 (لجنة المعايير) بدفع ميزات جديدة من مرحلة الاقتراح إلى التوحيد القياسي. بحلول منتصف عام 2026، أصبحت ميزات ES2024 متاحة على نطاق واسع عبر المتصفحات الحديثة و Node.js، وتم الانتهاء من ES2025 (Iterator Helpers، وطرق Set، وهروب علامة RegExp v، و Promise.try، والمزيد)، وهناك العديد من المقترحات في المرحلة 3 بانتظار التنفيذ النهائي. في هذا المنشور، سنغطي الإضافات الأكثر فائدة من السنوات الأخيرة، والمقترحات الواقعية للتخطيط لها، وبعض المقترحات التي تم سحبها أو إعادة هيكلتها.

ميزات ES2024

Promise.withResolvers()

قبل ES2024، كان إنشاء Promise والتحكم يدويًا في تسويته يتطلب تخزين مراجع خارج المنشئ (constructor). ميزة Promise.withResolvers() تحل هذه المشكلة.

// Before ES2024 — verbose
let resolve, reject;
const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});

// ES2024 — clean
const { promise, resolve, reject } = Promise.withResolvers();

// Real-world use case: event-driven resolution
const click = Promise.withResolvers();

document.getElementById('button').addEventListener('click', () => {
  click.resolve('Button clicked!');
});

const result = await click.promise;
console.log(result); // Button clicked!

دعم المتصفحات: Chrome 119+، Firefox 121+، Safari 17.4+، Node.js 22+

تجميع المصفوفات: Object.groupBy() و Map.groupBy()

تجميع البيانات بواسطة دالة مفتاحية هو عملية شائعة. وفرت ES2024 أخيرًا دعمًا أصليًا — ولكن لاحظ: لا يوجد Array.groupBy(). تم سحب اقتراح Array.prototype.group() / Array.prototype.groupBy() الأصلي بسبب مخاوف تتعلق بتوافق الويب واستبداله بطريقتين ثابتتين (static methods) على Object و Map.

const products = [
  { id: 1, name: 'Laptop', category: 'Electronics', price: 1200 },
  { id: 2, name: 'Chair', category: 'Furniture', price: 150 },
  { id: 3, name: 'Monitor', category: 'Electronics'price: 400 },
  { id: 4, name: 'Desk'category: 'Furniture'price: 500 }
];

// Object.groupBy() - returns a plain object keyed by the callback's return string
const byCategory = Object.groupBy(productsp => p.category);
// Result: { Electronics: [...], Furniture: [...] }

console.log(byCategory.Electronics); // Array of electronics

// You can use any iterable + any string-returning key function
const grouped = Object.groupBy(productsp => p.price > 500 ? 'expensive' : 'affordable');
// Result: { expensive: [...], affordable: [...] }

// Map.groupBy() - same idea, but the result is a Map (so keys can be objects)
const byCategoryMap = Map.groupBy(productsp => p.category);
// byCategoryMap.get('Electronics') -> Array of electronics

دعم المتصفحات: Chrome 117+، Firefox 119+، Safari 17.4+، Node.js 21+

طرق Set: الاتحاد، التقاطع، الفرق

تلقى كائن Set عمليات قوية من نظرية المجموعات.

const admins = new Set(['alice''bob''charlie']);
const moderators = new Set(['bob''david''eve']);

// Union: all members from both sets
const allStaff = admins.union(moderators);
console.log(allStaff); // Set(5) { 'alice', 'bob', 'charlie', 'david', 'eve' }

// Intersection: members in both sets
const staffWithBothRoles = admins.intersection(moderators);
console.log(staffWithBothRoles); // Set(1) { 'bob' }

// Difference: members in first set but not second
const adminsOnly = admins.difference(moderators);
console.log(adminsOnly); // Set(2) { 'alice', 'charlie' }

// Symmetric difference: members in either set but not both
const uniqueToEach = admins.symmetricDifference(moderators);
console.log(uniqueToEach); // Set(4) { 'alice', 'charlie', 'david', 'eve' }

// Relationship checks
console.log(admins.isSubsetOf(allStaff)); // true
console.log(admins.isSupersetOf(moderators)); // false
console.log(admins.isDisjointFrom(new Set(['frank''grace']))); // true

دعم المتصفحات: Chrome 122+، Firefox 127+، Safari 17+، Node.js 22+

find / findIndex / findLast / findLastIndex

ترجع findIndex() فهرس العنصر المطابق ولكن ليس العنصر نفسه، لذا غالبًا ما يحتاج الناس لكليهما. النمط الأنظف هو الحصول على العنصر باستخدام find (أو findLast) والفهرس باستخدام findIndex (أو findLastIndex) — باستخدام اسمي متغيرين متميزين.

const users = [
  { id: 1name: 'Alice'active: true }  { id: 2name: 'Bob'active: false }  { id: 3name: 'Charlie'active: true }
];

// Traditional findIndex - returns only the index
const bobIndex = users.findIndex(u => u.name === 'Bob');
const bobByIndex = users[bobIndex];

// Or grab the element directly with find
const bob = users.find(u => u.name === 'Bob');

// findLast / findLastIndex (ES2023) walk from the end
const lastActive = users.findLast(u => u.active);          // Charlie
const lastActiveIndex = users.findLastIndex(u => u.active); // 2

مساعدات المكررات (ES2025)

وصلت مساعدات المكررات (Iterator Helpers) إلى المرحلة 4 في الجلسة العامة لـ TC39 في أكتوبر 2024 وتم دمجها في ES2025. تمنح هذه المساعدات المكررات دوالها الخاصة الكسولة مثل .map، و .filter، و .take، و .drop، و .flatMap، و .reduce، و .forEach، و .some، و .every، و .find، و .toArray. الأهم من ذلك أنها كسولة (lazy) — حيث ترجع .map و .filter مساعدات مكررات لا تنشئ مصفوفة وسيطة؛ فقط .toArray هي التي تخصص مصفوفة.

// Lazy mapping + filtering over an iterable
const numbers = [12345;
const doubled = Iterator.from(numbers)
  .map(n => n * 2)
  .filter(n => n > 4)
  .toArray();

console.log(doubled); // [6, 8, 10]

// Chaining over a generator without building intermediate arrays
function* generateUsers() {
  yield { id: 1age: 25 };
  yield { id: 2age: 30 };
  yield { id: 3age: 35 };
}

const adults = Iterator.from(generateUsers())
  .filter(u => u.age >= 30)
  .toArray();

الحالة: تم توحيدها في ES2025. متاحة في Chrome 122+، Firefox 131+، Safari 18.4+، Node.js 22+، Bun 1.1.31+، و Deno 2 (خط أساسي منذ مارس 2025). يشحن TypeScript الأنواع في lib.es2025.iterator.d.ts من الإصدار 5.6 فصاعدًا.

المزخرفات (Decorators) (المرحلة 3)

تسمح المزخرفات بالبرمجة القائمة على البيانات الوصفية (metadata) وتُستخدم على نطاق واسع في TypeScript وأطر العمل. تصميم المرحلة 3 الحالي هو اقتراح "المزخرفات القياسية" — وهو متميز عن تصميم experimentalDecorators القديم الذي كانت تدعمه إصدارات TypeScript السابقة.

// Standard (Stage 3) decorator — TypeScript 5.0+ implements this without flags
function readonly<This>(
  _target: ClassAccessorDecoratorTarget<This>  context: ClassAccessorDecoratorContext<This>
): ClassAccessorDecoratorResult<This> {
  return {
    set() {
      throw new Error(`Cannot assign to read-only property '${String(context.name)}'`);
    }  };
}

class User {
  @readonly
  accessor id: number = 1;

  name: string = 'Alice';
}

const user = new User();
user.name = 'Bob';   // OK
// user.id = 2;       // Error at runtime: Cannot assign to read-only property 'id'

الحالة: في المرحلة 3 منذ عام 2023. جاهزة للإنتاج عبر TypeScript 5+ و Babel؛ لم يتم شحنها بشكل أصلي في أي محرك متصفح بعد. اعتمدت أطر عمل مثل Angular و Lit شكل المرحلة 3؛ تظل المزخرفات القديمة في TypeScript خلف experimentalDecorators لقواعد الأكواد القديمة في NestJS / TypeORM.

Record و Tuple — تم سحبها

قالت المسودات السابقة لهذا المنشور أن Record و Tuple كانا اقتراحًا في المرحلة 2. لم يعد هذا صحيحًا: في جلسة TC39 العامة في 14 أبريل 2025، تم التوصل إلى إجماع على سحب اقتراح Record و Tuple. لم يتمكن الاقتراح من الحصول على مزيد من الإجماع لإضافة أنواع أولية (primitive types) جديدة إلى اللغة، وتمت أرشفة مستودع GitHub الخاص به.

إذا كنت بحاجة إلى بيانات غير قابلة للتغيير (immutable) ذات دلالات قيمية اليوم، فإن الخيارات العملية هي:

  • الكائنات / المصفوفات العادية بالإضافة إلى Object.freeze (مساواة المرجع، وليس مساواة القيمة)
  • مكتبات مثل Immer أو Immutable.js
  • جهد TC39 اللاحق الذي يُطلق عليه أحيانًا "Composites" / "Tuples and Records v2"، والذي يستهدف كائنات جديدة غير قابلة للتغيير (مع مساواة هيكلية بأسلوب Object.is عند الاختيار) بدلاً من أنواع أولية جديدة — لا يزال في مرحلة مبكرة وغير جاهز للإنتاج
// The original syntax (#{...} / #[...]) will NOT ship as primitives:
// const point = #{ x: 1, y: 2 };  // syntax error in every engine — proposal withdrawn

// Today's pragmatic substitute
const point = Object.freeze({ x: 1y: 2 };
// point.x = 3; // silently ignored in non-strict mode, throws in strict mode

الحالة: تم سحبها (14 أبريل 2025). لا تخطط بناءً على بناء الجملة #{} / #[].

مطابقة الأنماط (Pattern Matching) (المرحلة 1)

مطابقة الأنماط هي اقتراح طويل الأمد للمطابقة بأسلوب التفكيك (destructuring) في شكل تعبير — أقرب إلى match في Rust منها إلى switch. اعتبارًا من أحدث دورات TC39 في عام 2025، يظل الاقتراح في المرحلة 1: لا تزال مساحة التصميم قيد النقاش، وهناك العديد من المقترحات الفرعية المتنافسة لبناء الجملة والدلالات. (أفادت بعض منشورات المدونات الخارجية أنها في المرحلة 3 — وهذا لا ينعكس في قائمة مقترحات TC39 الرسمية اعتبارًا من مايو 2026.) الشحن الأصلي الواقعي سيكون في عام 2027+.

// Conceptual syntax — STILL BEING DEBATED, do not use in production.
// The actual final syntax may differ.
const response = { status: 200data: [{ id: 1 }};

match (response{
  when ({ status: 200}{
    console.log('Success:';
  }
  when ({ status: 404 }{
    console.log('Not found';
  }
  when ({ status }{
    console.log('Error:';
  }
}

الحالة: المرحلة 1 (استكشاف مبكر؛ لم يتم تنفيذها في أي محرك؛ بناء الجملة النهائي لا يزال قيد التغيير).

ملخص توافق المتصفحات وبيئات التشغيل

بيانات التوافق أدناه مأخوذة من MDN و caniuse.com اعتبارًا من مايو 2026. تحقق دائمًا من caniuse.com مقابل هدف browserslist الفعلي الخاص بك قبل الشحن — تشحن المحركات الميزات بشكل أسرع مما يتم تحديث منشورات المدونات.

الميزةChromeFirefoxSafariNode.jsالحالة
Promise.withResolvers()119+121+17.4+22+ES2024 (تم الشحن)
Object.groupBy / Map.groupBy117+119+17.4+21+ES2024 (تم الشحن)
طرق Set (union، intersection، إلخ.)122+127+17+22+ES2025 (تم الشحن)
Iterator Helpers (Iterator.from، .map / .filter الكسول)122+131+18.4+22+ES2025 (تم الشحن، خط أساسي مارس 2025)
المزخرفات (المرحلة 3)المرحلة 3 — TypeScript 5+ / Babel فقط
Record / Tuple (#{} / #[])تم سحبها في 14 أبريل 2025
مطابقة الأنماط (تعبير match)المرحلة 1 (التصميم قيد التغيير)

أفضل الممارسات لاستخدام الميزات الجديدة

1. تحقق من بيئتك المستهدفة

إذا كان تطبيقك يحتاج إلى دعم المتصفحات القديمة، فاستخدم transpiler مثل Babel.

// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env', {
      targets: '> 0.5%, last 2 versions, not dead'
    }]
  ]
};

2. استخدم Polyfill عند الحاجة

بالنسبة للميزات التي تتوفر في المحركات الأحدث ولكن ليس في الحد الأدنى من المتصفحات التي تدعمها، قم بشحن polyfill صغير أو استخدم core-js / es-shims.

// polyfill-example.js
if (!Promise.withResolvers) {
  Promise.withResolvers = function() {
    let resolve, reject;
    const promise = new Promise((res, rej) => {
      resolve = res;
      reject = rej;
    });
    return { promise, resolve, reject };
  };
}

3. الاختبار في بيئاتك المستهدفة

استخدم caniuse.com أو browserslist للتحقق من الدعم.

{
  "browserslist": [
    "last 2 versions",
    "> 0.5%",
    "not dead"
  ]
}

الخلاصة

قدم ES2024 تحسينات جوهرية مع Promise.withResolvers()، و Object.groupBy / Map.groupBy، والأساس لعمليات نظرية المجموعات (set theory). ثم قدم ES2025 طرق الـ Set و Iterator Helpers عبر كل المحركات الحديثة الحالية. تظل الـ Decorators (المرحلة 3) قصة تخص الـ transpiler في الوقت الحالي، ولا يزال Pattern Matching في المرحلة الأولى المبكرة، وتم سحب اقتراح Record and Tuple — لذا فإن أي أدلة قديمة تخبرك بالتخطيط حول #{} / #[] هي أدلة قديمة وغير محدثة. ابقَ على اطلاع دائم بقائمة مقترحات TC39 (tc39.es/proposals)، ولكن أعطِ الأولوية للميزات الموجودة بالفعل في المتصفحات المستهدفة، واعتمد على الـ transpilers عندما تحتاج حقًا إلى أحدث الصيغ البرمجية، وتذكر أن ما يتم سحبه لا يقل أهمية عما يتم تطويره.


نشرة أسبوعية مجانية

ابقَ على مسار النيرد

بريد واحد أسبوعياً — دورات، مقالات معمّقة، أدوات، وتجارب ذكاء اصطناعي.

بدون إزعاج. إلغاء الاشتراك في أي وقت.