一句话解释Javascript系列
icon
password
一句话通俗解释什么是JavaScript之原型链以Todo List为例:1. 继承共有方法2. 提高性能和内存效率3. 动态修改共有行为示例代码与泛型(Generics)和多态(Polymorphism)的关系一句话通俗解释什么是JavaScript之This以Todo List为例:1. 对象方法中使用this2. 构造函数中使用this3. 事件处理器中使用this4. 箭头函数中的this与 bind、call和apply 的关系?bind和thiscall和apply与this使用场景对比一句话通俗解释什么是JavaScript之定型数组一些特点以Todo List为例:例子1:批量更新Todo项状态初始化Todo状态列表批量更新状态利用定型数组的优势例子2: 使用定型数组存储Todo项的优先级例子3: 加密存储Todo项
一句话通俗解释什么是JavaScript之原型链
JavaScript的原型链是一种使得对象能够继承其他对象属性和方法的机制,好比一个家族的遗传链,如果孩子没有某个特征,就会向上追溯到父母或祖辈中去找。
以Todo List为例:
在开发一个Todo List应用时,原型链的概念可以用于多种场景,尤其是在创建对象和继承共有方法时。这里有几个具体的实际用途示例:
1. 继承共有方法
假设你有多种类型的任务,比如
普通任务
、紧急任务
和长期任务
。它们都有一些共同的方法,比如完成任务
、显示详情
等,但是也有一些特殊的方法或属性。你可以创建一个Task
基类,它包含所有任务共有的方法和属性。然后,你可以创建普通任务
、紧急任务
和长期任务
这些子类,它们通过原型链继承Task
基类的方法和属性,同时也可以添加或重写自己特有的方法和属性。2. 提高性能和内存效率
通过使用原型链,所有从同一原型继承的实例共享相同的方法,而不是在每个实例中复制这些方法。这意味着如果你有成千上万的任务实例,它们都会使用相同的方法引用而不是每个实例都有自己的副本,从而节省内存。
3. 动态修改共有行为
通过原型链,你可以在运行时动态地修改原型上的方法,这些改变会立即反映到所有从该原型继承的实例上。比如,如果你想为所有任务添加一个新的共有方法
延期
,你只需在Task
原型上添加这个方法,所有的任务类型都会立即获得这个新方法。示例代码
在这个例子中,通过使用原型链,
UrgentTask
继承了Task
的方法,同时添加了自己的属性priority
和重写了display
方法以显示优先级。与泛型(Generics)和多态(Polymorphism)的关系
原型链是JavaScript特有的一种机制,它用于对象之间的继承。通过原型链,一个对象可以访问另一个对象的属性和方法。这是实现对象间继承和方法复用的一种方式。
多态是面向对象编程中的一个概念,指的是同一操作作用于不同的对象上时,可以有不同的行为。JavaScript通过原型链和构造函数(以及ES6中的类)可以实现多态。子类对象可以覆盖继承自父类的方法,以实现不同的行为。这种方法在JavaScript中是通过原型链实现的。
在JavaScript中,多态是通过原型链实现的。子类可以继承父类的方法和属性,子类的实例可以根据需要覆盖这些方法和属性以实现多态性。
一句话通俗解释什么是JavaScript之This
在JavaScript中,
this
是一个特殊的关键字,它指向函数被调用时的上下文对象,其具体指向取决于函数是如何被调用的。以Todo List为例:
1. 对象方法中使用this
当你在对象的方法中使用
this
时,它指向调用该方法的对象。这非常有用,因为你可以访问和修改对象的属性。2. 构造函数中使用this
在构造函数中,
this
指向新创建的对象实例。这允许你初始化对象实例的属性。3. 事件处理器中使用this
在DOM事件处理器中,
this
通常指向触发事件的元素。这使得直接在事件处理器中访问和修改元素变得很方便。4. 箭头函数中的this
箭头函数不绑定自己的
this
,它们"继承"了定义它们的上下文的this
值。这对于回调函数和闭包特别有用,因为你可以安全地使用this
而不担心它会指向全局对象或被错误地绑定。与 bind
、call
和apply
的关系?
this
关键字的行为在JavaScript中是灵活的,但这种灵活性有时会导致困惑。特别是在回调函数或将方法从一个对象传递到另一个对象的情况下,this
的值可能不会按照预期工作。为了解决这些问题,JavaScript提供了几个方法来明确设置this
的值:bind
、call
和apply
。bind
和this
bind
方法创建一个新的函数,你可以在这个新函数被调用时预先指定this
的值。这对于确保回调函数中this
的值与期望保持一致特别有用。call
和apply
与this
与
bind
不同,call
和apply
方法立即调用函数,并允许你为这次函数调用指定this
值。call
方法接受一个参数列表,而apply
接受一个参数数组。使用场景对比
bind
:当你想要预先设置函数内this
的值,但不立即执行该函数时,使用bind
。它常用于事件处理器和回调函数中。
call
和apply
:当你需要立即调用函数,并且想要指定函数体内this
的值时,使用call
或apply
。选择call
或apply
主要取决于你的参数是以列表形式传递还是以数组形式传递。
在开发Todo List应用时,如果你遇到
this
不指向预期对象的情况,比如在事件监听器或者将对象方法作为回调函数传递时,使用bind
可以帮助你锁定this
的值,确保代码的正确执行。同样,call
和apply
可以在特定的上下文中调用函数,这在处理类似于动态设置上下文或借用其他对象的方法时非常有用。一句话通俗解释什么是JavaScript之定型数组
content
定型数组 (typed array) 是 ECMAScript 新增的结构, 目的是提升向原生库传输数据的效率。实际上, JavaScript 并没有 “TypedArray” 类型, 它所指的其实是一种特殊的包含数值类型的数组。为理解如何使用定型数组, 有必要先了解一下它的用途。
6.3.1 历史
随着浏览器的流行, 不难想象人们会满怀期待地通过它来运行复杂的 3D 应用程序。早在 2006 年, Mozilla、Opera等浏览器提供商就实验性地在浏览器中增加了用于渲染复杂图形应用程序的编程平台,无须安装任何插件。其目标是开发一套 JavaScript API, 从而充分利用 3D 图形 API 和 GPU 加速, 以便在<canvas>元素上渲染复杂的图形。
- WebGL 最后的 JavaScript API 是基于 OpenGL ES ( OpenGL for Embedded Systems ) 2.0 规范的。OpenGL ES 是 OpenGL 专注于 $2 \mathrm{D}$ 和 3D 计算机图形的子集。这个新 API 被命名为 WebGL (Web Graphics Library),于 2011 年发布 1.0 版。有了它, 开发者就能够编写涉及复杂图形的应用程序, 它会被兼容 WebGL 的浏览器原生解释执行。
在 WebGL 的早期版本中, 因为 JavaScript 数组与原生数组之间不匹配, 所以出现了性能问题。图形驱动程序 API 通常不需要以 JavaScript 默认双精度浮点格式传递给它们的数值, 而这恰恰是 JavaScript 数组在内存中的格式。因此, 每次 WebGL 与 JavaScript 运行时之间传递数组时, WebGL 绑定都需要在目标环境分配新数组, 以其当前格式迭代数组, 然后将数值转型为新数组中的适当格式, 而这些要花费很多时间。
2. 定型数组
这当然是难以接受的, Mozilla 为解决这个问题而实现了 CanvasFloatArray。这是一个提供 JavaScript 接口的、 $\mathrm{C}$ 语言风格的浮点值数组。JavaScript 运行时使用这个类型可以分配、读取和写人数组。这个数组可以直接传给底层图形驱动程序 API, 也可以直接从底层获取到。最终, CanvasFloatArray 变成了 Float32Array, 也就是今天定型数组中可用的第一个 “类型”。
6.3.2 ArrayBuffer
Float32Array 实际上是一种 “视图”, 可以允许 JavaScript 运行时访问一块名为 ArrayBuffer 的预分配内存。ArrayBuffer 是所有定型数组及视图引用的基本单位。
注意 SharedArrayBuffer 是ArrayBuffer 的一个变体,可以无须复制就在执行上下文间传递它。关于这种类型, 请参考第 27 章。
156 第 6 章 集合引用类型
ArrayBuffer () 是一个普通的 JavaScript 构造函数,可用于在内存中分配特定数量的字节空间。 const buf = new ArrayBuffer(16); // 在内存中分配 16 字节 alert (buf.byteLength); // 16
ArrayBuffer 一经创建就不能再调整大小。不过, 可以使用 slice ()复制其全部或部分到一个新实例中:
const buf1 $=$ new ArrayBuffer(16);
const buf2 = buf1.slice $(4,12)$;
alert (buf2.byteLength); // 8
ArrayBuffer 某种程度上类似于 $\mathrm{C}++$ 的 malloc (),但也有几个明显的区别。
malloc () 在分配失败时会返回一个null 指针。ArrayBuffer 在分配失败时会抛出错误。
malloc () 可以利用虚拟内存, 因此最大可分配尺寸只受可寻址系统内存限制。ArrayBuffer 分配的内存不能超过 Number.MAX_SAFE_INTEGER ( $2^{53}-1$ ) 字节。
malloc () 调用成功不会初始化实际的地址。声明 ArrayBuffer 则会将所有二进制位初始化为 0 。
口 通过 malloc () 分配的堆内存除非调用 free () 或程序退出, 否则系统不能再使用。而通过声明 ArrayBuffer 分配的堆内存可以被当成垃圾回收, 不用手动释放。
不能仅通过对 ArrayBuffer 的引用就读取或写人其内容。要读取或写人 ArrayBuffer, 就必须通过视图。视图有不同的类型, 但引用的都是 ArrayBuffer 中存储的二进制数据。
6.3.3 Dataview
第一种允许你读写 ArrayBuffer 的视图是 DataView。这个视图专为文件 I/O 和网络 I/O 设计, 其 API 支持对缓冲数据的高度控制, 但相比于其他类型的视图性能也差一些。DataView 对缓冲内容没有任何预设,也不能迭代。
必须在对已有的 ArrayBuffer 读取或写人时才能创建 DataView 实例。这个实例可以使用全部或部分 ArrayBuffer,且维护着对该缓冲实例的引用,以及视图在缓冲中开始的位置。
const buf $=$ new ArrayBuffer(16);
// DataView 默认使用整个 ArrayBuffer
const fullDataView = new DataView (buf);
alert (fullDataView.byteoffset); // 0
alert (fullDataView.byteLength); // 16
alert (fullDataView.buffer === buf); // true
// 构造函数接收一个可选的字节偏移量和字节长度
// byteOffset=0 表示视图从缓冲起点开始
//byteLength=8 限制视图为前 8 个字节
const firstHalfDataView = new DataView (buf, 0,8 );
alert(firstHalfDataView.byteoffset); // 0
alert(firstHalfDataView.byteLength); // 8
alert(firstHalfDataView.buffer === buf); // true
// 如果不指定, 则 DataView 会使用剩余的缓冲
// byteOffset=8 表示视图从缓冲的第 9 个字节开始
//byteLength 未指定, 默认为剩余缓冲
const secondHalfDataView = new DataView (buf, 8);
alert (secondHalfDataView.byteoffset); // 8
alert (secondHalfDataView.byteLength); // 8
alert (secondHalfDataView.buffer === buf); // true
要通过 DataView 读取缓冲, 还需要几个组件。
口 首先是要读或写的字节偏移量。可以看成 DataView 中的某种“地址”。
DataView 应该使用 Element Type 来实现 JavaScript 的 Number 类型到缓冲内二进制格式的转换。
口 最后是内存中值的字节序。默认为大端字节序。
- ElementType
DataView 对存储在缓冲内的数据类型没有预设。它暴露的 API 强制开发者在读、写时指定一个 ElementType, 然后 DataView 就会忠实地为读、写而完成相应的转换。
ECMAScript 6 支持 8 种不同的 ElementType (见下表)。
ElementType | 字 | 节 | 说 明 | 等价的 C 类型 | 值的范围 |
Int8 | ㅤ | 1 | 8 位有符号整数 | signed char | $-128 \sim 127$ |
Uint8 | ㅤ | 1 | 8 位无符号整数 | unsigned char | $0 \sim 255$ |
Int16 | ㅤ | 2 | 16 位有符号整数 | short | $-32768 \sim 32767$ |
Uint16 | ㅤ | 2 | 16 位无符号整数 | unsigned short | $0 \sim 65535$ |
Int32 | ㅤ | 4 | 32 位有符号整数 | int | $-2147483648 \sim 2147483647$ |
Uint32 | ㅤ | 4 | 32 位无符号整数 | unsigned int | $0 \sim 4294967295$ |
Float32 | ㅤ | 4 | 32 位 IEEE-754 浮点数 | float | $-3.4 \mathrm{e}+38 \sim+3.4 \mathrm{e}+38$ |
Float64 | ㅤ | 8 | 64 位 IEEE-754 浮点数 | double | $-1.7 \mathrm{e}+308 \sim+1.7 \mathrm{e}+308$ |
DataView 为上表中的每种类型都暴露了 get 和 set 方法, 这些方法使用 byteOffset (字节偏移量)定位要读取或写人值的位置。类型是可以互换使用的,如下例所示:
// 在内存中分配两个字节并声明一个 DataView
const buf = new ArrayBuffer(2);
const view $=$ new DataView (buf);
// 说明整个缓冲确实所有二进制位都是 0
// 检查第一个和第二个字符
alert (view.getInt8(0)); // 0
alert (view.getInt8(1)); // 0
// 检查整个缓冲
alert (view.getInt16(0)); // 0
// 将整个缓冲都设置为 1
// 255 的二进制表示是 $11111111\left(2^{\wedge} 8-1\right)$
view.setUint 8 (0, 255);
// DataView 会自动将数据转换为特定的 Element Type
// 255 的十六进制表示是 0xFF
view.setUint $8(1,0 x F F)$;
// 现在, 缓冲里都是 1 了
// 如果把它当成二补数的有符号整数, 则应该是-1
alert (view.getInt16(0)); // -1
// 在内存中分配两个字节并声明一个 DataView
const buf = new ArrayBuffer(2);
const view = new DataView(buf);
// 填充缓冲, 让第一位和最后一位都是 1
view. setUint $8(0 , 0 \times 80) ; / /$ 设置最左边的位等于 1
view.setUint $8(1 , 0 \times 01) ; / /$ 设置最右边的位等于 1
// 缓冲内容 (为方便阅读, 人为加了空格)
// 0x8 0x0 0x0 0x1
// 1000000000000001
// 按大端字节序读取 Uint16
// 0x80 是高字节, $0 \times 01$ 是低字节
$/ / 0 \times 8001=2^{\wedge} 15+2^{\wedge} 0=32768+1=32769$
alert (view.getUint16(0)); // 32769
/ / 按小端字节序读取 Uint16
// 0x01 是高字节, $0 \times 80$ 是低字节
$/ / 0 \times 0180=2^{\wedge} 8+2^{\wedge} 7=256+128=384$
alert(view.getUint16(0, true)); // 384
/ / 按大端字节序写入 Uint16
view.setUint $16(0,0 \times 0004)$;
// 缓冲内容 (为方便阅读, 人为加了空格)
// 0x0 0x0 0x0 0x4
// 0000000000000100
alert (view.getUint8(0)); // 0
alert(view.getUint8(1)); // 4
/ / 按小端字节序写入 Uint16
view.setUint $16(0,0 \times 0002$, true);
// 缓冲内容 (为方便阅读, 人为加了空格)
$/ / 0 \times 0 \quad 0 \times 2 \quad 0 \times 0 \quad 0 \times 0$
// 0000001000000000
alert (view.getUint8(0)); // 2
alert (view.getUint8(1)); // 0
3. 边界情形
DataView 完成读、写操作的前提是必须有充足的缓冲区,否则就会抛出 RangeError:
const buf = new ArrayBuffer (6);
const view = new DataView (buf);
// 尝试读取部分超出缓冲范围的值
view.getInt32 (4);
/ / RangeError
/ / 尝试读取超出缓冲范围的值
view.getInt32 (8);
// RangeError
// 尝试读取超出缓冲范围的值
view.getInt32 (-1);
// RangeError
/ / 尝试写入超出缓冲范围的值
view.set Int 32 (4,123);
// RangeError
DataView 在写人缓冲里会尽最大努力把一个值转换为适当的类型, 后备为 0 。如果无法转换, 则抛出错误:
const buf = new ArrayBuffer(1);
const view = new DataView (buf);
view.setInt $8(0,1.5)$;
alert (view.getInt8(0)); // 1
view.setInt $8(0$, [4]);
alert(view.getInt8(0)); // 4
view.set Int $8(0, ' f ')$;
alert (view.getInt8(0)); // 0
view.set Int8 (0, Symbol ());
// TypeError
6.3.4 定型数组
定型数组是另一种形式的 ArrayBuffer 视图。虽然概念上与 DataView 接近, 但定型数组的区别在于, 它特定于一种 Element Type 且遵循系统原生的字节序。相应地, 定型数组提供了适用面更广的 API 和更高的性能。设计定型数组的目的就是提高与 WebGL 等原生库交换二进制数据的效率。由于定型数组的二进制表示对操作系统而言是一种容易使用的格式, JavaScript 引擎可以重度优化算术运算、按位运算和其他对定型数组的常见操作,因此使用它们速度极快。
创建定型数组的方式包括读取已有的缓冲、使用自有缓冲、填充可迭代结构, 以及填充基于任意类型的定型数组。另外, 通过<Element Type>. from () 和 $<$ ElementType>. of () 也可以创建定型数组:
// 创建一个 12 字节的缓冲
const buf = new ArrayBuffer(12);
// 创建一个引用该缓冲的 Int32Array
const ints $=$ new Int32Array (buf);
// 这个定型数组知道自己的每个元素需要 4 字节
// 因此长度为 3
alert(ints.length); // 3
// 创建一个长度为 6 的 Int 32Array
const ints $2=$ new Int $32 \operatorname{Array}(6)$;
// 每个数值使用 4 字节, 因此 ArrayBuffer 是 24 字节
alert (ints2.length);
// 6
// 类似DataView, 定型数组也有一个指向关联缓冲的引用
alert (ints2.buffer.byteLength); // 24
// 创建一个包含 $[2,4 , 6,8]$ 的 Int32Array
const ints 3 new $\operatorname{Int} 32 \operatorname{Array}([2,4,6,8])$;
alert(ints3.length); // 4
alert (ints3.buffer.byteLength); // 16
alert(ints3[2]); // 6
// 通过复制 ints 3 的值创建一个 Int16Array
const ints4 = new Int16Array (ints3);
// 这个新类型数组会分配自己的缓冲
// 对应索引的每个值会相应地转换为新格式
alert(ints4.length); // 4
alert (ints4.buffer.byteLength); // 8
alert(ints4[2]); // 6
// 基于普通数组来创建一个 Int16Array
const ints5 = Int16Array. from( $[3,5,7,9])$;
alert(ints5.length); // 4
alert(ints5.buffer.byteLength); // 8
alert(ints5[2]); // 7
// 基于传入的参数创建一个 Float32Array
const floats $=$ Float32Array.of $(3.14,2.718,1.618)$;
alert(floats.length); // 3
alert(floats.buffer.byteLength); // 12
alert(floats [2]); // 1.6180000305175781
定型数组的构造函数和实例都有一个 BYTES_PER_ELEMENT 属性, 返回该类型数组中每个元素的大小:
alert (Int16Array.BYTES_PER_ELEMENT); // 2
alert (Int32Array.BYTES_PER_ELEMENT); // 4
const ints $=$ new Int32Array (1),
floats $=$ new Float64Array(1);
alert (ints.BYTES_PER_ELEMENT); // 4
alert(floats.BYTES_PER_ELEMENT); // 8
如果定型数组没有用任何值初始化, 则其关联的缓冲会以 0 填充:
const ints $=$ new Int 32 Array (4);
alert(ints[0]); // 0
alert(ints[1]); // 0
alert(ints[2]); // 0
alert(ints[3]); // 0
- 定型数组行为
从很多方面看, 定型数组与普通数组都很相似。定型数组支持如下操作符、方法和属性:
[]
copywithin()
entries ()
every ()
fill()
filter()
find ()
findindex()
forEach ()
indexof ()
join()
keys ()
lastIndexOf ()
length
map ( )
reduce ()
reduceRight ()
reverse ()
slice()
some ()
sort ()
toLocalestring ()
tostring ()
values ()
其中, 返回新数组的方法也会返回包含同样元素类型 (element type) 的新定型数组:
const ints $=$ new $\operatorname{Int16Array}([1,2,3])$;
const doubleints $=$ ints. $\operatorname{map}(\mathrm{x}=>2 * \mathrm{x})$;
alert(doubleints instanceof Int16Array); // true
定型数组有一个 Symbol. iterator 符号属性, 因此可以通过 for . of 循环和扩展操作符来操作:
const ints $=$ new Int16Array $([1,2,3])$;
for (const int of ints) \{
alert (int);
\}
// 1
// 2
$1 / 3$
alert (Math. $\max ($...ints)); // 3
2. 合并、复制和修改定型数组
定型数组同样使用数组缓冲来存储数据, 而数组缓冲无法调整大小。因此, 下列方法不适用于定型数组:
concat ()
pop()
push()
shift ()
splice()
unshift ()
不过, 定型数组也提供了两个新方法, 可以快速向外或向内复制数据: set () 和 subarray ()。 set () 从提供的数组或定型数组中把值复制到当前定型数组中指定的索引位置:
// 创建长度为 8 的 int 16 数组
const container $=$ new Int $16 \operatorname{Array}(8)$;
$/ /$ 把定型数组复制为前 4 个值
// 偏移量默认为索引 0
container.set (Int8Array.of (1, 2, 3, 4));
console. $\log$ (container); // $[1,2,3,4,0,0,0,0]$
// 把普通数组复制为后 4 个值
// 偏移量 4 表示从索引 4 开始插入
container.set $([5,6,7,8], 4)$;
console.log(container); // $[1,2,3,4,5,6,7,8]$
// 溢出会批出错误
container.set $([5,6,7,8], 7)$;
// RangeError
subarray () 执行与 set () 相反的操作, 它会基于从原始定型数组中复制的值返回一个新定型数组。
复制值时的开始索引和结束索引是可选的:
const source $=$ Int16Array. of $(2,4,6,8)$;
// 把整个数组复制为一个同类型的新数组
const fullcopy = source.subarray ();
console.log(fullcopy); // [2, 4, 6, 8]
// 从索引 2 开始复制数组
const halfcopy = source.subarray (2);
console.log (halfCopy); // [6, 8]
// 从索引 1 开始复制到索引 3
const partialcopy $=$ source. subarray $(1,3)$;
console.log(partialcopy); // [4, 6]
定型数组没有原生的拼接能力, 但使用定型数组 API 提供的很多工具可以手动构建:
// 第一个参数是应该返回的数组类型
// 其余参数是应该拼接在一起的定型数组
function typedArrayConcat (typedArrayConstructor,...typedArrays) \{
// 计算所有数组中包含的元素总数
const numElements $=$ typedArrays.reduce $(\mathrm{x}, \mathrm{y})=>(\mathrm{x}$. length || $\mathrm{x})+\mathrm{y} \cdot$ length $)$;
// 按照提供的类型创建一个数组, 为所有元素留出空间
const resultArray = new typedArrayConstructor (numElements);
$/ /$ 依次转移数组
let currentoffset $=0$;
typedArrays.map ( $\mathrm{x}=>\{$
resultArray.set (x, currentoffset);
currentoffset $+=x$.length;
\});
return resultArray;
\}
const concatArray = typedArrayConcat (Int32Array,
Int 8Array .of $(1,2,3)$,
Int16Array.of $(4,5,6)$,
Float32Array.of $(7,8,9))$;
console. log(concatArray); // $[1,2,3,4,5,6,7,8,9]$
console.log(concatArray instanceof Int32Array); // true
- 下溢和上溢
定型数组中值的下溢和上溢不会影响到其他索引, 但仍然需要考虑数组的元素应该是什么类型。定型数组对于可以存储的每个索引只接受一个相关位, 而不考虑它们对实际数值的影响。以下代码演示了如何处理下溢和上溢:
// 长度为 2 的有符号整数数组
// 每个索引保存一个二补数形式的有符号整数
// 范围是-128 ( $-1 * 2 \wedge 7) \sim 127\left(2^{\wedge} 7-1\right)$
const ints $=$ new Int 8Array (2);
// 长度为 2 的无符号整数数组
// 每个索引保存一个无符号整数
// 范围是 0 255 (2^7 - 1)
const unsignedints $=$ new Uint8Array (2);
// 上溢的位不会影响相邻索引
// 索引只取最低有效位上的 8 位
unsignedints $[1]=256 ; \quad / / 0 \times 100$
console.log(unsignedints); // [0,0]
unsignedints $[1]=511 ; \quad / / 0 \times 1 F F$
console.log(unsignedints); // [0, 255]
// 下溢的位会被转换为其无符号的等价值
// $0 \times F F$ 是以二补数形式表示的 -1 (截取到 8 位),
// 但 255 是一个无符号整数
unsignedints $[1]=-1 \quad / / 0 \mathrm{xFF}$ (truncated to 8 bits)
console.log(unsignedints); // [0,255]
// 上溢自动变成二补数形式
// 0x80 是无符号整数的 128 , 是二补数形式的 -128
ints $[1]=128 ; \quad / / 0 \times 80$
console. $\log ($ ints); // $[0,-128]$
// 下溢自动变成二补数形式
// 0xFF 是无符号整数的 255 , 是二补数形式的 -1
ints $[1]=255 ; \quad / / 0 \times F F$
console. $\log$ (ints); // [0,-1]
除了 8 种元素类型, 还有一种 “夹板” 数组类型: Uint 8 ClampedArray, 不允许任何方向溢出。超出最大值 255 的值会被向下舍人为 255 , 而小于最小值 0 的值会被向上舍人为 0 。
const clampedints $=$ new Uint 8 ClampedArray $([-1,0,255,256])$;
console.log(clampedints); // [0, 0, 255, 255]
按照 JavaScript 之父 Brendan Eich 的说法: “Uint8ClampedArray 完全是 HTML5canvas 元素的历史留存。除非真的做跟 canvas 相关的开发, 否则不要使用它。”
定型数组(Typed Arrays)是一种高效的数据结构,旨在提升JavaScript与原生库(如进行图形处理的WebGL)之间数据传输的效率,允许JavaScript直接操作内存中的二进制数据,开发者可以创建固定类型的数组,如整数或浮点数数组,这些数组的表现形式更接近于底层硬件,从而使得数据操作更加快速和内存效率更高。
一些特点
- 直接操作内存:
- 定型数组允许JavaScript代码直接读写二进制数据,这一点与C或C++中的数组和指针操作类似,能够提升处理大量数值数据的效率。
- 相比于JavaScript的常规数组,定型数组因为绕过了JavaScript的高级抽象,而直接操作内存,从而实现了更高的性能。
- 定型数组背后的核心是ArrayBuffer对象,它代表了内存中的一块原始二进制数据区域,而定型数组则提供了一种读写这块内存的接口。
- 固定类型:
- JavaScript的常规数组是动态类型的,可以存储任意类型的数据。而定型数组只能存储单一类型的数据,如
Int32Array
仅存储32位整数。 - 这种固定类型的特性使得定型数组的行为更接近于Java或C#中的数组,其中数组的元素类型在声明时就已确定。
- 字节序:
- 定型数组遵循系统的原生字节序(大端或小端)。这一点在进行网络传输或文件I/O操作时尤其重要,因为不同的系统可能有不同的字节序。
- 这与Python的
struct
模块类似,后者也允许开发者指定字节序,但在JavaScript中,字节序的处理对于开发者来说是透明的。
- 与WebGL的紧密集成:
- 定型数组与WebGL等Web标准紧密集成,使得在网页中渲染复杂的3D图形成为可能。
- 这种集成让JavaScript在Web图形领域的应用与C++中使用OpenGL或DirectX的方式有了直接的比较基础,尽管后者更为底层和强大,但JavaScript通过定型数组和WebGL提供了一个更为简便和可访问的3D图形解决方案。
以Todo List为例:
如果Todo List项目涉及到性能敏感的数据处理,例如批量操作大量Todo项的状态更新、统计或者加密解密,那么定型数组就可能派上用场。
例子1:批量更新Todo项状态
假设我们有一个包含成千上万个Todo项的列表,每个Todo项有一个状态值,表示该项是待完成(0)还是已完成(1)。如果我们想要批量更新这些Todo项的状态,可以使用
Uint8Array
来实现,因为它足以表示这两种状态,并且操作起来非常高效。初始化Todo状态列表
首先初始化一个定型数组来表示所有Todo项的状态:
批量更新状态
接下来定义一个函数来批量更新Todo项的状态。例如,我们想将前5000项标记为已完成(状态设置为1):
利用定型数组的优势
这里使用
Uint8Array
而不是常规的JavaScript数组的好处在于:- 内存使用更高效:
Uint8Array
为每个状态分配恰好1个字节,而常规数组可能会为相同的数据分配更多内存,尤其是当数组元素是数字时。
- 性能更优:对于大量数据,定型数组的读写速度通常快于常规数组,这是因为它避免了JavaScript引擎的某些动态类型检查。
例子2: 使用定型数组存储Todo项的优先级
假设我们有一个Todo List,每个Todo项除了基本的文本描述外,还有一个1到5的优先级评分。我们可以使用
Uint8Array
来存储这些优先级,因为Uint8Array
是用于存储8位无符号整数的定型数组,非常适合存储这种范围在1到5的小整数。首先创建了一个
Uint8Array
来存储初始的优先级。当我们需要添加一个新的Todo项时,我们创建了一个新的Uint8Array
来存储更新后的优先级列表,然后使用.set()
方法复制现有的优先级,最后添加新的优先级到数组末尾。例子3: 加密存储Todo项
如果我们想要在客户端安全地存储Todo项,可以使用定型数组结合Web Crypto API来进行加密。
首先使用
TextEncoder
将待加密的字符串转换为ArrayBuffer
,然后使用Web Crypto API的encrypt
方法对数据进行加密。加密函数返回的encrypted
也是一个ArrayBuffer
,我们可以将其转换为Uint8Array
以便于查看或存储。Last update: 2024-03-24
icon
password
I am currently in Melbourne
I am Looking for a Data Analytics Role…