JavaScript | 手写call(面试篇)

3,051 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情

手写call

1.call用法

var obj ={name:'lucky'}
function fn(a,b){
console.log(this,a+b) //obj,3
}
fn.call(obj,1,2)

2. 手写call

分析

  • 通过原型链我们知道调用call的肯定是函数
  • 执行当前的函数
  • 改变this指向

1.通过这三句就能写出核心代码(简易)

//首先在函数拓展出一个手写call方法
Function.prototype.myCall = function(context){
  //改变this指向 
  /**
  1.this就是fn1函数
  2.context.fn = this 相当于把fn1函数的地址赋值给context.fn的一个随意属性
  3.context.fn()就相当于把this指向了传进来的值context
  核心:把this(fn1)变成context(调用者)的一个方法,那么fn1的调用自然就变成了
      context的调用达到了改变this指向的作用
  **/
  context.fn = this
  context.fn();
  //调用完删除拓展属性
  delete context.fn
}
var obj ={name:'lucky'}
function fn1(a,b){
  console.log(this) //obj
}
fn1.myCall(obj)

2. 完整写法

  1. 因为call方法会给调用者自动包装类型,比如null、undefined、String、number的调用,但是我们自己写的方法就需要做判断了。
var obj ={name:'lucky'}
function fn(a,b){
  console.log(this,a+b) //obj,3
}
fn.call(null,1,2) //window
fn.call(undefined,1,2) //window
fn.call(true,1,2) //Boolean
fn.call(1,1,2) //Number
fn.call('1',1,2) //String

自动包装.png

  1. 考虑参数的处理 通过eval处理参数
   var arr = [2, 3, 4, 5];
        console.log(arr.toString())

        function fn() {
            console.log(arguments);
        }

        // fn("2,3,4,5")
        //eval可以把字符串转换成正常代码运行,所以只需要拼接'fn(1,2,3,4)'这样的字符串即可
        eval("fn(1,2,3,4)")

参数.png

  1. 最后是完整写法
Function.prototype.myCall = function (context) {
        var type = typeof context;
        //判断改变后的上下文对象是null和undefined的时候 this应该指向window
        if (context === null || context === undefined) {
            context = window;
        }
        //如果改变后的上下文对象是基本包装类型,则this指向其包装对象
        switch (type) {
            case "number":
                context = new Number(context);
                break;
            case "boolean":
                context = new Boolean(context);
                break;
            case "string":
                context = new String(context);
                break;
        }
        //获取第二个以后的参数,就是fn的参数
        var arg = Array.from(arguments).slice(1);
   /**
    梳理:
       1.fn1.myCall调用,所以这里的this指向的就是fn1,
        给context扩展一个方法,这个方法就是fn1:context[key] = this;
       2.context就是改变之后的上下文对象
       3.然后调用context的扩展的这个方法,fn1就会被调用,并且this指向了context
   **/

    //给context扩展的方法名要是一个独一无二的值,防止覆盖原有方法
    var key = Date.now().toString(36);
    context[key] = this;
    //然后调用context的扩展的这个方法,fn1就会被调用,并且this指向了context
    var result = eval("context[key](" + arg.toString() + ")");
    //此时改变之后的上下文对象context就会多一个方法,使用完成之后要删除掉这个方法
    delete context[key];

     return result;
    }

    var obj = {
        name: "lucky",
        fn: function () {
            console.log("hello")
        }
    }

    function fn1(a, b) {
        console.log(this, a + b)
        return "123";
    }

    console.log(fn1.call(obj, 1, 2));

    fn1.myCall(obj, 1, 2);
    fn1.myCall(null, 1, 2);
    fn1.myCall(undefined, 1, 2);
    fn1.myCall(1, 1, 2);
    fn1.myCall("str", 1, 2);
    fn1.myCall(true, 1, 2);

验证结果

验证结果.png

好了,以上就是本篇文章的分享,感谢阅读!