《Understanding ES6》chapter10 Improved Array Capabilities

154 阅读8分钟

2019-8-2

第十章 增强的数组功能

(一)创建数组

ES5创建数组有两种方式:Array构造器和数组字面量

ES6新增了两种方式:Array.of()Array.from()

1. Array.of()

Array.of():解决了使用Array构造器创建数组参数数量和类型不同时导致的不同结果的问题。Array.of()总会创建一个包含全部传入参数的数组,而不管参数数量和类型。

var a = new Array(1);
console.log(a[0]);  // undefined
console.log(a.length);  // 1

var b = Array.of(1);
console.log(b[0]); // 1
console.log(b.length); // 1

在向函数传递参数时,使用Array.of()而不是Array来确保行为一致,如:

let value = 1;
function CreateArray(arrayCreator, value){
  return arrayCreator(value);
}
// 如果传递的是Array构造器,就会创建出一个长度为1,值为undefined的数组,与预期行为不一致
let items = CreateArray(Array.of, value);
console.log(items);  //[1]

Array.of()没有使用Symbol.species属性来决定返回值的类型,而是使用了当前的构造器(即of()方法内部的this)来做决定。

class MyArray extends Array{
  static get [Symbol.species]() {
    return Array;
  }
}
let items = MyArray.of(1,2)
console.log(items instanceof MyArray); // true
console.log(items instanceof Array); // true
console.log(items);

2. Array.from()

Array.from():解决了将类数组对象和可迭代对象转换为数组的问题。

类数组对象:要有length属性;

可迭代对象:要有Symbol.iterator属性。

如果一个对象既是可迭代对象又是类数组对象,Array.from()方法会使用迭代器来决定需要转换的值。

ES5中将一个类数组对象转换为数组:

// 法1
function makeArray1(arrayLike) {
  var result = [];
  for(var i = 0; i < arrayLike.length; i++) {
    result.push(arrayLike[i]);
  }
  return result;
}

function doSomething() {
  var args = makeArray1(arguments);
  // 使用args
}

// 法2:利用slice()方法特性:只需要有数值类型的索引和长度属性就能正常工作
function makeArray2(arrayLike) {
  return Array.prototype.slice.call(arrayLike); // 不能明确体现出"要将类数组对象转换为数组"的目的
}

ES6:

Array.from(obj, function, thisObj);

Obj — 要转换的对象;

function — 类似数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组;

thisObj — 指定第二个参数的函数里用到的this关键字绑定

function doSomething() {
  var args = Array.from(arguments);
  // 使用args
}
function translate() {
  return Array.from(arguments, (value)=> value+1);
}

let nums = translate(1,2,3)
console.log(nums);  // [2,3,4]
function translate2() {
  return Array.from(arguments, helper.add, helper);
}
let numbers = translate2(1,2,3);
console.log(numbers);  // [2,3,4]

Array.from()在可迭代对象上的应用:

let numbers = {
  *[Symbol.iterator](){
    yield 1;
    yield 2;
    yield 3;
  }
};

let numbers2 = Array.from(numbers, (value) => value + 1);
console.log('numbers2: ', numbers2);  // numbers2: [2, 3, 4

(二)数组上的新方法

1.find()和findIndex()

ES5检索数组的方法:indexOf()lastIndexOf(), 缺点:若想在一系列数值中间查找第一个偶数,必须自己写代码来实现。

find和findIndex唯一的区别是:find()方法返回匹配的值(若没有则返回undefined),findIndex()返回匹配位置的索引(若没有则返回-1)。

array.find(function(currentValue, index, arr),thisValue)

currentValue -- 必须,当前元素
index -- 可选,当前元素的索引值
arr -- 可选,当前元素所属的数组对象
thisValue --可选,回调函数中的this值

find和findIndex会在回调函数第一次返回true时停止执行
let numbers = [2, 35, 32, 23, 53];
console.log(numbers.find(item => item > 33));  // 35 
console.log(numbers.findIndex(item => item > 33));  // 1

查找满足特定条件的数组元素,使用find()和findIndex();

查找特定值,使用indexOf() 和 lastIndexOf()

2. fill()

fill():使用特定值填充数组中的部分或全部元素,当只使用一个参数时,会用该参数的值填充整个数组。

array.fill(value, start, end)

value -- 必须,填充的值
start -- 可选,开始填充的位置,如果为负值,表示倒数。
end -- 可选,停止填充的位置(默认为array.length),开区间
let numbers = [1,2,3,4,5];
numbers.fill(1);
console.log('numbers: ', numbers); // [1,1,1,1,1]

