为什么我们需要this?
JavaScript中的this关键字常让不少开发者感到困惑,有时候明明写的是同样的代码,this却指向了不同的对象,这让人挠头!但其实,this存在的意义非常明确 - 它提供了一种更为优雅的方式来隐式"传递"对象引用,从而让我们的代码变得更加简洁和易于复用。
this的作用域范围有哪些?
在JavaScript中,this可以在以下两种作用域中使用:
- 函数作用域:this在不同的函数调用方式下会有不同的指向
- 全局作用域:在全局作用域中,this指向全局对象(浏览器环境中为window)
记住,this本质上是一个代词,它代指的是一个对象。这个对象是谁,取决于函数调用的方式。
this的绑定规则详解
默认绑定
当函数被独立调用(没有任何修饰的函数引用进行调用)时,this会指向全局对象(在浏览器中是window,在Node.js中是global)。
javascript
function foo() {
console.log(this); // window
}
foo()
console.log(this); // window
{
let a = this;
console.log(a); // window
}
这种情况是最常见的函数调用类型,也是this的"默认"指向。
隐式绑定
当函数引用有上下文对象并被该对象调用时,this会绑定到这个上下文对象上。
javascript
var a = 1
function foo() {
console.log(this.a); // 2
}
var obj = {
a: 2,
foo: foo
}
obj.foo() // 此时this指向obj
这里的关键是要注意,函数调用位置是否有上下文对象。如果有,那么this通常会指向那个对象。
隐式丢失
需要注意的是,当一个函数被多层对象调用时,this只会绑定到最近的那一层对象上。
javascript
var a = 1
function foo() {
console.log(this.a); // 2
}
var obj = {
a: 2,
foo: foo
}
var obj2 = {
a: 3,
obj: obj
}
obj2.obj.foo() // 输出2而不是3,因为最近的调用者是obj
这个例子中,尽管调用链是obj2.obj.foo()
,但foo函数的直接调用者是obj对象,所以this指向obj,而不是obj2。
还有一种常见的隐式丢失情况是通过变量赋值:
javascript
var a = 1
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
}
var bar = obj.foo; // 注意这里仅仅是引用了函数本身
bar(); // 输出1,因为这里是独立调用了bar函数
显式绑定
有时候我们想要明确指定函数执行时this的指向,JavaScript提供了三种方法:
javascript
function foo(x, y) {
console.log(this.a, x + y);
}
var obj = {
a: 1
}
//call
foo.call(obj, 1, 2) // 1 3
// apply
foo.apply(obj, [1, 2]) // 1 3
// bind
const bar = foo.bind(obj)
bar(2, 3) // 1 3
call(context, arg1, arg2, ...)
: 显式地将函数内部的this绑定到指定的对象上,后面跟的是参数列表apply(context, [arg1, arg2, ...])
: 与call类似,但第二个参数接收一个数组bind(context)
: 返回一个新函数,新函数中的this永久绑定到指定对象上
new绑定
在JavaScript中,构造函数也是普通函数,但如果使用new操作符调用函数,会执行以下步骤:
- 创建一个新对象(obj)
- 将构造函数的this指向这个新对象
- 执行构造函数内部代码(给新对象添加属性)
- 新对象的隐式原型(obj.__ proto __ )=== 构造函数的显式原型(function.prototype)
- 如果构造函数没有返回其他对象,则返回这个新创建的对象
javascript
function Person() {
this.name = 'Tom'
this.age = 18
}
const p = new Person()
console.log(p) // Person {name: "Tom", age: 18}
上面代码的执行过程实际上类似于:
javascript
function Person() {
// var obj = {} // 创建空对象
// Person.call(obj) // 将this绑定到obj
this.name = 'Tom'
this.age = 18
// obj.__proto__ = Person.prototype // 设置原型链
// return obj // 返回这个对象
}
箭头函数中的this
箭头函数是ES6中引入的新特性,它与普通函数有一个重要的区别:箭头函数中没有自己的this。
箭头函数中的this不是在调用时确定的,而是在定义时就确定了。它会捕获其所在上下文的this值,作为自己的this值。
简单地说,写在箭头函数中的this实际上是外层那个非箭头函数的this。
javascript
function a() {
let b = function() {
console.log(this); // 默认绑定,this指向window
let c = () => {
console.log(this); // 这里的this是b函数的this,即window
let d = () => {
console.log(this); // 这里的this依然是b函数的this,即window
}
d()
}
c()
}
b()
}
a()
但如果外层是对象方法调用,情况会不一样:
javascript
var obj = {
a: 2,
foo: function() {
console.log(this); // this指向obj
let bar = () => {
console.log(this); // 这里的this是foo函数的this,即obj
}
bar();
}
}
obj.foo();
this绑定的优先级
当同时应用多种绑定规则时,需要了解它们的优先级:
- new绑定(优先级最高)
- 显式绑定(call/apply/bind)
- 隐式绑定(对象调用)
- 默认绑定(独立调用,优先级最低)
类和对象方法中的this
在ES6类中,普通方法中的this指向实例,而静态方法中的this指向类本身:
javascript
class MyClass {
constructor() {
this.name = 'instance';
}
method() {
console.log(this.name); // 'instance'
}
static staticMethod() {
console.log(this.name); // 'MyClass'
}
}
灵活掌握this的真谛
JavaScript中的this机制既灵活又强大,正确理解和运用它可以让我们写出更加简洁、优雅的代码。但灵活性的代价就是复杂性,这就要求我们必须清楚地知道函数调用时this的指向规则。
记住一点:this的指向不是在函数定义时确定的,而是在函数调用时确定的(箭头函数除外)。所以,判断this的指向,关键是要看函数是如何被调用的。