Javascript方法call、apply、bind的解析与实现

1 this的指向

this的指向,始终坚持一个原理:this永远指向最后调用它的那个对象。下面我们来看几个简单的例子:

例1:在下面的例子中,非严格模式下,this.name打印出的是橘猫吃不胖,因为最后调用fun()的地方是全局对象window,相当于window.fun(),所以this指向了Window

var name = "橘猫吃不胖";
function fun() {
    
    
    var name = "小明";
    console.log(this.name); // 橘猫吃不胖
    console.log("fun内this:", this); // fun内this:Window
}
fun();
console.log("fun外this:" + this); // fun外this:Window

例2:在下面这个例子中,函数fun是被对象obj调用的,所以this的指向就是objthis.name就是对象obj中的name的值。

var name = "橘猫吃不胖";
var obj = {
    
    
    name: "小明",
    fun: function () {
    
    
        console.log(this.name); // 小明
    }
}
obj.fun();

例3:下面的例子中,虽然对象obj中的fun函数被赋值给了a,但是调用函数a()的是全局对象Window,因此this.name就是橘猫吃不胖

var name = "橘猫吃不胖";
var obj = {
    
    
    name: "小明",
    fun: function () {
    
    
        console.log(this.name); // 橘猫吃不胖
    }
}
var a = obj.fun;
a();

例4:在箭头函数中,this引用的是定义箭头函数的上下文。下面的例子中,箭头函数是在Window上定义的,因此箭头函数的this会保留定义该函数的上下文。

let name = "橘猫吃不胖";
let obj = {
    
    
    name: "小明"
};
let fun = () => console.log(this.name);
fun(); //橘猫吃不胖
obj.fun = fun;
obj.fun(); // 橘猫吃不胖

所以this指向的场景总结如下:

1、全局单独使用的this,与普通函数中的this,都指向全局对象Window,严格模式下,this指向undefined
2、调用对象中的方法时,this指向该对象;
3、构造函数中的this,指向了该构造函数的实例对象;
4、箭头函数中的this,指向了定义箭头函数的上下文;

2 如何改变this的指向

使用callapplybind函数可以改变this的指向。

下面会详细说明这三个函数的运用与实现。

3 call

3.1 使用方式

call()方法接收一些参数:第一个参数是函数内this的值,剩下的参数是其他的参数。它以指定的this值调用函数,即设置调用函数时函数体内this对象的值。用法如下:

函数.call(this, 参数1, 参数2, 参数3......);

例如:

fun.call(this, 10, 20, 30);

示例代码:

let obj = {
    
     num: 10 };
function sum(num1, num2) {
    
    
    console.log(this.num + num1 + num2);
}
sum.call(obj, 10, 20); // 40

在上面的例子中,sum使用了call方法,使this从原本的Window变成了obj对象,那么sum函数内的this.num就是obj.num就是10,因此最后输出40。

3.2 call的实现

首先,我们在调用call方法时,是直接在函数上调用的,说明call方法定义在Function的原型对象上,第一个参数是其内部this指向的对象,其余参数用rest参数接收:

// obj是内部this要指向的对象
Function.prototype.myCall = function (obj, ...args) {
    
    
    // ...
}

接下来,判断调用该方法的是不是一个函数,如果不是函数,应该报错:

// obj是内部this要指向的对象
Function.prototype.myCall = function (obj, ...args) {
    
    
    // 如果调用该方法的不是函数,即this不是function,报错
    if (typeof this !== "function") return new TypeError("TypeError");
}

判断是否传入了obj,如果没有传入,应该让其指向Window

// obj是内部this要指向的对象
Function.prototype.myCall = function (obj, ...args) {
    
    
    // 如果调用该方法的不是函数,即this不是function,报错
    if (typeof this !== "function") return new TypeError("TypeError");
    // 如果没有传入obj,那么this要指向Window
    obj = obj ? obj : window;
}

设置result存放最终的结果,给obj定义fun函数,使其等于this,也就是调用myCall方法的函数,再执行obj.fun,并传入参数args,结果赋值给result

// obj是内部this要指向的对象
Function.prototype.myCall = function (obj, ...args) {
    
    
    // 如果调用该方法的不是函数,即this不是function,报错
    if (typeof this !== "function") return new TypeError("TypeError");
    // 如果没有传入obj,那么this要指向Window
    obj = obj ? obj : window;
    // 存放最终的结果
    let result = null;
    // 将调用myCall的函数赋值给obj.fun
    obj.fun = this;
    // 调用obj.fun,使this指向obj
    result = obj.fun(...args);
}

最后,我们将obj上新增的方法fun删去,返回结果result即可,整体实现代码如下:

// obj是内部this要指向的对象
Function.prototype.myCall = function (obj, ...args) {
    
    
    // 如果调用该方法的不是函数,即this不是function,报错
    if (typeof this !== "function") return new TypeError("TypeError");
    // 如果没有传入obj,那么this要指向Window
    obj = obj ? obj : window;
    // 存放最终的结果
    let result = null;
    // 将调用myCall的函数赋值给obj.fun
    obj.fun = this;
    // 调用obj.fun,使this指向obj
    result = obj.fun(...args);
    //删除obj上新增的方法fun
    delete obj.fun;
    // 返回结果
    return result;
}

4 apply

4.1 使用方式

apply()call()的不同之处在于第二个参数的形式。

apply()方法接收两个参数:函数内this的值和一个参数数组。第二个参数可以是数组的字面量,也可以是Array的实例对象,但也可以是arguments对象。它也是以指定的this值调用函数,即设置调用函数时函数体内this对象的值。用法如下:

函数.apply(this, Array);

例如:

