黄子涵

第7章 数据处理

绩效

章节 代码量(行)
7.1

7.1 数组

数组是一种有序元素的集合。在 JavaScript 中,数组的长度是可变的。只要将元素加入数组的尾部,数组的长度就会自动增加。同时,也能够自由改写数组中的每一个元素。其实这并不值得惊讶,反而是理所应当的,因为在 JavaScript中数组也是一种对象。数组只不过是继承了 JavaScript 的对象的一些性质而已。

7.1.1 JavaScript 的数组

在 JavaScript 中,数组可以通过字面量与 new 表达式两种方法生成。通过 new 表达式的生成将在下一节中说明,这里先展示一个数组字面量的例子。

// 数组字面量的例子
var hzh1 = [3, 4, 5];
console.log("hzh1的数据类型是:");
console.log(typeof hzh1); // 对数组进行typeof运算之后是object
[Running] node "e:\HMV\JavaScript\JavaScript.js"
hzh1的数据类型是:
object
[Done] exited with code=0 in 2.555 seconds

数组字面量的书写方式是在中括号([])中列出数组元素,并通过逗号相分隔。不含有元素的数组的长度为零。

var hzh2 = [];
console.log("hzh2的长度是:" + hzh2.length);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
hzh2的长度是:0
[Done] exited with code=0 in 0.926 seconds

在 JavaScript 中,我们通常会先生成一个长度为零的数组,之后再向其中添加元素。

var hzh4 = [];
hzh4[0] = "黄子涵";
hzh4[1] = "是帅哥!"
console.log("hzh[0] = " + hzh4[0]);
console.log("hzh[1] = " + hzh4[1]);
console.log("hzh[0] + hzh[0] = " + (hzh4[0] + hzh4[1]));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
hzh[0] = 黄子涵
hzh[1] = 是帅哥!
hzh[0] + hzh[0] = 黄子涵是帅哥!
[Done] exited with code=0 in 8.728 seconds

在 JavaScript 中,我们可以将任意的值或者对象的引用指定为元素,并且不需要确保数组中元素类型的一致性。由于已经知道了可以将任意类型的值赋值给某一变量,因此或许大家不会对这一特性感到惊讶,不过,这确实是与 Java 数列的不同之处。在 Java 中,原则上同一数组中的元素必须类型一致。一方面,JavaScript 的高自由度确实很方便,但另一方面也可能会造成由于意外的数据类型转换而引起的元素赋值错误,所以对此请多加注意。

// 不需要确保各个元素的类型一致。
var hzh5 = '黄子涵';
var hzh6 = [1, '黄子涵', hzh5, true, null, undefined,
{hzh7: 3, hzh8: 4}, [2, 'hzh9'],
function(hzh10, hzh11) {
return Number(hzh10) + Number(hzh11);
}
];
console.log("hzh6 = " + hzh6);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
hzh6 = 1,黄子涵,黄子涵,true,,,[object Object],2,hzh9,function(hzh10, hzh11) {
return Number(hzh10) + Number(hzh11);
}
[Done] exited with code=0 in 0.795 seconds

在书写数组字面量时,还可以省略一些中间的元素。被省略元素的值将被认为是 undefined 值。

// 中间元素被省略的数组,。被省略元素的值将被认为是 undefined 值。
var hzh12 = [3, ,5];
console.log("hzh12[0] = " + hzh12[0]);
console.log("hzh12[1] = " + hzh12[1]);
console.log("hzh12[2] = " + hzh12[2]);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
hzh12[0] = 3
hzh12[1] = undefined
hzh12[2] = 5
[Done] exited with code=0 in 0.196 seconds

在 ECMAScript 中,如果像下面这样在书写数组字面量时以逗号作为结尾,则该逗号将会被忽略。不过在旧版本的Internet Explorer 中,这一会引起错误的原因已经广为人知了。因此,应该避免在数组的最后使用逗号。

var hzh13 = [3, 4, ];
console.log("hzh13[0] = " + hzh13[0]);
console.log("hzh13[1] = " + hzh13[1]);
console.log("hzh13[2] = " + hzh13[2]);
console.log("hzh13的长度: " + hzh13.length);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
hzh13[0] = 3
hzh13[1] = 4
hzh13[2] = undefined
hzh13的长度: 2
[Done] exited with code=0 in 0.285 seconds

7.1.2 数组元素的访问

可以通过中括号运算符([] 运算符)来访问数组的元素,[] 内所写的是下标的数值。下标由 0 开始。如果该下标没有相对应的元素,则会获得 undefined 值。

// 使用数组的例子
var hzh1 = [3, 4, 5];
console.log("分别输出hzh数组各个元素:");
console.log("hzh1[0] = " + hzh1[0]);
console.log("hzh1[1] = " + hzh1[1]);
console.log("hzh1[2] = " + hzh1[2]);
console.log("hzh1[3] = " + hzh1[3]);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
分别输出hzh数组各个元素:
hzh1[0] = 3
hzh1[1] = 4
hzh1[2] = 5
hzh1[3] = undefined
[Done] exited with code=0 in 1.122 seconds

