this指向的几种情况

this 指向的几种情况

  • 全局环境下的 this 指向 window
  • 上下文调用中的 this , 谁调用 this 就指向谁
  • bind/call/apply 改变 this 指向
  • 构造函数中的 this
  • 箭头函数中的 this

1. 全局环境下的 this 指向 window

// 例1

function foo() {
  console.log(this) // window
}
foo()
复制代码

上面这个例子不用说大家应该也明白 this 指向的是 window, 函数 foo 正是在全局的环境下执行的, 但是值得注意一下, 在严格模式中, 这里的 this 就是 undefined, 如下:

// 例2

"use strict"
function foo() {
  console.log(this) // undefined
}
foo()
复制代码

再接着瞅瞅下面这个例子:

// 例3

var obj = {
  foo: function () {
    console.log(this) // window
  },
}
var foo1 = obj.foo
foo1()
复制代码

例3打印的是 window, obj.foo 的值是一个函数, 现在 foo1 被赋值了一个函数, foo1 执行的场景其实就是例1

2. 上下文调用中的 this , 谁调用 this 就指向谁

我们基于例3改一下

// 例4

var obj = {
  foo: function () {
    console.log(this) // {foo: ƒ}
  },
}
obj.foo()
复制代码

秉持着谁调用 this 就指向谁, 例4是 obj 调用了 foo 函数, 所以函数内部的 this 指向的就是 obj 对象本身

// 例5

var obj = {
  name: 'obj name',
  foo: function () {
    console.log(this)
  },
  inner: {
    name: 'inner name',
    fn: function () {
      console.log(this) // {name: "inner name", fn: ƒ}
    }
  }
}
obj.inner.fn()
复制代码

我们可以看到 this 指向的是 inner, 在这种嵌套的关系中, this 指向最后调用它的对象

如果下面这个例子搞懂了, 那我觉得第二种情况的 this 指向大家基本应该是整明白了

// 例6

var name = 'outer'
var foo1 = {
  name: 'foo1',
  fn: function () {
    console.log(this.name)
  }
}
var foo2 = {
  name: 'foo2',
  fn: function(fun) {
    fun()
  }
}
var fun = foo1.fn
fun() // outer
foo1.fn() // foo1
foo2.fn(foo1.fn) // outer
foo2.fn = foo1.fn
foo2.fn() // foo2
复制代码
  • 上面 fun() 执行其实就是例3的情况, fun 拿到 foo1.fn 的函数引用之后, 执行的环境是在全局, 所以打印的是 outer
  • foo1.fn() 打印 foo1 这个比较简单, 同例4
  • foo2.fn(foo1.fn) 我觉得大部人应该会折在这个上面, foo2.fn 指向的函数接收一个函数参数, 所以 foo1.fn 作为参数传进去的时候, 在 foo2.fn 指向的函数体内直接执行了,注意是 直接, 所以执行的环境是在全局, 有谁调用这个函数(参数)去执行吗? 并没有! 你品~ 你细品~
  • 最后一种打印情况其实就是例3和例4的一个结合版, 只不过这里的赋值对象不是赋值给一个变量, 还是 foo2 对象上的 fn, 此时的 foo2 就是下面这种情况, 这么一整之后, 再看 foo2.fn(), 打印的结果是不是就显而易见了
    var foo2 = {
      name: 'foo2',
      fn: function () {
        console.log(this.name)
      }
    }
    复制代码

3. bind/call/apply 改变 this 指向

下面几个都是把 this 指向了 foo2 这个对象

// 例7

var foo1 = {
  name: 'foo1',
  fn: function () {
    console.log(this.name)
  }
}
var foo2 = {
  name: 'foo2',
}
foo1.fn.call(foo2) // foo2
foo1.fn.bind(foo2)() // foo2
foo1.fn.apply(foo2) // foo2
复制代码

4. 构造函数中的 this

// 例8

var foo = 123
function print({
    this.foo = 234
    console.log(foo)
}

new print() // 123
复制代码

我们可以先来了解一下构造函数的原理, 只要我们 new 了一个构造函数之后, 就会有以下三步的隐式操作

  • 在函数内部隐式创建了一个 this
  • 执行 this.xxx = xxx, 为其添加属性方法
  • 返回这个 this
    function print({
        // var this = Object.create(print.prototype)
        this.foo = 234
        console.log(foo)
        // return this
    }
    new print()
    复制代码

经过这么一看, 打印的结果是不是显而易见了, 此时的 this 是隐式创建出来的, 并不是指向全局哦~

var foo = 123
function print({
    this.foo = 234
    console.log(foo)
}

print() // 234
复制代码

如果是这种情况的话, 打印的就是 234, 此时的 this 指向 window, this.foo = 234 改变了全部变量 foo 的值

上面是隐式的返回一个对象, 如果显示的返回一个值会怎么样?

  • 显示的返回一个对象, 此时 print1 的值就是一个空对象, 自然打印上面的属性值返回的就是 undefined
    // 例9
    
    var foo = 123
    function print({
        this.foo = 234
        return {}
    }
    
    var print1 = new print()
    console.log(print1.foo) // undefined
    复制代码
  • 显示的返回一个原始值, print1 还是目标实例
    // 例10
    
    var foo = 123
    function print({
        this.foo = 234
        return 1
    }
    
    var print1 = new print()
    console.log(print1.foo) // 234
    复制代码

5. 箭头函数中的 this

对于普通函数来说, 内部的 this 指向函数运行时所在的对象, 但是对于箭头函数是不成立的, 它是没有自己的 this 对象, 内部的 this 就是定义时上层作用域中 this, 所以箭头函数内部的 this 指向是固定的, 相比之下, 普通函数的 this 指向是可变的

我们可以看下在 babel 转义下的 this 就能更好的理解上面这段话

// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}
复制代码

这样能很清楚的看出, 箭头函数内的 this 是引用的外层 this

// 例11

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}
var id = 21;
foo(); // 21
复制代码

由于箭头函数的 this 总是指向函数定义生效时所在的对象, 所以这边打印的是 21

// 例11

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭头函数
  setInterval(() => this.s1++, 1000);
  // 普通函数
  setInterval(function () {
    this.s2++;
  }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100); // 3
setTimeout(() => console.log('s2: ', timer.s2), 3100); // 0
复制代码

3秒之后, 箭头函数的 this 一直指向的就是定义时所在的作用域 —— Timer函数, 而普通函数的 this 指向是是函数运行时所在的作用域 —— window, 因此, timer 内部的 s1 被更新了3次, s2 并没有被更新

以上, 欢迎大家指正!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享