JavaScript المتقدم (ES6+)

Typed Arrays و ArrayBuffer - التعامل مع البيانات الثنائية

13 دقيقة الدرس 25 من 40

Typed Arrays و ArrayBuffer - التعامل مع البيانات الثنائية

توفر Typed Arrays آلية للوصول إلى البيانات الثنائية الأولية في JavaScript. إنها ضرورية للعمل مع الملفات وبروتوكولات الشبكة ورسومات WebGL ومعالجة الصوت والعمليات الأخرى الحرجة للأداء التي تتطلب معالجة الذاكرة المباشرة.

ما هي Typed Arrays؟

Typed Arrays هي عروض شبيهة بالمصفوفة توفر آلية للوصول إلى البيانات الثنائية الأولية. على عكس المصفوفات العادية، Typed Arrays:

  • تخزن البيانات في مخزن ثنائي ذو حجم ثابت
  • يمكن أن تحتوي فقط على أرقام من نوع محدد
  • توفر أداءً أفضل للعمليات الرقمية
  • تُستخدم في WebGL و Canvas و Web Audio و File APIs
  • لا يمكن تغيير حجمها بعد الإنشاء
حقيقة مهمة: تم إنشاء Typed Arrays في البداية لـ WebGL للتعامل بكفاءة مع بيانات الرسومات ثلاثية الأبعاد، لكنها تُستخدم الآن في العديد من Web APIs.

ArrayBuffer - الأساس

ArrayBuffer هو مخزن بيانات ثنائية أولي عام وثابت الطول:

// إنشاء مخزن 16 بايت const buffer = new ArrayBuffer(16); console.log(buffer.byteLength); // 16 console.log(buffer); // ArrayBuffer { byteLength: 16 } // لا يمكن معالجة ArrayBuffer مباشرة // تحتاج إلى "عرض" لقراءة/كتابة البيانات // التحقق من دعم ArrayBuffer console.log(typeof ArrayBuffer); // 'function' // لا يمكنك تغيير حجم ArrayBuffer // لـ "تغيير الحجم"، يجب إنشاء واحد جديد ونسخ البيانات
مهم: ArrayBuffer مجرد ذاكرة أولية. لا يمكنك القراءة أو الكتابة إليها مباشرة. تحتاج إلى عرض Typed Array أو DataView للوصول إلى البيانات.

أنواع Typed Array

يوفر JavaScript عدة منشئات Typed Array لأنواع رقمية مختلفة:

أنواع الأعداد الصحيحة: Int8Array - عدد صحيح 8 بت موقّع (-128 إلى 127) Uint8Array - عدد صحيح 8 بت غير موقّع (0 إلى 255) Uint8ClampedArray- عدد صحيح 8 بت غير موقّع، محدود (0 إلى 255) Int16Array - عدد صحيح 16 بت موقّع (-32768 إلى 32767) Uint16Array - عدد صحيح 16 بت غير موقّع (0 إلى 65535) Int32Array - عدد صحيح 32 بت موقّع (-2^31 إلى 2^31-1) Uint32Array - عدد صحيح 32 بت غير موقّع (0 إلى 2^32-1) أنواع الفاصلة العائمة: Float32Array - فاصلة عائمة IEEE 32 بت Float64Array - فاصلة عائمة IEEE 64 بت أنواع BigInt (ES2020+): BigInt64Array - عدد صحيح 64 بت موقّع BigUint64Array - عدد صحيح 64 بت غير موقّع

إنشاء Typed Arrays

يمكنك إنشاء Typed Arrays بعدة طرق:

// الطريقة 1: الإنشاء بالطول const int8 = new Int8Array(4); console.log(int8); // Int8Array [0, 0, 0, 0] console.log(int8.length); // 4 console.log(int8.byteLength); // 4 بايت // الطريقة 2: الإنشاء من مصفوفة const uint8 = new Uint8Array([10, 20, 30, 40]); console.log(uint8); // Uint8Array [10, 20, 30, 40] // الطريقة 3: الإنشاء من ArrayBuffer const buffer = new ArrayBuffer(8); const int16 = new Int16Array(buffer); console.log(int16.length); // 4 (8 بايت / 2 بايت لكل عنصر) // الطريقة 4: الإنشاء من Typed Array آخر const float32 = new Float32Array(uint8); console.log(float32); // Float32Array [10, 20, 30, 40] // الطريقة 5: استخدام طريقة .of() الثابتة const arr = Uint8Array.of(1, 2, 3, 4); console.log(arr); // Uint8Array [1, 2, 3, 4] // الطريقة 6: استخدام طريقة .from() الثابتة const arr2 = Uint8Array.from([5, 6, 7, 8]); console.log(arr2); // Uint8Array [5, 6, 7, 8]

العمل مع Typed Arrays

تتصرف Typed Arrays بشكل مشابه للمصفوفات العادية ولكن مع بعض الاختلافات:

const numbers = new Uint8Array([1, 2, 3, 4, 5]); // الوصول إلى العناصر console.log(numbers[0]); // 1 console.log(numbers[2]); // 3 // تعيين العناصر numbers[0] = 10; numbers[1] = 20; console.log(numbers); // Uint8Array [10, 20, 3, 4, 5] // طرق المصفوفة تعمل console.log(numbers.length); // 5 console.log(numbers.map(x => x * 2)); // Uint8Array [20, 40, 6, 8, 10] console.log(numbers.filter(x => x > 3)); // Uint8Array [10, 20, 4, 5] console.log(numbers.reduce((sum, x) => sum + x, 0)); // 42 // التقطيع ينشئ Typed Array جديد const slice = numbers.slice(1, 3); console.log(slice); // Uint8Array [20, 3] // التكرار for (const num of numbers) { console.log(num); } // مشغل النشر يعمل console.log([...numbers]); // [10, 20, 3, 4, 5]

تقييد القيمة والفيضان

تتعامل Typed Arrays المختلفة مع القيم خارج النطاق بشكل مختلف:

// Uint8Array تلتف (0-255) const uint8 = new Uint8Array(3); uint8[0] = 255; uint8[1] = 256; // تلتف إلى 0 uint8[2] = -1; // تلتف إلى 255 console.log(uint8); // Uint8Array [255, 0, 255] // Uint8ClampedArray تقيد القيم (0-255) const clamped = new Uint8ClampedArray(3); clamped[0] = 255; clamped[1] = 256; // محدد إلى 255 clamped[2] = -1; // محدد إلى 0 console.log(clamped); // Uint8ClampedArray [255, 255, 0] // Int8Array تلتف مع الإشارة (-128 إلى 127) const int8 = new Int8Array(3); int8[0] = 127; int8[1] = 128; // تلتف إلى -128 int8[2] = -129; // تلتف إلى 127 console.log(int8); // Int8Array [127, -128, 127] // مصفوفات Float لا تلتف const float = new Float32Array(2); float[0] = 1.5; float[1] = 3.14159; console.log(float); // Float32Array [1.5, 3.14159]
حالة الاستخدام: Uint8ClampedArray مصمم خصيصاً لمعالجة بكسل Canvas حيث يجب تقييد القيم إلى 0-255.

DataView - الوصول الثنائي المرن

يوفر DataView واجهة منخفضة المستوى لقراءة وكتابة أنواع أرقام متعددة في ArrayBuffer:

const buffer = new ArrayBuffer(8); const view = new DataView(buffer); // كتابة أنواع مختلفة إلى نفس المخزن view.setInt8(0, 127); // 1 بايت عند الإزاحة 0 view.setInt16(1, 32767); // 2 بايت عند الإزاحة 1 view.setFloat32(4, 3.14); // 4 بايت عند الإزاحة 4 // قراءة القيم مرة أخرى console.log(view.getInt8(0)); // 127 console.log(view.getInt16(1)); // 32767 console.log(view.getFloat32(4)); // 3.14 // التحكم في Endianness (الافتراضي هو big-endian) view.setInt16(0, 256, true); // Little-endian console.log(view.getInt16(0, true)); // 256 view.setInt16(2, 256, false); // Big-endian console.log(view.getInt16(2, false)); // 256

عروض المخزن - عروض متعددة على نفس البيانات

يمكن لعدة Typed Arrays عرض نفس ArrayBuffer:

// إنشاء مخزن 4 بايت const buffer = new ArrayBuffer(4); // إنشاء عروض مختلفة const view8 = new Uint8Array(buffer); const view16 = new Uint16Array(buffer); const view32 = new Uint32Array(buffer); // التعديل من خلال عرض 8 بت view8[0] = 255; view8[1] = 255; // القراءة من خلال عرض 16 بت (يجمع قيمتين 8 بت) console.log(view16[0]); // 65535 (255 * 256 + 255) // التعديل من خلال عرض 32 بت view32[0] = 0x12345678; // القراءة من خلال عرض 8 بت (يقسم إلى بايتات) console.log(view8); // Uint8Array [120, 86, 52, 18] (little-endian) // مثال عملي: تحويل الألوان const colorBuffer = new ArrayBuffer(4); const rgba = new Uint8Array(colorBuffer); const color32 = new Uint32Array(colorBuffer); // تعيين قيم RGBA rgba[0] = 255; // أحمر rgba[1] = 128; // أخضر rgba[2] = 64; // أزرق rgba[3] = 255; // ألفا // الحصول كقيمة 32 بت واحدة console.log(color32[0].toString(16)); // RGBA كـ hex

حالات الاستخدام العملية