可以将任何结果为数值的表达式作为下标使用。从内部来看,作为下标的表达式将被作为字符串来求值,然后以数值的方式来使用。因此,像下面这样,把能够被解释为数值的字符串作为下标来使用也没问题。不过,这样一来,代码的可读性会变差,因此并不推荐这种做法。

// 使用数组的例子
var huangzihan = [3, 4, 5];
var hzh0 = '0';
var hzh1 = '1';
var hzh2 = '2';
var hzh3 = '3';
console.log("分别输出huangzihan数组各个元素:");
console.log("huangzihan[0] = " + huangzihan[hzh0]);
console.log("huangzihan[1] = " + huangzihan[hzh1]);
console.log("huangzihan[2] = " + huangzihan[hzh2]);
console.log("huangzihan[3] = " + huangzihan[hzh3]);
console.log("");
console.log("输出huangzihan数组的第11个元素:");
console.log("huangzihan[11] = " + huangzihan[hzh1 + 1]);
console.log("输出huangzihan数组的第22个元素:");
console.log("huangzihan[22] = " + huangzihan[hzh2 + 2]);
console.log("输出huangzihan数组的第33个元素:");
console.log("huangzihan[33] = " + huangzihan[hzh3 + 3]);
console.log("");
var zero = { toString:function() { return '0'} };
var one = { toString:function() { return '1'} };
var two = { toString:function() { return '2'} };
var three = { toString:function() { return '3'} };
console.log("分别输出huangzihan数组各个元素:");
console.log("huangzihan[0] = " + huangzihan[zero]);
console.log("huangzihan[1] = " + huangzihan[one]);
console.log("huangzihan[2] = " + huangzihan[two]);
console.log("huangzihan[3] = " + huangzihan[three]);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
分别输出huangzihan数组各个元素:
huangzihan[0] = 3
huangzihan[1] = 4
huangzihan[2] = 5
huangzihan[3] = undefined
输出huangzihan数组的第11个元素:
huangzihan[11] = undefined
输出huangzihan数组的第22个元素:
huangzihan[22] = undefined
输出huangzihan数组的第33个元素:
huangzihan[33] = undefined
分别输出huangzihan数组各个元素:
huangzihan[0] = 3
huangzihan[1] = 4
huangzihan[2] = 5
huangzihan[3] = undefined
[Done] exited with code=0 in 0.169 seconds

如果将中括号运算符写在赋值表达式的左侧,则可以改写相应的元素。

// 改写数组的元素
var huangzihan = [3, 4, 5];
huangzihan[2] = huangzihan[2] * 2; // 改写下标为2的元素的值
console.log("输出huangzihan数组:");
console.log(huangzihan);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出huangzihan数组:
[ 3, 4, 10 ]
[Done] exited with code=0 in 0.174 seconds

如果在赋值表达式左侧所写的下标超过了元素数量,则会向数组增加新的元素。新增的元素下标值不必紧接着现有元素的个数。如果访问中间被跳过的元素,则会返回 undefined 值。

// 改写数组的元素
var huangzihan = [3, 4, 5];
huangzihan[3] = 20;
// 元素数量为3的时候,如果赋值给下标为3的元素(第4个元素),就会新增一个元素。
console.log("数组huangzihan:");
console.log(huangzihan);
console.log("");
// 如果对下标为10(第11个元素)进行赋值,元素的数量就会变为11
huangzihan[10] = 100;
console.log("数组huangzihan:");
console.log(huangzihan);
console.log("数组huangzihan的长度:");
console.log(huangzihan.length);
console.log("");
//如果访问被跳过的元素,则会返回 undefined 值
console.log("huangzihan[4] = " + huangzihan[4]);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
数组huangzihan:
[ 3, 4, 5, 20 ]
数组huangzihan:
[ 3, 4, 5, 20, <6 empty items>, 100 ]
数组huangzihan的长度:
11
huangzihan[4] = undefined
[Done] exited with code=0 in 0.18 seconds

7.1.3 数组的长度

在一个数组之后写上点运算符与 length 就能够获得该数组的长度。数组的长度值为数组中最后一个元素的下标值加 1。之所以用这样稍显复杂的表达方式,是因为如果生成的是元素之间存在间隙的数组,元素的数量与数组的长度不同。请看下面的具体示例。

var hzh1 = [2,,,,,3];
// 与元素数量不同,数组的长度为
console.log("数组hzh1的长度:" + hzh1.length);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
数组hzh1的长度:6
[Done] exited with code=0 in 0.367 seconds

在向末尾添加了元素之后,length 的值将会自动增加(代码清单 7.1)。如果在添加元素时跳过了一些中间元素,length 的值则是最后的那个元素的下标值减去 1。

代码清单 7.1 数组的length值的自动计算
var hzh2 = ['zero', 'one', 'two'];
console.log("数组hzh2第一次长度:" + hzh2.length);
// 借助hzh2.length向数组的末尾添加元素是一种习惯用法
hzh2[hzh2.length] = 'three';
console.log("数组hzh2第二次长度:" + hzh2.length);
hzh2[100] = '黄子涵';
console.log("数组hzh2第二次长度:" + hzh2.length);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
数组hzh2第一次长度:3
数组hzh2第二次长度:4
数组hzh2第二次长度:101
[Done] exited with code=0 in 0.186 seconds

