JavaScript之call,apply的模拟实现

173 阅读3分钟

前言

最近在整理回顾之前的知识,故在此记录一下,众所周知,JavsScript中this是在执行上下文创建时确定的一个在执行过程中不可更改的变量,对于this不熟悉的同学可以去:理解Javascript的this。而call,apply,bind的出现则是打破了这一规则,所以本质上来说bind和call,apply的作用类似,都是用来更改函数的this值的,其中call和apply类似,只是传参的不同,前者是若干连续参数,后者则是传入一个数组,而bind则是返回一个函数,这里不多说,接下来我们开始模拟

使用call方法举个例子

以下是用原生的call方法书写的例子,可以看到bar函数的this指向foo对象,而且可以传递参数,也就是说,call干了两件事:

1.改变函数的this指向

2.传参执行函数

var foo = {
    value: 1
};

function bar(n = 0) {
    console.log(this.value, n);
}

bar.call(foo); // 1,0
bar.call(foo, 6); // 1,6

模拟第一步

试想我们要是想得出上一个例子的结果,可以直接在foo对象上挂载一个bar函数,也就是如下示例:

var foo = {
    value: 1,
    bar: function (n = 0) {
        console.log(this.value, n)
    }
};

foo.bar(6); // 1,6

也就是说我们把这个方法赋值给对象,然后对象调用这个函数就可以了。总结:

1.将this要指向的函数赋值给传入的对象,

2.执行此函数,

3.执行之后从对象上删除这个函数

那我们就可以实现第一版了,示例如下:

var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}
Function.prototype._call = function (obj) {
    // this 指的是调用_call的函数
    console.log(this);
    // obj.fun = this
    obj.fun()
    delete obj.fun
}
bar._call(foo); //1

测试之后发现可以打印1,nice,简直easy啊,别急,开头咱们说了原生call是可以传参的,说干就干

模拟第二步

var foo = {
    value: 1
};

function bar(n = 0) {
    console.log(this.value, n);
}

Function.prototype._call = function (obj, ...args) {
    // console.log(args);
    // this 指的是调用_call的函数
    // console.log(this);
    obj.fun = this
    obj.fun(...args)
    delete obj.fun
}

bar._call(foo, 6);//1,6

因为是可以传入不定长度的参数的,故我使用的是es6的...args剩余参数,可以直接取到全部参数。这就完事了?桥的马得,call还有一个特点是第一个参数为null时,this指向window,并且函数时有返回值的哦。 继续往下看。

模拟第三步

先举个原生call的例子

// 被调用的函数可能有返回值
var foo = {
    value: 1
};

function bar(n = 0) {
    console.log(this.value, n);
    return 123
}
let a = bar.call(foo)
console.log(a);  // 123

//传入null时指向window
  var value = 99;
  var foo = {
     value: 1,
  };

  function bar() {
     console.log(this.value);
   }

  bar.call(null); //99

接下来咱们就开始最后的修改啦,最后代码如下:

var foo = {
    value: 1
};

function bar(n = 0) {
    console.log(this.value, n);
    return 123
}
Function.prototype._call = function (obj, ...args) {
    //传入null/undefined时this指向window
    if (obj === null || obj === undefined) {
        obj = window;
    } else {
        obj = Object(obj) || obj;  //检验类型是否是对象
    }
    // console.log(args);
    // this 指的是调用_call的函数
    // console.log(this);
    obj.fun = this;
    const res = obj.fun(...args);
    delete obj.fun;
    return res
}

这就是我们的最终代码,此时

bar._call(obj,1) //1 1

let a = bar._call(foo, 1)
console.log(a); // 123

bar._call(2,1) //NaN 1

至此,我们完成了 call 的模拟实现,给自己一个赞!!!

apply的模拟实现

apply和call类似,只是传递参数的不同,apply是传递数组,我们改下_call方法

Function.prototype._apply = function (obj, arr) {
        if (obj === null || obj === undefined) {
          obj = window;
        } else {
          obj = Object(obj) || obj;
        }
        obj.fun = this;
        arr = arr || [];
        const res = obj.fun(...arr);
        delete obj.fun;
        return res;
      };

最后

我们实现过程都解决了以下问题:

1.更改被调用函数的this

2.传递参数给被调用函数

3.返回被调用函数的结果,第一个参数为null/undefined时,让被调用函数this指向window

4.判断第一个参数的类型

以上。不忘初心,砥砺前行,欢迎各位讨论,找出更好的方法。美女镇楼: