🔥一篇文章教会你JS的this!不会你来撅我!🔥

129 阅读9分钟

前言

如果大家学习过Java,那么相信大家对this这个关键字应该不会陌生,它就像小小怪和大大怪,华生和夏洛克,格鲁特和火箭浣熊,阿布和漂泊者一样,与创建实例对象的函数形影不离。

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name; 
        this.age = age;
    }

}

在这里,this.name 是成员变量,name 是构造方法的参数。

那么其在JavaScript中的表现会不会有所不同呢?接下来我将用一篇文章带你通透♂通透!

What the hell is this?

this是个啥玩意儿锕?this指的是这个?这个是哪个?

其实在JavaScriptthis拥有一个明确的定义:this 是 JavaScript 中的一个特殊关键字,它拥有一个值,它的值取决于函数的调用方式,而不是定义方式。

  • 在全局作用域this 指向 window(浏览器)或 global(Node.js)。
  • 在对象方法里,this 指向调用该方法的对象。
  • 在构造函数里,this 指向新创建的实例。
  • 在箭头函数里,this 继承外层作用域的值(不会自己绑定 this)。

四种绑定规则

判断this的值,我们必须找到它的调用位置,然后判断是下面4条规则的哪一条

默认绑定

默认规则就是我们最常用的独立函数调用,其会将this的值绑定到window/global

屏幕截图 2025-08-01 212640.png

比如下面的例子:

function example() {
    console.log(this.a);
}

var a = 3;

example();

输出结果为3,因为这里是独立函数调用,直接对函数进行调用,此时的this,在浏览器中绑定到了window,在Node.js中绑定到了global,引擎会在this所指的对象中寻找相关变量或方法,比如这一个,我们输出了this.a,其就会在this所指的Window/global对象中寻找定义的a变量的值。

this所绑定的Window:

image.png

Window对象包含了浏览器可以实现的各种方法,以及我们刚刚在全局定义的变量(var)以及函数。

⚠⚠⚠Caution: 如果使用严格模式,默认绑定将会绑定到undefined

隐式绑定

隐式绑定简单来说就是看是不是通过了哪个对象调用了函数this指向最后一层调用它的那个对象

举个栗子:

function example() {
    console.log(this);
}

var obj2 = {
    name: 'obj2',
    a: 42,
    example: example
}
var obj1 = {
    name: 'obj1',
    a: 21,
    obj2: obj2
}

obj1.obj2.example();

这里的this指向了谁呢? 当然是指向了最后选择了离她最近的那个人,也就是obj2(无论前面套用了多少个对象,this永远指向最靠近它的那个):

image.png

调用过程解析:

  1. obj1.obj2 访问 obj1 的 obj2 属性,得到 obj2 对象
  2. obj2.example() 通过 obj2 调用 example 方法
  3. 此时 this 指向调用者 obj2

image.png

隐式丢失

隐式丢失(Implicit Binding Loss) 是指:
函数在调用时,this 不再指向预期的对象,而是退回到默认绑定(如 windowglobal)。

以下是常发生隐式丢失的场景:

(1) 函数赋值给变量

const obj = {
  name: "Bob",
  greet() {
    console.log(this.name);
  },
};
 
const greetFunc = obj.greet; 
greetFunc(); // undefined(this 指向全局对象)

原因

  • this是动态绑定的,其是在执行过程中确认的
  • const greetFunc = obj.greet就相当于const greetFunc = function greet(){ console.log(this.name) },实际的调用还是进行了普通的函数调用,并没有在对象中进行调用。

(2) 回调函数

回调函数(Callback Function) 是指 将一个函数作为参数传递给另一个函数,并在某个特定条件或事件发生后执行这个函数

function foo(){
    console.log(this.a);
}
function doFoo(fn){
    fn();
}
var obj = {
    a:2,
    foo:foo
};
var a = "oops,global!";

doFoo(obj.foo);

原因

  • 在这里传入的obj.foo就只代表了函数本身,此时函数没有执行,this没有绑定
  • doFoo函数中,执行了foo函数,属于普通调用,this直接绑定到了Window/global

