关于JS中类数组对象的一点理解

最近看见有个人写的面试题目里面有一题:jq获取的DOM节点对象是数组么?

结论

其实问题很简单的,jq获取的是一种类数组对象,是一个Object,他是不能够调用数组方法的;
实际操作如图,我们可以使用原生的方法来仿照获取到类数组对象,比如document.getElementByClassNamequerySelectAll等等
我们使用如下的方式创建一个test页面进行测试,测试结果如下:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title></title>
  6. </head>
  7. <body>
  8. <div class="box1"></div>
  9. <div class="box1"></div>
  10. <div class="box1"></div>
  11. <div class="box1"></div>
  12. <script type="text/javascript">
  13. const boxList = document.getElementsByClassName('box1')
  14. console.log("boxList的类型:", Array.isArray(boxList))
  15. console.log("boxList的原型:",boxList.__proto__)
  16. const boxArr = Array.from(boxList)
  17. console.log("boxList的类型:",Array.isArray(boxArr))
  18. boxArr.forEach(function(item){
  19. console.log("boxArr-item:",item)
  20. })
  21. console.log("boxArr的原型:",boxArr.__proto__)
  22. </script>
  23. </body>
  24. </html>

09.JS中的类数组对象 - 图1
这个时候我们就能够清楚的看到,我们获取到的类数组对象是一个HTMLSelection对象,而且是Symbol类型的,它仅仅具有length属性,和一些Symbol类型的方法;
使用from方法转成数组的boxArr还是有区别的
防止杠精,他们本质上都是对象,都是Object
但是我们可以清楚的知道,现在我们获取到的DOM节点对象里面的值保存的是类数组对象,他不可以像正常的数组一样,使用数组方法,为什么非要这样呢?当初设计的时候为什么不直接生成一个数组呢?


近一步寻找答案

再研究发现,其实我们可以将一个对象伪装成一个数组来用,比如

  1. let a = {}
  2. for(let i = 0; i < 10; i++){
  3. a[i] = i * i
  4. }
  5. a.length = i
  6. let total = 0
  7. for(let j = 0; j < a.length; j++){
  8. total += a[j]
  9. }

区别

上述a就是一个类数组对象
我们可以比较一下类数组对象和数组之间的区别

  1. 一个是对象,一个是数组,但是本质上都是对象,这个就不要争论了
  2. 关于length属性问题,当新的元素添加到列表当中的时候,数组的length会及时刷新,类数组则不会
  3. 数组的length属性可以实现数组的扩展和截断功能
  4. 数组可以操作原型上数组自带的方法,而类数组则不能操作数组方法

关于类数组对象的实例

  1. function函数内部的arguments对象就是一个类数组对象
  2. 获取Dom节点的方法
  3. jq中的$(‘’)

我们可以看一下jq中的部分源码:

  1. jQuery = window.jQuery = window.$ = function(selector, context) {
  2. return new jQuery.fn.init(selector, context);
  3. },
  4. jQuery.fn = jQuery.prototype = {
  5. init: function(selector, context) {
  6. selector = selector || document;
  7. // Handle $(DOMElement)
  8. if (selector.nodeType) {
  9. this[0] = selector;
  10. this.length = 1;
  11. this.context = selector;
  12. return this;
  13. }
  14. }

在这里面,我们就可以看见selector就是原生的DOM对象,this指向jq使用$()构造的对象,此时dom就作为对象的第一个属性值object[0]
这个时候我们大概对类数组对象有一点认识,但是对于为什么不在根源上使用数组进行存储,这个问题我还需要进一步了解。。。
目前还没找到答案!


类数组转数组方法

类数组对象转为数组的方法大概有以下几种:

  1. Array.from()
    这是数据的方法,可以将类数组对象转化成数组,当然他的功能远远不止这些,还有很多新的用途,有兴趣的可以去了解一下
    这个是关于Array.from的详细用法介绍
    我们可以这样使用:
    定义: arr代表数组,arguments代表类数组
  1. const arr = Array.from(arguments)

arr就变成了由arguments对象转变过来的数组,当然是用from函数只能进行浅拷贝

  1. ...扩展运算符
    这是es6提供的一个扩展运算符
  1. const arr = [...arguments]

arguments每一个参数传递到新的数组当中,是用arr进行接收

  1. Array.prototype.slice.call(arguments, [0, arguments.length])
    其中arguments就是类数组对象,我们将普通的类数组对象传递过去就可以进行转化,但是需要注意一点:
    (网传:IE下的dom对象是以com对象的形式实现的,js对象与com对象不能进行转换)
  1. let arr = Array.prototype.slice.call(arguments)
  2. // let arr = [].slice.call(list) 这两个的效果是一样的
  3. list.forEach(item => {
  4. console.log(item)
  5. })

call让一个对象调用另一个对象的方法。你可以使用call来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)
slice从一个数组中切割,返回新的数组,不修改切割的数组
这个理解起来就是:
是用arguments类数组对象使用call调用构造函数(Array)中的slice方法,在不修改原类数组对象的情况下,生成一个数组对象副本,同时后面的参数可以调节,用来控制生成副本的长度
那么同样的我们可以使用常规生成数组的方法来生成新的副本,比如
Array.prototype.forEach.call(arguments,callback)
Array.prototype.map.call(arguments,callback)

  1. Array.prototype.slice.apply(arguments,[callback])
    既然能想到call,那apply也是理所当然

  2. Array.prototype.forEach.bind(arguments)
    bind方法会创建一个函数,Array构造的函数(forEach),然后将bind里面第一个参数传递给 他创建的函数的this: 会将arguments -> this
    bind方法会将传递进入的arguments -> this,而this -> Array
    那么就可以产生一个新的数组副本并使用了

  3. for循环
    当然还有一种比较原始的方法,直接看就明白了

  1. const arr = []
  2. for(let i = 0; i< arguments.length; i++){
  3. arr.push(arguments[i])
  4. }

目前就整理这么多,如果还有新的方法,请留言!