手写 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 -> 隐式绑定 -> 默认绑定

相关推荐
天蓝色的鱼鱼26 分钟前
前端开发者的组件设计之痛:为什么我的组件总是难以维护?
前端·react.js
codingandsleeping28 分钟前
使用orval自动拉取swagger文档并生成ts接口
前端·javascript
石金龙1 小时前
[译] Composition in CSS
前端·css
白水清风1 小时前
微前端学习记录(qiankun、wujie、micro-app)
前端·javascript·前端工程化
Ticnix2 小时前
函数封装实现Echarts多表渲染/叠加渲染
前端·echarts
用户22152044278002 小时前
new、原型和原型链浅析
前端·javascript
阿星做前端2 小时前
coze源码解读: space develop 页面
前端·javascript
叫我小窝吧2 小时前
Promise 的使用
前端·javascript
NBtab2 小时前
Vite + Vue3项目版本更新检查与页面自动刷新方案
前端
天天扭码2 小时前
来全面地review一下Flex布局(面试可用)
前端·css·面试