0%

数组

4.1 数组

今天又是学习的一天,接下来我们开始看数组,这是一个非常重要的对象,而且是我们除了原始对象以外,经常用到的一个对象,对于整个 Js 的学习非常重要。本次学习主要学习数组的定义,即如何创建数组,几种轮询数组的方法,还有就是一些数组常用的函数。

4.11 创建数组

首先,我们学习的是怎么创建一个数组并把它初始化,这就好比如我们在建房子的时候,得先了解怎么建房,建成什么规格的。底下我们来看代码示例:

1
2
3
4
5
6
7
8
var arr = [];
var arr1 = [1,2,3];//做一个数组并且要进行初始化的时候可以这样写

var arr2 = [1,2,,6];
var arr3=[,,,];

var arr4=['aaa',0,undefined,null,true,{}];

那么房子建好了接下去是不是就是搬进去住的问题了,这就是我们要如何访问数组元素,

1
2
console.log(arr1[1]);
console.log(arr2[4]);

在这边呢,还是要再强调一点,大家一定要把代码在自己的电脑里面重新敲一遍并且运行,这样对一些函数和方法的调用印象都会比较强,更加有助于学习。接下来,我们看下底下这句代码,大家认为是否会报错,先不要在机器里边敲啊,自己根据理解猜测下,

1
arr[3]=5;

很多人看到我这样问,就感觉说这边有坑了,猜测应该不会报错,自信点,把应该去掉,这边是没有报错的,运行结果是:

1
 [empty × 3, 5]

为什么是这样的结果捏,跟我们的理解可能不太一样, arr 明明是空的,但是为什么 arr[3] = 5 ? 也就是说刚开始的数组长度是 0 ,arr[3] 时数组长度是 4 ,在位置 4 的地方赋值 5,而前面的三个仍然是空值,就是会在数组中强制塞进去。

再来看这样一个示例:

1
2
3
4
5
arr2[2]=8;  
console.log(arr2);//[1, 2, 8, 6]

arr2[0]='ab';
console.log(arr2);//['ab', 2, 8, 6]

结合上面两段代码呢,我们可以知道,如果一个下标位置原来不存在,会添加,如果必须的话,还会添加 length ,而如果下标存在的话,就会覆盖掉原来所传进去数组的值。

使用构造函数方式生成数组,请看示例:

1
2
3
4
5
arr = new Array();//等价于 arr = [] ;
arr1 = new Array(1,2,8,6);//等价于 arr = [1,2,8,6] ;
arr3 = new Array(4);/*为什么不是 arr = [4]; 其实相当等价于 arr = [];
这个需要记忆下来
*/

这块知识点我们后面会深入学习,所以这边知识先带大家了解下,就不展开来讲解了。有的同学在问,说可不可以在一个数组里边插入另外的数,首先我们不知道要插入的这个位置原先是否有其他值的存在,这就涉及到了稀疏数组,所谓的稀疏,顾名思义就是,某些数组的内存不是连续的,存在一些空隙。例如:

1
2
3
4
5
var larr = Array(10000);
//(10000) [empty × 10000] length: 10000

larr[3] = 8 ;
//(10000) [empty × 3, 8, empty × 9996]

再来看个意想不到的东西,

1
2
larr[1.5]=7;
// [empty × 3, 8, empty × 9996, 1.5: 7] 3: 81.5: 7 length: 10000

数组里面还可以写小数???结果是肯定的,可以把数组当作 map 来用,一个 Map对象在迭代时会根据对象中元素的插入顺序来进行 一个 for…of 循环在每次迭代后会返回一个形式为[key,value]的数组。比如:

1
2
3
4
5
var a4=[];
a4['China']='满汉全席';
a4['American']='火鸡';
a4['France']='牛排';
console.log(a4);

那我们要如何去判断一个变量是否是数组呢,请看示例:

1
2
3
var arr=[];
console.log(typeof(arr));//object
console.log(arr.constructor.name == 'Array');//true

介绍下轮询数组的方法:

接下来我们相继学习三种轮询数组的方法,首先先学习前面两种,例如:

1
2
3
4
5
6
7
8
9
10
arr=[0,1,2,3,4];
//for(i)
for(var i = 0; i<arr.length;i++){
console.log(arr[i]);
}

//for(in)
for(var i in arr){
console.log(arr[i]);
}

上述两段代码的运行结果为下图所示:

那么,思考一下,这两者之间的区别是什么呢?可以说,for(i) 的方法适用于数组里面所包含的元素全部都是一个一个,清清楚楚地写出来那种,而相反,for(in) 就用到了 map 。我们看个示例:

