当前位置: 首页 > news >正文

【javascript】this关键字

this 指向总结

this 的指向取决于函数的调用方式:

  • 全局上下文:指向全局对象(windowglobal
  • 普通函数调用:指向全局对象或 undefined(严格模式)
  • 对象方法调用:指向该对象
  • 构造函数调用:指向新创建的对象
  • callapplybind:指向指定的对象
  • 箭头函数:捕获其所在上下文的 this
  • 事件处理程序:指向触发事件的 DOM 元素
  • setTimeoutsetInterval:指向全局对象

this 介绍

在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)。this 不能在执行期间被赋值,并且在每次函数被调用时 this 的值也可能会不同。

ES5 引入了 bind 方法来设置函数的 this 值,而不用考虑函数如何被调用的。
ES2015(es6) 引入了箭头函数,箭头函数不提供自身的 this 绑定(this 的值将保持为闭合词法上下文的值)。

this 指向:

在 JavaScript 中,this 关键字的指向在不同上下文中会有不同的结果。
以下是几种常见的场景:

1. 全局上下文

在全局执行环境中,this 指向全局对象

  • 在浏览器中,全局对象是 window
  • 在 Node.js 中,全局对象是 global
    console.log(this); // 在浏览器中输出 window 对象,在 Node.js 中输出 global 对象
    

2. 函数上下文

this 出现在函数内部时,它的指向取决于函数的调用方式。

普通函数调用

在 JavaScript 中,严格模式和非严格模式下 this 的指向区别主要体现在普通函数调用时的行为上在非严格模式下,普通函数调用时 this 指向全局对象;而在严格模式下,普通函数调用时 this 指向 undefined。在其他调用场景(如对象方法调用、构造函数调用等)中,严格模式和非严格模式下 this 的指向是一致的。

当函数作为一个独立的函数被调用时,this 的指向取决于运行环境:

  • 在严格模式下:

    • 如果函数在没有被任何东西访问的情况下被调用,this 将是 undefined
      	function logThis() {"use strict";console.log(this);}[1, 2, 3].forEach(logThis);  // undefined、undefined、undefined```
      
    • 如果方法被访问的值是一个原始值,this 也将是一个原始值
      function getThisStrict() {"use strict"; // 进入严格模式return this;
      }
      // 仅用于演示——你不应该改变内置的原型对象
      Number.prototype.getThisStrict = getThisStrict;
      console.log(typeof (1).getThisStrict()); // "number"
      
  • 在非严格模式下,一个特殊的过程称为 this 替换以确保 this 的值总是一个对象。这意味着:

    • 如果一个函数被调用时 this 被设置为 undefinednullthis 会被替换为 globalThis。
    • 如果函数被调用时 this 被设置为一个原始值,this 会被替换为原始值的包装对象
      function getThis() {return this;
      }
      // 仅用于演示——你不应该修改内置的原型对象
      Number.prototype.getThis = getThis;
      console.log(typeof (1).getThis()); // "object"
      console.log(getThis() === globalThis); // true
      
    • 当一个函数作为回调函数传递时,this 的值取决于如何调用回调,这由 API 的实现者决定。回调函数通常以 undefined 作为 this 的值被调用(直接调用,而不附加到任何对象上),这意味着如果函数是在非严格模式,this 的值会是全局对象(globalThis)。这在迭代数组方法、Promise() 构造函数等例子中都是适用的。
      function logThis() {console.log(this);
      }
      [1, 2, 3].forEach(logThis); // Window {...}、Window {...}、Window {...}
      
      一些 API 允许你为回调函数的调用设置一个 this 值。例如,所有的迭代数组方法和相关的方法,如Set.prototype.forEach(),都接受一个可选的 thisArg 参数。
      [1, 2, 3].forEach(logThis, { name: "obj" });
      // { name: 'obj' }, { name: 'obj' }, { name: 'obj' }
      

作为对象方法调用

当函数作为对象的方法被调用时,this 指向该对象。

const obj = {name: 'test',test: function() {console.log(this); }
};
obj.test();
// 输出 { name: 'test', test: f() }

构造函数调用

当函数作为构造函数被调用(使用 new 关键字),this 指向新创建的对象

function Person(name) {this.name = name;
}
const person = new Person('test');
console.log(person.name); 
// 输出 'test'

使用 callapplybind 调用

callapplybind 方法可以明确指定函数内部 this 的指向。

const obj1 = { name: 'obj1' };
const obj2 = { name: 'obj2' };function test() {console.log(this.name);
}test.call(obj1); // 输出 'obj1'
test.apply(obj2); // 输出 'obj2'const boundFunc = test.bind(obj1);
boundFunc(); // 输出 'obj1'

3. super

在 JavaScript 的类继承中,super 关键字用于调用父类的构造函数或方法。当使用 super.method() 调用父类的方法时,该方法内部的 this 指向的是当前子类的实例,而不是父类的实例。这是因为 super.method() 的调用上下文是子类的实例,而不是父类的实例。

class Parent {constructor() {this.parentValue = "Parent";}getParentValue() {console.log(this.parentValue);return this.parentValue;}
}class Child extends Parent {constructor() {super(); // 调用父类的构造函数this.childValue = "Child";}getChildValue() {// 调用父类的 getParentValue 方法super.getParentValue(); // 输出 "Parent"console.log(this.childValue); // 输出 "Child"return this.childValue;}
}const child = new Child();
child.getChildValue();
// 输出:
// Parent
// Child

4. 类上下文

JavaScript 的存在两种上下文:静态上下文实例上下文

  • 实例上下文:包括构造函数、实例方法和实例字段初始化器。在这些地方,this 指向类的实例

    • 构造函数: 通过 new 调用,this 指向新创建的实例。
      class Person2 {constructor(name) {this.name = name; // this = 新实例}
      }
      const alice = new Person2("Alice");
      console.log(alice.name); 
      // 输出: "Alice"
      
    • 实例方法:this 是调用它的对象(通常是实例)。
      	window.name = "Global";class Person2 {constructor(name) {this.name = name; }greet() { //实例方法console.log(`Hello, I'm ${this.name}`);}}const alice = new Person2("Alice");alice.greet(); // 输出: "Hello, I'm Alice"// 如果方法被提取到其他对象--如下所示--this 将不再指向实例,this 将指向全局对const greetFn = alice.greet;greetFn();// 报错: Cannot read property 'name' of undefined// 报错原因:ES6 类语法设计上强制启用严格模式,确保代码更安全
      
    • 实例字段初始化器:this 指向正在构造的实例
      class Person3 {age = 30; // 实例字段getAge = () => {console.log(this.age); // 箭头函数绑定实例};
      }
      const bob = new Person3();
      bob.getAge();
      // 输出: 30
      
  • 静态上下文:包括静态方法、静态字段初始化器和静态初始化块。在这些地方,this 指向类本身(或子类)

    • 静态方法: this 指向类本身
      class Person4 {static species = "Human";static describe() {console.log(`Species: ${this.species},this:${this}`); // this = Person 类}
      }
      Person4.describe();// 子类继承静态方法
      class Student extends Person4 { }
      Student.describe();
      //输出:
      // Species: Human, this: class Person4 {
      //     static species = "Human";
      //     static describe() {
      //         console.log(`Species: ${this.species},this:${this}`); // this = Person 类
      //     }
      // }
      // Species: Human, this: class Student extends Person4 { }
      
    • 静态字段初始化器: this 指向类本身
      class MyClass {static staticValue = 42;static staticArrow = () => {console.log(this.staticValue); // 箭头函数绑定类};
      }
      MyClass.staticArrow(); // 输出: 42
      
    • 静态初始化块: 初始化块中的 this 指向当前类
      class MyClass2 {static x;static {this.x = 100; // this = MyClass}
      }
      console.log(MyClass2.x); // 输出: 100
      

5. 箭头函数

箭头函数没有自己的 this,它会捕获其所在上下文的 this 值。

const obj = {name: 'test',test: () => {console.log(this); // 输出全局对象(window 或 global)}
};
obj.test();

在全局代码中,无论是否在严格模式下,由于全局上下文绑定,this 值总是 globalThis

const globalObject = this;
const foo = () => this;
console.log(foo() === globalObject); 
// 输出:true

箭头函数在其周围的作用域上创建一个 this 值的闭包,这意味着箭头函数的行为就像它们是“自动绑定”的——无论如何调用,this 都绑定到函数创建时的值(在上面的例子中,是全局对象)。在其他函数内部创建的箭头函数也是如此:**它们的 this 值保持为闭合词法上下文的 this **。

const obj4 = {getThisGetter() {const getter = () => this;return getter;},
};
const fn = obj4.getThisGetter();
console.log(fn() === obj4); 
// 输出:true
const fn2 = obj4.getThisGetter;
//调用 fn2()() 将返回 globalThis,因为它遵循 fn2() 的 this,由于它没有附加到任何对象上进行调用,所以是 globalThis。
console.log(fn2()() === globalThis); 
// 输出:在非严格模式下为 true

当使用 call()、bind() 或 apply() 调用箭头函数时,thisArg 参数会被忽略。

const foo = () => this;
const obj3 = { name: "obj" };
// 尝试使用 call 设置 this
console.log(foo.call(obj3) === globalObject); // true
// 尝试使用 apply 设置 this
console.log(foo.apply(obj3) === globalObject); // true
// 尝试使用 bind 设置 this
const boundFoo = foo.bind(obj3);
console.log(boundFoo() === globalObject); // true

6. 事件处理程序

当函数作为事件处理程序时,this 指向触发该事件的 DOM 元素

const button = document.createElement('button');
button.addEventListener('click', function() {console.log(this); // 输出按钮元素
});

7. setTimeout 和 setInterval

setTimeoutsetInterval 中,this 指向全局对象。

setTimeout(function() {console.log(this); // 在浏览器中输出 window,在 Node.js 中输出 global
}, 1000);

拓展

补充一些概念。

JavaScript 字段初始化器(Field Initializers)

一、字段初始化器的定义

字段初始化器是 ES6+ 中直接在类中声明属性值的语法,用于 自动初始化属性,分为两类:

  1. 实例字段初始化器:为每个实例初始化属性。它们会在 构造函数执行前 运行,并可以访问实例的 this
  2. 静态字段初始化器:为类本身初始化属性。
class MyClass {instanceField = "实例属性";   // 实例字段初始化器static staticField = "静态属性"; // 静态字段初始化器
}

二、核心特性对比

特性实例字段初始化器静态字段初始化器
所属对象实例属性(每个实例独有)静态属性(类自身拥有)
初始化时机在构造函数执行 运行在类定义加载时运行
this 指向当前正在构造的实例类本身(构造函数)
访问其他字段可访问已声明的实例字段(注意顺序)可访问已声明的静态字段
应用场景实例默认值、方法绑定类级别的配置、共享数据

三、实例字段初始化器详解

1. 初始化时机
  • 在构造函数之前执行:字段初始化器的赋值操作发生在构造函数代码之前。
  • 执行顺序:父类字段 → 子类字段 → 父类构造函数 → 子类构造函数。
    class Test {a = console.log("字段初始化器执行");constructor() {console.log("构造函数执行");}
    }
    new Test();
    // 输出顺序:
    // 字段初始化器执行
    // 构造函数执行
    
2. 基础用法
class Person {name = "Anonymous";  // 实例字段初始化器age = 0;             // 初始化为 0constructor(name) {if (name) this.name = name; // 可覆盖默认值}
}const alice = new Person("Alice");
console.log(alice.name); // "Alice"
console.log(alice.age);  // 0
3. 箭头函数绑定 this
class Button {handleClick = () => { // 箭头函数绑定实例的 thisconsole.log("Clicked", this);};
}const btn = new Button();
document.addEventListener("click", btn.handleClick); // 无论何时调用,this 正确指向实例
4. 初始化顺序问题
class Problematic {a = this.b + 1; // ❌ 错误:b 尚未初始化b = 2;
}class Correct {b = 2;a = this.b + 1; // ✅ 正确:b 已初始化
}
5. 与传统构造函数初始化的对比
1. 构造函数中初始化(传统方式)
class Person {constructor() {this.name = "Anonymous";this.age = 0;}
}
2. 使用字段初始化器(ES6+)
class Person {name = "Anonymous";age = 0;constructor() {} // 可省略
主要区别:
特性字段初始化器构造函数初始化
代码可读性属性声明集中,更直观分散在构造函数中
多构造函数支持需在每个构造函数中重复初始化代码只需在基类声明一次
执行顺序在构造函数之前运行在构造函数内部运行
访问其他字段需注意声明顺序可自由访问已初始化的字段

四、静态字段初始化器详解

1. 基础用法
class Config {static apiUrl = "https://api.example.com"; // 静态字段static maxRetry = 3;
}
console.log(Config.apiUrl); // "https://api.example.com"
2. 访问其他静态成员
class MathUtils {static PI = 3.14159;static radius = 10;static area = this.PI * this.radius ** 2; // ✅ 正确:访问静态字段
}
console.log(MathUtils.area); // 314.159
3. 静态初始化块(Static Block)
class Database {static connection;static {// 复杂初始化逻辑this.connection = connectToDB();}
}

五、执行顺序规则

  1. 父类静态字段 → 子类静态字段
  2. 父类实例字段 → 父类构造函数
  3. 子类实例字段 → 子类构造函数
class Parent {parentField = console.log("父类实例字段");static parentStatic = console.log("父类静态字段");constructor() { console.log("父类构造函数"); }
}class Child extends Parent {static childStatic = console.log("子类静态字段");childField = console.log("子类实例字段");constructor() { super();console.log("子类构造函数"); }
}new Child();
/* 输出顺序:
父类静态字段
子类静态字段
父类实例字段
父类构造函数
子类实例字段
子类构造函数
*/

六、注意事项

  1. 避免循环依赖
    字段之间不要相互依赖未初始化的属性。
  2. 性能影响
    避免在字段初始化器中执行复杂操作(每次实例化都会运行)。
  3. 动态值限制
    字段初始化器无法接收参数,动态值需在构造函数中处理。

七、总结

  • 用途
    • 实例字段:定义实例的默认属性,解决 this 绑定问题。
    • 静态字段:定义类级别的配置或共享数据。
  • 优势
    • 代码更简洁,属性声明集中化。
    • 箭头函数自动绑定 this,避免方法丢失上下文。
  • 适用场景
    • 简单默认值、事件处理器绑定、类级别常量。

静态初始化块(Static Initialization Blocks)

一、静态初始化块的定义

静态初始化块是 ES2022(ES13) 引入的特性,用于在类定义时执行复杂的静态成员初始化逻辑。它通过 static {} 语法声明,在类加载阶段运行一次,适用于需要多步骤计算、异常处理或依赖其他静态字段的场景。

基本语法:

class MyClass {static staticProperty;static { // 静态初始化块this.staticProperty = 'value';}
}

二、核心特性

1. 执行时机与顺序
  • 触发条件:当类被首次加载时(如首次访问静态成员、创建实例或继承时)。
  • 执行顺序
    • 父类静态块 → 子类静态块(遵循继承链)。
    • 同一类中按代码顺序执行(静态块与静态字段初始化交织)。
    • 所有静态块在 实例化对象前 完成。

示例

class Parent {static parentProp = 'Parent';static { console.log('Parent 静态块'); }
}class Child extends Parent {static childProp = 'Child';static { console.log('Child 静态块'); }
}// 输出顺序:
// Parent 静态块
// Child 静态块
2. 作用域与 this 指向
  • 作用域:静态块内可访问类的静态成员,无法访问实例成员。
  • this 指向this 指向当前类(构造函数),而非实例。
  • 变量隔离:静态块内声明的变量为块级作用域,外部不可访问。

示例

class Example {static x = 1;static {this.x = 2; // 修改静态属性const y = 3; // 块内局部变量console.log(this.x); // 2}
}

三、典型应用场景

1. 复杂静态属性初始化

当静态属性的初始化需要多步计算或依赖其他静态字段时:

class Circle {static radius = 10;static area;static circumference;static {this.area = Math.PI * this.radius ** 2;this.circumference = 2 * Math.PI * this.radius;}
}
2. 异常处理

在初始化过程中捕获错误:

class Config {static apiKey;static {try {this.apiKey = localStorage.getItem('apiKey') || 'default';} catch (e) {this.apiKey = 'default';}}
}
3. 初始化私有静态字段

配合 # 语法初始化私有静态字段:

class SecureData {static #secretKey;static {this.#secretKey = generateCryptoKey(); // 假设 generateCryptoKey 是外部函数}
}

四、与静态字段初始化器的对比

特性静态字段初始化器(static x = 1;静态初始化块(static { ... }
适用场景简单赋值(单行表达式)多步逻辑、异常处理、依赖其他字段
执行顺序按代码顺序与静态块交织执行按代码顺序执行(可包含多个块)
代码灵活性低(仅限表达式)高(支持语句、循环、条件判断)
异常处理无法直接捕获异常支持 try/catch

五、注意事项

  1. 避免循环依赖
    静态块中不要引用尚未初始化的静态字段。

    class Problem {static a = this.b; // ❌ 错误:b 未初始化static b = 2;
    }
    
  2. 性能影响
    避免在静态块中执行耗时操作,否则会延迟类加载速度。

  3. 不可访问实例成员
    静态块无法通过 this 访问实例属性或方法。

    class MyClass {instanceProp = 42;static {console.log(this.instanceProp); // undefined}
    }
    

六、总结

  • 核心价值:提供类级别复杂初始化的解决方案,弥补静态字段初始化器的不足。
  • 最佳实践
    • 简单赋值用静态字段,复杂逻辑用静态块。
    • 优先处理静态成员间的依赖关系。
    • 避免在静态块中引入副作用或全局污染。
  • 兼容性:ES2022+ 支持,现代浏览器及 Node.js 16+ 已实现。

参考

this

http://www.lqws.cn/news/534817.html

相关文章:

  • vue + vue-router写登陆验证的同步方法和异步方法,及页面组件的分离和后端代码
  • Unity Netcode自定义数据传输——结构体及其序列化
  • .NET测试工具Parasoft dotTEST内置安全标准,编码合规更高效
  • 基于STM32的智能书房系统的设计
  • SpringBoot定时任务 - Timer实现方式
  • 算法打卡 day4
  • 大数据赋能智慧城市:从数据洪流到科学规划的“智慧之匙”
  • Leetcode百题斩-DP
  • 全面学习 OpenAI API:从 Python 教程到 API Key 使用详解,快速上手调用和部署
  • 微服务分布式事务解决方案
  • Beam2.61.0版本消费kafka重复问题排查
  • Git 使用规范与命令使用场景详解
  • 【Excel数据分析】花垣县事业单位出成绩了,用Excel自带的M语言做一个数据分析
  • 45. 跳跃游戏 II
  • uniapp 和原生插件交互
  • Sentinel 授权规则详解与自定义异常处理
  • Tailwind CSS 尺寸控制
  • c++多线程编程
  • 《聊一聊ZXDoc》之汽车标定、台架标定、三高标定
  • 基于定制开发开源AI智能名片S2B2C商城小程序源码的H5游戏开发模式创新研究
  • 从零开始的云计算生活——第二十四天,重起航帆,初见MySQL数据库
  • 智能体决策框架对决:ReAct极速响应 vs Plan-and-Execute稳控全局
  • 【全志V821_FoxPi】3-2 Linux 5.4 SPI + XPT2046触摸(ADS7846) + tslib
  • SQL SERVER存储过程
  • 分享一些实用的PHP函数(对比js/ts实现)
  • VIVADO设定寄存器/存储器的初始值
  • 深入解析与修复 Linux 中的种种依赖项错误:Dependencies packages error solution
  • 【UniApp 日期选择器实现与样式优化实践】
  • 03.图生图基础工作流|提示词自动化|存储节点预设|提示词风格化
  • 以太网基础与 VLAN 配置实验