Skip to content

this

以下代码只要涉及到window,必须在浏览器环境下执行

this的几种模式:

  1. 方法调用模式下,this总是指向调用它所在方法的对象,this的指向与所在方法的调用位置有关,而与方法的声明位置无关
  2. 函数调用下,this指向window,调用方法没有明确对象的时候,this指向window,如setTimeout、匿名函数等
  3. 构造函数调用模式下,this指向被构造的对象
  4. applycallbind调用模式下,this指向第一个参数
  5. 箭头函数,在声明的时候绑定this,而非取决调用的位置
  6. 严格模式下,如果this没有被执行环境(execution context)定义,那thisundefined

优先级

优先级绑定方式示例
1 (最高)new 绑定new Foo()
2显式绑定foo.call(obj), foo.bind(obj)
3隐式绑定obj.foo()
4 (最低)默认绑定foo() (非严格模式下指向 window/global)
js
var obj = {
  name: 'obj',
  foo: function () {
    console.log(this)
  }
}

var f = new obj.foo() // foo {}

new优先级高于隐式绑定的

js
function foo() {
  console.log(this)
}

var bar = foo.bind('aaa')

var obj = new bar() // foo {}

方法调用模式

js
var test = function () {
  console.log(this.x)
}

var x = '2'

var obj = {
  x: '1',
  fn: test
}

obj.fn() // 1
test() // 2

可以看出,this指向调用它所在方法的对象,test方法在obj对象下,所以this指向objtestwindow对象下,所以this指向window。也可以看出:this和声明位置无关,和调用位置有关

js
let obj1 = {
  a: 222
}

let obj2 = {
  a: 111,
  fn: function () {
    console.log(this.a)
  }
}

obj1.fn = obj2.fn
obj1.fn() // 222

这个其实不难理解,虽然obj1.fn是从obj2.fn赋值而来,但是调用函数的是obj1,所以this指向obj1

函数调用模式

js
var a = 1
function fn1() {
  console.log(this.a) // 1
}

fn1() // 1

window.b = 2
function fn2() {
  console.log(this.b) // 2
}
fn2() // 可以理解为 window.fn()

需要在浏览器下运行代码,因为window是浏览器的全局对象


匿名函数,setTimeout

js
;(function () {
  console.log('1', this) // window
})()

setTimeout(() => {
  console.log('2', this) // window
}, 0)

setTimeout(function () {
  console.log('3', this) // window
}, 0)

构造函数模式

js
var flag = undefined

function Fn() {
  flag = this
}

var obj = new Fn()

console.log(flag === obj) // true

这个this指向obj,内部原理还是用applythis指向obj的,回忆一下jsnew一个对象的过程

call apply bind

call和apply作用完全一样,唯一区别就是参数

js
var obj = {
  name: '111',
  getName: function () {
    console.log(this.name)
  }
}

var otherObj = {
  name: '222'
}

var name = '333'

obj.getName() // 111
obj.getName.call() // 333
obj.getName.call(otherObj) // 222
obj.getName.apply() // 333
obj.getName.apply(otherObj) // 222
obj.getName.bind(this)() // 333
obj.getName.bind(otherObj)() // 222

不传参数,callapplybind会把this指向window,那些333由此而来

箭头函数

关于ES6的箭头函数,官方解释是:箭头函数里面的this是山下文(context),外部作用域的this就是箭头函数的this

js
let obj = {
  a: 222,
  fn: function () {
    setTimeout(() => console.log(this.a), 0)
  }
}

obj.fn() // 222

由于fn是由obj调用的,fn内部的this指向obj,箭头函数寻找this,会往外层函数fn找,发现fnthisobj,于是,这个箭头函数的this就永久的寄生在obj

如果不用箭头函数

js
let obj = {
  a: 222,
  fn: function () {
    setTimeout(function () {
      console.log(this.a)
    })
  }
}
obj.fn() // 输出:undefined (在非严格模式浏览器中可能是 100 等全局变量)