1
2
3
4
5
6
7
8
9
arr=[0,1,,,4];
//for(i)
for(var i = 0; i<arr.length;i++){
console.log(arr[i]);
}
//0
//1
//2 undefined
//4
1
2
3
4
5
6
7
arr=[0,1,,,4];
for(var i in arr){
console.log(arr[i]);
}
//0
//1
//4

相信看完这两个代码大家就可以深入理解,说的通透点就是,for(i) 方法可以透过表面读取深度数据,按照数组的方式改变数组,而 for(in) 就只能读取表面看到的数据,使用 map 的方式返回所有非稀疏节点的 key。

这边涉及下第三种轮询数组的方法,forEach(f) :返回所有数字且非稀疏的节点的 value ,示例:

1
2
3
arr.forEach(function(x)){
console.log(x);
}

这种呢,我们下节会具体学,在这边就先简单带过。

4.12 for in

循环、轮询怎么去访问它。

4.13 concat()

concat() 函数用于把两个数组连接起来,合并成为一个数组,该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。这个点是相对好理解的,在本例中,我们创建数组并把它们连接起来:

1
2
3
4
5
var arr=[3,2,1];
var arr2=[4,5,6];

var arr3=arr.concat(arr2);
console.log(arr3);// [3, 2, 1, 4, 5, 6]

4.14 join()

把数组的里边的元素全部打开,组成一个字符串,元素通过指定的分隔符进行分割。

1
2
3
console.log(arr3.join(','));//3,2,1,4,5,6
console.log(arr3.join('-'));//3-2-1-4-5-6
console.log(arr3.join(' '));//3 2 1 4 5 6

4.15 sort()

对数组里边的值进行排序,并且返回数组。例:创建一个数组,实现排序并且返回功能,

1
2
arr3=[1,5,3,2,9,4,0,8];
console.log(arr3.sort());//[0, 1, 2, 3, 4, 5, 8, 9]

4.2 数据结构

4.21 栈

栈相当于一个桶,原则上是后进先出,进栈 :push ,出栈 :pop 。

示例:

1
2
3
4
5
6
7
//push-pop  尾进尾出
var arr=['a','b','c'];
console.log(arr.join(',')); //a,b,c
arr.push('z');
console.log(arr.join(','));//a,b,c,z
console.log(arr.pop());//z
console.log(arr.join(','));//a,b,c

数组中还有一个有意思的地方, unshift-shift 。shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值,而 unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度,示例:

1
2
3
4
5
6
7
/*shift 是把东西拿走移动,而 unshift 是把东西放进栈里面     unshift-shift  头进头出*/
var arr=['a','b','c'];
console.log(arr.join(',')); //a,b,c
arr.unshift('z');
console.log(arr.join(','));//z,a,b,c
console.log(arr.shift());//z
console.log(arr.join(','));//a,b,c

4.22 队列

队列,采用的原则是先进先出,就好比如我们排队买东西,先排队的先付款走人,如果这时候是先来的最后买单,整个秩序就会完全乱掉。

示例:

1
2
3
4
5
6
arr=['a','b','c'];
console.log(arr.join(',')); //a,b,c
arr.push('z');
console.log(arr.join(','));//a,b,c,z
console.log(arr.shift());//a(头进头出)
console.log(arr.join(','));//b,c,z

4.3 数组中的成员函数

4.31 slice() 函数

slice() 函数是一个数组的函数,主要是节选数组中的一段,原数组是不受影响的。在本例中:

1
2
3
//slice(iStart[,iEnd]) 
//slice(iStart,iEnd)
//slice(iStart)

上面主要是 slice() 函数的写法,大家注意下, [] 里面的数可有可不有,不会产生影响。用法示例:

1
2
3
4
5
6
7
8
var arr7=[0,1,2,3,4,5,6,7,8];
console.log('[slice] ' + arr7.slice(6));//从第六开始 6,7,8

console.log('[slice] ' + arr7.slice(-3));//后三个 6,7,8

console.log('[slice] ' + arr7.slice(2,5));//2,3,4
console.log('[slice] ' + arr7.slice(-7,5));//2,3,4,从 -7 的位置开始,到第五的位置结束

在这边呢,要注意下,正着数的时候是从 0 开始数的,而倒着数的时候是从 1 开始数的,这是所有程序员从今往后要习惯的事情。

4.32 splice() 函数

splice 主要用来对 js 中的数组进行操作,包括删除,添加,替换等,与 slice 函数不同的是 splice() 函数操作完原数组会受到影响。splice(iIndex[, iHowmany][,item1]),从 iIndex 开始,删除元素,删除几个由 iHowmany 决定, item1 是要插入的元素。示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
arr=[0,1,2,3,4,5,6,7,8];
var arr1=arr.splice(6);
console.log('[splice] ' + arr);//0,1,2,3,4,5
console.log('[splice] ' + arr1);//6,7,8

