如何理解 JS 的类数组?

  其实 JS 中一直存在一种类数组的对象,它们不能直接调用数组的方法,但是又和数组比较类似,在某些特定的编程场景中会出现,这会让很多 JS 的初学者比较困惑。

  先来看看在 JavaScript中有哪些情况下的对象是类数组呢?主要有以下几种:

  1. 函数里面的参数对象 arguments
  2. getElementsByTagName/ClassName/Name 获得的 HTMLCollection
  3. querySelector获得的 NodeList

  上述这些基本就是在 JavaScript编程过程中经常会遇到的。

在开始前请先思考几个问题:

  1. 类数组是否能使用数组的方法呢?
  2. 类数组有哪些方式可以转换成数组?

类数组基本介绍

arguments

  先来重点讲讲 arguments对象,我们在日常开发中经常会遇到各种类数组对象,最常见的便是在函数中使用的 arguments,它的对象只定义在函数体中,包括了函数的参数和其他属性。我们通过一段代码来看下 arguments的使用方法,如下所示。

function foo(name, age, sex) {

    console.log(arguments);

    console.log(typeof arguments);

    console.log(Object.prototype.toString.call(arguments));

}

foo('mark', '18', 'male');

这段代码比较容易,就是直接将这个函数的 arguments在函数内部打印出来,那么我们看下这个 arguments打印出来的结果,请看控制台的这张截图。

在这里插入图片描述

从结果中可以看到,typeof这个 arguments返回的是 object,通过 Object.prototype.toString.call 返回的结果是 '[object arguments]',可以看出来返回的不是 '[object array]',说明 arguments和数组还是有区别的。

  length 属性很好理解,它就是函数参数的长度,我们从打印出的代码也可以看得出来。另外可以看到 arguments不仅仅有一个 length属性,还有一个 callee属性,我们接下来看看这个 callee是干什么的,代码如下所示。

function foo(name, age, sex) {

    console.log(arguments.callee);

}

foo('jack', '18', 'male');

请看这段代码的执行结果。

在这里插入图片描述

  从控制台可以看到,输出的就是函数自身,如果在函数内部直接执行调用 callee 的话,那它就会不停地执行当前函数,直到执行到内存溢出

HTMLCollection

  HTMLCollection简单来说是 HTML DOM 对象的一个接口,这个接口包含了获取到的 DOM元素集合,返回的类型是类数组对象,如果用 typeof来判断的话,它返回的是'object'。它是及时更新的,当文档中的 DOM变化时,它也会随之变化。

  描述起来比较抽象,还是通过一段代码来看下 HTMLCollection 最后返回的是什么,我们先随便找一个页面中form表单的页面,在控制台中执行下述代码。

var elem1, elem2;

// document.forms 是一个 HTMLCollection

elem1 = document.forms[0];

elem2 = document.forms.item(0);

console.log(elem1);

console.log(elem2);

console.log(typeof elem1);

console.log(Object.prototype.toString.call(elem1));

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Nl1azVq-1635412164265)(C:\Users\JYcx\AppData\Roaming\Typora\typora-user-images\image-20211028164827915.png)]

可以看到,这里打印出来了页面第一个 form表单元素,同时也打印出来了判断类型的结果,说明打印的判断的类型和 arguments返回的也比较类似,typeof返回的都是 ‘object’,和上面的类似。

另外需要注意的一点就是 HTML DOM 中的 HTMLCollection是即时更新的,当其所包含的文档结构发生改变时,它会自动更新。

NodeList

  NodeList 对象是节点的集合,通常是由 querySlector返回的。NodeList不是一个数组,也是一种类数组。虽然 NodeList 不是一个数组,但是可以使用 for...of 来迭代。在一些情况下,NodeList 是一个实时集合,也就是说,如果文档中的节点树发生变化,NodeList也会随之变化。还是利用代码来理解一下 Nodelist这种类数组。

var list = document.querySelectorAll('input[type=radio]');

for (var checkbox of list) {

  checkbox.checked = true;

}

console.log(list);

console.log(typeof list);

console.log(Object.prototype.toString.call(list));

从上面的代码执行的结果中可以发现,我们是通过有 ·radio的页面执行的代码,在结果可中输出了一个 NodeList类数组,里面有一个 radio元素,并且我们判断了它的类型,和上面的 argumentsHTMLCollection其实是类似的,执行结果如下图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MCT59297-1635412164270)(C:\Users\JYcx\AppData\Roaming\Typora\typora-user-images\image-20211028165150859.png)]

类数组应用场景

遍历参数操作

  在函数内部可以直接获取 arguments这个类数组的值,那么也可以对于参数进行一些操作,比如下面这段代码,可以将函数的参数默认进行求和操作。

function add() {

    var sum =0,

        len = arguments.length;

    for(var i = 0; i < len; i++){

        sum += arguments[i];

    }

    return sum;

}

add()                           // 0

add(1)                          // 1

add(12)                       // 3

add(1,2,3,4);                   // 10

  结合上面这段代码,在函数内部可以将参数直接进行累加操作,以达到预期的效果,参数多少也可以不受限制,根据长度直接计算,返回出最后函数的参数的累加结果,其他的操作也都可以仿照这样的方式来做。

定义链接字符串函数

  我们可以通过 arguments这个例子定义一个函数来连接字符串。这个函数唯一正式声明了的参数是一个字符串,该参数指定一个字符作为衔接点来连接字符串。该函数定义如下。

function myConcat(separa) {

  var args = Array.prototype.slice.call(arguments, 1);

  return args.join(separa);

}

myConcat(", ", "red", "orange", "blue");

// "red, orange, blue"

myConcat("; ", "elephant", "lion", "snake");

// "elephant; lion; snake"

myConcat(". ", "one", "two", "three", "four", "five");

// "one. two. three. four. five"

  这段代码说明了,可以传递任意数量的参数到该函数,并使用每个参数作为列表中的项创建列表进行拼接。从这个例子中也可以看出,可以在日常编码中采用这样的代码抽象方式,把需要解决的这一类问题,都抽象成通用的方法,来提升代码的可复用性。

传递参数使用

  借助 arguments将参数从一个函数传递到另一个函数,请看下面这个例子。

// 使用 apply 将 foo 的参数传递给 bar

function foo() {

    bar.apply(this, arguments);

}

function bar(a, b, c) {

   console.log(a, b, c);

}

foo(1, 2, 3)   //1 2 3

  上述代码中,通过在 foo函数内部调用 apply方法,用 foo函数的参数传递给 bar函数,这样就实现了借用参数的妙用。你可以结合这个例子再思考一下,对于 foo这样的函数可以灵活传入参数数量,通过这样的代码编写方式是不是也可以实现一些功能的拓展场景呢?

如何将类数组转换成数组

类数组借用数组方法转数组

  applycall 方法之前我们有详细讲过,类数组因为不是真正的数组,所以没有数组类型上自带的那些方法,就需要利用下面这几个方法去借用数组的方法。比如借用数组的 push 方法,请看下面的一段代码。

var arrayLike = { 

  0: 'java',

  1: 'script',

  length: 2

} 

Array.prototype.push.call(arrayLike, 'mark', 'eric'); 

console.log(typeof arrayLike); // 'object'

console.log(arrayLike);

// {0: "java", 1: "script", 2: "mark", 3: "eric", length: 4}

  从中可以看到,arrayLike其实是一个对象,模拟数组的一个类数组,从数据类型上说它是一个对象,新增了一个 length的属性。从代码中还可以看出,用 typeof来判断输出的是 'object',它自身是不会有数组的 push方法的,这里我们就用 call的方法来借用 Array原型链上的 push 方法,可以实现一个类数组的 push方法,给 arrayLike添加新的元素。

  从控制台的结果可以看出,数组的 push方法满足了我们想要实现添加元素的诉求。再来看下 arguments如何转换成数组,请看下面这段代码。

function sum(a, b) {

  let args = Array.prototype.slice.call(arguments);

 // let args = [].slice.call(arguments); // 这样写也是一样效果

  console.log(args.reduce((sum, cur) => sum + cur));

}

sum(1, 2);  // 3

function sum(a, b) {

  let args = Array.prototype.concat.apply([], arguments);

  console.log(args.reduce((sum, cur) => sum + cur));

}

sum(1, 2);  // 3

  这段代码中可以看到,还是借用 Array原型链上的各种方法,来实现 sum函数的参数相加的效果。一开始都是将 arguments通过借用数组的方法转换为真正的数组,最后都又通过数组的 reduce方法实现了参数转化的真数组 args的相加,最后返回预期的结果。

ES6 的方法转数组

  对于类数组转换成数组的方式,我们还可以采用 ES6新增的 Array.from方法以及展开运算符的方法。那么还是围绕上面这个 sum函数来进行改变,看下用 Array.from 和展开运算符是怎么实现转换数组的,请看下面一段代码的例子。

function sum(a, b) {

  let args = Array.from(arguments);

  console.log(args.reduce((sum, cur) => sum + cur));

}

sum(1, 2);    // 3

function sum(a, b) {

  let args = [...arguments];

  console.log(args.reduce((sum, cur) => sum + cur));

}

sum(1, 2);    // 3

function sum(...args) {

  console.log(args.reduce((sum, cur) => sum + cur));

}

sum(1, 2);    // 3

  从代码中可以看出,Array.fromES6的展开运算符,都可以把 arguments这个类数组转换成数组 args,从而实现调用 reduce方法对参数进行累加操作。其中第二种和第三种都是用 ES6的展开运算符,虽然写法不一样,但是基本都可以满足多个参数实现累加的效果。

总结

  可以看到,类数组这节课的知识点与 applycall还是有紧密联系的,你通过下面的表格再重新梳理一下类数组和数组的异同点。

方法特征数组类数组
自带方法多个参数
length属性
calles属性
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值