Bootstrap

手写实现call、apply、 bind

目录

什么是call、bind、apply?

call、bind、apply的使用

call()方法

bind()方法

apply()

call、bind、apply的区别

总结

手写call、apply、bind方法

手写call()

手写apply()

手写bind()


什么是call、bind、apply?

callapplybind是Javascript中用于改变普通函数this指向(无法改变箭头函数this指向)的方法,这三个函数实际上都是绑定在function构造函数的prototype上,而每一个函数都是Function的实例,因此每一个函数都可以直接调用call,apply,bind

call、bind、apply的使用

call()方法

语法:function.call(thisArg, arg1, arg2, ...)。其中thisArg是要设置为函数执行上下文的对象,也就是this要指向的对象,从第二个参数开始,arg1,arg2,···是传递给函数的参数。通过使用call方法,可以将一个对象的方法应用到另一个对象上

//定义一个对象
const obj1={
 name:'Alice',
 greet:function(){
   console.log(`Hello, ${this.name}!`);
 }
};

//定义另一个对象
const obj2={
  name:'Bob'
};

//使用call方法将obj1的greet方法应用到obj22上
obj1.greet.call(obj2); // 输出:Hello, Bob!

作用:

1.第一个调用函数,第二个可以改变函数内的this指向

2.call的主要作用可以实现继承

bind()方法

语法:function.bind(thisArg, arg1, arg2, ...)。其中thisArg是要绑定到函数执行上下文的对象,也就是this要指向的对象,从第二个参数开始,arg1, arg2, ...是传递给函数的参数。与call和apply方法不同,bind方法并不会立即执行函数,而是返回一个新函数,可以稍后调用。这对于事件处理程序和setTimeout函数等场景非常有用

function greet(name){
 console.log("Hello,"+name);
}

const delayGreet=greet.bind(null,"John");
setTimeout(delayGreet,2000);//2s后输出,hello,John

作用:

1.不会调用原来的函数,可以改变原来函数内部的this指向

2.返回的是原函数改变this之后产生的新函数

3.如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向此时用bind

apply()

语法:function.apply(thisArg, [argsArray])。 其中thisArg是要设置为函数执行上下文的对象,也就是this要指向的对象,argsArray是一个包含参数的数组。通过使用apply方法,可以将一个对象的方法应用到另一个对象上,并使用数组作为参数。

function greet(name) {
  console.log(`Hello, ${name}!`);
}

const person = { name: 'John' };
greet.apply(person, ['Mary']); // 输出:Hello, Mary!

作用:

1.也是调用函数 第二个可以改变函数内部this的指向

2.但是它的参数必须是数组(伪数组)

3.apply的主要应用 比如我们可以利用apply借助于数学内置对象求最大值

call、bind、apply的区别

1.调用方式:

call:使用函数的call方法可以直接调用函数,并传递参数列表。

bind:使用函数的bind方法可以返回一个新的函数,这个新函数的this值被绑定到指定的对象,但不会立即执行。

apply:使用函数的apply方法可以直接调用函数,并传递参数列表,与call方法类似,但参数需要以数组或类数组的形式传递。

2.参数传递方式:

call:使用call方法时,参数需要一个一个地列举出来,通过逗号分隔。

bind:使用bind方法时,可以传递任意数量的参数,可以在绑定时传递参数,也可以在调用时传递参数。

apply:使用apply方法时,参数需要以数组或类数组的形式传递。

3.执行时机

call:调用call方法时,函数会立即执行。

bind:调用bind方法时,返回一个新函数,需要后续再调用这个新函数才会执行。

apply:调用apply方法时,函数会立即执行。

总结

相同点: 都可以改变函数内部的this指向

区别点

1.call和apply 会调用函数并且改变函数内部this指向

2.call和apply传递的参数不一样,call传递参数aru1,aru2..形式 apply必须数组形式[arg)

3.bind 不会调用函数可以改变函数内部this指向

主要应用场景:

1.call 经常做继承

2.apply经常跟数组有关系比如借助于数学对象实现数组最大值最小值

3. bind 不调用函数但是还想改变this指向比如改变定时器内部的this指向

call可以直接调用函数,并传递参数列表,立即执行。

bind返回一个新函数,将绑定的对象作为this值,可以在绑定时或调用时传递参数,需要手动调用新函数执行。

apply可以直接调用函数,并传递参数列表,立即执行,参数以数组或类数组的形式传递。

手写call、apply、bind方法

手写call()

原理:

  • 首先,通过 Function.prototype.myCall 将自定义的 myCall 方法添加到所有函数的原型对象上,使得所有函数实例都可以调用该方法。

  • myCall 方法内部,首先通过 typeof this !== "function" 判断调用 myCall 的对象是否为函数。如果不是函数,则抛出一个类型错误。

  • 然后,判断是否传入了上下文对象 context。如果没有传入,则将 context 赋值为全局对象;ES11 引入了 globalThis,它是一个统一的全局对象,无论在浏览器还是 Node.js 中,都可以使用 globalThis 来访问全局对象。

  • 接下来,使用 Symbol 创建一个唯一的键 fn,用于将调用 myCall 的函数绑定到上下文对象的新属性上

  • 调用 myCall 的函数赋值给上下文对象的 fn 属性,实现了将函数绑定到上下文对象上的效果。

  • 调用绑定在上下文对象上的函数,并传入 myCall 方法的其他参数 args

  • 将绑定在上下文对象上的函数删除,以避免对上下文对象造成影响。

  • 返回函数调用的结果。

