前言
在 JavaScript 的江湖里,this 就像一把“隐形匕首”——看不见、摸不着,却总在关键时刻给你来一下。初学者以为它指向“当前对象”,结果一运行,控制台噼里啪啦全是 undefined;老鸟自信满满写箭头函数,一不留神还是踩了“丢失上下文”的坑。
今天,我们就把 this 从“玄学”拉回“科学”,带你从编译器视角拆给它“验尸”——看到底是谁偷走了你的上下文。读完这篇文章,你将获得一张“this 指向速查表”,贴在键盘边,再也不用担心“谁调的我”!
为什么要有 this ?
简而言之,this 提供了一种更优雅的方式来隐式的传递一个对象引用,可以让代码更简洁易于复用 ,剩下的交给代码来解释:当我们要看懂下面这一串代码时,需要在函数形参和实参中不断来回切换,作为初学者看下面代码都有可能会被绕晕,这还只是两个函数的嵌套,如果是项目开发过程中无数个函数嵌套,别说是人 ,AI 都有可能被绕晕。(注意toUpperCase 将字符串 'Tom' 转换为大写,输出 'TOM')
function identify(context) {
return context.name.toUpperCase()
}
function speek(context) {
var greeting = 'hello, I am ' + identify(context)
console.log(greeting);
}
var me = {
name: 'Tom'
}
speek(me)
为了让代码更简洁易于复用,我们就能用 this 来解决,直接省去形参(如下)
function identify() {
return this.name.toUpperCase()
}
function speek() {
var greeting = 'Hello, I am ' + identify.call(this)
console.log(greeting);
}
var me = {
name: 'Tom'
}
speek.call(me)
两段代码打印结果是一样的,但明显下面代码更加简练
this 用在哪?以及 this 的绑定规则
this用在全局作用域和函数作用域, 在全局作用域里面 this == window ,在函数作用域里面就得看具体绑定规则(this是一个代词,用在不同的地方代指不同的值)
this 的绑定规则有四种它们分别是
1. 默认绑定 --- 当函数被独立调用时,函数中的 this 指向 window
话不多说依旧一套小连招,上代码
var a = 1
function bar () {
var a = 2
function foo() {
console.log(this.a);
}
foo()
}
bar()
如上所示,foo 里面的 this ,由于函数foo是被直接调用,所以this 指向 window ,现在会了吧,再来一段代码试试手
var a = 1
function foo() {
console.log(this.a);
}
function bar () {
var a = 2
foo()
}
bar()
有没有那么一瞬间你把上面这个代码认为是指向 foo 或者 bar 函数呢,那就大错特错了,foo 里面的 this ,由于函数foo是被直接调用,所以this 就是指向 window ,在这里我们只认死理,只要是 foo() ,那 this 就是指向 window 。
2. 隐式绑定 --- 当函数引用有上下文对象且被该对象调用时,函数中的 this 会绑定到这个上下文对象上
依旧上代码
function foo() {
console.log(this);
}
var a = 1
var obj = {
foo: foo
}
obj.foo()
// obj.foo
如上所示, this 在 foo 函数里,foo 函数被引用在 obj 对象里,并且被 obj 对象调用了,this 会绑定到这个上下文对象上,在隐式绑定里要注意两点,一是得分清楚引用和调用,引用是直接把函数拿过来但是不运行它,调用则是并运行它。如上图如果把倒数第二行代码删了用最后一行被注释掉的代码,那么this 就是指向 window 。第二个就是隐式丢失 --- 当一个函数被多层对象调用时,函数的 this 指向最近的那个对象,接着上代码
function foo() {
console.log(this.a);
}
var obj = {
a: 1,
foo: foo
}
var obj2 = {
a: 2,
foo: obj
}
obj2.foo.foo()
this 是 foo 函数里面的,foo 函数被两个对象调用,函数的 this 指向最近的那个对象,也就是 obj 对象
3. 显式绑定
fn.call(obj, x, x)显示的将 fn 里面的 this 绑定到 obj 这个对象上,call 负责帮 fn 接受参数
var obj = {
a: 1
}
function foo(x, y) { // new Function() // foo.__proto__ === Function.prototype
console.log(this.a, x + y);
}
foo.call(obj, 1, 2)
fn.apply(obj, [x, x])显示的将 fn 里面的 this 绑定到 obj 这个对象上,apply 负责帮 fn 接受参数,与 call 不同之处在于它是以数组形式接受参数
var obj = {
a: 1
}
function foo(x, y) {
console.log(this.a, x + y);
}
var arr = [1, 2]
foo.apply(obj, arr)
fn.bind(obj, x, x)()显示的将 fn 里面的 this 绑定到 obj 这个对象上,bind 负责帮 fn 接受参数,与 call 不同之处在于它既可以从obj 所在括号里接受参数,也可以在后面的小括号里接收参数(前者优先级大于后者)因为他的原理是构成一个新对象你可在新对象里接受参数,因为优先级存在,x 和 y 分别被赋予了 2 和 4,而不是 3 。
var obj = {
a: 1
}
function foo(x, y) {
console.log(this.a, x + y);
}
const bar = foo.bind(obj, 2, 4)
bar(3)
4.new 绑定
new的原理会导致 函数的this指向 实例对象
new 也是我这的老人了,连续三篇文章都有提到它,这个就不做过多解释,代码加注释足矣。
Person.prototype.say = 'hello'
function Person() {
// var obj = {name: '冯总'} 1
// Person.call(obj) 2
this.name = '冯总' // 3
// obj.__proto__ = Person.prototype 4
// return obj // 5
}
let p = new Person()
- 当构造函数中存在
return,并且return的是一个引用类型的数据,则new的返回失效 对于这一点咱还是有必要聊一聊,老规矩上代码:
function Person() {
this.name = '冯总'
return {a: 1} //return [] //return fn{}
}
let p = new Person()
console.log(p);
如果像第三行代码一样,在构造函数中存在 return ,并且 return 的是一个引用类型的数据,我们用的是对象做代表,那么new就不能成功
箭头函数
最后为箭头函数单开一页,箭头函数中没有 this 这个概念,写在了箭头函数中的 this,也是它外层那个非箭头函数的 this 。看下面代码this 能最终指向 obj 对象,即它存在 foo 函数里面。
function foo() {
// this
var bar = () => {
this.a = 2
}
bar()
}
var obj = {
a: 1,
baz: foo
}
obj.baz()
结语
一张速查表,把 this 从“玄学”变“显学”
-
this 的价值 没有 this,就必须显式把对象当参数传来传去;有了 this,JavaScript 才能把“方法”做成通用组件,实现真正的复用与动态绑定。
-
this 只认“谁最终调用我” 记住 4 条铁律,优先级从低到高:
① 默认绑定 → 独立调用时指向全局(严格模式 undefined)。
② 隐式绑定 → 被对象“点”出来调用时,指向最近的那个对象;小心“赋值给变量”造成的隐式丢失。
③ 显式绑定 → call / apply / bind 强行把 this 钉在指定对象上;bind 返回新函数,永久生效。
④ new 绑定 → 构造函数里 this 指向正在创建的那个实例;若构造函数 return 引用类型,则 new 失效。
-
箭头函数是“外人” 它自己没有 this,写在里面的 this 就是外层非箭头函数的词法 this,一旦定义永不改变,call / apply / new 都休想动它。
-
调试口诀 控制台打
console.log(this)之前,先问自己三句话:- 函数是裸调的吗?→ 看默认。
- 函数被谁“点”出来的?→ 看隐式。
- 有没有 call / apply / bind / new?→ 按优先级覆盖。
把这张“this 四象限”脑图设成浏览器书签,下次再遇到 undefined 狂刷控制台时,3 秒内就能定位是哪条规则在“偷家”。愿你从此写 this 不猜、不蒙、不踩坑!