call方法真是一个有意思的东西,它能够改变函数调用时this的值。而咱们知道,在函数里,this指向了调用这个函数的环境对象,好比一道经典面试题:面试
var num = 2; var obj = { num: 1, show: function () { console.log(this.num) } }; var foo = obj.show; obj.show();/* 显示1;show是被obj调用的,因此this指向obj */ foo();/* 显示2;至关于global.foo(),因此this指向global,若是在浏览器里global就是window */
换句话说,若是一个对象obj上有方法foo,你能够经过obj.foo()
调用;若是没有obj上没有方法foo,obj.foo()
是会报错的,可是,使用foo.call(obj)
,能够强行达到obj.foo()
的效果,好比:数组
function foo(){ console.log(this.num); } var obj = { num: 1 } foo.call(obj);// 1
Array.prototype.slice.call
的用处就是这样,能够在array-like(类数组,就是长得像数组,但不是数组)的对象上强行使用slice方法,好比:Array.prototype.slice.call(arguments)
就是把arguments
对象转化为数组。固然,除了arguments
,咱们还能在HTMLCollection
或NodeList
身上使用。那么到底什么算是类数组呢?浏览器
有length属性的对象。函数
好比:this
var obj1 = { 0: 'Tom', 1: 'Jack', 2: 'Jason', length: 3 } var arr = [].slice.call(obj1); console.log('arr: ', arr);/* [ 'Tom', 'Jack', 'Jason' ] */
那若是没有length呢?prototype
var obj1 = { 0: 'Tom', 1: 'Jack', 2: 'Jason' } var arr = [].slice.call(obj1);//* [] */
原来没有length属性的对象也会被转为数组,只不过认为它length=0而已。code
那若是对象的属性没有按照0-n顺序乖乖排好呢?对象
var obj1 = { 1: 'Tom', 3: 'Jack', 5: 'Jason', 7: 'Dave', foo: 'bar', length: 6 } var arr = [].slice.call(obj1);/* [ , 'Tom', , 'Jack', , 'Jason' ] */
原来转化的时候,会以length
为基础,生成一个长度为length
的数组,obj
的属性是数组的有效index
的话,就会把对应值填入到对应位置,其余的位置找不到值,就会填入undefined
。字符串
因此前面的说法其实不对,全部的对象均可以被视为类数组,有length
的视为长度为length
的数组,没有的,视为长度为0的数组。io
以length
属性为基础
这句话很重要。
另外,call
方法的参数若是是原始值类型
,会传入它的自动包装对象
:
var arr = [].slice.call('hello');
等价于:
var arr = [].slice.call(new String('hello'));/* [ 'h', 'e', 'l', 'l', 'o' ] */ 由于new String('hello')就是 { 0: "h", 1: "e", 2: "l", 3: "l", 4: "o", length: 5 }
以上就是Array.prototype.slice.call
的一些细节,那么除了slice
以外,Array
对象还有不少其余的方法,这些方法是否是也能用到对象身上呢?
join方法是把数组转化为字符串的方法,具体表现再也不赘述,看两个例子:
var obj1 = { 0: 'Tom', 1: 'Jack', 2: 'Jason', length: 6 } var arr = [].join.call(obj1, '-');// Tom-Jack-Jason--- var obj1 = { 0: 'Tom', 1: 'Jack', 2: 'Jason', } var arr = [].join.call(obj1, '-'); // ''
仍是那句话,以length
为基础,没有length
属性的,视为长度为0的数组。
这个方法比较好玩:
var obj1 = { 0: 'Tom', 1: 'Jack', 2: 'Jason', length: 6 } var arr = [].push.call(obj1, 'Dave'); console.log('arr: ', arr);// 7,由于push方法返回的是push以后array的操做数 console.log('obj: ', obj1);// { '0': 'Tom', '1': 'Jack', '2': 'Jason', '6': 'Dave', length: 7 }
能够看到obj1
里新增属性6
,值为'Dave'
,而且length
也更新为7
,这说明调用push
时会对原有对象进行修改。
咱们能够利用这个特性,好比当咱们须要一个obj1
的类数组副本时:
var obj = { foo: 'foo', bar: 'bar', cei: 'cei' }; var copy = {}; for (var i in obj) { [].push.call(copy, obj[i]) } console.log(copy);// { '0': 'foo', '1': 'bar', '2': 'cei', length: 3 }
若是,没有传入length
呢?
var obj1 = { 0: 'Tom', 1: 'Jack', 2: 'Jason' } var arr = [].push.call(obj1, 'Dave'); console.log('arr: ', arr);// 1 console.log('obj: ', obj1);// { '0': 'Dave', '1': 'Jack', '2': 'Jason', length: 1 }
这里的行为有些诡异,不过也更好地解释了以length为基础这句话:
没有length
的时候,认为数组长度为0
,而且会对obj
进行修改,把属性0的值改成Dave
.
那么,会触类旁通的话,对于pop
, shift
和unshift
这三个方法的行为应该能想象得出来,就再也不赘述了。
var obj1 = { 0: 'Tom', 1: 'Jack', 2: 'Jason', length: 6 } var arr = [].reverse.call(obj1); console.log('arr: ', arr);// { '3': 'Jason', '4': 'Jack', '5': 'Tom', length: 6 } console.log('obj: ', obj1);// { '3': 'Jason', '4': 'Jack', '5': 'Tom', length: 6 }
reverse
的话,arr === obj1
var obj1 = { 0: 'c', 1: 'b', 2: 'a', length: 6 } var arr = [].sort.call(obj1); console.log('arr: ', arr);// { '0': 'a', '1': 'b', '2': 'c', length: 6 } console.log('obj: ', obj1);// { '0': 'a', '1': 'b', '2': 'c', length: 6 }
sort
也同样,arr === obj1
concat
的表现就不是咱们意料之中的了:
var obj1 = { 0: 'c', 1: 'b', 2: 'a', length: 6 } var add = { foo: 'foo', bar: 'bar' } var arr = [].concat.call(obj1, add); console.log('arr: ', arr);// [ { '0': 'c', '1': 'b', '2': 'a', length: 6 }, 'foo', 'bar' ] console.log('obj: ', obj1);// { '0': 'c', '1': 'b', '2': 'a', length: 6 }
var obj1 = { 0: 'c', 1: 'b', 2: 'a', length: 6 } var arr = [].concat.call(obj1, 'foo', 'bar'); console.log('arr: ', arr);// [ { '0': 'c', '1': 'b', '2': 'a', length: 6 }, 'foo', 'bar' ] console.log('obj: ', obj1);// { '0': 'c', '1': 'b', '2': 'a', length: 6 }
能够看到obj1
并不会改变,不会像push
同样会接着造成一个类数组的对象.
var obj1 = { 0: 'c', 1: 'b', 2: 'a', length: 6 } var arr = [].splice.call(obj1, 0, 1); console.log('arr: ', arr);// [ 'c' ] console.log('obj: ', obj1);// { '0': 'b', '1': 'a', length: 5 }
var obj1 = { 0: 'c', 1: 'b', 2: 'a', length: 6 } var arr = [].splice.call(obj1, 1, 0, 'foo','bar'); console.log('arr: ', arr);// [] console.log('obj: ', obj1);// { '0': 'c', '1': 'foo', '2': 'bar', '3': 'b', '4': 'a', length: 8 }
var obj1 = { 0: 'c', 1: 'b', 2: 'a', length: 6 } var arr = [].splice.call(obj1, 1, 1, 'foo','bar'); console.log('arr: ', arr);// [ 'b' ] console.log('obj: ', obj1);// { '0': 'c', '1': 'foo', '2': 'bar', '3': 'a', length: 7 }
splice
的行为回归了,它如今对obj1
产生影响,而且是咱们预计的样子
var obj1 = { 0: 'c', 1: 'b', 2: 'a', length: 6 } var arr = [].every.call(obj1, function (val) { return val === 'a' || val === 'c' }); console.log('arr: ', arr);// false console.log('obj: ', obj1);// { '0': 'c', '1': 'b', '2': 'a', length: 6 }
var obj1 = { 0: 'c', 1: 'b', 2: 'a', length: 6 } var arr = [].filter.call(obj1, function (val) { return val === 'a' || val === 'c' }); console.log('arr: ', arr);// [ 'c', 'a' ] console.log('obj: ', obj1);// { '0': 'c', '1': 'b', '2': 'a', length: 6 }
var obj1 = { 0: 'c', 1: 'b', 2: 'a', length: 6 } var arr = [].forEach.call(obj1, function (val) { return val + ' add'; }); console.log('arr: ', arr);// undefined console.log('obj: ', obj1);// { '0': 'c', '1': 'b', '2': 'a', length: 6 }
var obj1 = { 0: 'c', 1: 'b', 2: 'a', length: 6 } var arr = [].map.call(obj1, function (val) { return val + ' add'; }); console.log('arr: ', arr);// [ 'c add', 'b add', 'a add', , , ] console.log('obj: ', obj1);// { '0': 'c', '1': 'b', '2': 'a', length: 6 }
var obj1 = { 0: 'c', 1: 'b', 2: 'a', length: 6 } var arr = [].reduce.call(obj1, function (pre, cur) { return pre + ' ' + cur }); console.log('arr: ', arr);// 'c b a' console.log('obj: ', obj1);// { '0': 'c', '1': 'b', '2': 'a', length: 6 }