
教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载this是 JavaScript 中最容易让人困惑的关键字之一它的取值取决于代码的执行上下文execution context与调用方式而非定义位置。本文以 30 seconds of code 仓库中的 Understanding the this keyword in JavaScript 为核心骨架系统梳理this在全局、函数、对象方法、构造函数、箭头函数与事件处理器中的取值规则并结合仓库内 arrow-functions、function-call-apply-bind 等关联文章与源码示例深入讲解call()、apply()、bind()三种显式绑定手段的差异与实战用法。读完本文你将能准确预判任意调用场景下this的值并熟练借助显式绑定规避回调函数中的上下文丢失问题。什么是this在 JavaScript 中this关键字指向当前正在执行代码的那个对象。它的值不是静态的而是由函数被调用invocation的方式在运行时决定的。原文档用一句话概括了this在各种场景下的取值规则默认情况下this指向全局对象在非严格模式的函数中this指向全局对象在严格模式的函数中this为undefined在箭头函数中this保留其外层词法作用域enclosing lexical context的this值在对象方法中this指向调用该方法的对象在构造函数调用new中this绑定到正在被构造的新对象在事件处理器中this绑定到放置监听器的那个元素。把这七条规则记牢就等于拿到了解读this的“查表”。接下来我们逐一展开用可运行代码验证每一条。全局上下文中的this在全局执行上下文中this指向全局对象。在浏览器环境中全局对象就是windowconsole.log(this window); // true需要注意的是这段代码依赖浏览器运行环境在 Node.js 等非浏览器环境下全局对象是global或globalThis。这是this规则中唯一与环境耦合紧密的一条其余规则在各 JavaScript 运行环境中基本一致。函数上下文中的this当一段普通代码运行在函数内部时this的取值取决于该函数是否处于严格模式。在非严格模式下普通函数不作为方法、不使用new、不显式绑定的this指向全局对象function f() { return this; } console.log(f() window); // true在严格模式下如果进入执行上下文时没有为this赋值则函数内的this是undefineduse strict; function f() { return this; } console.log(f()); // undefined严格模式通过use strict指令开启它消除了非严格模式下默认绑定到全局对象的“兜底行为”让this的取值更可预测因此推荐在模块化代码中默认开启严格模式。对象方法中的this当一个函数作为对象的方法被调用时this指向调用该方法的那个对象。这条规则同样适用于原型链上定义的方法——无论是对象自身own的属性还是继承inherited的方法只要方法是通过obj.method()这种形式触发的this就绑定到objconst obj { f: function() { return this; } }; const myObj Object.create(obj); myObj.foo 1; console.log(myObj.f()); // { foo: 1 }上面的例子中myObj通过Object.create(obj)继承了obj上的f方法并为自己添加了foo属性。调用myObj.f()时this指向调用者myObj因此返回值是{ foo: 1 }——注意这里返回的是myObj自身包含继承链并非只有foo一个属性。构造函数中的this当函数作为构造函数被new调用时this绑定到正在被构造的新对象上。这一点在现代class语法中体现得最直观class C { constructor() { this.x 10; } } const obj new C(); console.log(obj.x); // 10constructor()中的this就是随后赋给obj的那个新实例this.x 10等价于为新对象定义属性x。这也是new表达式与普通函数调用最本质的区别之一。箭头函数中的this箭头函数arrow function是 ES6 引入的语法它不定义自己的执行上下文因此this始终保留其外层词法作用域的this值。这意味着无论箭头函数在哪里被调用、如何被调用它的this都不会改变const f () this; console.log(f() window); // true const obj { foo: function() { const baz () this; return baz(); }, bar: () this }; console.log(obj.foo()); // { foo, bar } console.log(obj.bar() window); // true注意第二个例子中的两个关键差异obj.foo()是普通函数调用其this指向obj内部的baz是箭头函数它保留了外层foo的词法this所以baz()返回的是objobj.bar本身是箭头函数它在对象字面量定义时捕获的是全局词法上下文因此即使以obj.bar()的形式调用this依然是window。原文档对此给出的结论值得反复咀嚼箭头函数的this指向全局对象除非它被包裹在一个普通function调用中——此时它继承的是该普通函数调用方的this即调用方的词法上下文被箭头函数保留。关于箭头函数的词法绑定仓库中的 Introduction to arrow functions in JavaScript 做了更系统的对比。在该文章的“Execution context”一节中可以看到普通函数的this是动态的取决于调用方式普通调用、new调用、方法调用、call间接调用而箭头函数则恒定为定义时所在的词法作用域const simple () this; const object { method: () this }; class Class { classMethod () { console.log(this); } } const instance new Class(); simple(); // this refers to the global object new simple(); // Uncaught TypeError: simple is not a constructor object.method(); // this refers to the global object simple.call(object); // this refers to the global object instance.classMethod(); // this refers to instance setTimeout( instance.classMethod, 0 // this refers to instance );从这段对照可以看出箭头函数不能用作构造函数new simple()会抛出TypeError不能通过call()改变其this而作为类字段class field的箭头函数则能稳定保留实例的this——这在后续“常见坑点”一节还会展开。事件处理器中的this在事件处理器中this绑定到放置监听器的那个元素上。只要回调使用普通函数这一规则就成立const el document.getElementById(my-el); el.addEventListener(click, function() { console.log(this el); // true });仓库中的 Can I use an arrow function as the callback for an event listener? 专门分析了这一场景的陷阱。如果用箭头函数替换回调this将不再指向元素而是解析为词法上下文——在模块顶层就是全局对象Windowconst toggleElements document.querySelectorAll(.toggle); toggleElements.forEach(el { el.addEventListener(click, () { this.classList.toggle(active); // this 指向 Window // Error: Cannot read property toggle of undefined }); });这段代码会在点击时抛出错误因为Window没有classList属性更隐蔽的情况是条件判断对Window恒为false而对元素为true导致代码“静默失败”排查起来非常费时。该文章给出的稳妥解法是改用回调的第一参数通过Event.currentTarget或Event.target获取元素const toggleElements document.querySelectorAll(.toggle); toggleElements.forEach(el { el.addEventListener(click, (e) { e.currentTarget.classList.toggle(active); // works correctly }); });这是“事件处理器中this与箭头函数冲突”的标准规避手法值得在实际项目中作为默认写法。显式绑定thisbind()、call()与apply()除了依赖调用方式自动绑定JavaScript 还提供了三种显式绑定手段用于在调用时人为指定this。Function.prototype.bind()bind()会从现有函数返回一个新函数并把this永久绑定到bind()的第一个参数上function f() { return this.foo; } const x f.bind({foo: hello}); console.log(x()); // helloFunction.prototype.call()与Function.prototype.apply()与bind()返回新函数不同call()和apply()会立即调用原函数并将本次调用的this绑定到第一个参数仅对这一次调用生效function f() { return this.foo; } console.log(f.call({foo: hi})); // hi仓库中的 JavaScript function methods - call(), apply() and bind() 对三者差异做了更精确的界定核心区别在于参数的传递方式call()this之后参数逐个传入apply()this之后参数以数组形式一次性传入bind()this之后参数作为预置参数prepended arguments在调用返回的新函数时排在最前面。function printThisAndData(...data) { console.log(this.data, ...data); } const obj { data: 0 }; const data [1, 2, 3]; printThisAndData.call(obj, data); // logs: 0 [1, 2, 3] printThisAndData.call(obj, ...data); // logs: 0 1 2 3 printThisAndData.apply(obj, data); // logs: 0 1 2 3 printThisAndData.apply(obj, ...data); // Throws a TypeErrorapply 不接受展开后的参数列表而bind()的预置参数特性让它非常适合部分应用partial applicationconst printObjTwoAndData printThisAndData.bind(obj, 2); printObjTwoAndData(data); // logs: 0 2 [1, 2, 3] printObjTwoAndData(...data); // logs: 0 2 1 2 3显式绑定在实战中的典型场景该关联文章还给出了几个可复用的实战模式全部基于apply()实现按对象键绑定方法——生成一个固定以context为this、调用context[fn]的函数const bindKey (context, fn, ...boundArgs) (...args) context[fn].apply(context, [...boundArgs, ...args]); const freddy { user: fred, greet: function(greeting, punctuation) { return greeting this.user punctuation; } }; const freddyBound bindKey(freddy, greet); console.log(freddyBound(hi, !)); // hi fred!绑定对象的所有方法——遍历指定方法名用apply重写为固定this的版本const bindAll (obj, ...fns) fns.forEach( fn ( (f obj[fn]), (obj[fn] function() { return f.apply(obj); }) ) ); let view { label: docs, click: function() { console.log(clicked this.label); } }; bindAll(view, click); document.body.addEventListener(click, view.click); // Log clicked docs when clicked.绑定任意函数与上下文——与bindKey思路一致但作用于普通函数而非对象方法const bind (fn, context, ...boundArgs) (...args) fn.apply(context, [...boundArgs, ...args]); function greet(greeting, punctuation) { return greeting this.user punctuation; } const freddy { user: fred }; const freddyBound bind(greet, freddy); console.log(freddyBound(hi, !)); // hi fred!这三种模式覆盖了“方法解绑后丢失上下文”的主流修复思路可以作为工具函数直接沉淀进自己的代码库。常见误用与坑点汇总综合原文档与仓库内关联文章以下四种情形最容易踩坑需要特别留意。1. 箭头函数不能用new调用由于this词法解析箭头函数没有自己的构造上下文使用new会抛出TypeError。仓库中 What are the differences between arrow functions and regular functions? 给出了明确示例function Foo(bar) { this.bar bar; } const a new Foo(42); // Foo {bar: 42} const Bar foo { this.foo foo; }; const b new Bar(42); // TypeError: Bar is not a constructor2.call()/bind()/apply()无法改变箭头函数的this箭头函数的this由词法作用域决定显式绑定的三个方法对它都不生效function logThis() { console.log(this); } logThis.call(42); // Logs: 42 const logThisArrow () { console.log(this); }; logThisArrow.call(42); // Logs the global object3. 方法作为回调传入时丢失上下文普通函数方法的this依赖调用方式。把instance.classMethod直接传给setTimeout时回调中的this会变成全局对象。解决方案有二使用Function.prototype.bind()或把方法定义为箭头函数借助 class fields 语法箭头类字段会稳定捕获实例的this。详见 arrow-functions 一文。4. 箭头函数没有自己的arguments对象普通函数拥有arguments而箭头函数会向外层作用域解析arguments现代写法推荐用**剩余参数rest parameters**替代function sum() { return arguments[0] arguments[1]; } sum(4, 6); // 10 const sumRest (...args) { return args[0] args[1]; } sumRest(4, 6); // 10总结回顾原文档的七条规则可以归纳出理解this的三条主线调用方式决定this全局调用、普通函数调用、方法调用、构造函数调用、事件回调五种场景对应五种取值规则箭头函数跳出规则之外它没有自己的执行上下文this恒为定义时的词法this也因此不能作为构造函数、无法被显式绑定显式绑定作为“接管手段”bind()返回永久绑定的新函数call()/apply()只对单次调用生效三者参数传递方式各有差异可用于修复回调中丢失的上下文。本文基于的原始文档位于 content/snippets/js/s/this.md所属的 JavaScript Function Articles 集合 收录了函数主题下的系列文章建议进一步阅读 arrow-functions、function-call-apply-bind 与 arrow-function-event-listeners 三篇关联文章它们从不同角度反复印证了本文的每一条规则。赞分享教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载相关推荐深入理解 JavaScript 的 this 关键字上下文绑定、词法作用域与实战陷阱30-seconds-of-interviews 面试题精讲深入理解 JavaScript 的 this 关键字上下文绑定、词法作用域与实战陷阱30 seconds of interviews 面试题精讲 导读 本教程前端30 Seconds of Code 实战JavaScript 事件监听回调中的箭头函数this 绑定陷阱与正确写法30 Seconds of Code 实战JavaScript 事件监听回调中的箭头函数this 绑定陷阱与正确写法 本篇文章聚焦 30 Seconds o教程文档30 Seconds of CodeJavaScript 箭头函数Arrow Functions完全入门指南30 Seconds of CodeJavaScript 箭头函数Arrow Functions完全入门指南 导读 箭头函数是 ES6 引入的一种全新的函教程文档上一篇探秘App解密神器bagbak助力iOS逆向工程新高度下一篇如何快速扩展Ookii.Dialogs.WinForms创建自定义对话框的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考