[TOC]
前言
先抛两个问题,读者朋友们可以思考一下:
['1','2','3','4','5'].map(parseInt)的结果是什么? 是[1,2,3,4,5]吗?['10','10','10','10','10'].map(parseInt)的结果是什么? 是[10,10,10,10,10]吗?
(备注:本文进行测试的浏览器都是 Chromium 86.0.4240.75,运行结果都是在该版本的浏览器中执行的)
正文
我们在 Chromium 86.0.4240.75 浏览器里执行上述两个表达式,结果如下图所示:
并不是[1,2,3,4,5]和 [10,10,10,10,10],为什么呢?
我们看下 Array.prototype.map 和 parseInt的API说明。
1. Array.prototype.map 的API解读:
let new_array = arr.map(function callback( currentValue[, index[, array]]) {
// return element for new_array
}[, thisArg])
数组的map方法是一个遍历的过程。map方法的参数是一个回调函数,回调的意思是:现在只是将函数定义在这里而暂时先不去调用,等到时机成熟时再回过头来调用这个函数。 而一旦这个函数被调用时,给这个函数所传递的实参和返回值将分别是:
- 参数1 currentValue: 原数组当前被遍历到的元素的数值
- 参数2 index:原数组当前被遍历到的元素的下标
- 参数3 array:原数组自身
- 返回值:是一个新的数组(假设称作新数组),新数组的元素个数和原数组的相等,新数组中的每一个元素和原数组中相同下标位置上的元素之间是一一映射的关系。具体的映射关系是:原数组中的某个元素经过回调函数处理后得到一个新的数值,这个新的数值就作为新数组中相同下标位置处的元素。
也就是说:对于['1','2','3','4','5'].map(parseInt)来说,
假设有定义:var arr1 = ['1','2','3','4','5'], 那么,对各个元素的遍历过程中,回调函数每次执行时的实参情况如下表所示:
| 元素 | 实参1 | 实参2 | 实参3 | 回调函数的调用情况 |
|---|---|---|---|---|
| '1' | '1' | 0 | arr1 | parseInt('1', 0, arr1) |
| '2' | '2' | 1 | arr1 | parseInt('2', 1, arr1) |
| '3' | '3' | 2 | arr1 | parseInt('3', 2, arr1) |
| '4' | '4' | 3 | arr1 | parseInt('4', 3, arr1) |
| '5' | '5' | 4 | arr1 | parseInt('5', 4, arr1) |
2. parseInt 的API解读:
parseInt(string, radix);
解释下参数和函数的返回值:
- 参数1 string: 表示一个想要转换为int类型的字符串
- 参数2 radix: 表示这个字符串当前对应的进制数
- 返回值:
- 表示将这个字符串按照 radix 所指定的进制所代表的整数,转换为十进制后的结果。转换后的结果可能是一个整数或 NaN.
- 有个特例:如果 radix 是 undefined 或 null 或 0,并且 string 不是以二进制或八进制特有的字符开头(二进制是:'0', 八进制是 '0x' 或 '0X'),则认为 radix 是 10 单纯看文字描述可能有些难懂,我们举几个例子:
- [例1]
parseInt("101", 2)分为两个步骤:- 将字符串 '101' 去除空格前缀,然后按照二进制从左向右逐位转换为整数,转换过程中只要遇到无法转换的位就停止当前及后续位的转换。无法转换的情况包括:可能是一个非数字的字符,或者是一个超过二进制合法范围0~1的数字(例如:3)。'101'按照二进制转换为整数的结果是 101
- 再将步骤1转换后得到的二进制数转换为十进制,这个十进制数就是函数的返回值。步骤1中的二进制数 101 转换为十进制就是
1 × 2^2 + 0 × 2^1 + 1 × 2^0 = 5,所以函数返回值就是5
- [例2]
parseInt("101", 3)分为两个步骤:- 将字符串 '101' 去除空格前缀,然后按照三进制从左向右逐位转换得到的结果是 101
- 将步骤1得到的三进制数字 101 转换为十进制是
1 × 3^2 + 0 × 3^1 + 1 × 3^0 = 10,所以函数返回值就是10
- [例3]
parseInt("123", 2)分为两个步骤:- 将字符串 '123' 去除空格前缀,然后按照二进制从左向右逐位转换,我们会发现,当转换到数值2时,由于2超出了二进制的合法数值范围 0~1,所以转换的过程就此停止,那么只转换了数字1,所以最终得到的转换结果就是二进制的1.
- 将步骤1得到的二进制数字1转换为十进制是
1 × 2^0 = 1,所以函数返回值就是1
- [例4]
parseInt("234", 2)分为两个步骤:- 将字符串 '234' 去除空格前缀,然后按照二进制从左向右逐位转换,我们会发现,首个字符都无法转换,因为它超过了二进制的合法数值范围 0~1,所以转换的过程就此停止,转换还没有成功过,所以得到的结果直接是 NaN
- 步骤1得到的结果是NaN, 那么转换为十进制也是 NaN, 所以函数返回值是 NaN.
- [例5]
parseInt("123", 0)分为两个步骤:- 将字符串 '123' 去除空格前缀,然后发现radix是特殊数值0,并且字符串没有前缀'0'、'0x'、'0X',那么radix等效于是10,所以需要按照十进制从左向右逐位转换,转换结果就是十进制的整数 123
- 步骤1得到的结果是十进制的123, 那么转换为十进制仍是 123, 所以函数返回值是 123.
- [例6]
parseInt("123", undefined)分为两个步骤:- 将字符串 '123' 去除空格前缀,按照十进制从左向右逐位转换,转换结果是十进制的整数 123
- 步骤1得到的结果转换为十进制仍是 123, 所以函数返回值是 123.
- [例7]
parseInt("123", null)同理,函数返回值是 123. 上述七个例子在 Chromium 86.0.4240.75 里的运行结果如下:
详细分析前言提到的问题
经过对上述两个API的解读,我们再回来分析前言中提到的两个问题。
1. ['1','2','3','4','5'].map(parseInt)数组中各个元素的转换过程
| 元素 | 下标 | 回调函数的初始情况 | 回调函数的等效情况 | 回调函数执行结果 | NaN的原因解释 |
|---|---|---|---|---|---|
| '1' | 0 | parseInt('1', 0, arr1) | parseInt('1', 10) | 1 | \ |
| '2' | 1 | parseInt('2', 1, arr1) | parseInt('2', 1) | NaN | 2超过一进制的数值范围 |
| '3' | 2 | parseInt('3', 2, arr1) | parseInt('3', 2) | NaN | 3超过二进制的数值范围 |
| '4' | 3 | parseInt('4', 3, arr1) | parseInt('4', 3) | NaN | 4超过三进制的数值范围 |
| '5' | 4 | parseInt('5', 4, arr1) | parseInt('5', 4) | NaN | 5超过四进制的数值范围 |
2. ['10','10','10','10','10'].map(parseInt)数组中各个元素的转换过程:
| 元素 | 下标 | 回调函数的初始情况 | 回调函数的等效情况 | 回调函数执行结果 | NaN的原因解释 |
|---|---|---|---|---|---|
| '10' | 0 | parseInt('10', 0, arr1) | parseInt('10', 10) | 10 | \ |
| '10' | 1 | parseInt('10', 1, arr1) | parseInt('10', 1) | NaN | 首数字(数字1)超过一进制的数值范围 |
| '10' | 2 | parseInt('10', 2, arr1) | parseInt('10', 2) | 2 | \ |
| '10' | 3 | parseInt('10', 3, arr1) | parseInt('10', 3) | 3 | \ |
| '10' | 4 | parseInt('10', 4, arr1) | parseInt('10', 4) | 4 | \ |
替代方案
如果想让 ['1','2','3','4','5'] 转换为 [1,2,3,4,5], ['10','10','10','10','10'] 转换为 [10,10,10,10,10],怎么办呢?
-
方案一: 可以继续使用 map,map 的参数使用
Number, 即:['1','2','3','4','5'].map(Number)['10','10','10','10','10'].map(Number)
上述方案在 Chromium 86.0.4240.75 里的运行结果如下:
-
方案二:仍然是继续使用 map,提供一个对 parseInt的封装函数,进制参数在内部被强制封装为 10,将封装后的新函数作为 map 的参数.
var parseIntByRadixTen = function(str) { return parseInt(str, 10) }调用:
['1','2','3','4','5'].map(parseIntByRadixTen) ['10','10','10','10','10'].map(parseIntByRadixTen)上述方案在 Chromium 86.0.4240.75 里的运行结果如下: