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

深入理解 this 指向与作用域解析

引言

JavaScript 中的 this 关键字的灵活性既是强大特性也是常见困惑源。理解 this 的行为对于编写可维护的代码至关重要,但其动态特性也会让我们感到困惑。

与大多数编程语言不同,JavaScript 的 this 不指向函数本身,也不指向函数的词法作用域,而是根据函数调用方式在运行时动态确定。这种灵活性虽然强大,但也容易引起混淆,让我们一步步揭开这个谜团。

执行上下文与 this 基础

什么是执行上下文?

执行上下文是 JavaScript 引擎执行代码时创建的环境,包含三个重要组成部分:

  • 变量对象:存储变量、函数声明和函数参数
  • 作用域链:当前上下文和外部上下文的变量对象组成的链表
  • this 值:当前执行代码的上下文对象

JavaScript 引擎创建执行上下文的时机有三种:

  1. 全局执行上下文:代码执行前创建,只有一个
  2. 函数执行上下文:每次调用函数时创建
  3. Eval 执行上下文:执行 eval 函数内的代码时创建

理解执行上下文对理解 this 至关重要,因为 this 是上下文的一部分,会根据函数的调用方式而变化。

// 全局执行上下文中的 this
console.log(this); // 在浏览器中指向 window 对象,Node.js 中指向 global 对象// 函数执行上下文中的 this
function checkThis() {console.log(this); // 非严格模式下依然指向全局对象
}checkThis(); 

在上面的例子中,当我们在全局作用域直接访问 this 时,它指向全局对象。这是因为此时我们处于全局执行上下文中。而当我们调用 checkThis() 函数时,尽管创建了新的函数执行上下文,但 this 仍指向全局对象,这是因为函数是被独立调用的,没有明确的调用者。

严格模式的影响

ECMAScript 5 引入的严格模式对 this 的行为有显著影响:

"use strict";function strictThis() {console.log(this); // undefined,而非全局对象
}strictThis();// 对比非严格模式
function nonStrictThis() {console.log(this); // 全局对象 (window/global)
}nonStrictThis();

严格模式通过将默认的 this 值设为 undefined 而非全局对象,防止了许多意外的全局变量创建。这种差异经常成为错误的源头,因为开发者可能在不同的严格模式环境中工作,而忘记考虑这种行为差异。

初学者常犯的错误是假设 this 总是指向函数本身或其词法作用域,但实际上 JavaScript 中的 this 完全由调用点决定,与函数的定义位置无关。这是理解 this 的关键。

this 的绑定规则

JavaScript 确定 this 值的过程遵循明确的规则层次。了解这些规则对于预测和控制 this 的行为至关重要。

1. 默认绑定

默认绑定是最常见的函数调用类型:独立函数调用。当函数不满足其他绑定规则时,默认绑定适用。

function showThis() {console.log(this);
}// 独立函数调用
showThis(); // 非严格模式: window/global, 严格模式: undefined

在这个例子中,showThis 作为普通函数调用,没有其他上下文,因此应用默认绑定。在非严格模式下,默认绑定指向全局对象(浏览器中的 window 或 Node.js 中的 global);而在严格模式下,默认绑定的 this 值为 undefined

这种差异是许多难以追踪的 bug 的源头,特别是在混合使用严格和非严格模式的代码库中。例如,当一个函数从严格模式文件导入到非严格模式文件时,其 this 绑定会根据调用位置的严格模式状态而变化。

默认绑定还会在嵌套函数中引起问题:

