手写 实现call、apply和bind方法 超详细!!!

不了解call、apply和bind方法的可以参考前面写的这篇文章:call、apply和bind方法的用法、区别和使用场景_前端圆圆-CSDN博客 https://blog.csdn.net/weixin_45844049/article/details/117926588

实现call

举一个使用call方法的例子:

var foo = {
    value: 1
};

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

bar.call(foo); // 1

注意两点:

  • call 改变了 this 的指向,指向到 foo
  • bar 函数执行了

第一步

我们该怎么模拟实现这两个效果呢?

试想当调用 call 的时候,把 foo 对象改造成如下:

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

foo.bar(); // 1

这个时候 this 就指向了 foo,是不是很简单呢?

但是这样却给 foo 对象本身添加了一个属性,这可不行呐!

不过也不用担心,我们用 delete 再删除它不就好了~

所以我们模拟的步骤可以分为:

  • 将函数设为对象的属性
  • 执行该函数
  • 删除该函数

以上个例子为例,就是:

// 第一步
foo.fn = bar
// 第二步
foo.fn()
// 第三步
delete foo.fn

fn 是对象的属性名,反正最后也要删除它,所以起成什么都无所谓。

根据这个思路,我们可以尝试着去写第一版的 call2 函数:

// 第一版
Function.prototype.call2 = function(context) {
    // 首先要获取调用call的函数,用this可以获取
    context.fn = this;//this指向的是使用call方法的函数(Function的实例,即下面测试例子中的bar方法)
    context.fn();
    delete context.fn;
}

// 测试一下
var foo = {
    value: 1
};

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

bar.call2(foo); // 1

正好可以打印 1 !接着往下走~

第二步

实现可传参

一开始也讲了,call 函数还能给定参数执行函数。举个例子:

var foo = {
    value: 1
};

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

bar.call(foo, 'kevin', 18);
// kevin
// 18
// 1

注意:传入的参数并不确定,这可咋办?

不急,我们可以从 Arguments 对象中取值,取出第二个到最后一个参数,然后放到一个数组里。这里我们就可以把 Arguments 对象解构到数组里,再用slice方法取第二个到最后一个参数。

第二版代码如下:

// 第二版
Function.prototype.call2 = function(context) {
    // 首先要获取调用call的函数,用this可以获取
    context.fn = this;//this指向的是使用call方法的函数(Function的实例,即下面测试例子中的bar方法)
    let rest = [...arguments].slice(1);//用slice方法取第二个到最后一个参数(获取除了this指向对象以外的参数), 空数组slice后返回的仍然是空数组
    let result = context.fn(...rest); //隐式绑定,当前函数的this指向了context.
    delete context.fn;
}

// 测试一下
var foo = {
    value: 1
};

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

bar.call2(foo, 'kevin', 18); 
// kevin
// 18
// 1

第三步

模拟代码已经完成 80%,还有两个小点要注意:
1.this 传null或undefined时,将是JS执行环境的全局变量。浏览器中是window,其它环境(如node)则是global。

2.函数是可以有返回值的!

实现call方法的最终版代码:

Function.prototype.call2 = function (context) {
	// 判断传入的this,为null或者是undefined时要赋值为window或global
    if (!context) {
        //context为null或者是undefined
        context = typeof window === 'undefined' ? global : window;
    }
    // 获取调用call的函数,用this可以获取
    context.fn = this; this指向的是使用call方法的函数(Function的实例,即下面测试例子中的bar方法)
    let rest = [...arguments].slice(1);//获取除了this指向对象以外的参数, 空数组slice后返回的仍然是空数组
    let result = context.fn(...rest); //隐式绑定,当前函数的this指向了context.
    delete context.fn;
    return result;
}

//测试代码
var foo = {
    name: 'Selina'
}
var name = 'Chirs';
function bar(job, age) {
    console.log(this.name);
    console.log(job, age);
}
bar.call2(foo, 'programmer', 20);
// Selina 
// programmer 20
bar.call2(null, 'teacher', 25);
// undefined
// teacher 25

实现apply

apply的实现和call很类似,但是需要注意他们的参数是不一样的,apply的第二个参数是数组或类数组.

Function.prototype.apply = function (context, rest) {
    if (!context) {
        //context为null或者是undefined时,设置默认值
        context = typeof window === 'undefined' ? global : window;
    }
    context.fn = this;
    let result;
    if(rest === undefined || rest === null) {
        //undefined 或者 是 null 不是 Iterator 对象,不能被 ...
        result = context.fn(rest);
    }else if(typeof rest === 'object') {
        result = context.fn(...rest);
    }
    delete context.fn;
    return result;
}


// 测试代码
var foo = {
    name: 'Selina'
}
var name = 'Chirs';
function bar(job, age) {
    console.log(this.name);
    console.log(job, age);
}
bar.apply(foo, ['programmer', 20]);
// Selina programmer 20
bar.apply(null, ['teacher', 25]);
// 浏览器环境: Chirs programmer 20; node 环境: undefined teacher 25

实现bind

在实现bind之前,先介绍了解一下bind:

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。(来自于 MDN )

实现bind需要注意的两个特点:

  • bind 会创建一个新函数,不会立即执行
  • bind 后面传入的这个参数列表可以分多次传入,call和apply则必须一次性传入所有参数。

