本篇分析下 take
家族的方法,该系列的方法主要是用来从数组的两端开始提取切片。包括take
、takeWhile
、takeRight
、takeRightWhile
以及核心方法baseWhile
、slice
,并根据 ECMAScript
标准分析下>>> 0
逻辑右移的作用。
具体的依赖路径图如下所示:
Number 类型
在 JS
中存储时是不区分小数和整数的。所有的 Number
类型,都是用 IEEE-754标准
中的双精度浮点数存储。组成方式如下:
但是某些运算只有整数才能完成,此时 JavaScript
会自动把 64
位浮点数,转成 32
位有符号整数,然后再进行运算,比如位运算。
位运算符只对整数起作用,如果不是整数,会自动转为 32
位有符号整数后再执行。
逻辑右移
之前的文章中分析过 slice
和 baseWhile
,在这里再拿出来的目的主要是分析下其中用到的逻辑右移。
ECMA262
中关于逻辑右移的内容如下:
The Unsigned Right Shift Operator (
>>>
)NOTE: Performs a zero-filling bitwise right shift operation on the left operand by the amount specified by the right operand.
Runtime Semantics:
Evaluation : ShiftExpression >>> AdditiveExpression
- Let
lref
be the result of evaluating ShiftExpression.- Let
lval
be ? GetValue(lref).- Let
rref
be the result of evaluating AdditiveExpression.- Let
rval
be ? GetValue(rref).- Let
lnum
be ? ToNumeric(lval).- Let
rnum
be ? ToNumeric(rval).- If Type(lnum) is different from Type(rnum), throw a TypeError exception.
- Let
T
be Type(lnum).- Return
T::unsignedRightShift(lnum, rnum)
.
Number::unsignedRightShift ( x, y )
- Let
lnum
be ! ToInt32(x).- Let
rnum
be ! ToUint32(y).- Let
shiftCount
be the result of masking out all but the least significant 5 bits ofrnum
, that is, computernum & 0x1F
.- Return the result of performing a zero-filling right shift of
lnum
byshiftCount
bits. Vacated bits are filled with zero. The result is an unsigned32-bit
integer.
翻译下:
无符号右移操作符(
>>>
)注意:在左操作数上,按右操作数所指定数量的 0 填充位,来完成右移操作。
运行时语义:
计算 : ShiftExpression >>> AdditiveExpression
- 让
lref
为 ShiftExpression 计算的结果。- 让
lval
为 ? GetValue(lref)。- 让
rref
为 AdditiveExpression 计算的结果。- 让
rval
为 ? GetValue(rref)。- 让
lnum
为 ? ToNumeric(lval)。- 让
rnum
为 ? ToNumeric(rval)。- 如果 Type(lnum) 与 Type(rnum) 不同, 则抛出
TypeError
错误。- 让 T 为 Type(lnum)。
- 返回
T::unsignedRightShift(lnum, rnum)
。
Number::unsignedRightShift ( x, y )
x >>> 0
在核心方法 slice
中,用到了 (end - start) >>> 0
,所以可以根据标准看看到底有什么用。
先看看前文第 5
条,用 ToNumeric
方法将 lnum
转化为数字。下面的表格是ECMA262标准
中ToNumeric
如何将各种类型转化为数字。
参数类型 | 结果 |
---|---|
Undefined | 返回 NaN。 |
Null | 返回 +0。 |
Boolean | if(true) return 1; if(false) return +0; |
Number | 返回参数 (不转化)。 |
String | 能转化为数字就返回对应数字,否则就返回 NaN |
Symbol | 抛出 TypeError 错误。 |
BigInt | 抛出 TypeError 错误。 |
Object | 应用如下步骤: 1. 让 primValue 为 ? ToPrimitive(argument, hint Number)。 2. 返回 ? ToNumber(primValue)。 |
可以发现转化为数字后,左操作数就变为了 NaN
或者数字,同时第 8
条又排除了 NaN
,所以第 8
条执行完后左操作数是绝对的数字类型 Number
或 BigInt。
接下来执行第 9
条unsignedRightShift ( x, y )
方法,在这个方法中先把左操作数变为 32
位有符号整数,右操作数变为 32
位无符号整数,再执行 0
填充右移。
所以如果右操作数为 0
时,就直接把左操作数最高位设为 0
,其余不动。
通过以上操作后,不管左操作数是什么类型,是正是负,是小数还是整数,统统变成了非负整数。
举个例子,-1.1(64位浮点) -> -1(32位有符号整形) = 11111......111(位) -> 01111......111(逻辑右移0位) -> 4294967295(32位有符号整形) -> 4294967295(64位浮点)
。
核心方法
slice
方法在之前的文章中分析过,现在再拿出来主要是看看优秀 JS
库对参数的严格判断。在 slice
真正的裁剪功能实现前,用了大量的篇幅去进行参数的判断和转化,包括如下的判断:
- 数组的存在和数组长度的判断。
start
的存在、start
是否小于0
和start
与length
的大小,计算转化为标准start
。end
的存在end
是否小于0
和end
与length
的大小,计算转化为标准length
。start
和length
必须为非负整数。
slice
1 | /** |
baseWhile
baseWhile
是对 slice
方法的封装,不管是 drop
还是 take
其实都是裁剪字符串。
1 | import slice from '../slice.js'; |
take 家族
take
和 takeRight
这两个方法可以放在一起说,都是简单封装的 slice
,提前计算出起始和结束的 index
即可。
take
1 | import slice from './slice.js'; |
takeRight
1 | import slice from './slice.js'; |
takeWhile
takeWhile
和 takeRightWhile
也是对 baseWhile
的简单封装,第三个参数 isDrop
都是传的 false
,只是第四个参数 fromRight
的区别罢了。
1 | import baseWhile from './.internal/baseWhile.js'; |
takeRightWhile
1 | import baseWhile from './.internal/baseWhile.js'; |
原生实现
take
和 takeRight
本来就是用封装的 lodash
的 slice
方法,所以原生实现时直接调用 slice
即可,原生的 slice
已经实现了对参数的各种判断。
1 | // 没有做各种错误情况的判断,比如n < 0 |