
定型数组是新增结构,目的是提升向原生库传输数据的效率。实际上,是一种特殊的包含数值类型的数组
DataView是第一种允许读写ArrayBuffer的视图,专为文件I/O和网络I/O设计,其API支持对缓冲数据的高度控制,性能较较差。对缓冲内容没有预设,也不能迭代。必须在对已有的ArrayBuffer读取或写入时才能创建DataView实例,该实例可以使用全部或部分ArrayBuffer,且维护着对该缓冲实例的引用,以及视图在缓冲中开始的位置。
const buf = new ArrayBuffer(16);
// DataView默认使用整个ArrayBuffer
const fullDataView = new DataView(buf);
console.log(fullDataView.byteOffset); // 0
console.log(fullDataView.byteLength); // 16
console.log(fullDataView.buffer === buf); // true
// 构造函数接收一个可选的字节偏移量和字节长度
// byteOffset=0 表示视图从缓冲起点开始
// byteLength=8 限制视图为前8个字节
const firstHalfDataView = new DataView(buf, 0, 8);
console.log(firstHalfDataView.byteOffset); // 0
console.log(firstHalfDataView.byteLength); // 8
console.log(firstHalfDataView.buffer === buf); // true
// 如果不指定,则DataView会使用剩余的缓冲
// byteOffset=8 表示视图从缓冲起点开始
// byteLength 未指定,则默认为剩余缓冲
const secondHalfDataView = new DataView(buf, 8);
console.log(secondHalfDataView.byteOffset); // 8
console.log(secondHalfDataView.byteLength); // 8
console.log(secondHalfDataView.buffer === buf); // true要通过DataView读取缓冲,还需要几个组件
| ElementType | 字节 | 说明 | 等价的C类型 | 值的范围 | 
|---|---|---|---|---|
| Int8 | 1 | 8位有符号整数 | signed char | -128~127 | 
| UInt8 | 1 | 8位无符号整数 | unsigned char | 0~255 | 
| Int16 | 2 | 16位有符号整数 | short | -32768~32767 | 
| Uint16 | 2 | 16位无符号整数 | unsigned short | 0~65535 | 
| Int32 | 4 | 32位有符号整数 | int | -2147483648~2147483647 | 
| Uint32 | 4 | 32位无符号整数 | unsigned int | 0~4294967295 | 
| Float32 | 4 | 32位IEEE754浮点数 | float | -3.4e+38~+3.4e+38 | 
| Float64 | 8 | 64位IEEE754浮点数 | double | -1.7e+308~+1.7e+308 | 
// 在内存中分配两个字节并声明一个DataView
const buf = new ArrayBuffer(2);
const view = new DataView(buf);
console.log(view.getInt8(0)); // 0
console.log(view.getInt8(0)); // 0
// 检查整个缓冲
console.log(view.getInt16(0)); // 0
view.setUint8(0, 255); // 将整个缓冲都设置为1 255=>11111111(2^8-1)
view.setUint8(1, 0xFF); // DataView会自动将数据转换为特定的ElementType
// 现在缓冲里都是1了
console.log(view.getInt16(0)); // -1 如果当成二补数的有符号整数 应该是-1const buf = new ArrayBuffer(2);
const view = new DataView(buf);
view.setUint8(0, 0x80); // 设置最左边为1
view.setUint8(1, 0x01); // 设置最右边为1
// 缓冲内容
// 0x8  0x0  0x0  0x1
// 1000 0000 0000 0001
// 按大端字节序读取Uint16
// 0x8001 = 2^15 + 2^0 = 32768 + 1 = 32769
console.log(view.getUint16(0));
// 按小端字节序读取Uint16
// 0x0180 = 2^8 + 2^7 = 256 + 128 = 384
console.log(view.getUint16(0, true));
// 按大端字节序写入Uint16
view.setUint16(0, 0x0004);
// 按小端字节序写入Uint16
view.setUint16(0, 0x0002, true);定型数组是另一种形式的ArrayBuffer视图。概念上与DataView接近,但定型数组特定于一种ElementType且遵循系统原生的字节序,提供了适用面更广的API和更高的性能。设计定型数组的目的就是提高与WebGL等原生库交换二进制数据的效率。
const buf = new ArrayBuffer(12); // 创建一个12字节的缓冲
const ints = new Int32Array(buf); // 创建一个引用该缓冲的Int32Array
// 这个定型数组知道自己的每个元素需要4字节, 因此长度为3
console.log(ints.length); // 3
// 创建一个长度为6的Int32Array
const ints2 = new Int32Array(6);
// 每个数值使用4字节,因此ArrayBuffer是24字节
console.log(ints2.length); // 6
console.log(ints2.buffer.byteLength); // 24
// 创建一个包含[2,4,6,8]的Int32Array
const ints3 = new Int32Array([2, 4, 6, 8]);
console.log(ints3.length); // 4
console.log(ints3.buffer.byteLength); // 16
console.log(ints3[2]); // 6
// 通过复制ints3的值创建一个Int16Array
const ints4 = new Int16Array(ints3);
// 这个新类型数组会分配自己的缓冲
// 对应索引的每个值会相应地转换为新格式
console.log(ints4.length); // 4
console.log(ints4.buffer.byteLength); // 8
console.log(ints4[2]); // 6
// 基于普通数组来创建一个Int16Array
const ints5 = new Int16Array.from([3,5,7,9]);
console.log(ints5.length); // 4
console.log(ints5.buffer.byteLength); // 8
console.log(ints5[2]); // 7
// 基于传入的参数创建一个Float32Array
const floats = Float32Array.of(3.14, 2.718, 1.618);
console.log(floats.length); // 3
console.log(floats.buffer.byteLength); // 12
console.log(floats[2]); // 1.6180000305175781
定型数组同样使用数组缓冲来存储数据,而数组缓冲无法调整大小,故以下方法不适用于定型数组
定型数组提供set()和subarray()快速向外或向内复制数据
// 创建长度为8的int16数组
const container = new Int16Array(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
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((x, y) => (x.length || x) + y.length);
  // 按照提供的类型创建一个数组,为所有元素留出空间
  const resultArray = new typedArrayConstructor(numElements);
  // 依次转移数组
  let currentOffset = 0;
  typedArrays.map(x => {
    resultArray.set(x, currentOffset);
    currentOffset += x.length;
  });
  return resultArray;
}
const concatArray = typedArrayConcat(Int32Array,
  Int8Array.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]
