关于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 = i
let total = 0
for(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])
}
目前就整理这么多,如果还有新的方法,请留言!