引言
各位掘友大家好,今天我想要和大家一起来学习一下JS中的 "this" 关键字。"this" 是 JavaScript 中一个非常重要的概念,但也容易让初学者陷入一些常见的误解和陷阱中。比如忘记绑定 "this" 、在对象方法中丢失等等。
而在其他编程语言中,比如 Java 或 C++,"this" 关键字通常用于表示当前对象的引用,而在 JavaScript 中,"this" 的值取决于函数被调用的方式。这意味着在 JavaScript 中,同一个函数在不同的上下文中可能会有不同的 "this" 值,而在其他语言中 "this" 通常是固定的,指向当前对象。
此外,在 JavaScript 中,"this" 在箭头函数中的行为与传统函数不同,箭头函数没有自己的 "this" 绑定,它会继承外层函数的 "this" 值。而在其他语言中,箭头函数类似的语法可能不存在,或者具有不同的行为。
因此,要想在 JavaScript 中正确地理解和使用 "this",需要对其绑定规则有深入的理解,而不是简单地将其视为其他语言中的 "this" 关键字的直接对应。
为什么需要 "this" ?
为了理解 "this" 的作用,让我们考虑一个简单的场景:在一个对象中定义了一系列方法,这些方法需要访问对象自身的属性。如果没有 "this",我们可能需要将对象作为参数传递给这些方法,然后在方法内部使用对象参数来访问属性。这样做会使代码变得冗长且不直观。
js
// 没有使用 "this" 的情况下访问对象属性
const person = {
name: 'John',
greet: function() {
console.log('Hello, ' + person.name);
}
};
person.greet(); // 输出:Hello, John
而有了 "this",我们就可以更自然地访问对象自身的属性,而无需显式传递对象:
js
// 使用 "this" 访问对象属性
const person = {
name: 'John',
greet: function() {
console.log('Hello, ' + this.name);
}
};
person.greet(); // 输出:Hello, John
总而言之,this 让对象中的函数有能力访问对象自己的属性。this 可以显著提升代码质量,减少上下文参数的传递。
"this" 的绑定规则
在 JavaScript 中,"this" 的值取决于函数被调用的方式,有着不同的绑定规则:
1. 默认绑定
默认绑定 是当函数被独立调用时应用的规则。当函数没有显式绑定到任何对象时,它的
this
会被绑定到全局对象。或者说,当一个函数独立调用,不带任何修饰符时,默认绑定规则会把this绑定到全局对象。函数在哪个词法作用域中生效,函数的this就指向哪里。这种行为在浏览器环境下通常是
window
对象,在 Node.js 环境下是global
对象。
js
function foo() {
console.log(this === window);
}
foo(); // true
在例子中,当我们调用 foo()
函数时,没有明确指定执行上下文,因此默认绑定规则生效,将 this
绑定到全局对象 window
(在浏览器环境下)
2. 隐式绑定
隐式绑定 是当函数作为对象的方法被调用时应用的规则。在这种情况下,函数的
this
将自动绑定到调用它的对象上。或者说,当函数有上下文对象时,即函数被某个对象所拥有时,this
将隐式地绑定到该对象。函数的this指向引用它的对象。
js
const obj = {
name: 'Alice',
greet() {
console.log(`Hello, ${this.name}!`);
}
};
obj.greet(); // Hello, Alice!
在这个例子中,this
指向了对象 obj
。当我们调用 obj.greet()
时,greet
函数作为 obj
的方法被调用,因此隐式绑定规则生效,将 this
隐式地绑定到 obj
。这使得在 greet
函数内部可以访问 obj
对象的属性。
3. 显式绑定
显式绑定 是通过调用函数的
call
、apply
或bind
方法来手动指定函数的执行上下文。这种方式可以覆盖默认绑定和隐式绑定,确保函数的this
是我们明确指定的对象。通过显式绑定,我们可以更精确地控制函数的执行上下文,从而避免意外的行为和错误。
- call 方法
call
方法允许你调用一个函数,并且可以指定函数体内 this
对象的值。例如:
js
function greet() {
console.log(`Hello, ${this.name}!`);
}
const person = { name: 'Alice' };
// 使用 call 方法将 greet 函数的 this 绑定到 person 对象上
greet.call(person); // 输出:Hello, Alice!
这个例子中,我们调用了 greet
函数,并且通过 call
方法将 this
绑定到了 person
对象上,从而让函数可以访问 person
对象的属性。
- apply 方法
apply
方法与 call
方法类似,唯一的区别是 apply
方法接收一个参数数组而不是一系列参数。例如:
js
function greet(message) {
console.log(`${message}, ${this.name}!`);
}
const person = { name: 'Bob' };
// 使用 apply 方法将 greet 函数的 this 绑定到 person 对象上,并传递一个参数数组
greet.apply(person, ['Greetings']); // 输出:Greetings, Bob!
在这个例子中,我们调用了 greet
函数,并且通过 apply
方法将 this
绑定到了 person
对象上,并传递了一个参数数组 ['Greetings']
给 greet
函数。
- bind 方法
bind
方法创建一个新函数,其中 this
的值被永久地绑定到指定的对象。
js
function greet() {
console.log(`Hello, ${this.name}!`);
}
const person = { name: 'Charlie' };
// 使用 bind 方法创建一个新函数,其中 this 绑定到 person 对象上
const greetPerson = greet.bind(person);
// 调用新函数
greetPerson(); // 输出:Hello, Charlie!
在这个例子中,我们使用 bind
方法创建了一个新函数 greetPerson
,其中 this
被永久地绑定到了 person
对象上。这意味着,无论在何处调用 greetPerson
,其 this
都将指向 person
对象。
这些方法都用于显式绑定函数的
this
,但它们之间存在一些细微的区别。bind
方法会创建一个新函数,而不会立即调用它;call
和apply
则会立即调用函数,并允许传递参数列表或参数数组。
以上就是三种显示绑定方法的示例。它们都允许你明确指定函数体内 this
的值,并在需要时将其绑定到指定的对象上。
4. 隐式丢失
隐式丢失 指的是当函数被多个对象链式调用时,函数的
this
指向就近的那个对象。这种情况下,函数的this
可能不是我们期望的对象。为了避免隐式丢失,我们可以使用
call
、apply
或bind
方法显式地绑定函数的this
。这样可以确保函数的执行上下文是我们所期望的对象。
js
const person = {
name: 'Bob',
greet() {
console.log(`Hello, ${this.name}!`);
}
};
const anotherPerson = {
name: 'Charlie'
};
person.greet.call(anotherPerson); // Hello, Charlie!
在这个例子中,我们定义了一个 person
对象和另一个没有 greet
方法的 anotherPerson
对象。然后,我们通过 call
方法将 greet
函数从 person
对象上解绑,并显式绑定到 anotherPerson
对象上。尽管 greet
函数原本属于 person
对象,但由于我们使用了显式绑定,this
指向了最近的对象 anotherPerson
。这个过程展示了隐式丢失的现象。
5. new 绑定
当函数作为构造函数使用时,
this
指向新创建的实例对象。这种方式称为new
绑定。在使用构造函数创建对象时,
this
可以让我们在每个实例中访问和操作特定于该实例的属性和方法,这对于创建可重复使用的代码模块非常有用。
js
function Person(name) {
this.name = name;
}
const john = new Person('John');
console.log(john.name); // John
在构造函数中,this
指向新创建的实例对象。
理解这些绑定规则有助于我们编写更具可维护性和可扩展性的 JavaScript 代码,并避免常见的错误和陷阱。对于初学者来说,熟悉这些规则可能需要一些时间和练习,但随着经验的增长,它们将成为编写高效 JavaScript 代码的重要工具。
箭头函数中的 "this"
箭头函数与传统函数不同,箭头函数没有this这个机制,它没有自己的
this
绑定,而是继承父作用域的this
值。写在箭头函数中this
也是它外层非箭头函数的this。
js
function outer() {
return () => {
console.log(this === window);
};
}
const inner = outer();
inner(); // true
在箭头函数内部,this
将指向外层函数 outer
的 this
,即全局对象 window
。
var 关键字的作用域
这里我想补充一个小的知识点:
在全局,通过
var
声明的变量相当于是往window 上添加了一个属性,而通过let
声明的变量相当于是往全局对象上添加了一个属性。 这句话的意思是:当你在全局作用域中使用
var
声明变量时,这个变量会被添加为全局对象的属性。在浏览器环境中,全局对象通常是window
对象。因此,通过var
声明的全局变量实际上是在window
对象上创建了一个属性
js
var a = 10;
console.log(window.a); // 10
let b = 20;
console.log(window.b); // undefined
通过了解 this
的绑定规则以及如何正确使用箭头函数和变量声明关键字,我们可以编写更加优雅和易于理解的 JavaScript 代码。
this 陷阱
在严格模式下,JavaScript 的行为与非严格模式下有所不同,特别是在默认绑定方面。在严格模式下调用函数时,全局对象不会被绑定到this
,而是会被绑定到undefined
。这可能导致意外的行为,特别是在访问全局变量时。让我们详细解释一下这个陷阱:
在非严格模式下,如果一个函数在全局作用域中被调用而没有显式绑定到其他对象,this
会默认绑定到全局对象,如浏览器环境 下的window
对象。
js
function foo() {
console.log(this === window);
}
foo(); // true
但是,在严格模式下,这种行为会有所不同:
js
'use strict';
function bar() {
console.log(this === window);
}
bar(); // false
在严格模式下调用函数bar()
时,this
不再指向全局对象window
,而是被绑定到undefined
。这种行为的变化可能会导致意外的结果,特别是当函数在严格模式和非严格模式之间切换时。
一个典型的陷阱是当我们在严格模式下访问全局变量时可能会导致错误:
js
'use strict';
let globalVar = 'Hello';
function logGlobalVar() {
console.log(this.globalVar); // TypeError: Cannot read property 'globalVar' of undefined
}
logGlobalVar();
在这个例子中,logGlobalVar()
函数尝试访问全局变量globalVar
,但由于在严格模式下调用,this
被绑定到undefined
,因此会导致TypeError
错误。
为了避免这种陷阱,当在严格模式下编写代码时,要格外小心不要依赖于默认绑定到全局对象的行为,并确保正确处理this
的值。
闭包中的 "this"
当在闭包中使用 this
时,可能会导致一些意外的结果,这是因为闭包中的函数并不会绑定新的 this
,而是共享外部函数的 this
。
在 JavaScript 中,闭包是指内部函数可以访问其外部函数作用域中的变量。当内部函数引用外部函数的变量时,即使外部函数已经执行完毕,这些变量仍然可以被内部函数访问,因为内部函数形成了闭包。
然而,在闭包中使用 this
时,this
的值取决于函数的调用方式,而不是定义它的位置。这意味着闭包中的函数并不会创建新的 this
,而是共享其外部函数的 this
。这可能会导致一些意外的结果,特别是当我们期望 this
指向闭包函数自身所在的对象时。
下面我们举个例子,一起来看看怎么回事:
js
const obj = {
myName: "Alice",
sayName: function() {
setTimeout(function() {
console.log(this.myName); // 在闭包中使用 this
}, 1000);
}
};
obj.sayName(); // 输出:undefined
首先补充一个知识点,就是 setTimeout 函数中的 this 指向全局,浏览器中也就是window
在这个例子中,obj.sayName()
方法内部的 setTimeout
函数形成了一个闭包。在闭包中,我们尝试使用 this.myName
来访问 obj
对象的 myName
属性。
然而,由于 setTimeout
的回调函数是在全局作用域中调用的,而不是 obj
对象上调用的,因此 this
的值不再是 obj
对象,而是全局对象(在浏览器中是 window
)。因此,this.myName
的结果是 undefined
。
为了解决这个问题,可以使用箭头函数来代替普通函数,因为箭头函数继承了父作用域的 this
值,而不是创建新的 this
。修改示例代码如下:
js
const obj = {
myName: "Alice",
sayName: function() {
setTimeout(() => {
console.log(this.myName); // 在箭头函数中使用 this
}, 1000);
}
};
obj.sayName(); // 输出:Alice
在这个修改后的代码中,箭头函数继承了 sayName
方法中的 this
值,因此在闭包中可以正确地访问 obj
对象的 myName
属性,输出结果为 "Alice"
。
总结
以上便是对 this 在 JS 中的重要知识点的分享,希望能够对你有所帮助和理解,未来我还会继续学习 this 关键字并和大家分享,我们拭目以待。
觉得有用话,可以点赞收藏哟~