手写 call、apply 和 bind

1、实现 call

call 方法

  • call 方法用于调用一个函数,并指定函数内部的 this 值。它接受一个函数的上下文对象作为第一个参数,后面可以跟随任意数量的参数。
  • 使用示例:func.call(context, arg1, arg2, ...)

手写 call:

js 复制代码
// 传递参数从一个数组变成逐个传参了,不用...扩展运算符的也可以用arguments代替
Function.prototype.myCall = function (context, ...args) {
  // 这里默认不传就是给window,也可以用es6给参数设置默认参数
  context = context || window;
  args = args ? args : [];
  // 给context新增一个独一无二的属性以免覆盖原有属性
  const key = Symbol();
  context[key] = this;
  // 通过隐式绑定的方式调用函数
  const result = context[key](...args);
  // 删除添加的属性
  delete context[key];
  // 返回函数调用的返回值
  return result;
};

2、实现 apply

apply 方法

  • apply 方法也用于调用一个函数,并指定函数内部的 this 值,但与 call 不同,它接受一个数组或类数组对象作为参数列表,其中数组中的每个元素都被传递给函数作为参数。
  • 使用示例:func.apply(context, [arg1, arg2, ...])

手写 apply:

js 复制代码
Function.prototype.myApply = function (context, args) {
  // 这里默认不传就是给window,也可以用es6给参数设置默认参数
  context = context || window;
  args = args ? args : [];
  // 给context新增一个独一无二的属性以免覆盖原有属性
  const key = Symbol();
  context[key] = this;
  // 通过隐式绑定的方式调用函数
  const result = context[key](...args);
  // 删除添加的属性
  delete context[key];
  // 返回函数调用的返回值
  return result;
};

3、实现 bind

bind 方法

  • bind 方法用于创建一个新函数,其 this 值永久地绑定到指定的上下文对象。与 callapply 不同,bind 不会立即执行函数,而是返回一个新的函数,稍后可以调用。
  • 使用示例:var boundFunc = func.bind(context);

手写 bind:

js 复制代码
Function.prototype.myBind = function (context, ...args) {
  // 保存原始函数的引用
  const fn = this;
  // 如果没有传入参数,则初始化为空数组
  args = args ? args : [];
  // 返回一个新的函数 newFn,这个函数实际上是用来代理原函数的
  return function newFn(...newFnArgs) {
    // 如果 newFn 是通过构造函数调用的(使用 new 关键字),则执行原函数,并传递参数
    if (this instanceof newFn) {
      return new fn(...args, ...newFnArgs);
    }
    // 如果 newFn 是普通函数调用的,则使用 apply 方法将原函数绑定到指定的上下文 context,并传递参数
    return fn.apply(context, [...args, ...newFnArgs]);
  };
};

4、区别

  • 参数传递方式

    • callapply 接受不同的参数传递方式。call 接受一系列参数,而 apply 接受一个数组。
  • 函数执行时机

    • callapply 立即执行函数,并返回函数的执行结果。
    • bind 不会立即执行函数,而是返回一个新的函数,需要在稍后手动调用。
  • 函数绑定

    • callapply 不会改变原始函数的 this 值,它们只是在调用时临时改变上下文。
    • bind 创建一个新函数,该函数永久绑定了指定的上下文对象,不会改变原始函数的 this 值。
  • 返回值

    • callapply 直接返回函数的执行结果。
    • bind 返回一个新的函数。

举个例子🌰:

js 复制代码
var person = {
  name: 'John',
  greet: function(greeting) {
    console.log(greeting + ' ' + this.name);
  }
};

person.greet('Hello'); // 输出 "Hello John"

var anotherPerson = {
  name: 'Alice'
};

// 使用 call
person.greet.call(anotherPerson, 'Hi'); // 输出 "Hi Alice"

// 使用 apply
person.greet.apply(anotherPerson, ['Hola']); // 输出 "Hola Alice"

// 使用 bind
var greetFunc = person.greet.bind(anotherPerson);
greetFunc('Bonjour'); // 输出 "Bonjour Alice"

5、补充:this 的指向

this 的指向是在函数调用的时候确定下来的,this 的指向大致可以分为五种。

5.1、默认绑定

默认绑定一般发生在回调函数,函数直接调用。

js 复制代码
function test() {
  // 严格模式下是undefined
  // 非严格模式下是window
  console.log(this);
}
setTimeout(function () {
  // setTimeout的比较特殊
  // 严格模式和非严格模式下都是window
  console.log(this);
});

arr.forEach(function () {
  // 严格模式下是undefined
  // 非严格模式下是window
  console.log(this);
});

5.2、隐式绑定

这个通俗点用一句话概括就是谁调用就是指向谁。

js 复制代码
const obj = {
  name: 'joy',
  getName() {
    console.log(this); // obj
    console.log(this.name); // joy
  },
};
obj.getName();

5.3、显示绑定 call、apply、bind

js 复制代码
const obj1 = {
  name: 'joy',
  getName() {
    console.log(this);
    console.log(this.name);
  },
};

const obj2 = {
  name: 'sam',
};

obj1.getName.call(obj2); // obj2 sam
obj1.getName.apply(obj2); // obj2 sam
const fn = obj1.getName.bind(obj2);
fn(); // obj2 sam

5.4、new 绑定

js 复制代码
function Vehicle() {
  this.a = 2
  console.log(this);
}
new Vehicle(); // this指向Vehicle这个new出来的对象

5.5、es6 的箭头函数

es6 的箭头函数比较特殊,箭头函数 this 为父作用域的 this,不是调用时的 this。要知道前四种方式,都是调用时确定,也就是动态的,而箭头函数的 this 指向是静态的,声明的时候就确定了下来。比较符合 js 的词法作用域吧。

js 复制代码
window.name = 'win';
const obj = {
  name: 'joy',
  age: 12,
  getName: () => {
    console.log(this); // 其父作用域this是window,所以就是window
    console.log(this.name); // win
  },
  getAge: function () {
    // 通过obj.getAge调用,这里面this是指向obj
    setTimeout(() => {
      // 所以这里this也是指向obj,所以结果是12
      console.log(this.age);
    });
  },
};
obj.getName();
obj.getAge();

既然有5种 this 的绑定方式,那么肯定有优先级的先后。

箭头函数 -> new绑定 -> 显示绑定call/bind/apply -> 隐式绑定 -> 默认绑定

相关推荐
weixin_382395234 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__5 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.5 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~6 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华7 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子9 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅9 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni10 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学11 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端