JavaScript中this指向规则

时间:2021-1-8 作者:admin

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?

  1. 函数是否存在new 创建(new 绑定),如果存在则this绑定是新创建的对象
  2. 函数是否通过call,apply,bind(显式绑定),如果是怎绑定到指定的对象,注意:如果是传递是null,undefined作为this,则会忽略,会使用默认绑定
  3. 函数是否在某个对象上调用: object.func()?如果是,则绑定到这个对象上
  4. 如果以上都不是则使用默认绑定,在严模式下绑定到undefined,非严格绑定到window

法外狂徒:箭头函数

ES6中的箭头函数的this是继承于外层函数或者全局作用域的,不受上面4条规则限制.

参考书籍:你不知道的javascript(上)

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。