同样的隐式丢失也会发生在语言内置的回调函数中:

function foo(){
    console.log(this.a);
}
var obj = {
    a:2,
    foo:foo
};
var a = "oops,global!";

setTimeout(obj.foo,1000);

输出结果为:oops,global!

原因与上面相同,setTimeout的函数实现大约是以下:

function setTimeout(fn,delay){
    // 等待delay毫秒
    fn(); // 直接调用fn
}

(3) 嵌套函数

在嵌套函数中某些情况也会发生this的隐式丢失:

const obj = {
  name: "Alice",
  greet() {
    function innerFunc() {
      console.log(this.name); 
    }
    innerFunc(); 
  },
};
obj.greet(); 

原因

  • 在调用obj.greet()后,直接执行了innerFunc(),属于普通的函数调用,因此this绑定到了Window/global
const obj = {
  name: "Bob",
  logName() {
    setTimeout(function() {
      console.log(this.name); // this → window/undefined(丢失)
    }, 1000);
  },
};
obj.logName(); // 输出 undefined

这个例子也是一样,都是发生了函数的直接调用,而非通过对象调用,发生this绑定与我们的预期不符,我们本来期待它绑定到obj,但是它绑定到了Window/global.

相信各位朋友们看到这里也都感觉出来了,隐式丢失不像一个bug,而更像是符合默认绑定规则的结果

这时候可能就有小伙伴要问了:“主播主播!既然它与我们期待的绑定不符,有没有什么办法强迫它绑定到某个地方上?”

Of Course!Man!What can i say!Mamba out!

微信图片_20250512074642.jpg

接下来我们介绍一下强人♂锁男的方法——————显式绑定

显式绑定

显式绑定的方法很简单,就是三个词:callapplybind

利用函数的这三个方法可以强制性将函数的this绑定到对象上,用法如下:

function.call()

call() 是 JavaScript 中的一种 函数调用方式,它允许你:

  1. 显式指定 this 的值(改变函数执行时的上下文)。
  2. 以参数列表形式传递参数

基本语法:

func.call(thisArg, arg1, arg2, ...)
  • thisArg:函数运行时 this 指向的对象(如果传 null/undefined,非严格模式下默认 this 指向全局对象)。
  • arg1, arg2, ... :函数需要的参数(逐个传递,而非数组)。

例子:

image.png

我们可以看到在这里利用了talk.call(me,'it',true),其将talk函数的this绑定到了me上,并向talk函数传递了两个参数,分别为'it'true

也就是说,第一个参数为this绑定的对象,其他参数均为传入函数的参数

函数的call方法,绑定了this指向,传递参数后会立刻执行函数

function.apply()

函数的apply方法与call方法是一模一样的,唯一的区别就是其可以将所有的参数作为数组传入:

image.png

没错,这就是唯一的区别,其他方面和call方法一模一样,只不过,执行起来call要更快一丢丢。

function.bind()

bind() 是 JavaScript 中用于 永久绑定 this 并返回新函数 的方法,与 call()apply() 不同的是,bind() 不会立即执行函数,而是返回一个绑定了 this 的新函数,稍后可以调用。

基本语法:

const boundFunc = func.bind(thisArg, arg1, arg2, ...)
  • thisArg:函数运行时 this 的指向(绑定后的上下文)。
  • arg1, arg2, ... (可选):提前传入函数的参数(部分应用)。
  • 返回值:返回一个新的函数,this 被固定为 thisArg

其中,它的参数和call一样,是一个一个传递的,不像apply一样可以数组传递

并且bind的有一个返回值,会返回一个新的函数,与原函数的区别就是this绑定到了thisArg

所以我们需要单独再创建一个变量来接受它的返回值,以方便我们以后调用。

例子:

function talk(lang, isPolite) {
    if (isPolite) {
        if (lang === 'en') {
            return `Hello,I am ${this.name}`
        }
        else if (lang === 'it') {
            return `Ciao,io sono ${this.name}`
        }
    }
    if (!isPolite) {
        if (lang === 'en') {
            return `${this.name},what you want`
        }
        else if (lang === 'it') {
            return `Sono ${this.name},🤬`
        }
    }
}

