深入理解JavaScript中的「this」:从概念到实战

为什么我们需要this?

JavaScript中的this关键字常让不少开发者感到困惑,有时候明明写的是同样的代码,this却指向了不同的对象,这让人挠头!但其实,this存在的意义非常明确 - 它提供了一种更为优雅的方式来隐式"传递"对象引用,从而让我们的代码变得更加简洁和易于复用。

this的作用域范围有哪些?

在JavaScript中,this可以在以下两种作用域中使用:

  1. 函数作用域:this在不同的函数调用方式下会有不同的指向
  2. 全局作用域:在全局作用域中,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
  1. call(context, arg1, arg2, ...): 显式地将函数内部的this绑定到指定的对象上,后面跟的是参数列表
  2. apply(context, [arg1, arg2, ...]): 与call类似,但第二个参数接收一个数组
  3. bind(context): 返回一个新函数,新函数中的this永久绑定到指定对象上

new绑定

在JavaScript中,构造函数也是普通函数,但如果使用new操作符调用函数,会执行以下步骤:

  1. 创建一个新对象(obj)
  2. 将构造函数的this指向这个新对象
  3. 执行构造函数内部代码(给新对象添加属性)
  4. 新对象的隐式原型(obj.__ proto __ )=== 构造函数的显式原型(function.prototype)
  5. 如果构造函数没有返回其他对象,则返回这个新创建的对象
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绑定的优先级

当同时应用多种绑定规则时,需要了解它们的优先级:

  1. new绑定(优先级最高)
  2. 显式绑定(call/apply/bind)
  3. 隐式绑定(对象调用)
  4. 默认绑定(独立调用,优先级最低)

类和对象方法中的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的指向,关键是要看函数是如何被调用的。

相关推荐
一 乐27 分钟前
民宿|基于java的民宿推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·源码
testleaf1 小时前
前端面经整理【1】
前端·面试
好了来看下一题1 小时前
使用 React+Vite+Electron 搭建桌面应用
前端·react.js·electron
啃火龙果的兔子1 小时前
前端八股文-react篇
前端·react.js·前端框架
小前端大牛马1 小时前
react中hook和高阶组件的选型
前端·javascript·vue.js
刺客-Andy1 小时前
React第六十二节 Router中 createStaticRouter 的使用详解
前端·javascript·react.js
秋田君2 小时前
深入理解JavaScript设计模式之策略模式
javascript·设计模式·策略模式
萌萌哒草头将军3 小时前
🚀🚀🚀VSCode 发布 1.101 版本,Copilot 更全能!
前端·vue.js·react.js
GIS之路3 小时前
OpenLayers 图层叠加控制
前端·信息可视化
90后的晨仔3 小时前
ArkTS 语言中的number和Number区别是什么?
前端·harmonyos