本菜鸟在面试的过程中,曾经被问到一些有关this的问题,在面试官的反问下,我开始质疑自己对this到底了解多少?
(本文内容如有任何不正确,欢迎交流)
1.this是什么
根据MDN的说法:this为当前执行上下文(global、function 或 eval)的一个属性,在非严格模式下,总是指向一个对象,在严格模式下可以是任意值。
注:本文所有代码都是在浏览器环境下运行(非严格模式下)
2.函数内的this指向谁?
2.1.普通函数内的this指向取决于谁最后调用了它
根据《javascript高级程序设计(第四版)》:在标准函数中, this 引用的是把函数当成方法调用的上下文对象(在网页的全局上下文中调用函数时, this 指向 windows)。
// from MDN
// 在浏览器中, 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"
/*
作为全局函数时的表现
*/
var name = "bd";
function T() {
var name = "BD";
console.log(this.name); // bd
console.log("T:" + this); // T: Window
}
T();
console.log("t:" + this) // t: Window
复制代码
T函数内部的this指向取决于谁最后调用了它,此时在全局对象 Window 上调用T函数,故 this->window ,所以 this.name==window.name ,输出bd
/*
作为对象方法时的表现
*/
var name = "BD";
var a = {
name: "bd",
fn : function () {
console.log(this.name); // "bd"
},
fn2 : function() {
console.log(this)
}
}
a.fn();
a.fn2();
复制代码
结果如下:
将fn2改成以下后:
var name = "BD";
var a = {
name: "bd",
fn : function () {
console.log(this.name); // "bd"
},
fn2 : function() {
return function(){
console.log(this) // "window......"
}
}
}
a.fn();
a.fn2()();
复制代码
结果如下:
对象 a 调用了 fn2( )( ),此时为独立函数调用,此时 this 是指向 window 对象,如果想在该匿名函数内打印出 this ,可以使用闭包
fn2:function() {
var _this=this
return function(){
console.log(_this)
}
}
复制代码
结果如下:
再看看下面的代码:
var o = {
prop: 'bd'
};
var prop = 'BD';
function fn() {
return this.prop;
}
o.f = fn;
console.log(o.f()); // 'bd'
复制代码
在这里函数 fn( ) 虽然定义在全局作用域,可是 fn( ) 是作为对象 o 的 f 属性调用的,函数内的 this 指向取决于谁最后调用了它,故输出 “bd” 。
接着看:
o.b = {
g: fn,
prop: 'bbdd'
};
console.log(o.b.g()); // 'bbdd'
复制代码
这里更加充分说明了普通函数内的 this 指向取决于谁最后调用了它,跟是不是谁的成员没有关系,最近的引用才是最重要的!
2.2.箭头函数内的this指向取决于定义箭头函数的上下文
根据《javascript高级程序设计(第四版)》:在箭头函数中, this 引用的是定义箭头函数的上下文
window.color = 'red';
let o = {
color: 'blue'
};
let sayColor = () => console.log(this.color);
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'red'
复制代码
很明显,定义 sayColor( ) 时,是在 window 上下文中定义的,故输出 red。
再看看阮一峰老师《ECMAScript 6 入门》中的例子:
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数
setInterval(() => this.s1++, 1000);
// 普通函数
setInterval(function () {
this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
复制代码
匿名函数受 window 调用,并没有指向 Timer( )函数 ,而箭头函数中 this 指向定义时所在的作用域(Timer函数),故s1等于3
同样还有个来自《ECMAScript 6 入门》中的例子:
const cat = {
lives: 9,
jumps: () => {
this.lives--;
}
}
复制代码
调用cat.jumps()时,如果是普通函数,该方法内部的this指向cat;如果写成上面那样的箭头函数,使得this指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就是全局作用域。–《ECMAScript 6 入门》
所以对象的属性建议是使用传统的写法来定义。
2.4.构造函数中的this
先来看看代码:
function Person (name) {
this.name = name;
this.printThis = function () {
return this
};
this.sayName = function () {
return this.name;
};
}
var name='BD'
var person = new Person("bd");
person.sayName()
person.printThis()
复制代码
结果如下:
此时我们发现,构造函数 Person 内的 this 是指向刚刚创建的对象 person 的,其实这个跟 new 关键字的操作原理是息息相关的,我们来看看 new 操作符的工作过程
- 首先创建一个新对象 / var person = {}
- 接着将新对象的 proto 属性指向构造函数的原型对象 prototype
- 然后将构造函数内的 this 属性绑定到新对象中 / this -> person
- 然后执行构造函数内的代码,相当于给新对象绑定新属性
- 最后如果构造函数内有返回其他对象,该新对象就等于该对象,否则新对象就创建好了
根据以上的工作过程再结合上述的关于普通函数和箭头函数 this 指向的问题,我们理解构造函数内的 this 指向问题就轻而易举了。
2.3.apply、call和bind中的this
本文先不探讨,实质上其实是改变了被调用函数的this指向。