JS中的this

126 阅读4分钟

关于this的理解

this是在运行时进行绑定的,而不在编写绑定,他的上下文取决于函数调用时的各种条件。this的绑定和函数的声明位置没有关系,只取决于函数的调用方式。 ——《你不知道的javascript》

this实际上是在函数被调用时进行绑定,他指向什么完全取决于函数在哪里被调用(或者说有没有被某个对象调用)

1.关于this的绑定规则

1.1默认绑定

独立函数调用,即直接调用函数,此时函数内 this 默认指向全局对象

function a() {
	console.log(this) // 指向 window
}
a()

1.2隐式绑定

在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接(隐式)绑定到这个对象上。当函数作为某个对象的方法进行调用,此时函数内 this 指向调用他的那个对象,

function a() {
  console.log(this) // 直接调用默认指向全局对象
}
var obj = {
  a: a // 把 函数a 赋值给 obj.a,可以理解为 obj.a = a
}
obj.a() // 这里 函数a 作为 obj对象 的方法被调用

再看一下这种情况

function a() {
  console.log(this) // 直接调用默认指向全局对象
}

var obj1 = {
  a: a,
  obj2: obj2
}

var obj2 = {
  a: a
}

obj1.obj2.a() // 这里其实是调用的 obj2对象 的 a方法

对象属性调用链中只有位置在最后的属性会影响 this 的指向;

1.2.1隐式绑定的丢失

function a() {
  console.log(this) // 指向全局对象
}
var obj = {
  a: a
}
var func = obj.a // func 保存着 obj.a 的引用,实际上 func 保存的就是 函数a 本身
func() // 这里就相当于直接调用函数,而不是作为对象的方法进行调用

最常见的隐式绑定丢失就是回调函数了

function func(callback) {
  callback() // 直接执行,输出 window
}

var obj = {
  a: function () {
    console.log(this)
  }
}

func(obj.a) 

函数的参数传递其实是一种隐式赋值,实参赋值给形参; obj.a本身就是一个函数 赋值给了 callback ,目前callback就是一个普通函数, 没有了对象的包装 因此应用 默认绑定 的规则,最后输出的是全局对象

// 常见的 setTimeout 的回调函数 this 为什么指向全局对象就是如此
// 这是一段伪代码
function setTimeout(fn, delay) {
  // 等待 delay 毫秒
  ...
  fn() // 执行回调,应用默认绑定的规则
}

1.3显示绑定

JS所有的函数都提供了 callapplybind 方法,这些方法可以显示的指定函数在调用时内部的 this 指向,他们的第一个参数都是提供一个对象绑定到指定函数的 this,第二个参数略有区别

function fn(a, b) {
  console.log(this.num + a + b)
}	

var obj = {
  num: 1
}

// call函数第一个之后的所有参数都作为执行目标函数时的参数
fn.call(obj, 2, 3) // 1 + 2 + 3

// apply函数第二参数需要一个数组,将数组内的元素作为执行目标函数时的参数
fn.apply(obj, [2, 3]) // 1 + 2 + 3

// bind函数返回一个新的函数,返回函数内部this总是指向第一个参数,之后的参数作为返回函数的预传参,在执行的时候自动添加
var bindFn = fn.bind(obj ,2) // 2 作为预传参,每次执行都会自动添加

bindFn(3) // 1 + 2 + 3

1.4new绑定

使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

1.创建(或者说构造)一个全新的对象。

2.这个新对象会被执行[[原型]]连接。

3.这个新对象会绑定到函数内部的this。

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

new操作符可以改变 bind返回函数内部的 this 指向

function func(n) {
	this.a = n
}

var obj1 = {}

var fn = func.bind(obj)
fn(2018) // obj1: {a: 2018}
fn(2019) // obj1: {a: 2019}

var obj2 = new fn(5201314) // 改变了 fn 函数内部this的指向
console.log(obj2.a) // 5201314

2.如何判断 this 所指向的是谁

现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的顺序来进行判断:

1.函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。

var bar = new foo()

2.函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。

var bar = foo.call(obj2)

3.函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。

var bar = obj1.foo()

4.如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。

var bar = foo()

本文参考自:《你不知道的Javascript(上卷)》