zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    8.9_java_35
    8.8_java_34
    8.7_java_33
    8.6_java_32
    8.5_java_31
    8.4_java_30
    8.3_java_29
    2020年春季学期《软件工程》教学总结
    json的标准格式
    详解 【Vue】 生命周期
  • 原文地址:https://www.cnblogs.com/BAHG/p/15190794.html
Copyright © 2011-2022 走看看