现代 JavaScript(ES6+)— this、相等性、数组方法与 async/await
现代 JavaScript(ES6+)— this、相等性、数组方法与 async/await
这一章讲什么: 原书把 TypeScript 叫作「JavaScript++」——TypeScript 不替代 JavaScript, 只是给它加类型1。所以要写好后面的 React 和 Node 代码,JavaScript 本身的地基必须先打牢。 这一章就是那块地基:this 是什么、相等判断坑在哪、数组的五个标配方法、 异步代码的四种写法、模块怎么拆文件。 全书后面每一章都在直接用这里的东西。
1. 顶层全景:三块硬骨头 + 一套新工具
① 变量与作用域 var 的老毛病 → let / const 的块级作用域
② this 一个关键字,四种身份,全看「怎么被调用」
③ 相等与真假 == 会偷偷转型;对象比的是内存地址;falsy 有张固定清单
④ 数组五件套 find / filter / map / reduce / some·every —— React 里天天见
⑤ 异步进化史 回调 → setTimeout → Promise → async/await,同一个需求四种写法
⑥ 模块 import / export;模块只执行一次,导出的是同一个实例
图说:①–③ 是「为什么别人的 JavaScript 总出怪事」,④–⑥ 是「现代代码实际长什么样」。2. 变量与作用域:var 不认花括号,const 只管绑定的名字
老 JavaScript 里,作用域的容器只有函数体,没有花括号(block)什么事:
if (true) { var val1 = 1; }
console.log(val1); // 1 —— if 的花括号根本没拦住它var 还有一个老毛病叫提升(hoisting):引擎会把声明悄悄挪到函数顶部,
所以「先用后声明」不会报错,只会给你 undefined2。
ES6 新增的两个声明关键字修好了这两点:
const 有个必须当场说清的细节,它是本章第一个反直觉点:
const val2 = [];
val2.push('hello'); // ✅ 成功const 锁的是「变量指向哪个对象」,不是「那个对象的内容」。
val2 推入元素后,变量仍然指向内存里同一个数组对象——内容变了,指向没变,
所以合法;把 val2 整个换成另一个数组才会报错4。
原书的使用口径:默认用 const(它宣告「这个名字不会再变」,读过的人最省心;
就算只在绑定层面不变,也能挡住「误重赋值」这类最常见的小错),
确定要重新赋值才用 let,别再用 var5。
3. this:不看你写在哪,看你被谁调用
this 是 JavaScript 最出名的坑。原书的讲法先给定义:this 是函数运行时的
「对象上下文」——函数(它本身也是对象)的成员此刻归谁所有,this 就是谁6。
关键在「运行时」三个字:同一个函数,调用方式不同,this 的身份就不同。
书里演示了三种身份:
身份一:构造器里的 this = 新实例。 函数前面加 new,它就变成构造器,
this 指向刚造出来的对象7。
身份二:普通脚本顶层的 this = 全局对象。 浏览器里是 window,Node 里叫
global,现在统一的叫法是 globalThis。在浏览器脚本顶层定义的函数,
会自动挂到 window 上8。
身份三:模块里的顶层函数不属于全局对象。 Node 把文件当模块跑,
模块的根级函数不会自动挂到 globalThis 上——这就是模块的封装性9。
身份四:严格模式下的裸调用,this 是 undefined。 ES 模块默认严格模式,
TypeScript 6.0 起也默认严格模式;函数不作为方法、不作为构造器、被单独调用时,
this 不再退回全局对象,而是 undefined10。
配套的重要推论是箭头函数:它没有自己的 this,声明时从最近的父作用域
原样继承一个(这叫词法作用域绑定);作为代价,它没有传统函数的
arguments 对象11。在 React 里这救了无数人的命——回调里写 this 不再漂移。
4. 相等与真假:== 会偷偷转型,对象比的是地址
==(宽松相等)会先类型强制——把两边转成同一类型再比,所以
"25" == 25 是 true12。===(严格相等)不转型,类型不同直接 false13。
原书的口径:永远优先 ===,除非你有明确的理由要 ==13。
再看对象。两个字段一模一样的对象,== 比出来是 false:
const usera = { name: "jon", age: 25 };
const userb = { name: "jon", age: 25 };
usera == userb; // false因为 JavaScript 不能自定义相等规则,对引用类型的比较比的是内存地址:
usera 和 userb 是两块不同的内存,自然不相等14。
想判断对象是不是某个自定义类型,用 instanceof15。
顺带两个冷知识:typeof null 返回 "object"——这是语言诞生时的 bug,
为了向后兼容永远修不了了16;基础类型就那七种(数字、字符串、布尔、
undefined、null、symbol、bigint),其余(对象、数组、函数、日期)都是引用类型。
真假值(truthy/falsy)是另一张值得背下来的清单。在 if 这种布尔上下文里,
JavaScript 会把值强行按「真/假」分类。falsy 一共六个:0、0n(BigInt 零)、
空字符串 ""、null、undefined、NaN——除此之外全是 truthy,
包括字符串 "0"、字符串 "false"、所有非空对象17。所以 if (val) 一句
顶 if (val !== null && val !== undefined && val !== "" && …) 一串。
5. 复制、合并、解构、rest、?. 和 ??:一把顺手的工具
原书把几个「让代码变短」的特性打包讲。它们共同的底色是:新写法都返回新对象/新数组, 不改原件。
展开(spread):{ ...a, ...b } 把两个对象合成一个全新对象;[...a, ...b] 同理
合成新数组。注意是浅拷贝——如果某字段的值本身是对象,新对象和它共享同一个引用18。
对比 Object.assign(a, b):它把 b 的属性灌进 a,返回的就是 a——原对象被改了19。
数组侧还有个 concat,和展开等效,但展开兼容任何可迭代对象(Set、Map),不只数组18。
解构(destructuring):从对象或数组里「点名取出」成员,还可以用冒号起别名:
const { name: fullName, age } = getEmployee(22); // 只要 name(改名 fullName)和 age
const [id, officeAddress] = getEmployeeWorkInfo(33); // 数组按位置取,顺序有意义这是现代 JavaScript 里使用频率最高的特性之一20。
rest:function doSomething(a, ...others) 把「剩下的所有参数」收成一个数组;
只能放在参数列表最后21。
可选链(optional chaining) ?.:遇到 null/undefined 就短路返回 undefined,
不再抛 TypeError: Cannot read properties of undefined。本章主线例子的小车:
car?.wheels 在 car 是 undefined 时安静给出 undefined22;
car.wheels?.wheelsCount() 同理防住第二层22。
空值合并(nullish coalescing) ??:左边不是 null/undefined 就用左边,
否则用右边。它和 || 像,但 || 把所有 falsy(包括 0 和 "")都当「没值」,
?? 只认 null/undefined23——0 ?? 10 是 0,而 0 || 10 是 10。
6. 数组五件套:同一个数组走五遍(本章主线例子)
原书反复强调:数组方法用得多,而且 React、Node 开发全程都要用24。 把书里那组「姓 jon 的用户」当作本章主线例子,五个方法各走一遍:
主线例子:items = [ {jon,20}, {linda,22}, {jon,40} ]
find items.find(i => i.name === 'jon')
→ 只回第一个匹配: {jon,20};一个都没有 → undefined
filter items.filter(i => i.name === 'jon')
→ 回全部匹配: [{jon,20}, {jon,40}];没有 → 空数组 [](不是 undefined!)
map items.map(i => `<div>${i.id} - ${i.name}</div>`)
→ 一一变换,新数组等长;React 里用它把数据数组变成组件数组
reduce [2,5,7,10].reduce((total, cur) => total + cur, 0)
→ 聚合成一个值: 24;第二个参数 0 是累加器的起点,空数组时缺它会报错
some widgets.some(i => i.color === 'blue') → true(存在即可)
every widgets.every(i => i.color === 'blue') → false(全员才算)每个方法都收一个回调函数,由你决定判断逻辑25。原书给的两个易错点:
find 找不到回 undefined、filter 找不到回空数组,别混26;
reduce 的聚合逻辑不限于求和,返回的可以是任何单个值或对象——reduce 因此得名27。
map 被原书点名为「可能最常用的数组方法」,React 组件创建全靠它28。
7. 异步进化史:同一个「等 3 秒」,四种写法
异步代码解决一个矛盾:网络请求、定时器、读文件都很慢,如果傻等,页面就卡死; 但如果不等,代码执行的顺序就乱了29。原书用同一个小需求串起了四代写法:
第 ① 代:回调。 把「干完之后要跑的函数」当参数传进去:
function letMeKnowWhenComplete(size, callback) {
// ……慢速计算……
callback();
}原书特意澄清:回调本身并不异步,异步的是调用它的机制(定时器、网络、文件)30。
第 ② 代:setTimeout 与时间。 看原书那个著名的顺序颠倒实验:
console.log('Let\'s begin.'); // ① 先打印
setTimeout(() => { console.log('I waited…'); }, 3000); // ② 3 秒后打印
console.log('Did I finish yet?'); // ③ 居然第二个打印!运行顺序是 ① → ③ → ②。setTimeout 是异步函数:它启动定时后立刻放行,
后面的代码不等它31。真正的原因是 JavaScript 单线程,定时器由浏览器引擎
在调用栈(call stack)之外代跑,到点再把结果送回主线程32。
第 ③ 代:Promise。 一个「在未来某个不确定的时刻通知你工作完成」的对象:
成功调 resolve,失败调 reject;外面用 .then() 接结果、.catch() 接错误33。
Promise 能工作,但一连串 .then 嵌套多了同样难读——这正是第 ④ 代要解决的33。
第 ④ 代:async/await。 原书一句话点破:async/await 只是 Promise 的语法糖33。
async 标在函数前,等于宣布「这个函数返回 Promise」;await 标在调用前,
等于「在这里等它完成,拿到真正的返回值再继续——但等的时候不卡主线程」34:
const response = await fetch(url); // 网络调用本身
if (response.ok) {
const result = await response.json(); // 再把响应体解析成 JSON
console.log(result);
}对比第 ③ 代的链式 .then,同样的逻辑读起来像同步代码——这就是它迅速被
全面采用的原因35。两个小规矩:await 顶层使用需要文件是 ES 模块
(.mjs 扩展名或 package.json 里 "type": "module");老式写法里得包一层
IIFE(立即调用的函数表达式)34。
8. ES6 模块:一个文件一个模块,只执行一次
老式模块规范 CommonJS 用 require 引、module.exports 出,Node 生态里
存量巨大;现代写法是 ES6 模块,export/import36。两种导出:
- 默认导出(
export default):一个模块的「主打产品」,import 时起什么名都行; - 命名导出(
export单个成员):import 时必须用花括号,名字对得上36。
模块有一个贯穿全书的隐藏属性,原书用一个三文件实验演示:
模块在整个应用里只被求值一次,导出的是同一个共享实例——
importer.mjs 把共享对象 currentUser.age 改成 21,
importer2.mjs 再读,读到的就是 21,尽管两者「各自」import37。
另外,模块的根级函数和变量只属于模块自己,不会挂到 globalThis38——
这就是第 3 节「身份三」的来源,也是模块封装性的意义。
9. 作者的判断与证据
- 「永远优先
===」「默认const、避免var」「用性能优化过的标准数组方法 而不是自己造轮子」——这三条是作者明写的实践口径51324,也是行业主流共识。 - 「async/await 只是 Promise 的语法糖」是准确的机制性说法,不是比喻33。
- 关于
this的讲解全部是行为演示(浏览器里看window、Node 里看报错), 属于可复现证据;「箭头函数从父作用域继承 this」是语言规范事实11。
10. 边界与局限
- this 只讲了四种身份的一半:书里说「值取决于怎么调用」,但没展开「方法被
拆出来单独调用会丢 this」这个最日常的爆炸场景(如
setTimeout(obj.method)), 而箭头函数正是为它而生——两者之间的因果链要靠读者自己接上。 - Promise 的并发工具没讲:
Promise.all/Promise.allSettled是真实项目里 并发的标配,本章只字未提(本书后端章也几乎不依赖它们)。 - fetch 只在示例里露脸:错误处理只演示了
response.ok一条路, 网络层失败(请求根本没发出去)的 try/catch 没讲。 - 时间坐标:本章纯语言特性,不过时;提到的运行版本要求(顶层 await 需模块) 是 2026 年时点的合理基线。
判断(我们的,不是书里的): 这一章的组织暗合一个高效的教法: 异步部分不先讲概念,而是让你亲眼看到「顺序颠倒」 (先打印「Did I finish yet?」再等 3 秒),困惑先于解释出现,解释才有抓手。 数组五件套共用一个数组例子同理——五个方法的差异(第一个还是全部、 undefined 还是空数组)在并列对照里一句话就固化了。 如果错,会错在: 困惑先行对「先现象后原理」的初学者有效,但对已经 被回调坑过的读者,直接从 Promise 讲起可能更省时间——顺序的最优解 取决于读者已经踩过哪批坑,而书只能选一种排法。
11. 可带走的
var只有函数级作用域且会提升;let/const是块级。默认const,要重赋值才let。const锁的是「指向」,对象内容照改。this看调用方式:new→ 新实例;浏览器顶层 →window;模块顶层 → 不挂全局; 严格模式裸调用 →undefined。箭头函数没有自己的this。==偷偷转型("25" == 25为真);===不转型。引用类型比的是内存地址。- falsy 只有六个:
0、0n、""、null、undefined、NaN;其余皆 truthy。 - spread 合成新对象/新数组(浅拷贝);
Object.assign会改目标对象。 - 解构取字段、rest 收余参、?. 防空值炸、?? 只对 null/undefined 让步。
find回第一个或 undefined;filter回全部或空数组;map一一变换;reduce聚合成单值;some/every 回布尔。- 异步四代:回调 → setTimeout → Promise → async/await(后者只是前者的语法糖);
await等的是 Promise,不卡主线程。 - ES 模块只执行一次,导出对象是全应用共享的同一个实例。
12. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| TypeScript = JavaScript++ | Chapter 3: Building Better Apps with ES6+ Features | text/22-ch03-chapter-3-building-better-apps-with-es6-features.txt:11(搜「JavaScript++」) |
| var 函数级作用域与提升 | 同上 | text/22-ch03-chapter-3-building-better-apps-with-es6-features.txt:244(搜「scoping containers」) · text/22-ch03-chapter-3-building-better-apps-with-es6-features.txt:250(搜「hoisted」) |
| const 保护绑定而非内容 | 同上 | text/22-ch03-chapter-3-building-better-apps-with-es6-features.txt:304(搜「binding」) · text/22-ch03-chapter-3-building-better-apps-with-es6-features.txt:353(搜「same array object」) |
| 默认 const、避免 var | 同上 | text/22-ch03-chapter-3-building-better-apps-with-es6-features.txt:464(搜「Prefer using」) |
| this = 对象上下文 | Understanding the this keyword | text/24-fm-understanding-the-this-keyword.txt:11(搜「object context」) |
| 严格模式裸调用 this 为 undefined | Understanding the this keyword | text/24-fm-understanding-the-this-keyword.txt:437(搜「undefined」) |
| 箭头函数继承 this、没有 arguments | Learning about arrow functions | text/23-fm-learning-about-arrow-functions.txt:11(搜「lexical scope」) · text/23-fm-learning-about-arrow-functions.txt:19(搜「arguments」) |
| == 的类型强制 | Arrow function syntax and body styles | text/25-fm-arrow-function-syntax-and-body-styles.txt:424(搜「loose equality」) |
| 对象相等是内存比较 | Arrow function syntax and body styles | text/25-fm-arrow-function-syntax-and-body-styles.txt:326(搜「memory comparison」) |
| falsy 清单 | Arrow function syntax and body styles | text/25-fm-arrow-function-syntax-and-body-styles.txt:522(搜「falsy values」) |
| spread 浅拷贝;Object.assign 改目标 | Spread, Object.assign, and Array.concat | text/26-fm-spread-object-assign-and-array-concat.txt:35(搜「shallow copy」) · text/26-fm-spread-object-assign-and-array-concat.txt:53(搜「target is being modified」) |
| 解构与别名 | Destructuring | text/27-fm-destructuring.txt:27(搜「name: fullName」) |
| rest 参数 | Rest | text/28-fm-rest.txt:31(搜「last parameter」) |
| 可选链防 TypeError | Optional chaining | text/29-fm-optional-chaining.txt:166(搜「Cannot read properties」) |
| ?? 与 || 的区别 | Nullish coalescing and logical OR | text/30-fm-nullish-coalescing-and-logical-or.txt:84(搜「falsy comparisons」) |
| 数组方法五件套 | find function | text/31-fm-find-function.txt:82(搜「first one found」) · text/32-fm-filter-function.txt:81(搜「empty array」) · text/33-fm-map-function.txt:11(搜「React component creation」) · text/34-fm-reduce-function.txt:9(搜「aggregator」) · text/35-fm-some-and-every.txt:5(搜「functions are designed to test for certa」) |
| setTimeout 顺序颠倒 | Some and every | text/35-fm-some-and-every.txt:288(搜「allows whatever code」) |
| Promise 与语法糖 | Some and every | text/35-fm-some-and-every.txt:320(搜「notify」) · text/35-fm-some-and-every.txt:312(搜「syntactic sugar」) |
| fetch 示例 | Some and every | text/35-fm-some-and-every.txt:581(搜「pokeapi」) |
| 模块只执行一次、共享实例 | Some and every | text/35-fm-some-and-every.txt:873(搜「only evaluated once」) |
Footnotes
-
出处:「Chapter 3: Building Better Apps with ES6+ Features」第 11–17 段(
text/22-ch03-chapter-3-building-better-apps-with-es6-features.txt:11,搜「JavaScript++」)。 ↩ -
出处:「Chapter 3: Building Better Apps with ES6+ Features」第 240–268 段(
text/22-ch03-chapter-3-building-better-apps-with-es6-features.txt:250,搜「hoisted」;:268,搜「temporal dead zone」)。 ↩ -
出处:「Chapter 3: Building Better Apps with ES6+ Features」第 284–294 段与 377–395 段(
text/22-ch03-chapter-3-building-better-apps-with-es6-features.txt:286,搜「block-level scoping」;:385,搜「also block-scoped」)。 ↩ ↩2 -
出处:「Chapter 3: Building Better Apps with ES6+ Features」第 339–359 段(
text/22-ch03-chapter-3-building-better-apps-with-es6-features.txt:353,搜「same array object」)。 ↩ -
出处:「Chapter 3: Building Better Apps with ES6+ Features」第 462–486 段(
text/22-ch03-chapter-3-building-better-apps-with-es6-features.txt:464,搜「Prefer using」;:484,搜「avoid using」)。 ↩ ↩2 -
出处:「Understanding the this keyword」第 11–19 段(
text/24-fm-understanding-the-this-keyword.txt:13,搜「owner, at runtime」)。 ↩ -
出处:「Understanding the this keyword」第 84–120 段(
text/24-fm-understanding-the-this-keyword.txt:106,搜「, and this variable is what is being ref」)。 ↩ -
出处:「Understanding the this keyword」第 150–164 段与 295–321 段(
text/24-fm-understanding-the-this-keyword.txt:152,搜「window」;:299,搜「global context」)。 ↩ -
出处:「Understanding the this keyword」第 381–419 段(
text/24-fm-understanding-the-this-keyword.txt:397,搜「do not automatically get applied」)。 ↩ -
出处:「Understanding the this keyword」第 423–443 段(
text/24-fm-understanding-the-this-keyword.txt:425,搜「strict」;:437,搜「undefined」)。 ↩ -
出处:「Learning about arrow functions」第 3–19 段(
text/23-fm-learning-about-arrow-functions.txt:11,搜「lexical scope」;:13,搜「argument」)。 ↩ ↩2 -
出处:「Arrow function syntax and body styles」第 414–446 段(
text/25-fm-arrow-function-syntax-and-body-styles.txt:424,搜「loose equality」;:432,搜「type coercion」)。 ↩ -
出处:「Arrow function syntax and body styles」第 448–496 段(
text/25-fm-arrow-function-syntax-and-body-styles.txt:456,搜「strict equality operator」;:488,搜「always prefer」)。 ↩ ↩2 ↩3 -
出处:「Arrow function syntax and body styles」第 316–336 段(
text/25-fm-arrow-function-syntax-and-body-styles.txt:326,搜「memory comparison」)。 ↩ -
出处:「Arrow function syntax and body styles」第 213–245 段(
text/25-fm-arrow-function-syntax-and-body-styles.txt:221,搜「instanceof」)。 ↩ -
出处:「Arrow function syntax and body styles」第 199–211 段(
text/25-fm-arrow-function-syntax-and-body-styles.txt:209,搜「cannot be fixed」)。 ↩ -
出处:「Arrow function syntax and body styles」第 498–567 段(
text/25-fm-arrow-function-syntax-and-body-styles.txt:522,搜「falsy values」;:542,搜「"0"」)。 ↩ -
出处:「Spread, Object.assign, and Array.concat」第 29–37 段与 174–186 段(
text/26-fm-spread-object-assign-and-array-concat.txt:35,搜「shallow copy」;:184,搜「iterable」)。 ↩ ↩2 -
出处:「Spread, Object.assign, and Array.concat」第 43–53 段(
text/26-fm-spread-object-assign-and-array-concat.txt:51,搜「returning the target」;:53,搜「target is being modified」)。 ↩ -
出处:「Destructuring」第 3–9 段与 27–66 段(
text/27-fm-destructuring.txt:9,搜「very frequently used」;:27,搜「fullName」)。 ↩ -
出处:「Rest」第 3–35 段(
text/28-fm-rest.txt:31,搜「last parameter」)。 ↩ -
出处:「Optional chaining」第 164–190 段与 196–255 段(
text/29-fm-optional-chaining.txt:166,搜「Cannot read properties」;:182,搜「car?.wheels」)。 ↩ ↩2 -
出处:「Nullish coalescing and logical OR」第 36–94 段(
text/30-fm-nullish-coalescing-and-logical-or.txt:84,搜「falsy comparisons」)。 ↩ -
出处:「Nullish coalescing and logical OR」第 96–106 段(
text/30-fm-nullish-coalescing-and-logical-or.txt:102,搜「performance-optimized」)。 ↩ ↩2 -
出处:「find function」第 3–28 段与「filter function」第 3–11 段(
text/31-fm-find-function.txt:9,搜「first instance」;text/32-fm-filter-function.txt:11,搜「all items」)。 ↩ -
出处:「find function」第 84–90 段与「filter function」第 69–89 段(
text/31-fm-find-function.txt:90,搜「undefined」;text/32-fm-filter-function.txt:81,搜「empty array」)。 ↩ -
出处:「Reduce function」第 44–82 段(
text/34-fm-reduce-function.txt:54,搜「omit this second argument」;:80,搜「single value」)。 ↩ -
出处:「map function」第 3–13 段与 59 段(
text/33-fm-map-function.txt:11,搜「React component creation」;:59,搜「most commonly used」)。 ↩ -
出处:「Some and every」第 82–142 段(
text/35-fm-some-and-every.txt:130,搜「never waits」;:136,搜「harder to read」)。 ↩ -
出处:「Some and every」第 171–187 段(
text/35-fm-some-and-every.txt:185,搜「not asynchronous」)。 ↩ -
出处:「Some and every」第 270–296 段(
text/35-fm-some-and-every.txt:288,搜「allows whatever code」)。 ↩ -
出处:「Some and every」第 215–231 段(
text/35-fm-some-and-every.txt:227,搜「single-threaded」)。 ↩ -
出处:「Some and every」第 298–417 段(
text/35-fm-some-and-every.txt:312,搜「syntactic sugar」;:320,搜「indeterminate future time」)。 ↩ ↩2 ↩3 ↩4 -
出处:「Some and every」第 458–543 段(
text/35-fm-some-and-every.txt:464,搜「Promise」;:517,搜「wait on our behalf」)。 ↩ ↩2 -
出处:「Some and every」第 545–557 段(
text/35-fm-some-and-every.txt:545,搜「much shorter and easier to read」)。 ↩ -
出处:「Some and every」第 653–688 段与 703–788 段(
text/35-fm-some-and-every.txt:667,搜「CommonJS」;:731,搜「default export」)。 ↩ ↩2 -
出处:「Some and every」第 869–917 段(
text/35-fm-some-and-every.txt:873,搜「only evaluated once」)。 ↩ -
出处:「Some and every」第 919–933 段(
text/35-fm-some-and-every.txt:919,搜「scoped to the module」)。 ↩