Skip to content

JavaScript深入之类数组对象与arguments #14

@mqyqingfeng

Description

@mqyqingfeng
Owner

类数组对象

所谓的类数组对象:

拥有一个 length 属性和若干索引属性的对象

举个例子:

var array = ['name', 'age', 'sex'];

var arrayLike = {
    0: 'name',
    1: 'age',
    2: 'sex',
    length: 3
}

即便如此,为什么叫做类数组对象呢?

那让我们从读写、获取长度、遍历三个方面看看这两个对象。

读写

console.log(array[0]); // name
console.log(arrayLike[0]); // name

array[0] = 'new name';
arrayLike[0] = 'new name';

长度

console.log(array.length); // 3
console.log(arrayLike.length); // 3

遍历

for(var i = 0, len = array.length; i < len; i++) {
   ……
}
for(var i = 0, len = arrayLike.length; i < len; i++) {
    ……
}

是不是很像?

那类数组对象可以使用数组的方法吗?比如:

arrayLike.push('4');

然而上述代码会报错: arrayLike.push is not a function

所以终归还是类数组呐……

调用数组方法

如果类数组就是任性的想用数组的方法怎么办呢?

既然无法直接调用,我们可以用 Function.call 间接调用:

var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }

Array.prototype.join.call(arrayLike, '&'); // name&age&sex

Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"] 
// slice可以做到类数组转数组

Array.prototype.map.call(arrayLike, function(item){
    return item.toUpperCase();
}); 
// ["NAME", "AGE", "SEX"]

类数组转数组

在上面的例子中已经提到了一种类数组转数组的方法,再补充三个:

var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
// 1. slice
Array.prototype.slice.call(arrayLike); // ["name", "age", "sex"] 
// 2. splice
Array.prototype.splice.call(arrayLike, 0); // ["name", "age", "sex"] 
// 3. ES6 Array.from
Array.from(arrayLike); // ["name", "age", "sex"] 
// 4. apply
Array.prototype.concat.apply([], arrayLike)

那么为什么会讲到类数组对象呢?以及类数组有什么应用吗?

要说到类数组对象,Arguments 对象就是一个类数组对象。在客户端 JavaScript 中,一些 DOM 方法(document.getElementsByTagName()等)也返回类数组对象。

Arguments对象

接下来重点讲讲 Arguments 对象。

Arguments 对象只定义在函数体中,包括了函数的参数和其他属性。在函数体中,arguments 指代该函数的 Arguments 对象。

举个例子:

function foo(name, age, sex) {
    console.log(arguments);
}

foo('name', 'age', 'sex')

打印结果如下:

arguments

我们可以看到除了类数组的索引属性和length属性之外,还有一个callee属性,接下来我们一个一个介绍。

length属性

Arguments对象的length属性,表示实参的长度,举个例子:

function foo(b, c, d){
    console.log("实参的长度为:" + arguments.length)
}

console.log("形参的长度为:" + foo.length)

foo(1)

// 形参的长度为:3
// 实参的长度为:1

callee属性

Arguments 对象的 callee 属性,通过它可以调用函数自身。

讲个闭包经典面试题使用 callee 的解决方法:

var data = [];

for (var i = 0; i < 3; i++) {
    (data[i] = function () {
       console.log(arguments.callee.i) 
    }).i = i;
}

data[0]();
data[1]();
data[2]();

// 0
// 1
// 2

接下来讲讲 arguments 对象的几个注意要点:

arguments 和对应参数的绑定

function foo(name, age, sex, hobbit) {

    console.log(name, arguments[0]); // name name

    // 改变形参
    name = 'new name';

    console.log(name, arguments[0]); // new name new name

    // 改变arguments
    arguments[1] = 'new age';

    console.log(age, arguments[1]); // new age new age

    // 测试未传入的是否会绑定
    console.log(sex); // undefined

    sex = 'new sex';

    console.log(sex, arguments[2]); // new sex undefined

    arguments[3] = 'new hobbit';

    console.log(hobbit, arguments[3]); // undefined new hobbit

}

foo('name', 'age')

传入的参数,实参和 arguments 的值会共享,当没有传入时,实参与 arguments 值不会共享

除此之外,以上是在非严格模式下,如果是在严格模式下,实参和 arguments 是不会共享的。

传递参数

将参数从一个函数传递到另一个函数

// 使用 apply 将 foo 的参数传递给 bar
function foo() {
    bar.apply(this, arguments);
}
function bar(a, b, c) {
   console.log(a, b, c);
}

foo(1, 2, 3)

强大的ES6

使用ES6的 ... 运算符,我们可以轻松转成数组。

function func(...arguments) {
    console.log(arguments); // [1, 2, 3]
}

func(1, 2, 3);

应用

