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!