全局作用域中的this永远为global对象,在浏览器环境下即为window对象。

函数中的this根据函数的不同调用方式分下列4中情况:

1、直接调用(Function Invocation Pattern)

例如:

function foo () {
  console.log(this)
}
foo() // 输出:window

注意,此时的 foo 是作为单独的变量出现的,而不是对象的属性。

在这种模式下,函数中的this永远是global对象,在浏览器环境下即window对象。

2、作为方法调用(Method Invocation Pattern)

示例:

var obj = {
  name: 'papi',
  say: function () {
    console.log(this)
  }
}

obj.say() // 输出:obj

这种情况下函数作为对象的属性被调用,就必然会运用到 . 操作符或者 []操作符,这种情况下,函数中的 this 等于 .[ 前面的那个对象,上面的例子中即为obj。 obj['say']() 这种调用方式输出的 this 也是 obj。

看下面一个例子:

var length = 100
    
function func () {
  console.log(this.length)
}

var o = {
  length: 10,
  add: function (func) {
    func()
    arguments[0]()
  }
}
o.add(func)

执行 o.add 方法并把全局变量 func 作为参数传入。

add 方法内部执行传进来的 func 函数,这种调用属于函数直接调用,是上述第1种情况,因此函数中的 this 指向 windowwindow.length即全局变量 length,值为100。

arguments[0]() 这一条语句是取到了函数的第一个参数(显然是 func 函数)并执行,这种调用方式等效于 func() 这种调用方式?答案是否定的!众所周知,函数中的 arguments 是一个类数组对象,它具有 length 属性,其值为参数的个数,上例中只传入了一个参数,因此 arguments.length === 1,深入 arguments 对象,展开大体是以下结构:

{
  0: function func () {},
  'length': 1
}

因此,arguments[0]() 这种调用方式其实是调用了arguments对象的 0 方法!而这个所谓的 0 方法从何而来呢?就是通过将 func 函数复制给它得来的。因此,方法中的 this 应该指向 arguments 对象,因此输出 1。

如果执行 o.add(func, 'hahaha') 呢?没错,输出2,因为此时 arguments.length === 2

3、作为构造函数调用(Constructor Pattern)

new Foo() 这种调用方式下,Foo 内部的 this 永远指向 new 操作符返回的对象

function Book (name) {
  this.name = name
  
  console.log(this)
}

var book = new Book('百年孤独') // 输出 {name: '百年孤独'}

4、apply调用(Apply Pattern)

function speak() {
  console.log(this)
}

var mao = {
  name: 'mao'
}

speak.call(mao)       // 输出 {name: 'mao'}
speak.apply(mao)      // 输出 {name: 'mao'}
speak.bind(mao)()     // 输出 {name: 'mao'}
speak.call()          // 输出 window
speak.call(null)      // 输出 window
speak.call(undefined) // 输出 window

函数的 callapplybind 方法的第一个参数就是指定函数中 this 值的,如果不传参或传入 nullundefined 则将函数中的 this 指向global,在浏览器环境下即为 window对象。

5、ES6箭头函数中的this

ES6的箭头函数中的 this 与ES5中的 this 行为不一样,箭头函数中的 this 值是在函数定义时就决定的,它不会随着运行时的上下文不同发生改变。

例如:

function printThis() {
  let print = function () {
    console.log(this)
  }
  print()
}

printThis.call([1])
printThis.call([2])

根据第一种情形,上述代码两次调用均输出 window,如果把 print 函数定义为箭头函数,则 print 函数内部的 this 值会在定义 print 函数时决定,示例如下:

function printThis() {
  let print = () => {
    console.log(this)
  }
  print()
}

printThis.call([1]) // [1]

上例中, printThis.call([1]) 调用 printThis 函数时将其内部的 this 值设为 [1]

printThis 函数内部定义了一个箭头函数 print,箭头函数中的 this,就是其外层函数 printThisthis,也就是 [1]

随后, print() 语句执行刚刚定义的函数,因此输出 [1]

总结

弄清楚以上5种JS函数的调用方式,函数中 this 的指向就很清楚了。

–END–