还可以显式地更改 length 的值(代码清单 7.2)。在进行改写之后数组的长度也会相应发生改变。如果该值变小,超出部分的元素将被舍去。如果该值变大,新增部分的元素将是 undefined 值。

代码清单 7.2 更改数组的长度
var hzh3 = ['zero', 'one', 'two'];
hzh3.length = 2; // 将数组的长度缩短
console.log("长度缩短后的数组:");
console.log(hzh3); // 最后一个元素将会丢失
console.log("");
hzh3.length = 3; // 恢复(加长)数组至原来的长度
console.log("加长后的数组:");
console.log(hzh3); // 新增的部分是undefined值
console.log("");
console.log("查看新增部分的数据类型:");
console.log(typeof hzh3[2]);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
长度缩短后的数组:
[ 'zero', 'one' ]
加长后的数组:
[ 'zero', 'one', <1 empty item> ]
查看新增部分的数据类型:
undefined
[Done] exited with code=0 in 0.165 seconds

从内部来看,数组长度就是 length 属性,所以也可以像下面这样,通过中括号运算符对其访问。不过,这除了增加代码长度之外没有任何好处,所以一般并不会这样使用。

var hzh3 = ['zero', 'one', 'two'];
console.log("使用中括号检查数组长度:");
console.log(hzh3['length']);
console.log("");
console.log("使用length属性检查数组长度:");
console.log(hzh3.length);
console.log("");
console.log("判断上面两个是不是相等的:");
console.log(hzh3['length'] == hzh3.length);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
使用中括号检查数组长度:
3
使用length属性检查数组长度:
3
判断上面两个是不是相等的:
true
[Done] exited with code=0 in 0.162 seconds

7.1.4 数组元素的枚举

for 语句是最常用的数组元素的枚举方式。下面是一个例子。

var hzh1 = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log("枚举数组hzh1的元素:");
for(var i = 0, len = hzh1.length; i < len; i++) {
console.log(hzh1[i]);
}
[Running] node "e:\HMV\JavaScript\JavaScript.js"
枚举数组hzh1的元素:
1
2
3
4
5
6
7
8
9
[Done] exited with code=0 in 0.194 seconds

虽然通过 for in 语句或 for each in 语句也可以枚举数组元素,但它们无法保证枚举的顺序。如果要确保枚举按期望的顺序进行,请使用 for 语句。

除了使用 for 语句这样的循环语句(loop 语句),还有一些可以按顺序调用数组中各个元素的方法。可以通过这样的方法来实现数组元素的枚举。由于其内部机制仍然是一种循环语句,所以也被称为内部循环。

专栏

数组长度的上限

在 ECMAScript 中,JavaScript 的数组长度的上限是 2 的32 次方。这与 JavaScript 中数值的上限值是不同的,请加以注意。2 的 32 次方以上的数值仅会被识别为属性名而非数值。因此,虽然看似能够使用大于 2 的 32 次方的数字来新增元素,但这并不是数组的元素,所以 length的值并不会因此而自动增加。如果超过了边界值就可能引发严重的错误。

var hzh1 = [];
hzh1[Math.pow(2, 32) - 2] = '';
console.log("输出数组的长度:");
console.log(hzh1.length); // 数组长度为2^32 - 1
console.log("");
hzh1[Math.pow(2, 32) - 1] = ''; // 尽管看起来似乎是成功增加了元素
console.log("看有没有增加元素:");
console.log(hzh1.length);
console.log("");
console.log("2^32-1以属性的形式存在,而没有被识别为数组元素的下标:")
console.log(Object.keys(hzh1));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出数组的长度:
4294967295
看有没有增加元素:
4294967295
2^32-1以属性的形式存在,而没有被识别为数组元素的下标:
[ '4294967294', '4294967295' ]
[Done] exited with code=0 in 0.191 seconds

ECMAScript 第 5 版有多个这种类型的内部循环方法。下面将对其中最具代表性的 forEach 方法进行介绍。forEach 方法的参数应该是一个能够被数组中的各个元素调用的函数(回调函数)。

下面这样的代码能够实现对数组中所有元素的枚举

Array.forEAch(function(e) {console.log(e);})

有三个参数被传递给了回调函数,它们分别是元素、下标值以及数组对象。下面是一个具体的例子。

var hzh1 = [
'黄子涵是帅哥!',
'黄子涵是靓仔!',
'黄子涵真厉害!',
'黄子涵真聪明!'
];
// 回调函数的参数
// 参数 e :元素值
// 参数 i :下标值
// 参数 a :数组对象
hzh1.forEach(function(e, i, a) {
console.log(i, e);
})
[Running] node "e:\HMV\JavaScript\JavaScript.js"
0 黄子涵是帅哥!
1 黄子涵是靓仔!
2 黄子涵真厉害!
3 黄子涵真聪明!
[Done] exited with code=0 in 0.262 seconds

还可以将回调函数内的 this 引用所指向的对象指定为 forEach 的第 2 参数。

7.1.5 多维数组

由于任意内容都可以被指定为数组的元素,因此数组本身自然也可以成为另一个数组的元素。某个值若被指定为了数组的元素,可以像下面这样,通过连续使用多个 [] 运算符来访问元素。数组甚至还可以将其自身作为该数组的元素。