arguments的应用其实很多,在下个系列,也就是 JavaScript 专题系列中,我们会在 jQuery 的 extend 实现、函数柯里化、递归等场景看见 arguments 的身影。这篇文章就不具体展开了。

如果要总结这些场景的话,暂时能想到的包括:

  1. 参数不定长
  2. 函数柯里化
  3. 递归调用
  4. 函数重载
    ...

欢迎留言回复。

下一篇文章

JavaScript深入之创建对象的多种方式以及优缺点

深入系列

JavaScript深入系列目录地址:https://github.com/mqyqingfeng/Blog

JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。

Activity

eczn

eczn commented on May 8, 2017

@eczn

image

感觉如果类数组对象的原型指向 Array.prototype 他可以被认为是一个数组了。


image

毕竟 typeof {} 跟 typeof [] 结果是一样的。

mqyqingfeng

mqyqingfeng commented on May 8, 2017

@mqyqingfeng
OwnerAuthor

@eczn 哈哈,把原型指向Array.prototype后就可以调用Array.prototype上的方法,行为上确实是跟数组一样,然而Array.isArray和Object.prototype.toString不认呐😂

default

eczn

eczn commented on May 8, 2017

@eczn

233 很接近 但是还是有所区别

stoneyallen

stoneyallen commented on May 9, 2017

@stoneyallen

Array.prototype.concat.apply([], arguments)
这个是不是写错了,不应该是arguments

mqyqingfeng

mqyqingfeng commented on May 9, 2017

@mqyqingfeng
OwnerAuthor

@stoneyallen 十分感谢指出,确实是写错了。o( ̄▽ ̄)d

hugeorange

hugeorange commented on May 10, 2017

@hugeorange

谢谢楼主的分享!我把您的文章里的 demo 全敲了一遍,有两个地方不太明白,还请指教!
md格式的不太会用写的有点丑陋,还请见谅

callee 属性 解决闭包经典面试题的那个例子,虽然跑通了,但不明白是什么意思?
这是什么写法,不太懂??
(data[i] = function () { console.log(arguments.callee.i) }).i = i;

传递参数里面,demo 没有跑通

`

     function foo(){

              bar.apply(this,arguments); 

             // 这句的意思是把 bar的参数 传递给 foo 吗? 如果是的话,下面会打印出 3 ,

             console.log(arguments.callee.length); // 0
     }

    function bar(a,b,c){
              console.log(arguments); // []
              console.log(arguments.callee.length); // 3
    }
    foo()
    bar()

`

还有楼主应该在补充讲一下,arguments还有一个属性 caller 指向 调用当前函数的函数的引用

mqyqingfeng

mqyqingfeng commented on May 10, 2017

@mqyqingfeng
OwnerAuthor

哈哈,那我把我的回复再回复一遍哈,如果以后有相同的问题,大家也都可以看到~

mqyqingfeng

mqyqingfeng commented on May 10, 2017

@mqyqingfeng
OwnerAuthor

关于第一个问题,写个简单例子:

var fun1 = function(){}

fun1.test = 'test';

console.log(fun1.test)

函数也是一种对象,我们可以通过这种方式给函数添加一个自定义的属性。
这个解决方式就是给 data[i] 这个函数添加一个自定义属性,这个属性值就是正确的 i 值。

mqyqingfeng

mqyqingfeng commented on May 10, 2017

@mqyqingfeng
OwnerAuthor

关于第二个问题,是把foo的参数传递给bar,可以看这个跑通的例子:

function foo() { bar.apply(this, arguments); }

function bar(a, b, c) { console.log(a, b, c) }

foo(1, 2, 3)
mqyqingfeng

mqyqingfeng commented on May 10, 2017

@mqyqingfeng
OwnerAuthor

关于caller,直接截图MDN哈:

default

gnipbao

gnipbao commented on Jun 2, 2017

@gnipbao

解释的很详细!!我再补充点

类数组检测

function isArrayLike(o) {
    if (o &&                                // o is not null, undefined, etc.
        typeof o === 'object' &&            // o is an object
        isFinite(o.length) &&               // o.length is a finite number
        o.length >= 0 &&                    // o.length is non-negative
        o.length===Math.floor(o.length) &&  // o.length is an integer
        o.length < 4294967296)              // o.length < 2^32
        return true;                        // Then o is array-like
    else
        return false;                       // Otherwise it is not
}

arguments

image
如图可以看出

  1. arguments的长度只与实参的个数有关,与形参定义的个数没有直接关系。
  2. arguments 有一个Symbol(Symbol.iterator)属性这个表示该对象是可迭代的

思考

image

  • 字符串可以像类数组一样操作是因为js自动包装成String对象的原因,String对象照上面检测函数也是类数组对象。不过因为本身值不能被改变,所以给指定下标赋值不会改变。

40 remaining items

Lirong6

Lirong6 commented on Mar 22, 2020

@Lirong6

大大,请问这里是不是应该是形参和arguments不会共享?arguments代表实参的值呀

