
写 JS 代码的时候你有没有被this坑过明明在setTimeout回调里只是想访问一下组件实例的数据结果this.name直接给你报undefined明明在事件监听器里想拿到点击的元素结果this指向了window。这种问题在我刚开始写前端时几乎天天遇到排查半天最后发现不就是回调函数里的this丢了吗后来 ES6 普及箭头函数大量出现在代码里不少同学说“箭头函数好用this不会丢了”。这句话对了一半箭头函数确实能解决一部分this丢失问题但它背后的机制并不是“把 this 绑定到了当前对象”而是箭头函数根本没有自己的 this。理解不了这层区别你会在一堆新代码里写出更隐蔽的 bug。这篇文章我用「问题 → 原理 → 写法 → 场景 → 坑位 → 实践」这条线彻底讲清楚 JS 箭头函数和this的关系。看完之后你再遇到回调函数里的this问题应该能直接说出原因而不只是换个写法碰运气。1. 这篇文章真正要解决的问题在开始讲原理之前先看一个几乎所有前端都写过的代码const user { name: 张三, greet: function() { setTimeout(function() { console.log(你好我是 ${this.name}); }, 1000); } }; user.greet(); // 输出你好我是 undefined为什么这里this.name拿不到user.name因为setTimeout里的回调函数是普通函数它被调用时this指向的是全局对象浏览器里是windowNode.js 里是global而不是外层的user对象。传统解决方案是先把this存下来const user { name: 张三, greet: function() { const that this; setTimeout(function() { console.log(你好我是 ${that.name}); }, 1000); } };或者用bindconst user { name: 张三, greet: function() { setTimeout(function() { console.log(你好我是 ${this.name}); }.bind(this), 1000); } };这两种写法都能解决但都显得笨重。箭头函数出现后直接这么写const user { name: 张三, greet: function() { setTimeout(() { console.log(你好我是 ${this.name}); }, 1000); } };完美输出你好我是 张三。所以这篇文章要解决的核心问题就是普通函数的this是怎么确定的箭头函数的this为什么“不丢”箭头函数到底能不能完全替代普通函数实际项目里应该怎么选下面逐一拆开讲。2. 普通函数this 在调用时确定要理解箭头函数先得理解普通函数的this机制。JavaScript 里有一个核心规则普通函数的this是在函数被调用时确定的而不是在定义时确定的。这个“调用时确定”听起来抽象实际上可以归纳成四种绑定规则。2.1 默认绑定独立调用函数没有任何修饰this指向全局对象。严格模式下指向undefined。function show() { console.log(this); } show(); // 浏览器环境下输出 windowfunction showStrict() { use strict; console.log(this); } showStrict(); // undefined2.2 隐式绑定函数作为某个对象的方法被调用this指向这个对象。const obj { name: 张三, show: function() { console.log(this.name); } }; obj.show(); // 张三但隐式绑定有一个非常经典的坑把方法取出来单独调用this 就丢了。const obj { name: 张三, show: function() { console.log(this.name); } }; const fn obj.show; fn(); // undefinedthis 指向全局对象这正好解释了第一节里user.greet()执行时为什么内部setTimeout回调拿不到this.name那个回调函数是独立调用的不是作为user的方法调用的。2.3 显式绑定通过call、apply、bind手动指定this。function show() { console.log(this.name); } const obj { name: 张三 }; show.call(obj); // 张三 show.apply(obj); // 张三 const boundShow show.bind(obj); boundShow(); // 张三call和apply的区别只在传参方式call逐个传apply传数组。bind比较特殊它返回一个新函数这个新函数的this永久绑定到传入的对象。2.4 new 绑定使用new调用函数时this指向新创建的对象。function Person(name) { this.name name; } const person new Person(张三); console.log(person.name); // 张三2.5 四种规则对比绑定规则调用方式this 指向默认绑定独立调用全局对象 / undefined严格模式隐式绑定作为对象方法调用调用该方法的对象显式绑定call / apply / bind手动传入的对象new 绑定new 调用新创建的对象普通函数还有一个特点调用方式一变this 就可能变。这既是灵活也是混乱的根源。3. 箭头函数this 在定义时确定箭头函数不是普通函数的语法糖它在this的处理上有一个本质区别箭头函数没有自己的 this。这句话怎么理解当你在箭头函数内部访问this时JavaScript 引擎不会为箭头函数单独绑定一个this而是直接沿作用域链向上查找找到最近一层非箭头函数作用域的this。如果一直找到全局作用域那就是全局对象。这种机制叫词法 thislexical this意思是this的取值规则由代码书写位置决定和函数怎么调用无关。3.1 定义时确定的核心逻辑const obj { name: 张三, greet: function() { setTimeout(() { console.log(this.name); }, 1000); } }; obj.greet(); // 张三这里的箭头函数没有自己的this它向上找到了greet函数作用域里的this。而greet是普通函数作为obj的方法调用所以它的this指向obj。箭头函数拿到的就是obj。再看一个对比const obj { name: 张三, greet: () { console.log(this.name); } }; obj.greet(); // undefined为什么这里不行因为greet自己就是箭头函数它没有自己的this只能向上找。obj不是函数作用域再向上就是全局作用域。全局作用域里没有name所以输出undefined。这里就是很多人踩坑的地方箭头函数作为对象方法拿不到对象实例的 this。因为它根本没打算给你绑定对象。3.2 call / apply / bind 无法改变箭头函数的 this普通函数可以通过显式绑定改变this但箭头函数不行。因为它根本没有自己的thiscall、apply、bind传进去的对象会被直接忽略。const arrow () { console.log(this.name); }; arrow.call({ name: 张三 }); // undefined arrow.apply({ name: 张三 }); // undefined const boundArrow arrow.bind({ name: 张三 }); boundArrow(); // undefined从材料里的热词也能看到“js箭头函数”、“this”、“箭头函数写法”一直是搜索高频词说明大家对这里的理解确实有难度。关键在于记住一句话箭头函数的 this 是定义时从外层作用域“继承”下来的与调用方式无关。4. 箭头函数的基本语法与写法理解原理之后再看语法就很轻松了。箭头函数的写法比普通函数简洁很多但不同场景下要选对格式。4.1 基础写法// 普通函数 function add(a, b) { return a b; } // 箭头函数完整写法 const add (a, b) { return a b; }; // 箭头函数简写单行表达式自动返回 const add (a, b) a b;单行表达式写法会把表达式的结果作为返回值这个特性配合数组的高阶函数非常好用。4.2 一个参数时省略括号const double (x) x * 2; const double x x * 2;两种写法等价。但当参数是 0 个或多个时括号不能省略。const getRandom () Math.random(); const sum (a, b) a b;4.3 返回对象字面量要注意箭头函数直接返回对象字面量时必须加括号否则解析器会把{}当成函数体。// 错误写法花括号被解析成函数体 const getObj () { name: 张三, age: 18 }; // 正确写法外层加括号 const getObj () ({ name: 张三, age: 18 });这个细节很容易被忽略特别是在写 React 组件返回配置对象的时候。4.4 与普通函数写法对比场景普通函数箭头函数无参数function() { return 1; }() 1一个参数function(x) { return x * 2; }x x * 2多个参数function(a, b) { return a b; }(a, b) a b多行逻辑function(x) { const y x 1; return y; }(x) { const y x 1; return y; }返回对象function() { return { name: 张三 }; }() ({ name: 张三 })5. 箭头函数的典型应用场景箭头函数的优势集中在“短函数 回调”场景。判断标准是你不需要函数自己的 this你只需要沿用外层 this。5.1 定时器与事件回调这是最经典的应用场景。function Timer() { this.seconds 0; setInterval(() { this.seconds; console.log(this.seconds); }, 1000); } const timer new Timer();如果用普通函数需要bind(this)才能让回调里的this指向Timer实例。用箭头函数直接沿外层作用域取到Timer实例的this。5.2 数组高阶函数map、filter、reduce、forEach这些方法回调函数通常很短用箭头函数可以让语义更聚焦。const numbers [1, 2, 3, 4, 5]; // 过滤出偶数 const evens numbers.filter(n n % 2 0); console.log(evens); // [2, 4] // 每个数乘以 2 const doubled numbers.map(n n * 2); console.log(doubled); // [2, 4, 6, 8, 10] // 求和 const total numbers.reduce((sum, n) sum n, 0); console.log(total); // 15在这个场景里基本上不会遇到this问题因为回调本身不需要自己的this箭头函数让代码更简洁。5.3 Promise 链与异步回调写异步代码时箭头函数能避免this丢失。const service { data: [], load: function() { fetch(/api/data) .then(response response.json()) .then(data { this.data data; console.log(this.data); }) .catch(error { console.error(error); }); } }; service.load();这里的this.data data如果写在普通函数里this就不会指向service。箭头函数让异步回调整链都保持着外层的this。5.4 Vue 或 React 组件中的实践在 Vue 组件的methods里使用箭头函数要非常谨慎但在定时器、事件监听、异步回调用箭头函数则很自然。// Vue 组件示例 export default { data() { return { count: 0 }; }, methods: { start() { // 这里是方法this 指向组件实例 setInterval(() { // 箭头函数沿用上面普通方法的 this this.count; }, 1000); } } };在 React 类组件中箭头函数常用于绑定事件处理函数class Counter extends React.Component { constructor(props) { super(props); this.state { count: 0 }; } // 箭头函数写法this 指向组件实例 handleClick () { this.setState({ count: this.state.count 1 }); }; render() { return ( button onClick{this.handleClick} 点击次数{this.state.count} /button ); } }这种用类属性加箭头函数的方式在 React 类组件里非常常见比在构造函数里逐个bind要省事得多。5.5 封装高阶函数箭头函数返回函数的写法非常适合封装通用逻辑。const withPrefix (prefix) (name) ${prefix}_${name}; const addUserPrefix withPrefix(user); console.log(addUserPrefix(张三)); // user_张三 console.log(addUserPrefix(李四)); // user_李四这里withPrefix接收一个前缀返回一个新函数新函数接收名字返回带前缀的结果。箭头函数的简洁语法让这类代码写起来非常舒服。6. 箭头函数不能乱用的场景箭头函数确实好用但它不是万能的。很多项目里的this问题恰恰是箭头函数用错了位置造成的。下面这些场景建议继续使用普通函数。6.1 对象方法前面已经演示过箭头函数作为对象方法是拿不到对象实例的this的。const user { name: 张三, greet: () { console.log(你好我是 ${this.name}); } }; user.greet(); // undefined如果需要通过this访问对象自身的数据必须用普通函数const user { name: 张三, greet: function() { console.log(你好我是 ${this.name}); } }; user.greet(); // 张三6.2 原型方法同样的问题也出现在原型方法里。function Person(name) { this.name name; } // 错误无法访问实例属性 Person.prototype.greet () { console.log(你好我是 ${this.name}); }; const person new Person(张三); person.greet(); // undefined // 正确使用普通函数 Person.prototype.greet function() { console.log(你好我是 ${this.name}); }; person.greet(); // 张三6.3 需要动态 this 的 DOM 事件监听在 DOM 事件监听中普通函数的this会指向触发事件的元素这是一个很实用的特性。// 普通函数this 指向被点击的按钮 button.addEventListener(click, function() { console.log(this); // button 元素 }); // 箭头函数this 沿用外层作用域 button.addEventListener(click, () { console.log(this); // window 或其他外层 this });如果你需要拿到触发事件的元素普通函数反而更合适。箭头函数会把外层的this带进来这在某些场景反而不方便。6.4 构造函数箭头函数不能用作构造函数。它没有自己的this也就无法为新对象初始化属性。const Person (name) { this.name name; }; // TypeError: Person is not a constructor const person new Person(张三);6.5 arguments 对象普通函数的arguments对象可以直接访问参数但箭头函数没有arguments。如果你要在函数内部访问参数列表有两种选择使用普通函数或者用 rest 参数。// 普通函数 function logParams() { console.log(arguments); } logParams(1, 2, 3); // Arguments(3) [1, 2, 3] // 箭头函数中 arguments 不存在 const arrowLog () { console.log(arguments); // ReferenceError: arguments is not defined }; // 用 rest 参数代替 const restLog (...args) { console.log(args); }; restLog(1, 2, 3); // [1, 2, 3]6.6 动态上下文场景如果一段代码需要根据调用方式动态改变this箭头函数绝对不能用。比如某些设计模式中函数作为工具库的方法由不同调用方使用它需要动态接收调用对象的上下文。7. 箭头函数 this 的常见误区与面试题解析关于箭头函数和this网上讨论非常多但不少结论是错的。这里我整理几个最常见的误区顺便帮你看穿几类面试题。7.1 误区一箭头函数自动绑定外层 this这句话不准确。准确的说法是箭头函数没有自己的this它访问this时会沿作用域链向上查找。差别在哪里“自动绑定外层 this”给人一种箭头函数主动做了绑定的感觉但实际上箭头函数根本没有 this 绑定这一步。它的行为更像是词法查找和你查一个普通变量一样。7.2 误区二箭头函数不能改变 this更准确的说法是箭头函数的 this 在定义时已经确定call、apply、bind无法重新绑定它。但这不代表箭头函数内部的 this 永远不变。如果它外层函数的 this 变化了箭头函数拿到的 this 也会跟着变化。const objA { name: A, show: function() { const arrow () { console.log(this.name); }; arrow(); } }; const objB { name: B }; objA.show(); // A // 通过 call 改变 show 的 this箭头函数拿到的 this 随之改变 objA.show.call(objB); // B这里arrow的定义位置在show内部所以它查找的是show函数的 this。当show的 this 被改成objB时箭头函数输出的就是B。所以说“箭头函数 this 永远固定”是不对的只能说对箭头函数本身无法显式改变但它依赖的外层 this 可以被改变。7.3 误区三箭头函数完全可以替代普通函数这是项目里最常见的问题。箭头函数不能用作构造函数不能当对象方法没有 arguments。它适合的是回调、嵌套函数、链式调用这类不需要动态 this 的场景而不是取代普通函数。7.4 面试题这段代码输出什么const obj { name: 张三, sayHi: () { console.log(Hi, ${this.name}); } }; obj.sayHi();输出Hi, undefined。因为sayHi是箭头函数没有自己的 this向上查找全局作用域全局没有 name 属性。const obj { name: 张三, sayHi: function() { const arrow () { console.log(Hi, ${this.name}); }; arrow(); } }; obj.sayHi();输出Hi, 张三。因为sayHi是普通函数this 指向 obj箭头函数定义在sayHi内部沿作用域向上找到的就是sayHi的 this。7.5 面试题延迟输出const obj { name: 张三, greet: function() { setTimeout(function() { console.log(this.name); }, 0); } }; obj.greet();输出undefined。setTimeout的回调是普通函数被独立调用this 指向全局对象。如果改成箭头函数const obj { name: 张三, greet: function() { setTimeout(() { console.log(this.name); }, 0); } }; obj.greet();输出张三。箭头函数沿作用域找到greet的 this也就是obj。7.6 面试题call 能改箭头函数的 this 吗const arrow () { console.log(this.name); }; arrow.call({ name: 张三 });输出undefined。箭头函数没有自己的 thiscall传参无效。8. 实际项目里的最佳实践与工程建议前面讲了原理和场景最后落到工程实践。这一节给出具体可执行的建议帮助团队规范使用箭头函数减少this相关 bug。8.1 什么时候优先用箭头函数定时器、事件回调、异步回调内部需要沿用外层 this。数组高阶函数的回调代码短且不需要动态 this。函数内部嵌套的辅助函数。返回函数的工厂函数、高阶函数。不需要函数自身 arguments 的短函数。8.2 什么时候必须用普通函数对象方法需要 this 访问对象属性。原型方法。构造函数。需要动态 this 的 DOM 事件处理函数。函数内部需要使用 arguments。8.3 判断标准一个很简单的判断方法这个函数是否需要拥有自己的 this需要用普通函数。不需要用箭头函数。这里的“需要”不只是“用到 this”还包括“即使现在不用未来也可能被调用方动态指定 this”。比如一个事件处理函数虽然不是你自己调用但浏览器会往回调里塞一个 this 指向触发元素这个信息有时是有价值的。8.4 team lint 规则建议在 ESLint 中有一个规则叫prefer-arrow-callback建议在回调场景使用箭头函数。还有一个规则叫no-this-assignment会警告类似const that this这样的写法——当你用了箭头函数后这种写法基本可以消失。{ rules: { prefer-arrow-callback: error } }配置这条规则会让团队代码中“为了存 this 而定义的变量”明显减少。8.5 代码审查时重点盯的场景代码审查时重点看这几个位置对象方法是否误用了箭头函数生命周期函数Vue 的created、mountedReact 的componentDidMount内部是否混用了箭头函数和普通函数事件监听回调是普通函数还是箭头函数是否符合意图是否有人在 class 里把字段直接写成箭头函数这个在 React 类组件里常见没问题但普通类里要确认 this 的预期8.6 关于 this 的心智模型在项目里建议统一一套心智模型避免每个成员靠记忆“这个场景箭头函数行不行”来写代码。我建议这样理解普通函数的 this 像一个“调用时注入的外部参数”由调用方式决定。箭头函数的 this 像一个“定义时捕获的变量”它从外层作用域继承。有了这个模型分析代码时只需要看箭头函数最近的普通函数作用域在哪里这个普通函数的 this 是什么箭头函数拿到的 this 就是什么。如果箭头函数外面没有普通函数那 this 就是全局对象模块作用域下可能是 undefined取决于模块模式。8.7 一段综合练习最后放一段综合代码你可以先自己在心里判断输出再运行验证。const name 全局; const obj { name: 对象, normalFn: function() { return function() { console.log(this.name); }; }, arrowFn: function() { return () { console.log(this.name); }; }, arrowMethod: () { console.log(this.name); } }; obj.normalFn()(); // ? obj.arrowFn()(); // ? obj.arrowMethod(); // ?运行结果obj.normalFn()()输出全局。内部返回的是普通函数独立调用this 指向全局。obj.arrowFn()()输出对象。箭头函数定义在arrowFn内部查找的是arrowFn的 this也就是 obj。obj.arrowMethod()输出全局。arrowMethod本身是箭头函数没有自己的 this向上找到全局作用域的name。在浏览器控制台运行时会发现第三个输出可能在严格模式下是undefined。这是因为 ES6 模块默认严格模式this 不会指向全局对象。这个差异也提醒我们同样的代码在不同的运行环境下 this 的默认值可能不同。8.8 生产环境的注意事项在生产项目中还有几个实际建议不要滥用箭头函数做“性能优化”。箭头函数确实比普通函数少绑定 this但两者的性能差异在绝大多数业务代码里可以忽略。真正的影响是代码可读性和正确性。避免在定义对象字面量时让箭头函数阅读起来像是方法。如果团队成员习惯写const obj { foo: () {} }很容易误解为 foo 可以使用 this 访问 obj。建议在代码规范里直接禁止对象字面量使用箭头函数写方法。在 TypeScript 里箭头函数结合 class 字段也有注意点。类字段箭头函数会在每个实例创建时新建函数如果实例数量很大内存开销比原型方法大。这在列表项组件、表格行组件里特别明显。9. 总结与后续学习方向这篇文章的核心内容可以归纳成下面这几点。第一普通函数的 this 由调用方式决定有默认绑定、隐式绑定、显式绑定、new 绑定四种规则。这也是 this 经常丢失的原因函数被独立调用时this 不再指向你预期的对象。第二箭头函数没有自己的 this。它在定义时从外层作用域捕获 this与调用方式无关。这正是它解决回调函数 this 丢失问题的原因——它直接把外层 this 沿作用域“继承”下来了。第三箭头函数不能替代普通函数。对象方法、原型方法、构造函数、需要动态 this 的 DOM 事件等场景都应该继续使用普通函数。第四判断用哪个函数的简单标准是这个函数是否需要拥有自己的 this。如果想继续学习可以往这几个方向深入JavaScript 作用域、闭包和执行上下文的完整机制函数式编程中的高阶函数、柯里化和组合Vue 3 或 React Hooks 中 this 设计差异以及为什么 Hooks 风格更依赖闭包而非 thisTypeScript 中对函数类型、this 类型的类型声明建议你写完这篇文章后找几个项目里的老代码把其中回调函数里的that、self变量和bind(this)改成箭头函数再跑一遍测试。这个过程能让你对这些机制的理解落到实处而不只是记住几条规则。