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
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
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(上)