0

0

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

betcha

betcha

发布时间:2025-09-19 22:06:01

|

947人浏览过

|

来源于php中文网

原创

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 元素。这在某些情况下是方便的,但如果你想在事件处理函数中访问其定义所在对象的属性,就可能遇到问题。

    
    

    这里

    app.handleClick
    作为回调函数,
    this
    变成了
    myButton
    元素,导致
    this.name
    访问不到
    app
    对象的
    name

    Change Style AI
    Change Style AI

    多风格照片生成器!AI生成30种照片

    下载
  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
机制的深刻理解中。

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

536

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

372

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

707

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

470

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

388

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

989

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

652

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

537

2023.09.20

虚拟号码教程汇总
虚拟号码教程汇总

本专题整合了虚拟号码接收验证码相关教程,阅读下面的文章了解更多详细操作。

25

2025.12.25

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 1.8万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 2.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号