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

相关推荐
Mintopia8 分钟前
3D Quickhull 算法:用可见性与冲突图搭建空间凸壳
前端·javascript·计算机图形学
Mintopia9 分钟前
Three.js 三维数据交互与高并发优化:从点云到地图的底层修炼
前端·javascript·three.js
陌小路15 分钟前
5天 Vibe Coding 出一个在线音乐分享空间应用是什么体验
前端·aigc·vibecoding
成长ing1213822 分钟前
cocos creator 3.x shader 流光
前端·cocos creator
Alo36531 分钟前
antd 组件部分API使用方法
前端
BillKu34 分钟前
Vue3数组去重方法总结
前端·javascript·vue.js
GDAL36 分钟前
Object.freeze() 深度解析:不可变性的实现与实战指南
javascript·freeze
江城开朗的豌豆1 小时前
Vue+JSX真香现场:告别模板语法,解锁新姿势!
前端·javascript·vue.js
这里有鱼汤1 小时前
首个支持A股的AI多智能体金融系统,来了
前端·python
袁煦丞1 小时前
5分钟搭建高颜值后台!SoybeanAdmin:cpolar内网穿透实验室第648个成功挑战
前端·程序员·远程工作