JavaScript中的this使用详解
在JavaScript中,当我们使用关键字“this”时,其背后的含义随着函数的使用场合不同而有所变化。但有一个核心原则始终不变:那就是“this”指的是调用函数的那个对象。今天,我们来深入下“this”在JavaScript中的使用。
实际上,“this”是一个常见且常被讨论的话题。尽管关于“this”的文章浩如烟海,我曾以为自己已经对其有了深入的理解,但在最近的项目中,我再次被其深深吸引,产生了一些疑惑。这使我想起了之前收藏的关于“this”的详解文章,以及一位前辈的推荐文章,阅读后,我对“this”的认识有了更深的提升。
在JavaScript中,“this”是动态的。它的值不是在函数声明时确定的,而是在函数运行时确定的。所有的函数都可以调用“this”,无论该函数是否属于某个对象。关于“this”,主要有以下几种情况:
一、当函数作为对象的方法被调用时
如果函数是作为某一对象的方法被调用,那么这个函数的“this”会指向那个对象。例如:
```javascript
var john = {
firstName: "John"
}
function func() {
alert(this.firstName + ": hi!")
}
john.sayHi = func;
john.sayHi(); // this = john
```
这里有一点需要注意,当一个对象的方法被取出并赋值给一个变量时,该方法作为函数触发,“this”会指向window或undefined(严格模式下)。
二、函数内部调用
当函数中有“this”时,意味着它被当作方法调用。在这种情况下,“this”指向window。值得注意的是,ES5规定这种情况下“this”为undefined,但在大多数浏览器中仍然按照旧的方式执行(在版的Chrome、Safari、Firefox中测试都指向window)。例如:
```javascript
func()
function func() {
alert(this) // [object Window] 或 [object global] 或类似...
}
```
为了传递“this”,在调用函数之前应该将其视为引用类型,类似于obj.a 或 obj['a']。当对象的方法中还存在函数时,该函数实际上是作为函数模式触发的,所以其“this”默认为window(严格模式下为undefined)。解决办法是给该函数绑定“this”。例如:
```javascript
var numbers = {
numberA: 5,
numberB: 10,
sum: function() {
console.log(this === numbers); // => true (使用绑定后的)numbers.sum(); => true 同样为true 表示函数内的this绑定到numbers对象了。 } } numbers.sum(); // => 正确的结果而不是NaN或TypeError var numbers = { numberA: 5, numberB: 10, sum: function() { console.log(this === numbers); // => true function calculate() { console.log(this === numbers); // 使用 .call() 方法改变上下文 return this.numberA + this.numberB; } return calculate.call(this); } }; numbers.sum(); // => 结果正确且符合预期值 15 (而非NaN或TypeError) ``` 三、使用new关键字调用时 在JavaScript中,当我们使用new关键字创建一个新的对象实例时,"this"关键字则指向这个新创建的对象实例。 "this"在JavaScript中的使用非常复杂且多变,但其核心原则始终不变:它指向调用函数的那个对象。理解并熟练掌握"this"的使用对于编写高效、可维护的JavaScript代码至关重要。JavaScript中的`new`关键字与`this`的变迁
当我们使用`new`关键字创建一个新的对象实例时,实际上经历了几步操作。`this`被赋值为一个空对象。随后,构造函数中的属性和方法被添加到这个对象上,最后返回这个被修改的`this`。
例如,当我们定义一个Animal构造函数并创建一个新的animal对象时:
```javascript
function Animal(name) {
this.name = name;
this.canWalk = true;
}
var animal = new Animal("beastie");
alert(animal.name); // "beastie"
```
在这里,使用`new`关键字调用`Animal`函数时,一个全新的对象被创建,其`this`指向这个新对象。然后,这个对象的`name`和`canWalk`属性被赋予相应的值。
值得注意的是,如果构造函数返回一个对象,那么`this`将指向返回的那个对象。例如:
```javascript
function Animal() {
this.name = 'Mousie';
this.age = '18';
return {
name: 'Godzilla'
} // <-- 返回的对象将决定this的指向
}
var animal = new Animal(); // 使用new关键字创建实例时返回的对象将具有不同的属性。
console.log(animal.name); // 输出 "Godzilla",而不是 "Mousie",因为返回的对象覆盖了原本对this的修改。
console.log(animal.age); // 输出 "undefined",因为返回的对象没有定义age属性。
``` 如果没有使用 `new` 关键字,函数会以普通函数调用方式执行,此时 `this` 通常指向全局对象(在浏览器环境中通常是 `window`)。在使用构造函数时,不要忘记使用 `new` 关键字。如果不使用 `new` 创建对象实例,构造函数内部的 `this` 会指向全局对象 `window`。例如:
```javascript
function Vehicle(type, wheelsCount) {
this.type = type;
this.wheelsCount = wheelsCount;
return this;
}
让我们看一下继承。这里有一个Runner和Rabbit的继承关系。当创建Rabbit的实例时,它的构造函数会先调用Runner的构造函数,从而继承了Runner的一些属性。这种行为确保了子类的实例能够访问父类的属性和方法。
接下来,我们了JavaScript中的另一个重要概念:函数绑定。.bind()方法创建了一个新的函数,该函数在调用时会绑定特定的上下文环境(即this的值)。这与.apply()和.call()方法不同,后者会立即执行函数,并不创建新的函数。绑定函数具有永久性的上下文链,即使在调用时使用不同的上下文环境也无法改变其绑定的上下文。
假设我们有一个Period类,它代表一段时间,包括小时和分钟。这个类有一个format方法,用于将时间段格式化为字符串形式。让我们看看这个代码是如何运作的。
```javascript
class Period {
constructor(hours, minutes) {
this.hours = hours; // 'this'在这里指代新创建的Period对象
this.minutes = minutes; // 'this'同样指代这个对象
}
format() {
console.log(this === window); // 在浏览器环境中,'this'确实等于window对象
return `${this.hours} hours and ${this.minutes} minutes`; // 使用模板字符串格式化输出
}
}
const walkPeriod = new Period(2, 30); // 创建了一个新的Period对象,设置小时为2,分钟为30
console.log(walkPeriod.format()); // 输出应为 '2 hours and 30 minutes',而不是 'undefined hours and undefined minutes'
```
在这个例子中,“this”关键字在构造函数中用于指代新创建的Period对象。在format方法中,“this”同样指代这个对象,用于访问它的属性(如hours和minutes)。在浏览器环境中,如果你在全局作用域中调用一个方法并打印“this === window”,它将返回true,因为在这个上下文中,“this”指代的是全局对象(在浏览器中是window对象)。我们使用模板字符串来格式化输出时间段。
如果大家对“this”还有疑问,不要害怕提出。交流促进思考,共同进步。通过讨论和分享,我们可以一起解决编程中的难题,更好地理解JavaScript中的复杂概念。希望大家在编程的道路上越走越远,不断提升自己的技能和能力。对于觉得难以理解的部分,不妨多读几遍,结合具体的例子来思考,相信你会逐渐掌握这个强大的概念。推荐大家去看看相关的参考资料和文章,巩固自己的知识。让我们一起学习,一起进步!