const me = {
    name: 'Sina'
}

const res = talk.bind(me, 'it', false);
console.log(res()); // Sono Sina,🤬

注意⚠⚠⚠

  1. bind 后的函数无法通过 call/apply 修改 this

    function greet() {
      console.log(this.name);
    }
    const boundGreet = greet.bind({ name: "Alice" });
    boundGreet.call({ name: "Bob" }); // 仍然输出 "Alice"
    
  2. bind 可以多次调用,但只有第一次生效

    const bound1 = greet.bind({ name: "Alice" });
    const bound2 = bound1.bind({ name: "Bob" }); // 仍然绑定 Alice
    bound2(); // "Alice"
    

New 绑定

new 绑定是一种 通过构造函数(Constructor Function)创建对象时绑定 this 的机制。当使用 new 关键字调用函数时,this 会被自动绑定到新创建的对象上。

例如:

function Person(name, age) {
   this.name = name;
   this.age = age;
}

const person = new Person('Trump', 79);

console.log(`My name is ${person.name}, I am ${person.age} years old`);

当利用new新创建一个对象时,引擎会调用构造函数,构造函数中的 this 会被绑定到新创建的实例对象,也就是说,在person这个变量中,它的this就指向了后面new Person('Trump', 79)这个实例。

绑定规则优先级排序

  1. new 绑定 (最高优先级)
  2. 显式绑定 (call/apply/bind)
  3. 隐式绑定 (通过对象调用)
  4. 默认绑定 (最低优先级,通常是全局对象或 undefined)

显式绑定 vs 隐式绑定

const obj1 = { name: 'Alice' };
const obj2 = { name: 'Bob' };
 
function showName() {
  console.log(this.name);
}
 
obj1.showName = showName;
obj2.showName = showName;
 
obj1.showName.call(obj2); // 'Bob' (显式绑定优先)

new 绑定 vs 显式绑定

function Person(name) {
  this.name = name;
}
 
const obj = { name: 'Pre-bound' };
const BoundPerson = Person.bind(obj);  // Person的this指向了obj
 // new BoundPerson是因为BoundPerson的this已经被修改,我们看看用new能不能覆盖其原来的修改
const p = new BoundPerson('New instance'); // BoundPerson的this指向了'New instance'
// 创建了新实例
console.log(p.name); // 'New instance' (`new` 绑定优先)
console.log(obj.name); // 'Pre-bound' (未被覆盖)

当然,new绑定和显式绑定一起出现的概率极其小,一般不会对构造函数使用。

关于箭头函数

this在箭头函数中的绑定规则是独立的,箭头函数本身不会创建新的this,而会利用外层作用域的this,来当作自己的this.

例如:

 const obj = {
            name: 'Alice',
            regularFunc: function () {
                console.log('Regular function:', this.name); // this 指向 obj
            },
            arrowFunc: () => {
                console.log('Arrow function:', this.name); // this 指向外层(这里可能是 window/global)
            }
        };
        var name = 'Skye'
        obj.regularFunc(); // 输出: "Regular function: Alice"
        obj.arrowFunc();   // 输出: "Arrow function: Skye"

注意:!!!!!!

这是在浏览器中运行得到的结果,在node中运行结果就会不一样,因为 箭头函数的外层作用域是它的定义位置所在的词法作用域,而不是调用位置。由于对象本身不会形成作用域,所以在这里箭头函数会跳过对象,直接继承外层的作用域中的this,其中在node.js中指向的是顶层模块,在浏览器中指向的是window.如果想让arrowFunc()输出Skye,则在全局内声明this.name='Skye'就可以了。

模块顶层作用域this 是一个空对象 {}

结语

OK啦,这一次关于this的讲解就是这样了,如有不对的地方,期待各位大佬多多指正,我们下一期再见啦!

67ED92D97FC3BEE13FEBE8BC90949971.jpg