浅谈用原生 js 实现数组的 slice 方法

slice 方法有这几种情况:不传参、传一个参数、传两个参数。并且传参支持负数,具体每个情况的效果在这里就不说了。

核心方法:通过 for 循环遍历 调用此方法的数组 ,把要取出的内容放入新数组,然后将新数组返回。

一切的条件处理判断,都为了 for 循环能够正确的执行

这是好久以前写的了,虽然就8行代码,但是三元运算符套用的太多了,还没加括号,现在回过头来看看真是满眼的星星。

Array.prototype._slice = function _slice() {
var n1 = Number(arguments[0]), n2 = arguments[1], n3 = [];
n2 = n2 === undefined ? this.length : Number(n2) ? Number(n2) : 0;
n2 = n2 < 0 ? Math.abs(n2) > this.length && n2 > -1 ? 0 : this.length + Math.ceil(n2) : n2 > this.length ? this.length : Math.floor(n2);
n1 = n1 ? n1 < 0 ? Math.abs(n1) > this.length || n1 > -1 ? 0 : this.length + Math.ceil(n1) : n1 > this.length ? this.length : Math.floor(n1) : 0;
for (var i = n1; i < n2; i++) {
n3[n3.length] = this[i];
}
return n3;
};

所以上面的代码看看就行了,我们先来分析一下 for 循环,因为一切条件判断都是围绕这个循环来处理的,这样也便于理解接下来所解析的各种条件判断是为了什么。

 i = n1 定义起始位置(包含 n1 的位置那项)

 i < n2 定义结束位置(不包含 n2 的位置那项)

这个循环设置了从哪里开始取到哪里结束,所以我们只需要处理好 n1 和 n2 的值,就能满足所有的传参情况

  for 循环分析完毕,其实也没啥好分析,现在来一行一行的拆分分析代码。

因为个人表达能力有限,有些话越是想表达清楚就越是表达不清楚,觉得下方框内的文字太啰嗦的话,可以不看或者选择性忽略,或者只看标记红色的部分

第一行:

  var n1 = Number(arguments[0]), n2 = arguments[1], n3 = [];

    用两个变量 n1 和 n2 接收 arguments 中的两个参数,用来具体判断传参情况。

    n3 的用途暂时先不说。

因为 arguments 是类数组也是个对象,所以如果当 arguments[0] 或者 arguments[1] 不存在,也不会报错而是会默认为 undefined

第二行:

  n2 = n2 === undefined ? this.length : Number(n2) ? Number(n2) : 0;

  如果觉得上面的三元运算符难以理解,可以看下面用 if 写的

    if (n2 === undefined) {
        n2 = this.length;
    } else if (Number(n2)) {
        n2 = Number(n2);
    } else {
        n2 = 0;
    }
第二行代码_IF

  通过 n2 来判断第二个形参是否有值。

  • 如果是 undefined 则表示没传值。

这时候,已经可以确定外部运行的执行代码是 slice() 或者 slice(X)

到这里,如果你想的:"判断第一个形参是否有值,如果有值就返回数组中的相应项,没值就返回所有项",不是不正确,是和本文的代码思路有些不同,我们不管第二个形参是否有传值进来、传进来的值是否是有效值,我们都要给第二个形参确定一个有效值。

这是为了把所有可能存在的分支整合成一个分支,到最后统一的一次性处理。也就是:不管传参可能会是什么样的,我们在代码尾部的for循环内只处理一次,最后我们只 return 一次就够了

  在没传值的情况下,就表示用户需要的内容为:从数组中指定的位置开始,截取到末尾

  所以我们就给这个形参赋值:this.length 。

this 代表调用 _slice 方法的数组,那 this.length 自然就是这个数组的长度了

在第二个参数为空或者无效的情况下,代表从 n1 的位置开始,取出来数组后面的所有内容。

    var ary = [1,2,3,4,5,6]
    ary.slice(1)//这里就表示从数组的第一个位置开始,取出剩下的所有项
    //结果也就是[2,3,4,5,6]

这里在 for 循环中,n1 就等于 1 , n2 就等于 5

因为现在还没有处理 n1 的值,所以 n2 是多少我们并不知道。所以先赋值 this.length ,这个值比实际所需值只多不少,所以不担心取不够的情况

给多了当然也不行,所以需要在后面会再处理一下这个情况。

  •  如果不是 undefined 则表示有值传进来,用 Number() 方法强制转换一下,保证有效性。

如果传进来的是一个数字,就将其赋给 n2

如果传进来的参数不是一个标准的数字就会被 Number() 处理为数字或 NaN,如果为 NaN 的话,为 n2 赋值为 0,表示一项也不取出(根据内置的 slice 结果处理的)

