this 指向

this 是什么

理解 this 之前, 先纠正一个观点,this 既不指向函数自身,也不指函数的词法作用域。如果仅通过 this 的英文解释,太容易产生误导了。它实际是在函数被调用时才发生的绑定,也就是说this 具体指向什么,取决于你是怎么调用的函数

!> this 是在调用时被绑定的,取决于函数的调用位置(也就是调用方法)

this 的四种绑定规则

this 的 4 种绑定规则分别是:默认绑定、隐式绑定、显示绑定、new 绑定。优先级从低到高。

1. 默认绑定

什么叫默认绑定,即没有其他绑定规则存在时的默认规则。这也是函数调用中最常用的规则。

来看这段代码:

function foo() {
  console.log(this.a);
}
var a = 2;
foo(); //打印的是什么?
// foo() 打印的结果是2。

因为 foo() 是直接调用的(独立函数调用),没有应用其他的绑定规则,这里进行了默认绑定,将全局对象绑定 this 上,所以 this.a 就解析成了全局变量中的 a,即 2。

2. 隐式绑定

除了直接对函数进行调用外,有些情况是,函数的调用是在某个对象上触发的,即调用位置上存在上下文对象。

function foo() {
  console.log(this.a);
}
var a = 2;
var obj = {
  a: 3,
  foo: foo
};
obj.foo(); // ?
// obj.foo() 打印的结果是3。

这里foo函数被当做引用属性,被添加到obj对象上。这里的调用过程是这样的:

获取obj.foo属性 -> 根据引用关系找到foo函数,执行调用

所以这里对foo的调用存在上下文对象objthis进行了隐式绑定,即this绑定到了obj上,所以this.a被解析成了obj.a,即 3。

隐式丢失(函数别名)

!> 注意:这里存在一个陷阱,大家在分析调用过程时,要特别小心

先看个代码:

function foo() {
  console.log(this.a);
}
var a = 2;
var obj = {
  a: 3,
  foo: foo
};
var bar = obj.foo;
bar(); //?
// bar() 打印的结果是2。

为什么会这样,obj.foo 赋值给 bar,那调用 bar() 为什么没有触发隐式绑定,使用的是默认绑定呢。

这里有个概念要理解清楚,obj.foo 是引用属性,赋值给 bar 的实际上就是 foo 函数(即:bar 指向 foo 本身)。

那么,实际的调用关系是:通过 bar 找到 foo 函数,进行调用。整个调用过程并没有 obj 的参数,所以是默认绑定,全局属性 a。

隐式丢失(回调函数)

function foo() {
  console.log(this.a);
}
var a = 2;
var obj = {
  a: 3,
  foo: foo
};
setTimeout(obj.foo, 100); // ?
// 打印的结果是2。

同样的道理,虽然参传是obj.foo,因为是引用关系,所以传参实际上传的就是foo对象本身的引用。对于setTimeout的调用,还是 setTimeout -> 获取参数中foo的引用参数 -> 执行 foo 函数,中间没有obj的参与。这里依旧进行的是默认绑定

3. 显示绑定

相对隐式绑定,this 值在调用过程中会动态变化,可是我们就想绑定指定的对象,这时就用到了显示绑定。

显示绑定主要是通过改变对象的 prototype 关联对象。具体使用上,可以通过这两个方法 call(…)或 apply(…)来实现(大多数函数及自己创建的函数默认都提供这两个方法)。

call 与 apply 是同样的作用,区别只是其他参数的设置上

function foo() {
    console.log( this.a );
}

var a = 2;

var obj1 = {
    a: 3,
};

var obj2 = {
    a: 4,
};
foo.call( obj1 ); // ?
foo.call( obj2 ); // ?
打印的结果是3, 4

这里因为显示的申明了要绑定的对象,所以 this 就被绑定到了 obj 上,打印的结果自然就是 obj1.a 和 obj2.a。

4. new 绑定

js 中的 new 操作符,和其他语言中(如 JAVA)的 new 机制是不一样的。js 中,它就是一个普通函数调用,只是被 new 修饰了而已。

使用 new 来调用函数,会自动执行如下操作:

如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。

看个代码:

function foo(a) {
  this.a = a;
}
var a = 2;
var bar1 = new foo(3);
console.log(bar1.a); // ?
var bar2 = new foo(4);
console.log(bar2.a); // ?
// 最后一个打印是3, 4。

因为每次调用生成的是全新的对象,该对象又会自动绑定到 this 上,所以答案显而易见。

绑定规则优先级

上面也说过,这里在重复一下。优先级是这样的,以按照下面的顺序来进行判断:
数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。
数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是 指定的对象。
数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象。
果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到 全局对象。

规则例外

在显示绑定中,对于 nullundefined 的绑定将不会生效。 代码如下:

function foo() {
  console.log(this.a);
}
foo.call(null); // 2
foo.call(undefined); // 2

这种情况主要是用在不关心 this 的具体绑定对象(用来忽略 this),而传入 null 实际上会进行默认绑定,导致函数中可能会使用到全局变量,与预期不符。

所以对于要忽略 this 的情况,可以传入一个空对象 ø,该对象通过 Object.create(null)创建。这里不用{}的原因是,ø 是真正意义上的空对象,它不创建 Object.prototype 委托,{}和普通对象一样,有原型链委托关系。

普通函数 this

// 1. 在dom中使用this
var btn = document.querySelector("button");
btn.addEventListener("click", function() {
  console.log(this);
});
// 2. 构造函数
function Cat() {
  this.name = name;
}

var cat = new Cat("yy");

// 3. 一般函数
function fun1() {
  console.log(this);
}
fun1();

var obj1 = {
  // 4. 对象上的函数,this指向该对象
  name: "obj1",
  fun: function(a, b) {
    console.log(this, a, b);
  }
};
obj1.fun();

// window.setTimeout(function () {
// 	console.log('setTimeout', this)
// }, 1000);

//匿名函数,默认指向window
(function() {
  console.log("匿名", this);
})();

箭头函数 this

通过 => 而不是function创建的函数,叫做箭头函数。它的 this 绑定取决于外层(函数或全局)作用域。

箭头函数中的 this 指向的是定义时的 this,而不是执行时的 this

var obj = {
  getName() {}
};

//箭头函数 ()=>{}
//如果函数只有返回值,可以省略花括号和return 例:(a+b) =>a+b
//如果形参只有一个,可以省略小括号,例如 a=>a+1
var fun1 = (a, b) => {
  console.log("fun1", this);
  return a + b;
};
fun1(1, 2);

var obj1 = {
  name: "obj",
  getName: function() {
    console.log(1, this); //obj1
    var that = this;
    //调用fun1函数,fun1执行作用域 fun1 ->window
    //执行环境就是window
    fun1(); //箭头函数 window
    //正常写法
    setTimeout(function() {
      console.log("function", this); //window
    }, 100);
    //箭头函数
    setTimeout(() => {
      //箭头函数自己没有this
      //环境 getName -> window
      //箭头函数 调用的是that
      //this 和执行的上下文有关
      console.log("箭头函数", this); //obj1
    }, 100);
  }
};
obj1.getName();
var obj3 = {
  name: "obj3",
  getName: () => {
    console.log("obj3", this);
  }
};
obj3.getName();
Last Updated:
Contributors: zerojs