JavaScript数组操作

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 总结

  数组是一种重要的数据类型,必须熟练数组的常用操作、类数组的意义以及掌握在原型链上编程的方法。

posted @ 2019-04-28 14:54  Chris_Chen98  阅读(244)  评论(0编辑  收藏  举报