numbers.fill(2,2);
console.log('numbers: ', numbers); // [1,1,2,2,2]

numbers.fill(3,1,3);
console.log('numbers: ', numbers); //[1,3,3,2,2]

numbers.fill(-1);
console.log('numbers: ', numbers); //[-1, -1, -1, -1, -1]

numbers.fill(2, -1);
console.log('numbers: ', numbers); //[-1, -1, -1, -1, 2]

numbers.fill(3, -4, -1);
console.log('numbers: ', numbers); //[-1, 3, 3, 3, 2]

3. copyWithin()

copyWithin():从数组的指定位置拷贝元素到数组的另一个指定位置中。

array.copyWithin(target, start, end)
参数 描述
target 必需。复制到指定目标索引位置。
start 可选。元素复制的起始位置。(默认为0)
end 可选。停止复制的索引位置 (默认为 array.length)。如果为负值,表示倒数。
let numbers = [1,2,3,4,5];
numbers.copyWithin(2);
console.log('numbers: ', numbers); // [1,2,1,2,3]

numbers = [1,2,3,4,5];
numbers.copyWithin(3,0);
console.log('numbers: ', numbers); // [1,2,3,1,2]

numbers = [1,2,3,4,5];
numbers.copyWithin(2,1,3);
console.log('numbers: ', numbers); // [1,2,2,3,5]

(三)类型化数组

1. 数组缓冲区和视图

数组缓冲区:array buffer是内存中包含一定数量字节的区域。使用ArrayBuffer构造器创建数组缓冲区;

DataView:视图,操作数组缓冲区

new DataView(buffer, byteOffset, byteLength);
 
-- buffer :该视图所绑定的数组缓冲区
-- byteOffset: 字节偏移量
-- byteLength: 字节数
let buffer = new ArrayBuffer(10); // 分配了10个字节
console.log(buffer.byteLength); //10

//使用slice()方法创建一个包含已有缓冲区部分内容的新数组缓冲区
let buffer2 = buffer.slice(4,6);
console.log(buffer2.byteLength); //2

//使用视图view操作数组缓冲区
let view1 = new DataView(buffer),	// 包含所有字节
    view2 = new DataView(buffer, 5, 2);  //包含位置5和位置6的字节

console.log(view1.buffer === buffer);       // true
console.log(view2.buffer === buffer);       // true
console.log(view1.byteOffset);              // 0
console.log(view2.byteOffset);              // 5
console.log(view1.byteLength);              // 10
console.log(view2.byteLength);              // 2

读写数据:get和set方法,

读写整数:以int8和uint8为例(可将8换成16,32)

  • getInt8(byteOffset) - Read an int8 starting at byteOffset
  • setInt8(byteOffset, value) - Write an int8 starting at byteOffset
  • getUint8(byteOffset) - Read an uint8 starting at byteOffset
  • setUint8(byteOffset, value) - Write an uint8 starting at byteOffset

读写浮点数:

  • getFloat32(byteOffset, littleEndian) - Read a float32 starting at byteOffset
  • setFloat32(byteOffset, value, littleEndian) - Write a float32 starting at byteOffset
  • getFloat64(byteOffset, littleEndian) - Read a float64 starting at byteOffset
  • setFloat64(byteOffset, value, littleEndian) - Write a float64 starting at byteOffset

比整数多了个参数littleEndian,来表示是否采用小端模式,默认值为false

let buffer = new ArrayBuffer(10),
    view = new DataView(buffer);

view.setInt8(0,5);
view.setInt8(1,-1);
console.log(view.getInt8(0)); // 5
console.log(view.getInt8(1)); // -1

//视图允许使用任意格式对任意位置进行读写,而无需考虑这些数据此前使用的是什么格式存储的
console.log(view.getInt16(0)); // 1535
new ArrayBuffer(2)      00000000 00000000
view.setInt8(0, 5);     00000101 00000000
view.setInt8(1, -1);    00000101 11111111

2. 类型化数组

类型化数组即视图,可以使用类型化数组来处理特定的数据类型,而不必使用通用的DataView对象,类型化数组只能在特定的一种数据类型上工作,如Int8Array的所有操作都只能处理int8值。

BYTES_PER_ELEMENT属性:表示类型化数组的元素大小

类型化数组的构造器可接受多种类型的参数,所以创建类型化数组的方式也有多种:

  1. 同DataView,传递buffer, byteOffset, byteLength
let buffer = new ArrayBuffer(10),
    view1 = new Int8Array(buffer),
    view2 = new Int8Array(buffer, 5, 2);

