JavaScript关于this指向问题
JavaScript中this指向问题是一个值得深入研究的问题,在阅读你不知道的javascript感觉终于理解了this到底是如何指向的,接着和大家分享下
this指向的是它自身吗?
请看下面代码:
function foo(num) {
console.log('foo ' + num);
// 记录foo被调用的次数
this.count++;
}
foo.count = 0;
for (let i=0; i<10; i++) {
foo(i);
}
console.log(foo.count); // 0
我们发现函数确实执行了10次,但是foo.count却是0. 难道foo中的this.count 和 foo.count不是同一个吗?确实不是同一个,请看下面代码
function foo(num) {
console.log('foo ' + num);
// 记录foo被调用的次数
this.count++;
console.log(this.count);
}
foo.count = 0;
for (let i=0; i<10; i++) {
foo(i);
}
console.log(foo.count); // 0
- 我们在foo中输出this.count 为 NaN。
- 外层的foo.count为0
- 所以this.count 和 foo.count并不是同一个
- foo 中 this并非是指向foo自身
那么this到底是指向什么呢?
this的指向是取决于函数的调用方式,因为this并不是在函数编写时绑定的,是由函数调用时决定this的指向(也可以叫做this绑定)
- 当函数调用时,JS引擎会为函数创建一个执行上下文(context),
- 执行上下文会记录函数相关的一些信息(函数调用栈,函数的调用方法,传入的参数等),
- this则是执行上下文中的一个属性。
如何确定函数的调用位置?
我们已经知道this的指向却决于函数的调用方式,那么我们需要先确定函数的调用位置(不是声明的位置)
函数的调用会形成一个调用栈,如下代码
function f1() {
console.log('f1');
debugger;
f2();// f2调用位置
}
function f2() {
console.log('f2');
debugger;
f3();// f3调用位置
}
function f3() {
console.log('f3');
debugger;
f4();// f4调用位置
}
function f4() {
console.log('f4');
debugger;
}
f1(); // f1调用位置
借助chrome开发者工具我们可以找到f1,f2,f3,f4的调用栈在Call Stack中
点击对应的函数名字可以定位到函数调用位置
确定位置后,this是如何绑定对象呢?
this的绑定规则有4中,1.默认绑定 2.隐试绑定 3.显式绑定 4.new 绑定
1.默认绑定:在我们身边经常可以看到
function foo () {
console.log(this.a);
}
var a = 1;
foo(); // 1
//上面代码相当于 如下写法
window.foo = function () {
console.log(this.a);
}
window.a = 1;
window.foo();
- a 定义成全局变量, foo 也属于全局下的,
- 所以这里的this被默认绑定到全局对象window下,this指向是window,输出为 1,
- 注意这里foo() 调用位置在全局环境下
- 需要注意的是在严格模式下(use strict)this无法绑定到window,会报错
2.隐式绑定
隐式绑定通常常发生在‘.’调用下
function foo() {
console.log(this.a);
}
var obj = {
a: 1,
foo: foo,
};
obj.foo();// 1
foo调用位置在obj.foo(),这里this指向obj,正常来说foo是属于window
function foo() {
console.log(this.a, this);
}
var obj = {
a: 1,
foo: foo,
};
window.foo();
obj.foo();// 1
、
- 从输出结果可以看出,直接调用foo()或者window.foo() 输出this是window,
- 而通过obj.foo()调用输出this是obj,
- 这里foo的this就是发生隐式绑定.
- 所以这里的this指向取决于它 调用位置 和 调用方式
3.显式绑定
在js中显式绑定通常是通过调用call,apply方法来强制改变this的指向,这种方式被称为显示绑定
function foo() {
console.log( this.a );
}
var obj = {
a:2
};
// 将foo的this绑定到obj上,此时foo的this指向obj,因此this.a 相当于 obj.a
foo.call( obj ); // 2
值得注意的是如果绑定的是原始值,js引擎会进行自动装箱操作,如下
上面例子中我们发现每次call都会改变this的指向,那么如何能保证foo函数this的指向不变呢?
function foo() {
console.log( this.a );
}
var obj = {
a:2
};
// 将foo包装一层,每次调用都会绑定obj
var bar = function() {
foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// 硬绑定的 bar 不可能再修改它的 this
bar.call( window ); // 2
以上也叫做 硬绑定
4.new 绑定
使用new 构建对象的时候this会被绑定到对象上
function foo(a) {
this.a = a;
}
const f1 = new foo(1);
console.log(f1.a) // 1
至此我们已经知道this绑定为3步:调用位置=>调用方式=>绑定规则
绑定规则又有4个规则,分别是: 默认绑定、隐式绑定、显式绑定、new绑定
那么倒是使用哪个规则来绑定呢?取决于规则的优先级,规则优先级从左到右为:new绑定>显式绑定>隐式绑定>默认绑定
总结:如何判断this?
- 函数是否存在new 创建(new 绑定),如果存在则this绑定是新创建的对象
- 函数是否通过call,apply,bind(显式绑定),如果是怎绑定到指定的对象,注意:如果是传递是null,undefined作为this,则会忽略,会使用默认绑定
- 函数是否在某个对象上调用: object.func()?如果是,则绑定到这个对象上
- 如果以上都不是则使用默认绑定,在严模式下绑定到undefined,非严格绑定到window
法外狂徒:箭头函数
ES6中的箭头函数的this是继承于外层函数或者全局作用域的,不受上面4条规则限制.
参考书籍:你不知道的javascript(上)