• 面试官:能解释一下javascript中bind、apply和call这三个函数的用法吗


    作者:小土豆biubiubiu

    博客园:www.cnblogs.com/HouJiao/

    掘金:https://juejin.im/user/58c61b4361ff4b005d9e894d

    简书:https://www.jianshu.com/u/cb1c3884e6d5

    微信公众号:土豆妈的碎碎念(扫码关注,一起吸猫,一起听故事,一起学习前端技术)

    码字不易,点赞鼓励哟~

    一.前言

        不知道大家还记不记得前一篇文章:《面试官:能解释一下javascript中的this吗

      那今天这篇文章虽然是介绍javascript中bind、apply和call函数,但是多少也和this有点关联。

      假如在前面那场面试末尾,面试官不依不饶继续问你javascript中的this,那看完本篇文章后一定还会有收获。

      (本篇文章不会站在this的角度去回答问题,而是重于解释bind、apply和call这三个函数的用法和使用场景)

    二.正戏开始

      面试官:能解释一下javascript中bind、apply和call这三个函数的用法吗?

      我:(这三个函数我也只是了解它们的用法,仅此而已)

      我:这三个函数都是用于改变函数运行时内部this的指向的,只是每个函数的用法不一样。

      面试官:那你分别说一下具体都怎么用吧。

      我:(接着我边回忆之前做过的小练习边回答面试官)

      (以下描述和回答均基于浏览环境)

      首先是一个很简单的示例

     1 var objMM = {
     2     name: 'MM',
     3     age: 18,
     4     getPresonInfo: function(addr){
     5         console.log(this.name + "年龄" + this.age + " 地址: " + addr);
     6     }
     7 };
     8 
     9 var objZZ = {
    10     name: 'ZZ',
    11     age: 28,
    12     getPresonInfo: function(addr){
    13         console.log(this.name + "年龄" + this.age + " 地址: " + addr);
    14     }
    15 }
    16 
    17 
    18 objMM.getPresonInfo('上海');
    19 
    20 objZZ.getPresonInfo('深圳');

      18行和20行的打印信息分别为:

      

      bind方法

      首先是bind方法,它的基本语法为 targetFunction.bind(thisArg,arg1,arg2,...)。

      第一个参数thisArg会作为目标函数targetFunction运行时的this值传递给目标函数。

      后面的参数列表arg1,arg2,... 是传递给目标函数的参数。

      bind方法的返回值是一个目标函数的一个拷贝。

      这个拷贝出来的函数运行时this指向的就是调用bind传递的thisArg参数。

      并且拷贝函数还拥有调用bind时传递的arg1,arg2,...多个参数。

      感觉这段描述把我自己都说晕了。

      所以如果觉得语言描述不清楚,就写一个简单的用法示例:

    var copyF = objMM.getPresonInfo.bind(objZZ,'远方');
    copyF();

      这个示例是要在前面第一个示例的基础上运行的。

      在结合前面那段晦涩难懂的文字描述,可以这样理解这两行代码:

        copyF为objMM.getPresonInfo函数的一个拷贝。

        copyF运行时内部的this指向objZZ;

        copy函数拥有一个参数'远方'

      这样调用copy函数的结果就很显而易见了。

      

      apply方法

      apply方法的基本语法为 targetFunction.apply(thisArg,[arg1,arg2])。

      第一个参数的作用同bind方法。

      第二个参数的作用也是和bind方法相同,只是将参数列表变为数组的形式进行传递。

      apply方法的返回值和bind方法就完全不同了,它会直接调用并执行目标函数。

      那话不多说,在写一个示例

    objMM.getPresonInfo.bind(objZZ,['你管我在哪']);
    

      打印结果:

      

      call方法

      call方法的基本语法为 targetFunction.call(thisArg,arg1,arg2,...)。

      第一个参数的作用同bind方法,也同apply方法。

      第二个参数的作用也是和bind、apply相同,只是形式同bind方法是参数列表形式。

      call方法的返回值同apply方法,也是直接调用并执行目标函数。

    objMM.getPresonInfo.call(objZZ,'我爱在哪在哪');
    

       打印结果:

      

      面试官:那这些函数你平时用过吗,具体有什么使用场景。

      我:(这下惨了,平时还真没咋用过,如实回答)平时在写代码的时候,基本没咋用过。

      面试官:那好吧

      我:(凉凉)......

    三.自我反思

      回家后深刻进行了自我反思:平时好像还真的没有使用过这个三个函数呀,不过没关系,现在学还来得及。

      于是我开始各种搜罗,然后依照个人理解,将其分为两种使用场景。

      (怎么分类不重要,后面的示例才重要)

    1.使用场景一:借用函数

      借用函数大概意思就是借用现有方法去自己的需求。

      在文章开始的第一个示例的基础上,稍作一下修改

     1 var objMM = {
     2     name: 'MM',
     3     age: 18,
     4     getPresonInfo: function(addr){
     5         console.log(this.name + "年龄" + this.age + " 地址: " + addr);
     6     }
     7 };
     8  
     9 var objZZ = {
    10     name: 'ZZ',
    11     age: 28
    12 }

      这个代码中,objZZ已经没有getPersonInfo这个方法了,假如我们想像objMM那样去打印对象自身的信息怎么办呢?

      此时这三个函数就能派上用场了。

    1 objMM.getPresonInfo.bind(objZZ,'我爱在哪在哪')();   // ZZ年龄28 地址: 我爱在哪在哪
    2 objMM.getPresonInfo.apply(objZZ,['我爱在哪在哪']);  // ZZ年龄28 地址: 我爱在哪在哪
    3 objMM.getPresonInfo.call(objZZ,'我爱在哪在哪');     // ZZ年龄28 地址: 我爱在哪在哪

      

      在就是javascript里面有很多工具对象(我自己这样叫),比如Math。

      Math类有两个函数max和min,一般情况下依照这两个函数的语法只能这样使用:

    var maxNum = Math.max(23,197,88,35,109,11);
    console.log(maxNum);   // 197
    
    var minNum = Math.min(23,197,88,35,109,11);
    console.log(minNum);   //11
    

      假设现在代码里面有一个数组变量要求出最大最小值,我们又不想自己去实现。

      那我们就只能借助Math提供的max和min方法,使用apply函数去实现这个功能。

    var arr = [23,197,88,35,109,11];
    
    var maxNum = Math.max.apply(Math,arr);
    console.log(maxNum);   // 197
    
    var minNum = Math.min.apply(Math,arr);
    console.log(minNum);   //11

      

      除了Math类之外,数组也有很多api,比如最常见的forEach。

      这个方法也只能是数组类型的变量才能使用,那非数组类型的变量要使用怎么办呢?

     1 var divCollections = document.getElementsByTagName('html');
     2 
     3 Array.prototype.forEach.bind(divCollections,function(item){
     4     console.log(item);
     5 })();
     6 Array.prototype.forEach.apply(divCollections,[function(item){
     7     console.log(item);
     8 }]);
     9 Array.prototype.forEach.call(divCollections,function(item){
    10     console.log(item);
    11 });

      

      借用函数的最后一个使用场景就是数据类型判断。

      我们知道javascript中使用typeof可以判断一个变量的类型,但是仅限于基础的类型。

      比如:number、string、boolean、undefined类型。

      其他类型的例如:array、object、null使用typeof 判断类型打印均为“object”

      所以我们可以借助Object对象提供的一个函数,准确的知道一个数据的类型。

    1 Object.prototype.toString.call(1);   // "[Object Number]"
    2 Object.prototype.toString.call('1'); // "[Object String]"
    3 Object.prototype.toString.call(true); // "[Object Boolean]"
    4 Object.prototype.toString.call(undefined); // "[Object Undefined]"
    5 Object.prototype.toString.call([]); // "[Object Array]"
    6 Object.prototype.toString.call({}); // "[Object Object]"
    7 Object.prototype.toString.call(null); // "[Object Null]"

      

    2.使用场景二:实现继承

      我们都知道,javascript中最简单的继承代码是通过将子类原型指向父类实例实现的。

     1 function Father(name,age){
     2     this.name = name;
     3     this.age  = age;
     4     this.sayInfo = function(){
     5         console.log(this.name + "年龄: "+ this.age);
     6     }
     7 }
     8 
     9 
    10 function Son(name,age){
    11     this.name = name;
    12     this.age  = age;
    13 }
    14 
    15 //将子类原型指向父类实例
    16 Son.prototype = new Father('我是你爸爸',);
    17 
    18 var s = new Son('Son',1)
    19 s.sayInfo();   //打印:Son年龄: 1

      那我们可以借助这三个函数实现javascript中的继承。

      (这里只写call方法的实现)

     1 function Father(name,age){
     2     this.name = name;
     3     this.age  = age;
     4     this.sayInfo = function(){
     5         console.log(this.name + "年龄: "+ this.age);
     6     }
     7 }
     8 
     9 function Son(name,age){
    10     Father.call(this,name,age)
    11 }
    12 
    13 
    14 var s = new Son('Son',1)
    15 s.sayInfo();   //打印:Son年龄: 1

      可以看到使用call实现继承时,只需要在子类Son中调用父类的构造函数,并且按照call函数的语法传入所需参数即可。

      后面直接使用Son的实例就能调用sayInfo函数。

      这种方式说来有点意思,因为前面es5语法的继承是子类原型指向父类实例,也就是通过原型链实现的。

      而这种方式的原理又是什么呢?

      好奇心驱使,我分别打印了前面es5中原型链实现继承后创建的实例s和使用call实现继承后创建的实例s

      

      从结果可以看到,使用call实现继承,实例化后的对象s本身已经拥有了sayInfo方法。

      所以说原型链式的继承和call实现的继承还是有本质的区别的。

      

      那到底里,关于bind、apply、call函数的使用场景就整理完了,下次遇到面试官问应该就不虚了。

      (使用场景有可能不全,欢迎大家补充)

    四.总结

      本篇到此就基本结束了,结合前一篇关于this的文章,javascript中的this基本就没啥大问题了。

      当然实际的项目千变万化,还是需要谨慎使用this。

      介于本篇文章主要还是解释javascript中bind、apply和call函数的用法,因此后续会在补一篇总结《使用原生Javascript实现bind、apply和call函数》。

        最近作者新开通了一个微信公众号。

      微信公众号会分享一些自己日常的东西,包括个人总结呀,吸猫日常呀,同时也会分享一些博客上的前端技术文章。

      

      

       欢迎大家扫码关注~

      

      

  • 相关阅读:
    [总结]FFMPEG视音频编解码零基础学习方法--转
    ffmpeg中swscale 的用法
    FFmpeg解码H264及swscale缩放详解
    C++: std::string 与 Unicode 如何结合?
    SpringBoot-@RequestParam
    SpringBoot --web 应用开发之文件上传
    Java并发编程:线程池的使用
    Executor线程池的最佳线程数量计算
    并发下线程池的最佳数量计算
    根据CPU核心数确定线程池并发线程数
  • 原文地址:https://www.cnblogs.com/HouJiao/p/12283748.html
Copyright © 2020-2023  润新知