// 长度为2的有符号整数数组
// 每个索引保存一个二补数形式的有符号整数,范围是-128(-1*2^7)~127(2^7-1)
const ints = new Int8Array(2);
// 长度为2的无符号整数数组
// 每个索引保存一个无符号整数,范围是0~255(2^8-1)
const unsignedInts = new Uint8Array(2);
// 上溢的位不会影响相邻索引,索引只取最低有效位上的8位
unsignedInts[1] = 256; // 0x100 => 0001 0000 0000
console.log(unsignedInts); // [0, 0]
unsignedInts[1] = 511; // 0x1FF => 0001 1111 1111
console.log(unsignedInts); // [0, 255]
// 下溢的位会被转换为其无符号的等价值
// 0xFF是以二补数形式表示的-1(截取到8位),但255是一个无符号整数
unsignedInts[1] = -1; // 0xFF
console.log(unsignedInts); // [0, 255]
// 上溢自动变成二补数形式
// 0x80是无符号整数的128,是二补数形式的-128
ints[1] = 128; // 0x80;
console.log(ints); // [0, -128]
// 下溢自动变成二补数形式
// 0xFF是无符号整数的255 ,是二补数形式的-1
ints[1] = 255; // 0xFF
console.log(ints); // [0, -1]Map是一种新的集合类型,带来真正的键/值存储机制,Map的大多数特性都可以通过Object类型实现,但存在细微差异
const m = new Map(); // 创建空映射
// 创建的同时初始化
const m1 = new Map([
  ["key1", "value1"],
  ["key2", "value2"],
  ["key3", "value3"]
]); // 使用嵌套数组初始化
const m2 = new Map({
  [Symbol.iterator]: function*() {
    yield ["key1", "value1"];
    yield ["key2", "value2"];
    yield ["key3", "value3"];
  }
}); // 使用自定义迭代器初始化映射
const m3 = new Map([[]]); // 映射期待的键值对,无论是否提供
console.log(m3.has(undefined)); // true
console.log(m3.get(undefined)); // undefinedconst m = new Map();
console.log(m.has("firstName")); // false
console.log(m.get("firstName")); // undefined
console.log(m.size); // 0
m.set("firstName", "John")
  .set("lastName", "Frisbie");
