0 写在前面
数组在任何语言中,都是一种十分重要的数据类型。因此,在JavaScript中,熟练掌握数组以及字符串的操作,是十分必要的。
为此,我在这里列举并练习了一些有关于数组与类数组的基本操作。
1 数组的常用操作
1-1 数组常用操作的大致分类
- 在原数组上进行修改(不占用额外空间)的方法
- push,pop,unshift,shift,reverse,sort,splice
- 不在原数组上进行修改(占用额外空间)的方法
- slice,concat,join,split,toString
1-2 修改原数组的方法
1-2-1 push
【用法】
arr.push(ele)
【说明】
在arr尾部添加新元素ele(可以为数据列表)
【原型链编程】
push的用法十分简单,我们可以根据其特点重写其方法进行练习。这里练习原型链编程,即在Array类的原型上修改push方法。
代码如下
1 // declare an Array 2 var arr = [1,2,3,4,5]; 3 var arr1 = new Array(1,2,3,4,5); 4 // override push 5 Array.prototype.myPush = function(){ 6 for(var i = 0 ; i < arguments.length ; i ++){ 7 this[this.length] = arguments[i]; 8 } 9 return this.length; 10 }
1-2-2 pop
【用法】
arr.pop()
【说明】
在arr尾部弹出并删除末尾元素
1-2-3 unshift
【用法】
arr.unshift(ele)
【说明】
在arr头部插入元素ele(可以为数据列表)
1-2-4 shift
【用法】
arr.shift()
【说明】
在arr头部弹出并删除首个元素
1-2-5 splice
【用法】
arr.splice(i,n[,args])
【说明】
在arr的第i位开始,删除n个元素。后面的[,args]为用逗号分隔的可选参数列表,表示向“切口”出添加的数据。
【拓展】
可以使用splice代替insert的功能,只需将n=0即可,表示向第i位插入[,args]这么多数据。
【代码示例】
原型链编程:实现unshift方法的重写。思路:每次向数组首插入数据。
1 <script> 2 // override unshift 3 Array.prototype.myUnshift = function(){ 4 var n = arguments.length; 5 for(var i = 0 ; i < n ; i ++){ 6 this.splice(i,0,arguments[i]); 7 } 8 return this.length; 9 } 10 </script>
【补充】
若第一位索引位i的值为负数,系统则会按照以下方式进行处理:
1 function splice(pos){ 2 pos += (pos >= 0) ? 0 : this.length; 3 }
即对于负数的索引为:从数组尾向前进行查找。
1-2-6 sort(重要)
【使用】
arr.sort([function(a,b){}])
【说明】
sort内的参数为可选项,类比于C++中的cmp函数进行理解,内部参数为一个函数形式:若返回负值则将a放置在b前面,整值则将b放在a前面。
由此可知,JavaScript中的sort方法也是基于比较的(对数组中的元素两两分别进行比较)(经过验证发现JavaScript中的sort是稳定的)
【默认】
在sort不传递参数的情况下,默认进行的排序是将arr的内容视为字符串按照其字典序进行升序排列。
【拓展】
根据其传入的参数方法,可以实现任意我们想实现的排序方式。
以下来看多组示例代码。
【代码示例】
【例1】按照数值顺序升序排列。
1 <script> 2 // asc sort by value 3 var arr = [3,2,1,10,29,392,12,3,-3,-3029,-1,0]; 4 arr.sort(function(a,b){ 5 return a - b; 6 }) 7 </script>
降序只需修改返回值为return b - a即可。
【例2】按照对象中某一属性排序,如按照rank升序
1 <script> 2 var chen = { 3 name : "ChenXianxian", 4 age : 20, 5 gender : 'male', 6 rank : 1 7 } 8 var meng = { 9 name : "MengZY", 10 age : 20, 11 gender : 'male', 12 rank : 32 13 } 14 var fei = { 15 name : "FeiY", 16 age : 20, 17 gender : 'male', 18 rank : 23 19 } 20 var liu = { 21 name : "LiuCJ", 22 age : 20, 23 gender : 'undefined', 24 rank : 12 25 } 26 var arr3 = [chen,liu,meng,fei]; 27 arr3.sort(function(a,b){ 28 return a.rank - b.rank; 29 }) 30 </script>
执行结果如下
【例3】将有序数组随机打乱顺序
思路:将排序的交换变成随机正负数返回即可。
1 <script> 2 var arr4 = [1,2,3,4,5,6,7,8,9]; 3 arr4.sort(function(a,b){ 4 return Math.random() - 0.5; 5 }) 6 </script>
【例4】按照字节数排序
扩展知识:ASCII码小于255的占用1个字节,超过255的则占用2字节。
在sort中对a和b的比较也可以定义函数进行比较,这里练习字符串字节数的计算函数。
1 <script> 2 // asc sort by bytes 3 var arr5 = ['abdofajefid','a','ab','哈哈哈c','哈哈','a哈a哈a']; 4 5 function getBytes(str){ 6 var res = str.length; 7 var n = str.length; 8 for(var i = 0 ; i < n ; i ++){ 9 if(str.charCodeAt(i) > 255){ 10 res ++; 11 } 12 } 13 return res; 14 } 15 16 arr5.sort(function(a,b){ 17 return getBytes(a) - getBytes(b); 18 }) 19 </script>
1-3 不修改原数组的方法
1-3-1 concat
【用法】
arr1.concat(arr2)
【说明】
返回结果是一个新的字符串arr1与arr2拼接,但是不对arr1和arr2进行修改。
1-3-2 toString
【用法】
arr.toString()
【说明】
将数组arr的内容转化成一个字符串。
1-3-3 slice
【用法】
arr.slice(i,j)
arr.slice(i)
arr.slice()
【说明】
arr.slice(i,j) 表示从arr中截取第[i,j)位
arr.slice(i) 若i为正,表示从arr第i位开始截取(i从0开始计数);若i为负,表示从arr倒数第i位开始截取(i从1开始计数)
arr.slice() 表示截取完整的arr
【注意】
arr.slice()由于不会修改原数组,因此其返回值必须有一个变量接收,否则操作就失去了意义。
1-3-4 join/split
【用法】
arr.join(regex)
arr.split(regex)
【说明】
将arr按照regex的内容进行拼接或拆分,拼接是在原数组的基础上补上一个regex中的内容,拆分则是将字符串中的该字符删掉。
【举例】
1 <script> 2 var arr = [1,2,3,4,5,6]; 3 arr.join("-"); // "1-2-3-4-5-6" 4 arr.join(""); // "123456" 5 var str = "1,2,3,4,5,6"; 6 arr.split(","); // ["1","2","3","4","5","6"] 7 </script>
【注意】
若要进行大量字符串的拼接操作,请使用 arr.join(""); 的方式进行操作。
这是由于循环使用运算符'+'进行拼接会降低程序执行效率。
2 类数组
2-1 类数组概述
类数组在形式上表现为数组的特征,但是本质是一个对象,使用对象中的属性去模拟数组特性。
如:函数参数传递列表arguments就是一个类数组,形式上表现为一个数组,但不具有数组所通常具有的方法(在第1部分提到的方法)
2-2 类数组的基本形式
类数组的基本形式如下
1 <script> 2 // ArrayLike 3 var obj = { 4 "0" : 1, 5 "1" : 2, 6 "2" : 3, 7 "length" : 3, 8 "push" : Array.prototype.push; 9 } 10 </script>
其中的几个关键要素:按照相似于数组的索引方法,将key值设置为数组下标的形式;必须有length属性来记录数组长度。
由此便可实现,按照下标索引类数组中的数据(如obj[0]或obj['0']取出的都是数值1),使用push方法向类数组中添加元素。
此外还可根据需要自行添加类数组的行为:如添加Array.prototype.splice方法等等(注意原型上的属性值不要加())。
2-3 类数组使用的注意事项
在2-2接种提到,类数组中的一个必要属性是length
在这里我们回顾在1-2-1小节中编写的push方法,该方法在原型上的实现为在arr的length位添加push进的数值。因此可知length为下一次添加进类数组的数据位置提供了必要的索引方式。
我们来看一个例子
1 <script> 2 // example 3 var obj1 = { 4 "2" : 'a', 5 "3" : 'b', 6 "length" : 2, 7 "push" : Array.prototype.push 8 } 9 obj1.push('c'); 10 obj1.push('d'); 11 </script>
执行结果中,obj1 = {"2" : 'c' , "3" : 'd' , "length" : 4 , "push" : Array.prototype.push},由此可知此处的push修改的是obj1中的length位置索引到的数据值,并length++。
2-4 类数组向数组的转换
由于类数组不含有数组的常用方法,因此将类数组转换为数组会大大简化操作。
类数组转换成数组的方法如下:
var arr = Array.prototype.slice.call(arrayLike);
注意必须使用call来改变this指向arrayLike这个类数组,结果用变量arr进行接收。
3 数组操作的两个练习
3-1 封装type区分引用值类型
主要实现思路为,先利用typeof本身提供的功能区分原始值与引用值,但是引用值(null,object和array)是区分不开的。因此需要调用Object.prototype.toString.call(target)来判断target的类型。
需要注意的一点是以下两种声明方式使得变量的类型不一样:
var a = 1;
var a = Number(1);
同样表示整数1,但是前者是整数Number,后者是一个包装类的实现是一个object,我们希望我们的程序能够对以上两种不同情况进行区分。
可以将封装好的type方法放进工具类库如:tool.js里面,以便随时需要用到时调用之。
代码实现如下:
1 <script> 2 // abstract function type 3 var template = { 4 "[object Array]" : 'array', 5 "[object Object]" : 'object', 6 "[object Number]" : 'object_number', 7 "[object Boolean]" : 'object_boolean', 8 "[object String]" : 'object_string' 9 } 10 function type(target){ 11 if(typeof(target) == "null"){ 12 return "null"; 13 } 14 else if(typeof(target) == "object"){ 15 var str = Object.prototype.toString.call(target); 16 return template[str]; 17 } 18 else{ 19 return typeof(target); 20 } 21 } 22 </script>
检查结果
经验证发现,实验结果符合预期。
3-2 数组去重
需求十分简单,给定一个数组,编写一个删除重复元素的算法。
实现思路也十分简单,由于对象object本身就是一个键值对key-value的组合,所以自带map的特性。删除重复元素完全可以当作一个hashmap的问题来进行处理。
实现代码如下
1 <script> 2 // delete repeat number in array 3 var arr = [1,1,1,1,1,1,1,2,2,2,1,3,1,1,2,1,2,3,1,2,2,2,1]; 4 function rmRepeat(arr){ 5 var obj = {}, 6 res = [], 7 n = arr.length; 8 for(var i = 0 ; i < n ; i ++){ 9 if(!obj[arr[i]]){ 10 res.push(arr[i]); 11 obj[arr[i]] = 'xxx'; 12 } 13 } 14 return res; 15 } 16 </script>
同样,也可以把数组去重作为一个数组操作方法放到Array的原型链上
1 <script> 2 // delete repeat number in array - proto method 3 Array.prototype.unique = function(){ 4 var n = this.length, 5 obj = {}, 6 res = []; 7 for(var i = 0 ; i < n ; i ++){ 8 if(!obj[this[i]]){ 9 obj[this[i]] = 'xxx'; 10 res.push(this[i]); 11 } 12 } 13 return res; 14 } 15 </script>
【注意】
在实现上需要注意一点:方法二的第9行,对obj对象(map)的value值的赋值,必须不能为一个可能为false的值。
可能为false的值有以下6种:
-
0
-
“”
-
false
-
null
-
undefined
-
NaN
因此再此我的处理方法是手动赋值为一个字符串,保证其不可能为空。
4 总结
数组是一种重要的数据类型,必须熟练数组的常用操作、类数组的意义以及掌握在原型链上编程的方法。