详细步骤和解析全部放在代码里,最终实现bind方法的代码:

Function.prototype.bind2 = function (context) {
    if (typeof this !== "function") {
        throw new TypeError("not a function");
    }
    let self = this;
    // 这里的arguments是使用bind方法时传入的参数列表,即bind方法第一个传参的arguments
    let args = [...arguments].slice(1);

    function Fn() {};
    Fn.prototype = this.prototype;
    let bound = function () {
        // bind方法返回一个函数,并且这个函数可以继续传参,此时定义的bound方法就是最后bind返回的方法,
        // 所以bound里的arguments就是新方法执行时传的参数列表
        let res = [...args, ...arguments]; //bind传递的参数和函数调用时传递的参数拼接
        /* 
        对三目运算符两种情况的解释:
        1.当作为构造函数时,this 指向实例(注意!!!这里的this是bind返回的新方法里执行时的this,
        和上面的this不是一个!!!),Fn 为绑定函数,因为上面的 `Fn.prototype = this.prototype;`,
        已经修改了 Fn.prototype 为 绑定函数的 prototype,此时结果为 true,
        当结果为 true 的时候,this 指向实例。
        
        2.当作为普通函数时,this 指向 window,Fn 为绑定函数,此时结果为 false,
        当结果为 false 的时候,this 指向绑定的 context。 */
        context = this instanceof Fn ? this : context || this;
        // 我们用apply来指定this的指向
        return self.apply(context, res);
    }
    //原型链
    bound.prototype = new Fn();
    return bound;
}



var name = 'Jack';

function person(age, job, gender) {
    console.log(this.name, age, job, gender);
}
var Yve = {
    name: 'Yvette'
};
let result = person.bind2(Yve, 22, 'enginner')('female');
// Yvette 22 enginner female

参考

  • Function.prototype.bind() - JavaScript | MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
  • https://juejin.cn/post/6844903476477034510
  • https://www.cnblogs.com/snandy/archive/2012/03/01/2373243.html
  • https://juejin.cn/post/6844903476623835149
  • 24
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
好的,我可以帮你这三个方法并解析。 1. Call 方法 Call 方法是 JavaScript 中函数对象的一个方法,它可以在指定的 this 值和参数列表的前提下,调用一个函数。 Call 方法的基本思路是: - 把当前函数设为指定的 this 值; - 执行当前函数,并把参数列表传递进去; - 返回当前函数执行的结果。 下面是一个简单的实现: ```javascript Function.prototype.myCall = function(context, ...args) { // 首先要判断传入的 context 是否为 null 或 undefined,如果是,那么 this 指向全局对象 window context = context || window; // 将当前函数设为传入的 context 的方法 context.fn = this; // 执行当前函数,并将参数列表传递进去 let result = context.fn(...args); // 删除当前函数作为 context 的方法 delete context.fn; // 返回当前函数执行的结果 return result; }; ``` 使用示例: ```javascript function greet(name) { console.log(`Hello, ${name}! My name is ${this.name}.`); } const person = { name: 'John' }; greet.myCall(person, 'Alice'); // 输出:Hello, Alice! My name is John. ``` 2. Apply 方法 Apply 方法和 Call 方法类似,只不过它的参数列表是一个数组或类数组对象。 Apply 方法的基本思路是: - 把当前函数设为指定的 this 值; - 执行当前函数,并把参数列表传递进去; - 返回当前函数执行的结果。 下面是一个简单的实现: ```javascript Function.prototype.myApply = function(context, args) { // 首先要判断传入的 context 是否为 null 或 undefined,如果是,那么 this 指向全局对象 window context = context || window; // 将当前函数设为传入的 context 的方法 context.fn = this; // 执行当前函数,并将参数列表传递进去 let result = context.fn(...args); // 删除当前函数作为 context 的方法 delete context.fn; // 返回当前函数执行的结果 return result; }; ``` 使用示例: ```javascript function add(a, b) { return a + b; } const numbers = [1, 2]; console.log(add.myApply(null, numbers)); // 输出:3 ``` 3. Bind 方法 Bind 方法会创建一个新的函数,它的 this 值被绑定到传入的参数上。新函数被调用时,它的 this 值将永久地被绑定到传入的参数上,不会再被改变。 Bind 方法的基本思路是: - 把当前函数保存起来,以便后面返回一个新的函数; - 返回一个新的函数,这个新函数的 this 值被绑定到传入的参数上; - 当新函数被调用时,它会调用原函数,并把传入的参数和绑定的 this 值一起传递进去。 下面是一个简单的实现: ```javascript Function.prototype.myBind = function(context, ...args1) { // 保存当前函数 const fn = this; // 返回一个新的函数 return function(...args2) { // 调用原函数,并绑定 this 值和参数列表 return fn.call(context, ...args1, ...args2); }; }; ``` 使用示例: ```javascript const person = { name: 'John', greet(name) { console.log(`Hello, ${name}! My name is ${this.name}.`); } }; const greet = person.greet.myBind({ name: 'Alice' }, 'Bob'); greet('Charlie'); // 输出:Hello, Bob! My name is Alice. ``` 以上就是 Call、ApplyBind 方法的基本实现和解析。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值