由于数据和循环经常连用,JS 的数组也不算难,所以这里一并介绍。数组主要就是用来保存一系列数据的一种变量。我们想象这样一个场景,我现在是一个老师,我的班上有 50 个人,我要组织一场考试,每个人会有一个分数,我需要把这 50 人的分数存起来,如果利用我们已经学过的知识,我要存下 50 个人,就需要建立 50 个变量,这显然不合逻辑。我们就可以使用一个数组来把这所有的数据都存下来。
有的读者可能会想到前面介绍过的对象,对象确实可以用来做这件事,但是这里我们介绍的是数组,所以先简单忽略对象。
我们常见的创建数组方式主要有以下几种
js
var arr1 = new Array(10);//创建了一个长度为10的空数组
console.log("arr1",arr1);
var arr2 = new Array(1,2,3,4,5);//创建了一个数组,初始化每一项的值,即第一项为1,第二项为2,第三项为3,第四项为4,第五项为5。长度为5
console.log("arr2",arr2);
var arr3 = [3,4,5];//创建了一个数组,第一项为3,第二项为4,第三项为5。长度为3。
console.log("arr3",arr3);
var arr4 = [];//创建了一个长度为0的空数组
console.log("arr4",arr4);
相信以上的例子已经可以让读者理解数组的创建方式了,读者需要注意,对于 new Array() 这种方式,如果括号里不填入东西,创建的是一个长度为 0 的空数组,如果只填入一个整数,就是指定长度为多少的空数组,如果填入多个数据,则是挨个指定数据。
JS 中的数组和其他很多语言有很多不同,JS 属于弱类型语言,在强类型语言,例如 JAVA 中,数组是有类型这一概念的,整型数组只能存储整数,字符型数组只能存储字符型数据。但在 JS 中,数组的每一项都可以存放不同类型的数据,如下所示。
js
var arr = [null,undefined,"String",true,{x:1,y:2},[1,2,3],520];以上代码在 JS 中是完全合法的,可以看到,这个 arr 数组里存放了非常多不同的数据类型,甚至可以存储另一个数组,这个例子很好地体现了弱类型语言灵活的优势。
数组的本质是一个对象,读者可以使用 typeof 来验证这一结论。任何一个对象都是有属性的。我们可以验证一下
js
var arr = [null,undefined,"String",true,{x:1,y:2},[1,2,3],520];
console.log(arr);
我们可以看到,当我们在控制台将这个对象展开之后,可以看到这个对象的属性,但这个属性很特别,居然有数字,这一点和我们前面介绍的对象不太一样,我们前面介绍的对象中,我们说过,对象的属性只能是字符串,不可能是数字。其实这并不是什么特殊情况,实际上,这些东西看起来是数字,本质上还是一个字符串,也就是说属性名并不是数字 0,而是字符串 0。
在介绍对象的时候,我们介绍了对象的两种取值方式。我们举个例子回顾一下:
js
var obj = {
test1:"test",
"0":"test2",
};
console.log(obj.test1);
console.log(obj.0);//这个报错
console.log(obj[0],obj["0"]);//如果要正确获取属性名为"0"的属性,需要这么写这里解释一下为什么上例中的第二种输出会报错,因为数字开头的东西不是一个合法的标识符,这一点在变量那一节介绍过。而对于
obj[0]则会自动将 0 转换为字符串的 0。
在数组里面,这种数字类型的字符串属性,我们称为下标或者索引。可以理解为每个数据的编号。值得注意的是,在数组中,下标是从 0 开始编号的。
js
var arr = ["data1","data2","data3"];
console.log(arr[1]);//可以获取"data2"
arr[1] = "test";
console.log(arr[1]);在上例中我们可以看到,我们同样可以通过这种访问对象属性的方式来访问数组的某一项。同时还可以修改某一项的值。
js
var arr = ["data1",{x:1,y:2,}];
console.log(arr[1].x);//arr[1]为一个对象,这里表示取这个对象的x属性接下来,考虑数组的 length 属性,这里先给出结论,数组的 length 属性表示数组中以数字命名的属性的最大值加一。
js
var arr = [1,2,3,4];
console.log(arr.length);//输出4
arr.temp = 5;//给arr添加了一个新属性,请不要忘记数组的本质是对象
console.log(arr.temp);//可以正确访问,可见添加是正确的
console.log(arr.length);//输出4
arr[4] = 5;
console.log(arr.length);//输出5在上例中,我定义了一个长度为 4 的数组,然后给数组添加了一个不是数字的属性,此时数组的 length 属性并没有变化,但是当我给 arr[4] 赋值的时候,arr 的 length 属性发生了变化,增加了 1。数组中原来没有属性名为 4 的属性,只有 arr[0]到arr[3],此时我增加了一个数字类型的属性,数组中就有 0,1,2,3,4 这样几个数字属性,而根据前面的说法,长度就为 4+1,即 5。
js
var arr = [0,1];
arr[9] = 9;
console.log(arr.length);//输出10在上例中,先创建了一个长度为 2 的数组,然后强行给 arr[9] 赋值为了 9,此时 arr 的 length 属性变成了 10,显然,当我给 arr[9] 赋值的时候,数组中就添加了一个属性名为 9 的属性,此时最大的以数字命名的属性就是 9,length 属性就会自动变为 10。在这个例子中,数组就只有 0,1,9 三项,显然,在 JS 中,数组下标并不一定是连续的。
对于下标不连续的数组,我们称为稀松数组
数组的长度只取决于 length 属性,即只要我将 length 属性赋值,就可以改变数组的长度,在其他编程语言里这种方式可能很扯淡,但在 JS 中是允许的。但值得注意的是,如果将 length 变小,数组的数字属性会被截断。在实际开发中,也不建议直接修改 length 的值。
数组的最后一项后面可以加上一个逗号,我们称为尾后逗号
var arr = [1,2,3,]这种写法和var arr = [1,2,3]一模一样。
接下来介绍一系列数组的常见操作
js
var arr = [1,2,3];
arr[3] = 4;//数组指定位置添加
console.log(arr);
arr[arr.length] = 5;//这种写法可以在arr的最后一项添加一项,这是一种通用写法
console.log(arr);
delete arr[2];//删除某个项,并不会改变length属性,因此会导致导致稀松数组
arr.push(3);//这种写法,也可以向数组的末位添加一项,在这个例子中,向数组的末位添加了一个数据3,会改变legnth的值
arr.push(1,2,3);//可以添加多个数据,unshift业同理
arr.pop();//表示删除数组的最后一项,这个函数返回删除后的数组,会改变length值
arr.unshift(4);//这种写法可以向数组的起始位置添加一位数据,相当于让数组每一项的下标向后移动一位,然后给arr[0]赋值,这里相当于在起始位置添加上了一个数据4,可以理解为插入,会改变legnth的值
arr.shift();//删除数组的第一项,后面的项依次前移,返回删除的结果,会改变legnth的值
//其实在数组中插入的操作,是一个很常见的操作,这里我们介绍一个函数用于插入
arr.splice(2,3,"test","test2");//在下标为2的位置,删除3个数据,同时插入"test"和"test2"两个数据
//很显然,splice可以直接替换push,unshift,甚至包括后面的pop,shift也可以替换,是一个非常万金油的方法
//splice加了很多健壮性的判断,不会造成稀松数组,也就是说如果下标超过范围,或者说是大于length属性,他会自动替换成length属性,如果小于0,也会自动替换为0
//splice的返回值为一个数组,用于记录被删除的数据
console.log(arr.slice(2,3));//从下标为2的位置开始,到下标为3的元素结束,包前不包后,即包括下标为2的数据,不包括下标为3的数据
//slice函数并不会改变原数组
//slice函数允许下标为负数,下标为负数的情况就是倒数第几个
//slice如果省略第二个参数,默认取到末尾
//要实现数组清空,考虑以下写法
arr.splice(0,arr.length);
arr.length = 0;
//查找数组中指定数据元素的下标
console.log(arr.indexOf("test"));//可以输出第一个值为"test"的下标
//indexOf查询的时候使用===进行比较
//如果没有找到,返回-1
//当出现相同数据的时候,indexOf会返回第一个的下标,而如果使用lastIndexOf方法,则返回
arr.fill("abc");//将数组的所有项填充为指定数据
arr.fill("abc",2);//将数组从下标为2的元素开始,后面所有元素填充为指定数据
arr.fill("abc",2,5);//将数组从下标为2的元素开始,到下标为5的元素,填充为指定数据,一样是包前不包后如果读者学过数据结构,就会发现,实际上,在 JS 中使用以上方法,数组可以直接当做栈、队列和线性表这些数据结构来用。
in 关键字
in 关键字用于判断某个属性在对象中是否存在。语法为 属性名 in 对象。返回一个布尔。其中属性名要是一个字符串。
in 关键字判断的是属性名,而不是属性值。
for-in 循环
用于循环对象中的每一个属性。
for(var 变量 in 对象名){ console.log(对象名.变量) }
在上面这个循环中,就可以遍历整个对象。也就是说,for-in 循环可以可以取出变量的所有属性名,每次循环的时候,将其中一个属性名赋值给变量
for-in 循环可以用于遍历数组,在遍历数组的时候,for-in 循环可以直接忽略空位置
数组有非常多的应用场景,也会有一些坑。
首先,我们知道,数组是一个引用类型,所以我们就可以解决下面的问题
js
var arr1 = [1,2,3];
var arr2 = arr1;
arr2[0] = 100;
console.log(arr1[0]);//输出100显然,修改 arr 2 的同时,arr 1 也被修改了。这就是由于 arr 2 是引用类型的原因,忘掉引用类型的读者可以参考 5.引用类型
这种直接 arr2 = arr1 的方式,相当于直接将地址进行复制,我们称这种方式为浅拷贝,而如果我就是要开辟一个新空间,让 arr 1 和 arr 2 完全独立,就要使用深拷贝,深拷贝参考以下代码:
js
var arr1 = [1,2,3,4,5];
var arr2 = [];
for(var i = 0;i<arr1.length;i++){
arr2[i] = arr1[i];
}当然,我们也可以有很取巧的方式:
var arr2 = arr1.slice();就可以实现深拷贝。
slice 函数如果不写参数,默认获取整个数组。
如果数组中的每一项都是一个数组,那么这个数组我们称为二维数组
js
var arr = [
[1,2,3],
[2,3,4],
[4,5,6],
];
//遍历这个二维数组
for(var i = 0;i<arr.length;i++){
for(var j = 0;j<arr[i].length;j++){
console.log(arr[i][j]);
}
}接下来我要将数组按照某种特定格式输出
js
var arr = [1,2,3];
var str = "";
for(var i = 0;i<arr.length;i++){
str += arr[i];
if(i < arr.length-1)
str+=",";
}
可以看到,数组就按照一个比较美观的格式输出了,当然,对于这种,把数组的每一项分隔开,拼成一个新的字符串,我们其实有更好的方案。
js
var arr = [1,2,3];
console.log(arr.join(","));//以逗号分割,将数组转为字符串我们可以认为,join 方法就是把数组转为字符串。说严谨一点就是把数组的每一项拿出来,拼接成一个字符串。该函数返回一个字符串。
接下来考虑数组的拼接
js
var arr1 = [1,2,3];
var arr2 = [4,5,6];
var arr3 = arr1.concat(arr2);//可以将arr2拼接到arr1的末尾,返回一个新数组,该表达式不会对原数组造成影响最后介绍一种数组的排序算法,冒泡排序
js
// 对给定数组arr进行冒泡排序
var arr = [1, 5, 7, 334, 6, 88];
// 主循环,遍历整个数组,每次都将最大的元素“冒泡”到数组末尾
for (var i = 0; i < arr.length - 1; i++) {
// 内部循环,两两比较并交换位置,确保每轮结束后当前子数组范围内的最大元素位于末尾
for (var j = 0; j < arr.length - 1 - i; j++) {
// 如果当前元素后面的元素小于当前元素,说明顺序有误,交换它们的位置
if (arr[j + 1] < arr[j]) {
// 临时变量存储当前元素的值
var temp = arr[j];
// 将较小的元素赋值给当前元素的位置
arr[j] = arr[j + 1];
// 将原来存储在临时变量中的较大元素赋值给下一个位置
arr[j + 1] = temp;
} // 结束if语句,当无需交换时则继续比较下一个元素对
} // 结束内层循环,完成一轮冒泡操作
} // 结束外层循环,完成整个数组的冒泡排序
// 输出排序后的数组
console.log(arr);