fun.apply(this, ["张三", "李四"]); // 第二个参数是数组字面量表示方式
fun.apply(this, new Array("张三", "李四")); // 第二个参数是Array的实例对象

示例代码:

let obj = {
    
     num: 10 };
function sum(num1, num2) {
    
    
    console.log(this.num + num1 + num2);
}
sum.apply(obj, [10, 20]); // 40

在上面的例子中,sum使用了apply方法,使this从原本的Window变成了obj对象,那么sum函数内的this.num就是obj.num就是10,因此最后输出40。

4.2 apply的实现

同样,调用apply方法时,是直接在函数上调用的,说明apply方法定义在Function的原型对象上,第一个参数是其内部this指向的对象,第二个参数用args参数接收:

// obj是this需要指向的对象
Function.prototype.myApply = function (obj, args) {
    
    
    // ...
}

判断是否是函数调用了myApply,不是则报错:

// obj是this需要指向的对象
Function.prototype.myApply = function (obj, args) {
    
    
    // 如果不是函数调用myApply,报错提示
    if (typeof this !== "function") return new TypeError("TypeError");
}

判断是否传入了obj,如果没有传入,应该让其指向Window

// obj是this需要指向的对象
Function.prototype.myApply = function (obj, args) {
    
    
    // 如果不是函数调用myApply,报错提示
    if (typeof this !== "function") return new TypeError("TypeError");
    // 是否传入了obj,不是则指向window
    obj = obj ? obj : window;
}

设置result存放最终的结果,给obj定义fun函数,使其等于this,也就是调用myApply方法的函数,再执行obj.fun,并传入参数args,结果赋值给result

// obj是this需要指向的对象
Function.prototype.myApply = function (obj, args) {
    
    
    // 如果不是函数调用myApply,报错提示
    if (typeof this !== "function") return new TypeError("TypeError");
    // 是否传入了obj,不是则指向window
    obj = obj ? obj : window;
    // 存放最终的结果
    let result = null;
    // 将调用myApply的函数this赋值给obj.fun
    obj.fun = this;
    // 调用obj.fun,使this指向obj
    result = obj.fun(...args);
}

最后,我们将obj上新增的方法fun删去,返回结果result即可,整体实现代码如下:

// obj是this需要指向的对象
Function.prototype.myApply = function (obj, args) {
    
    
    // 如果不是函数调用myApply,报错提示
    if (typeof this !== "function") return new TypeError("TypeError");
    // 是否传入了obj,不是则指向window
    obj = obj ? obj : window;
    // 存放最终的结果
    let result = null;
    // 将调用myApply的函数this赋值给obj.fun
    obj.fun = this;
    // 调用obj.fun,使this指向obj
    result = obj.fun(...args);
    // 删掉obj.fun
    delete obj.fun;
    return result;
}

5 bind

5.1 使用方式

bind()方法会创建一个新的函数实例,其this值会被绑定到传给bind()的对象。它的返回值也是方法,接受的第一个参数是一个对象,其余参数会作为新的函数的参数。用法如下:

函数.bind(this, 参数1, 参数2, 参数3......);

例如:

fun.bind(this, "aaa", "bbb", "ccc");

示例代码:

let obj = {
    
     num: 20 };
function num() {
    
    
    console.log(this.num);
    console.log(Math.max(...arguments));
}
let pnum = num.bind(obj, 2, 3, 5, 7, 68, 8);
pnum(); // 20 68

在上面的代码中,num使用了bind方法,但是该函数并未立即被调用,而是使其this从原本的Window变成了obj对象,返回了一个函数并赋值给了pnum,所以调用pnum函数,this.num就是obj.num就是20,其余的参数执行后会输出68。

5.2 bind的实现

调用bind方法时,是直接在函数上调用的,说明bind方法定义在Function的原型对象上,第一个参数obj是其内部this指向的对象,第二个参数用args参数接收:

Function.prototype.myBind = function (obj, ...args) {
    
    
    // ...
}

判断是否是函数调用了myBind,不是则报错:

Function.prototype.myBind = function (obj, ...args) {
    
    
    // 如果不是函数调用了myBind,则报错
    if (typeof this !== "function") return new TypeError("TypeError");
}

判断是否传入了obj,如果没有传入,应该让其指向Window

Function.prototype.myBind = function (obj, ...args) {
    
    
    // 如果不是函数调用了myBind,则报错
    if (typeof this !== "function") return new TypeError("TypeError");
    // 判断是否传入了obj,如果没有传入,应该让其指向Window
    obj = obj ? obj : window;
}

将调用myBind的函数赋值给fun,并在myBind函数最后返回一个函数,该函数就是fun使用了apply方法:

Function.prototype.myBind = function (obj, ...args) {
    
    
    // 如果不是函数调用了myBind,则报错
    if (typeof this !== "function") return new TypeError("TypeError");
    // 判断是否传入了obj,如果没有传入,应该让其指向Window
    obj = obj ? obj : window;
    // 将调用myBind的函数存放起来
    let fun = this;
    // 返回一个函数,并为该函数预留出传参数的空间
    return function Fn(...rest) {
    
    
        return fun.apply(obj, args.concat(rest));
    }
}

6 call、apply、bind的区别

callapply的区别:

call方法接受的是若干个参数列表,而apply接收的是一个包含多个参数的数组

bindapplycall区别:

1、bind不会立即调用函数,callapply会立即调用
2、bindcall接收的参数形式相同,第一个都是this要指向的对象,其余的参数是其他参数,apply第一个参数是this要指向的对象,第二个参数是数组

猜你喜欢

转载自blog.csdn.net/m0_46612221/article/details/127955024