关于Array.from的实现,MDN是这么说的, 这句话的意思是:
- 这是Array的静态方法
- 用于浅拷贝类数组、数组或者可迭代对象
- 返回一个新数组实例。
该方法接收三个参数,其中,后两个是可选参数,分别是
- 可迭代对象(像Map或者Set)或者类数组(拥有length属性的对象)
- map函数,类似于Array.prototype.map
- 绑定的this对象
关于Array.from的用法,可以做个测试
数组对象
类数组对象
可迭代对象
带表达式函数的
接下来看一下Array.from的源码实现:
if (!Array.from) {
Array.from = (function () {
var symbolIterator;
try {
symbolIterator = Symbol.iterator
? Symbol.iterator
: 'Symbol(Symbol.iterator)';
} catch (e) {
symbolIterator = 'Symbol(Symbol.iterator)';
}
var toStr = Object.prototype.toString;
var isCallable = function (fn) {
return (
typeof fn === 'function' ||
toStr.call(fn) === '[object Function]'
);
};
var toInteger = function (value) {
var number = Number(value);
if (isNaN(number)) return 0;
if (number === 0 || !isFinite(number)) return number;
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
var maxSafeInteger = Math.pow(2, 53) - 1;
var toLength = function (value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
var setGetItemHandler = function setGetItemHandler(isIterator, items) {
var iterator = isIterator && items[symbolIterator]();
return function getItem(k) {
return isIterator ? iterator.next() : items[k];
};
};
var getArray = function getArray(
T,
A,
len,
getItem,
isIterator,
mapFn
) {
// 16. Let k be 0.
var k = 0;
// 17. Repeat, while k < len… or while iterator is done (also steps a - h)
while (k < len || isIterator) {
var item = getItem(k);
var kValue = isIterator ? item.value : item;
if (isIterator && item.done) {
return A;
} else {
if (mapFn) {
A[k] =
typeof T === 'undefined'
? mapFn(kValue, k)
: mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
}
k += 1;
}
if (isIterator) {
throw new TypeError(
'Array.from: provided arrayLike or iterator has length more then 2 ** 52 - 1'
);
} else {
A.length = len;
}
return A;
};
// The length property of the from method is 1.
return function from(arrayLikeOrIterator /*, mapFn, thisArg */) {
// 1. Let C be the this value.
var C = this;
// 2. Let items be ToObject(arrayLikeOrIterator).
var items = Object(arrayLikeOrIterator);
var isIterator = isCallable(items[symbolIterator]);
// 3. ReturnIfAbrupt(items).
if (arrayLikeOrIterator == null && !isIterator) {
throw new TypeError(
'Array.from requires an array-like object or iterator - not null or undefined'
);
}
// 4. If mapfn is undefined, then let mapping be false.
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
var T;
if (typeof mapFn !== 'undefined') {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError(
'Array.from: when provided, the second argument must be a function'
);
}
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 2) {
T = arguments[2];
}
}
// 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
var len = toLength(items.length);
// 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method
// of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
return getArray(
T,
A,
len,
setGetItemHandler(isIterator, items),
isIterator,
mapFn
);
};
})();
}
关于这个源码我们拆开来看
- Array.from最后执行from,from返回getArray。先看from方法。
- 判断第一个参数是不是数组、类数组或者可迭代的对象。首先该参数不能为null或者undefined,其次该参数是可以迭代的。判断是否可迭代,就看能否通过for...of循环,for..of循环取决于Symbol.iterator属性,因此会有以下这一段
var isIterator = isCallable(items[symbolIterator]);
Symbol.iterator属性一般存在于可迭代对象的原型对象中,且是一个函数,这个函数是怎样的呢?文章最后面会有讲解,这里不是重点。
- 接下来获取map方法和this对象,因为可选,故可能不存在,因此做个容错处理
- 接下来获取对象的length,并新建一个数组
- 最后调用getArray方法
- 调用getArray方法。
- 获取每个迭代元素的值,如果是可以迭代的,就通过value属性获取,否则item就是元素的值。
- 可迭代的done属性如果为true,就停止并返回新数组;否则给新数组赋值。
- 给新数组赋值,要判断map函数是否存在,注意给map函数传递的参数只有两个,一个是每个元素值,一个是索引
- 最后返回新数组
这就是Array.from的源码解析,接下来说一下可迭代。
可迭代取决于Symbol.iterator方法。那么这个方法是怎样的呢?vue源码里面有这么一段:
if (hasSymbol && val[Symbol.iterator]) {
ret = [];
var iterator = val[Symbol.iterator]();
var result = iterator.next();
while (!result.done) {
ret.push(render(result.value, ret.length));
result = iterator.next();
}
}
那么我们可以推测一下,这个方法到底是什么样的。
js中纯对象是没有可迭代特性的,只能使用for..in但是不能使用for..of,我们可以尝试给对象赋值一个可迭代的属性并测试一下:
Object.prototype[Symbol.iterator] = function () {
const obj = this;
const keys = Object.keys(obj);
let index = 0;
return {
next: function () {
return {
done: index === keys.length,
value: obj[keys[index++]]
}
}
}
}
const data = { name: '名称', value: '值', title: '标题' };
for (let item of data) {
console.log(item);
}
结果如下:
所以这就是可迭代的逻辑了。