普通函数作为回调被setTimeout调用时,由于js引擎在全局环境触发的,此时内部的this会丢失,指向全局对象(windowglobal),而全局对象找不到a,所以是undefined

js
var name = 'window'
var A = {
  name: 'A',
  sayHello: () => {
    console.log(this.name)
  }
}

A.sayHello() // 输出的是 window var 会把变量挂载到 window全局变量上

那么如何永远绑定A呢

js
var name = 'window'
var A = {
  name: 'A',
  sayHello: function () {
    var s = () => console.log(this.name)
    return s // 返回的是箭头函数S
  }
}

var satHello = A.sayHello() // A.sayHello()返回的是箭头函数S
satHello() // 执行箭头函数S
  • callapplybind方法对于箭头函数来说都只是传入参数,对他的this毫无影响
js
var globalObject = this
var foo = () => this

console.log(globalObject === foo()) // true

var obj = { foo: foo }
console.log(foo.call(obj) === globalObject) // true

foo = foo.bind(obj)
console.log(foo() === globalObject) // true

严格模式

非严格模式下,this默认指向全局对象window

js
// 非严格模式下
function f() {
  return this
}

console.log(f() === window) // true

严格模式下,this为undefined

js
'use strict'
function f() {
  return this
}

console.log(f() === window) // true

示例一

js
globalThis.a = 100
function fn() {
  return {
    a: 200,
    m: function () {
      console.log(this.a)
    },
    n: () => {
      console.log(this.a)
    },
    k: function () {
      return function () {
        console.log(this.a)
      }
    }
  }
}

const fn0 = fn()

fn0.m() // 200 this指向 { a, m, n}
fn0.n() // 100 this指向 globalThis
fn0.k()() // 100 this指向globalThis

const context = { a: 300 }
const fn1 = fn().call(context) // 改变 箭头函数 this指向
fn1.m() // 200 this指向 { a, m, n }
fn1.n() // 300 this指向 context
fn1.k().call(context) // 300 this指向 context

前三个就讲最后一个

fn0.k()返回了一个匿名普通函数。随后执行()独立函数调用。在非严格模式下,独立调用的函数this始终指向全局对象

fn1.m()

关键点:fn().call(context)只是在"生产"这个对象的过程改变了this指向,并没有把生产出来的m方法锁死在context上,所以当fn1.m()的时候,m内部的this指向fn1,而fn1长:{ a: 200, m: function...}

fn1.n()

m是普通函数,n是箭头函数,它在定义的那一刻就看准了当时的this(也就是fn执行时的this),因为用了.call(context)执行了fn,导致n定义时的环境就是context。所以输出了300

fn1.k():

fn1.k() 返回一个普通函数,接着我们使用 .call(context) 显式地将这个返回函数的 this 指定为 context,所以输出 300。

示例2

js
globalThis.a = 100

const person1 = {
  a: 'person1',
  foo1: function () {
    console.log(this.a)
  },
  foo2: () => {
    console.log(this.a)
  },
  foo3: function () {
    return function () {
      console.log(this.a)
    }
  },
}

const person2 = {
  a: 'person2',
}

person1.foo1() // person1
person1.foo1.call(person2) // person2

person1.foo2() // 100
person1.foo2.call(person2) // 100

person1.foo3()() // 100
person1.foo3.call(person2)() // 100
  • person1.foo2().call(person2):箭头函数完全无视callapplybind等方法,this指向外层作用域的this,即globalThis,因为箭头函数被创建时,已经把this固定了
  • foo3.call(person2)只改变了foo3内部的this,但返回的是全新创建的函数,这个函数并没有绑定person2,所以直接调用时this是全局对象->100

示例3

js
let length = 10

function fn() {
  return this.length + 1
}

const obj = {
  length: 5,
  test1: function () {
    return fn()
  }
}

obj.test2 = fn

console.log(obj.test1()) // window 的窗口数
console.log(fn() === obj.test2()) // false