// المثال 1: قراءة بيانات ملف ثنائي async function readBinaryFile(url) { const response = await fetch(url); const buffer = await response.arrayBuffer(); const uint8View = new Uint8Array(buffer); console.log(`حجم الملف: ${buffer.byteLength} بايت`); console.log(`أول 10 بايتات:`, uint8View.slice(0, 10)); return uint8View; } // المثال 2: معالجة بيانات الصورة function invertImageColors(imageData) { const pixels = new Uint8ClampedArray(imageData.data.buffer); // التكرار عبر بكسلات RGBA for (let i = 0; i < pixels.length; i += 4) { pixels[i] = 255 - pixels[i]; // أحمر pixels[i + 1] = 255 - pixels[i + 1]; // أخضر pixels[i + 2] = 255 - pixels[i + 2]; // أزرق // ألفا (i + 3) يبقى دون تغيير } return imageData; } // المثال 3: معالجة الصوت function generateSineWave(frequency, duration, sampleRate = 44100) { const numSamples = duration * sampleRate; const buffer = new Float32Array(numSamples); for (let i = 0; i < numSamples; i++) { const time = i / sampleRate; buffer[i] = Math.sin(2 * Math.PI * frequency * time); } return buffer; } const audioData = generateSineWave(440, 1); // نوتة A بتردد 440 هرتز، ثانية واحدة console.log(`تم إنشاء ${audioData.length} عينة`); // المثال 4: تحليل بروتوكول الشبكة function parseHeader(buffer) { const view = new DataView(buffer); return { version: view.getUint8(0), type: view.getUint8(1), length: view.getUint16(2), timestamp: view.getUint32(4), checksum: view.getUint16(8) }; } // المثال 5: مخزن رؤوس WebGL function createVertexBuffer(gl, vertices) { const buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); // التحويل إلى Float32Array لـ WebGL const vertexData = new Float32Array(vertices); gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW); return buffer; }

التحويل بين الأنواع

// Typed Array إلى مصفوفة عادية const typed = new Uint8Array([1, 2, 3]); const regular = Array.from(typed); const regular2 = [...typed]; // مصفوفة عادية إلى Typed Array const arr = [10, 20, 30]; const typed2 = new Uint8Array(arr); const typed3 = Uint8Array.from(arr); // Typed Array إلى Typed Array آخر const uint8 = new Uint8Array([1, 2, 3]); const int16 = new Int16Array(uint8); const float = new Float32Array(uint8); // ArrayBuffer إلى نص Base64 function bufferToBase64(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } // نص Base64 إلى ArrayBuffer function base64ToBuffer(base64) { const binary = atob(base64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes.buffer; }

اعتبارات الأداء

متى تستخدم Typed Arrays: ✓ معالجة كميات كبيرة من البيانات الرقمية ✓ العمل مع صيغ الملفات الثنائية ✓ برمجة رسومات WebGL ✓ معالجة الصوت/الفيديو ✓ تنفيذ بروتوكول الشبكة ✓ الحسابات الرياضية عالية الأداء فوائد الأداء: - وصول أسرع من المصفوفات العادية للبيانات الرقمية - عبء ذاكرة أقل - أفضل لاستخدام ذاكرة التخزين المؤقت للمعالج - مطلوب من قبل العديد من Web APIs (WebGL و Canvas وغيرها) القيود: - حجم ثابت (لا يمكن النمو/الانكماش) - قيم رقمية فقط - لا توجد ثقوب (المصفوفات المتفرقة غير ممكنة) - أقل مرونة من المصفوفات العادية

تمرين عملي:

التحدي: أنشئ دالة تحول قيم ألوان RGB إلى تدرج رمادي.

function rgbToGrayscale(imageData) { const pixels = new Uint8ClampedArray(imageData.data.buffer); // معالجة كل بكسل (4 بايتات: RGBA) for (let i = 0; i < pixels.length; i += 4) { const r = pixels[i]; const g = pixels[i + 1]; const b = pixels[i + 2]; // حساب التدرج الرمادي باستخدام طريقة الإضاءة const gray = 0.299 * r + 0.587 * g + 0.114 * b; // تعيين RGB إلى نفس قيمة التدرج الرمادي pixels[i] = gray; pixels[i + 1] = gray; pixels[i + 2] = gray; // ألفا (i + 3) يبقى دون تغيير } return imageData; } // الاختبار ببيانات بكسل عينة const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const imageData = ctx.createImageData(2, 2); // تعيين بكسل أحمر imageData.data[0] = 255; // R imageData.data[1] = 0; // G imageData.data[2] = 0; // B imageData.data[3] = 255; // A const grayscale = rgbToGrayscale(imageData); console.log(grayscale.data[0]); // ~76 (أحمر بتدرج رمادي)

جربه بنفسك: أنشئ دالة لضبط السطوع أو تطبيق فلتر سيبيا.

الملخص

في هذا الدرس، تعلمت:

  • ArrayBuffer هو مخزن بيانات ثنائية ثابت الحجم
  • توفر Typed Arrays عروضاً مكتوبة في ArrayBuffers
  • توجد أنواع Typed Array متعددة لأنواع رقمية مختلفة
  • يمكّن DataView القراءة/الكتابة المرنة لأنواع بيانات مختلطة
  • Typed Arrays ضرورية لـ WebGL و Canvas و File APIs
  • يختلف سلوك تقييد القيمة والفيضان حسب النوع
  • يمكن لعروض متعددة مشاركة نفس ArrayBuffer الأساسي
  • توفر Typed Arrays أداءً أفضل للعمليات الرقمية
اكتمل الوحدة! لقد أكملت الوحدة 4: هياكل بيانات ES6+. بعد ذلك، سنغوص في الوحدة 5: JavaScript الكائنية التوجه مع فئات ES6!