- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
导读
本文围绕 Modern JavaScript Tutorial(当前仓库en.javascript.info)中 Object methods, "this" 章节的经典练习"Create a calculator"(任务说明)展开。我们将以官方参考答案为骨架,完整剖析read()、sum()、mul()三个方法的实现细节、this的运行时求值规则、一元+的类型转换,并结合仓库内的 测试用例 与同系列 构造函数版计算器 做对比扩展。读完后,你将掌握对象方法简写语法、this的动态绑定规律,以及如何把一道练习题改造成可测试、可复用的真实代码。
任务背景:在对象方法中认识this
在进入解题之前,先明确这道题在教程中的位置。它出自 Object methods, "this" 一章,该章的核心结论有三条(见原文档 Summary):
- 存储在对象属性中的函数称为方法(method);
- 方法让对象能够"行动",例如
object.doSomething(); - 方法内部通过
this关键字引用调用它的对象。
关键在于:this的值在运行时才被求值。函数声明时可以写this,但在调用之前它没有值;一个函数可以被复制到多个对象上;当以"方法语法"object.method()调用时,调用期间this的值就是object(即"点号前面的对象")。
这道计算器练习正是为了让读者亲身体验这套规则:三个方法都依赖this来读写对象自身的属性a和b,只有用点号调用(calculator.read()、calculator.sum())时,this才会正确指向calculator本身。
官方参考实现逐行解析
任务的完整要求(见 task.md,难度标记 importance: 5)是创建一个包含三个方法的calculator对象:
read():弹出两次输入框,把两个值分别保存为对象属性a和b;sum():返回已保存两个值的和;mul():将已保存的两个值相乘并返回结果。
官方参考答案 的完整代码如下:
let calculator = { sum() { return this.a + this.b; }, mul() { return this.a * this.b; }, read() { this.a = +prompt('a?', 0); this.b = +prompt('b?', 0); } }; calculator.read(); alert( calculator.sum() ); alert( calculator.mul() );方法简写语法:sum() { ... }
注意三个方法的定义方式。教程原文指出,在对象字面量中存在更短的语法:
// 这两种写法等价 user = { sayHi: function() { alert("Hello"); } }; user = { sayHi() { // 等价于 "sayHi: function(){...}" alert("Hello"); } };参考答案正是使用了这种简写:sum()、mul()、read()都省略了function关键字。严格来说两种写法并非完全等同(存在与对象继承相关的细微差异,后续章节会涉及),但在绝大多数场景下,简写语法是首选。
read():保存用户输入
read() { this.a = +prompt('a?', 0); this.b = +prompt('b?', 0); }这里有两个关键点:
prompt返回字符串:prompt('a?', 0)弹出的输入框返回的是字符串(即使输入的是数字)。第二个参数0是默认值。- 一元加号
+强制转成数字:+prompt(...)把字符串转换为数字,例如+"2"得到2。这保证了sum()做的是数值加法而非字符串拼接——如果省略+,"2" + "3"会得到字符串"23"而不是5。
this.a = ...把输入值保存为calculator对象自己的属性,后续sum()、mul()就能通过this.a、this.b读取它们。
sum()与mul():通过this访问属性
sum() { return this.a + this.b; }, mul() { return this.a * this.b; }当以calculator.sum()调用时,this就是calculator,因此this.a、this.b读取的是read()保存下来的属性值。
教程在这里专门强调过:虽然技术上也可以用外层变量名直接引用对象(例如alert(user.name)代替alert(this.name)),但这种代码不可靠——一旦把user复制给admin并覆盖了user,就会访问到错误的对象甚至报TypeError。这正是"对象方法必须用this"的最佳例证。
调用顺序:先read()再使用
最后三行展示了正确的使用流程:
calculator.read(); alert( calculator.sum() ); alert( calculator.mul() );必须先调用read()让a、b有值,然后才能求和、求积。这也符合对象"封装状态 + 行为"的设计理念:属性是状态,方法是行为。
深入原理:this为何指向calculator
参考答案的核心考点就是this的运行时求值规则。教程原文用下面的例子说明了"点号前的对象决定this":
let user = { name: "John" }; let admin = { name: "Admin" }; function sayHi() { alert( this.name ); } // 同一个函数挂到两个对象上 user.f = sayHi; admin.f = sayHi; user.f(); // John (this == user) admin.f(); // Admin (this == admin) admin['f'](); // Admin(点号或方括号访问方法,结果相同)规则很简单:如果调用obj.f(),那么f执行期间this就是obj。在计算器例子中,calculator.read()的this是calculator,calculator.sum()的this也是calculator。
教程还特别提醒两种边界情况:
- 无对象调用时
this == undefined:如果直接调用sayHi()(没有点号),严格模式下this是undefined,访问this.name会报错;非严格模式下this是全局对象(浏览器中是window),这是"use strict"修复的历史遗留行为。 this是"自由的":它的值在调用时求值,不取决于方法在哪里声明,而取决于调用时点号前面是哪个对象。这种设计让同一个函数可以被不同对象复用(本例中sum、mul挂在calculator上),但也带来了更高的出错可能。
此外,教程指出箭头函数没有自己的this——在箭头函数里引用this,会取自外层"普通"函数。所以如果在本例中把sum写成箭头函数sum: () => this.a + this.b,this将不再指向calculator,结果会是undefined。这也是本题要求使用普通方法语法的原因之一。
用仓库自带测试验证正确性
仓库为这道题提供了配套的自动化测试 test.js,它基于教程第 3 章介绍的 Mocha/BDD 风格(describe/context/it/assert,详见 Automated testing with Mocha)。测试逻辑如下:
describe("calculator", function() { context("when 2 and 3 entered", function() { beforeEach(function() { sinon.stub(window, "prompt"); prompt.onCall(0).returns("2"); prompt.onCall(1).returns("3"); calculator.read(); }); afterEach(function() { prompt.restore(); }); it('the read get two values and saves them as object properties', function () { assert.equal(calculator.a, 2); assert.equal(calculator.b, 3); }); it("the sum is 5", function() { assert.equal(calculator.sum(), 5); }); it("the multiplication product is 6", function() { assert.equal(calculator.mul(), 6); }); }); });这份测试生动地验证了参考答案的四个行为:
- 用 sinon 桩替代
prompt:sinon.stub(window, "prompt")拦截浏览器原生的prompt,onCall(0).returns("2")、onCall(1).returns("3")让第一次调用返回"2"、第二次返回"3"——这正是read()中两次prompt的执行顺序。 read()保存属性:测试断言calculator.a === 2、calculator.b === 3,证明一元+把字符串"2"、"3"转成了数字。sum()求和:断言结果为5(2 + 3,数值加法,而非字符串拼接)。mul()求积:断言结果为6(2 * 3)。
注意测试代码直接引用了全局变量calculator,说明该解法文件(solution.js)以对象字面量形式定义了calculator,测试与实现通过同一个全局对象衔接——这也是教程练习页的惯例:先由calculator.read()产生交互,再由测试断言验证属性与运算结果。
延伸对比:构造函数版计算器
在教程后续章节 Constructor, operator "new" 中,同一道题以构造函数形式出现(2-calculator-constructor 的参考答案):
function Calculator() { this.read = function() { this.a = +prompt('a?', 0); this.b = +prompt('b?', 0); }; this.sum = function() { return this.a + this.b; }; this.mul = function() { return this.a * this.b; }; } let calculator = new Calculator(); calculator.read(); alert( "Sum=" + calculator.sum() ); alert( "Mul=" + calculator.mul() );两版对比如下:
| 对比维度 | 对象字面量版(本章) | 构造函数版(后续章节) |
|---|---|---|
| 定义方式 | let calculator = { ... } | function Calculator() { ... }+new Calculator() |
| 方法挂载 | 直接写在对象字面量里(简写语法) | 用this.read = function() {...}赋给新实例 |
| 实例数量 | 单个对象 | 可new Calculator()创建多个独立实例 |
this指向 | 调用时的calculator | 调用时的实例对象(new创建) |
两版都依赖this读写属性,原理完全一致;区别在于构造函数版把"创建计算器"抽象成可复用的工厂,而对象字面量版更贴近本章主题——直接在字面量中定义方法与this的交互。
常见错误与排查要点
基于this的运行时规则和一元+的类型转换,这道题最常见的错误有以下几类:
- 忘记加一元
+:this.a = prompt('a?', 0)存下字符串,sum()会得到字符串拼接结果(如"23")。修复:+prompt(...)。 - 用外层变量名代替
this:例如在sum()里写calculator.a + calculator.b。一旦calculator被重新赋值,方法就会失效或报错,应始终使用this.a、this.b。 - 把方法写成箭头函数:
sum: () => this.a + this.b中箭头函数没有自己的this,会取外层作用域的值(可能是undefined或全局对象),导致计算结果错误。本题应使用普通方法语法。 - 调用顺序颠倒:在
read()之前调用sum(),此时this.a、this.b尚未定义,结果会是NaN。务必先read()再计算。
小结
这道 "Create a calculator" 练习浓缩了 Object methods, "this" 一章的全部核心知识:
- 方法 = 对象属性中的函数,用简写语法
read() { ... }定义; this在调用时求值,calculator.method()中this就是calculator,因此sum()/mul()能通过this.a、this.b访问read()保存的状态;- 一元
+确保输入是数字,让+运算符做加法而不是字符串拼接; - 仓库自带的 test.js 用 Mocha + sinon 桩验证了上述行为,可作为后续写自动化测试的范本;
- 后续 构造函数版 将同一逻辑升级为可复用的多实例方案,体现了从"单个对象"到"对象工厂"的思路演进。
如果你希望进一步练习,可以试着为calculator增加sub()(减法)和div()(除法)方法,并为除法补充除零保护——这既能巩固this的用法,也为后续学习 Constructor, operator "new" 章节的构造器模式做好准备。
- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
相关推荐
Ralph 自治开发循环用例体系解析:从 Actor 目录到熔断器状态机的完整场景建模
Ralph 自治开发循环用例体系解析:从 Actor 目录到熔断器状态机的完整场景建模 Ralph 是一个面向 Claude Code 的自治 AI 开发循环系
文档/教程前端Modern JavaScript Tutorial 实战解析:用构造器函数实现可累积求和的 Accumulator
Modern JavaScript Tutorial 实战解析:用构造器函数实现可累积求和的 Accumulator 本篇实战指南基于现代 JavaScript
文档/教程前端用构造函数实现 Calculator 计算器:JavaScript 构造器模式实战演练
用构造函数实现 Calculator 计算器:JavaScript 构造器模式实战演练 导读 本文围绕 Modern JavaScript Tutorial h
文档/教程前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考