javascript中的arguments

512 阅读1分钟

arguments是一个对应于传递给函数参数的类数组(array-like)对象。它长得像数组,但是并不是数组,本质上是个对象

function foo(num1, num2, num3) {
  console.log(arguments);
}

foo(10, 20, 30, 40, 50)

我们在浏览器中运行看到的结果。我们可以看到这个对象中有Symbol(Symbol.iterator),我们后续进行介绍。 image.png 它不是一个数组类型,而是一个对象类型,但是它却拥有数组的一些特性,例如获取参数的长度,根据索引值获取某一个参数。但是它没有数组的方法,例如forEach、map等。

function foo(num1, num2, num3) {
  console.log(arguments);
  console.log(arguments.length);
  console.log(arguments[1]);
  console.log(arguments[2]);
}

image.png 另外,我们可以通过callee获取当前arguments所在的函数。

function foo(num1, num2, num3) {
  console.log(arguments);
  console.log(arguments.callee);
}

foo(10, 20, 30, 40, 50)

image.png 注意:我们不能在函数内部再去调用arguments.callee,会进入死循环。

arguments转成数组

我们说arguments是个伪数组,那么怎么把它变成真的数组呢?

  • Array.prototype.slice.call(arguments)
  • Array.from(arguments)
  • 展开运算符[...arguments]

方式一: 自己遍历arguments中的所有元素

function foo(num1, num2) {
  var newArr = []
  for(let i = 0; i <= arguments.length; i++) {
    newArr.push(arguments[i] * 10)
  }
  console.log(newArr);
}

foo(10, 20, 30, 40)

方式二: Array.prototype.slice.call将arguments转成array

function foo(num1, num2) {
  var newArr2 = Array.prototype.slice.call(arguments)
  console.log(newArr2); // [ 10, 20, 30, 40 ]
}

foo(10, 20, 30, 40)

我们知道数组有slice方法,这是因为数组的原型Array.prototype上有slice方法,Array.prototype.slice()。 例如我们现在有一个数组,我们可以直接调用slice方法,这是因为Array的原型上有slice方法。

var names = [1, 2, 3]
names.slice()

如果我们没有像names这样的具体的数组,还是想调用slice方法,那么我们就直接这样调用Array.prototype.slice(),那为什么还需要使用call或者apply呢? 如果直接进行调用Array.prototpye.slice(),那么slice方法里面的this指向的是调用者—调用这个slice方法的数组原型。

Array.prototype.slice = function() {
console.log(this);
}

Array.prototype.slice()

但是我们拿到原型是没有意义的,我们想要拿到的是可遍历的对象。如果我们进行call调用,那么现在slice方法中的this进行了显示绑定,指向的是['aaa', 'bbb', 'ccc']这个数组。

Array.prototype.slice.call(['aaa', 'bbb', 'ccc'])

那么有什么用呢?其实Array.prototype.slice方法里面是这样实现的。

Array.prototype.slice = function () {
  console.log(this);
  var arr = this // 拿到['aaa', 'bbb', 'ccc']这个数组
  var newArr = []
  for (var i = 0; i < arr.length; i++) {
    newArr.push(arr[i])
  }
  return newArr
}

// 接收到新的数组
var newArray = Array.prototype.slice.call(['aaa', 'bbb', 'ccc'])

我们知道slice是可以传参数的。

var names = [1, 2, 3]
names.slice(1, 2)

Array.prototype.slice方法方法也会接收参数。

Array.prototype.slice = function (start, end) {
  console.log(this);
  var arr = this
  // 不传参数
  start = start || 0
  end = end || arr.length
  var newArr = []
  for (var i = start; i < end; i++) {
    newArr.push(arr[i])
  }
  return newArr
}

我们来试验下。

Array.prototype.mySlice = function (start, end) {
 // console.log(this);
  var arr = this
  var newArr = []
  for (var i = start; i < end; i++) {
    newArr.push(arr[i])
  }
  return newArr
}

var newArray = Array.prototype.mySlice.call(['aaa', 'bbb', 'ccc'], 1, 3)
console.log(newArray);

image.png 现在我们传入的是数组,那么我们传入不是数组的arguments会怎么样呢?不会影响。为什么?因为slice内部是进行了遍历,而arguments是可以进行遍历的。

无论是数组还是arguments,Array.prototype.slice.call()都可以传入,因为它的内部无非是进行遍历,然后返回一个新的数组中,不传索引,默认返回整个长度。

所以我们也可以直接通过[].slice.call(arguments)获取新的数组。[].slice()直接调用slice方法内部的this指向的是[],因为进行了隐式绑定。而通过[].slice.call(arguments)调用,this指向了arguments,对arguments进行遍历返回新的数组。

function foo(num1, num2) {
/*   var newArr1 = Array.prototype.mySlice.call(arguments)
  console.log(newArr1); */
  var newArr2 = [].slice.call(arguments)
  console.log(newArr2);
}

方式三:Array.from(arguments)

在ES6中,还提供了Array.from方法,里面还可以传入可迭代的东西或者ArrayLike伪数组。它的内部也进行了遍历并返回一个数组。

function foo(num1, num2) {
  var newArr1 = Array.prototype.slice.call(arguments)
  console.log(newArr1);
  var newArr2 = [].slice.call(arguments)
  console.log(newArr2);
  var newArr3 = Array.from(arguments)
  console.log(newArr3);
}

方式四:展开运算符

展开运算符相当于遍历整个arguments里面的所有元素,然后挨个放到新的数组中。

function foo(num1, num2) {
  var newArr1 = Array.prototype.slice.call(arguments)
  console.log(newArr1);
  var newArr2 = [].slice.call(arguments)
  console.log(newArr2);
  var newArr3 = Array.from(arguments)
  console.log(newArr3);
  var newArr4 = [...arguments]
  console.log(newArr4);
}

箭头函数中没有arguments

箭头函数没有arguments,它会去上层作用域查找arguments,如果没有,一层层往上查找,直到全局。在浏览器中是没有arguments的,而在node中是有arguments的。

案例一

var foo = () => {
  console.log(arguments);
}

foo()

箭头函数中没有arguments,它会找到全局。

在浏览器中运行。 image.png

在node中运行。

image.png 案例二

function foo () {
  var bar = () => {
    console.log(arguments); // [Arguments] { '0': 123 }
  }
  return bar
}

var fn = foo(123)
fn()

箭头函数找到上层作用域foo中的arguments。

案例三

如果想要传递额外参数并且获取额外参数,可以通过剩余参数获取。

var foo = (num1, num2, ...args) => {
  console.log(args); // [ 30, 40, 50 ]
}

foo(10, 20, 30, 40, 50)