关于JS中类数组对象的一点理解
最近看见有个人写的面试题目里面有一题:jq获取的DOM节点对象是数组么?
结论
其实问题很简单的,jq获取的是一种类数组对象,是一个Object,他是不能够调用数组方法的;
实际操作如图,我们可以使用原生的方法来仿照获取到类数组对象,比如document.getElementByClassName、querySelectAll等等
我们使用如下的方式创建一个test页面进行测试,测试结果如下:
<!DOCTYPE html><html><head><meta charset="utf-8"><title></title></head><body><div class="box1"></div><div class="box1"></div><div class="box1"></div><div class="box1"></div><script type="text/javascript">const boxList = document.getElementsByClassName('box1')console.log("boxList的类型:", Array.isArray(boxList))console.log("boxList的原型:",boxList.__proto__)const boxArr = Array.from(boxList)console.log("boxList的类型:",Array.isArray(boxArr))boxArr.forEach(function(item){console.log("boxArr-item:",item)})console.log("boxArr的原型:",boxArr.__proto__)</script></body></html>

这个时候我们就能够清楚的看到,我们获取到的类数组对象是一个HTMLSelection对象,而且是Symbol类型的,它仅仅具有length属性,和一些Symbol类型的方法;
使用from方法转成数组的boxArr还是有区别的
防止杠精,他们本质上都是对象,都是Object类
但是我们可以清楚的知道,现在我们获取到的DOM节点对象里面的值保存的是类数组对象,他不可以像正常的数组一样,使用数组方法,为什么非要这样呢?当初设计的时候为什么不直接生成一个数组呢?
近一步寻找答案
再研究发现,其实我们可以将一个对象伪装成一个数组来用,比如
let a = {}for(let i = 0; i < 10; i++){a[i] = i * i}a.length = ilet total = 0for(let j = 0; j < a.length; j++){total += a[j]}
区别
上述a就是一个类数组对象
我们可以比较一下类数组对象和数组之间的区别
- 一个是对象,一个是数组,但是本质上都是对象,这个就不要争论了
- 关于
length属性问题,当新的元素添加到列表当中的时候,数组的length会及时刷新,类数组则不会 - 数组的
length属性可以实现数组的扩展和截断功能 - 数组可以操作原型上数组自带的方法,而类数组则不能操作数组方法
关于类数组对象的实例
function函数内部的arguments对象就是一个类数组对象- 获取Dom节点的方法
- jq中的$(‘’)
我们可以看一下jq中的部分源码:
jQuery = window.jQuery = window.$ = function(selector, context) {return new jQuery.fn.init(selector, context);},jQuery.fn = jQuery.prototype = {init: function(selector, context) {selector = selector || document;// Handle $(DOMElement)if (selector.nodeType) {this[0] = selector;this.length = 1;this.context = selector;return this;}}
在这里面,我们就可以看见selector就是原生的DOM对象,this指向jq使用$()构造的对象,此时dom就作为对象的第一个属性值object[0]
这个时候我们大概对类数组对象有一点认识,但是对于为什么不在根源上使用数组进行存储,这个问题我还需要进一步了解。。。
目前还没找到答案!
类数组转数组方法
类数组对象转为数组的方法大概有以下几种:
Array.from()
这是数据的方法,可以将类数组对象转化成数组,当然他的功能远远不止这些,还有很多新的用途,有兴趣的可以去了解一下
这个是关于Array.from的详细用法介绍
我们可以这样使用:
定义:arr代表数组,arguments代表类数组
const arr = Array.from(arguments)
arr就变成了由arguments对象转变过来的数组,当然是用from函数只能进行浅拷贝
...扩展运算符
这是es6提供的一个扩展运算符
const arr = [...arguments]
将arguments每一个参数传递到新的数组当中,是用arr进行接收
Array.prototype.slice.call(arguments, [0, arguments.length])
其中arguments就是类数组对象,我们将普通的类数组对象传递过去就可以进行转化,但是需要注意一点:
(网传:IE下的dom对象是以com对象的形式实现的,js对象与com对象不能进行转换)
let arr = Array.prototype.slice.call(arguments)// let arr = [].slice.call(list) 这两个的效果是一样的list.forEach(item => {console.log(item)})
call让一个对象调用另一个对象的方法。你可以使用call来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)
slice从一个数组中切割,返回新的数组,不修改切割的数组
这个理解起来就是:
是用arguments类数组对象使用call调用构造函数(Array)中的slice方法,在不修改原类数组对象的情况下,生成一个数组对象副本,同时后面的参数可以调节,用来控制生成副本的长度
那么同样的我们可以使用常规生成数组的方法来生成新的副本,比如
Array.prototype.forEach.call(arguments,callback)
Array.prototype.map.call(arguments,callback)
…
Array.prototype.slice.apply(arguments,[callback])
既然能想到call,那apply也是理所当然Array.prototype.forEach.bind(arguments)
bind方法会创建一个函数,Array构造的函数(forEach),然后将bind里面第一个参数传递给 他创建的函数的this: 会将arguments -> this
bind方法会将传递进入的arguments -> this,而this -> Array
那么就可以产生一个新的数组副本并使用了for循环
当然还有一种比较原始的方法,直接看就明白了
const arr = []for(let i = 0; i< arguments.length; i++){arr.push(arguments[i])}
目前就整理这么多,如果还有新的方法,请留言!
