一文带你了解call、apply和bind——强制改变this方法


前言

本文不再说明他们的具体用法,直接说明他们的内部实现原理。

一、call的实现

思考一个问题:我们在执行call方法时总是是对某函数调用该方法,既然想让每个函数都有该方法,所以call这个方法应该写到它的原型上,即Function.prototype.call=function(){},那我们又知道call的作用是改变this指向,这个原理是怎么样的呢 我们先进行如下尝试,看看this究竟是什么

function person()
{
    console.log(this.name)
}
var egg={name:'123'}
Function.prototype.mycall=function(obj)
{
    console.log(this) 
}
person.mycall(egg)

在这里插入图片描述
可以看到我们若不对this指向进行修改,则它依旧指向的是函数本身(this指向这个函数意思也就是它和这个函数值相同!!!),而我们的目的是将this指向obj这个参数,那么怎么改this呢?我们知道this总是指向最后调用它的对象,所以我们要通过obj去调用一下person方法,就可以成功让this指向obj然后再删除这个调用。也就完成了this的显示绑定。

1.初实现:

记住this总是指向最后调用它的那个对象!!!

Function.prototype.myCall=function(obj)
{
    console.log(this) //谁调用myCall谁就是this
    obj.fn=this	//令这个obj的一个属性=this
    obj.fn()//通过调用(执行)绑定this到obj上
    delete obj.fn //它的使命完成了 删除即可
}

2.进一步完善

我们还要在这个初级版本进一步考虑各种情况然后完善。
考虑1:若调用myCall的对象不为函数时怎么办?抛出错误就好啦。
考虑2:若不为它传参数怎么解决?this即为window。

Function.prototype.mycall=function(obj)
{
    if( typeof obj=='function')
    {
        throw new TypeError('not a function')
    }
    obj=obj||window
    obj.fn=this
    obj.fn()
    delete obj.fn
}

3.终极实现

但是我们还要考两个问题:
一是参数的问题,因为call方法第一个参数为this要指向的对象,第2-n个参数为要传给function的参数。2-n参数用…剩余参数以数组模式传入,然后调用这个函数时解构传入就好啦。
二是返回值,调用call函数默认是返回该函数的执行结果的,所以要保存一下执行结果并返回。

Function.prototype.myCall = function(context, ...args) {
  // 如果context是null或undefined,设置为全局对象
  // 如果context是一个基本类型,将其包装成对象
  context = context == null ? globalThis : Object(context);

  // 为了避免与context上已有的属性冲突,使用一个独一无二的key
  const uniqueKey = Symbol('key');

  // 将当前函数作为context的一个方法
  context[uniqueKey] = this;

  // 调用这个方法,并传入args
  const result = context[uniqueKey](...args);

  // 删除这个方法
  delete context[uniqueKey];

  // 返回结果
  return result;
};

至此,call方法就已经完整实现了一遍。

二、apply的实现

apply和call的区别只是传参不同,apply第二个参数为一个数组,而call第2-n个参数类型不确定,其余都一样,所以在来试试叭~

Function.prototype.myApply = function(context, args) {
  // 如果context是null或undefined,设置为全局对象
  // 如果context是一个基本类型,将其包装成对象
  context = context == null ? globalThis : Object(context);

  // 如果args不是一个数组或类似数组的对象,抛出一个错误
  if (typeof args !== 'undefined' && (typeof args !== 'object' || args === null)) {
    throw new TypeError('CreateListFromArrayLike called on non-object');
  }

  // 为了避免与context上已有的属性冲突,使用一个独一无二的key
  const uniqueKey = Symbol('key');

  // 将当前函数作为context的一个方法
  context[uniqueKey] = this;

  // 调用这个方法,并传入args
  const result = context[uniqueKey](...(args || []));

  // 删除这个方法
  delete context[uniqueKey];

  // 返回结果
  return result;
};

三、call、apply总结

所以说call和apply的内部过程就是:
1.给想要绑定的对象设置一个属性,且指向this(即需要调用的对象)
2.通过该对象调用该函数
3.结束调用后删除该属性

四、bind的实现

先来说一下它的实现原理,我们知道bind是返回一个函数但是不执行,什么时候调用什么时候执行,此外,bind有两种运用场景,一是普通函数调用、二是构造函数调用,所以还要判别一下调用它的函数类型。
实现流程大概如下:
bind实现思路:
判断是否是函数调用,若非函数调用抛异常
返回函数。
判断函数的调用方式,是否是被new出来的
new出来的话返回空对象,是new出来的话实例的__proto__指向_this的prototype。
不是则直接绑定this到第一个参数传入的对象上。

Function.prototype.myBind = function(context, ...args) {
  const self = this; // 保存当前函数的引用

  // 创建一个中间函数,用于处理原型链
  function Temp() {}

  // 创建一个新的绑定函数
  function BoundFunction(...innerArgs) {
    // 如果BoundFunction作为构造函数被调用,则使用新创建的对象作为this值
    // 否则,使用指定的context作为this值
    const isCalledAsConstructor = this instanceof BoundFunction;
    const thisArg = isCalledAsConstructor ? this : context;

    // 调用原函数,并将thisArg作为this值,同时合并外部参数和内部参数
    const result = self.call(thisArg, ...args, ...innerArgs);

    // 如果原函数返回了一个对象,那么将其作为绑定函数的返回值
    // 否则,如果绑定函数作为构造函数被调用,将新创建的对象作为返回值
    // 否则,返回原函数的返回值
    return (typeof result === 'object' && result !== null) || typeof result === 'function'
      ? result
      : isCalledAsConstructor
      ? thisArg
      : undefined;
  }

  // 设置中间函数的原型为原函数的原型
  Temp.prototype = self.prototype;

  // 设置绑定函数的原型为中间函数的实例
  // 这样,绑定函数的实例将能够访问原函数的原型链
  BoundFunction.prototype = new Temp();

  // 返回绑定函数
  return BoundFunction;
};

或者直接用apply

Function.prototype.myBind = function(context, ...args) {
  const self = this; // 保存当前函数的引用

  // 返回一个新的函数
  return function(...innerArgs) {
    // 调用原函数,并将context作为this值,同时合并外部参数和内部参数
    return self.apply(context, [...args, ...innerArgs]);
  };
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值