当前位置: 首页 > 工具软件 > Args.js > 使用案例 >

JS: ...args,arguments,类数组转为真正的数组

锺霍英
2023-12-01

1、rest参数(...变量名)

ES6 引入 rest 参数(...变量名),用于获取函数的多余参数。rest 参数对应的是一个数组,成员依次是函数的多余参数。

function f (...args) {
  console.log(args instanceof Array);
  console.log(args);
}
f(1,2,3);

// true
// [ 1, 2, 3 ]

它用于接收剩余参数。因此,它只能是函数最后一个参数,且一个函数中最多只能出现一个。

function f(...args, a){}
// SyntaxError: Rest parameter must be last formal parameter

function f(...args, ...args1){}
// SyntaxError: Rest parameter must be last formal parameter

已知,函数体内部有arguments对象,可以在函数体内部读取所有参数。为什么还需要rest参数?

因为arguments对象不是数组,而是一个类似数组的对象。

rest 参数是一个真正的数组,数组特有的方法都可以使用。

2、类数组

如果一个对象的所有键名都是正整数或零,并且有length属性,那么这个对象就很像数组,语法上称为“类似数组的对象”。类数组并不是数组,它们不具备数组特有的方法。

“类数组的对象”的根本特征,就是具有length属性。只要有length属性,就可以认为这个对象类似于数组。但这种length属性不是动态值,不会随着成员的变化而变化。

let obj = {
  length: 0
};
obj[3] = 'd';
obj.length // 0

典型的类数组对象:

  • 函数的 arguments 对象
  • 字符串
  • DOM NodeList 对象

类数组对象想要使用数组方法,一般是两种方式

  • 类数组对象变成真正的数组
  • 通过call()把数组的方法放到对象上面

2.1 类数组对象变成真正的数组

方法一:for循环

// arguments对象
function f () {
  let args = [];
  for (var i = 0; i < arguments.length; i++) {
    args.push(arguments[i]);
  }
  return args
}

f(1,2,3); // [1, 2, 3]

方法二:数组的slice方法

以下两种方式都可行

  • Array.prototype.slice.call(arrayLike)
  • [].slice.call(arrayLike)
// arrayLike 对象
let arrayLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};
Array.prototype.slice.call(arrayLike) // [ 'a', 'b', 'c' ]

[].slice.call(arrayLike)              // [ 'a', 'b', 'c' ]

// String
[].slice.call('abc')                  // [ 'a', 'b', 'c' ]

 方法三:Array.from(arrayLike)

// arrayLike 对象
let arrayLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};
Array.from(arrayLike) // ['a', 'b', 'c']

// String
Array.from('abc')     // ['a', 'b', 'c']

 方法四:扩展运算符...(对象必须具备 Iterator 接口,即具有Symbol.iterator属性)

  • 函数的 arguments 对象、String,DOM NodeList 对象都可使用此种方式
  • 自定义的arrayLike不具备 Iterator 接口,报错
// arguments
function f () {
  console.log(Symbol.iterator in arguments)
  return [...arguments]
}
f(1, 2, 3)
// true
// [1, 2, 3]

// String
Symbol.iterator in String.prototype // true
[...'abc']                          // ['a', 'b', 'c']

// DOM NodeList 对象
Symbol.iterator in NodeList.prototype  // true
[...document.querySelectorAll('div')]  // 数组

// arrayLike 对象不支持
let arrayLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};
Symbol.iterator in arrayLike // false
[...arrayLike]               // TypeError: arrayLike is not iterable

2.2 通过call()、apply()把数组的方法放到对象上面

  • Array.prototype.forEach.call(arrayLike, callback)
  • Array.prototype.forEach.apply(arrayLike, [callback])
// call()
// forEach 方法
function logArgs() {
  Array.prototype.forEach.call(arguments, function (elem, i) {
    console.log(i + '. ' + elem);
  });
}
logArgs('a', 'b', 'c')
// 0. a
// 1. b
// 2. c

// map方法
Array.prototype.map.call('abc', function (item) {
  console.log(item);
});
// a
// b
// c    
// apply()
// forEach 方法
function logArgs() {
  Array.prototype.forEach.apply(arguments, [function (elem, i) {
    console.log(i + '. ' + elem);
  }]);
}
logArgs('a', 'b', 'c')
// 0. a
// 1. b
// 2. c

// map方法
Array.prototype.map.apply('abc', [function (item) {
  console.log(item);
}]);
// a
// b
// c    

通过call/apply借用数组方法的方式,比直接使用数组原生的forEach要慢。

所以更优解是把类数组对象变成真正的数组。

思考:

var obj = {
    2: 'a',
    3: 'd',
    length: 2,
    push: Array.prototype.push
};
obj.push('b');
obj.push('c');
obj // 结果是什么?

The end.

参考链接:

函数 - JavaScript 教程 - 网道

数组 - JavaScript 教程 - 网道

 类似资料: