前言
在JavaScript这个充满魔法的世界里,有一个特别的角色——this
。它是一个指向函数运行环境的神秘指针,一个可以让你的代码与众不同的魔法棒。今天,让我们一起享受一场甜蜜的约会,深入了解 this 的秘密,看看它是如何在 JavaScript 中施展它的魅力。
一、this
的诞生之地:调用栈与执行上下文
每当一个函数被调用时,它就像是一场新的冒险开始。在这场冒险中,this
扮演着非常重要的角色。它是一个指针,指向当前函数的执行上下文(即调用环境)。调用栈就像是一个舞台,每一个函数调用都是一个新的场景,在这个场景中,this
会根据不同的情况选择自己要代表的对象。
二、this
的身份转变:不同调用方式下的变身法则
- 代码示例
'use strict';
var x = 2;
var obj = {
x: 1,
foo: function() {
console.log(this);
console.log(this.x);
}
}
// 函数体
var foo = obj.foo
var obj2 = {
x: 5,
foo: foo
}
// 对象的方法被调用
obj2.foo(); // this 指向 obj2, 输出 {x: 5, foo: [Function: foo]} 和 5
obj.foo(); // this 指向 obj, 输出 {x: 1, foo: [Function: foo]} 和 1
// 普通函数被调用
foo(); // 2
- 对象方法的召唤: 当
this
在一个对象的方法中被唤醒时,它就会成为这个对象的代言人。例如:
// 对象的方法被调用
obj2.foo(); // this 指向 obj2, 输出 {x: 5, foo: [Function: foo]} 和 5
obj.foo(); // this 指向 obj, 输出 {x: 1, foo: [Function: foo]} 和 1
-
普通函数的独白: 如果
this
是在全局环境中被唤醒的,那么在非严格模式下它会化身成为整个世界的守护者——全局对象(在浏览器环境中即为window
)。但在严格模式下,this
会变得害羞,不愿意展示自己,这时它就是undefined
。'use strict'; foo(); // 在严格模式下,this 是 undefined
-
新生力量的崛起: 当
this
伴随着new
关键字出现时,它便化身为新生事物的引领者,指向新创建的对象实例。 -
指定召唤者的秘密: 有时候,开发者们会通过
.call()
、.apply()
或.bind()
等方法明确地告诉this
谁是它的召唤者,从而确保它不会迷失方向。 -
注意 调用
.call()
、.apply()
方法会立即执行指定的函数,.bind()
并不会立即调用函数,而是返回一个新函数,这个新函数可以稍后被调用。 -
.call()
接受的是一个参数列表。 -
.apply()
接受的是一个参数数组。
var a = {
name: 'Cherry',
// apply 一次性给,以数组的形式
// call 一个个给 call(thisBinder, a,b,c, ...参数)
fn: function(a, b) {
console.log(this.name)
console.log(a + b)
}
}
var b = a.fn; // 普通函数
console.log(b.apply(a, [1, 2]))
console.log(b.call(a, [1, 2])) // 输出:1,2undefined
代码最后一步输出 1,2undefined
是因为将 [1,2]
赋值给了a,而b并没有被赋值,因此b输出是 undefined
,而中间的+
是作为运算连接符,将两个字符串连接。输出结果不是数组而是字符串的原因是 [1,2].toString
是隐式类型转换。JavaScript 在进行加法操作时,如果其中一个操作数是字符串,它会将另一个操作数转换为字符串并执行字符串拼接。具体来说,数组 [1, 2]
被隐式转换成了它的字符串表示形式 "1,2"
。
可以将最后一步改为
console.log(b.call(a, 1, 2)); // 输出: Cherry 3
.bind()
var name = "刀郎"
var a = {
name: "薛之谦",
func1: function() {
console.log(this.name);
},
func2: function() {
setTimeout((function() {
// this被指定了
this.func1();
}).bind(a),1000)
}
}
a.func2(); // 输出薛之谦
三、异步世界的挑战:保持this
的一致性
在异步操作如setTimeout
中,this
很容易失去自己的方向,因为它可能会脱离原来的作用域。但是,聪明的开发者总能找到办法解决这个问题,比如使用变量保存当前的this
(如let _this = this;
),或者利用箭头函数来保持this
的绑定。
var a = {
name: "杜",
func1: function() {
console.log(this.name);
},
func2: function() {
console.log("func2",this);
let _this = this;
setTimeout((function() {
// this 丢失
_this.func1()
}), 1000);
}
}
a.func2();
四、箭头函数的小秘密:继承父级作用域的this
箭头函数就像是来自未来的使者,它并不遵循传统的this
规则。相反,它总是继承外部作用域的this
值,仿佛带着前世的记忆穿越而来。箭头函数在定义时(可以理解为编译阶段)
就决定了 this
的值,而不是在调用时动态绑定。因此,在需要保持this
一致性的地方,箭头函数是一个非常可靠的选择。
var a = {
name: "杜",
func1: function() {
console.log(this.name);
},
func2: function() {
console.log("func2",this); // 输出"杜"
setTimeout((() => { // 箭头函数内部没有this
this.func1(); // 箭头函数内的 this 继承自 func2 的 this
}), 1000);
}
}
a.func2();
结语:与this
和谐共处
在JavaScript的世界里,this
虽然看似变幻莫测,但只要我们理解了它背后的工作原理,就能轻松驾驭它,让它成为构建强大应用的好帮手。希望这篇小文能够帮助你更好地理解this
这位神秘的朋友,并在编写代码的过程中更加得心应手!
通过上述内容,我们可以看到this
的行为是由其调用上下文决定的,并且在不同的情况下有不同的表现形式。理解这些规则有助于写出更清晰、更可靠的JavaScript代码。