console.log(view1.buffer === buffer);       // true
console.log(view2.buffer === buffer);       // true
console.log(view1.byteOffset);              // 0
console.log(view2.byteOffset);              // 5
console.log(view1.byteLength);              // 10
console.log(view2.byteLength);              // 2
  1. 同数组Array构造器,传递单个数值参数,表示该数组包含的元素数量(而不是分配的字节数),可用length获取这个元素的数量
let ints = new Int8Array(2),
    floats = new Float32Array(5);

console.log(ints.byteLength); // 2  byteLength = length * BYTES_PER_ELEMENT
console.log(ints.length); // 2
console.log(ints.BYTES_PER_ELEMENT); // 1

console.log(floats.byteLength);     // 20 byteLength = length * BYTES_PER_ELEMENT
console.log(floats.length);         // 5
console.log(floats.BYTES_PER_ELEMENT); //4
  1. 传递单个对象(类型化数组,可迭代对象, 数组, 类数组对象)参数
let ints1 = new Int16Array([25, 50]),
    ints2 = new Int32Array(ints1);

console.log(ints1.buffer === ints2.buffer);     // false

console.log(ints1.byteLength);      // 4
console.log(ints1.length);          // 2
console.log(ints1[0]);              // 25
console.log(ints1[1]);              // 50

console.log(ints2.byteLength);      // 8
console.log(ints2.length);          // 2
console.log(ints2[0]);              // 25
console.log(ints2[1]);              // 50

(四)类型化数组与常规数组的相似点

  1. 都可以使用length属性获取元素数量;(注意:类型化数组的length值是不可写的,非严格模式下写入操作会被忽略,严格模式下回抛出错误)

  2. 都可以使用数值类型的索引值来直接访问元素;

  3. 公共方法:

    • copyWithin()
    • entries()
    • fill()
    • filter()
    • find()
    • findIndex()
    • forEach()
    • indexOf()
    • join()
    • keys()
    • lastIndexOf()
    • map()
    • reduce()
    • reduceRight()
    • reverse()
    • slice()
    • some()
    • sort()
    • values()

    类型化数组的方法会进行额外的类型检查,并且返回值会根据Symbol.species属性来确定,会是某种类型化数组,而不是常规数组。

    1. 相同的迭代器:entries(), keys(), values(),可以对类型化数组使用扩展运算符(…)或者for-of循环
    2. of()from()方法:所有类型化数组都包含静态的of()from()方法,作用类似于Array.of()和Array.from()方法
    let ints = Int16Array.of(1,2,3,4);
     
    console.log(ints instanceof Int16Array);
    console.log(ints.length);
    console.log(ints[0]);
    console.log(ints[1]);
    
    let ints = Int16Array.from({
      0: 1,
      1: 2,
      length: 2
    })
    console.log(ints[0]);
    console.log(ints[1]);
    

(五)类型化数组与常规数组的区别

  1. 最大区别:类型书数组不是常规数组,不是从Array对象派生的,使用Array.isArray()会返回false
let ints = Int16Array.of(1,2,3,'4','h');
console.log(ints instanceof Array); // false
console.log(Array.isArray(ints)); // false
  1. 类型化数组大小固定不变,不能通过改变length值改变,也不能通过给一个大于长度的索引值赋值来改变大小;
let ints = Int16Array.of(1,2,3,4);
ints[4] = 5;
console.log(ints[4]); // undefined
  1. 类型化数组会对数据类型进行检查以保证只使用有效的值,当传入无效值时,会被替换为0;
let ints = Int16Array.of(1,2,3,'4','h');
console.log(ints[3]); // 4
console.log(ints[4]); // 0
  1. 遗漏的方法: 会改变数组大小的方法:pop(), push(), shift(), unshift(), splice()

    以及concat(),因为连接两个类型化数组的结果是不确定的。

  2. 增加的方法:

set(array, start) — 将其他数组中的元素复制到当前的类型化数组。

subarray(start, end) — 将当前类型化数组的部分元素提取到新的类型化数组,类似slice()方法

let ints = new Int16Array(4);
ints.set([1,2]);
console.log(ints); // [1,2,0,0]
ints.set([3,4],1);
console.log(ints); // [1,3,4,0]
let ints = new Int16Array(4);
ints.set([1,2,3,4]);

let ints2 = ints.subarray();
console.log(ints2); // [1,2,3,4]
ints2 = ints.subarray(2);
console.log(ints2); // [3,4]
ints2 = ints.subarray(1,3);
console.log(ints2); //[2,3]