前言
在JavaScript中,“this”的行为有时确实显得有些“神秘”,因为它不像其他语言中的“this”那样始终指向调用它的对象。在JS中,“this”的实际绑定取决于函数的调用上下文,this的指向取决于函数是如何调用的,这可以导致一些初学者和甚至有经验的开发者时常感到困惑。下面,我们将讨论“this”的不同行为以及如何理解和控制它。
1. 全局作用域中的this
在全局作用域中,this通常指向全局对象:
- 在浏览器中,全局对象是
window。 - 在Node.js中,全局对象是
global。
this主要写在全局作用域以及函数作用域体内。
2. 函数调用中的this
当函数作为普通函数调用时(不作为任何对象的方法),this的行为如下:
- 非严格模式(non-strict mode):
this指向全局对象(如window)。 - 严格模式(strict mode):
this是undefined。
function sayHello() {
console.log(this);
}
sayHello(); // 在非严格模式下输出 window 对象,在严格模式下输出 undefined
当一个函数没有挂载在别的对象上时,也就是说该函数调用时不作为任何对象的方法,函数内的this会触发默认绑定。
举个栗子:
var a = 1
function foo(){
var a = 2
function bar(){
var a = 3
function baz(){
console.log(this.a)
}
baz()
}
bar()
}
foo() // 浏览器 打印 1
每个baz函数是独立调用,其内部this就是指向全局对象。
默认绑定:当一个函数独立调用时,不带任何修饰符的调用,该函数的this指向全局(window)。
3. 方法调用中的this
当函数作为对象的方法被调用时,这个时候触发的就是this的隐式绑定。this指向调用该方法的对象:
const obj = {
myname: '梁总',
age: 22,
say: function(){
console.log(this.myname);
}
}
obj.say(); // 打印 梁总
隐式绑定: 当一个函数被某个对象拥有,或者函数被某个上下文对象调用时,该函数中的this指向上下文对象。
当一个函数被多个对象链式调用,场景如下:
function foo(){
console.log(this.a)
}
var obj1 = {
a: 1,
foo:foo
}
var obj2 = {
a: 2,
obj : obj1
}
obj2.obj.foo() // 浏览器内 打印 1
这个时候就会出现this的隐式丢失:函数被多个对象链式调用时,this指向最近的那个对象。(this的指向遵循就近原则)
4. 构造函数中的this
在构造函数中,this指向新创建的对象实例:
function Person(name) {
this.name = name;
}
const john = new Person('John Doe');
console.log(john.name); // 输出 "John Doe"
5. 箭头函数中的this
箭头函数没有自己的this绑定;它们从封闭作用域继承this:
const person = {
name: 'John Doe',
sayHello: () => {
console.log(`Hello, ${this.name}`);
}
};
person.sayHello(); // 输出 "Hello, undefined" 或 "Hello, window",取决于全局作用域的`this`
箭头函数中没有this的机制,写在箭头函数中的this那也是外层非箭头函数的this:
var obj = {
a: 1,
foo: function(){
// this 当函数创建时会,其内部会产生一个this
const fn = ()=>{
console.log(this.a)
}
fn()
}
}
obj.foo() // 1
这里fn的内部函数不存在this,但箭头函数fn继承了外部foo函数的this,而函数foo又属于obj对象的方法,所以obj对象调用foo方法时,触发隐式绑定,this会指向obj对象。
6. Event Handler 中的this
在事件处理器中,this通常指向触发事件的DOM元素:
<button id="myButton">Click me</button>
<script>
document.getElementById('myButton').addEventListener('click', function() {
console.log(this.id); // 输出 "myButton"
});
</script>
控制this的指向
为了更精确地控制this的指向,可以使用.call(), .apply(), 和.bind()方法:
.call()和.apply()允许立即调用函数,并指定this的值以及参数。.bind()创建一个新的函数,其this值被固定。
这个通常称为:显式绑定,它是通过call,apply,bind,将函数的this掰弯到一个对象中。
例如: 我们对三个方法逐一分析:
call::
var obj = {
a: 1
}
function foo(){
console.log(this.a);
}
foo.call(obj) // call会把foo中的this指向到obj
// 打印 1
.apply:
var obj = {
a: 1
}
function foo(x,y){
console.log(this.a, x + y);
}
foo.apply(obj,[2,2]) // 1 4
如果函数foo存在参数的话,可以在apply函数传入多个参数,第一个参数为this的指向对象,第二个为一个数组对象,像上面那样。
.bind:
const person = {
name: '小李',
sayHello: function() {
console.log(`Hello, ${this.name}`);
}
};
const sayHelloBound = person.sayHello.bind({ name: '小黄' });
sayHelloBound(); // 输出 "Hello, 小黄"
明白了上面的一些this绑定方式后,你是不是对this有了更加深的印象呢?那么接下来我们针对call方法来手动模拟实现一个mycall的方法吧,学了理论知识,也该实践一下了吧,哈哈哈哈。
首先我们清楚,这三个方法(call、apply、bind)都是传入this将要指向的对象,以及参数的传入,在上面我们了解到隐式绑定的方法可以将函数的this指向一个对象,且主要就是将该函数挂载到指向的对象上,基于这一点,我们可以来手动实现它了:
var obj = {
a: 1
}
function foo(){
console.log(this.a);
return 123 //函数可能存在返回值
}
Function.prototype.mycall = function(){
}
如果按照这个模板去实现mycall的话我们可以分析,mycall的实现分为以下步骤:
- 拿到foo
- 将foo引用到obj
- 让obj触发foo
- 移除掉obj身上的foo 所以我们可以看看如下的实现代码:
var obj = {
a: 1
}
function foo(){
console.log(this.a);
return 123 //函数可能存在返回值
}
Function.prototype.mycall = function(){
// arguments 函数内会接受到的所有参数
const context = arguments[0] // 拿到第一个参数 ,obj对象就等于 context 对象
const args = Array.from(arguments).slice(1) // 一个数组[]对象,mycall可能接收不止一个参数,这里是将后面的参数切割下来放在args数组内
context.fn = this; // this指向 foo函数 ,并把函数挂载到obj对象内。
const res = context.fn(...args) // 执行
delete context.fn // 移除函数fn
return res
}
let res = foo.mycall(obj,4,5)
console.log(res);
由于mycall内部出现了this,这个this本能来是指向mycall函数对象的,但是该函数挂载在Function的原型上(Function.prototype.mycall),因为foo也是一个函数,所以foo的隐式原型就指向Function的显式原型,当foo函数调用mycall函数时,我们有foo.mycall(obj,4,5),这将触发隐式绑定,所以函数mycall的this就是foo函数。
我们将fn 函数挂载到传入的obj对象上,然后直接执行掉fn函数,这里也是触发this的隐式绑定。拿到结果存储,且此时的this是指向context的,也就是obj对象。
然后移除函数,我们就使得函数foo的this会指向obj,实现了和call一样的方法。
希望小伙伴们可以自行尝试哦,这样记忆会更加深刻呢!!
为了让对象中的函数有能力访问对象中自己的属性,this可以显著的提升代码质量,减少上下文参数的传递
理解并掌握this的这些特性,可以帮助你写出更加健壮和可预测的JavaScript代码。