var hzh1 = [1, ['hzh2', 'hzh3', 'hzh4', 'hzh5']];
console.log("分别输出hzh1数组的各个元素:");
console.log("hzh1[0] = " + hzh1[0]);
console.log("hzh1[1][0] = " + hzh1[1][0]);
console.log("hzh1[1][1] = " + hzh1[1][1]);
console.log("hzh1[1][2] = " + hzh1[1][2]);
console.log("hzh1[1][3] = " + hzh1[1][3]);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
分别输出hzh1数组的各个元素:
hzh1[0] = 1
hzh1[1][0] = hzh2
hzh1[1][1] = hzh3
hzh1[1][2] = hzh4
hzh1[1][3] = hzh5
[Done] exited with code=0 in 0.164 seconds

7.1.6 数组是一种对象

在 JavaScript 中,数组是一种对象。从内部来看,它是Array 对象(Array 类)的对象实例。因此,也可以通过 new 表达式来调用 Array 的构造函数以生成数组。

根据具体情况,可以以不同的方式来解释传递给 Array 构造函数的参数。如果参数的数量为 1 且是一个数值,它的含义是数组的长度(元素数量);如果参数的数量大于等于 2,则这些参数代表的是数组的元素。请看代码清单 7.3 的具体例子。

代码清单 7.3 调用Array构造函数的例子
// 对于参数只有1个的情况,该参数将会成为数组的长度
var hzh1 = new Array(5);
console.log("数组hzh1:");
console.log(hzh1);
console.log("");
// 参数将会成为数组的元素
var hzh2 = new Array(3, 4, 'huangzihan');
console.log("数组hzh2:");
console.log(hzh2);
console.log("");
// 由于不会发生隐式的数据类型转换而将该参数转换为数值类型,
// 因此这一参数将被认为是数组中下标为0的元素
var hzh3 = new Array('5');
console.log("数组hzh3:");
console.log(hzh3);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
数组hzh1:
[ <5 empty items> ]
数组hzh2:
[ 3, 4, 'huangzihan' ]
数组hzh3:
[ '5' ]
[Done] exited with code=0 in 0.172 seconds

虽然前文介绍了通过 new 表达式生成数组的方式,不过,如果没有特别的理由,最好还是使用字面量表达式来生成数组,因为通过字面量的表达方式更为简单。通过数组字面量表达式生成的数组也是 Array 的实例对象,可以像下面这样对这一点进行确认。

var arr = []; // 通过数组字面量来生成数组对象
arr.constructor; // 实际上这与 new Array() 所生成的对象没有区别
function Array() {
[native code]
}

在通过 new 表达式生成数组时,根据参数数量的不同,参数的含义也会发生改变,而这常常会引起错误。为了避免发生预料之外的错误,建议不要使用这一方式。不过在一些特定情况下,使用 new 表达式反而更好。例如,在生成数组的同时指定数组长度时,new 表达式更为方便。

举例来说,通过数组字面量来生成一个元素数量为 100 且每个元素的值都未定的数组,虽然也并非无法做到(需要在 [] 内书写 100 个),但会非常麻烦。这时最好使用 new 表达式。此外,由于在新增元素时数组的长度将会自动增加,所以并不一定要在生成数组的同时指定数组的长度。之所以要求同时指定数组的长度,一方面是为了提高执行效率,另一方面是为了使数组的意义更为明确从而提高代码的可读性。

下面是对数组对象的方法进行调用的例子。

// 对数组对象的方法进行调用的例子
var hzh4 = ['zero', 'one', 'two'];
// 对join方法进行调用
console.log("hzh4.join('_') = " + (hzh4.join('_')));
console.log("");
// 也可以直接对数组字面量进行方法调用
console.log("[3, 4, 5].join('_') = " + ([3, 4, 5].join('_')));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
hzh4.join('_') = zero_one_two
[3, 4, 5].join('_') = 3_4_5
[Done] exited with code=0 in 0.161 seconds

中括号运算符在这里的作用是用于访问数组的元素,其实,这就是在访问对象的属性。也就是说,从内部来看,下标值0或1之类的数值其实是数组对象的属性名。可以像代码清单 7.4 中那样通过多种手段确认。

在代码清单 7.4 中可以看到,length 也是属性名之一。不过 for 语句并不会对其枚举。这是因为 length 属性的 enumerable这一属性为假。

代码清单 7.4 数组的属性
var hzh5 = ['zero', 'one', 'two'];
console.log("枚举数组hzh5的属性值:");
for (var n in hzh5) {
console.log(n); // 下标值的枚举,即属性名的枚举
}
console.log("属性名的枚举:" + (Object.keys(hzh5)));
console.log("属性名的枚举(忽略enumerable属性):" +
(Object.getOwnPropertyNames(hzh5)));
// 对下标0是否存在进行检验
console.log("下标0是否存在:" + ('0' in hzh5));
// 数值0将会被转换为字符串型'0'以进行检验
console.log("对数值0的检验:" + (0 in hzh5));
// 对length属性是否存在进行检验
console.log("对length属性进行检验:" + ('length' in hzh5));
[Running] node "e:\HMV\JavaScript\tempCodeRunnerFile.js"
枚举数组hzh5的属性值:
0
1
2
属性名的枚举:0,1,2
属性名的枚举(忽略enumerable属性):0,1,2,length
下标0是否存在:true
对数值0的检验:true
对length属性进行检验:true
[Done] exited with code=0 in 0.208 seconds