第三行:

  n2 = n2 < 0 ? Math.abs(n2) > this.length && n2 > -1 ? 0 : this.length + Math.ceil(n2) : n2 > this.length ? this.length : Math.floor(n2);

  如果觉得上面的三元运算符难以理解,可以看下面用 if 写的

    if(n2 < 0){
        if(Math.abs(n2) > this.length && n2 > -1){
            n2 = 0;
        }else {
            n2  = this.length + Math.ceil(n2);
        }
    }else if(n2 > this.length){
        n2  = this.length;
    }else{
        n2 = Math.floor(n2);
    }
第三行代码_IF
  • 如果 n2 为负数,结束位置则是从数组末尾为开始计算的,比如:
  •     var ary = [1,2,3,4,5];
        ary.slice(0,-3); //输出结果为[1,2]
        //结束位置从末尾开始算起,也就是从第0个位置开始,到倒数第3个(不包括)为止

当 n2 小于 0,并且 n2 > -1 ,也就是 n2 是零点几的负小数时,返回空数组,所以给 n2 赋值 0

当 n2 小于 0,并且 n2 的绝对值比数组长度更大时,返回空数组,所以给 n2 赋值 0

不要问为什么这么处理,这里是根据官方内置 slice 方法的结果处理的,哈哈哈~


 当 n2 小于 0 ,剩下的情况下为什么是 this.length + Math.ceil(n2) ?

因为负数对应的位置转为正数就是用 数组长度+负数 来计算

比如上方的代码中,slice(0,-3),相当于 slice(0,2)

为什么还用 Math.ceil(n2)?

遇到小数向上进位

当 0 < n2 < this.length 时,自动向下取整,所以 Math.floor(n2)

当 n2 大于数组长度时,返回开始位置之后的所有项,所以给 n2 赋值 this.length

老规矩,根据内置的 slice 方法返回的结果处理的

  至此,第二个形参所有的情况已经处理完毕。


 最后一行(没算上 for 循环):

  n1 = n1 ? n1 < 0 ? Math.abs(n1) > this.length || n1 > -1 ? 0 : this.length + Math.ceil(n1) : n1 > this.length ? this.length : Math.floor(n1) : 0;

  如果觉得上面的三元运算符难以理解,可以看下面用 if 写的

    if (n1) {
        if (n1 < 0) {
            if (Math.abs(n1) > this.length || n1 > -1) {
                n1 = 0;
            } else {
                n1 = this.length + Math.ceil(n1)
            }
        } else if (n1 > this.length) {
            n1 = this.length;
        } else {
            n1 = Math.floor(n1)
        }
    } else {
        n1 = 0;
    }
最后一行代码_IF

  n1 的处理就相对来说比较简单了

当 n1 为假时 ,设为从数组第一项开始,所以为 n1 赋值为 0

当 n1 为负数,并且 n1 的绝对值大于 this.length ,设为从数组第一项开始,所以为 n1 赋值为 0

当 n1 为负数,并且 n1 小于 -1 ,也就是负零点几小数 ,设为从数组第一项开始,所以为 n1 赋值为 0

剩下的 n1 为负数的区间,用 this.length + Math.ceil(n1) 来处理 n1 的值,这里的情况和上面 n2 的情况一样,就不重复了


当 n1 大于 this.length 时,从数组末尾开始取,所以为 n1 赋值 this.length

接下来,只剩 this.length >= n1 > 0 的时候了,一律向下取整(针对的小数),所以为 n1 赋值 Math.floor(n1)

为什么这么处理呢?

根据 slice 返回的结果判断的

  剩下的 for 循环在开头就已经提过了,n3 的作用也不必多说了吧,里面放的就是从执行这个方法的函数中取出来的内容。
  虽然篇幅略多,但其实思路很简单,就是处理两个形参的值,只不过文笔有限只能期望用更多的文字来表达心中的想法,暂时还无法做到一针见血的清晰表达。
  如果代码有误或者和原生 slice 有存在差别的地方,还望大家留言提醒,我会第一时间修改,提前谢谢啦!
  同时,本人也严重不建议把时间浪费在阅读这些文字描述上,大概了解思想然后通过自己一点点的尝试去总结自己的方法,比在这里生啃要强的太多了,代码不值钱,关键是思想,多动手动脑是最有用的
  再次感叹下,理解和表达真是有区别的,想完整的把心中所想的表达出来看来还需要多写一写了,写的比较粗糙希望各位不要太介意…
原文地址:https://www.cnblogs.com/xwant/p/7253121.html