ArrayBuffer في TypeScript
تشرح هذه المقالة ArrayBuffer في TypeScript۔
سنشرح ArrayBuffer في TypeScript خطوة بخطوة، بدءًا من الأساسيات وصولًا إلى التقنيات العملية۔
YouTube Video
ArrayBuffer في TypeScript
ArrayBuffer هو كائن مدمج يمثل منطقة ذاكرة خام للبيانات الثنائية۔ يمثل مخزن بيانات خام بطول ثابت، حيث يتم إنشاء TypedArray أو DataView فوقه للقراءة والكتابة۔
المفاهيم الأساسية وخصائص ArrayBuffer
ArrayBuffer هو تسلسل بايتات بطول ثابت۔ تحدد الحجم بالبايت عند إنشائه، ولا يمكن تغيير طوله بعد ذلك۔
1// Create a new ArrayBuffer of 16 bytes.
2const buf = new ArrayBuffer(16);
3console.log(buf.byteLength); // 16
- ينشئ هذا الكود مخزن بيانات فارغ بحجم 16 بايت۔ يمكنك التحقق من الحجم باستخدام
byteLength۔
TypedArray و DataView — طرق لعرض ومعالجة البيانات الثنائية (Buffers)
ArrayBuffer لا يمتلك القدرة على قراءة أو كتابة البيانات۔ لذلك، يتم تنفيذ العمليات الفعلية من خلال TypedArray أو DataView، مع تحديد أنواع البيانات مثل الأعداد الصحيحة أو الأعداد ذات الفاصلة العائمة وترتيب البايتات عند الوصول إلى البيانات۔
1// Create a buffer and a Uint8Array view over it. Then write bytes and read them.
2const buffer = new ArrayBuffer(8);
3const u8 = new Uint8Array(buffer);
4
5u8[0] = 0x41; // 'A'
6u8[1] = 0x42; // 'B'
7console.log(u8); // Uint8Array(8) [ 65, 66, 0, 0, 0, 0, 0, 0 ]
Uint8Arrayهو تمثيل مصفوفي على مستوى البايتات ويمكن الوصول إليه كمصفوفة عادية۔ إذا أنشأت عدة عروض (views) على نفسArrayBuffer، فإنها تشارك نفس الذاكرة في القراءة والكتابة۔
DataView: القراءة والكتابة عند حدود وترتيبات بايتات مختلفة
DataView مفيد للقراءة والكتابة الدقيقة على مستوى البايت، ويسمح بتحديد الترتيب الصغير أو الكبير للبايتات۔
1// Using DataView to write/read multi-byte values with endianness control.
2const buf2 = new ArrayBuffer(8);
3const view = new DataView(buf2);
4
5// Write a 32-bit integer (little-endian)
6view.setInt32(0, 0x12345678, true);
7
8// Read it back as little-endian and big-endian
9const little = view.getInt32(0, true);
10const big = view.getInt32(0, false);
11console.log(little.toString(16)); // "12345678"
12console.log(big.toString(16)); // "78563412"
DataViewيسمح لك بقراءة وكتابة القيم من خلال تحديد مواقع البيانات في الذاكرة، مما يجعله مناسبًا لتطبيق البروتوكولات التي تتطلب التعامل مع ترتيب بايت الشبكة۔
التحويل بين النصوص وArrayBuffer (TextEncoder / TextDecoder)
لتحويل النص إلى ثنائي أو العكس، استخدم TextEncoder و TextDecoder۔
1// Convert string -> ArrayBuffer and back using TextEncoder/TextDecoder.
2const encoder = new TextEncoder();
3const decoder = new TextDecoder();
4
5// Unicode escape sequences
6const str = "\u3053\u3093\u306B\u3061\u306F";
7const encoded = encoder.encode(str); // Uint8Array
8console.log(encoded); // Uint8Array([...])
9
10// If you need an ArrayBuffer specifically:
11const ab = encoded.buffer.slice(encoded.byteOffset, encoded.byteOffset + encoded.byteLength);
12console.log(ab.byteLength); // bytes length of encoded text
13
14// Decode back
15const decoded = decoder.decode(encoded);
16console.log(decoded);- وظيفة
TextEncoder.encodeترجع كائنًا من نوعUint8Array، فإذا كنت بحاجة إلى كائن من نوعArrayBuffer، يجب أن تشير إلى الخاصية.bufferالخاصة به۔ باستخدام طريقةslice، يمكنك استخراج البيانات المطلوبة عن طريق تحديد الموقع والطول۔
تقطيع ونسخ ArrayBuffer
طريقة slice ترجع كائنًا جديدًا من نوع ArrayBuffer۔ ArrayBuffer لا يمكن تغيير حجمه؛ إذا كان من الضروري تغيير الحجم، أنشئ مخزن مؤقت جديد وانسخ البيانات إليه۔
1// Slice an ArrayBuffer and copy to a new sized buffer.
2const original = new Uint8Array([1,2,3,4,5]).buffer;
3const part = original.slice(1, 4); // bytes 1..3
4console.log(new Uint8Array(part)); // Uint8Array [ 2, 3, 4 ]
5
6// Resize: create a new buffer and copy existing content
7const larger = new ArrayBuffer(10);
8const target = new Uint8Array(larger);
9target.set(new Uint8Array(original), 0);
10console.log(target); // first bytes filled with original data
- نظرًا لأن طريقة
sliceترجع مخزنًا مؤقتًا جديدًا من نوعArrayBuffer، إذا كنت تعطي الأولوية للكفاءة أو مشاركة المراجع، يمكنك استخدام طريقةsubarrayمنTypedArrayللإشارة إلى نفس المخزن المؤقت۔
الفرق بين TypedArray.subarray والنسخ
subarray من TypedArray يُرجع عرضًا جديدًا يشير إلى نفس الـArrayBuffer۔
1// subarray shares the same underlying buffer; modifying one affects the other.
2const arr = new Uint8Array([10,20,30,40]);
3const viewSub = arr.subarray(1,3); // shares memory
4viewSub[0] = 99;
5console.log(arr); // Uint8Array [10, 99, 30, 40]
- طرق العرض المشتركة يمكن أن تتجنب تكلفة النسخ، ولكن بما أنها تشير إلى نفس المخزن المؤقت، يجب أن تكون حذرًا من التأثيرات الجانبية۔
دمج المخازن (جمع عدة ArrayBuffers معا)
بما أن ArrayBuffer ذو طول ثابت، للجمع بين مخازن متعددة عليك إنشاء ArrayBuffer جديد ونسخ البيانات إليه۔
1// Concatenate multiple ArrayBuffers
2function concatBuffers(buffers: ArrayBuffer[]): ArrayBuffer {
3 const total = buffers.reduce((sum, b) => sum + b.byteLength, 0);
4 const result = new Uint8Array(total);
5 let offset = 0;
6 for (const b of buffers) {
7 const u8 = new Uint8Array(b);
8 result.set(u8, offset);
9 offset += u8.length;
10 }
11 return result.buffer;
12}
13
14const a = new Uint8Array([1,2]).buffer;
15const b = new Uint8Array([3,4,5]).buffer;
16const c = concatBuffers([a, b]);
17console.log(new Uint8Array(c)); // [1,2,3,4,5]
- إذا كنت تقوم غالبًا بدمج كميات كبيرة من البيانات، فإن حساب الحجم الكلي مسبقًا وتهيئة مخزن مؤقت جديد مرة واحدة فقط، كما هو موضح في هذا الكود، يكون أكثر كفاءة۔
تمرير ArrayBuffer إلى عامل (Transferable)
في postMessage الخاص بالمتصفح، يمكنك نقل كائن ArrayBuffer ككائن "قابل للنقل" (transferable)۔ يمكن نقل الملكية دون الحاجة إلى النسخ، مما يتجنب تكلفة النسخ۔
1// Example: posting an ArrayBuffer to a Worker as a transferable object (browser)
2const worker = new Worker('worker.js');
3const bufferToSend = new Uint8Array([1,2,3,4]).buffer;
4
5// Transfer ownership to the worker (main thread no longer owns it)
6worker.postMessage(bufferToSend, [bufferToSend]);
7
8// After transfer, bufferToSend.byteLength === 0 in many browsers (detached)
9console.log(bufferToSend.byteLength); // may be 0
- عن طريق تحديد الكائنات المراد نقلها في مصفوفة كوسيط ثاني في
postMessage، يمكنك نقل ملكية الكائنات القابلة للنقل مثلArrayBufferبدون نسخ۔ - بعد النقل، يصبح المخزن المؤقت "مفصولًا" من الجهة الأصلية ولا يمكن الوصول إليه۔ استخدام
SharedArrayBufferيسمح بالوصول في الوقت نفسه من عدة خيوط (threads)، ولكن استخدامه يأتي مع متطلبات أمنية وقيود خاصة بالبيئة۔
التعامل في Node.js (التحويل المتبادل مع Buffer)
في Node.js، يمكنك التحويل بين نوع البيانات الثنائية Buffer وArrayBuffer۔ يكون ذلك مفيدًا عند استهداف كل من المتصفح وNode.js باستخدام TypeScript۔
1// Convert ArrayBuffer <-> Node.js Buffer
2// In Node.js environment:
3const ab = new Uint8Array([10,20,30]).buffer;
4const nodeBuffer = Buffer.from(ab); // ArrayBuffer -> Buffer
5console.log(nodeBuffer); // <Buffer 0a 14 1e>
6
7const backToAb = nodeBuffer.buffer.slice(
8 nodeBuffer.byteOffset,
9 nodeBuffer.byteOffset + nodeBuffer.byteLength
10);
11console.log(new Uint8Array(backToAb)); // Uint8Array [10,20,30]
Buffer.from(arrayBuffer)في Node.js ينشئ نسخة عادةً، لكن في بعض الحالات يمكن مشاركة المرجع، لذا انتبه للإزاحة۔
اعتبارات الأداء وأفضل الممارسات
لتحسين الأداء والتعامل مع ArrayBuffer بكفاءة، هناك عدة نقاط هامة ينبغي الانتباه لها۔ أدناه، نذكر ونشرح أفضل الممارسات العملية۔
-
قلل من عدد النسخ عند التعامل مع بيانات ثنائية كبيرة، استخدم
subarray(عرض مشترك) أو الكائنات القابلة للنقل لتقليل النسخ۔ -
خصص مخازن كبيرة دفعة واحدة تخصيص الحواجز الصغيرة بشكل متكرر يزيد من الحمل الزائد۔ إذا أمكن، خصص حاجزًا كبيرًا دفعة واحدة واستخدم الأجزاء منه حسب الحاجة۔
-
حدد ترتيب البايتات بشكل صريح عند التعامل مع قيم متعددة البايتات، استخدم
DataViewوحدد ترتيب البايتات بوضوح۔ الترتيب الكبير للبايتات (Big-endian) هو المعيار غالبًا في بروتوكولات الشبكات۔
أمثلة لحالات الاستخدام الشائعة
ArrayBuffer يُستخدم على نطاق واسع سواء في المتصفحات أو في Node.js۔
- تحليل وإنشاء بروتوكولات ثنائية (معالجة معلومات رؤوس البيانات باستخدام
DataView) - معالجة الوسائط مثل بيانات الصور والصوت (
fetch(...).then(res => res.arrayBuffer())) - الذاكرة المشتركة لـ WebAssembly (ذاكرة Wasm تعتمد على
ArrayBuffer) - تحويل المعالجة الثقيلة إلى العمال (بتمرير
ArrayBufferككائن قابل للنقل)
الكود التالي هو مثال على الحصول على بيانات ثنائية وتحليلها۔
1// Example: fetch binary data in browser and inspect first bytes
2async function fetchAndInspect(url: string) {
3 const resp = await fetch(url);
4 const ab = await resp.arrayBuffer();
5 const u8 = new Uint8Array(ab, 0, Math.min(16, ab.byteLength));
6 console.log('first bytes:', u8);
7}- يقوم هذا الكود بجلب بيانات ثنائية من أي عنوان ويب في المتصفح ويعرض البايتات القليلة الأولى۔ يقوم الكود بتحميل البيانات التي تم جلبها باستخدام API
fetchكمخزنArrayBufferويفحص أول 16 بايت باستخدامUint8Array۔
الملخص
ArrayBuffer هو تمثيل ذاكرة خام، مما يمكّن من عمليات ثنائية فعالة عبر TypedArray وDataView۔ من خلال التصميم لتجنب النسخ غير الضروري وتحديد ترتيب البايتات صراحة، يمكنك تحقيق معالجة ثنائية آمنة وعالية الأداء۔
يمكنك متابعة المقالة أعلاه باستخدام Visual Studio Code على قناتنا على YouTube.۔ يرجى التحقق من القناة على YouTube أيضًا.۔