Array.from的Polyfill都做了什么
今天在看MDN中关于Array.from方法Polyfill实现,发现其中还是有很多值得学习的地方。
if (!Array.from) {
Array.from = (function () {
/*
* 保存toString方法
* 减少原型链查找消耗,另外保证代码的体面
*/
var toStr = Object.prototype.toString;
// 判断是否为一个方法
var isCallable = function (fn) {
/*
* 为什么要用 toStr.call 来判断是否为 [object Function]
* 无论 fn 是通过赋值还是直接定义,其自身的 toString 方法并不能够表示其类型
* 因为 toString 方法是将自身的值通过字符串展示出来
*
* 赋值
* var fn = function() {}
* fn.toString() === 'function() {}'
*
* 定义
* function fn() {}
* fn.toString() === 'function fn() {}'
*/
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
};
// 将值整数化
var toInteger = function (value) {
var number = Number(value);
// 非数字返回 0
if (isNaN(number)) { return 0; }
/*
* isFinite 方法用来判断数字是否是正(负无穷
* IEEE 754 标准规定: 只有大于等于1.7976931348623158 x 10^308 的数才会被 round 到 Infinity
*
* 1.7976931348623158e+308 * 1.000000000000001 的结果为 Infinity
* Javascript中的 Number.MAX_VALUE 距离这个数还差很远
* Number.MIN_SAFE_INTEGER 更是小于 Number.MAX_VALUE
*
* 如果数字是 0 或者不为正(负)无穷,则返回自己
*/
if (number === 0 || !isFinite(number)) { return number; }
/*
* Math.floor() 向下取整 Math.floor(4.6) => 4
* Math.ceil() 向上取整 Math.ceil(4.1) => 5
* Math.round() 四舍五入 Math.round(4.1) => 4, Math.round(4.5) => 5
*
* 这里主要处理负数取整的问题,先保留符号,再乘以绝对值之后向下取整。
* 可以认为是,当前这个处理方法遇到小数时,都是向 0 取整。
*/
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
// 当然作为Polyfill,Number.MIN_SAFE_INTEGER 这个静态常量也是不存在的
// 手动计算一下。
var maxSafeInteger = Math.pow(2, 53) - 1;
// 拿到一个大于等于 0 的值
var toLength = function (value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
// 数据类型Set的实现
var toItems = function (value) {
// support set
if (value.size > 0 && value.values) {
let values = value.values()
var it = values.next()
var o = []
while (!it.done) {
o.push(it.value)
it = values.next()
}
return o
}
return Object(value);
};
// The length property of the from method is 1.
return function from(arrayLike/*, mapFn, thisArg */) {
// 1. Let C be the this value.
var C = this;
// 2. Let items be ToObject(arrayLike).
var items = toItems(arrayLike);
// 3. ReturnIfAbrupt(items).
if (arrayLike == null) {
throw new TypeError("Array.from requires an array-like object - not null or undefined");
}
// 4. If mapfn is undefined, then let mapping be false.
/*
* void expression 最后都会获得一个undefined的返回
*/
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);
// 16. Let k be 0.
var k = 0;
// 17. Repeat, while k < len… (also steps a - h)
var kValue;
while (k < len) {
kValue = items[k];
// 实现mapFn
if (mapFn) {
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
k += 1;
}
// 18. Let putStatus be Put(A, "length", len, true).
A.length = len;
// 20. Return A.
return A;
};
}());
}
Detail
1. 关于Object.prototype.toStirng()
Object作为Javascript万物之源,几乎所有的对象都继承自Object,如果在继承时原型上的toString方法不被覆盖掉,它会默认的打印出 [object type],type为当前数据的类型。
但是一些继承与Object的基础类型会将toString方法覆盖,且不继承。
比如:
-
String
String.prototype.toString()的返回值和String.prototype.valueOf()的一样,都是返回对象的字符串形式。
-
Number
Numer.prototype.toString()接受一个参数,并且按照参数返回指定基数的字符串表示。
-
Array
Array.prototype.toString()返回以逗号分隔,连接好的数组。
2. 取整
数字的向0取整是个基本的策略,就是想绝对值较小的那个方向取整。
但是值得思考的是,为什么要使用向0取整这个策略。难道是因为正数向上,负数向下取整可能会超过精度的最大值?
Reference
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/string/toString
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/number/toString
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/array/toString
https://www.zhihu.com/question/24423421