Open toFrankie opened 1 year ago
事情经过是这样的,前几天上班路上,跟往常一样拿起手机看头条、逛知乎、刷掘金嘛。
过程中,看到以下这个面试题:
['1', '2', '3'].map(parseInt) 输出什么?
['1', '2', '3'].map(parseInt)
其实,这题很简单,不就是类似 [0, 1, 2].filter(Boolean) 这种变形题目嘛,但我却没能马上说出答案。
[0, 1, 2].filter(Boolean)
我知道 parseInt() 的第二个参数跟进制数相关,但由于平常多数是缺省第二个参数,平常写项目也会启用 ESLint 的 radix 规则,但规则启用时也几乎是填写 10 作为实参,因为涉及其他进制数的情况确实很少很少...
parseInt()
10
所以,趁机再熟悉下 parseInt(string, radix) 这个函数,也是挺不错的。
parseInt(string, radix)
回到上面的题目,分解一下,就是返回以下三个运算结果组成的数组嘛:
parseInt('1', 0, ['1', '2', '3']) parseInt('2', 1, ['1', '2', '3']) parseInt('3', 2, ['1', '2', '3'])
对于 parseInt() 函数,仅接收两个参数,所以第三个参数实际上没有任何作用,因此 ['1', '2', '3'].map(parseInt) 结果就是:
[ parseInt('1', 0), parseInt('2', 1), parseInt('3', 2) ]
但这篇文章的重点并非是答案,我们应该关注 parseInt(string, radix) 函数本身。
如果常用 ESLint 的同学,应该知道它有一个规则 radix 是跟 parseInt() 相关的。
看个例子,它们分别打印什么结果?
parseInt('071') // 57 parseInt('071', 10) // 71
有些本着求真的同学,将 parseInt('071') 拷到控制台发现,无论是 Chrome、Firefox 还是 Safari 都是打印出 71 而不是 57。
parseInt('071')
71
57
我为什么写成 57 呢?是写错了吗?明明在浏览器中 parseInt('071') 都是打印出 71 呢!
先别急,我们知道在「严格模式」下,是不允许使用以 0 开头的八进制语法的。
0
"use strict" var n = 071 // SyntaxError: Octal literals are not allowed in strict mode.
你有可能不知道,其实在 ES6 标准发布之前,ECMAScript 是没有八进制语法的,至于类似 071 这种八进制表示法它只是被所有浏览器厂商支持罢了。比如 Object.prototype.__proto__ 从来就不是 ECMAScript 的标准语法一样,但所有浏览器都支持罢了,标准语法是 Object.getPrototypeOf()。
071
Object.prototype.__proto__
Object.getPrototypeOf()
在 ES6 中提供了八进制数的标准规范:在数字前加上 0o 来表示八进制数,比如八进制的 71 用 0o71 表示。
0o
0o71
回到 parseInt(string, radix) 与八进制的话题上,
当没有指定 radix 参数时,看看各家是如何解析八进制数的?
radix
ES3「不提倡」将带有 0 开头的数值字符串解析为一个八进制。(不赞成,但没禁止)
ES5 规范中「不允许」parseInt 函数的实现环境把带有 0 开头的数值字符串解析为八进制数值,而是以 10 为基数(即十进制)进行解析。(规范禁止了,但浏览器没有按标准实现)
parseInt
各浏览器厂商大爷们:我偏不按你规范去实现,仍要把带有 0 开头的数值字符串解析成八进制数值。(我行我素)
本着求真的态度,拿出了上古神器去验证并得出结果:在 IE8 及以下浏览器 parseInt('071') 打印结果为 57(下图),而 IE9 及以上则为 71。
随着 JavaScript 的飞速发展,浏览器厂商们都向标准靠近了,不再肆意我行我素了。至于浏览器 parseInt('071') 打印结果是 71,原因正是现在的浏览器 JS 引擎是以 10 为基数进行解析了。
尽管 2022 年了,但仍要兼容旧版(远古)浏览器,所以显式指定 radix 参数是非常有必要的。本节用一个比较典型的案例来说明,使用 parseInt 函数时,应当指定 radix 参数。
在 JavaScript 中,有四种进制数的表示语法:
0b
0B
0O
0 ~ 7
0x
0X
解析一个字符串并返回指定基数的「十进制整数」或者 NaN。
NaN
string 被解析的值。若参数不是字符串类型,内部先隐式类型转换为字符串类型(即调用相应值的 toString() 方法)
string
toString()
radix(可选,取值范围 2 ~ 36 的整数) 表示字符串的基数。但请注意,10 不是默认值!当参数 radix 缺省时,会有几种情况,下面会介绍。
2 ~ 36
若 string 参数带有前导或尾随空白符(包括 \n、\r、\f、\t、\v 和空格),它们将会被忽略。换句话说,实际有意义的是第一个非空白符开始。
\n
\r
\f
\t
\v
parseInt(' \n\r\f\t\v 11', 2) // 3,相当于 parseInt('11', 2)
当 string 参数的「第一个非空格字符」不能转换为数字,或者当 radix < 2 || radix > 36 时,返回值为 NaN。
radix < 2 || radix > 36
parseInt('a11') // NaN parseInt('11', 1) // NaN parseInt('11', 37) // NaN
但请注意,并不是所有的字母开头的都返回 NaN。比如 parseInt('a11', 12) 返回值为 1453。因为超过十进制之后,字母也可能用于表示相应的进制数的。
parseInt('a11', 12)
1453
当 radix 参数为 undefined、null、0 或缺省(未显式指定)时,JavaScript 引擎会假定以下情况:
undefined
null
如果 string 是以 0x 或 0X 开头的,那么 radix 将会假定为 16,将其余部分当作十六进制数去解析。比如 parseInt('0xf') 相当于 parseInt('f', 16),结果为 15。
16
parseInt('0xf')
parseInt('f', 16)
15
如果 string 是以 0 开头的,那么 radix 在不同的环境下,有可能被假定为 8(八进制)或假定为 10(十进制)。具体选择哪一种作为 radix 的值,视乎运行 JavaScript 的宿主环境(前面提到过了)。因此,在使用 parseInt 时,一定要显式指定 radix 参数。
8
如何 string 是以任何其他值开头,radix 会被假定为 10(十进制)。
parseInt 对 string 的解析规则是:从第一个非空白符开始,然后一直往后面查找,若有任意一个字符不能被转换为数值就会停止,那么最终被解析的字符串就是从开始到停止的这一截字符串。
parseInt('11a', 10) // 结果为 11,被解析的字符串为 '11' parseInt('11a', 11) // 结果为 142,被解析的字符串为 '11a',因为在十一进制里面,a 是有意义的。
所以,在使用 parseInt 处理 BigInt 类型的时候,最终的返回值总是为 Number 类型(过程中会失去精度),其中 BigInt 类型的拖尾的 n 是会被丢弃的。
BigInt
Number
n
parseInt(1024n, 10) // 1024 parseInt(1024n, 36) // 46732,相当于 parseInt('1024', 36) // 注意,有别于以下这个 parseInt('1024n', 36) // 1682375
原因非常的简单,前面也提到过的。当 parseInt 的第一个参数不是 String 类型时,会调用 BigInt.prototype.toString() 方法先转换为字符串,即 1024n.toString(),结果为 1024。
String
BigInt.prototype.toString()
1024n.toString()
1024
回到文章开头的题目:
这时候,是不是就可以快速说出答案了:[1, NaN, NaN]。
[1, NaN, NaN]
借机彻底弄懂了 parseInt() 的方法,可以满意地离开了。
一、背景
事情经过是这样的,前几天上班路上,跟往常一样拿起手机看头条、逛知乎、刷掘金嘛。
过程中,看到以下这个面试题:
其实,这题很简单,不就是类似
[0, 1, 2].filter(Boolean)
这种变形题目嘛,但我却没能马上说出答案。我知道
parseInt()
的第二个参数跟进制数相关,但由于平常多数是缺省第二个参数,平常写项目也会启用 ESLint 的 radix 规则,但规则启用时也几乎是填写10
作为实参,因为涉及其他进制数的情况确实很少很少...所以,趁机再熟悉下
parseInt(string, radix)
这个函数,也是挺不错的。回到上面的题目,分解一下,就是返回以下三个运算结果组成的数组嘛:
对于
parseInt()
函数,仅接收两个参数,所以第三个参数实际上没有任何作用,因此['1', '2', '3'].map(parseInt)
结果就是:但这篇文章的重点并非是答案,我们应该关注 parseInt(string, radix) 函数本身。
二、八进制数表示法的前世今生
如果常用 ESLint 的同学,应该知道它有一个规则 radix 是跟
parseInt()
相关的。看个例子,它们分别打印什么结果?
我为什么写成
57
呢?是写错了吗?明明在浏览器中parseInt('071')
都是打印出71
呢!先别急,我们知道在「严格模式」下,是不允许使用以
0
开头的八进制语法的。你有可能不知道,其实在 ES6 标准发布之前,ECMAScript 是没有八进制语法的,至于类似
071
这种八进制表示法它只是被所有浏览器厂商支持罢了。比如Object.prototype.__proto__
从来就不是 ECMAScript 的标准语法一样,但所有浏览器都支持罢了,标准语法是Object.getPrototypeOf()
。回到
parseInt(string, radix)
与八进制的话题上,当没有指定
radix
参数时,看看各家是如何解析八进制数的?ES3「不提倡」将带有
0
开头的数值字符串解析为一个八进制。(不赞成,但没禁止)ES5 规范中「不允许」
parseInt
函数的实现环境把带有0
开头的数值字符串解析为八进制数值,而是以10
为基数(即十进制)进行解析。(规范禁止了,但浏览器没有按标准实现)各浏览器厂商大爷们:我偏不按你规范去实现,仍要把带有
0
开头的数值字符串解析成八进制数值。(我行我素)本着求真的态度,拿出了上古神器去验证并得出结果:在 IE8 及以下浏览器
parseInt('071')
打印结果为57
(下图),而 IE9 及以上则为71
。随着 JavaScript 的飞速发展,浏览器厂商们都向标准靠近了,不再肆意我行我素了。至于浏览器
parseInt('071')
打印结果是71
,原因正是现在的浏览器 JS 引擎是以10
为基数进行解析了。尽管 2022 年了,但仍要兼容旧版(远古)浏览器,所以显式指定
radix
参数是非常有必要的。本节用一个比较典型的案例来说明,使用parseInt
函数时,应当指定radix
参数。在 JavaScript 中,有四种进制数的表示语法:
0b
或0B
开头的数值。0o
或0O
开头的数值。浏览器等宿主环境也支持以「前导零」开头,且只有0 ~ 7
的数字表示八进制数。0x
或0X
开头的数值。三、parseInt
语法
解析一个字符串并返回指定基数的「十进制整数」或者
NaN
。string
被解析的值。若参数不是字符串类型,内部先隐式类型转换为字符串类型(即调用相应值的toString()
方法)radix
(可选,取值范围2 ~ 36
的整数) 表示字符串的基数。但请注意,10
不是默认值!当参数radix
缺省时,会有几种情况,下面会介绍。注意点 1
若
string
参数带有前导或尾随空白符(包括\n
、\r
、\f
、\t
、\v
和空格),它们将会被忽略。换句话说,实际有意义的是第一个非空白符开始。注意点 2
当
string
参数的「第一个非空格字符」不能转换为数字,或者当radix < 2 || radix > 36
时,返回值为NaN
。但请注意,并不是所有的字母开头的都返回
NaN
。比如parseInt('a11', 12)
返回值为1453
。因为超过十进制之后,字母也可能用于表示相应的进制数的。注意点 3
当
radix
参数为undefined
、null
、0
或缺省(未显式指定)时,JavaScript 引擎会假定以下情况:如果
string
是以0x
或0X
开头的,那么radix
将会假定为16
,将其余部分当作十六进制数去解析。比如parseInt('0xf')
相当于parseInt('f', 16)
,结果为15
。如果
string
是以0
开头的,那么radix
在不同的环境下,有可能被假定为8
(八进制)或假定为10
(十进制)。具体选择哪一种作为radix
的值,视乎运行 JavaScript 的宿主环境(前面提到过了)。因此,在使用parseInt
时,一定要显式指定radix
参数。如何
string
是以任何其他值开头,radix
会被假定为10
(十进制)。注意点 4
parseInt
对string
的解析规则是:从第一个非空白符开始,然后一直往后面查找,若有任意一个字符不能被转换为数值就会停止,那么最终被解析的字符串就是从开始到停止的这一截字符串。所以,在使用
parseInt
处理BigInt
类型的时候,最终的返回值总是为Number
类型(过程中会失去精度),其中BigInt
类型的拖尾的n
是会被丢弃的。原因非常的简单,前面也提到过的。当
parseInt
的第一个参数不是String
类型时,会调用BigInt.prototype.toString()
方法先转换为字符串,即1024n.toString()
,结果为1024
。四、最后
回到文章开头的题目:
这时候,是不是就可以快速说出答案了:
[1, NaN, NaN]
。借机彻底弄懂了
parseInt()
的方法,可以满意地离开了。