arr=[0,1,2,3,4,5,6,7,8];
arr1=arr.splice(-3);
console.log('[splice] ' + arr);//0,1,2,3,4,5
console.log('[splice] ' + arr1);//6,7,8


arr=[0,1,2,3,4,5,6,7,8];
arr1=arr.splice(2,3);
console.log('[splice] ' + arr);//0,1,5,6,7,8,这边相当于从下标为 2 的位置开始,删除 3 个元素
console.log('[splice] ' + arr1);//2,3,4,返回删除掉的三个元素

arr=[0,1,2,3,4,5,6,7,8];
arr1=arr.splice(-7,3);
console.log('[splice] ' + arr);//0,1,5,6,7,8
console.log('[splice] ' + arr1);//2,3,4

arr=[0,1,2,3,4,5,6,7,8];
arr1=arr.splice(2,3,-1,-2,-3,-4);
console.log('[splice] ' + arr);//0,1,-1,-2,-3,-4,5,6,7,8;在删除掉的地方插入后面四个元素
console.log('[splice] ' + arr1);//2,3,4

4.33 delete

在JavaScript中,delete操作符用的比较少,但是还是比较重要的,我们在这块就举个例子说明下:

1
2
3
4
5
//delete
arr=[0,1,2,3,4,5,6,7,8];
console.log('[delete] ' + arr);//0,1,2,3,4,5,6,7,8
delete arr[4];
console.log('[delete] ' + arr + '=====' + arr.length);// 0,1,2,3,,5,6,7,8=====9

4.4 内存问题

4.41 值类型 vs 引用类型

回顾下之前学的值类型都有哪些呢?值类型:number , string , Boolean , undefined , null ; 而引用类型有:array , function , object 。这些涉及的是要在栈里分配还是队列分配。

这边的栈跟之前代码里面规约四则运算式的栈是不一样的,他们相同的点的是都有相同的数据的操作方式,就是后进先出,但是,事实上,之前的栈是 javascript 的对象,是你在写业务代码的时候,提供的一个栈的功能给你,而今天要学的这个栈,是 javascript 引擎在它的代码层运用,javascript 引擎并不是存在于 javascript , 而是 C++ 。

值类型存储在栈里面,引用类型存储在堆里面。因为值类型占用空间小、大小固定,通过按值来访问,属于被频繁使用的数据,而因为引用类型占据空间大、大小不固定。 如果存储在栈中,将会影响程序运行的性能; 引用类型在栈中存储了指针,该指针指向堆中该实体的起始地址。 当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。比如说:

1
2
3
4
5
6
var a=5;//key 是变量名 a,value 是 5
var b=a;
var arr=[];//key 是变量名 arr,value 是[]的地址(引用),现阶段来说,引用就是地址,但是严格来说的话,引用并不能当作是地址
var arr1=arr;
var a=[];
var s='abc';

这个变量 a ,arr 是存在于栈内存里边的,在栈内存里面的如果是值类型的话,就可以一起放进去,但是,如果后面是数组的话,数组放在堆内存里边,然后把它的地址放到栈里边,即 arr 的位置,可以说是,栈内存小,堆内存大。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
// 基本数据类型-栈内存
let a1 = 0;
// 基本数据类型-栈内存
let a2 = 'this is string';
// 基本数据类型-栈内存
let a3 = null;

// 对象的指针存放在栈内存中,指针指向的对象存放在堆内存中
let b = { m: 20 };
// 数组的指针存放在栈内存中,指针指向的数组存放在堆内存中
let c = [1, 2, 3];

4.42 栈 vs 堆

堆内存中对象,有一个引用计数,就是 GC ,就是用来垃圾收集,通俗点说就是知道什么东西已经不用,而什么东西还要用,内存回收的机制,最早是由 MS COM 实现的。GC 的作用时间,第一是,堆内存达到一定的门槛,第二是,定期。
谁的引用数是0,就被摧毁。而栈内存是当代码运行到栈内变量的作用域以外时,变量将被摧毁(就是内存里边删除了),如果变量被摧毁,引用数-1。来看个底下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//循环引用
var a1=[];
var a2=[];

a1[0]=a2;
a2[0]=a1;

//栈内存
ST001:a1,HP001//运行 a1,栈内存存放 a1 和地址,[]存放在堆内存中,计数为1
ST002:a2,HP002//运行 a2,栈内存存放 a2 和地址,[]存放在堆内存中,计数为1
ST003:
ST004:
ST005:
ST006:


//堆内存
HP001:[HP002],(2)//运行第三个式子,将 a2地址赋给 a1[0],计数 +1,即为2
HP002:[HP001],(2)//同上解释
HP003:
HP004:
您的支持是对我最大的鼓励