前言
学习的underscore.js 源码版本为1.13.1
rest parameter (剩余参数)
ES6 剩余参数语法允许我们将一个不定数量的参数表示为一个数组.
function fn(a, b, ...args) {
console.log(args); // [3, 4, 5]
}
fn(1, 2, 3, 4, 5);
function sum(...theArgs) {
return theArgs.reduce((previous, current) => {
return previous + current;
});
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4)); // 10
剩余参数和 arguments 对象的区别
- 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参。
- arguments对象不是一个真正的数组(除了length属性和索引元素之外没有任何
Array
属性),而剩余参数是真正的 Array 实例,也就是说你能够在它上面直接使用所有的数组方法,比如sort
,map
,forEach
或pop
。 - arguments对象还有一些附加的属性 (如callee属性)
类数组如何转换成数组?
类数组: 是指拥有一个length属性和若干属性的对象。
underscore中isArrayLike
函数的实现:
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var isArrayLike = function(obj) {
var sizeProperty = obj == null ? void 0 : obj.length
return typeof sizeProperty == 'number' && sizeProperty >= 0 && sizeProperty <= MAX_ARRAY_INDEX;
}
即认为有 length 属性且length 属性值是不大于 Number.MAX_SAFE_INTEGER 的自然数的对象为类数组
常见的类数组对象包括:
- function 中的 arguments 对象
- 使用document.getElementsByTagName/ClassName()等方法获得的HTMLCollection;
- 使用querySelector获得的NodeList
- ...
如何转换?
1. ES6展开运算符(只能作用于 iterable 对象
)
一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator
属性,或者说,一个数据结构只要具有Symbol.iterator
属性,就可以认为是“可遍历的”(iterable)。Symbol.iterator
属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。
原生具备 Iterator
接口的数据结构如下。
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
所以对于没有 Iterator 接口的数据使用展开运算符是会报错的。
关于Iterator可以学习阮一峰 es6.ruanyifeng.com/#docs/itera…
function sum(a, b){
var args = [...arguments];
args.push(3);
console.log(args.reduce((prev, next) => prev + next ,0));
}
sum(1, 2); // 6
[...undefined]; // Uncaught TypeError: undefined is not iterable
[...{length: 3}]; // Uncaught TypeError: {(intermediate value)} is not iterable
2. 利用Array.from()
function sum(a, b){
var args = Array.from(arguments);
args.push(3);
console.log(args.reduce((prev, next) => prev + next ,0));
}
sum(1, 2);
3. 在 ES5 中可以借用 Array API 通过 call/apply 改变 this 或者 arguments 来完成转化
如下面的类数组:
var arrayLike = {
0: 3,
1: 4,
2: 5,
length: 3
}
Array.apply(null, arrayLike); // 借用 arguments
Array.prototype.concat.apply([], arrayLike) // 借用 arguments
Array.prototype.slice.call(arrayLike) // 借用 this
Array.prototype.map.call(arrayLike, x => x) // 借用 this
考虑稀疏数组 (sparse array)
使用 Array(n)
将会创建一个稀疏数组,为了节省空间,稀疏数组内含非真实元素,在控制台上将以 empty
显示,如下所示
先来看下使用:
console.log([,,,]); // [empty × 3]
console.log(Array(3)); // [empty × 3]
当类数组为 { length: 3 }
时,一切将类数组作为 this
的方法将都返回稀疏数组,而将类数组作为 arguments
的方法将都返回密集数组
即:
var arrayLike1 = {
length: 3
}
Array.apply(null, arrayLike1); // [undefined, undefined, undefined]
Array.prototype.concat.apply([], arrayLike1) // [undefined, undefined, undefined]
Array.prototype.slice.call(arrayLike1) // [empty × 3]
Array.prototype.map.call(arrayLike, x => x) // [empty × 3]
restArguments
如果不使用 ... 拓展操作符,仅用 ES5 的内容,该怎么实现呢?
我们可以写一个 restArguments 函数,传入一个函数,使用函数的最后一个参数储存剩下的函数参数,使用效果如下:
var fn = restArguments(function (a, b, args) {
console.log(args); // [3,4,5]
});
fn(1, 2, 3, 4, 5);
在实现第一版代码前,先来看看Function.length
Function.length --- 函数的形参个数
length 是函数对象的一个属性值,指该函数有多少个必须要传入的参数,即形参的个数。
形参的数量不包括剩余参数个数,仅包括第一个具有默认值之前的参数个数
。
与之对比的是, arguments.length 是函数被调用时实际传参的个数
。
// Function 构造器的length 属性值为 1
console.log(Function.length); // 1
// 且该属性 Writable: false, Enumerable: false, Configurable: true
console.log(Object.getOwnPropertyDescriptor(Function, 'length'));
// {value: 1, writable: false, enumerable: false, configurable: true}
// Function.prototype对象的 length 属性值为 0
console.log(Function.prototype.length); // 0
console.log((function(){}).length); // 0
console.log((function(a, b){}).length); // 2
// 形参的数量不包括剩余参数个数
console.log((function(...args) {}).length); // 0
// 仅包括第一个具有默认值之前的参数个数
console.log((function(a, b = 1, c) {}).length); // 1
第一版实现:
function restArguments(func) {
return function() {
// startIndex 表示使用哪个位置的参数用于储存剩余的参数
// 默认使用传人的函数的最后一个参数存储剩余的参数
var startIndex = func.length - 1;
var length = arguments.length - startIndex,
rest = Array(length),
index = 0;
// 使用一个数组储存剩余的参数
// 以上面例子为例,rest结果为[3, 4, 5]
for (; index < length; index++) {
rest[index] = arguments[index + startIndex];
}
var args = Array(startIndex + 1);
// args此时为[1, 2, undefined]
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index];
}
// args此时为[1, 2, [3, 4, 5]]
args[startIndex] = rest;
return func.apply(this, args);
};
}
优化最终版
优化点:
- 增加参数startIndex,表明从第几个参数存储剩余的参数
- 考虑剩余参数的个数
- 性能考虑,参数少的时候尽量使用call
function restArguments(func, startIndex) {
startIndex = startIndex == null ? func.length - 1 : +startIndex;
return function() {
var length = Math.max(arguments.length - startIndex, 0),
rest = Array(length),
index = 0;
for (; index < length; index++) {
rest[index] = arguments[index + startIndex];
}
switch (startIndex) {
case 0: return func.call(this, rest);
case 1: return func.call(this, arguments[0], rest);
case 2: return func.call(this, arguments[0], arguments[1], rest);
}
var args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index];
}
args[startIndex] = rest;
return func.apply(this, args);
};
}
测试:
var fn = restArguments(function(a, b, c, d) {console.log(a, b, c, d);}, 5);
fn(1, 2, 3, 4); // 1 2 3 4
var fn1 = restArguments(function(a, args) {console.log(a, args);});
fn1(); // undefined []
var fn2 = restArguments(function(a, b, c, d) {console.log(a, b, c, d);}, 2);
fn2(1, 2, 3, 4); // 1 2 [3, 4] undefined
参考资料: github.com/mqyqingfeng…
developer.mozilla.org/zh-CN/docs/…
juejin.cn/post/684490…