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
这个比较简单, 同例4foo2.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
并没有被更新
以上, 欢迎大家指正!