数组的 map 方法
规格的22.1.3.15 小节定义了数组的map
方法。该小节先是总体描述map
方法的行为,里面没有提到数组空位。
后面的算法描述是这样的。
- Let
O
beToObject(this value)
.ReturnIfAbrupt(O)
.- Let
len
beToLength(Get(O, "length"))
.ReturnIfAbrupt(len)
.- If
IsCallable(callbackfn)
isfalse
, throw a TypeError exception.- If
thisArg
was supplied, letT
bethisArg
; else letT
beundefined
.- Let
A
beArraySpeciesCreate(O, len)
.ReturnIfAbrupt(A)
.- Let
k
be 0.- Repeat, while
k
<len
- Let
Pk
beToString(k)
.- Let
kPresent
beHasProperty(O, Pk)
.ReturnIfAbrupt(kPresent)
.- If
kPresent
istrue
, then
- Let
kValue
beGet(O, Pk)
.ReturnIfAbrupt(kValue)
.- Let
mappedValue
beCall(callbackfn, T, «kValue, k, O»)
.ReturnIfAbrupt(mappedValue)
.- Let
status
beCreateDataPropertyOrThrow (A, Pk, mappedValue)
.ReturnIfAbrupt(status)
.- Increase
k
by 1.- Return
A
.
翻译如下。
- 得到当前数组的
this
对象- 如果报错就返回
- 求出当前数组的
length
属性- 如果报错就返回
- 如果 map 方法的参数
callbackfn
不可执行,就报错- 如果 map 方法的参数之中,指定了
this
,就让T
等于该参数,否则T
为undefined
- 生成一个新的数组
A
,跟当前数组的length
属性保持一致- 如果报错就返回
- 设定
k
等于 0- 只要
k
小于当前数组的length
属性,就重复下面步骤
- 设定
Pk
等于ToString(k)
,即将K
转为字符串- 设定
kPresent
等于HasProperty(O, Pk)
,即求当前数组有没有指定属性- 如果报错就返回
- 如果
kPresent
等于true
,则进行下面步骤
- 设定
kValue
等于Get(O, Pk)
,取出当前数组的指定属性- 如果报错就返回
- 设定
mappedValue
等于Call(callbackfn, T, «kValue, k, O»)
,即执行回调函数- 如果报错就返回
- 设定
status
等于CreateDataPropertyOrThrow (A, Pk, mappedValue)
,即将回调函数的值放入A
数组的指定位置- 如果报错就返回
k
增加 1- 返回
A
仔细查看上面的算法,可以发现,当处理一个全是空位的数组时,前面步骤都没有问题。进入第 10 步中第 2 步时,kPresent
会报错,因为空位对应的属性名,对于数组来说是不存在的,因此就会返回,不会进行后面的步骤。
const arr = [, , ,];
arr.map(n => {
console.log(n);
return 1;
}) // [, , ,]
上面代码中,arr
是一个全是空位的数组,map
方法遍历成员时,发现是空位,就直接跳过,不会进入回调函数。因此,回调函数里面的console.log
语句根本不会执行,整个map
方法返回一个全是空位的新数组。
V8 引擎对map
方法的实现如下,可以看到跟规格的算法描述完全一致。
function ArrayMap(f, receiver) {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map");
// Pull out the length so that modifications to the length in the
// loop will not affect the looping and side effects are visible.
var array = TO_OBJECT(this);
var length = TO_LENGTH_OR_UINT32(array.length);
return InnerArrayMap(f, receiver, array, length);
}
function InnerArrayMap(f, receiver, array, length) {
if (!IS_CALLABLE(f)) throw MakeTypeError(kCalledNonCallable, f);
var accumulator = new InternalArray(length);
var is_array = IS_ARRAY(array);
var stepping = DEBUG_IS_STEPPING(f);
for (var i = 0; i < length; i++) {
if (HAS_INDEX(array, i, is_array)) {
var element = array[i];
// Prepare break slots for debugger step in.
if (stepping) %DebugPrepareStepInIfStepping(f);
accumulator[i] = %_Call(f, receiver, element, i, array);
}
}
var result = new GlobalArray();
%MoveArrayContents(accumulator, result);
return result;
}