大家好!我是萝卜,this关键字是javascript中最复杂的机制之一,this被自动定义在所有函数的作用域中,即使是有多年经验的前端同学,有时也很难说清它到底指向什么,实际上,this机制并没有那么先进,下面就带大家彻底搞懂this。
我们为什么要用this
既然this关键字是javascript中最复杂的机制之一,那么我们为什么还要使用this,因为this提供了优雅的方式来隐性的“传递”一个对象引用,通过这种方式可以将API设计的更加简洁且易于复用。
function a() {
return this.name;
}
function b() {
var str = "Hello, I'm " + a.call( this );
console.log( str );
}
var obj1 = {
name: "胡萝卜"
};
var obj2 = {
name: "白萝卜"
};
a.call( obj1 ); // 胡萝卜
a.call( obj2 ); // 白萝卜
b.call( obj1 ); // Hello, 我是 胡萝卜
b.call( obj2 ); // Hello, 我是 白萝卜
这段代码可以在不同的上下文对象(obj1 和 obj2)中重复使用函数 a() 和 b(),不用针对每个对象编写不同版本的函数。 如果不使用 this,那就需要给 a() 和 b() 显式传入一个上下文对象。
function a(context) {
return context.name;
}
function b(context) {
var str = "Hello, I'm " + a( context );
console.log( greeting );
}
var obj1 = {
name: "胡萝卜"
};
var obj2 = {
name: "白萝卜"
};
a( obj1 ); // 胡萝卜
b( obj2 ); //hello, 我是 白萝卜
this到底是什么
当一个函数被调用时,会创建一个活动记录也被称为执行上下文。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在函数的执行过程中用到。this 既不指向函数自身也不指向函数的词法作用域,this 总是指向一个对象,具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。this 的指向和函数声明的位置没有任何关系,只取决于函数的调用位置。
如何找到函数的调用位置
我们可以借助浏览器的开发者工具中的调用栈来找到函数被调用的位置,先看如下代码:
function a() {
// 当前调用栈是:a
// 因此,当前调用位置是全局作用域
b(); // <-- b 的调用位置
}
function b() {
// 当前调用栈是 a -> b
// 因此,当前调用位置在 a 中
c(); // <-- c 的调用位置
}
function c() {
// 当前调用栈是 a -> b -> c
// 因此,当前调用位置在 b 中
}
// a函数的调用位置
a();

函数调用栈
其中Call Stack就是函数在栈中的调用位置,通过这种方式我们就能真正的分析出函数的真正的调用位置。
具体开发中常见的4种this的指向
- 作为对象的方法调用
当函数作为对象的方法被调用时,this指向该对象:
var obj = {
a: 1,
getA: function(){
alert ( this === obj ); // 输出:true
alert ( this.a ); // 输出: 1
}
};
obj.getA();
2. 作为普通函数调用
当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的 this 总是指向全局对象。在浏览器的JavaScript里,这个全局对象是 window 对象。
window.name = 'globalName';
var getName = function(){
return this.name;
};
// 输出:globalName
console.log( getName() );
3. 构造器调用
当用 new 运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的 this 就指向返回的这个对象。
var MyClass = function(){
this.name = '萝卜';
};
var obj = new MyClass();
alert ( obj.name ); // 输出:萝卜
4. Function.prototype.call或Function.prototype.apply调用
跟普通的函数调用相比,用 Function.prototype.call 或 Function.prototype.apply 可以动态地 改变传入函数的 this。
var obj1 = {
name: '胡萝卜',
getName: function(){
return this.name;
}
};
var obj2 = {
name: '白萝卜'
}
console.log(obj1.getName()); // 输出: 胡萝卜
console.log(obj2.getName.call(obj2)); // 输出: 白萝卜
ES6中的箭头函数
我们之前介绍的4种this指向已经可以包含所有正常的函数。但是 ES6 中介绍了一种无法使用 这些规则的特殊函数类型:箭头函数。
箭头函数并不是使用 function 关键字定义的,而是使用操作符 => 定 义的。箭头函数不实用 this 的4种指向,而是根据外层(函数或者全局)作用域来决定。
function a() {
// 返回一个箭头函数
return (name) => {
//this 继承自 foo()
console.log( this.name );
};
}
var obj1 = {
name: '胡萝卜'
};
var obj2 = {
name: '白萝卜'
};
var b = a.call( obj1 );
b.call( obj2 ); // 输出:胡萝卜, 而不是白萝卜!
a() 内部创建的箭头函数会捕获调用时 a() 的 this。由于 a() 的 this 绑定到 obj1, b(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定无法被修改。
小结
如果要判断一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。找到之后
就可以顺序应用下面这4种情况来判断 this 的绑定对象。
1. 作为对象的方法调用。
2. 作为普通函数调用。
3. 构造器调用。
4. Function.prototype.call或Function.prototype.apply调用。
ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这其实和 ES5 中通过 self = this 这种方式防止作用域漂移是一样的。