什么是类数组?
类数组(类似于一个数组的对象):是相当于一个对象,里面有数组的值以及相应的属性(length)。
类数组本质上是一个对象,使用对象模拟出来的一个数组,它用数组下标作为对象属性的键,数组元素作为对象各字段的值。另外,类数组对象还有一个length属性。除此之外不再有其它像push、pop等数组属性和方法。
var arrObj = {
0: "a",
1: "b",
2: "c",
3: "d",
length: 4
}
for(var i = 0; i < arrObj.length; i++) { // 同样可以用常规遍历数组的方式来遍历它
console.log(arrObj[i]);
}
何时会用到类数组?
也许你真的和类数组相遇过
它!就!是!------arguments
每个函数里面都会存在一个arguments属性,它存储着函数所传入的实际参数。
console.log(useArguments(1, 2, 3, 4)); // 输出10
// 定义函数时并没有标明形参但是函数里面依然可以通过arguments接收到实参
function useArguments() {
var sum = 0;
for(var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
再如,我们平时用到的字符串对象实际上也是一个元素为单个字符的类数组对象。
var str = new String("abcd");
console.log(str);
类数组真的可以当数组用?
那么类数组是否可以坐稳数组这把交椅?答案当然是no,存在即合理(指数组)。当我们需要对类数组进行push、split、splice等原生数组操作时,瞬间感到无能为力。“啥玩意儿!这类数组还这么倔???!”。
只要我们把类数组对象里面的字段一个一个取出来放到一个数组里,就可以完成类数组向数组的转化了。从而实现所需的数组操作。
那么接下来我们就来好好调教调教它,嗷~
调教奥义——原型方法走起来!
首先我们知道数组里有一个方法——slice(),它是用来获取子数组的一个方法,有时候也常用来做数组深拷贝的操作。通过这个方法,我们可以把类数组调教为真正的数组,大概思路如下:
- 调用(访问)到slice()方法
- 将slice()方法里的this对象替换成所要转化的类数组对象
那么怎么才能访问到slice()呢?因为这个方法是存在于数组里的,所以访问的方法大概有两种:
- 通过一个匿名数组来访问
- 通过使用数组的构造函数Array来访问到数组原型里的slice()
实现起来的话就像这样:
[].slice(); // 方法一
Array.prototype.slice(); // 方法二
// 方法二需要结合到原型链的概念,如果这里有点懵建议先了解一下原型链
那么访问到了slice(),接下来实现第 2 点。
有小同学可能要冒问号了,为啥替换this对象就可以完美调教了呢??
我们来看一下slice()这个方法的大概实现原理:
Array.prototype.slice = function(start,end){
var result = new Array();
start = start || 0;
end = end || this.length; //this指向调用的对象,当用了call后,能够改变this的指向,也就是指向传进来的对象,这是关键
for(var i = start; i < end; i++){
result.push(this[i]);
}
return result;
}
引自
https://www.cnblogs.com/littledu/archive/2012/05/19/2508672.html
可以看到,方法里面是对this进行操作,也就是说谁用它,谁就是这个this。
嘿嘿,这会儿我们可以想,只要想办法把这个this换成类数组,不就可以对类数组进行操作了吗?
偷梁换柱的apply()、call()和bind()
说apply、call和bind是三兄弟一点都不为过,它们都是 Function.prototype
下的方法,因此所有的函数、方法都可以调用这三兄弟。它们虽略有差异,但都有一个共同点,就是可以通过这三个方法的第一个参数来改变调用函数的执行上下文,也就是改变函数里的this指向。
饿了?客官您的栗子!!
function fn() {
console.log(this)
}
var obj = {
name: 'html',
friend: 'javascript'
}
fn();
fn.apply(obj);
fn.call(obj);
fn.bind(obj)();
吃完栗子之后,我们发现
- apply和call在不使用第二个参数以及其后的参数的情况下,完全一样。(确实在这种情况下这俩可以随便用)
- 通过参数,我们把函数里面所要操作的ths对象给替换了
- bind()执行完后还是一个函数,所以需要加一个()来执行bind返回的函数
- 总结之后,apply和call替换this加执行,bind只负责替换this执行还需自个儿来~
我们再来一个有后续参数的栗子
function fn(name, a, b) {
console.log(`${name}a + b = ${a+b}`);
console.log(this);
}
var obj = {
name: 'html',
friend: 'javascript'
}
fn('fn():', 1, 2);
fn.apply(obj, ['apply():', 1, 2]);
fn.call(obj, 'call():', 1, 2);
fn.bind(obj, 'bind():', 1, 2)();
同样从例子可以发现
- 除了第一个参数,后续的参数只需完全照搬原来函数的参数
- 除了apply()的参数需要封装在一个数组里整合为一个参数外,call()和bind()的参数写法是完全一样的
- 出第一个参数obj外,后续参数传入函数后和直接调用fn()本身没什么两样
首位呼应一下:这样就达到了this的偷梁换柱,而不影响函数原来参数的效果(干笑哈哈哈~)
大结局
害,总该结束了
现在结合前面所讲的一堆,可以得出类数组转化成真数组的方法应该如下:
var arrObj = {
0: "a",
1: "b",
2: "c",
3: "d",
length: 4
}
// 以call为例,其它两兄弟灵活使用
var way1 = [].slice.call(arrObj); // 方法一
var way2 = Array.prototype.slice.call(arrObj); // 方法二
console.log(arrObj);
console.log(way1);
console.log(way2);
啰嗦了那么多,只为了能受惠于更多的小白,对于大佬们可以挑着看哈
谢谢捧场~~