JS中call()和apply()以及bind()的使用及区别
1、来历
在js中,所有的函数都是Function的实例,而且对于Function来说,它的原型即Function.prototype中包含很多东西,其中call,apply和bind方法就是Function原型中的方法,所以根据原型的规则,所有的函数都可以使用原型中属性和方法,即对于所有的函数都可以使用call,apply和bind方法。
简单一句话:call,apply和bind都是Function原型中的方法,而所有的函数都是Function的实例。
2、作用
在js中,所有的函数在被调用的时候都会默认传入两个参数,一个是this,还有一个是arguments。在默认情况下this都是指当前的调用函数的对象。但是有时候我们需要改变this的指向,也就是说使函数可以被其他对象来调用,那么我们应该怎样做呢?这时候我们就可以使用call,apply和bind方法了。
3、有关this
在面向对象的JS中,我们了解到在JS中,一切都是对象。因为一切都是对象,我们开始明白我们可以为函数设置和访问其他属性。而this提供了一种更优雅的方式隐式“传递”一个对象的引用。
关于this,我们常见的误区:认为this指向函数本身
function f1() { console.log(this) } f1() // window
函数的调用方式决定了this的值。它指向什么完全取决于函数在哪里被调用,也就是说,谁调用,谁负责。而bind()、apply()、call()则是可以更改this指向的方法。请看下面例子:
var a = { user:"bahg", fn:function(){ console.log(this.user); } } var b = a.fn; b(); //undefined
我们是想打印对象a里面的user却打印出来undefined是怎么回事呢?因为b()相当于window.b(),this指向window,如果我们直接执行a.fn()是可以的。
var a = { user:"bahg", fn:function(){ console.log(this.user); } } a.fn(); //bahg
这里能够打印是因为,这里的this指向的是函数a,那为什么上面的不指向a?我们如果需要了解this的指向问题,请看https://www.cnblogs.com/pssp/p/5216085.html这篇文章。
虽然这种方法可以达到我们的目的,但是有时候我们不得不将这个对象保存到另外的一个变量中,那么就可以通过call、apply、bind方法。
4、区别
它们在功能上是没有区别的,都是改变this的指向(第一个参数都是this的指向对象),它们的区别主要是在于方法的实现形式和参数传递上的不同。
(1)call() 和 apply() 会立即执行,而 bind() 不会立即执行,它返回一个函数。
(2)call() 期望所有参数都单独传递,参数之间用逗号分隔,而 apply() 的参数都必须放在一个数组里面传进去。
(3)bind() 除了返回的是一个函数以外,它的参数和call() 一样。
5、使用
(1)call
var a = { user:"bahg", fn:function(){ console.log(this.user); } } var b = a.fn; b.call(a); // bahg
此时打印的就不再是undefined了,通过call方法使得b的this指向a对象,而不是window。call方法除了第一个参数以外还可以添加多个参数,这些参数会传入函数,如下:
var a = { user:"bahg", fn:function(a,b){ console.log(this.user); //bahg console.log(a+b); //3 } } var b = a.fn; b.call(a,1,2);
(2)apply
apply方法和call方法有些相似,它也可以改变this的指向。区别在于传递多个参数时必须使用数组,如下:
var a = { user:"bahg", fn:function(a,b){ console.log(this.user); /bahg console.log(a+b); //11 } } var b = a.fn; b.apply(a,[10,1]);
注意:如果call和apply的第一个参数写的是null,那么this指向的是window对象
(3)bind
var a = { user:"bahg", fn:function(a,b,c){ console.log(this.user); //bahg console.log(a,b,c); //10 1 2 } } var b = a.fn;
// b.bind(a); 不会输出任何结果
// var c = b.bind(a); // console.log(c); 输出 function() { [native code] }
// var c = b.bind(a,10,1,2);
// c(); // 输出结果
var c = b.bind(a,10);
c(1,2); // 输出结果
bind方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加,这是它们的区别,可以根据自己的实际情况来选择使用。
6、call 和 apply 的常见应用
(1)将伪数组转化为数组
伪数组:无法调用数组的方法,但是有length属性,又可以索引获取内部项的数据结构。比如:arguments、getElementsByTagName等一系列dom获取的NodeList对象,这些都是伪数组。
<body> <div></div> <div></div> <div></div> <div></div> <script type="text/javascript"> let divList = document.getElementsByTagName('div') console.log(divList) // HTMLCollection(4) [div, div, div, div] const arr = Array.prototype.slice.call(divList) // 相当于 divList.slice() const arr1 = [].slice.call(divList) // 相当于上面一行 console.log(arr1) // (4) [div, div, div, div] </script> </body>
原理:因为divList并没有slice方法,需要使用call来借调。通过 [].slice.call(divList),slice方法内部的this就会被替换成divList,并循环遍历divList,复制到新数组返回。
原理解释参考 https://segmentfault.com/a/1190000023473930?utm_source=sf-similar-article
同理,如果是函数参数arguments,类数组对象也采用同样方法,如下:
function fn() {
console.log(arguments)
console.log([].slice.call(arguments)) // (4) [1, 2, 3, 4]
}
fn(1,2,3,4)
let obj1 = {
0: 1, 1: 'bahg', 2: 'Amy', length: 3 //一定要有 } console.log([].slice.call(obj1)) // (3) [1, "bahg", "Amy"]
(2)数组的拼接
let arr1 = [1,2,3] let arr2 = [4,5,6] Array.prototype.push.apply(arr1,arr2) console.log(arr1) // (6) [1, 2, 3, 4, 5, 6]
(3)判断数据类型
function isArray(arr) { return Object.prototype.toString.call(arr) === '[object Array]' // return Object.prototype.toString.call(arr) === '[object Object]' // return Object.prototype.toString.call(arr) === '[object String]' // return Object.prototype.toString.call(arr) === '[object Null]' } let arr = [1,2,3] console.log(isArray(arr)) // true
参考 https://segmentfault.com/a/1190000017462138、https://www.cnblogs.com/pssp/p/5215621.html、https://www.cnblogs.com/lmsblogs/p/11271111.html