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

相关推荐
CSR-kkk15 分钟前
前端工程化速通——①ES6
前端·es6·速通
yt9483220 分钟前
C#实现CAN通讯接口
java·linux·前端
前端小巷子21 分钟前
Cookie与Session:Web开发中的身份验证与数据存储
前端·javascript·面试
小磊哥er32 分钟前
【前端工程化】前端开发中如何做一套规范的项目模版
前端
Wetoria43 分钟前
管理 git 分支时,用 merge 还是 rebase?
前端·git
前端开发与ui设计的老司机1 小时前
UI前端与数字孪生融合新领域:智慧环保的污染源监测与治理
前端·ui
一只小风华~1 小时前
Web前端开发: :has功能性伪类选择器
前端·html·html5·web
成遇1 小时前
Eslint基础使用
javascript·typescript·es6
Mr_Mao5 小时前
Naive Ultra:中后台 Naive UI 增强组件库
前端
前端小趴菜057 小时前
React-React.memo-props比较机制
前端·javascript·react.js