5.9 节已经说明了在 JavaScript 将对象作为关联数组使用的情况。如果以这种方式来解释的话JavaScript 中的数组就可以被看作键值恰巧是连续数值的关联数组。此外需要说明的是,如果没有以正整数对一个数组对象进行 [] 运算,该值就会被解释为属性名,而进行属性访问操作。请看下面的例子。

var hzh6 = ['zero', 'one', 'two'];
hzh6.x = 'X'; // 向数组对象中添加属性x
console.log("枚举数组hzh6的属性值:");
for(var p in hzh6) {
console.log(p);
}
[Running] node "e:\HMV\JavaScript\JavaScript.js"
枚举数组hzh6的属性值:
0
1
2
x
[Done] exited with code=0 in 0.174 seconds

7.1.7 Array 类

表 7.1 Array 类的函数以及构造函数调用

image

表 7.2 Array 类的属性

image

表 7.3 Array.prototype 对象的属性

image

表 7.4 Array 类的实例属性

image

7.1.8 数组对象的意义

数组是一种对象,且用于元素访问的下标值是其相应的属性名。

那么,像下面这样,通过对象字面量生成的对象和数组是否是等同的呢?

var hzh1 = {
0: "黄子涵是帅哥!",
1: "黄子涵是靓仔!",
2: "黄子涵真聪明!",
3: "黄子涵真厉害!",
}
// 它是否与 hzh1 = [ "黄子涵是帅哥!", "黄子涵是靓仔!",
// "黄子涵真聪明!","黄子涵真厉害!"]是否相同?
console.log("检查一下它们两个是否一样:");
console.log(hzh1[1]); // 在这条语句中,这两者貌似是一样的
[Running] node "e:\HMV\JavaScript\JavaScript.js"
检查一下它们两个是否一样:
黄子涵是靓仔!
[Done] exited with code=0 in 0.325 seconds

结论就是,上面的这一对象(关联数组)并不是数组。

第一个不同点在于 length 属性的 enumerable 属性。

另一个不同点则是数组的 length 属性的值会自动增加。对于数组来说,在新增加元素时,length 属性将会自动增加。通过 push 方法或 unshift 方法来增加元素,也能使上述的 hzh1 对象产生相同的结果,但仅通过对元素赋值,则无法实现增加 length 属性的值的效果。

【评】上面这些话还可以检测一下。

7.1.9 数组的习惯用法

排序

可以通过 sort 方法对数组的元素值进行排序。如果不使用参数来调用 sort 方法,则会将其作为字符串进行排序。当以字符串的形式进行排序时,排序是通过对 Unicode的编码值的大小比较来实现的。下面是个具体例子。

var hzh1 = ['1', '9', '2', '5', '3', '6', '7', '4', '8'];
console.log("对数组hzh进行排序:");
console.log(hzh1.sort());
console.log("");
var hzh2 = ['黄子涵', '尤雨溪', '彭于晏', '吴彦祖', '周杰伦'];
console.log("对数组hzh2进行排序:");
console.log(hzh2.sort());
console.log("");
var hzh3 = ['h', 'Z', 'H', 'y', 'X', 'p', 'Y', 'w', 'J', 'l'];
console.log("对数组hzh3进行排序:");
console.log(hzh3.sort());
[Running] node "e:\HMV\JavaScript\JavaScript.js"
对数组hzh进行排序:
[
'1', '2', '3',
'4', '5', '6',
'7', '8', '9'
]
对数组hzh2进行排序:
[ '吴彦祖', '周杰伦', '尤雨溪', '彭于晏', '黄子涵' ]
对数组hzh3进行排序:
[
'H', 'J', 'X', 'Y',
'Z', 'h', 'l', 'p',
'w', 'y'
]
[Done] exited with code=0 in 0.165 seconds

排序之后,数组将改变。

如果要以字符串之外的方式对数组进行排序,则需要将比较函数作为参数传递给 sort 方法。如果是一个由数值组成的数组,则可以使用下面的比较函数。使用默认的字符串排序方式对数值进行排序时,虽然对于个位数的数值可以获得预期的结果。但是也会产生 10 比 2 更小这样的结果(请确认 '10'>'2' 的结果)。因此需要注意,不能仅仅因为字符串形式的排序在个位数的情况下能得到正确的结果,而误以为 它也适用于所有的数值排序。

// 数值组成的数组的排序
var hzh = [1, 0, 20, 100, 55];
console.log("输出排序前的数组:");
console.log(hzh);
console.log("");
console.log("输出排序后的数组:");
console.log(hzh.sort(function(a,b) { return a - b; }));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出排序前的数组:
[ 1, 0, 20, 100, 55 ]
输出排序后的数组:
[ 0, 1, 20, 55, 100 ]
[Done] exited with code=0 in 0.169 seconds

数组中的每个元素都会在排序时,调用被传递至 sort 方法的参数的比较函数。函数在接受了两个元素的值之后将会返回比较的结果。以 x 与 y 为例,如果 x 比 y 大,则会返回正值。也就是说,如果排序时 x 在 y 之后出现,则会返回一个正值 1 。反之则会返回一个负值。如果值的顺序相同,则会返回 0。