console.log(m.has("firstName")); // true
console.log(m.get("firstName")); // John
console.log(m.size); // 2
m.delete("firstName");
console.log(m.has("firstName")); // false
console.log(m.has("lastName")); // true
console.log(m.size); // 1
m.clear();
console.log(m.has("firstName")); // false
console.log(m.has("lastName")); // false
console.log(m.size); // 0
const m = new Map([
  ['key1', 'value1'],
  ['key2', 'value2'],
  ['key3', 'value3']
]);
console.log(m.entries === m[Symbol.iterator]); // true
for (let pair of m.entries()) {
  console.log(pair);
}
// [key1, value1]
// [key2, value2]
// [key3, value3]
for (let pair of m[Symbol.iterator]()) {
  console.log(pair);
}
// [key1, value1]
// [key2, value2]
// [key3, value3]
// entries()是默认迭代器,所以可以直接对映射实例使用扩展操作,把映射转换为数组
console.log([...m]); // [[key1, value1], [key2, value2], [key3, value3]]
// forEach
m.forEach((val, key) => {
  console.log(`${key}->${val}`);
});
// key1 -> value1
// key2 -> value2
// key3 -> value3
for (let key of m.keys()) {
  console.log(key);
}
// key1
// key2
// key3
for (let val of m.values()) {
  console.log(val);
}
// value1
// value2
// value3
WeakMap是Map的兄弟类型,其API也是Map的子集,weak描述的是JS垃圾回收程序对待“弱映射”中键的方式
const wm = new WeakMap();
const key1 = { id: 1 },
      key2 = { id: 2 },
      key3 = { id: 3 };
const wm1 = new WeakMap([
  [key1, "value1"],
  [key2, "value2"],
  [key3, "value3"]
]);
console.log(wm1.get(key1)); // value1
console.log(wm1.get(key2)); // value2
console.log(wm1.get(key3)); // value3
const wm = new WeakMap();
wm.set({}, "val");
// 因为没有指向这个对象的其他引用,当这行代码执行完后,对象键就会被当做垃圾回收
// 该键值对就从弱映射中消失,使其成为一个空映射
// 因为值也没有被引用,所以键值对被破坏后,值本身也会成为垃圾回收的目标
const wm = new WeakMap();
const container = {
  key: {}
};
wm.set(container.key, "val");
function removeReference() {
  container.key = null;
}
// container对象维护一个对弱映射键的引用,因此这个对象键不会成为垃圾回收的目标
// 如果调用了removeReference(),就会摧毁键对象的最后一个引用,垃圾回收程序就可以吧这个键值对清理掉因为WeakMap中的键值对任何时候可能被销毁,所以没必要提供迭代其键值对的能力。也用不着像clear()这样一次性毁掉所有键值的方法。所以不可能在不知道对象引用的情况下从弱映射中取得值。之所以限制只能用对象作为键,就是为了保证只有通过键对象的引用才能取得值。
私有变量
const wm = new WeakMap();
class User {
  constructor(id) {
    this.idProperty = Symbol("id");
    this.setId(id);
  }
  setPrivate(property, value) {
    const privateMembers = wm.get(this) || {};
    privateMembers[property] = value;
    wm.set(this, privateMembers);
  }
  getPrivate(property) {
    return wm.get(this)[property];
  }
  setId(id) {
    this.setPrivate(this.idProperty, id);
  }
  getId() {
    return this.getPrivate(this.idProperty);
  }
}
const user = new User(123);
console.log(user.getId()); // 123
user.setId(456);
console.log(user.getId()); // 456
const User = (() => {
  const wm = new WeakMap();
  class User {
    constructor(id) {
      this.idProperty = Symbol("id");
      this.setId(id);
    }
    setPrivate(property, value) {
      const privateMembers = wm.get(this) || {};
      privateMembers[property] = value;
      wm.set(this, privateMembers);
    }
    getPrivate(property) {
      return wm.get(this)[property];
    }
    setId(id) {
      this.setPrivate(this.idProperty, id);
    }
    getId() {
      return this.getPrivate(this.idProperty);
    }
  }
  return User;
})();
const user = new User(123);
console.log(user.getId()); // 123
user.setId(456);
console.log(user.getId()); // 456
DOM节点元数据
const wm = new WeakMap();
const loginButton = document.querySelector('#login');
// 给这个节点关联一些元数据
wm.set(loginButton, { disabled: true });
// 当节点从DOM树中被删除后,垃圾回收程序就可以立即释放其内存(假设没有其他地方引用该对象)
Set很多方面像加强的Map,二者大多数API和行为都是共有的。
const s = new Set();
// 使用数组初始化集合
const s1 = new Set(["value1", "value2", "value3"]);
console.log(s1.size); // 3
// 使用自定义迭代器初始化集合
const s2 = new Set({
  [Symbol.iterator]: function*() {
    yield "value1";
    yield "value2";
    yield "value3";
  }
});
console.log(s2.size); // 3
const s3 = new Set();
console.log(s3.has("Matt")); // false
console.log(s3.size); // 0
s3.add("Matt")
  .add("Frisbie");