const user = {name: "张三",greet() {function innerFunction() {console.log(`你好,${this.name}`); // this 指向全局对象,而非 user}innerFunction();}
};user.greet(); // "你好,undefined",因为全局对象没有 name 属性

这里的 innerFunction 尽管在 user.greet 方法内定义,但调用时没有任何上下文对象,所以应用默认绑定,this 指向全局对象而非 user。这是初学者常见的困惑点。

2. 隐式绑定

当函数作为对象的方法调用时,this 会隐式绑定到该对象上:

const user = {name: "张三",greet() {console.log(`你好,我是${this.name}`);}
};user.greet(); // 输出: "你好,我是张三"

在这个例子中,调用点是 user.greet(),因此 this 指向 user 对象。隐式绑定使得方法可以访问其所属对象的属性,这是面向对象编程的基础。

重要的是理解,隐式绑定仅在方法直接通过对象引用调用时有效。如果获取方法的引用并独立调用,隐式绑定会丢失:

隐式绑定的丢失
const user = {name: "张三",greet() {console.log(`你好,我是${this.name}`);}
};// 保存对方法的引用
const greetFunction = user.greet;// 独立调用
greetFunction(); // 输出: "你好,我是undefined"// 另一种丢失绑定的情况
function executeFunction(fn) {fn(); // this 指向全局对象
}executeFunction(user.greet); // 输出: "你好,我是undefined"

在这两个例子中,尽管 greetFunction 引用了 user.greet 方法,但调用发生在全局环境中,与 user 对象无关。这导致应用默认绑定规则,this 指向全局对象或 undefined(严格模式下)。

这种绑定丢失是许多与 this 相关 bug 的来源,特别是在将方法作为回调函数传递时:

const user = {name: "张三",greetAfterDelay() {setTimeout(function() {console.log(`你好,我是${this.name}`); // this 指向全局对象}, 1000);}
};user.greetAfterDelay(); // 1秒后输出: "你好,我是undefined"

在这个例子中,尽管 setTimeout 是在 user.greetAfterDelay 方法内调用的,但回调函数执行时没有维持与 user 的关联,所以 this 指向全局对象。我们将在后面讨论解决这种问题的方法。

3. 显式绑定

JavaScript 提供了 callapplybind 方法,允许我们明确指定函数执行时的 this 值:

function introduce(hobby, years) {console.log(`我是${this.name},喜欢${hobby},已经${years}年了`);
}const person = { name: "李四" };// call: 参数逐个传递
introduce.call(person, "编程", 5); // "我是李四,喜欢编程,已经5年了"// apply: 参数作为数组传递
introduce.apply(person, ["绘画", 3]); // "我是李四,喜欢绘画,已经3年了"// bind: 返回新函数,永久绑定this,不立即调用
const boundFn = introduce.bind(person, "摄影");
boundFn(2); // "我是李四,喜欢摄影,已经2年了"
boundFn(10); // "我是李四,喜欢摄影,已经10年了"

这三个方法的区别:

  • callapply 立即调用函数,只是传参方式不同
  • bind 返回一个新函数,原函数不会执行,新函数的 this 永久绑定到第一个参数

显式绑定特别有用的一个场景是解决隐式绑定丢失问题:

const user = {name: "张三",greetAfterDelay() {// 使用 bind 解决回调中的 this 问题setTimeout(function() {console.log(`你好,我是${this.name}`);}.bind(this), 1000); // 将外层的 this (指向 user) 绑定给回调函数}
};user.greetAfterDelay(); // 1秒后输出: "你好,我是张三"

显式绑定的一个重要特性是"硬绑定",即通过 bind 创建的函数不能再被改变其 this 指向,即使使用其他绑定规则:

function greeting() {console.log(`你好,我是${this.name}`);
}const person1 = { name: "张三" };
const person2 = { name: "李四" };const boundGreeting = greeting.bind(person1);
boundGreeting(); // "你好,我是张三"// 尝试用 call 改变 this,但无效
boundGreeting.call(person2); // 仍然输出: "你好,我是张三"

这种特性使得 bind 成为确保函数始终在正确上下文中执行的强大工具,特别是在复杂的异步代码中。

4. new 绑定

当使用 new 关键字调用函数时,会发生以下步骤:

  1. 创建一个新对象
  2. 该对象的原型链接到构造函数的 prototype
  3. 构造函数内的 this 绑定到这个新对象
  4. 如果构造函数没有返回对象,则返回新创建的对象
function Developer(name, language) {// this 指向新创建的对象this.name = name;this.language = language;this.introduce = function() {console.log(`我是${this.name},专注于${this.language}开发`);};// 隐式返回 this (新创建的对象)// 如果显式返回非对象值如基本类型,则仍返回 this// 如果显式返回对象,则返回该对象而非 this
}const dev = new Developer("王五", "JavaScript");
dev.introduce(); // "我是王五,专注于JavaScript开发"// 注意:没有使用 new 时结果完全不同
const notDev = Developer("赵六", "Python"); // this 指向全局对象
console.log(notDev); // undefined,因为构造函数没有显式返回值
console.log(window.name); // "赵六",属性被添加到全局对象

这个例子展示了 new 操作符如何改变 this 的指向。当使用 new 调用 Developer 时,this 指向新创建的对象。但当没有使用 new 时,Developer 作为普通函数调用,this 指向全局对象(非严格模式下),导致全局变量污染。

这种差异也是为什么在 ES6 引入类语法前,推荐构造函数名使用大写字母开头,以提醒开发者使用 new 调用。

// ES6 类语法会强制使用 new
class ModernDeveloper {constructor(name, language) {this.name = name;this.language = language;}introduce() {console.log(`我是${this.name},专注于${this.language}开发`);}
}// 不使用 new 会抛出错误
// TypeError: Class constructor ModernDeveloper cannot be invoked without 'new'
// const error = ModernDeveloper("小明", "Java");const modern = new ModernDeveloper("小明", "Java");
modern.introduce(); // "我是小明,专注于Java开发"

ES6 的类语法通过强制 new 调用避免了意外的 this 绑定错误,这是其优势之一。

5. 箭头函数中的 this

ES6 引入的箭头函数是处理 this 的一场革命。与传统函数不同,箭头函数不创建自己的 this 上下文,而是继承外围词法作用域的 this 值:

const team = {members: ["张三", "李四", "王五"],leader: "张三",printMembers() {// 这里的 this 指向 team 对象(隐式绑定)console.log(`团队领导: ${this.leader}`);// 普通函数会创建新的 thisthis.members.forEach(function(member) {// 这个回调是独立调用的,所以这里的 this 是全局对象或 undefinedconsole.log(member === this.leader ? `${member} (领导)` : member);});// 箭头函数继承外部的 thisthis.members.forEach((member) => {// 这里的 this 仍然是 team 对象,因为箭头函数没有自己的 thisconsole.log(member === this.leader ? `${member} (领导)` : member);});}
};team.printMembers();

箭头函数的这一特性使其成为回调函数的理想选择,尤其是在需要访问父级作用域 this 的情况下。它有效解决了许多传统函数中 this 丢失的问题。

需要强调的是,箭头函数的 this 在定义时确定,而非调用时。这意味着无法通过 callapplybind 改变箭头函数的 this 指向:

const obj1 = { name: "对象1" };
const obj2 = { name: "对象2" };// 箭头函数在 obj1 中定义
obj1.getThis = () => this; // this 指向定义时的上下文,即全局对象// 尝试通过 call 改变 this
const result = obj1.getThis.call(obj2);
console.log(result === window); // true,call 没有改变箭头函数的 this

箭头函数的限制和特点:

  • 不能用作构造函数(不能与 new 一起使用)
  • 没有 prototype 属性
  • 不能用作生成器函数(不能使用 yield
  • 不适合做方法,因为可能无法访问对象本身

作用域链解析

理解 this 的关键是区分它与作用域的区别。作用域决定变量访问权限,而 this 提供了对象访问上下文。JavaScript 使用词法作用域(静态作用域),即变量的作用域在定义时确定,而非运行时。

const global = "全局变量";function outer() {const outerVar = "外部变量";function inner() {const innerVar = "内部变量";console.log(innerVar); // 访问自身作用域console.log(outerVar); // 访问外部作用域console.log(global);   // 访问全局作用域}inner();
}outer();

在这个例子中,inner 函数可以访问三个层次的变量:

  1. 自身作用域中的 innerVar
  2. 外部函数 outer 作用域中的 outerVar
  3. 全局作用域中的 global

这种嵌套结构形成了"作用域链",JavaScript 引擎沿着这个链向上查找变量。

作用域链的详细工作机制

当 JavaScript 引擎执行代码时,它会为每个执行上下文创建一个内部属性 [[Environment]],指向外部词法环境,形成作用域链:

function grandfather() {const name = "爷爷";function parent() {const age = 50;function child() {const hobby = "编程";// 作用域链查找:先查找本地作用域,再查找 parent,然后是 grandfather,最后是全局console.log(`${name}今年${age}岁,喜欢${hobby}`);}child();}parent();
}grandfather(); // "爷爷今年50岁,喜欢编程"

child 函数访问 name 变量时,JavaScript 引擎:

  1. 首先在 child 的本地作用域查找,未找到
  2. 然后在 parent 的作用域查找,未找到
  3. 继续在 grandfather 的作用域查找,找到 name
  4. 停止查找并使用找到的值

这种链式查找机制是 JavaScript 作用域工作的核心。与之相比,this 是在函数调用时确定的,两者工作方式完全不同。

块级作用域与暂时性死区

ES6 引入的 letconst 声明创建了块级作用域,为作用域链增加了新的复杂性:

{// age 在 TDZ (Temporal Dead Zone) 中// console.log(age); // ReferenceErrorlet age = 30;{// 内部块可以访问外部块的变量console.log(age); // 30// 但如果声明同名变量,则形成新的屏蔽区域let age = 40;console.log(age); // 40}console.log(age); // 仍然是 30
}

块级作用域为防止变量泄漏和控制变量生命周期提供了更精细的控制。

作用域污染与解决方案

作用域污染是指变量意外地暴露在不应访问它的作用域中,全局作用域污染是最常见的问题:

// 不使用声明关键字,意外创建全局变量
function leakyFunction() {leakyVar = "我污染了全局作用域"; // 未使用 var/let/const
}leakyFunction();
console.log(window.leakyVar); // "我污染了全局作用域"

解决作用域污染的方法:

1. 使用 IIFE (立即调用函数表达式) 创建私有作用域
// 创建独立作用域,防止变量泄漏到全局
(function() {const privateVar = "私有变量";let privateCounter = 0;function privateFunction() {privateCounter++;console.log(privateVar, privateCounter);}privateFunction(); // 可以在IIFE内部访问
})();// 外部无法访问IIFE中的变量
// console.log(privateVar); // ReferenceError
// privateFunction(); // ReferenceError

IIFE 在模块系统普及前是创建私有作用域的主要手段,它创建的变量完全隔离于全局作用域。

2. 模块模式

模块模式结合了IIFE和闭包,只暴露必要的接口:

const counterModule = (function() {// 私有变量,外部无法直接访问let count = 0;// 私有函数function validateCount(value) {return typeof value === 'number' && value >= 0;}// 返回公共API,形成闭包return {increment() { return ++count; },decrement() { if (count > 0) return --count;return 0;},setValue(value) {if (validateCount(value)) {count = value;return true;}return false;},getValue() { return count; }};
})();counterModule.increment(); // 1
counterModule.increment(); // 2
console.log(counterModule.getValue()); // 2
counterModule.setValue(10);
console.log(counterModule.getValue()); // 10
// 无法直接访问内部的 count 变量和 validateCount 函数

模块模式通过闭包实现了数据封装和信息隐藏,这是JavaScript中实现面向对象编程的重要模式。

3. ES6 模块

现代 JavaScript 提供了官方的模块系统:

// counter.js
let count = 0;export function increment() {return ++count;
}export function decrement() {return count > 0 ? --count : 0;
}export function getValue() {return count;
}// main.js
import { increment, getValue } from './counter.js';increment();
increment();
console.log(getValue()); // 2

ES6 模块有几个重要特点:

  • 模块只执行一次,结果被缓存
  • 模块默认在严格模式下运行
  • 模块有自己的作用域,顶级变量不会污染全局
  • this 不指向全局对象,而是 undefined
  • 导入导出是静态的,有助于静态分析和优化

深入理解闭包与 this

闭包是函数及其词法环境的组合,使函数能够访问其定义作用域中的变量。闭包与 this 绑定结合时容易产生混淆:

function Counter() {this.count = 0;// 错误方式: setTimeout 中的回调是独立调用,// 所以其 this 指向全局对象而非 Counter 实例setTimeout(function() {this.count++; // 这里的 this 不是 Counter 实例console.log(this.count); // NaN,因为全局对象没有 count 属性}, 1000);
}new Counter(); // 创建计数器但计数失败

闭包保留了对外部变量的引用,但不保留 this 绑定,因为 this 是调用时确定的。有几种方法可以解决这个问题:

解决方案1: 在闭包外保存 this 引用

function Counter() {this.count = 0;// 将外部的 this 保存在变量中const self = this; // 或 const that = this;setTimeout(function() {// 使用 self 引用正确的对象self.count++;console.log(self.count); // 1// 闭包引用了自由变量 self,但 this 仍指向全局对象console.log(this === window); // true}, 1000);
}new Counter();

这种模式在 ES6 之前非常常见,通过创建一个闭包捕获外部 this 引用。

解决方案2: 使用箭头函数

function Counter() {this.count = 0;// 箭头函数没有自己的 this,继承外部的 thissetTimeout(() => {this.count++; // this 仍然指向 Counter 实例console.log(this.count); // 1}, 1000);
}new Counter();

箭头函数是最简洁的解决方案,因为它不创建自己的 this 绑定,而是继承外部作用域的 this

解决方案3: 使用 bind 方法

function Counter() {this.count = 0;// 使用 bind 显式绑定回调函数的 thissetTimeout(function() {this.count++;console.log(this.count); // 1}.bind(this), 1000);
}new Counter();

bind 方法创建一个新函数,永久绑定 this 值,是显式控制 this 的有力工具。

闭包与 this 的常见误区

初学者常见的错误是混淆闭包变量访问和 this 绑定:

const user = {name: "张三",friends: ["李四", "王五"],printFriends() {// this 指向 userconsole.log(`${this.name}的朋友:`);// 错误:forEach回调中的this已经变化this.friends.forEach(function(friend) {console.log(`${this.name}认识${friend}`); // this.name 是 undefined});// 正确:使用闭包捕获外部变量const name = this.name;this.friends.forEach(function(friend) {console.log(`${name}认识${friend}`); // 通过闭包访问name});// 或使用箭头函数this.friends.forEach((friend) => {console.log(`${this.name}认识${friend}`); // this 仍指向 user});}
};user.printFriends();

记住:闭包可以捕获变量,但不能捕获 this 绑定,因为 this 是调用时确定的。

this 绑定优先级

当多个规则同时适用时,JavaScript 遵循明确的优先级顺序:

  1. new 绑定:使用 new 调用构造函数
  2. 显式绑定:使用 call/apply/bind 明确指定 this
  3. 隐式绑定:函数作为对象方法调用
  4. 默认绑定:独立函数调用

以下示例说明这些规则的优先级:

const obj = { name: "对象" };function showThis() {console.log(this.name);
}// 默认绑定
showThis(); // undefined (或 "" 若全局有 name 属性)// 隐式绑定
obj.showThis = showThis;
obj.showThis(); // "对象"// 显式绑定胜过隐式绑定
obj.showThis.call({ name: "显式绑定" }); // "显式绑定"// new 绑定胜过显式绑定
function Person(name) {this.name = name;
}// 尽管使用 bind 绑定 this,但 new 绑定优先级更高
const boundPerson = Person.bind({ name: "绑定对象" });
const person = new boundPerson("new对象");
console.log(person.name); // "new对象"

理解优先级有助于预测复杂场景下 this 的值,尤其是在多种绑定规则同时出现时。

绑定例外:忽略 this

当使用 callapplybind 并传入 nullundefined 作为第一个参数时,这些值会被忽略,应用默认绑定规则:

function greet() {console.log(`Hello, ${this.name}`);
}// 传入 null 作为 this,会应用默认绑定
greet.call(null); // "Hello, undefined",this 指向全局对象// 安全的做法是使用空对象
const emptyObject = Object.create(null);
greet.call(emptyObject); // "Hello, undefined",但 this 指向空对象

这种模式在不关心 this 值但需要使用 apply 传递参数数组时很有用:

// 找出数组中最大值,不关心 this
const numbers = [5, 2, 8, 1, 4];
const max = Math.max.apply(null, numbers); // 8// ES6 中可以使用展开运算符代替
const max2 = Math.max(...numbers); // 8,更清晰且没有 this 混淆

实际应用与最佳实践

React 类组件中的 this

React 类组件中的 this 处理是经典案例,因为事件处理函数中的 this 默认不指向组件实例:

class Button extends React.Component {constructor(props) {super(props);this.state = { clicked: false };// 方法1:在构造函数中绑定 thisthis.handleClick1 = this.handleClick1.bind(this);}// 普通方法定义,需要绑定 thishandleClick1() {this.setState({ clicked: true });console.log('按钮被点击,状态已更新');}// 方法2:使用箭头函数属性(类字段语法)handleClick2 = () => {this.setState({ clicked: true });console.log('使用箭头函数,this 自动绑定到实例');}render() {return (<div>{/* 方法1: 使用构造函数中绑定的方法 */}<button onClick={this.handleClick1}>方法1</button>{/* 方法2: 使用箭头函数属性 */}<button onClick={this.handleClick2}>方法2</button>{/* 方法3: 在渲染中使用内联箭头函数 */}<button onClick={() => {this.setState({ clicked: true });console.log('内联箭头函数,每次渲染都创建新函数实例');}}>方法3</button>{/* 这种写法会导致问题,因为 this 丢失 */}{/* <button onClick={this.handleClick1()}>错误写法</button> */}</div>);}
}

这三种方法各有优缺点:

  1. 构造函数绑定:代码清晰,但需要额外代码
  2. 箭头函数属性(类字段):简洁,但使用了实验性语法
  3. 内联箭头函数:简便,但每次渲染都创建新函数实例,可能影响性能

提升代码可维护性的建议

  1. 使用箭头函数处理回调:当需要访问外部 this 时,箭头函数是最佳选择:
// 适合箭头函数的场景
const api = {data: [],fetchData() {fetch('/api/data').then(response => response.json()).then(data => {// 箭头函数保持 this 指向 api 对象this.data = data;this.processData();}).catch(error => {console.error('获取数据失败:', error);});},processData() {console.log('处理数据:', this.data.length);}
};
  1. 为类方法使用显式绑定:对于需要在多处使用的类方法,显式绑定可提高代码清晰度:
class UserService {constructor() {this.users = [];// 一次绑定,多处使用this.getUser = this.getUser.bind(this);this.addUser = this.addUser.bind(this);}getUser(id) {return this.users.find(user => user.id === id);}addUser(user) {this.users.push(user);return user;}
}const service = new UserService();// 可以将方法传递给其他函数而不用担心 this
document.getElementById('btn').addEventListener('click', service.getUser);
  1. 避免深度嵌套函数:嵌套函数增加了 this 指向混淆的可能性:
// 不良实践:多层嵌套导致 this 混乱
function complexFunction() {const data = { value: 42, name: "重要数据" };$('#button').click(function() {// 这里的 this 指向被点击的元素$(this).addClass('active');function processData() {// 这里的 this 指向全局对象或 undefined,而非期望的 dataconsole.log(`处理${this.name}中的值: ${this.value}`);}processData(); // this 绑定丢失});
}// 良好实践:扁平化结构,明确引用
function improvedFunction() {const data = { value: 42, name: "重要数据" };// 将处理逻辑提取为独立函数function processData(targetData) {console.log(`处理${targetData.name}中的值: ${targetData.value}`);}$('#button').click(function() {$(this).addClass('active');// 明确传递数据,避免依赖 thisprocessData(data);});
}
  1. 使用严格模式捕获隐式全局变量:
"use strict";function strictDemo() {value = 42; // 不使用 var/let/const 会抛出 ReferenceErrorreturn this; // 严格模式下为 undefined,而非全局对象
}function nonStrictDemo() {value = 42; // 创建全局变量,污染全局作用域return this; // 非严格模式下为全局对象
}
  1. 优先使用对象解构而非 this 引用
// 不良实践:大量重复的 this 引用
function processUser(user) {console.log(`姓名: ${this.name}`);console.log(`年龄: ${this.age}`);console.log(`职业: ${this.job}`);if (this.age > 18) {console.log(`${this.name}是成年人`);}
}// 良好实践:使用解构获取所需属性
function processUser(user) {const { name, age, job } = user;console.log(`姓名: ${name}`);console.log(`年龄: ${age}`);console.log(`职业: ${job}`);if (age > 18) {console.log(`${name}是成年人`);}
}
  1. 保持方法的纯粹性:避免在对象方法中修改不相关的状态:
// 不良实践:方法有副作用,修改其他对象
const app = {user: { name: "张三", loggedIn: false },settings: { theme: "light", notifications: true },login() {this.user.loggedIn = true;this.settings.lastLogin = new Date(); // 修改不相关对象localStorage.setItem('user', JSON.stringify(this.user)); // 外部副作用}
};// 良好实践:职责明确,减少副作用
const app = {user: { name: "张三", loggedIn: false },settings: { theme: "light", notifications: true },login() {this.user.loggedIn = true;return this.user; // 返回修改后的对象,而非产生副作用},updateLoginTime() {this.settings.lastLogin = new Date();},saveUserToStorage(user) {localStorage.setItem('user', JSON.stringify(user));}
};// 调用方决定如何组合这些功能
const loggedInUser = app.login();
app.updateLoginTime();
app.saveUserToStorage(loggedInUser);

现代 JavaScript 中的替代方案

随着 JavaScript 的演进,处理 this 有更多现代选择。

类字段语法

类字段语法(Class Fields)允许在类中直接定义实例属性,包括方法:

class ModernComponent {// 实例属性state = { clicked: false, count: 0 };// 类字段作为箭头函数,自动绑定thishandleClick = () => {this.setState({ clicked: true,count: this.state.count + 1});console.log('处理点击,当前计数:', this.state.count);};// 私有字段(以#开头)#privateCounter = 0;// 私有方法#incrementPrivate() {return ++this.#privateCounter;}getPrivateCount = () => {this.#incrementPrivate();return this.#privateCounter;};render() {// 无需绑定,直接使用实例方法return <button onClick={this.handleClick}>点击 ({this.state.count})</button>;}
}

类字段语法极大简化了 React 类组件中的 this 绑定问题,使代码更加简洁。私有字段(私有类成员)进一步增强了封装,避免状态意外暴露或修改。

面向对象编程的演进

现代 JavaScript 提供了更丰富的面向对象特性:

// 经典原型继承
function Animal(name) {this.name = name;
}Animal.prototype.speak = function() {console.log(`${this.name}发出声音`);
};function Dog(name, breed) {Animal.call(this, name); // 调用父构造函数this.breed = breed;
}// 设置原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;Dog.prototype.speak = function() {console.log(`${this.name}汪汪叫`);
};// ES6 类语法
class ModernAnimal {constructor(name) {this.name = name;}speak() {console.log(`${this.name}发出声音`);}// 静态方法static isAnimal(obj) {return obj instanceof ModernAnimal;}
}class ModernDog extends ModernAnimal {constructor(name, breed) {super(name); // 调用父类构造函数this.breed = breed;}speak() {console.log(`${this.name}汪汪叫`);}// 获取器方法get description() {return `${this.breed}犬:${this.name}`;}
}const modernDog = new ModernDog('旺财', '金毛');
modernDog.speak(); // "旺财汪汪叫"
console.log(modernDog.description); // "金毛犬:旺财"
console.log(ModernAnimal.isAnimal(modernDog)); // true

ES6 类语法不仅使代码更易读,还提供了静态方法、获取器和设置器、以及更清晰的继承模型,减少了与 this 相关的常见错误。

函数式组件与 Hooks

React Hooks 的引入彻底改变了状态管理方式,完全避开了 this 问题:

import React, { useState, useEffect, useCallback } from 'react';function FunctionalButton() {// 状态管理无需 thisconst [clicked, setClicked] = useState(false);const [count, setCount] = useState(0);// 副作用处理useEffect(() => {if (clicked) {document.title = `按钮点击了${count}次`;}// 清理函数return () => {document.title = '应用';};}, [clicked, count]); // 依赖数组// 记忆化回调函数const handleClick = useCallback(() => {setClicked(true);setCount(prevCount => prevCount + 1);console.log('按钮被点击');}, []); // 空依赖数组表示回调不依赖任何状态return (<div><button onClick={handleClick}>{clicked ? `已点击${count}次` : '点击我'}</button><p>状态: {clicked ? '激活' : '未激活'}</p></div>);
}

使用 Hooks 的函数式组件有几个显著优势:

  • 无需理解 this 绑定机制
  • 状态和生命周期更加明确
  • 组件逻辑更容易拆分和重用
  • 避免了类组件中的常见陷阱

模块化代替全局对象

现代 JavaScript 模块系统提供了更好的组织代码方式,减少了对全局对象的依赖:

// utils.js
export const formatDate = (date) => {return new Intl.DateTimeFormat('zh-CN').format(date);
};export const calculateTax = (amount, rate = 0.17) => {return amount * rate;
};// 命名空间对象
export const validators = {isEmail(email) {return /\S+@\S+\.\S+/.test(email);},isPhone(phone) {return /^\d{11}$/.test(phone);}
};// main.js
import { formatDate, calculateTax, validators } from './utils.js';console.log(formatDate(new Date())); // "2023/5/18"
console.log(calculateTax(100)); // 17
console.log(validators.isEmail('user@example.com')); // true

模块系统提供了自然的命名空间,避免了全局污染,同时保持了代码的组织性和可维护性。

函数编程方法避免 this 困惑

函数式编程提供了不依赖 this 的替代方法:

// 面向对象风格:依赖 this
const calculator = {value: 0,add(x) {this.value += x;return this;},subtract(x) {this.value -= x;return this;},multiply(x) {this.value *= x;return this;},getValue() {return this.value;}
};calculator.add(5).multiply(2).subtract(3);
console.log(calculator.getValue()); // 7// 函数式风格:不依赖 this
const add = (x, y) => x + y;
const subtract = (x, y) => x - y;
const multiply = (x, y) => x * y;// 使用组合函数
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);const calculate = pipe(x => add(x, 5),x => multiply(x, 2),x => subtract(x, 3)
);console.log(calculate(0)); // 7

函数式方法:

  • 通过消除 this 简化推理
  • 提高函数的可组合性和可测试性
  • 减少副作用,使代码更加可预测
  • 拥抱不可变数据,避免状态管理问题

总结:掌握 this 与作用域的核心要点

JavaScript 的 this 关键字是一个强大而复杂的机制,其值完全取决于函数调用的方式,而非函数定义的位置。理解不同的绑定规则对于编写可维护的代码至关重要:

  1. 默认绑定:独立函数调用时,this 指向全局对象(非严格模式)或 undefined(严格模式)
  2. 隐式绑定:作为对象方法调用时,this 指向调用该方法的对象
  3. 显式绑定:使用 call/apply/bind 时,this 指向指定的对象
  4. new 绑定:使用 new 调用构造函数时,this 指向新创建的实例
  5. 箭头函数:没有自己的 this,继承定义时外围词法作用域的 this

这些规则的优先级从高到低依次是:new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定。

而作用域链决定了变量访问的权限范围,是由代码的词法结构(静态结构)决定的,与 this 的动态绑定机制不同。了解作用域链的工作方式有助于避免变量污染和命名冲突。

实践总结

  1. 使用严格模式捕获潜在错误,避免隐式全局变量
  2. 对回调函数使用箭头函数保持 this 指向
  3. 使用 ES6 类语法获得更清晰的面向对象模型
  4. 考虑 React Hooks 和函数式组件避免 this 相关问题
  5. 使用模块系统代替全局对象和命名空间
  6. 减少方法链和深度嵌套,使 this 指向更加清晰
  7. 采用函数式编程思想,减少对 this 的依赖
  8. 使用解构和直接引用代替多次 this 引用

随着 JavaScript 的发展,我们有更多工具和模式来简化 this 的处理。但即使使用现代特性,深入理解 this 的底层机制仍然是成为高级 JavaScript 开发者的必备素质。正确掌握 this 绑定和作用域规则,能够帮助我们写出更可靠、可维护的代码,并更容易理解和调试他人的代码。

深入学习资源

  • MDN Web Docs: this - MDN 的官方文档,提供了深入解释和示例

  • You Don’t Know JS: this & Object Prototypes - Kyle Simpson 的深入解析,被视为理解 this 的权威资源

  • JavaScript.info: 对象方法与 “this” - 现代 JavaScript 教程,提供清晰的说明和交互示例

  • React 官方文档:处理事件 - React 中正确处理 this 的官方指南


如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

相关文章:

深入理解 this 指向与作用域解析

引言 JavaScript 中的 this 关键字的灵活性既是强大特性也是常见困惑源。理解 this 的行为对于编写可维护的代码至关重要&#xff0c;但其动态特性也会让我们感到困惑。 与大多数编程语言不同&#xff0c;JavaScript 的 this 不指向函数本身&#xff0c;也不指向函数的词法作…...

c++20引入的三路比较操作符<=>

目录 一、简介 二、三向比较的返回类型 2.1 std::strong_ordering 2.2 std::weak_ordering 2.3 std::partial_ordering 三、对基础类型的支持 四、自动生成的比较运算符函数 4.1 std::rel_ops的作用 4.2 使用<> 五、兼容他旧代码 一、简介 c20引入了三路比较操…...

Spring框架(三)

目录 一、JDBC模板技术概述 1.1 什么是JDBC模板 二、JdbcTemplate使用实战 2.1 基础使用&#xff08;手动创建对象&#xff09; 2.2 使用Spring管理模板类 2.3 使用开源连接池&#xff08;Druid&#xff09; 三、模拟转账开发 3.1 基础实现 3.1.1 Service层 3.1.2 Da…...

CS016-4-unity ecs

【37】将系统转换为任务 Converting System to Job 【Unity6】使用DOTS制作RTS游戏|17小时完整版|CodeMonkey|【37】将系统转换为任务 Converting System to Job_哔哩哔哩_bilibili a. 将普通的方法&#xff0c;转化成job。第一个是写一个partial struct xxx&#xff1b;第二…...

CMU-15445(4)——PROJECT#1-BufferPoolManager-Task#2

PROJECT#1-BufferPoolManager Task #2 - Disk Scheduler 在前一节我实现了 TASK1 并通过了测试&#xff0c;在本节中&#xff0c;我将逐步实现 TASK2。 如上图&#xff0c;Page Table&#xff08;页表&#xff09;通过哈希表实现&#xff0c;用于跟踪当前存在于内存中的页&am…...

[原创](计算机数学)(The Probability Lifesaver)(P10): 生日概率问题.

[作者] 常用网名: 猪头三 出生日期: 1981.XX.XX 企鹅交流: 643439947 个人网站: 80x86汇编小站 编程生涯: 2001年~至今[共24年] 职业生涯: 22年 开发语言: C/C++、80x86ASM、Object Pascal、Objective-C、C#、R、Python、PHP、Perl、 开发工具: Visual Studio、Delphi、XCode、…...

计算机组成原理——数据的表示

2.1数据的表示 整理自Beokayy_ 1.进制转换 十六进制与二进制的转换 一位十六进制等于四位二进制 四位二进制等于一位十六进制 0x173A4C0001 0111 0011 1010 0100 1100 十六进制与十进制的转换 十六转十&#xff1a;每一位数字乘以相应的16的幂再相加 十转十六&#xff1a…...

源码:处理文件格式和字符集的相关代码(3-3)

总入口&#xff1a;源码&#xff1a;处理文件格式和字符集的相关代码&#xff08;3-1&#xff09;-CSDN博客 目录 六、预览&#xff08;正确显示文本文件&#xff09; 6.1 总体逻辑 6.2 二进制显示 6.3 文本显示 六、预览&#xff08;正确显示文本文件&#xff09; 6.1 总…...

Spring MVC 对 JavaWeb 的优化:从核心组件到注解

Spring MVC 功能组件与注解对 JavaWeb 的优化 文章介绍&#xff1a; SpringMVC对比JavaWeb优势&#xff0c;Spring MVC 通过引入功能组件和注解&#xff0c;从多个维度对传统 JavaWeb 开发进行了优化&#xff0c;显著提升了开发效率和代码可维护性。以下是关键优化点的详细对…...

Mysql数据库详解

在cmd中选择数据库操作用 USE test_db; 相关概念 Sql是操作关系型数据库的编程语言 关系型数据库&#xff1a;建立在关系模型基础上&#xff0c;由多张相互连接的二维表组成的数据库 语法 sql语法分类 DDL-数据库操作 创建&#xff1a;CREATE DATABASE db_name;创建完整…...

R1 快开门式压力容器操作证备考练习题及答案

R1 快开门式压力容器操作证备考练习题及答案 判断题 1、快开门式压力容器的快开门应设计成有延时功能的联锁装置。&#xff08;√&#xff09; 解析&#xff1a;延时功能的联锁装置可以防止在容器内有压力时过早开启快开门&#xff0c;避免发生危险&#xff0c;保障设备和人…...

python打卡训练营Day27

作业&#xff1a; 编写一个装饰器 logger&#xff0c;在函数执行前后打印日志信息&#xff08;如函数名、参数、返回值&#xff09; logger def multiply(a, b):return a * bmultiply(2, 3) # 输出: # 开始执行函数 multiply&#xff0c;参数: (2, 3), {} # 函数 multiply …...

【RabbitMQ】消息丢失问题排查与解决

RabbitMQ 消息丢失是一个常见的问题&#xff0c;可能发生在消息的生产、传输、消费或 Broker 端等多个环节。消息丢失的常见原因及对应的解决方案&#xff1a; 一、消息丢失的常见原因 1. 生产端&#xff08;Producer&#xff09;原因 (1) 消息未持久化 原因&#xff1a;生产…...

idea 保证旧版本配置的同时,如何从低版本升到高版本

文章目录 前言idea 保证旧版本配置的同时,如何从低版本升到高版本1. 备份项目2. 下载最新的idea3. 安装安装包4. 导入idea2019旧配置5. 验证前言 如果您觉得有用的话,记得给博主点个赞,评论,收藏一键三连啊,写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差,…...

校园社区小程序源码解析

基于ThinkPHP、FastAdmin和UniApp开发的校园社区小程序源码&#xff0c;旨在为校园内的学生和教职员工提供一个便捷的在线交流和服务平台。 该小程序前端采用UniApp进行开发&#xff0c;具有良好的跨平台兼容性&#xff0c;可以轻松发布到iOS和Android平台。同时&#xff0c;后…...

Windows11安装rockerMq5.0+以及springboot集成rockerMq

安装jdk17&#xff0c;rockermq5.0需要jdk11&#xff0c;我这里使用jdk17 配置系统环境变量 ROCKETMQ_HOME D:\work\mmq\rocketmq-all-5.2.0-bin-release 编写启动脚本 D: cd D:\work\mmq\rocketmq-all-5.2.0-bin-release\bin start mqnamesrv.cmd start mqbroker.cmd -n 127.0…...

ros2中自定义的package查不到?

在ros2中自定义的package功能包&#xff0c;使用命令&#xff1a;ros2 pkg list无法查找到自己的功能包&#xff1f; 首先&#xff0c;利用ros2 pkg create命令创建好功能包之后要利用colcon build命令进行编译&#xff0c;编译成功之后&#xff0c;在当前路径需要运行命令&am…...

【Python CGI编程】

Python CGI&#xff08;通用网关接口&#xff09;编程是早期Web开发中实现动态网页的技术方案。以下是系统化指南&#xff0c;包含核心概念、实现步骤及安全实践&#xff1a; 一、CGI 基础概念 1. 工作原理 浏览器请求 → Web服务器&#xff08;如Apache&#xff09; → 执行…...

idea运行

各种小kips Linuxidea上传 Linux 部署流程 1、先在idea打好jar包&#xff0c;clean之后install 2、在Linux目录下&#xff0c;找到对应项目目录&#xff0c;把原来的jar包放在bak文件夹里面 3、杀死上一次jar包的pid ps -ef|grep cliaidata.jar kill pid 4、再进行上传新的jar…...

2025年渗透测试面试题总结-安恒[社招]售前工程师(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 安恒[社招]售前工程师 1. 自我介绍 2. 离职原因 3. 掌握的法律法规 4. 等级保护实施框架 5. 行业方…...

[ linux-系统 ] 命令行参数 | 环境变量

命令行参数 命令行参数是指用户在启动程序时通过命令行传递给程序的参数。这些参数可以用于控制程序的行为、传递输入数据或配置选项。 在 C/C 中&#xff0c;命令行参数通过 main 函数的参数传递 命令行参数列表 argc:参数的个数 argv[]&#xff1a;参数的清单 为什么要…...

Spring三级缓存的作用与原理详解

在Spring框架中&#xff0c;Bean的创建过程涉及到了三级缓存机制。这个机制主要是为了提高单例模式下bean实例化和依赖注入的效率。本文将深入探讨Spring中的三级缓存&#xff0c;以及其在bean生命周期中的重要作用。 首先&#xff0c;让我们理解什么是三级缓存。Spring中的三…...

Linux信号的保存

Linux系统中信号的保存涉及内核为每个进程维护的数据结构&#xff0c;确保信号在产生后、处理前被正确记录和管理。以下是详细的解释&#xff1a; 1. 信号的基本概念 信号&#xff08;Signal&#xff09;&#xff1a;用于通知进程发生了特定事件的异步通知机制&#xff0c;如…...

leetcode0215. 数组中的第K个最大元素-medium

1 题目&#xff1a;数组中的第K个最大元素 官方标定难度&#xff1a;中 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 k 个最大的元素。 请注意&#xff0c;你需要找的是数组排序后的第 k 个最大的元素&#xff0c;而不是第 k 个不同的元素。 你必须设计并实现时…...

C++(17):引用传参

目录 一、核心概念 二、代码示例&#xff1a;对比指针和引用 1. 指针传参的问题 2. 引用传参的改进 三、引用传参的优势 四、总结 一、核心概念 别名机制&#xff1a;引用是变量的别名&#xff0c;操作引用等同于操作原变量。 避免拷贝&#xff1a;直接操作原始变量&…...

handsome主题美化及优化:10.1.0最新版 - 1

文章目录 前言右侧导航栏主题标题居中页面两侧框架留白间距handsome 原生入站提示评论一键赞、踩、打卡时光机头像圆形logo 扫光赞赏按钮跳动鼠标点击特效复制版权提示彩色标签云及右栏数字自定义右键响应时间和访客总数全站字数统计版权提示时间流逝添加心知天气总结 前言 ha…...

基于React的高德地图api教程006:两点之间距离测量

文章目录 6、距离测量6.1 两点之间距离测量6.1.1 两点距离测量按钮6.1.2 点击地图添加点6.1.3 测量两点之间距离并画线6.2 测量过程显示两点之间预览线6.3 绘制完毕6.4 显示清除按钮6.5 代码下载6.06、距离测量 6.1 两点之间距离测量 6.1.1 两点距离测量按钮 实现代码: re…...

Java回溯算法解决非递减子序列问题(LeetCode 491)的深度解析

文章目录 问题描述错误代码分析原代码实现错误原因 修正方案与代码实现修正后的代码关键修正点 核心问题&#xff1a;为什么 used 不需要回溯&#xff1f;作用域与生命周期示例分析 总结算法设计要点复杂度分析 问题描述 给定一个整数数组 nums&#xff0c;找出并返回所有不同…...

基于PXIE 总线架构的Kintex UltraScale 系列FPGA 高性能数据预处理板卡

基于PXIE 总线架构的Kintex UltraScale 系列FPGA 高性能数据预处理板卡 一款基于3U PXIE 总线架构的高性能数据预处理FMC 载板&#xff0c;板卡具有1 个FMC&#xff08;HPC&#xff09;接口&#xff0c;1 个X8 GTH 背板互联接口&#xff0c;可以实现1 路PCIe x8。板卡采用Xili…...

[前端] wang 富文本 vue3

官方链接 https://www.npmjs.com/package/wangeditor-next/editor-for-vue <template><div style"border: 1px solid #ccc"><Toolbar style"border-bottom: 1px solid #ccc" :editor"editorRef" :defaultConfig"toolbarCo…...

【Python 操作 MySQL 数据库】

在 Python 中操作 MySQL 数据库主要通过 pymysql 或 mysql-connector-python 库实现。以下是完整的技术指南&#xff0c;包含连接管理、CRUD 操作和最佳实践&#xff1a; 一、环境准备 1. 安装驱动库 pip install pymysql # 推荐&#xff08;纯Python实现&#xff0…...

编译opencv4.11gstreamer 参考

0xC0000139: Entry Point Not Found gstreamer-1.0 如需要编译gstreamer模块需要提前安装好2个文件Index of /data/pkg/windows 下载带msvc的表示用Visual Studio编译 gif功能顺便勾上 最后 测试可能遇到的问题 把F:\gstreamer\1.0\x86_64\bin目录下的所有dll复制到exe所在位置…...

OpenCV边界填充(Border Padding)详解:原理、方法与代码实现

一、什么是边界填充&#xff1f; 边界填充&#xff08;Border Padding&#xff09;是图像处理中一项基础而重要的技术&#xff0c;它通过在图像边缘周围添加像素来解决卷积等操作导致的边界问题。当我们对图像应用滤波器或进行卷积操作时&#xff0c;图像边缘的像素无法像中心…...

Deeper and Wider Siamese Networks for Real-Time Visual Tracking

现象&#xff1a; the backbone networks used in Siamese trackers are relatively shallow, such as AlexNet , which does not fully take advantage of the capability of modern deep neural networks. direct replacement of backbones with existing powerful archite…...

保安员考试报名时,体检项目包含哪些?

保安员考试报名时的体检项目主要包括以下几类&#xff1a; 实验室检查&#xff1a;血常规、尿常规、大便常规是必检项目&#xff0c;主要用于检查血液、尿液和消化系统是否存在问题。部分地区可能还会检查肝功能、肾功能等&#xff0c;通过检测相关指标来评估肝脏和肾脏的功能状…...

基于SpringBoot的房屋租赁管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…...

MCU开发学习记录16* - 看门狗学习与实践(HAL库) - IWDG与WWDG -STM32CubeMX

名词解释&#xff1a; IWDG&#xff1a;Independent watchdog WWDG&#xff1a;Window watchdog LSE&#xff1a;​Low-Speed External Clock​ 统一文章结构&#xff08;数字后加*&#xff09;&#xff1a; 第一部分&#xff1a; 阐述外设工作原理&#xff1b;第二部分&#…...

如何解决LCMS 液质联用液相进样器定量环漏液问题

以下是解决安捷伦1260液相色谱仪为例的进样器定量环漏液问题的一些方法&#xff1a;视频操作 检查相关部件 检查定量环本身&#xff1a;观察定量环是否有破损、裂纹或变形等情况。如果发现定量环损坏&#xff0c;需及时更换。检查密封垫&#xff1a;查看进样阀的转子密封垫、计…...

【Linux】ssh命令 – 安全的远程连接服务

原创&#xff1a;厦门微思网络 SSH命令的概念 ssh命令的功能是安全地远程连接服务器主机系统&#xff0c;作为OpenSSH套件中的客户端连接工具&#xff0c;ssh命令可以让我们轻松地基于SSH加密协议进行远程主机访问&#xff0c;从而实现对远程服务器的管理工‍作。 语法 ssh 参…...

硬件中的OID是什么?SNMP如何通过OID获取信息?——用“图书馆”比喻彻底讲清底层原理-优雅草卓伊凡|小无

硬件中的OID是什么&#xff1f;SNMP如何通过OID获取信息&#xff1f;——用“图书馆”比喻彻底讲清底层原理-优雅草卓伊凡|小无 1. 终极比喻&#xff1a;OID是设备的“图书编码系统” 想象你走进一座巨型图书馆&#xff08;这个图书馆就是一台网络设备&#xff0c;比如路由器…...

java后端学习

1.Java基础 Java基础学习-CSDN博客 2.spring->springboot spring学习-&#xff1e;sprintboot-CSDN博客 3.maven Maven-CSDN博客 4mybatis -&#xff1e;mybatisplus mybatis -&#xff1e;mybatisplus-CSDN博客 5.git操作学习 git版本控制学习-CSDN博客 6.mysql …...

Python打卡 DAY 27

知识点回顾&#xff1a; 1. 装饰器的思想&#xff1a;进一步复用 2. 函数的装饰器写法 3. 注意内部函数的返回值 作业&#xff1a; 编写一个装饰器 logger&#xff0c;在函数执行前后打印日志信息&#xff08;如函数名、参数、返回值&#xff09; def logger(func):def wrap…...

2025蓝桥杯JAVA编程题练习Day8

1. 路径 题目描述 小蓝学习了最短路径之后特别高兴&#xff0c;他定义了一个特别的图&#xff0c;希望找到图 中的最短路径。 小蓝的图由 2021 个结点组成&#xff0c;依次编号 1 至 2021。 对于两个不同的结点 a, b&#xff0c;如果 a 和 b 的差的绝对值大于 21&#xff0…...

7 个正则化算法完整总结

哈喽&#xff01;我是我不是小upper&#xff5e;之前和大家聊过各类算法的优缺点&#xff0c;还有回归算法的总结&#xff0c;今天咱们来深入聊聊正则化算法&#xff01;这可是解决机器学习里 “过拟合” 难题的关键技术 —— 想象一下&#xff0c;模型就像个死记硬背的学生&am…...

lesson03-简单回归案例(理论+代码)

一、梯度下降 二、 线性方程怎么样&#xff1f; 三、有噪音吗&#xff1f; 四、让我们看一个列子 五、如何优化 启发式搜索 学习进度 六、线性回归、逻辑回归、分类 总结、 简单线性回归是一种统计方法&#xff0c;用于确定两个变量之间的关系。具体来说&#xff0c;它试图…...

Linux系统篇——文件描述符FD

&#x1f9e0; Linux 文件描述符&#xff08;File Descriptor&#xff09;详解与学习指南 一、什么是文件描述符&#xff08;fd&#xff09; 在 Linux 中&#xff0c;一切皆文件&#xff08;everything is a file&#xff09;&#xff0c;包括普通文件、目录、套接字&#xff…...

C++ Kafka客户端(cppkafka)安装与问题解决指南

一、cppkafka简介 cppkafka是一个现代C的Apache Kafka客户端库&#xff0c;它是对librdkafka的高级封装&#xff0c;旨在简化使用librdkafka的过程&#xff0c;同时保持最小的性能开销。 #mermaid-svg-qDUFSYLBf8cKkvdw {font-family:"trebuchet ms",verdana,arial,…...

MySQL的缓存策略

一、MySQL缓存方案用来解决什么 缓存用户定义的热点数据&#xff0c;用户直接从缓存获取热点数据&#xff0c;降低数据库的读写压力场景分析&#xff1a; 内存访问速度是磁盘访问速度 10 万倍&#xff08;数量级&#xff09;读的需求远远大于写的需求mysql 自身缓冲层跟业务无…...

ubuntu22.04卸载vscode

方法 1&#xff1a;通过 Snap 卸载 VSCode 如果你是通过 Snap 安装的 VSCode&#xff08;Ubuntu 22.04 默认推荐方式&#xff09;&#xff0c;按照以下步骤卸载&#xff1a; 检查是否通过 Snap 安装&#xff1a; bash snap list | grep code如果输出显示 code&#xff0c;说明…...

主流数据库排查与优化速查手册

主流数据库排查与优化速查手册&#xff08;优化版&#xff09; 一、连接失败 1.1 统一排查流程 #mermaid-svg-IIyarbd8VatJFN14 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-IIyarbd8VatJFN14 .error-icon{fill:…...