学习JavaScript数据结构与算法(第3版)阅读笔记---第3章

3.2 创建和初始化数组

用 JavaScript 声明、创建和初始化数组很简单,就像下面这样。

let daysOfWeek = new Array(); // {1} 
daysOfWeek = new Array(7); // {2}
daysOfWeek = new Array('Sunday', 'Monday', 'Tuesday', 'Wednesday','Thursday', 'Friday', 'Saturday'); // {3}

使用 new 关键字简单地声明并初始化一个数组(行{1})。用这种方式,还可以创建 一个指定长度的数组(行{2})。也可以直接将数组元素作为参数传递给它的构造器 (行{3})。
只用中括号([])的形式创建一个数组,如下所示。

let daysOfWeek = [];

使用一些元素初始化数组,如下所示。

let daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday','Friday', 'Saturday'];

使用数组的 length 属性可以知道数组里已经存了多少个元素(它的大小)。

console.log(daysOfWeek.length);

访问元素和迭代数组
访问数组里特定位置的元素,可以用中括号传递数值位置,得到想知道的值或者赋新的值。
想输出数组 daysOfWeek 里的所有元素,可以通过循环迭代数组、打印元素,如 下所示。

for (let i = 0; i < daysOfWeek.length; i++) { 
    console.log(daysOfWeek[i]); 
}

例子:求斐波那契数列的前 20 个数。已知斐波那契数列中的前两项是 1, 从第三项开始,每一项都等于前两项之和。

const fibonacci = []; // {1}
fibonacci[1] = 1; // {2}
fibonacci[2] = 1; // {3}

for (let i = 3; i < 20; i++) {
    fibonacci[i] = fibonacci[i - 1] + fibonacci[i - 2]; // {4}
}
for (let i = 1; i < fibonacci.length; i++) { // {5} 
    console.log(fibonacci[i]); // {6} 
}

在行{1}处,我们声明并创建了一个数组。
在行{2}和行{3},把斐波那契数列中的前两个数分别赋给了数组的第二和第三个位置。
数组第一位的索引始终是 0。因为斐波那契数列中不存在 0,所以这里直接略过,从第二位开始分别保存斐波那契数列中对应位置的元素。
为了得到斐波那契数列中第三到第二十个位置上的数,可以用循环来处理,把数组中前两位上的元素相加,结果赋给当前位置上的元素(行{4}——从数组中的索引 3 到索引 19)。
看看输出(行{6}),只需要循环迭代数组的各个元素(行{5})。

3.3 添加元素
let numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
3.3.1 在数组末尾插入元素

如果想要给数组添加一个元素(比如 10),只要把值赋给数组中最后一个空位上的元素即可。

numbers[numbers.length] = 10;

使用 push 方法
push 方法,能把元素添加到数组的末尾。

numbers.push(11); 
numbers.push(12, 13);
3.3.2 在数组开头插入元素

在数组中插入一个新元素(数1)首先要腾出数组里第一个元素的位置,把所有的元素向右移动一 位。
循环数组中的元素,从最后一位(长度值就是数组的末尾位置)开始,将对应的前 一个元素(i-1)的值赋给它(i),最后把我们想要的值赋给第一个位置(索引 0) 上。
将该方法直接添加在 Array 的原型上,使所有数组的实例都可以访问到该方法。

Array.prototype.insertFirstPosition = function(value) { 
    for (let i = this.length; i >= 0; i--) {
        this[i] = this[i - 1];
    } 
    this[0] = value; 
}; 
numbers.insertFirstPosition(-1);



使用 unshift 方法
unshift,可以直接把数值插入数组的开头

numbers.unshift(-2); 
numbers.unshift(-4, -3);
3.4.1 从数组末尾删除元素

要删除数组里最靠后的元素,可以用 pop 方法。

numbers.pop();
3.4.2 从数组开头删除元素

要移除数组里的第一个元素,可以用下面的代码。

for (let i = 0; i < numbers.length; i++) {
    numbers[i] = numbers[i + 1];
}

下图呈现了这段代码的执行过程。

