this 指向谁?
多数情况下,this指向调用它所在方法的那个对象。说的通俗一点就是谁调用的函数,就指向谁。当调用方法没有明确对象时,this就指向全局对象,浏览器中指向window,在node中指向Global。(严格模式下指向undefined)
this的指向是在调用时决定的,而不是在书写的时候决定的,这一点跟闭包正好反着。
js 是词法作用域模型,无论我是一个对象也好,一个方法也好,它的生命周期只和我们声明它的位置有关。我把它写在哪个位置,它就活在哪个位置。
我们来看这样一个例子:
// 声明位置
var me = {
name: '青莲使者',
hello: function() {
console.log(`你好,我是${this.name}`)
}
}
var you = {
name: 'xiaoming',
hello: me.hello
}
// 调用位置
me.hello() // 青莲使者
you.hello() // xiaoming
看到 hello 在代码中分别被 me 和 you 调用了,因此两次调用的 this 也就分别指向了 me 和 you,这没毛病。我们稍微把这个例子改一下:
// 声明位置
var me = {
name: '青莲使者',
hello: function() {
console.log(`你好,我是${this.name}`)
}
}
var name = 'BigBear'
var hello = me.hello
// 调用位置
me.hello() // 你好,我是青莲使者
这里我们直接调用 hello 的时候,输出了全局的 name 变量。我们可以理解为是因为 name 和 hello 都挂在在全局对象 window 上,所以 hello () 其实等价于 window.hello (),此时 hello 方法内部的 this 自然指向 window,于是this.name 就等价于 window.name。这也没毛病。
我们再改一下:
// 声明位置
var me = {
name: '青莲使者',
hello: function() {
console.log(`你好,我是${this.name}`)
}
}
var you = {
name: 'xiaoming',
hello: function() {
var targetFunc = me.hello targetFunc()
}
}
var name = 'BigBear'
// 调用位置
you.hello()
上面这段代码,大家先给自己 1 分钟的时间,在脑子里面跑一下。
OK,现在我默认你心里已经有了一个自己的答案了(还没有跑完的同学不要急着往下看哈,自觉暂停一下,先有 一个自己的结论再来看我们的解析,收获会更大)。
调用位置输出的结果是 BigBear—— 竟然不是 xiaoming?的确,我们打眼看过去,直觉上肯定会认为是 you 这个对象在调用 hello 方法、进而调用 targetFunc,所以此时 this 肯定指向 you 对象啊!为啥会输出一个 window 上的name 呢?
我们再复习一下我们开头那句话 ——“this 指向调用它所在方法的那个对象”。
回头看我们例题中的 targetFunc 这个方法,大家之所以第一直觉会认为它的 this 应该指向 you 这个对象,其实还是因为把 “声明位置” 和 “调用位置” 混淆了。我们看到虽然 targetFunc 是在 you 对象的 hello 方法里声明的,但是在调用它的时候,我们是不是没有给 targetFunc 指明任何一个对象作为它前缀? 所以 you 对象的 this 并不会神奇地自动传入 targetFunc 里,js 引擎仍然会认为 targetFunc 是一个挂载在 window 上的方法,进而把 this 指向window 对象。
在面试命题过程中,this 指向问题如果想往难了出, 就会像楼上这样把声明位置和调用位置故意揉在一起,考验你对两者的区分能力。 但只要各位能记住,“不管方法被书写在哪个位置,它的 this 只会跟着它的调用方走” 这个核心原则,就一定不会出错。
特殊情境下的 **this** 指向
TIP 👉在三种特殊情境下,this 会 100% 指向 window:
- 立即执行函数(IIFE)
- setTimeout 中传入的函数
- setInterval 中传入的函数 也就是说大家在做 this 指向题的时候,第一步其实倒不该是老老实实去看 this 所在的函数属于哪个对象,而是应该先定位 this 是否出现在了以上三种类型的函数里面。如果是,那么想也不想,直接去对应 window 就好了~
TIP 👉 隐式绑定的
this
指的是调用堆栈的上一级(.
前面一个)
function fn () {
console.log(this.a)
}
const obj1 = {
a: 1,
fn
}
const obj2 = {
a: 2,
obj1
}
obj2.obj1.fn() // console what ? 1
TIP 👉显式绑定(
call
、bind
、apply
)
function fn () {
console.log(this.a)
}
const obj = {
a: 100
}
fn.call(obj) // console what ? 100
bind
这里单拎出来,因为面试常常问
function fn() {
console.log(this)
}
// 为啥可以绑定基本类型 ?
// boxing(装箱) -> (1 ----> Number(1))
// bind 只看第一个 bind(堆栈的上下文,上一个,写的顺序来看就是第一个)
fn.bind(1).bind(2)() // console what ? 1
TIP 👉 如果函数
constructor
里没有返回对象的话,this
指向的是new
之后得到的实例
function foo(a) {
this.a = a
}
const f = new foo(2)
f.a // console what? 2
// ------------------------- 变 ---------------------------
function bar(a) {
this.a = a
return {
a: 100
}
}
const b = new bar(3)
b.a // console what ? 100
这主要是考察new关键字的实现
import _ from 'lodash';
function myNew(fn, ...args) {
// fn 必须是一个函数
if (typeof fn !== 'function') throw new Error('fn must be a function.')
// es6 new.target
myNew.target = fn
// 原型继承
const temp = Object.create(fn.prototype) // 步骤 1. 2.
// fn执行绑定 this 环境
const res = fn.apply(temp, ...args) // 步骤 3.
// 如果该函数没有返回对象,则返回this。
return _.isObject(res) ? res : temp
}
TIP 👉 箭头函数
箭头函数比较特殊,它和严格模式,非严格模式都没关系,认“死理“,就认“词法作用域”,所以说箭头函数中的this,和你如何调用没有关系,由你书写它的位置决定
function fn() {
return {
b: () => {
console.log(this)
}
}
}
fn().b() // console what? window
fn().b.bind(1)() // console what? window
fn.bind(2)().b.bind(3)() // console what? 2
实战一波
// 1.
function foo() {
console.log( this.a ) // console what 2
}
var a = 2;
(function(){
"use strict" // 迷惑大家的
foo();
})();
// 2.
var name="the window"
var object={
name: "My Object",
getName: function(){
return this.name
}
}
object.getName() // console what ? My Object
(object.getName)() // console what ? My Object
(object.getName = object.getName)() // console what ? ()里面有运算,赋值丢失this指向 window
(object.getName, object.getName)() // console what ? window
因为根据 MDN ,逗号操作符也是返回最后一个操作数的值,也是函数本身, 所以是window
// 3.
function a(x){
this.x = x
return this
}
var x = a(5) // 替换为 let 再试试
var y = a(6) // 替换为 let 再试试 // 再换回 var,但是去掉 y 的情况,再试试
console.log(x.x) // console what ? undefined
console.log(y.x) // 6
等价于
window.x = 5;
window.x = window;
window.x = 6;
window.y = window;
console.log(x.x) // undefined
console.log(y.x) // 6