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

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

1. this 绑定方式概述

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

  • 默认绑定 :函数被独立调用时,this 默认指向全局对象(在浏览器环境中是 window)。
  • 隐式绑定 :函数被某个对象调用时,this 指向该对象。
  • 显式绑定 :通过 call、apply 和 bind 来显式地指定 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. 显式绑定:call、apply 和 bind

4.1 call 和 apply

call 和 apply 都是 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. 自定义 call、apply 和 bind

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

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. 总结

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

相关推荐
JavaGuide20 分钟前
DeepSeek Harness 官方桌面端终于有了!
前端·后端
wshzd23 分钟前
LLM之Agent(104)|DeepSeek-Harness(十三)ReactLoopAgent 总览:kick → turn → step
开发语言·前端·javascript
茉莉玫瑰花茶31 分钟前
OpenGL [ 基础概念 ]
java·前端·数据库
随性而行36038 分钟前
企业微信二次开发如何接入大模型工具?API接口实现智能任务调用的技术思路
java·前端·人工智能·python·微信·机器人·企业微信
IMPYLH1 小时前
HTML 的 <td> 元素
前端·html
gnip1 小时前
Flutter GetX 三件套开发规范(Skill)
前端·flutter
张小姐的猫1 小时前
【AI大模型接入SDK】 —— 前端页面 & 项目总结与拓展
前端·数据结构·数据库·c++·人工智能·chatgpt
web打印社区1 小时前
远程打印:WebSocket 与 HTTP 轮询怎么选
前端·vue.js·websocket·网络协议·http·electron·pdf
吴声子夜歌1 小时前
HTML——庞杂的表单控件元素(一)
前端·html
天天喝旺仔2 小时前
浏览器渲染原理:从解析 HTML/CSS、构建渲染树到重排重绘与首屏优化
前端·javascript·css·性能优化·html