数组里所有的元素都左移了一位,但数组的长度依然是 17,在最后一次循环里,i+1 引用了数组里还未初始化的一个位置。
我们只是把数组第一位的值用第二位覆盖了,并没有删除元素。
创建一个新的数组,将所有不是 undefined 的值从原来的数组复制到新的数组中,并且将这个新的数组赋值给我们的数组。

Array.prototype.reIndex = function(myArray) { 
    const newArray = []; 
    for(let i = 0; i < myArray.length; i++ ) { 
        if (myArray[i] !== undefined) { 
            // console.log(myArray[i]); 
            newArray.push(myArray[i]); 
        } 
    } return newArray;
}

// 手动移除第一个元素并重新排序 
Array.prototype.removeFirstPosition = function() { 
    for (let i = 0; i < this.length; i++) {
        this[i] = this[i + 1];
    } 
    return this.reIndex(this); 
};

numbers = numbers.removeFirstPosition();

使用 shift 方法
要删除数组的第一个元素,可以用 shift 方法实现。

numbers.shift();

假如本来数组中的值是从4 到 12,长度为 17。执行了上述代码后,数组就只有3 到 12 了, 长度也会减小到 16。

3.5 在任意位置添加或删除元素

使用 splice 方法,简单地通过指定位置/索引,就可以删除相应位置上指定数量 的元素。

numbers.splice(5,3);

这行代码删除了从数组索引 5 开始的 3 个元素。这就意味着 numbers[5]、numbers[6]和 numbers[7]从数组中删除了。
用 delete 运算符删除数组中的元素, 例如 delete numbers[0]。数组位置 0 的值会变成 undefined,也就 是说,以上操作等同于 numbers[0] = undefined。
把数 2、3、4 插入数组里,放到之前删除元素的位置上,可以再次使用 splice 方法。

numbers.splice(5, 0, 2, 3, 4);

splice 方法接收的第一个参数,表示想要删除或插入的元素的索引值。第二个参数是删除元素的个数(这个例子里,我们的目的不是删除元素,所以传入 0)。第三个参数往后,就是要 添加到数组里的值(元素 2、3、4)。
以下代码的含义是从索引 5 开始删除了 3 个元素,再从索引 5 开始添加了元素 2、3、4。。

numbers.splice(5, 3, 2, 3, 4);
3.6 二维和多维数组

用数组套数组,实现矩阵或任一多维数组。

let averageTemp = [];
averageTemp[0] = [72, 75, 79, 79, 81, 81];
averageTemp[1] = [81, 79, 75, 75, 73, 73];

数组中的内容如下图所示。

3.6.1 迭代二维数组的元素

如果想看上面那个数组可以使用如下代码。

function printMatrix(myMatrix) { 
    for (let i = 0; i < myMatrix.length; i++) { 
        for (let j = 0; j < myMatrix[i].length; j++) { 
            console.log(myMatrix[i][j]); 
        } 
    } 
}

使用一个嵌套的 for 循环来处理,其中变量 i 为行, 变量 j 为列。每个 myMatrix[i]同样代表一个数组,因此需要在嵌套的 for 循 环中迭代 myMatrix[i]的每个位置。
可以使用以下代码来输出矩阵 averageTemp 的内容。

printMatrix(averageTemp);
3.6.2 多维数组

创建一个 3 × 3 × 3 的矩阵,每一格里包 含矩阵的 i(行)、j(列)及 z(深度)之和。

const matrix3x3x3 = [];
for (let i = 0; i < 3; i++) {
  matrix3x3x3[i] = [];
  for (let j = 0; j < 3; j++) {
    matrix3x3x3[i][j] = [];
    for (let z = 0; z < 3; z++) {
      matrix3x3x3[i][j][z] = i + j + z;
    }
  }
}

3 × 3 × 3 的矩阵立体图如下所示。

用以下代码输出这个矩阵的内容。

for (let i = 0; i < matrix3x3x3.length; i++) {
  for (let j = 0; j < matrix3x3x3[i].length; j++) {
    for (let z = 0; z < matrix3x3x3[i][j].length; z++) {
      console.log(matrix3x3x3[i][j][z]);
    }
  }
}
3.7 JavaScript 的数组方法参考

