解析夺命连环call——阿里面试题

本文详细探讨了JavaScript中call方法的作用、原理以及如何还原call。通过实例解析了call如何改变this的指向,并通过夺命连环call的题目,阐述了多次调用call的执行过程,帮助读者深入理解call的本质。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

call的作用

  call最主要的作用就是改变this的指向,所有函数都能基于原型链在Function.prototype里找到call方法,所以所有函数都能直接调用call方法,如下:

函数.call(新的this指向,参数1,参数2,…)

  call的第一个参数是新的this指向,其余的参数都是调用call方法的函数的实参,调用call方法的函数会被执行。

  如果call方法的第一个参数是null或者undefined,在非严格模式下,thi指向window,在严格模式下,this指向null或者undefined;如果没有给call方法传递参数,那么在非严格模式下,this指向window,在严格模式下,this指向undefined。

call的原理

  在做题之前需要简单了解一下call的原理,我们先来看一串代码:

    function show(){
        console.log(this);
    }
    var obj={}
    show();

  此时show函数执行后,show函数里面的this指向的是window,我们要将这个this的指向改成obj的话,就需要将show函数添加到obj里面,并且通过obj调用来执行,如下:

    function show(){
        console.log(this);
    }
    var obj={};
    obj.show=show;
    obj.show();

  现在show函数里的this就指向obj了。

  call方法的实现也是基于这个原理。

还原call方法

  了解了call方法的原理之后,我们就可以自己来还原一下call方法,先写一个初级版本的,这里我用函数mycall来表示我重写的call方法:

    Function.prototype.mycall = function mycall(context,...params){
        context['this'] = this;//this--->show函数
        let result = context['this'](...params);
        return result;
    }
    function show(){
        console.log(this,...arguments);
    }
    var obj = {name:'chen'};
    show.mycall(obj,10,20);//执行结果是{name:'chen',this:f},10,20

  我们不难发现这初级版本有以下几个弊端:

  1. 如果第一个参数不是引用数据类型,没有原型链,无法添加show函数,所以代码会报错,根据万物皆对象的理论,所有函数都可以通过原型链找到Object的原型对象,而Object本身也是一个函数,我们可以通过Object()将基本数据类型转换为对应的引用数据类型.举个简单的例子,如下:
    console.log(Object(123));//Number{123}
    conosle.log(Object('haha'));//String{'haha'}
    var obj = Object('chen');
    console.log(obj instanceof Object);//true
    console.log(obj instanceof String);//true
  1. 如果第一个参数是null或者是undefined,代码也会报错,因为null和undefined也不是引用数据类型,无法添加show函数。(在一开始我们提到,call方法在非严格模式下,第一个参数是null或者undefined的时候,this是指向window的)
  2. 在执行mycall方法后输出的结果,临时添加在context里的this属性也被打印出来了,所以我们需要在return前把这个临时添加的属性给删除。

  经过以上分析,我们可以进一步升级mycall方法:

     Function.prototype.mycall = function mycall(context,...params){
     //this--->show函数   参数:context  params:[10,20]
     //2. 第一个参数如果是null或者undefined
     //this--->window   show函数添加在window身上
        if(context === null || context === undefined){
            context = window;
        }//
     //1. 如果第一个参数不是引用数据类型,转换成引用数据类型
        if(typeof context !== 'object' || typeof context !== 'function'){
            context = Object(context);
        }
     /*注意:上面两个判断条件不能交换位置,不然当第一个参数是null或者undefined的时候,
     this指向的是一个空对象*/
        context['this'] = this;
        let result = context['this'](...params);
     //3. 删除临时添加的属性
        delete context['this'];
        return result;
    }
    function show(){
        console.log(this,...arguments);
    }
    var obj = {name:'chen'};
    show.mycall(obj,10,20);//执行结果是{name:'chen'},10,20

夺命连环call

题目本体:

    const fn1 = function fn1(){
        console.log(1);
    }
    const fn2 = function fn2(){
        console.log(2);
    }
    
    fn1.call(fn2);
    fn1.call.call.call.call(fn2);
    Function.prototype.call(fn2);
    Function.prototype.call.call.call.call(fn2);
  1. fn1.call(fn2)

这题是把fn1的this指向改成了fn2,并且执行fn1函数,结果为1.

  1. fn1.call.call.call.call(fn2)

  为了避免混乱,我们可以使用上面初级版本的mycall方法来进行分析推理,可以把这里的call都换成mycall

    function mycall(fn2){
        fn2['this'] = this;//this----->fn1.mycall.mycall.mycall
        let result = fn2['this']();//fn1.mycall.mycall.mycall()
        //注意!!!:此时fn1.mycall.mycall.mycall里的this变成了fn2
        return result;
    }

  此时fn1.mycall.mycall.mycall继续执行:

    //经过上一步,此时的this指向是fn2,并且括号里没有参数,所以是在window上添加属性
    function mycall(){
        window['this'] = this;//this----->fn2
        let result = window['this']();//fn2()---->所以结果是2
    //这里的this变成了window
        return result;
    }

  经过以上步骤可以得出最后的结果是2

  我们可以通过上面的过程推导出一个结论:fn1.call.call…(n个call).call(fn2)最后都会变成fn2(),而且此时的this指向是window,所以为了确保最后的this指向是window,我们可以完善这一公式:fn1.call.call…(n个call).call(fn2)---->fn2.call(),所以下面两题我们就可以直接使用这个公式了。

  1. Function.prototype.call(fn2)

  这题是把this指向改成fn2,并且执行函数Function.prototype,Function.prototype是Function的原型对象,但它的本质是一个匿名空函数,这里不做过多阐述,我们可以打印试验一下,打印的结果就是一个空函数,所以这题的答案是空的,什么也没有。

  1. Function.prototype.call.call.call.call(fn2)

  这题可以直接用第二题得出的结论来解答,所以这题就演变成fn2.call(),答案是2。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值