this 和函数绑定:理解 call、apply、bind 和它们的实现

在 JavaScript 中,this 关键字是一个非常核心的概念,理解 this 的不同绑定方式对于编写高效且清晰的代码至关重要。本文将深入探讨 JavaScript 中的 this 绑定方式,包括默认绑定、隐式绑定、显式绑定以及如何实现自定义的 callapplybind 方法。

1. this 绑定方式概述

在 JavaScript 中,this 的值并不是由函数定义时决定的,而是由函数执行时的调用方式来决定的。主要的绑定方式有以下几种:

  • 默认绑定 :函数被独立调用时,this 默认指向全局对象(在浏览器环境中是 window)。
  • 隐式绑定 :函数被某个对象调用时,this 指向该对象。
  • 显式绑定 :通过 callapplybind 来显式地指定 this 的指向。
  • 构造函数绑定 :当函数作为构造函数通过 new 关键字调用时,this 指向新创建的实例。

2. 默认绑定

默认绑定是最简单的一种情况。函数如果是独立调用的,那么 this 会指向全局对象。例如:

javascript 复制代码
function foo() {
  console.log(this);
}
foo();  // 在浏览器中,this 会指向 window 对象

3. 隐式绑定

隐式绑定指的是函数是作为对象的方法被调用时,this 会指向该对象。例如:

javascript 复制代码
let obj = {
  a: 1,
  foo: function() {
    console.log(this.a);
  }
};

obj.foo();  // 输出 1,因为 this 指向 obj

当函数链式调用时,函数被一连串的对象调用,this指向最近调用的对象

javascript 复制代码
var a = 1;
let obj = {
  a: 2,
  fn: foo
}
let obj2 = {
  a: 3,
  fm: obj
}
function foo() {
  console.log(this.a);
}
obj2.fm.fn()   //输出2

4. 显式绑定:callapplybind

4.1 callapply

callapply 都是 JavaScript 中显式绑定 this 的方法,二者的区别在于传参方式:

  • call:接受一系列零散的参数。
  • apply:接受一个参数数组。
javascript 复制代码
function foo(x, y) {
  console.log(this.a, x + y);
}

let obj = {
  a: 2
};

foo.call(obj, 3, 4);  // 输出 2 7
foo.apply(obj, [3, 4]);  // 输出 2 7

4.2 bind

bind 方法返回一个新的函数,新的函数会绑定指定的 this,并且可以接收其他参数。bind 方法的一个特别之处是,它会返回一个新的函数对象,且可以在以后调用时传入新的参数。并且如果通过 new 调用这个新函数,this 会指向新创建的实例对象。

javascript 复制代码
let obj = { a: 2 };

function foo(x, y) {
  console.log(this.a, x + y);
}

let boundFoo = foo.bind(obj, 3, 4);
boundFoo();  // 输出 2 7  //新函数也可以接受参数

5. 自定义 callapplybind

我们可以根据原生 callapplybind 的实现方式,自己实现这些方法。接下来是 callapplybind 的自定义实现代码:

5.1 自定义 mycall

javascript 复制代码
// 把.mycall方法挂载函数的原型上,这样函数就可以调用了
Function.prototype.mycall = function(context, ...args) {
  if (typeof this !== 'function') {   //判断当前调用的是不是一个函数
    throw new TypeError('Error');
  }
  context = context || window;   //如果没有context,就挂载在window上
  // 防止原型上也有个fn,所以Symbol('fn')保证唯一性
  const key = Symbol('fn');
  
  //this指的是call函数前面绑定的函数  //把这个函数放在obj里面
  context[key] = this;
  
  const result = context[key](...args);   
  
  // 删除不影响函数本身
  delete context[key];
  
  return result;
};

5.2 自定义 myapply

javascript 复制代码
//和.mycall类似,只是传参形式不同
Function.prototype.myapply = function(context, args) {
  if (typeof this !== 'function') {
    throw new TypeError('Error');
  }
  context = context || window;
  const key = Symbol('fn');
  context[key] = this;
  const result = context[key](...args);
  delete context[key];
  return result;
};

5.3 自定义 mybind

javascript 复制代码
Function.prototype.mybind = function (context, ...args) {
  if (typeof(this) !== 'function') {
    throw new TypeError('Error')
  }
  context = context || window
  const self = this
  
  // 保证返回一个函数体
  return function Fn(...arg) {  
  // 原生bind被new调用会返回 foo 的实例,所以这里我们也要保证有这个功能
    if (this.__proto__ === Fn.prototype) {  // 被 new 调用 返回 foo 的实例
      return new self(...args, ...arg)     
    } else {  //没有的话就调用apply,这里偷个懒,后续代码和之前写的myapply一样就可以了
      return self.apply(context, [...args,...arg])
    }
  }
}

6. new 操作符和 this

new 操作符会创建一个新的实例并将 this 指向这个实例对象。在构造函数中,this 会指向新创建的对象。例如:

javascript 复制代码
function Test() {
  this.a = 1;
}

const instance = new Test();
console.log(instance.a);  // 输出 1

在上面的代码中,Test 函数作为构造函数被调用时,this 指向新创建的 instance 对象。

7. 总结

通过灵活使用 callapplybind,我们可以更精确地控制函数this指向。在开发中,掌握这些技巧能够帮助我们更好地编写高效、可维护的代码。

相关推荐
冴羽4 分钟前
SvelteKit 最新中文文档教程(3)—— 数据加载
前端·javascript·svelte
百万蹄蹄向前冲21 分钟前
组建百万前端梦之队-计算机大学生竞赛发展蓝图
前端·vue.js·掘金社区
云隙阳光i34 分钟前
实现手机手势签字功能
前端·javascript·vue.js
imkaifan1 小时前
vue2升级Vue3--native、对inheritAttrs作用做以解释、声明的prop属性和未声明prop的属性
前端·vue.js·native修饰符·inheritattrs作用·声明的prop属性·未声明prop的属性
觉醒法师1 小时前
HarmonyOS NEXT - 电商App实例三( 网络请求axios)
前端·华为·typescript·axios·harmonyos·ark-ts
Danta1 小时前
HTTP协议版本演进:从HTTP/0.9到HTTP/3的高分面试回答
前端·网络协议·面试
柠檬树^-^2 小时前
app.config.globalProperties
前端·javascript·vue.js
太阳花ˉ2 小时前
react(一):特点-基本使用-JSX语法
前端·react.js
赵大仁2 小时前
深入解析 React Diff 算法:原理、优化与实践
前端·react.js·前端框架
1024小神2 小时前
vue/react/vite前端项目打包的时候加上时间最简单版本,防止后端扯皮
前端·vue.js·react.js