به طور کلی، در حال حاضر دو نوع نمای اصلی داریم: «نماهای آرایه نوعدار» (Typed Array Views) و DataView.
تفاوت اصلی این دو در این است که آرایههای نوعدار متدهای کاربردی و راحتی را برای دستکاری دادههای باینری در اختیارتان میگذارند، اما DataView بسیار سطحپایینتر (Low-level) است و کنترل دقیقتر و ریزتری روی نحوه دسترسی به دادهها به شما میدهد. روشهای خواندن و نوشتن داده در این دو نما کاملاً با یکدیگر متفاوت است.
با این حال، هر دو نوع نما باعث میشوند که خروجی متد ArrayBuffer.isView() مقدار true باشد. همچنین هر دو نما ویژگیهای (Properties) مشترک زیر را دارند:
buffer: بافر اصلی و زیرینی که این نما به آن اشاره میکند (ارجاع میدهد).
byteOffset: فاصله یا آفستِ نما بر حسب بایت، از ابتدای بافر اصلی آن.
byteLength: طول واقعی خودِ نما بر حسب بایت.
هر دو متد سازنده (Constructor) این نماها، این سه مورد بالا را به عنوان آرگومانهای مجزا میپذیرند؛ البته با یک تفاوت کوچک: متد سازنده در آرایههای نوعدار، پارامترِ طول (length) را به عنوان تعداد عناصر دریافت میکند، نه تعداد بایتها.
نام این نماها کاملاً خودتوصیفگر است (یعنی از روی اسمشان میتوانید کارشان را بفهمید) و برای تمام انواع دادههای عددی معمول مثل Int8، Uint32، Float64 و غیره، نماهای مشخصی ارائه میدهند.
در این میان یک نمای ویژه به نام Uint8ClampedArray وجود دارد که مقادیر را بین 0 تا 255 قفل یا محدود (Clamp) میکند؛ یعنی اگر عددی بزرگتر از ۲۵۵ یا کوچکتر از ۰ به آن بدهید، خودش آن را به این محدوده میآورد. این ویژگی مثلاً برای پردازش دادههای بوم نقاشی (Canvas) فوقالعاده کاربردی است.
بیا نگاهی به جدول انواع این آرایهها بیندازیم:
| نوع آرایه | مقدار قابل ذخیره | اندازه به بایت | نوع وب IDL |
Int8Array | -128 تا 127 | 1 بایت | Int8 |
Uint8Array | 0 تا 255 | 1 بایت | Octet |
Uint8ClampedArray | 0 تا 255 | 1 بایت | Octet |
Int16Array | -32768 تا 32767 | 2 بایت | Short |
Uint16Array | 0 تا 65535 | 2 بایت | Unsigned Short |
Int32Array | -2147483648 تا 2147483647 | 4 بایت | Long |
Uint32Array | 0 تا 4294967295 | 4 بایت | Unsigned Long |
Float16Array | -65504 تا 65504 | 2 بایت | ندارد |
Float32Array | $-3.4 \times 10^{38}$ تا $3.4 \times 10^{38}$ | 4 بایت | Unrestricted Float |
Float64Array | $-1.8 \times 10^{308}$ تا $1.8 \times 10^{308}$ | 8 بایت | Unrestricted Double |
BigInt64Array | $-2^{63}$ تا $2^{63} - 1$ | 8 بایت | Bigint |
BigUint64Array | 0 تا $2^{64} - 1$ | 8 بایت | Bigint |
تمامی نماهای آرایه نوعدار متدها و ویژگیهای یکسانی دارند که توسط کلاس پایه TypedArray تعریف شدهاند. آنها فقط در نوع دادهی اصلی و اندازه بایت با هم تفاوت دارند (که جزئیات بیشتر آن در بخش رمزگذاری ارزش و نرمالسازی بررسی میشود).
آرایههای نوعدار اصولا طول ثابتی (Fixed-length) دارند؛ بنابراین متدهایی از آرایههای معمولی که طول آرایه را تغییر میدهند، در اینجا در دسترس نیستند. این متدها شامل موارد زیر میباشند:
pop ،push ،shift ،splice و unshift
همچنین متد flat در دسترس نیست چون ما اصلاً آرایه نوعدار تودرتو نداریم.
متدهای مرتبط دیگری مثل concat و flatMap هم چون کاربرد خاصی در این فضا نداشتند، حذف شدهاند.
از آنجا که splice وجود ندارد، متد جدید toSpliced هم در دسترس نخواهد بود.
(به جز این موارد، بقیه متدهای آرایههای معمولی بین Array و TypedArray مشترک هستند).
از طرف دیگر، TypedArray دو متد اختصاصی و اضافی به نامهای set() و subarray() دارد که کار با چندین آرایه نوعدار متصل به یک بافر را بهینهسازی میکنند:
متد set() اجازه میدهد چندین خانه از آرایه نوعدار را به صورت همزمان، با استفاده از دادههای یک آرایه معمولی یا یک آرایه نوعدار دیگر مقداردهی کنید. اگر این دو آرایه از یک بافر مشترک استفاده کنند، این عملیات به خاطر جابهجایی سریع در حافظه، بسیار سریعتر و بهینهتر انجام میشود.
متد subarray() یک نمای آرایه نوعدار جدید میسازد که به همان بافر اصلی اشاره میکند، اما محدوده یا طول کوچکتری را پوشش میدهد.
هیچ راهی برای تغییر مستقیم طول یک آرایه نوعدار بدون تغییر دادن بافر اصلی آن وجود ندارد. با این حال، اگر آرایه نوعدار به یک بافر با قابلیت تغییر سایز (Resizable Buffer) متصل باشد و مقدار byteLength ثابتی نداشته باشد، به اصطلاح «ردیاب طول» (Length-tracking) خواهد بود؛ یعنی با تغییر سایز بافر اصلی، اندازه آرایه هم خودکار تغییر میکند.
درست مثل آرایههای معمولی، شما میتوانید با علامت کروشه [ ] به عناصر یک آرایه نوعدار دسترسی داشته باشید. با این کار، بایتهای مربوطه در بافر زیرین بازیابی شده و به عنوان یک عدد تفسیر میشوند. هرگونه تلاش برای دسترسی به ویژگیها با استفاده از یک عدد (یا رشتهی معادل آن عدد)، توسط آرایه نوعدار پروکسی و مدیریت میشود و هرگز مستقیماً با خود شیء برخورد نمیکند. این موضوع به این معنی است که:
دسترسی به اندیسهای خارج از محدوده (Out-of-bounds) همیشه مقدار undefined برمیگرداند، بدون اینکه واقعاً به ویژگیهای خود شیء دست بزند.
هرگونه تلاش برای نوشتن داده در اندیسهای خارج از محدوده هیچ اثری ندارد: خطایی تولید نمیکند، اما بافر یا آرایه را هم تغییر نمیدهد.
اندیسهای آرایه نوعدار به نظر قابل تغییر (Configurable و Writable) میآیند، اما هرگونه تلاش برای تغییر دادن ویژگیهای ساختاری آنها با خطا مواجه میشود.
بیا با یک مثال کد این رفتارها را ببینیم:
const uint8 = new Uint8Array([1, 2, 3]);
console.log(uint8[0]); // خروجی: 1
// موارد زیر فقط برای نمایش رفتار کد است و نباید در کد واقعی استفاده شوند:
uint8[-1] = 0;
uint8[2.5] = 0;
uint8[NaN] = 0;
console.log(Object.keys(uint8)); // خروجی: ["0", "1", "2"]
console.log(uint8[NaN]); // خروجی: undefined
// دسترسیهای غیرعددی کماکان کار میکنند:
uint8[true] = 0;
console.log(uint8[true]); // خروجی: 0
// تلاش برای فریز کردن آرایه:
Object.freeze(uint8); // خطا میدهد: TypeError: Cannot freeze array buffer views with elements
DataViewابزار DataView یک رابط بسیار سطحپایین (Low-level) است که یک API به همراه متدهای Getter/Setter برای خواندن و نوشتن دادههای دلخواه در بافر به ما میدهد. این ابزار زمانی که با انواع مختلفی از دادهها (مثلاً یک فایل ترکیبی) سروکار دارید، بسیار مفید است.
نماهای آرایه نوعدار، دادهها را بر اساس ترتیب بایت بومی (Native byte-order) یا همان ویژگی Endianness پلتفرم و سختافزار شما ثبت میکنند. اما با کمک DataView شما میتوانید این ترتیب بایت را کاملاً کنترل کنید! به صورت پیشفرض، رفتار روی حالت Big-endian (ترتیب بایتها از باارزشترین به کمارزشترین) قرار دارد. شما میتوانید با استفاده از متدهای Getter و Setter این ترتیب را برعکس کرده و روی حالت Little-endian (از کمارزشترین به باارزشترین بایت) تنظیم کنید.
نکته جذاب دیگر این است که DataView نیازی به تراز کردن (Alignment) حافظه ندارد؛ شما میتوانید خواندن و نوشتنهای چندبایتی را از هر آفست و نقطهی مشخصی که دلتان خواست شروع کنید. متدهای Setter هم دقیقاً به همین صورت کار میکنند.
DataView:در مثال زیر، از یک DataView استفاده کردهایم تا به کمک آن بتوانیم نمایش باینری (دودویی) هر عددی را به دست آوریم:
function toBinary(
x,
{ type = "Float64", littleEndian = false, separator = " ", radix = 16 } = {},
) {
// پیدا کردن تعداد بایتهای مورد نیاز بر اساس نوع داده
const bytesNeeded = globalThis[`${type}Array`].BYTES_PER_ELEMENT;
const dv = new DataView(new ArrayBuffer(bytesNeeded));
// مقداردهی به کمک متد داینامیک ستتر
dv[`set${type}`](0, x, littleEndian);
// تبدیل بایتها به رشته خروجی با مبنای مشخصشده
const bytes = Array.from({ length: bytesNeeded }, (_, i) =>
dv
.getUint8(i)
.toString(radix)
.padStart(8 / Math.log2(radix), "0"),
);
return bytes.join(separator);
}
console.log(toBinary(1.1)); // خروجی: 3f f1 99 99 99 99 99 9a
console.log(toBinary(1.1, { littleEndian: true })); // خروجی: 9a 99 99 99 99 99 f1 3f
console.log(toBinary(20, { type: "Int8", radix: 2 })); // خروجی: 00010100
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://developer.mozilla.org/en-US/docs/Web/JavaScript