在JavaScript中,“context”
指的是一个对象。在一个对象中,关键字“this”
指向该对象,并提供了一个指向作为该对象成员的属性和方法的接口。当函数被执行时,关键字“this”
指向函数被执行的对象。
通常有这些场景来说明 this
的指向:
- 当函数在全局上下文中执行时,
“this”
指的是全局或“window”对象 - 当一个函数是一个对象的方法时,
“this”
指的是那个对象(除非它是在另一个对象的上下文中手动执行的) - 当一个函数在另一个函数内部执行时(无论嵌套有多深),
“this”
指的是执行该函数的上下文所在的对象 - 在实例对象内部实例化构造函数时,
“this”
指的是实例对象
所以,如你所见,“this”
很容易让你头疼。但是坚持住💪。
简而言之,在对象字面量中,你没有局部变量,你有对象的属性。在函数foo()
中,我可以说" var drink = ' beer ';
,对于一个叫做bar
的对象,我会说bar.dink=“beer”
。不同的是,“beer”
是“bar”
的属性,而当函数执行时,局部变量是由“var”
关键字定义的,不能被函数之外的任何人或任何东西看到。
为什么“this”在JavaScript中如此重要?
示例 1
drink = 'wine'; var foo = { drink: "beer", getDrink: function(){ return drink; } }; console.log( foo.getDrink() ); // wine
在示例# 1中,我们首先创建一个名为“drink”
的全局变量,并将其值赋值为“wine”
。
接下来,我们创建一个名为“foo”
的对象字面量,其属性“drink”
等于“beer”
。还有一个方法getDrink
简单地返回了“drink”
。但是为什么它返回“wine”
而不是“beer”
呢?这是因为在对象“foo”
中,“drink”
是foo
的属性,而不是变量。
在函数内部,当引用一个变量时,JavaScript引擎搜索作用域链并返回它找到的第一个匹配项。
虽然这个函数在“foo”
上下文中执行,但“foo”
没有名为“drink”
的变量。它有一个名为“drink”
的属性,但没有变量。因此JavaScript引擎搜索作用域链的下一级。作用域链的下一层是全局对象,它包含一个名为“drink”
的变量,因此返回该变量的值“wine”
。
示例 2
var drink = 'wine'; var foo = { drink: "beer", getDrink: function(){ return this.drink; // 'this' refers to the object "foo" } }; console.log( foo.getDrink() ); // beer
在例2中,我们所做的唯一改变是,在分配给“getDrink”
的匿名函数中,我们返回“this.drink”
,而不是“drink”
这是一个重要的细节。当函数在对象的上下文中执行时,关键字“this”
指向该对象。你可以通过使用“this”
关键字来访问对象的任何属性,添加新的属性(例如this.Color = " blue "
),并更改现有的(例如:this.drink=“juice"
)。
使用点表示法创建一个JavaScript对象字面量
示例 3
var drink = 'wine'; var foo = {}; foo.drink = "beer"; foo.getDrink = function(){ return this.drink; // 'this' refers to the object "foo" }; console.log( foo.getDrink() ); // beer
在例3中,我们拥有与例2完全相同的功能。从JavaScript引擎的角度来看,我们实现了相同的目标,控制台输出也完全相同。
区别在于我们如何组织代码。在例2中,我们在创建对象字面量“foo”
的同时创建了属性“drink”
和“getDrink”
。这都是一种表达式。在例# 3中,我们首先创建了一个名为“foo”
的空对象,然后使用点表示法逐个向对象添加属性。我只是想指出,从语法的角度来看,解决所有这些问题的方法不止一种。
对象字面量可以包含其他对象字面量,而那些对象有它们自己的上下文
示例 4
var drink = 'wine'; var foo = {}; foo.drink = "beer"; foo.getDrink = function(){ return this.drink; // 'this' refers to the object "foo" }; foo.under21 = {}; foo.under21.drink = 'soda'; foo.under21.getDrink = function(){ return this.drink; // 'this' refers to the object "foo.under21" }; console.log( foo.getDrink() ); // beer console.log( foo.under21.getDrink() ); // soda
在例 4中,我们为“foo”
添加了一个新属性,该属性也是另一个对象。一开始它是空的(例如foo.under21 ={}
),然后添加两个属性。第一个属性是“drink”
,它被赋值为“soda”
。不要把它和属性“drink”
混淆,它是在“foo”
的上下文中设置的,等于“beer
”。在“foo.under21”
的上下文中,“drink”
等于“soda”
。
foo
的第二个属性“foo.under21”
有一个匿名的功能。这个匿名函数返回“this.drink”
。在“foo.Under21"
上下文中,"drink"
等于"soda "
,因此调用该函数返回"soda"
。
这个例子的重点是对象字面量的属性本身可以是对象,并且那些对象有它们自己的上下文。当函数在这些对象的上下文中执行时,“this”
指的是对象。我知道这种对象嵌套是没有限制的。
JavaScript的.call()和.apply()方法允许你动态地改变函数执行的上下文
示例 5
drink = 'wine'; var foo = {}; foo.drink = "beer"; foo.getDrink = function(){ return this.drink; // 'this' refers to the object "foo" }; foo.under21 = {}; foo.under21.drink = 'soda'; foo.under21.getDrink = function(){ return foo.getDrink.call(); // execute foo.getDrink() }; console.log( foo.getDrink() ); // beer console.log( foo.under21.getDrink() ); // wine
好的,这里是额外的问题:在示例# 5中,为什么调用" foo.under21.getDrink()
"返回的是" wine "
?
这是因为我们改变了该函数的内部工作方式。而不是简单地返回“this.drink
。我们使用JavaScript的" .call() "
方法,它允许你在另一个对象的上下文中执行任何函数。当你没有指定函数被“调用”的上下文时,它会在全局对象的上下文中执行。在全局上下文中,有一个名为“drink”
的变量,它等于“wine”
,因此返回“wine”
。
示例 6
drink = 'wine'; var foo = {}; foo.drink = "beer"; foo.getDrink = function(){ return this.drink; // 'this' refers to the object "foo" }; foo.under21 = {}; foo.under21.drink = 'soda'; foo.under21.getDrink = function(){ return foo.getDrink.call(this); // execute foo.getDrink() }; console.log( foo.getDrink() ); // beer console.log( foo.under21.getDrink() ); // soda
在示例# 6中,返回" soda "
是因为当我们使用JavaScript的" .call() "
方法时,我们指定了函数将要在其中执行的上下文。在本例中,我们指定的上下文是“this”
。“this”指的是“foo.under21”
的上下文。“foo.Under21”
有一个名为“drink”
的属性,因此返回值“soda”
。
总结
我想指出的是,当你开始理解JavaScript对象字面量中的上下文概念时,必须意识到还有更多的内容需要考虑。JavaScript对象字面量可以有属性是对象,这些对象有它们自己的上下文。
在每种情况下,当函数在该上下文中执行时,在函数内部,“this”
关键字指向函数的属性对象,因为函数是在该对象的上下文中执行的。通过使用JavaScript的" .call() "方法(或" .apply() "方法
),可以通过这种方式改变函数执行的上下文,从而相应地改变" this "
的指向。