下表详述了数组的一些核心方法
方法|描述
-|-|-
concat|连接 2 个或更多数组,并返回结果
every|对数组中的每个元素运行给定函数,如果该函数对每个元素都返回 true,则返回 true
filter|对数组中的每个元素运行给定函数,返回该函数会返回 true 的元素组成的数组
forEach|对数组中的每个元素运行给定函数。这个方法没有返回值
join|将所有的数组元素连接成一个字符串
indexOf|返回第一个与给定参数相等的数组元素的索引,没有找到则返回-1
lastIndexOf|返回在数组中搜索到的与给定参数相等的元素的索引里最大的值
map|对数组中的每个元素运行给定函数,返回每次函数调用的结果组成的数组
reverse|颠倒数组中元素的顺序,原先第一个元素现在变成最后一个,同样原先的最后一个元素变成了现在 的第一个
slice|传入索引值,将数组里对应索引范围内的元素作为新数组返回
some|对数组中的每个元素运行给定函数,如果任一元素返回 true,则返回 true
sort|按照字母顺序对数组排序,支持传入指定排序方法的函数作为参数
toString|将数组作为字符串返回
valueOf|和 toString 类似,将数组作为字符串返回

3.7.1 数组合并

使用concat方法将多个数组,需要合并起来成为一个数组。

const zero = 0; 
const positiveNumbers = [1, 2, 3]; 
const negativeNumbers = [-3, -2, -1]; 
let numbers = negativeNumbers.concat(zero, positiveNumbers);

concat 方法可以向一个数组传递数组、对象或是元素。数组会按照该方法传入的参数顺序 连接指定数组。

3.7.2 迭代器函数

假设数组中的值是从 1 到 15;如果数组里的元素可以被 2 整除(偶数),函数就返回 true, 否则返回 false。

function isEven(x) { // 如果 x 是 2 的倍数,就返回 true 
    console.log(x); 
    return x % 2 === 0 ? true : false; 
}
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];

1. 用 every 方法迭代
every 方法会迭代数组中的每个元素,直到返回 false。

numbers.every(isEven);

在这个例子里,数组 numbers 的第一个元素是 1,它不是 2 的倍数(1 是奇数),因此 isEven 函数返回 false,然后 every 执行结束。
2. 用 some 方法迭代
some 方法会迭代数组的每个元素,直到函 数返回 true。

numbers.some(isEven);

这个例子里第一个被迭代的元素 是 1,isEven 会返回 false。第二个被迭代的元素是 2,isEven 返回 true——迭代结束。
3. 用 forEach 方法迭代
如果要迭代整个数组,可以用 forEach 方法。它和使用 for 循环的结果相同。

numbers.forEach(x => console.log(x % 2 === 0));

4. 使用 map 和 filter 方法
map方法返回新数组

const myMap = numbers.map(isEven);

数组 myMap 里的值是:[false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]。它保存了传入 map 方法的 isEven 函数的运行结果。
filter 方法,它返回的新数组由使函数返回 true 的元素组成。

const evenNumbers = numbers.filter(isEven);

这个例子里,evenNumbers 数组中的元素都是偶数:[2, 4, 6, 8, 10, 12, 14]。
5. 使用 reduce 方法
reduce 方法接收一个有如下四个参数的函数:previousValue、 currentValue、index 和 array。这个函数会返回一个将被叠加到累加器的值,reduce 方法停止执行后会返回 这个累加器。
如果要对一个数组中的所有元素求和,可以参考下面这个例子。

numbers.reduce((previous, current) => previous + current);
3.7.3 ECMAScript 6 和数组的新功能

