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

posted @ 2021-08-26 17:36  BAHG  阅读(333)  评论(0编辑  收藏  举报