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 返回的结果判断的