lodash源码乱读-drop后的故事(toNumber)

发到网络上的很可能是这篇,

编程是很抽象,很细致的一件事。

正如difference方法,有一层baseDifference的抽象。drop方法,也有一个baseXXX的方法(baseSlice)。

以base开头的函数,是一个基础的实现。一系列更具体的实现,都再这之上。

在这些基础或是具体的实现了,都不会缺少一些帮助函数,在drop中一系列的实现中,就有如下的函数。

  • toInteger
  • toFinite
  • toNumber

其实在drop的实现源码里只有toInteger。我将他们两个列出来是因为toInteger基于toFinite,toNumber, toNumber中也有两个帮助函数

  • isObject
  • isSymbol

先看toNumber这个函数的源码

function toNumber(value) {
  if (typeof value == 'number') {
    return value;
  }
  if (isSymbol(value)) {
    return NAN;
  }
  if (isObject(value)) {
    var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
    value = isObject(other) ? (other + '') : other;
  }
  if (typeof value != 'string') {
    return value === 0 ? value : +value;
  }
  value = value.replace(reTrim, '');
  var isBinary = reIsBinary.test(value);
  return (isBinary || reIsOctal.test(value))
    ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
    : (reIsBadHex.test(value) ? NAN : +value);
}

我第一个疑惑的地方是这里

var other = typeof value.valueOf == 'function' ? value.valueOf() : value;

valueOf的理解只是知道它是Object原型上的一个方法,实际调用,返回的是它的实际值。

valueOf返回指定对象的原始值。(primitive value )

link查阅更多

JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。因此,不同类型对象的valueOf()方法的返回值和返回值类型均可能不同

做些尝试

var arr = []
arr.valueOf() == arr 
// => true

var n = 1
n.valueOf() === n
// => true

var f = function(){}
f.valueOf() === f
// => true

var d = new Date()
d.valueOf() === d  
//false

var b = true
b.valueOf() === b
// true

var s = "123"
s.valueOf() === s
// true

var o = {a:1}
o.valueOf() === o
// true

只有Date类型有点奇怪,Date类型的valueOf返回的是存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。

之后是这里

value = isObject(other) ? (other + '') : other;

isObject

function isObject(value) {
  var type = typeof value;
  return value != null && (type == 'object' || type == 'function');
}

需要排除null的影响。

value在这在做一个类型转换,转换撑字符串。

value = value.replace(reTrim, ''); //这里删除了 字符串的前后的空白符
var isBinary = reIsBinary.test(value); // 二进制
return (isBinary || reIsOctal.test(value)) //二进制或者八进制
? freeParseInt(value.slice(2), isBinary ? 2 : 8)
: (reIsBadHex.test(value) ? NAN : +value);

要看懂上边的代码,要读懂下边的几个正则,

// 十六进制
var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
// 二进制
var reIsBinary = /^0b[01]+$/i;
// 八进制
var reIsOctal = /^0o[0-7]+$/i;

0x是十六进制的前缀,在计算机语言中,在数字前加进制前缀,更容易理解,必定同样的数字,同样是8进制或者二进制表示的真实数字天壤之别。

0b是二进制的前缀。

0o是八进制的前缀。

0d是十进制的前缀。

再回到之前代码的逻辑

  • 处理掉空白符
  • 保存二进制判断,因为有两处会用到
  • 二进制或者八进制判断
  • true => 截掉前缀并调用对应的parseInt返回结果
  • 如果是16进制,就返回NAN,否则返回value
+value 

貌似没见过这种写法,查阅相关资料,(链接如下)

一元加号运算符,位于它要操作的数的前方,计算其操作数的数值,如果操作的value不是数字,也会尝试将它转换成数字。

一元正号是转换其他对象到数值的最快方法,也是最推荐的做法,因为它不会对数值执行任何多余操作。它可以将字符串转换成整数和浮点数形式,也可以转换非字符串值 true,false 和 null。小数和十六进制格式字符串也可以转换成数值。负数形式字符串也可以转换成数值(对于十六进制不适用)。如果它不能解析一个值,则计算结果为 NaN.

+3     // 3
+"3"   // 3
+true  // 1
+false // 0
+null  // 0
+function(val){ return val;} //NaN

详细查看parseInt方法,在没有指定基数(传入的第二个参数,没有指定就是没传第二个参数),

  • 如果字符串 string 以"0x"或者"0X"开头, 则基数是16 (16进制).
  • 如果字符串 string 以"0"开头,基数是8(八进制)或者10(十进制),那么具体是哪个基数由实现环境决定。ECMAScript 5 规定使用10,但是并不是所有的浏览器都遵循这个规定。因此,永远都要明确给出radix参数的值。
  • 如果字符串 string 以其它任何值开头,则基数是10 (十进制)。
freeParseInt(value.slice(2), isBinary ? 2 : 8)

这里其实就是根据输入的字符串是否是某进制开头做的判断,如果是8进制或2进制,就移除"字符串开头的0x",然后设置相应的进制。。。

如此麻烦的去做也是为了做兼容。

实际上,我们使用parseInt的时候,应该明确的传入第二个参数。实际上,还是应该尽量使用_.toNumber来规避这个问题。

你可能感兴趣的