console.log(s3.has("Matt")); // true
console.log(s3.size); // 2
s3.delete("Matt");
console.log(s3.has("Matt")); // false
console.log(s3.has("Frisbie")); // true
console.log(s3.size); // 1
s3.clear();
console.log(s3.has("Matt")); // false
console.log(s3.has("Frisbie")); // false
console.log(s3.size); // 0
class XSet extends Set {
  union(...sets) {
    return XSet.union(this, ...sets);
  }
  intersection(...sets) {
    return XSet.intersection(this, ...sets);
  }
  difference(set) {
    return XSet.difference(this, set);
  }
  symmetricDifference(set) {
    return XSet.symmetricDifference(this, set);
  }
  cartesinaProduct(set) {
    return XSet.cartesinaProduct(this, set);
  }
  powerSet() {
    return XSet.powerSet(this);
  }
  // 返回两个或多个集合并集
  static union(a, ...bSets) {
    const unionSet = new XSet(a);
    for (const b of bSets) {
      for (const bValue of b) {
        unionSet.add(bValue);
      }
    }
    return unionSet;
  }
  // 返回两个或更多集合的交集
  static intersection(a, ...bSets) {
    const intersectionSet = new Set(a);
    for (const aValue of intersectionSet) {
      for (const b of bSets) {
        if (!b.has(aValue)) {
          intersectionSet.delete(aValue);
        }
      }
    }
    return intersectionSet;
  }
  // 返回两个集合的差集
  static difference(a, b) {
    const differenceSet = new XSet(a);
    for (const bValue of b) {
      if (a.has(bValue)) {
        differenceSet..delete(bValue);
      }
    }
    return differenceSet;
  }
  // 返回两个集合的对称差集
  static symmetricDifference(a, b) {
    // 集合A与集合B中所有不属于A∩B的元素的集合
    return a.union(b).difference(a.intersection(b));
  }
  // 返回两个集合(数组对形式)的笛卡尔积
  // 必须返回数组集合,因为笛卡尔积可能包含相同值的对
  static cartesinaProduct(a, b) {
    const cartesinaProductSet = new XSet();
    for (const aValue of a) {
      for (const bValue of b) {
        cartesinaProductSet.add([aValue, bValue]);
      }
    }
    return cartesinaProductSet;
  }
  // 返回一个集合的幂集
  static powerSet(a) {
    const powerSet = new XSet().add(new XSet());
    for (const aValue of a) {
      for (const set of new XSet(powerSet)) {
        powerSet.add(new XSet(set).add(aValue));
      }
    }
    return powerSet;
  }
}
WeakSet是Set的兄弟类型,其API是Set的子集。weak描述的是JS垃圾回收程序对待弱集合中值的方式
const ws = new WeakSet();
const val1 = { id: 1 },
      val2 = { id: 2 },
      val3 = { id: 3 };
// 使用数组初始化弱集合
const ws1 = new WeakSet([val1, val2, val3]);
console.log(ws1.has(val1)); // true
console.log(ws1.has(val2)); // true
console.log(ws1.has(val3)); // true
// 原始值可以先包装成对象在用作值
const stringVal = new String("val1");
const ws3 = new WeakSet([stringVal]);
console.log(ws3.has(stringVal)); // true
const ws4 = new WeakSet();
const val4 = { id: 1 },
      val5 = { id: 2 };
console.log(ws4.has(val4)); // false
ws4.add(val4)
   .add(val5);
console.log(ws4.has(val4)); // true
console.log(ws4.has(val5)); // true
ws4.delete(val4);
console.log(ws4.has(val4)); // false
const ws = new WeakSet();
ws.add({}); 
// 当该行代码执行完后,该对象值就会被当做垃圾回收,这个值就从弱集合中消失,成为空集合
const ws2 = new WeakSet();
const container = {
  val: {}
};
ws.add(container.val);
function removeReference() {
  container.val = null;
}
// 执行removeReference()后会摧毁值对象的最后一个引用,垃圾回收程序就可以把这个值清理掉
const disabledElements = new WeakSet();
const loginButton = document.querySelector('#login');
// 通过加入对应集合,给这个节点打上禁用标签
// 通过查询元素在不在disabledElements中,就可以知道它是不是被禁用了
disabledElements.add(loginButton); 
// 只要WeakSet中任何元素从DOM中移除,垃圾回收程序就可以忽略其存在,释放内存