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指向。在开发中,掌握这些技巧能够帮助我们更好地编写高效、可维护的代码。

相关推荐
kite01214 小时前
浏览器工作原理06 [#]渲染流程(下):HTML、CSS和JavaScript是如何变成页面的
javascript·css·html
крон4 小时前
【Auto.js例程】华为备忘录导出到其他手机
开发语言·javascript·智能手机
coding随想7 小时前
JavaScript ES6 解构:优雅提取数据的艺术
前端·javascript·es6
年老体衰按不动键盘7 小时前
快速部署和启动Vue3项目
java·javascript·vue
小小小小宇7 小时前
一个小小的柯里化函数
前端
灵感__idea7 小时前
JavaScript高级程序设计(第5版):无处不在的集合
前端·javascript·程序员
小小小小宇7 小时前
前端双Token机制无感刷新
前端
小小小小宇7 小时前
重提React闭包陷阱
前端
小小小小宇7 小时前
前端XSS和CSRF以及CSP
前端
UFIT7 小时前
NoSQL之redis哨兵
java·前端·算法