JavaScript之call()和apply()方法详解

简介:apply()和call()都是属于Function.prototype的一个方法属性,它是JavaScript引擎内在实现的方法,因为属于Function.prototype,所以每个Function实例,也就是每个方法都能使用apply和call方法。

作用:call 和 apply 都是为了改变某个函数运行时的 context 即上下文而存在的,换句话说,就是为了改变函数体内部 this 的指向。因为 JavaScript 的函数存在「定义时上下文」和「运行时上下文」以及「上下文是可以改变的」这样的概念。(需要理解JavaScript的执行环境和作用域的概念)

介绍完这两个方法后,说下它们的异同点:

相同点:这两个方法都是劫持另外一个对象的方法,继承另外一个对象的属性. 怎样理解这句话呢?代码如下:

例子 一:

    function people(){
        /*this.name="无名氏";*/
        this.sayName=function(){
            alert("您的姓名是:"+this.name);
        }
    }
    function xiaohua(){
        this.name="小花";
        people.call(this);  //这里使用call()方法的作用是,当前this对象(xiaohua对象),劫持了people对象,所以people中的this指向了xiaohua对象,所以xiaohua对象拥有了
                            //所有people对象的方法和属性
    }
    var aa=new xiaohua();
    aa.sayName(); //输出:  您的姓名是:小花

例子二:

function cat() {
}
cat.prototype={
    food:"fish",
    say:function(){
        alert("I love "+this.food)
    }
}
var blackcat=new cat();
blackcat.say();  //输出:I love fish

这里做个假设我们有一个对象whiteDog={food:"bone"},我不想对它重新定义say方法,因为这个say方法对需求来说完全适用!这个使用就需要使用call方法了!代码如下:

function cat() {
}
cat.prototype={
    food:"fish",
    say:function(){
        alert("I love "+this.food)
    }
}
var blackcat=new cat();
//这里做个假设我们有一个对象whiteDog={food:"bone"},我不想对它重新定义say方法,因为这个say方法对需求来说完全适用
var whiteDog={food:"bone"};
blackcat.say.call(whiteDog); //这句代码的意思是blackcat对象实例里面的say方法属性里面的this指向whiteDog对象,所以say方法里面的food属性就被whiteDog里面的food属性替换掉了
whiteDog.say();//输出I love bone

例子三:

下面这个例子据说是网易的前端面试题,来look,look

    var testA = function (b) {
        return this.a + b;
    };
    var obj = {a: 2};
    var testB = testA.myBind(obj, 1);
    alert(testB);

问题是:怎样实现myBind()方法,才能使testB的值3;

先一步步分析,

(1)我们发现myBind()方法是通过testA()方法调用的,我们知道在JavaScript中所有的方法都是一个对象,而所有的方法都继承自Function对象,所以所有在Function.prototype的方法和属性,将被所有的方法实例共享,比如call,apply。所有的方法实例都能通过fun1.(Function.prototype的中定义方法和属性)的形式调用。

而这里的myBind也是通过方法实例调用的方法,这种情况只有两种可能:

1:是上面所分析的是Function.prototype的方法实例,被所有的方法所共享的方法

2:内嵌函数,嵌套在testA函数中的函数

但是分析testA方法的形参和返回值,排除了第二种可能,那只能是第一种情况,代码如下:

    Function.prototype.myBind=function(object,extra){  //当哪个方法调用myBind方法,其this指针就指向该方法
        return this.call(object,extra); //传入的object对象劫持了testA对象,所以testA对象中的this指向objetc对象
    }
    var testA = function (b) {
        return this.a + b;//这里的this指向object对象,所以this.a=2,b=1;
    };
    var obj = {a: 2};
    var testB = testA.myBind(obj, 1);
    alert(testB);//输出:3

例子四:

通过document.getElementsByTagName选择的dom 节点是一种类似array的array。它不能应用Array下的push,pop等方法。我们可以通过: var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*")); 这样domNodes就可以应用Array下的所有方法了。

不同点:apply()和call()方法的区别就是在劫持对象后传递的参数类型不同,apply可以传递一个数组,而call只能一个参数一个参数传

例子五:个人觉得是网上理解call方法比较好的一种解释,也是比较好记的一种解释,下面看代码:

<script>
  function add(para1,para2) {
      alert(this); //输出:function substract(para1,para2) {return para1-para2;}
      return para1+para2;
  }
  function substract(para1,para2) {
      return para1-para2;
  }
     alert(window.add.call(window.substract,1,2));//输出:3
    // 这里注意:这里的上下文只和属性和方法有关,也就是说add方法劫持了substract方法的上下文对象,能使用其内部的属性和方法
    //但是其return语句并不会被覆盖,还是输出return para1+para2
  
    //首先先分析下上面这段语句的执行过程,首先将add的上下文对象替换成substract方法的对象,
    // 然后调用add方法并传入1和2两个参数