上面的说明可能会不容易理解,总之对于数值的情况,如果返回上面这样的减法运算结果,就能够获得符合的结果了。

sort 方法在调换元素时会对这一数组进行改变。改写目标对象的方法被称为破坏性的方法。在JavaScript 中,数组含有很多破坏性的方法。下面这些都是破坏性的方法。

  • pop、push、reverse、shift、sort、splice、unshift

如果只了解 JavaScript,有可能会想当然地认为数组元素顺序发生改变是理所应当的。然而,不对目标数组进行更改而完成排序,并返回一个新的数组的非破坏性的实现方式也是存在的。通常来说,破坏性的方法很容易引起错误,所以应尽可能避免使用。此外,可以使用在 freeze 方法来防止数组发生意外改变。

使用 sort 方法对被 freeze 的数组排序的话,会像下面这样引发错误。

var hzh1 = ['1', '9', '2', '5', '3', '6', '7', '4', '8'];
Object.freeze(hzh1);
console.log(hzh1.sort());
[Running] node "e:\HMV\JavaScript\JavaScript.js"
e:\HMV\JavaScript\JavaScript.js:3
console.log(hzh1.sort());
^
TypeError: Cannot assign to read only property '0' of object '[object Array]'
at Array.sort (<anonymous>)
at Object.<anonymous> (e:\HMV\JavaScript\JavaScript.js:3:18)
at Module._compile (internal/modules/cjs/loader.js:999:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
at Module.load (internal/modules/cjs/loader.js:863:32)
at Function.Module._load (internal/modules/cjs/loader.js:708:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12)
at internal/main/run_main_module.js:17:47
[Done] exited with code=1 in 0.166 seconds
通过数组生成字符串

接下来,我们将介绍一种通过 push 与 join 的组合来生成字符串的常用方法。可以通过数组来实现将字符串一部分一部分地拼接起来,生成一个新的字符串。将每一个部分的字符串全都 push 至数组之后,通过 join 将它们拼接成一个字符串。大家普遍认为,这种方法比通过字符串拼接运算(+ 运算或是 += 运算)更为迅速。

不过,运行速度会随着具体的实现而有所不同,因此也不能盲目认为这种方法的性能更好。如有必要,还是亲自测量速度为好。不过由于现在主流的观点都认为该方法性能更优,所以很多现有的代码都采用了这种方法。因此,即使并不需要使用该方法,也有必要读懂其含义。下面是个具体例子。

var hzh = [];
hzh.push('<div>');
hzh.push('黄');
hzh.push('子');
hzh.push('涵');
hzh.push(Date());
hzh.push('</div>');
console.log("将上面的字符串进行拼接:");
console.log(hzh.join(''));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
将上面的字符串进行拼接:
<div>黄子涵Sat May 07 2022 13:45:16 GMT+0800 (GMT+08:00)</div>
[Done] exited with code=0 in 0.169 seconds

join 的参数是在拼接字符串时用于分隔每个部分的字符。在上面的例子中传递了一个空字符,所以实际上没有用到分割字符。如果不传递给 join 参数,则默认的分割字符是逗号字符。

与 join 相对应的逆转换是 String 类的 split 方法。它根据分割字符将字符串分割,之后将每一部分的字符串作为元素加入数组,并将该数组返回。split 的第 1 个参数是用于表示分割字符的字符串值,也可以使用正则表达式。

下面是一个以空格作为分割字符来生成数组的具体例子。

var hzh1 = "黄 子 涵 是 帅 哥 !"
console.log("使用空格分割字符串hzh1:");
console.log(hzh1.split('')); // 通过空格对字符串进行分割
console.log("");
var hzh2 = '黄 子 涵 是 靓 仔 !'
console.log("使用空格的正则表达式分割字符串hzh2:");
console.log(hzh2.split(/\s/)); // 通过空格(以正则表达式的形式)对字符串进行分割
[Running] node "e:\HMV\JavaScript\JavaScript.js"
使用空格分割字符串hzh1:
[
'黄', ' ', '子', ' ',
'涵', ' ', '是', ' ',
'帅', ' ', '哥', ' ',
'!'
]
使用空格的正则表达式分割字符串hzh2:
[
'黄', '子',
'涵', '是',
'靓', '仔',
'!'
]
[Done] exited with code=0 in 0.189 seconds
数组的复制

下面我们来考虑数组的复制。在很多情况下,与复制及破坏性的方法相关的错误非常常见,想必很多人都曾经遇到过。数组也不例外。

由于在数组的赋值时代入的只是其引用,因此实际上并没有复制数组的元素。仅仅是将某一个变量指向了同一个数组实体而已。因为数组是一种对象,所以这一结果是必然的。下面是一个具体例子。

var hzh1 = [3, 5, 4];
var hzh2 = hzh1; // 从变量hzh2的角度来看,它含有和hzh1相同的元素
console.log("输出变量hzh2的值:");
console.log(hzh2);
console.log("");
hzh2[0] = 123; // 通过变量hzh2来修改数组的元素
console.log("输出hzh1数组:"); // 在变量hzh1处也能反映出这一修改
console.log(hzh1);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出变量hzh2的值:
[ 3, 5, 4 ]
输出hzh1数组:
[ 123, 5, 4 ]
[Done] exited with code=0 in 0.712 seconds

如果要复制数组的元素,可以使用 concat 方法或 slice 方法。

下面我将分别为大家介绍使用 concat 方法与 slice 方法的实例(代码清单 7.5、代码清单 7.6)。

代码清单 7.5 通过 concat 方法对数组进行复制
var hzh1 = [3, 5, 4];
var hzh2 = [].concat(hzh1);
console.log("输出数组hzh2:");
console.log(hzh2); // 从变量hzh2的角度来看,它含有和hzh1相同的元素
console.log("");
hzh2[0] = 123; // 通过变量hzh2来修改数组的元素
console.log("输出数组hzh1:");
console.log(hzh1); // 在变量hzh1处没有发生变化(因为对元素进行了复制)
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出数组hzh2:
[ 3, 5, 4 ]
输出数组hzh1:
[ 3, 5, 4 ]
[Done] exited with code=0 in 0.193 seconds
代码清单 7.6 通过slice方法对数组进行复制
var hzh1 = [3, 5, 4];
var hzh2 = hzh1.slice(0, hzh1.length);
console.log("输出数组hzh2:");
console.log(hzh2); // 从变量hzh2的角度来看,它含有和hzh1相同的元素
console.log("");
hzh2[0] = 123; // 通过变量hzh2来修改数组的元素
console.log("输出数组hzh1:");
console.log(hzh1); // 在变量hzh1处没有发生变化(因为对元素进行了复制)
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出数组hzh2:
[ 3, 5, 4 ]
输出数组hzh1:
[ 3, 5, 4 ]
[Done] exited with code=0 in 0.303 seconds

通常,对于对象或数组实体的复制,有深复制与浅复制两种方式。

深复制是一种完全的复制。如果该对象的属性还引用了其他对象,则那些对象也会一起被复制。

而浅复制则只会复制属性值以及元素值,并不会复制相关的引用对象。通过 concat 以及 slice 进行的复制都是浅复制。可以通过下面的方式确认。

var hzh1 = [ {x:2} ]; // 该数组的元素是某个对象的引用
var hzh2 = [].concat(hzh1); // 通过concat复制元素
hzh2[0].x = 123; // 修改变量hzh2处的元素所引用的对象
console.log("输出hzh1修改后的元素:");
console.log(hzh1[0].x); // 在变量hzh1处也能反映出这一修改(这是一种浅复制)
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出hzh1修改后的元素:
123
[Done] exited with code=0 in 0.161 seconds

如果需要使用深复制,则需要自己实现。不过在实际使用中几乎没有必须使用深复制的情况。

元素的删除

如果要删除数组中的元素,可以使用 delete 运算。不过,通过 delete 删除了元素之后,被删除的地方会留下所谓的空余元素。如果要将进行了元素删除操作后的数列中的空隙消除,可以使用 splice 方法。请看代码清单 7.7 中的示例。

代码清单 7.7 元素的删除(splice 方法)
var huangzihan = ['hzh0', 'hzh1', 'xxx', 'hzh2', 'hzh3'];
delete huangzihan[2]; // 如果仅仅通过delete进行删除操作
console.log("输出数组huangzihan:");
console.log(huangzihan); // 下标为2的位置将会留有空位
console.log("");
huangzihan.splice(2, 1); // 从下标为2的位置起删除1个元素
console.log("重新输出huangzihan:");
console.log(huangzihan); // 前面删除数列的元素后留下的空位被删去了
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出数组huangzihan:
[ 'hzh0', 'hzh1', <1 empty item>, 'hzh2', 'hzh3' ]
重新输出huangzihan:
[ 'hzh0', 'hzh1', 'hzh2', 'hzh3' ]
[Done] exited with code=0 in 0.187 seconds
筛选处理

对于forEach 方法,我们并不应该去关注对元素进行枚举的这一过程,而应该将数组看作集合了各种成员的单一的对象,将枚举视为对该对象进行的一种操作。

将原来的集合看作输入,而将之后生成的集合看作输出的话,这一操作也可以被看作一种函数。从某种意义上来说,它与函数之间只有表现形式上的差别而已。不过,这种表现形式的差别也是很重要的。如果换一个角度看问题,把这看作一种变换处理,就会发现不仅有能够用于枚举数组元素的 for 循环操作,还有能够用于筛选处理以及流水线处理的相关操作。

对于筛选处理或流水线处理,如果将其分为多级进行会比较方便。可以通过链式语法来实现数组方法的多级处理。下面是一个随意设计的使用示例,并没有提供什么实际的功能。

var hzh = ['zero', 'one', 'two', 'three', 'four'];
// map:该操作将元素字符串的长度作为新的元素并转换为数组
// filter:该操作将筛选出元素中值为偶数的部分
var huangzihan = hzh.map(
function(e) {
return e.length;
}
).filter(
function(e) {
return e % 2 == 0;
}
);
console.log("输出变量huangzihan:");
console.log(huangzihan);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出变量huangzihan:
[ 4, 4 ]
[Done] exited with code=0 in 0.165 seconds

将上面的代码拆分成两部分来看:

var hzh = ['zero', 'one', 'two', 'three', 'four'];
var huangzihan = hzh.map(
function(e) {
return e.length;
}
)
console.log("输出变量huangzihan:");
console.log(huangzihan);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出变量huangzihan:
[ 4, 3, 3, 5, 4 ]
[Done] exited with code=0 in 0.19 seconds

可以看到map这个函数将数组的字符串的长度转换为了数值,接下来看看后面的代码:

var hzh = ['zero', 'one', 'two', 'three', 'four'];
var hzh1 = hzh.map(
function (e) {
return e.length;
}
)
console.log("输出调用map方法后的变量hzh1:");
console.log(hzh1);
console.log("");
var hzh2 = hzh1.filter(
function (e) {
return e % 2 == 0;
}
);
console.log("输出调用filter方法后的hzh2变量:");
console.log(hzh2);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出调用map方法后的变量hzh1:
[ 4, 3, 3, 5, 4 ]
输出调用filter方法后的hzh2变量:
[ 4, 4 ]
[Done] exited with code=0 in 0.162 seconds

可以看到filter方法将奇数值全部删除了。照着上面的方法,我写了下面的代码:

var huangzihan = [
'黄子涵',
'',
'黄子涵是帅哥!',
'huangzihan',
'1921323493',
'19124896017',
]
var hzh = huangzihan.map(
function (e) {
return e.length;
}).filter(
function (e) {
return e % 2 == 0;
}
)
console.log("将转换后的hzh变量输出:");
console.log(hzh);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
将转换后的hzh变量输出:
[ 0, 10, 10 ]
[Done] exited with code=0 in 0.195 seconds

从表 7.3 中可以看到,有很多方法都可以用于这样的筛选处理。有人将这些方法称为迭代器类方法。如果不想通过循环的方式来实现对数组元素的枚举,还可以考虑一下是否能够使用这种类型的方法。

这种方法的优点之一是代码将变得更为简洁。还有一个优点就是这样的方法能够使开发者对破坏性的方法更为敏感,在使用时更加谨慎。这是如果因为要在筛选处理中使用链式语法,就应该避免使用破坏性的方法。

7.1.10 数组的内部实现

在 JavaScript 以外的很多语言中,数组将会隐式地占用一段连续的内存空间。这种隐式的内部实现,使得高效的内存使用以及高速的元素方法成为可能。然而,在 JavaScript 中,数组的实体是一个对象,所以其通常的实现方式并不是占用一段连续的内存空间。

如果你有其他程序设计语言的开发经验,或许会担心 JavaScript 中数组的执行效率是否比较低。其实 JavaScript 中的数组是否会使用连续的内存空间,取决于具体的实现方式。与其他那些确实是使用了连续内存空间的程序设计语言相比,JavaScript 的数组效率的确会有些让人担心,不过实际上,所有的 JavaScript 实现都为了提高其自身的性能而各自花了不少的功夫。

对比一下代码清单 7.1 与代码清单 7.2,我们就能够对不同实现方式中数组的内部实现进行一定程度的推测。顺便说明一下,这里的代码中的 1e7 表示 10 的 7 次方。指数形式的数值字面量(参见表 3.6)在性能测试类的代码中很有用,记住其使用方式的话会方便很多。

代码清单 7.1 访问大量的数组元素
var huangzihan = [];
for (var hzh1 = 0; hzh1 < 1e7; hzh1++) {
huangzihan[hzh1] = '';
}
console.log(huangzihan[hzh1]);

不知道是不是数值太大了,控制没有

代码清单 7.2 代码清单 7.1 的对象形式
var huangzihan = {}; // 对象
for (var hzh1 = 0; hzh1 < 1e7; hzh1++) {
huangzihan[hzh1] = '';
}
console.log(huangzihan[hzh1]);

根据实现方式的不同,代码清单 7.1 与代码清单 7.2 之间的执行速度会有所差异。这其实是数组是否使用了连续的内存空间的一种体现。然而,如果数组在内部总是使用连续的内存空间,下面的代码就应该会占用多达 GB 量级的连续内存。不过在一般的实现方式中,这样的情况是不会发生的。

var hzh = {};
hzh[1e9] =''; // 如果数组确实占用了连续的内存空间的话,应该会消耗大量的内存

在流行的 JavaScript 实现中,小型的数组(下标值较小的部分)会占用连续的内存空间,而下标值较大的部分,则一般会以类似于对象的属性的方式进行处理。

不过说到底,还是要看具体的实现方式。此外,在 JavaScript 中,也有人提出需要增加 Int32Array
或 Int8Array 这类自定义增强功能,一并备注于此(请参见下面的链接)。
https://developer.mozilla.org/en/JavaScript_typed_arrays

7.1.11 数组风格的对象

7.1.12 迭代器

7.1.13 生成器

7.1.14 数组的内包

7.2 JSON

7.2.1 JSON 字符串

7.2.2 JSON 对象

7.3 日期处理

7.4 正则表达式

7.4.1 正则表达式的定义

7.4.2 正则表达式相关的术语

7.4.3 正则表达式的语法

7.4.4 JavaScript 中的正则表达式

7.4.5 正则表达式程序设计

7.4.6 字符串对象与正则表达式对象

posted @   黄子涵  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示