Function.prototype.myCall(context,···args){
   //判断调用myCall的是否为函数
   if(typeof this !== 'function'){
      throw new TypeError("Function.prototype.myCall - 被调用的对象必须是函数");
   }
    
   //如果没有传入上下文的对象,则默认为全局对象
   context=context||globalThis;
   
   //用Symblo来创建唯一的fn,防止名字冲突
   let fn=Symbol("key");

   // this是调用myCall的函数,将函数绑定到上下文对象的新属性上
   context[fn]=this;

   //传入MyCall的多个参数
   const result=context[fn](···args);

   // 将增加的fn方法删除
   delete context[fn];

   return result;
}; 

测试:

const test={
   name:"csdn",
   hello:function(){
      console.log(`hello,${this.name}!`);
   },
   add:function(a,b){
      return a + b;
   },
};
const obj={
   name:"world"
};
test.hello.myCall(obj); //hello,world!
test.hello.call(obj); //hello,world!
console.log(test.add.myCall(null,1,2));//3
console.log(test.add.call(null,1,2));//3

手写apply()

原理:apply的实现思路跟call类似,就是apply传入参数是以数组的形式传入,所以多了一步判断传入的参数是否为数组以及在调用方法的时候使用扩展运算符 ... 将传入的参数数组 argsArr 展开

Function.prototype.myApply = function (context, argsArr) {
  // 判断调用myApply的是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Function.prototype.myApply - 被调用的对象必须是函数");
  }

  // 判断传入的参数是否为数组
  if (argsArr && !Array.isArray(argsArr)) {
    throw new TypeError("Function.prototype.myApply - 第二个参数必须是数组");
  }

  // 如果没有传入上下文对象,则默认为全局对象,globalThis 来访问全局对象。
  context = context || globalThis;

  // 用Symbol来创建唯一的fn,防止名字冲突
  let fn = Symbol("key");

  // this是调用myApply的函数,将函数绑定到上下文对象的新属性上
  context[fn] = this;

  // 传入myApply的多个参数
  const result = Array.isArray(argsArr)
    ? context[fn](...argsArr)
    : context[fn]();

  // 将增加的fn方法删除
  delete context[fn];

  return result;
};

测试:

const test={
   name:"csdn",
   hello:function(){
      console.log(`hello,${this.name}!`);
   }
};
const obj={
   name:"world"
};
test.hello.myApply(obj); //hello,world!
test.hello.apply(obj); //hello,world!
const arr=[2,3,4,9,5]
console.log(Math.max.myApply(null,arr));//9
console.log(Math.max.apply(null,arr));//9

手写bind()

原理:

  • 首先,通过 Function.prototype.myBind 将自定义的 myBind 方法添加到所有函数的原型对象上,使得所有函数实例都可以调用该方法。

  • myBind 方法内部,首先通过 typeof this !== "function" 判断调用 myBind 的对象是否为函数。如果不是函数,则抛出一个类型错误。

  • 然后,判断是否传入了上下文对象 context。如果没有传入,则将 context 赋值为全局对象;ES11 引入了 globalThis,它是一个统一的全局对象,无论在浏览器还是 Node.js 中,都可以使用 globalThis 来访问全局对象。

  • 保存原始函数的引用,使用 _this 变量来表示。

  • 返回一个新的闭包函数 fn 作为绑定函数。这个函数接受任意数量的参数 innerArgs

  • 在返回的函数 fn 中,首先判断是否通过 new 关键字调用了函数。这里需要注意一点,如果返回出去的函数被当作构造函数使用,即使用 new 关键字调用时,this 的值会指向新创建的实例对象。通过检查 this instanceof fn,可以判断返回出去的函数是否被作为构造函数调用。这里使用 new _this(...args, ...innerArgs) 来创建新对象。

  • 如果不是通过 new 调用的,就使用 apply 方法将原始函数 _this 绑定到指定的上下文对象 context 上。这里使用 apply 方法的目的是将参数数组 args.concat(innerArgs) 作为参数传递给原始函数。

Function.prototype.myBind = function (context, ...args) {
  // 判断调用myBind的是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Function.prototype.myBind - 被调用的对象必须是函数");
  }

  // 如果没有传入上下文对象,则默认为全局对象
  // ES11 引入了 globalThis,它是一个统一的全局对象
  // 无论在浏览器还是 Node.js 中,都可以使用 globalThis 来访问全局对象。
  context = context || globalThis;

  // 保存原始函数的引用,this就是要绑定的函数
  const _this = this;

  // 返回一个新的函数作为绑定函数
  return function fn(...innerArgs) {
    // 判断返回出去的函数有没有被new
    if (this instanceof fn) {
      return new _this(...args, ...innerArgs);
    }
    // 使用apply方法将原函数绑定到指定的上下文对象上
    return _this.apply(context,args.concat(innerArgs));
  };
};

测试:
 

const test = {
  name: "xxx",
  hello: function (a,b,c) {
    console.log(`hello,${this.name}!`,a+b+c);
  },
};
const obj = { name: "world" };
let hello1 = test.hello.myBind(obj,1);
let hello2 = test.hello.bind(obj,1); 
hello1(2,3)//hello,world! 6
hello2(2,3)//hello,world! 6
console.log(new hello1(2,3));
//hello,undefined! 6
// hello {}
console.log(new hello2(2,3));
//hello,undefined! 6
// hello {}

参考链接:https://juejin.cn/post/7268096685199327273

;