</script>

例子六:使用Call()方法实现单继承

<script>
    function Animal(name) {
/*        console.log(this);//输出Cat对象*/
        this.name=name;
        this.sayName=function () {
            alert(this.name);
        }
    }
    function Cat(name) {
        Animal.call(this,name);
        //首先Animal劫持了this(Cat)对象,将Animal中的上下文替换成了Cat的上下文,更具上面的输出可以看出,然后调用Animal方法并传入
        //name参数给Aniaml方法,我个人觉得应该是js引擎在做完上面的操作后返回一个Animal和Cat的结合的实体引用回去
        //所以我改变上面的代码加了一个  return,发现没有影响,也印证了我的猜测
    }
    var cat=new Cat("cat");
    cat.sayName();//输出:cat,根据输出得知,call方法可以实现oop的继承功能
</script>

例子七:使用call实现多继承,上面的单继承如果理解了的话,那么多继承也就很简单了

<script>
    function Add()
    {
        this.a=1;
        this.b=2;
        this.add=function(a,b)
        {
            return a+b;
        }
    }
    function Substract()
    {
        this.sub=function(a,b)
        {
            return a-b;
        }
    }
    function Operation(a,b)
    {
            Add.call(this,a,b);
            Substract.call(this,a,b);
    }
    var op=new Operation();
    alert(op.add(1,2));//输出:3
    alert(op.sub(1,2));//输出:-1

    //终极版分析:
    //分析上面方法的执行过程,首先初始化Operation对象,然后Add劫持Operation,Add内部的this指向Operation
    //其实这个劫持过程可以理解为在初始化Operation对象的时候,将Add对象初始化为自己的内部属性,方便调用
    //这个分析我是更具在chrome中的js运行过程看出来的,纯属我个人的观点
</script>

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

Java LinkedHashMap工作原理及实现

在理解了#7 介绍的HashMap后,我们来学习LinkedHashMap的工作原理及实现。首先还是类似的,我们写一个简单的LinkedHashMap的程序:

4110
来自专栏每日一篇技术文章

Foundation-String

最近写完了Swift 3.0教程 ,在接下来这段时间,继续写Foundation 的教程,帮助大家更加深入,系统的学习Foundation 框架,可能会持续一段...

11010
来自专栏mukekeheart的iOS之旅

《JavaScript高级程序设计》学习笔记(1)

欢迎关注本人的微信公众号“前端小填填”,专注前端技术的基础和项目开发的学习。 首先,我将从《JavaScript高级程序设计》这本JavaScript学习者必看...

29340
来自专栏技术博客

编写高质量代码改善C#程序的157个建议[正确操作字符串、使用默认转型方法、却别对待强制转换与as和is]

  字符串应该是所有编程语言中使用最频繁的一种基础数据类型。如果使用不慎,我们就会为一次字符串的操作所带来的额外性能开销而付出代价。本条建议将从两个方面来探讨如...

11340
来自专栏醒者呆

正则表达式——Java程序员懂你

正则表达式 关键字:正则表达式,Pattern,Matcher,字符串方法,split,replace 前文书立下了一个flag,这里要把它完成,就是正则...

38350
来自专栏大数据钻研

JavaScript 知识点整理

JavaScript是按照ECMAScript标准设计和实现的,后文说的JavaScript语法其实是ES5的标准的实现。 先说说有哪些基础语法? 最基础语法有...

29050
来自专栏编程心路

Java基础-Object类中的方法

下面这些是 Java 中的 Object 类中方法,共 11 个,9 种方法,wait() 方法被重载了。

14030
来自专栏前端架构与工程

【译】《Understanding ECMAScript6》- 第二章-函数

函数在任何一门编程语言中都是很重要的一个环节。JavaScript至今已有多年的历史,但是它的函数仍然停留在很初级的阶段。函数问题的大量堆积,以及某些函数非常微...

22770
来自专栏青玉伏案

窥探Swift之新添数据类型元组与可选值

  今天的博客中就总结一下关于Swift中相对Objc新添加的两个数据类型:元组(Tuple)和可选值类型(Optional)。上面这两个类型是Swift独有的...

20650
来自专栏IT可乐

JDK1.8源码(一)——java.lang.Object类

  本系列博客将对JDK1.8版本的相关类从源码层次进行介绍,JDK8的下载地址。   首先介绍JDK中所有类的基类——java.lang.Object。   ...

789150

扫码关注云+社区

领取腾讯云代金券