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