手动实现call、apply、bind

本文深入解析JavaScript中的call, apply和bind方法的实现原理及应用场景,包括ES3和ES6的不同实现方式,以及如何通过这些方法改变函数内部的this指向。

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

call的实现:

思路很简单:

  • 1、将函数设置为对象的属性:obj.fn = func
  • 2、执行函数:obj.fn()
  • 3、删除函数:delete obj.fn

注意的地方:

  • 4、this 参数可以传 null 或者 undefined,此时 this 指向 window
  • 5、this 参数可以传基本类型数据,原生的 call 会自动用 Object() 转换
  • 6、函数是可以有返回值的

思路的来源:

var obj = {
    value: 2,
    fn: function() {
        console.log(this.value);
    }
};

obj.fn(); // 2

(1)es3的实现:

Function.prototype.call2 = function(context) {
    context = context ? Object(context) : window;
    context.fn = this;//用this获取调用call2的函数,即func
    var args = [];
    var len = arguments.length;
    var result;
    for(var i = 1; i < len; i++) {
        args.push('arguments[' + i + ']'); // arguments[1] = 'kevin' arguments[2] = 18
    }
    // console.log(args);['arguments[1]','arguments[2]']
    result = eval('context.fn(' + args +')');//context.fn(args)
    console.log(result);
    delete context.fn;
    return result;
}
// 测试一下
var value = 2;
var obj = {
    value: 1
};
function func(name, age) {
    console.log(name);
    console.log(age);
    console.log(this.value);
}
func.call2(null); //2
func.call2(obj, 'kevin', 18);

 example:

Function.prototype.call2 = function (context) {
    context = context ? Object(context) : window;
    context.fn = this;//将函数设置为对象的属性,也可以写成context.fn = func;用this获取调用call2的函数
    var result = context.fn();//执行函数
    delete context.fn;//删除函数
    return result;//将函数执行结果返回
}

// 测试一下
function func() {
    console.log(this);

}
func.call2(123); // Number {123, fn: ƒ}

(2):es6的实现:

Function.prototype.call2 = function(context) {
    context = context ? Object(context) : window;
    context.fn = this;//context.fn = func;获取调用call2的函数
    let args = [...arguments].slice(1);
    let result = context.fn(...args);
    delete context.fn;
    return result;
}
// 测试一下
let value = 1;
let obj = {
    value: 2
};
function func(name, age) {
    console.log(name);
    console.log(age);
    console.log(this.value);
}
func.call2(null); //1
func.call2(obj, 'kevin', 18);// 'kevin' 18 2

call和apply的区别:

call()是挨个传递过去,而apply()是以数组的形式传递  /  arguments类数组对象

 

apply的实现:

(1) es3的实现:

Function.prototype.apply = function (context, arr) {
    context = context ? Object(context) : window;
    context.fn = this;
    var result;
    // 判断是否存在第二个参数
    if (!arr) {
        result = context.fn();
    } else {
        var args = [];
        var len = arr.length;
        for (var i = 0; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')');
    }
    delete context.fn;
    return result;
}

(2)es6的实现:

Function.prototype.apply1 = function (context, arr) {
    context = context ? Object(context) : window;
    context.fn = func;
    let result;
    if (!arr) {//判断是否存在第二个参数
        result = context.fn();
    } else {
        result = context.fn(...arr);
    }
    delete context.fn;
    return result;
}
// 测试一下
let value = 1;
let obj = {
    value: 2
};
function func(name, age) {
    console.log(name);
    console.log(age);
    console.log(this.value);
}
func.apply1(null); //1
func.apply1(obj, ['kevin',18]);// 'kevin' 18 2

bind的es3实现:

首先我们来实现以下四点特性:

  • 1、可以指定this
  • 2、返回一个函数
  • 3、可以传入参数
  • 4、柯里化

什么是柯里化?

调用函数的时候只传递一部分参数,让它返回的函数再去处理其他的参数

var add = function(x) {
  return function(y) {
    return x + y;
  };
};

var increment = add(1);
var addTen = add(10);

increment(2);
// 3

addTen(2);
// 12

add(1)(2);
// 3

这里定义了一个 add 函数,它接受一个参数并返回一个新的函数。调用 add 之后,返回的函数就通过闭包的方式记住了 add 的第一个参数。所以说 bind 本身也是闭包的一种使用场景。

 es3实现如下:


Function.prototype.bind2 = function (context) {
     //判断调用bind的是不是函数
    if (typeof this !== "function") {
        throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;//this指向调用者
    // 使用 arguments 获取参数数组,因为第1个参数是指定的this,所以只截取第1个之后的参数
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};//创建一个空对象
    // 直接修改 fBound.prototype 的时候,也会直接修改 this.prototype,所以要创建一个空对象,接着下面的(2)
    var fBound = function () {
        // 这时的arguments是指bind返回的函数传入的参数
        var bindArgs = Array.prototype.slice.call(arguments);
        //将参数合并成一个参数数组,作为apply()的第二个参数
        // 当作为构造函数时,this 指向实例,此时 this instanceof fBound 结果为 true,this指向调用者
        // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;//空对象的原型指向绑定函数的原型(2)
    fBound.prototype = new fNOP();//fBound.prototype作为空对象的实例
    return fBound;
}
var value = 1;

var obj = {
    value: 2
};

function func(name, age) {
    console.log(this.value);
    console.log(name);
    console.log(age);
};

var bindFunc = func.bind2(obj, "Jack");
bindFunc(20);

原文在此:

https://muyiy.vip/blog/3/3.4.html#使用场景

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值