下表列出了 ES2015 和 ES2016 新增的数组方法。
方法|描述
-|-|-
@@iterator|返回一个包含数组键值对的迭代器对象,可以通过同步调用得到数组元素的键值对
copyWithin|复制数组中一系列元素到同一数组指定的起始位置
entries|返回包含数组所有键值对的@@iterator
includes|如果数组中存在某个元素则返回 true,否则返回 false。E2016 新增
find|根据回调函数给定的条件从数组中查找元素,如果找到则返回该元素
findIndex|根据回调函数给定的条件从数组中查找元素,如果找到则返回该元素在数组中的索引
fill|用静态值填充数组
from|根据已有数组创建一个新数组
keys|返回包含数组所有索引的@@iterator
of|根据传入的参数创建一个新数组
values|返回包含数组中所有值的@@iterator

1. 使用 for...of 循环迭代

for (const n of numbers) { 
    console.log(n % 2 === 0 ? 'even' : 'odd'); 
}

2. 使用@@iterator 对象
ES2015 还为 Array 类增加了一个@@iterator 属性,需要通过 Symbol.iterator 来访问。 代码如下。

let iterator = numbers[Symbol.iterator](); 
console.log(iterator.next().value); // 1 
console.log(iterator.next().value); // 2 
console.log(iterator.next().value); // 3 
console.log(iterator.next().value); // 4 
console.log(iterator.next().value); // 5

不断调用迭代器的 next 方法,就能依次得到数组中的值。
可以用下面的代码来输出 numbers 数组中的 15 个值。

iterator = numbers[Symbol.iterator](); 
for (const n of iterator) { 
    console.log(n); 
}

数组中的所有值都迭代完之后,iterator.next().value 会返回 undefined。
3. 数组的 entries、keys 和 values 方法
entries 方法返回包含键值对的@@iterator,下面是使用该方法的代码示例。

let aEntries = numbers.entries(); // 得到键值对的迭代器 
console.log(aEntries.next().value); // [0, 1] - 位置 0 的值为 1 
console.log(aEntries.next().value); // [1, 2] - 位置 1 的值为 2 
console.log(aEntries.next().value); // [2, 3] - 位置 2 的值为 3

numbers 数组中都是数,key 是数组中的位置,value 是保存在数组索引的值。
也可以使用下面的代码。

aEntries = numbers.entries(); 
for (const n of aEntries) { 
    console.log(n); 
}

keys 方法返回包含数组索引的@@iterator,下面是使用该方法的代码示例。

const aKeys = numbers.keys(); // 得到数组索引的迭代器 
console.log(aKeys.next()); // {value: 0, done: false } 
console.log(aKeys.next()); // {value: 1, done: false } 
console.log(aKeys.next()); // {value: 2, done: false }

keys 方法会返回 numbers 数组的索引。一旦没有可迭代的值,aKeys.next()就会返回一 个 value 属性为 undefined、done 属性为 true 的对象。如果 done 属性的值为 false,就意 味着还有可迭代的值。
values 方法返回的@@iterator 则包含数组的值。使用这个方法的代码示例如下。

const aValues = numbers.values(); 
console.log(aValues.next()); // {value: 1, done: false } 
console.log(aValues.next()); // {value: 2, done: false } 
console.log(aValues.next()); // {value: 3, done: false }

4. 使用 from 方法
Array.from 方法根据已有的数组创建一个新数组。
要复制 numbers 数组,可以如 下这样做。

let numbers2 = Array.from(numbers);

还可以传入一个用来过滤值的函数,例子如下。

let evens = Array.from(numbers, x => (x % 2 == 0));

上面的代码会创建一个 evens 数组,以及值 true(如果在原数组中为偶数)或 false(如 果在原数组中为奇数)。
5. 使用 Array.of 方法
Array.of 方法根据传入的参数创建一个新数组。

let numbers3 = Array.of(1); 
let numbers4 = Array.of(1, 2, 3, 4, 5, 6);

它和下面这段代码的效果一样。

let numbers3 = [1]; 
let numbers4 = [1, 2, 3, 4, 5, 6];

也可以用该方法复制已有的数组,如下所示。

let numbersCopy = Array.of(...numbers4);

上面的代码和 Array.from(numbers4)的效果是一样的。
6. 使用 fill 方法
fill 方法用静态值填充数组。以下面的代码为例。