传入的参数,实参和 arguments 的值会共享,当没有传入时,实参与 arguments 值不会共享

除此之外,以上是在非严格模式下,如果是在严格模式下,实参和 arguments 是不会共享的。

`
function foo(name, age, sex, hobbit) {
'use strict';
console.log(name, arguments[0]); // name name

// 改变形参
name = 'new name';

console.log(name, arguments[0]); // new name name

// 改变arguments
arguments[1] = 'new age';

console.log(age, arguments[1]); // age new age

// 测试未传入的是否会绑定
console.log(sex); // undefined

sex = 'new sex';

console.log(sex, arguments[2]); // new sex undefined

arguments[3] = 'new hobbit';

console.log(hobbit, arguments[3]); // undefined new hobbit

}

foo('name', 'age')
`

HowToMeetYou

HowToMeetYou commented on Mar 26, 2020

@HowToMeetYou

sex = 'new se
x';

console.log(sex, arguments[2]); // new sex undefined

大大,请问这里是不是应该是形参和arguments不会共享?arguments代表实参的值呀

传入的参数,实参和 arguments 的值会共享,当没有传入时,实参与 arguments 值不会共享

除此之外,以上是在非严格模式下,如果是在严格模式下,实参和 arguments 是不会共享的。

`
function foo(name, age, sex, hobbit) {
'use strict';
console.log(name, arguments[0]); // name name

// 改变形参
name = 'new name';

console.log(name, arguments[0]); // new name name

// 改变arguments
arguments[1] = 'new age';

console.log(age, arguments[1]); // age new age

// 测试未传入的是否会绑定
console.log(sex); // undefined

sex = 'new sex';

console.log(sex, arguments[2]); // new sex undefined

arguments[3] = 'new hobbit';

console.log(hobbit, arguments[3]); // undefined new hobbit

}

foo('name', 'age')
`

当没有传入时,实参与 arguments 值不会共享
// 测试未传入的是否会绑定
console.log(sex); // undefined

sex = 'new sex';

console.log(sex, arguments[2]); // new sex undefined

OldDream

OldDream commented on Apr 1, 2020

@OldDream

(data[i] = function () {
console.log(arguments.callee.i)
})
js 的赋值语句会返回值,比如上面就会返回
function () {
console.log(arguments.callee.i)
}

anjina

anjina commented on Nov 22, 2020

@anjina

`
function test(a, b, c = 10) {
console.log(arguments); // [1, 2]
console.log(a, b, c); // 1 2 10
arguments[0] = 2; // [2, 2]
console.log(a); // 1
a = 3; // 3
console.log(arguments); // [2, 2]
b = 3;
console.log(arguments[1]); // [2, 2]
c = 20;
console.log(arguments); // [2, 2]
}

test(1, 2);
`
目前浏览器和node环境测试表明 实参和arguments之间没有什么关联了。有人解释下吗

anjina

anjina commented on Nov 22, 2020

@anjina

`
function test(a, b, c = 10) {
console.log(arguments); // [1, 2]
console.log(a, b, c); // 1 2 10
arguments[0] = 2; // [2, 2]
console.log(a); // 1
a = 3; // 3
console.log(arguments); // [2, 2]
b = 3;
console.log(arguments[1]); // [2, 2]
c = 20;
console.log(arguments); // [2, 2]
}

test(1, 2);
`
目前浏览器和node环境测试表明 实参和arguments之间没有什么关联了。有人解释下吗

MDN找到答案了,是因为我使用了参数默认值。

在严格模式下,剩余参数、默认参数和解构赋值参数的存在不会改变 arguments对象的行为,但是在非严格模式下就有所不同了。

当非严格模式中的函数没有包含剩余参数、默认参数和解构赋值,那么arguments对象中的值会跟踪参数的值(反之亦然)

lsc9

lsc9 commented on Dec 13, 2021

@lsc9

只有非严格模式下,且形参中没有rest参数、默认值和结构赋值时 arguments 才会与参数绑定。

czy5997

czy5997 commented on Apr 4, 2022

@czy5997

function foo() { bar.apply(this,arguments) }
这个里面的this 是谁的this呀,为啥这样写

xingqq

xingqq commented on Jan 17, 2025

@xingqq

arguments 和对应参数的绑定-部分

不应该是形参和arguments的值是共享的吗?
实参是函数调用时传递的值,给形参进行初始赋值,函数中name值的改变应该是形参的值吧

loveheavenlina

loveheavenlina commented on Jan 17, 2025

@loveheavenlina
rainbowyy

rainbowyy commented on Jan 17, 2025

@rainbowyy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @stoneyallen@mqyqingfeng@HuangQiii@EtheriousNatsu@eczn

        Issue actions

          JavaScript深入之类数组对象与arguments · Issue #14 · mqyqingfeng/Blog