JS 函数绑定与 this 指向 – 五种绑定规则的优先级与例外情况

this指向的优先级顺序为:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定,箭头函数则采用词法作用域确定this。

JS 函数绑定与 this 指向 – 五种绑定规则的优先级与例外情况

JavaScript 函数的

this

指向,说白了,就是函数执行时,它内部那个

this

关键字到底代表谁。这背后有五种核心的绑定规则在起作用,它们之间存在一个明确的优先级顺序:

new

绑定 > 显式绑定 (

call

/

apply

/

bind

) > 隐式绑定 > 默认绑定。而箭头函数则是一个特例,它根本不遵循这些规则,而是采用词法作用域来决定

this

。理解这些,是掌握 JS 函数行为的关键。

解决方案

要深入理解

this

,我们得把这五种绑定规则掰开揉碎了看,它们各自有自己的适用场景和逻辑,但又彼此影响。

  1. 默认绑定 (Default Binding) 这玩意儿最没存在感,但又无处不在。当一个函数作为普通函数被独立调用,没有任何其他绑定规则施加影响时,

    this

    会指向全局对象(在浏览器里是

    window

    ,Node.js 里是

    global

    )。但这里有个大坑:严格模式下,默认绑定会让

    this

    设为

    undefined

    。这是个重要的区别,因为

    undefined

    上你什么都访问不到,直接报错。

    function showThis() {     console.log(this); }  showThis(); // 浏览器非严格模式下:Window 对象;Node.js 非严格模式下:Global 对象 // 'use strict'; // showThis(); // 严格模式下:undefined
  2. 隐式绑定 (Implicit Binding) 这是最常见的,也是最容易让人误解的。当函数被作为某个对象的方法调用时,

    this

    会指向那个调用它的对象。简单来说,就是“谁调用我,我就是谁”。

    const person = {     name: 'Alice',     greet: function() {         console.log(`Hello, my name is ${this.name}`);     } };  person.greet(); // Hello, my name is Alice (this 指向 person)

    但这里有个经典的“

    this

    丢失”问题。如果你把

    person.greet

    赋值给另一个变量,或者作为回调函数传递,隐式绑定就失效了,会退回到默认绑定:

    const sayHello = person.greet; sayHello(); // Hello, my name is undefined (非严格模式下,this 指向 Window/Global,name 属性不存在)
  3. 显式绑定 (Explicit Binding) 当你想强行指定

    this

    的时候,

    call()

    apply()

    bind()

    就派上用场了。它们允许你明确地告诉函数,它的

    this

    应该是什么。

    • call(thisArg, arg1, arg2, ...)

      :立即执行函数,并接受多个参数。

    • apply(thisArg, [argsArray])

      :立即执行函数,并接受一个参数数组。

    • bind(thisArg, arg1, arg2, ...)

      :不会立即执行函数,而是返回一个新函数,这个新函数的

      this

      永远被绑定到

      thisArg

    function introduce(age, city) {     console.log(`My name is ${this.name}, I am ${age} years old and live in ${city}.`); }  const anotherPerson = { name: 'Bob' };  introduce.call(anotherPerson, 30, 'New York'); // My name is Bob, I am 30 years old and live in New York. introduce.apply(anotherPerson, [25, 'London']); // My name is Bob, I am 25 years old and live in London.  const boundIntroduce = introduce.bind(anotherPerson, 40); boundIntroduce('Paris'); // My name is Bob, I am 40 years old and live in Paris.

    一个需要注意的“陷阱”是,如果你给

    call

    /

    apply

    /

    bind

    传入

    null

    undefined

    作为

    thisArg

    ,那么

    this

    会被忽略,转而使用默认绑定规则。

  4. new

    绑定 (New Binding) 这更像是一个构造器的魔法,

    this

    被赋予了一个全新的身份。当使用

    new

    关键字调用一个函数(通常我们称之为构造函数)时,会发生以下几件事:

    • 创建一个全新的空对象。
    • 这个新对象会被设置为该构造函数调用的
      this

    • 函数体内的代码执行,为这个新对象添加属性和方法。
    • 如果构造函数没有显式返回其他对象,那么
      new

      表达式会隐式返回这个新对象。

    function Car(make, model) {     this.make = make;     this.model = model;     // console.log(this); // 这里的 this 就是新创建的 Car 实例 }  const myCar = new Car('Honda', 'Civic'); console.log(myCar.make); // Honda

    在这种情况下,

    this

    永远指向新创建的实例对象。

  5. 词法绑定 (Lexical Binding) – 箭头函数 箭头函数,这家伙就是个“叛逆者”,它根本不关心自己的

    this

    ,只看它爸妈是谁。箭头函数没有自己的

    this

    绑定,它会捕获其所在(定义时)的上下文的

    this

    值,作为自己的

    this

    。这个

    this

    的值在箭头函数定义时就已经确定,并且之后不会改变。

    const user = {     name: 'Charlie',     logName: function() {         setTimeout(function() {             console.log(this.name); // 默认绑定,this 指向 Window/Global,输出 undefined         }, 100);     },     logNameArrow: function() {         setTimeout(() => {             console.log(this.name); // 词法绑定,this 继承自 logNameArrow 所在的 user 对象,输出 Charlie         }, 100);     } };  user.logName(); user.logNameArrow();

    因此,箭头函数实际上是跳过了前面四种规则,直接从外层作用域“借用”

    this

JavaScript 中

this

绑定规则的优先级是怎样的?

理解

this

的优先级,就像是在解决一个复杂的决策树。当一个函数被调用时,JavaScript 引擎会按照一个特定的顺序去检查这些绑定规则,一旦找到匹配的规则,就会停止查找并应用该规则。这个优先级顺序是:

  1. new

    绑定:这是最高优先级的。如果函数是作为构造函数被

    new

    关键字调用,那么

    this

    就会指向新创建的对象,其他所有规则都会被忽略。

  2. 显式绑定:紧随其后的是
    call()

    apply()

    bind()

    。如果你通过这些方法明确地指定了

    this

    ,那么它就会覆盖隐式绑定和默认绑定。需要注意的是,

    bind()

    创建的新函数,其

    this

    一旦绑定就无法再次被显式绑定(除非是

    new

    调用)。

  3. 隐式绑定:如果函数是作为对象的方法被调用,那么
    this

    会指向那个调用它的对象。这比默认绑定优先级高。

  4. 默认绑定:这是最低优先级的。当以上所有规则都不适用时,函数会采用默认绑定规则,将
    this

    指向全局对象(非严格模式)或

    undefined

    (严格模式)。

箭头函数的特殊性: 箭头函数是个“局外人”,它不参与这个优先级排序。它的

this

是在定义时通过词法作用域确定的,一旦确定就雷打不动,不会受到

call

/

apply

/

bind

的影响,也不会因为被作为方法调用或

new

调用(箭头函数不能被

new

调用)而改变。你可以理解为,箭头函数在

this

绑定方面有自己的独立王国,凌驾于所有传统绑定规则之上。所以,如果你看到一个箭头函数,首先考虑它的外层作用域

this

是什么,而不是去套用那四条规则。

在实际开发中,

this

指向有哪些常见的‘陷阱’或‘意外’?

this

指向在实际开发中确实是块“雷区”,一不小心就可能踩到。这些“陷阱”往往源于对绑定规则理解不够透彻,或者是在不同上下文之间切换时,

this

行为的变化。

  1. 回调函数中的

    this

    丢失 这是最常见的问题之一。当你将一个对象的方法作为回调函数(例如

    setTimeout

    事件监听器、数组方法

    map

    /

    filter

    等)传递时,它通常会失去其原有的隐式绑定,转而采用默认绑定。

    const counter = {     count: 0,     increment: function() {         console.log(this.count++); // 期望是 this 指向 counter     },     start: function() {         setTimeout(this.increment, 1000); // 陷阱!this.increment 被作为普通函数调用     } }; counter.start(); // 1秒后输出 NaN 或报错,因为 this 变成了 Window/Global

    这里的

    this.increment

    setTimeout

    内部执行时,

    this

    不再指向

    counter

    对象,而是

    window

    (非严格模式)或

    undefined

    (严格模式)。

  2. 事件处理函数中的

    this

    在 DOM 事件处理函数中,

    this

    通常会指向触发事件的那个 DOM 元素。这在某些情况下是方便的,但如果你想在事件处理函数中访问其定义所在对象的属性,就可能遇到问题。

    <button id="myButton">Click Me</button> <script> const app = {     name: 'My App',     handleClick: function() {         console.log(`App name: ${this.name}`); // 期望 this 指向 app         console.log(`Button ID: ${this.id}`); // 期望 this 指向 button     } }; document.getElementById('myButton').addEventListener('click', app.handleClick); // 点击按钮后:App name: undefined (this 指向 button,button 没有 name 属性) // Button ID: myButton </script>

    这里

    app.handleClick

    作为回调函数,

    this

    变成了

    myButton

    元素,导致

    this.name

    访问不到

    app

    对象的

    name

    JS 函数绑定与 this 指向 – 五种绑定规则的优先级与例外情况

    Papercup

    使用AI为视频制作配音,可以自动翻译和本地化视频。

    JS 函数绑定与 this 指向 – 五种绑定规则的优先级与例外情况257

    查看详情 JS 函数绑定与 this 指向 – 五种绑定规则的优先级与例外情况

  3. call

    /

    apply

    /

    bind

    传入

    null

    /

    undefined

    虽然显式绑定可以强制改变

    this

    ,但如果你不小心传入

    null

    undefined

    ,JavaScript 会将其忽略,转而使用默认绑定规则。

    function greet() {     console.log(`Hello, ${this.name || 'Stranger'}`); } const globalName = 'World'; // 在全局作用域定义 greet.call(null); // Hello, World (非严格模式下,this 指向 Window/Global) // 'use strict'; // greet.call(null); // Hello, Stranger (严格模式下,this 仍为 null/undefined,没有 name 属性)

    这可能导致意外地访问到全局变量,或者在严格模式下直接报错,而不是你期望的

    this

    null

    undefined

  4. 箭头函数与传统函数的混用 箭头函数因为其词法

    this

    特性,在某些场景下非常方便,但也可能导致困惑。尤其是在对象方法中嵌套使用时。

    const myObject = {     value: 10,     getValue: function() {         return this.value; // this 指向 myObject     },     getArrowValue: () => {         return this.value; // 陷阱!this 指向定义时的全局对象(Window/Global),而不是 myObject     } }; console.log(myObject.getValue());      // 10 console.log(myObject.getArrowValue()); // undefined (或全局对象的 value 属性)
    getArrowValue

    是一个箭头函数,它的

    this

    myObject

    定义时,指向的是全局对象,而不是

    myObject

    本身。

如何有效地管理和控制 JavaScript 函数的

this

指向?

有效地管理

this

指向,核心在于理解其绑定规则和优先级,并选择最适合当前场景的策略。这不仅仅是避免错误,更是写出清晰、可维护代码的关键。

  1. 使用

    bind()

    方法进行永久绑定 当你需要将一个方法作为回调函数传递,但又希望它始终保持对其原始对象的

    this

    引用时,

    bind()

    是你的首选。它会返回一个新函数,这个新函数的

    this

    已经被永久固定。

    const counter = {     count: 0,     increment: function() {         console.log(++this.count);     },     start: function() {         // 使用 bind 绑定 this 到 counter 对象         setTimeout(this.increment.bind(this), 1000);     } }; counter.start(); // 1秒后输出 1

    对于事件监听器,这也是一个常见且有效的模式。

  2. 利用箭头函数的词法

    this

    箭头函数在处理回调函数或嵌套函数时,能够极大地简化

    this

    的管理,因为它没有自己的

    this

    ,而是捕获外层作用域的

    this

    。这让

    this

    的行为变得非常可预测。

    const user = {     name: 'David',     greetDelayed: function() {         // 这里的 this 指向 user         setTimeout(() => {             // 箭头函数捕获了外层 greetDelayed 的 this,所以也指向 user             console.log(`Hello, ${this.name}`);         }, 500);     } }; user.greetDelayed(); // 0.5秒后输出 "Hello, David"

    当你在一个方法内部需要定义另一个函数,并且希望这个内部函数的

    this

    仍然指向外部方法所属的对象时,箭头函数是完美的解决方案。

  3. 使用

    call()

    apply()

    进行一次性

    this

    绑定 如果你的需求只是在特定调用时临时改变

    this

    的指向,并且需要立即执行函数,那么

    call()

    apply()

    是理想选择。它们在运行时提供了灵活的

    this

    控制。

    function displayInfo(message) {     console.log(`${message}: ${this.id}`); }  const element = { id: 'my-element' }; displayInfo.call(element, 'Element ID'); // Element ID: my-element

    这在需要借用其他对象的函数时特别有用,例如,将一个数组方法应用于一个类数组对象。

  4. 在构造函数中使用

    new

    当你在设计构造函数(或者 ES6 的

    class

    )来创建对象实例时,

    new

    关键字会自动处理

    this

    的绑定,将其指向新创建的实例。这是 JavaScript 面向对象编程的基础。

    class Person {     constructor(name) {         this.name = name;     }     sayName() {         console.log(this.name);     } } const p = new Person('Eve'); p.sayName(); // Eve

    在这种模式下,你通常不需要手动干预

    this

    的绑定,因为

    new

    已经为你做好了。

总的来说,管理

this

的关键在于“知其然,知其所以然”。理解每种绑定规则的运作方式和优先级,才能在不同的场景下灵活选择合适的策略。是需要一个永久绑定的新函数?还是一个能捕获外层

this

的简洁回调?亦或只是一次性的临时

this

切换?答案就在你对

this

机制的深刻理解中。

javascript es6 java js node.js node 浏览器 app 回调函数 win 面向对象编程 JavaScript es6 NULL 面向对象 构造函数 Filter 全局变量 回调函数 class 参数数组 map JS undefined 对象 作用域 事件 default 严格模式 dom this

上一篇
下一篇