let numbersCopy = Array.of(1, 2, 3, 4, 5, 6);

numbersCopy 数组的 length 是 6,也就是有 6 个位置。再看下面的代码。

numbersCopy.fill(0);

numbersCopy 数组所有位置上的值都会变成 0([0, 0, 0, 0, 0, 0])。
还可以指定 开始填充的索引,如下所示。

numbersCopy.fill(2, 1);

上面的例子里,数组中从 1 开始的所有位置上的值都是 2([0, 2, 2, 2, 2, 2])。
也可以指定结束填充的索引。

numbersCopy.fill(1, 3, 5);

在上面的例子里,我们会把 1 填充到数组索引 3 到 5 的位置(不包括 3 和 5),得到的数组为 [0, 2, 2, 1, 1, 2]。
创建数组并初始化值的时候,fill 方法非常好用,就像下面这样。

let ones = Array(6).fill(1);

上面的代码创建了一个长度为 6、所有值都是 1 的数组([1, 1, 1, 1, 1, 1])。
7. 使用 copyWithin 方法
copyWithin 方法复制数组中的一系列元素到同一数组指定的起始位置。看看下面这个例子。

let copyArray = [1, 2, 3, 4, 5, 6];

假如我们想把 4、5、6 三个值复制到数组前三个位置,得到[4, 5, 6, 4, 5, 6]这个数 组,可以用下面的代码达到目的。

copyArray.copyWithin(0, 3);

假如我们想把 4、5 两个值复制到位置 1 和 2,可以这样做:

copyArray = [1, 2, 3, 4, 5, 6]; 
copyArray.copyWithin(1, 3, 5);

这种情况下,会把从位置 3 开始到位置 5 结束(不包括 3 和 5)的元素复制到位置 1,结果 是得到数组[1, 4, 5, 4, 5, 6]。
3.7.4 排序元素
首先,我们想反序输出数组 numbers(它本来的排序是 1, 2, 3, 4, ..., 15)。要实现 这样的功能,可以用 reverse 方法,然后数组内元素就会反序。

numbers.reverse();

现在,输出 numbers 的话就会看到[15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]。然后,我们使用 sort 方法。

numbers.sort();

然而,如果输出数组,结果会是[1, 10, 11, 12, 13, 14, 15, 2, 3, 4, 5, 6, 7, 8, 9]。因为 sort 方法在对数组做排序时,把元素默认成字符串进行相 互比较。
可以传入自定义的比较函数.

numbers.sort((a, b) => a - b);

之前的代码可以改成下面这个样子。

function compare(a, b) {
    if (a < b) {
        return -1;//如果返回-1,那么第一个参数a出现在第二个参数b的前面
    }
    if (a > b) {
        return 1;//如果返回1,那么第一个参数a出现在第二个参数b的后面
    } 
    // a 必须等于 b 
    return 0;
} 
numbers.sort(compare);

1. 自定义排序
例如, 对象 Person 有名字和年龄属性,我们希望根据年龄排序,就可以这么写。

const friends = [ 
    { name: 'John', age: 30 }, 
    { name: 'Ana', age: 20 }, 
    { name: 'Chris', age: 25 }, // ES2017 允许存在尾逗号 
]; 
function comparePerson(a, b) { 
    if (a.age < b.age) { 
        return -1; 
    } 
    if (a.age > b.age) { 
        return 1; 
    } 
    return 0;
} 
console.log(friends.sort(comparePerson));

在这个例子里,最后会输出 Ana(20), Chris(25), John(30)。
2. 字符串排序

let names = ['Ana', 'ana', 'john', 'John'];
console.log(names.sort());

因为 JavaScript 在做字 符比较的时候,是根据字符对应的 ASCII 值来比较的。例如,A、J、a、j 对应的 ASCII 值分别 是 65、74、97、106。所以答案如下所示。

["Ana", "John", "ana", "john"]

如果给 sort 传入一个忽略大小写的比较函数,将输出["Ana", "ana", "John", "john"]。

