this指向
与其他语言相比,函数的this
关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。
在绝大多数情况下,函数的调用方式决定了this
的值。this是在运行时基于函数的执行环境绑定的:在全局函数中this等于window,而当函数被作为某个对象方法调用时,this等于那个对象。ES5引入了bind方法来设置函数的this
值,而不用考虑函数如何被调用的。
全局环境
无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this
都指向全局对象。
// 在浏览器中, window 对象同时也是全局对象:
console.log(this === window); // true
a = 37;
console.log(window.a); // 37
this.b = "MDN";
console.log(window.b) // "MDN"
console.log(b) // "MDN"
函数(运行内)环境
在函数内部,this
的值取决于函数被调用的方式。
普通函数内部的this分两种情况,严格模式和非严格模式。
- 非严格模式下,this 默认指向全局对象window
function f1(){
return this;
}
//在浏览器中:
f1() === window; //在浏览器中,全局对象是window
//在Node中:
f1() === global;
然而,在严格模式下,this
将会默认为undefined
。
function f2(){
"use strict"; // 这里是严格模式
return this;
}
f2() === undefined; // true
作为对象的方法
当函数作为对象里的方法被调用时,它们的this
是调用该函数的对象。
- 函数的定义位置不影响其this指向,this指向只和调用函数的对象有关。
- 多层嵌套的对象,内部方法的this指向离被调用函数最近的对象(window也是对象,其内部对象调用方法的this指向内部对象, 而非window)。
下面的例子中,当o.f()
被调用时,函数内的this
将绑定到o
对象。
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // logs 37
请注意,这样的行为,根本不受函数定义方式或位置的影响。在前面的例子中,我们在定义对象o
的同时,将函数内联定义为成员 f
。但是,我们也可以先定义函数,然后再将其附属到o.f
。这样做会导致相同的行为:
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // logs 37
同样,this
的绑定只受最靠近的成员引用的影响。在下面的这个例子中,我们把一个方法g
当作对象o.b
的函数调用。在这次执行期间,函数中的this
将指向o.b
。事实证明,这与他是对象 o
的成员没有多大关系,最靠近的引用才是最重要的。
o.b = {g: independent, prop: 42};
console.log(o.b.g()); // 42
原型链中的this
对于在对象原型链上某处定义的方法,同样的概念也适用。如果该方法存在于一个对象的原型链上,那么this
指向的是调用这个方法的对象,就像该方法在对象上一样。
var o = {
f: function() {
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
可以看出, 在p中没有属性f,当执行p.f()时,会查找p的原型链,找到 f 函数并执行,但这与函数内部this指向对象 p 没有任何关系,只需记住谁调用指向谁。
以上对于函数作为getter & setter 调用时同样适用。
作为构造函数
当一个函数用作构造函数时(使用new关键字),它的this
被绑定到正在构造的新对象。
虽然构造器返回的默认值是this
所指的那个对象,但它仍可以手动返回其他的对象(如果返回值不是一个对象,则返回this
对象)。
/*
* 构造函数这样工作:
*
* function MyConstructor(){
* // 函数实体写在这里
* // 根据需要在this上创建属性,然后赋值给它们,比如:
* this.fum = "nom";
* // 等等...
*
* // 如果函数具有返回对象的return语句,
* // 则该对象将是 new 表达式的结果。
* // 否则,表达式的结果是当前绑定到 this 的对象。
* //(即通常看到的常见情况)。
* }
*/
function C(){
this.a = 37;
}
var o = new C();
console.log(o.a); // logs 37
function C2(){
this.a = 37;
return {a:38};
}
o = new C2();
console.log(o.a); // logs 38
在刚刚的例子中(C2
),因为在调用构造函数的过程中,手动的设置了返回对象,与this
绑定的默认对象被丢弃了。(这基本上使得语句 “this.a = 37;
”成了“僵尸”代码,实际上并不是真正的“僵尸”,这条语句执行了,但是对于外部没有任何影响,因此完全可以忽略它)。
作为一个DOM事件处理函数
当函数被用作事件处理函数时,它的this
指向触发事件的元素(一些浏览器在使用非addEventListener
的函数动态添加监听函数时不遵守这个约定)。
// 被调用时,将关联的元素变成蓝色
function bluify(e){
this.style.backgroundColor = '#A5D9F3';
}
// 获取文档中的所有元素的列表
var elements = document.getElementsByTagName('*');
// 将bluify作为元素的点击监听函数,当元素被点击时,就会变成蓝色
for(var i=0 ; i<elements.length ; i++){
elements[i].addEventListener('click', bluify, false);
}
作为一个内联事件处理函数
- 当代码被内联处理函数调用时,它的this指向监听器所在的DOM元素
- 当代码被包括在函数内部执行时,其this指向等同于 ****函数直接调用****的情况,即在非严格模式指向全局对象window, 在严格模式指向undefined
<button onclick="console.log(this)"></button> // button
<button onclick="(function() { console.log(this) })()"></button> // window
<button onclick="(function() {'use strict'; console.log(this) })()"></button> // undefined
setTimeout & setInterval
对于延时函数内部的回调函数的this指向全局对象window(当然我们可以通过bind方法改变其内部函数的this指向)
//默认情况下代码
function Person() {
this.age = 0;
setTimeout(function() {
console.log(this);
}, 3000);
}
var p = new Person();//3秒后返回 window 对象
//通过bind绑定
function Person() {
this.age = 0;
setTimeout((function() {
console.log(this);
}).bind(this), 3000);
}
var p = new Person();//3秒后返回构造函数新生成的对象 Person{...}
箭头函数中的 this
箭头函数的this定义:箭头函数的this是在定义函数时绑定的,不是在执行过程中绑定的。简单的说,函数在定义时,this就继承了定义函数的对象。
一句话,箭头函数内的this就是箭头函数外的那个this为什么?因为箭头函数没有自己的this。
所以,这会很好的解决匿名函数和setTimeout和setInterval的this指向问题。我们不用再去给其用that变量存储this。
// demo1
var obj = {
func: function () {
setTimeout(function () {
console.log(this) // window
}, 100)
// console.log(this)
},
say:function () {
setTimeout(() => {
console.log(this) // obj
}, 1000)
}
}
obj.func()
obj.say()
// demo2
var a = 0
var obj = {
a: 1,
b1: this.a,
b2: () => this.a,
};
console.log(obj.b1); // 0
console.log(obj.b2()); // 0
根据结果可以看到obj对象中的属性b1的值来自全局变量a,而不是obj对象的属性a,也就是说b1: this.a,
中的this指向window。而b2: () => this.a,
中的this同样指向window,返回全局变量a的值。
// demo3
var a = 0;
function fn(){
var a = 2;
console.log(this.a); // 0
const subf = ()=>this.a;
console.log(subf()); // 0
}
fn()
/*
箭头函数中有this就当做这个箭头函数不存在
fn函数 -> window.fn()
*/
// demo4
var a = 0
var obj2 = {
a: 3,
b1: function(){ return this.a;},
b2: function(){ return ()=>this.a},
};
console.log(obj2.b1()); // 3
console.log(obj2.b2()()); // 3
/*
箭头函数中有this就当做这个箭头函数不存在
this.a 指向的事当前的对象,因此结果都是:3
*/
// demo6
var obj3 = {
a: 4,
b1: function(){ return this.a;},
b2: function(){ return ()=> () => () => this.a},
};
console.log(obj4.b1()); // 4
console.log(obj4.b2()()()()); // 4
/*
这里箭头函数返回箭头函数再返回箭头函数,最后一个箭头函数返回this.a。这个this是谁?就是最后箭头函数外面的外面的外面的那个this,也就是obj3.b1中的那个this,也就是obj3.a。:)
实情是这样:箭头函数没有自己的this,所以不管嵌套多少层,都不影响this是谁。
*/
// demo7
var a = 0
function f0() {
var a = 5;
setTimeout(function() {
var b1 = this.a;
console.log(b1); // 0
}, 100);
setTimeout(function() {
var b2 = ()=> this.a;
console.log(b2()); // 0
}, 200);
}
f0(); // 0 0
/*
setTimeout中的function运行于全局环境下,所以第一个就是:0
第二个setTimeout中的函数是箭头函数,箭头函数里面的this和setTimeout所处环境一直,因此指向的也是window下的a结果就是0
*/