美文网首页饥人谷技术博客
栗子最实在:用实例来说明call、apply和bind方法

栗子最实在:用实例来说明call、apply和bind方法

作者: huangyh_max | 来源:发表于2017-06-28 01:00 被阅读0次

    前言

    call,apply和bind方法,之前使用的时候,总会有蒙圈的时候。这次顺着用实例来说明this的含义之后,就一次也把这三个方法整理明白吧。

    一、call和apply的比较

    ECMAScript标准中Function原型具备有call方法 Function.prototype.call和apply方法Function.prototype.apply,因此JavaScript中每个函数都有call方法和apply方法。
      call方法和apply方法,实现的效果作用是一样的,区别就在于参数的形式和数量上不同。具体而言是:

    • call方法接收的参数数量可以是多个。第一个参数是函数执行上下文的对象,后面的参数可以是多个
    <script>
            var a={
                name:'hyh'
            }
            var name='hyhaa'
            function exp(a,b){
                console.log(a+this.name+b)
            }
            exp.call(null,'happy ',' day') //happy hyhaa day
            exp.call(a,'happy ',' day') //happy hyh day
        </script>
    
    • apply方法接收的参数数量是两个。第一个参数也是函数执行上下文的对象,第二个参数是多个传入的函数参数组成的类数组。
    <script>
            var a={
                name:'hyh'
            }
            var name='hyhaa'
            function exp(a,b,c){
                console.log(this.name+a+b+c)
            }
            exp.apply(null,[' happy ',' every',' day']) //hyhaa happy  every day
        </script>
    

    call和apply方法的使用,其实就是换一种参数传递的形式来达到使用函数方法的目的。比如有些时候,我们手头有的参数形式是数组(类数组),直接传递给某个函数方法,但其不接受,那我们就借位,把函数方法换个apply写法,这样就能接受我们的参数形式。

    参数为数组的情况下,Math.max方法的使用示例和对比

    所以,鉴于apply和call方法的作用是一样的,那么如何挑选哪种写法合适,就看具体情况了。简单来说,就是参数是数组形式,就使用apply方法,参数是单独多个的话,就使用call方法。
      注意:如果第一个参数不需要写,不需要改动函数上下文的对象的话,记得写上null值来占位。如上面例子中的exp.apply(null,[' happy ',' every',' day'])写法

    二、call和apply的作用

    2.1 改变this的指向

    接上面所说的,call方法和apply方法的第一个参数都是作为函数执行上下文的对象,也就是说call方法和apply方法改变了函数被调用时this的指向

        <script>
            var value=100
            var obj5={
                value:200
            }
            function fn4(a,b){
                console.log(this.value+a+b)
            }
            fn4(3,4) //107
            fn4.call(obj5,3,4) //207
            fn4.apply(obj5,[3,4]) //207
        </script>
    

    这个例子中,等同于obj5这个对象传递给了函数fn4,函数fn4的this就指向obj5。等同于实现:

    function fn4(a,b){
      console.log(obj5.value+a+b)
    }
    

    再举个复杂点的例子:

    <script>
        var fruits={
         name:'apple',
         saleprice:function(price){
               console.log(this.name+' price is '+price +' yuan')
             }
         }
         var vegtable={
             name:'tomato'
         }
         fruits.saleprice('20')  //apple price is 20 yuan
         fruits.saleprice.call(vegtable,'12')  //tomato price is 12 yuan
    </script>
    

    在这个例子中,fruits.saleprice.call(vegtable,'12')是将vegtable传给到fruits对象中,因此this.name就等同于vegtable的name属性'tomato'。

    2.2 立即执行函数

    这两个方法的使用,会让函数立即执行。

    function exp(){
      console.log(1)
    }
    exp.call()
    exp.apply()
    

    不过因为例如exp()的写法也能让函数立即执行,所以常见的是这种更简便的写法exp()。

    三、call和apply常见的应用写法

    3.1 数组的扩充

       <script>
            var a=[123,'2fds','3sdf',34]
            var b=['aa','bb',343]
            Array.prototype.push.apply(a,b)
            console.log(a)  //[123, "2fds", "3sdf", 34, "aa", "bb", 343]
       </script>
    

    3.2 字符串的拼接

    function expjoin(arguments){
      var a=Array.prototype.join.apply(arguments);
      console.log(a);
    }
    expjoin([123,'2fds','3sdf',34]) //"123,2fds,3sdf,34"
    

    这个写法也等同于

    [].join.apply([123,'2fds','3sdf',34])  //"123,2fds,3sdf,34"
    

    3.3 最大/小值的获取

    var arr=[34,54,656,877]  //undefined
    Math.max.apply(Math,arr)  //877
    Math.max.apply(null,arr)  //877
    Math.max.call(null,34,54,656,877)  //877
    Math.min.call(null,34,54,656,877)  //34
    Math.min.apply(null,arr)  //34
    Math.min.apply(Math,arr)  //34
    

    3.4 验证是否为数组

    <script>
            function isArray(obj){
                return Object.prototype.toString.call(obj) ==='[object Array]'
            }
            console.log(isArray(343));
            console.log(isArray([4343,565]));
    </script>
    

    3.5 把一个类数组转换为真正的数组

    var arr= Array.prototype.slice.call([2,4,5]);
    arr  //[2, 4, 5]
    arr.push('33')  //[2, 4, 5, "33"],能够具备并使用数组的方法例如push()
    domNodes.push(3434)  //[2, 4, 5, "33", 3434]
    

    四、call和apply写法的转换

    如果看了感觉会晕,那么可以换种方式来看call和apply的写法

    Array.prototype.join.apply(arguments)等同于arguments.join()
    Array.prototype.slice.call(arguments)等同于arguments.slice()
    

    前面例子中的fruits.saleprice.call(vegtable,'12')等同于vegtable.saleprice('12')

    这样就不难理解,诸如下面的写法:

    function log(){
        console.log.apply(console, arguments); //console.log(arguments)
    }
    log(1,2,3,4,5)  //arguments会默认使用[ ]运算符获取参数,结果为:1 2 3 4 5
    

    对上面这个例子使用到了arguments,再来引申讲下arguments

    • 在函数调用时,会自动在该函数内部生成一个名为 arguments的隐藏对象

    • 该对象类似于数组,可以使用[ ]运算符获取函数调用时传递的实参

    • 只有函数被调用时,arguments对象才会创建,未调用时其值为null

     function fn5(name, age){
         console.log(arguments); //["Byron", 20]
         name = 'XXX';
         console.log(arguments); //["XXX", 20]
         arguments[1] = 30;
         console.log(arguments); //["XXX", 30]
     }
     fn5('Byron', 20);
    

    五、bind方法

    终于写到bind了~~bind的用法稍微迂回点,需要稍长点耐心理解。
    call方法和apply方法是ECMAScript3标准定义的,而bind方法是更晚的ECMAScript5标准定义的方法。

    5.1 bind、call和apply方法的相同之处

    前面我们已经说了call和apply的作用相同,区别就在参数的形式不同。bind方法的作用也是改变this的指向。bind方法的参数要求和call方法一样,第一个参数是函数执行上下文的对象,后面的参数可以是多个。
      但bind又有自己的特别之处,下面来说说bind的用法上的区别:

    5.2 bind方法的用法上的区别

    区别一: bind()方法是会创建一个新函数,当调用这个新函数时,会以创建新函数时传入 bind()方法的第一个参数作为 this。
    看栗子来体会:

    var name='xxx'; 
    var obj = {
        name: 'yyy'
    }
    function func() {
        console.log(this.name);
    }
    var func1 = func.bind(obj);
    func1();   //yyy
    func();     //xxx
    

    func1等于通过bind方法创建的和func函数相同的新函数。func1的this对象为传入的obj,所以this.name就只等于obj对象的name值‘yyy’。
      因为是新生成的函数,所以原来函数func()并不受影响,所以直接执行func(),this指向全局window,所以this.name为'xxx'。

    区别二: 参数的使用上,call方法是将第二个及之后的参数,作为实参传递到函数中。
      而 bind() 方法的第二个以及以后的参数,再加上新函数运行时的参数,按照顺序作为实参传递到新函数中。
      这样看文字解释,真的太绕了,直接看栗子,就很好理解:

    function func(a, b, c) {
        console.log(a, b, c);
    }
    var func1 = func.bind(null,'aaa');
    
    func('A', 'B', 'C');            // A B C
    func1('A', 'B', 'C');           // aaa A B
    func1('B', 'C');                // aaa B C
    func.call(null, 'aaa');      // aaa undefined undefined
    

    区别三: 最后一个不同,就是bind函数不是立即调用的,实现生成新的处理函数,然后再执行。而call和apply方法都是立即执行。所以写法上,bind函数需要再加个()
      还是看栗子吧,改写下前面的例子:(一样的实现效果,不一样的执行写法)

    var name='xxx'; 
    var obj = {
        name: 'yyy'
    }
    function func() {
        console.log(this.name);
    }
    console.log(func.bind(obj());  //yyy
    console.log(func.call(obj));    //yyy
    console.log(func.apply(obj));  //yyy
    
    5.3 bind方法常见的应用写法

    例子1:改变setTimeout()方法的this指向
      上一篇专门写this的使用实例的时候,讲到一个setTimeout()的例子。上一篇的例子中是使用var _this=this的方式来使setTimeout()的this不指向window。现在学了bind,同样也可以用bind来达到同样效果:

    <body>
        <button class="bindtest">dianwo</button>
        <script>
        /*bind的用法--和使用var _this=this达到同样的目的*/
            $bindtest=document.querySelector('.bindtest')
            $bindtest.addEventListener('click',function(){
                console.log(this) //<button class="domtest">dianwo</button>
                setTimeout(function(){
                    console.log(this)   //<button class="domtest">dianwo</button>
                }.bind(this),300)
            })
        </script>
    </body>
    

    前面说了,bind的作用是得到一个新的函数,而新函数的this就是bind传递的第一个参数。这里bind方法是和setTimeout方法同级的,属于$bindtest对象的点击事件下的,所以this是$bindtest对象。

    例子2:字符串拼接的bind方法实现
      前面call方法的使用例子中实现的字符串拼接,现在换成用bind方法来实现:
      常见的是,比如我需要一个join操作,但是我没有这个方法,那么我就需要借助数组Array原型的join方法来借位使用。

    function joinStr(){
          var joins=Array.prototype.join.bind(arguments);
          console.log(joins('-'))
    }
    joinStr('a','b','c')
    

    例子中的写法等同于['a','b','c'].join('-')

    六、结语

    写了这么一大通,结合实例来理解,真的事半功倍。我列举的call,apply和bind方法的常见使用的例子,只是我目前看到的常见写法。更多的变化写法,就待实践和学习,来扩充积累啦。

    参考文章:

    https://segmentfault.com/a/1190000009650716
    http://book.jirengu.com/fe/%E5%89%8D%E7%AB%AF%E8%BF%9B%E9%98%B6/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/this.html
    https://github.com/lin-xin/blog/issues/7
    https://segmentfault.com/a/1190000006993545

    相关文章

      网友评论

        本文标题:栗子最实在:用实例来说明call、apply和bind方法

        本文链接:https://www.haomeiwen.com/subject/vuqecxtx.html