names = ['Ana', 'ana', 'john', 'John']; // 重置数组的初始状态 
console.log(names.sort((a, b) => { 
    if (a.toLowerCase() < b.toLowerCase()) { 
        return -1; 
    } 
    if (a.toLowerCase() > b.toLowerCase()) { 
        return 1; 
    } 
    return 0; 
}));

如果希望小写字母排在前面,那么需要使用 localeCompare 方法。

names.sort((a, b) => a.localeCompare(b));

输出结果将是["ana", "Ana", "john", "John"]。
假如对带有重音符号的字符做排序的话,也可以用 localeCompare 来实现。

const names2 = ['Maève', 'Maeve']; 
console.log(names2.sort((a, b) => a.localeCompare(b)));

最后输出的结果将是["Maeve", "Maève"]。
1. ECMAScript 2015——find 和 findIndex 方法

let numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]; 
function multipleOf13(element, index, array) { 
    return (element % 13 == 0); 
} 
console.log(numbers.find(multipleOf13)); 
console.log(numbers.findIndex(multipleOf13));

find 和 findIndex 方法接收一个回调函数,搜索一个满足回调函数条件的值。上面的例子 里,我们要从数组里找一个 13 的倍数。
find 方法返回第一个满足条件的值,findIndex 方法则返回这个值在数组里的索引。 如果没有满足条件的值, find 会返回 undefined , 而 findIndex 返回-1。
2. ECMAScript 7——使用 includes 方法
如果数组里存在某个元素,includes 方法会返回 true,否则返回 false。

console.log(numbers.includes(15)); 
console.log(numbers.includes(20));

例子里的 includes(15)返回 true,includes(20)返回 false,因为 numbers 数组里没 有 20。
如果给 includes 方法传入一个起始索引,搜索会从索引指定的位置开始。

let numbers2 = [7,6,5,4,3,2,1]; 
console.log(numbers2.includes(4,5));

上面的例子输出为 false,因为数组索引 5 之后的元素不包含 4。
3.7.6 输出数组为字符串
如果想把数组里所有元素输出为一个字符串,可以用 toString 方法。

console.log(numbers.toString());

如果想用一个不同的分隔符(比如 )把元素隔开,可以用 join 方法。

const numbersString = numbers.join('-'); 
console.log(numbersString);
3.8 类型数组

类型数组则用于存储单一类型的数据。 它的语法是 let myArray = new TypedArray (length),其中 TypedArray 需替换为下表所列之一。
类型数组|数据类型
-|-|-
Int8Array | 8 位二进制补码整数
Uint8Array | 8 位无符号整数
Uint8ClampedArray | 8 位无符号整数
Int16Array | 16 位二进制补码整数
Uint16Array | 16 位无符号整数
Int32Array | 32 位二进制补码整数
Uint32Array | 32 位无符号整数
Float32Array | 32 位 IEEE 浮点数
Float64Array | 64 位 IEEE 浮点数

代码示例如下。

let length = 5; 
let int16 = new Int16Array(length);

let array16 = []; 
array16.length = length;

for (let i=0; i<length; i++){
    int16[i] = i+1;
} 
console.log(int16);

使用 WebGL API、进行位操作、处理文件和图像时,类型数组都可以大展拳脚。

3.9 TypeScript 中的数组

对 friends 数组的排序示例,我们可以用 TypeScript 将代码重构成如下这样。

interface Person { 
    name: string; 
    age: number; 
}

// const friends: {name: string, age: number}[]; 
const friends = [
    { name: 'John', age: 30 },
    { name: 'Ana', age: 20 },
    { name: 'Chris', age: 25 } 
];
function comparePerson(a: Person, b: Person) { 
    // comparePerson 函数的内容 
}

通过声明 Person 接口,我们确保了 comparePerson 函数只接收包含 name 和 age 属性的 对象。friends 数组也可以在本例中通过 const friends: Person[]显 式声明它的类型。
用 TypeScript 给 JavaScript 变量设置类型,只需要使用 const 或 let variableName: []

posted on 2020-04-12 15:18  donokamark  阅读(293)  评论(0编辑  收藏  举报