跳转至

this 与四种调用 — 函数上下文的来历、失控与收场

这一章讲一件事,但讲透: this 这个关键字。它常被误传为「指向调用它的对象」, 真相更奇怪——它是函数的一个隐式参数,值由调用方式现场决定。 本章把四种调用方式逐个拆开,看 this 分别是什么;再看「解绑方法」这一类经典事故; 最后看两种收场手段:bind 和箭头函数。 读完你会明白 JavaScript 最著名的那句抱怨:「我只是把方法存进了变量,它怎么就报错了?」

1. this:一个看不见的参数

每调用一个函数,除了你写在括号里的实参,JavaScript 还悄悄塞进去一个参数,叫 this。 它指向「与本次调用相关联的对象」,所以有个别名:函数上下文(function context)。1

来自 Java/C# 的开发者觉得自己懂 this:在类的方法里,它不就是「我这个实例」吗? 现代 JavaScript 里最常见的用法确实如此——书里的 Ninja 类,this.#skillLevel++ 里的 this 就是那个练功的忍者实例。2

但原书马上按住了这个理解。this 在 JavaScript 里不是由函数定义的位置决定的,也不总是实例—— 它可以受调用方式影响。这一章的全部内容,就是这个「可以」到底怎么发生。

主线例子:一个计数器对象

为了让每个机制都有落点,全章用同一个场景走到底: 页面上有个按钮,一个对象 buttonObject 负责记点击次数,处理器是它的方法 clickHandler。 问题只有一个:把 clickHandler 交给浏览器时,this 还是不是 buttonObject?

  场景:buttonElement.addEventListener("click", ???)
      │
      ├─ ??? = buttonObject.clickHandler     ← 直接传:翻车(§3)
      ├─ ??? = buttonObject.clickHandler.bind(buttonObject)  ← 绑死:修复①(§4)
      ├─ ??? = () => buttonObject.clickHandler()             ← 包一层:修复②(§4)
      └─ 结论:连「存进变量」这个无害动作都会改变 this(§1.1)

1.1 经典事故:存进变量,行为变了

先看事故本身(书里的 Ninja 版):3

const ninja = new Ninja();
const trainNinja = ninja.train;   // 只是把它存进一个变量……
trainNinja();                     // 运行时报错:this 是 undefined

train 方法内部有一句 this.#skillLevel++。直接调 ninja.train() 一切正常; 可一旦先存进 trainNinja 再调,this 变成 undefined,句点属性访问立刻抛错。

原书特意强调:「重新赋值」本身不是病根。 看这个变体:4

const ninja = new Ninja();
const trainCopy = ninja.train;
ninja.trainCopy = trainCopy;      // 同一个函数,挂回 ninja 名下
ninja.trainCopy();                // 正常!

函数一个字没变,只是换了个挂的地方(此例绕过了 TypeScript 的类型检查,纯 JavaScript 合法)。 差别在哪?调用那行的写法。ninja.trainCopy() 是方法调用,trainNinja() 是普通调用—— 调用方式不同,this 就不同。这就是全章的总纲,下一节把四种方式钉死。

(工具提示:typescript-eslint 的 unbound-method 规则能静态抓出「把方法赋给变量」这类写法。5)

2. 四种调用方式:this 的四种来源

原书给的分类,完整且互斥——任何一个非箭头函数的调用,必属其一:6

调用方式 写法 this 是什么
普通函数调用 skulk() undefined(严格模式;非严格是全局对象)
方法调用 ninja.skulk() 点号前面的那个对象 ninja
构造器调用 new Ninja() 一个刚创建的空对象
call/apply 调用 skulk.call(ninja) 第一个实参:ninja

逐个走。

2.1 普通调用:孤零零的 ()

函数不作为方法、不陪 new、不走 call/apply,就是普通调用。ninja()、samurai()、 (function(){})()(IIFE 也算)都在此类。7

严格模式下 this 是 undefined。这是 ES5(2009)「strict mode(严格模式)」 修正的历史事故之一:老行为里,普通函数里的 this 是全局对象—— 于是 this.someProperty 静默访问到 undefined 的全局属性,程序莫名其妙地坏; 改成 undefined 后,这种错误当场炸、当场可见。原书称之为「fail in a clear, immediate way」, 并给出背景:TypeScript、Babel 默认都吐 "use strict",ESM 模块天然就是严格模式, 所以「this 是 undefined」才是现代代码的常态。8

两个例外(书内列出的):函数若被 bind 过,this 以绑定为准(§4);非严格模式下还是全局对象。

2.2 方法调用:点号前面是谁,this 就是谁

函数挂在对象的属性上、且通过「属性名 + 括号」调用,就是方法调用:

const ninja1 = { getMyThis: whatsMyContext };
ninja1.getMyThis();   // this === ninja1

实验(原书 Listing 6.2)干净地展示了同一函数的两面:

"use strict";
function whatsMyContext() { return this; }

whatsMyContext() === undefined          // 普通调用:this 消失
const ninja1 = { getMyThis: whatsMyContext };
ninja1.getMyThis() === ninja1           // 方法调用:this 是挂着的对象

同一个函数,前一秒 undefined,后一秒是 ninja1。this 是调用现场装配的,不是出厂自带的。9

两个陷阱(原书专门列出):10

  • 箭头函数和 bind 过的函数不适用这条规则——它们有自己的规则(§4);
  • 只有点号写法才算。ninja1["getMyThis"]() 这种方括号写法,在书里被划成「行为如同普通调用」 的一类——它拿不到挂载对象作 this。(此处原书的表述比规范严格,工程上按「方法调用的识别以 调用表达式的形态为准」理解即可;日常代码中点号是绝对主流。)

2.3 构造器调用:new 的魔法三步

任何用 function 声明或表达式定义的函数(箭头函数、bind 产物除外),前面加 new 就变成了构造器。 new Ninja() 触发的是一套固定动作,书里画成了三步(我们照抄):11

  1. 造一个空对象 {}
  2. 把它作为 this 塞给函数
  3. 函数体往 this 上挂东西;结束时,除非函数显式 return 了另一个对象,否则返回这个 this

用 Ninja 构造器带数走一遍(书里的例子):12

function Ninja() {
  this.skulk = function () { return this; };   // 给刚造出的空对象挂一个方法
}
const ninja1 = new Ninja();
const ninja2 = new Ninja();
ninja1.skulk() === ninja1   // ✓ 每个实例的 skulk,指回自己
ninja2.skulk() === ninja2   // ✓ 两个实例互不相干

第 1 次 new Ninja():空对象造出 → this 指向它 → 挂 skulk → 返回它,ninja1 拿到这个对象。 第 2 次同理,ninja2 是另一个对象。skulk 里的 this 永远指回「造出自己的那次调用」的对象。

一个反直觉细节(书里单独立框):构造器里显式 return 的行为很怪——13

  • return 一个对象:返回它,丢掉 this(构造出的对象被弃);
  • return 一个原始值(数字、字符串):return 被无视,照常返回 this。

工程结论:构造器里别写 return。

另外两件实用小物:new.target(ES2015)能可靠分辨「我是不是被 new 调的」 (new.target !== undefined);老库时代曾流行「加不加 new 都行」的函数(如 Object()), 如今被 TypeScript 和 ESLint 视为该退烧的怪癖——现代约定:构造器写进 class,类名大写。14

2.4 call 与 apply:调用方直接指定 this

前三种方式都是「隐式」决定 this;最后一种是显式的:每个函数自带 call 和 apply 两个方法 (函数是一等对象,当然能挂方法),第一个参数就是「本次调用的 this」,之后的参数照常:15

function juggle(...numbers) {
  let result = 0;
  for (const number of numbers) result += number;
  this.result = result;          // 往「上下文对象」上写结果
}
const ninja1 = { result: 0 };
const ninja2 = { result: 0 };

juggle.apply(ninja1, [1, 2, 3, 4]);  // this=ninja1,实参来自数组 → ninja1.result === 10
juggle.call(ninja2, 5, 6, 7, 8);     // this=ninja2,实参逐个列 → ninja2.result === 26

唯一差别:apply 的实参打一个数组给,call 的实参逐个列。带数走一遍: juggle.apply(ninja1, [1,2,3,4]) 收到 this=ninja1、numbers=[1,2,3,4],求和 10,写进 ninja1.result; juggle.call(ninja2, 5,6,7,8) 求和 26,写进 ninja2。16

历史注脚:apply 当年有一个绝技:把数组「摊开」当实参列表用(Math.max.apply(null, [3,7,2]))。 今天这个功能归 展开运算符(spread):Math.max(...numbers)。加上 bind 的普及, call/apply 已不如当年常见——但「从别的对象借一个方法来用」时,它们仍是正解。17

3. 解绑方法事故:回到那个计数器

现在把 §1.1 的原理真正讲穿。主线例子的「翻车版」:18

const buttonObject = {
  clickCount: 0,
  clickHandler: function () {
    this.clickCount++;
    appendToEventList(`Click count: ${this.clickCount}`);
  },
};
buttonElement.addEventListener("click", buttonObject.clickHandler);   // 解绑的方法

点下按钮,页面打印 Click count: NaN,而且每次都 NaN。机制链:

  addEventListener 收到的,只是 clickHandler 这个「函数值」
      │  (传参只传值——函数是值,值只有一份,不带「我从哪来」的标签)
      ▼
  点击发生,浏览器以「普通函数」方式回调它:this = undefined(严格模式)
      │
      ▼
  undefined++ → NaN;模板串里 NaN 上卡着——于是 "Click count: NaN"

原书把 buttonObject.clickHandler 这种「脱离了原对象、还指望 this 管用的」函数叫 unbound method(未绑定方法):「你把方法传给一个函数,那个函数只拿到方法本身, 没有任何途径知道它曾挂在哪个对象上。」19

修复方案有两个,正好是这一章的两种「收场」。

4. 两种收场:bind 与箭头函数

4.1 bind:造一个 this 钉死的新函数

每个函数还有第三个方法 bind。它和 call 的关键区别:不调用,而是造一个新函数—— 新函数体与原函数相同,但 this 永远是被绑定的那个对象,无论它之后怎么被调用。20

buttonElement.addEventListener(
  "click",
  buttonObject.clickHandler.bind(buttonObject)   // 修复:点击打印 1、2、3……
);

点击三次,输出 1、2、3——bound 函数里 this.clickCount++ 永远改的是 buttonObject 的。 两个要点:21

  • bind 不动原函数。每次调用 bind 都产出一个新函数——以为「bind 是在给原函数上锁」是常见误会;
  • bind 过的函数,再方法调用、再 call/apply 也改不了它的 this(规则冲突时绑定赢,普通调用那次除外…… 其实普通调用时绑定的 this 照样生效——绑定对一切调用方式生效,这正是它的意义)。

bind 曾风行到一种程度:构造器里把每个方法都替换成绑定副本。ES2022 的类字段(class field) 语法让这件事有了原生写法(注意是 = 不是 :):22

class Button {
  #clickCount = 0;
  clickHandler = () => {              // 类字段:每个实例一份,绑死在自己实例上
    this.#clickCount++;
    appendToEventList(`Click count: ${this.clickCount}`);
  };
}

类字段的 clickHandler 技术上是一种特殊的箭头函数——这就接到下一种收场。

4.2 箭头函数:连「自己的 this」都不要

箭头函数对 this 问题的回答是釜底抽薪:它没有自己的 this。 函数体内的 this,就是它定义处外层的那个 this——术语叫词法上下文(lexical context)。23

在 new 的三步里看最直接。改造前的老姿势(箭头函数之前,开发者手工模拟词法 this):

function Ninja() {
  const _this = this;                    // 抓住构造时的 this,存进普通变量
  this.skulk = function () { return _this; };   // 方法永远用 _this,免疫调用方式
}

箭头函数干的就是这件事,而且是语法保证:

function Ninja() {
  this.skulk = () => { return this; };   // this = 定义处外层 = 构造时的实例,板上钉钉
}

两条保证(原书明列,都是「免疫」级的):24

  • 用 call/apply 调箭头函数?this 不受影响;
  • 对箭头函数 bind?新函数的 this 和原函数一样(绑不动)。

TypeScript 也从中受益:它能可靠推断箭头函数里 this 的类型(定义处定死了), 普通函数则不行——这是工程上偏爱箭头函数的一个隐性理由。25

但有一个著名的坑,原书单独警告:对象字面量里别这么写。26

const buttonObject = {
  clickCount: 0,
  clickHandler: () => {       // 别这么干!
    this.clickCount++;        // this 是「定义处外层」的 this——也就是外层的函数(或模块)的 this
    ...
  },
};

方法定义在对象字面量里,而普通对象不产生词法上下文——只有函数(以及 ES2022 起,类)产生。 所以这里的 this 抓的是外层那个函数的 this,跟 buttonObject 毫无关系。 普通对象上没有定义「绑定方法」的原生途径。

这个坑的正确绕法,也正是主线例子的第二修复:包一层。

buttonElement.addEventListener("click", () => { buttonObject.clickHandler(); });

箭头函数在这里的价值不是 this(它根本没用到外层的 this), 而是「把一个调用原样转交」的简洁——想稳妥传参,用 rest 参数透传: (...args) => { buttonObject.clickHandler(...args); }。27

5. 一张总表收尾

四种调用 × 箭头函数、绑定函数,合起来是本章的全部规则:28

函数种类 普通调用 方法调用 构造器调用 call/apply
普通函数(function 声明/表达式) undefined(严格) 挂着的对象 新对象 第一参数
bind 产物 绑定的对象 绑定的对象 (不用作构造器) 绑定的对象
箭头函数 定义处外层的 this 同上 不能当构造器 同上(call/apply 改不动)
类字段 所在实例 所在实例 — 所在实例

记不住的读者只需记一句:「this 看调用」只对 function 函数成立;箭头函数看定义,bind 看绑定。

6. 作者的判断与证据

可亲手验证的机制(原书全部给了 assert):

  • 同一函数在普通/方法调用下 this 不同(Listing 6.2 的两个 assert);
  • new 三步中「return 对象覆盖 this、return 原始值被无视」的行为;
  • bind 产物 this 不受调用方式影响;箭头函数 this 不受 call/apply/bind 影响。29

作者立场(当立场读):

  • 「构造器写进 class、类名大写、普通函数别 new」被说成现代约定——是社区规范而非语言强制;
  • 「React 式事件处理器该用类字段或包箭头」是作者推荐的修复姿势,等价方案很多;
  • 非严格模式下 this=全局对象,被定性为「language mistakes」之一——这是工程共识,但规范层面它是被冻结的历史行为,不会改了。

7. 边界与局限

  • 方括号调用那条(ninja1"getMyThis" 行为如普通调用)是原书的实验性表述,与规范文本存在出入; 读者只需掌握「点号是方法调用的识别特征」这一安全口径,异常案例留给规范。
  • bind 的部分应用(只绑 this、后面的实参也预填:fn.bind(obj, 1))本章未展开,原书没有覆盖。
  • 类字段为何是「特殊箭头函数」(实例字段 vs 原型方法的内存代价)要等第 9 章。
  • 事件处理器里 this 还会受浏览器「回调时指定 this」的老行为影响(HTML 属性式 onclick), 原书只讲了 addEventListener 的正统路径。

8. 可带走的(合上这一章,你应该有这八条)

  1. this 是隐式参数,值由调用方式现场决定——「指向调用它的对象」只对方法调用成立。
  2. 四种调用四种 this:普通调用 undefined(严格)、方法调用是点号前的对象、构造器调用是新建空对象、 call/apply 是第一参数。
  3. 存方法进变量 = 解绑;传参只传值,函数值不携带「我从哪来」。
  4. new 的三步:造空对象 → 当 this 塞入 → 返回它(return 对象会顶替、return 原始值被无视)。
  5. call 逐个列实参,apply 打一个数组;数组摊开的需求如今归展开运算符 ...。
  6. bind 每次造新函数、不动原函数,绑定的 this 对一切调用方式生效。
  7. 箭头函数没有自己的 this:取定义处外层的 this,call/apply/bind 都改不动;不能当构造器。
  8. 普通对象字面量不产生词法上下文——对象里写箭头函数方法是个坑;想转交调用,包一层箭头函数。

10. 深读一:this 机制的设计考古,与四条规则的边界

10.1 「隐式参数」设计的历史账

书里在 strict mode 一节给了一段考古,值得单独成段:ES5(2009)之前, 普通函数里的 this 是全局对象——于是 this.someProperty 默默读到的是 全局上的同名属性(undefined),程序坏得无声无息。strict mode 把它改成 undefined,错误当场抛、位置当场可见。原书称这是「fail in a clear, immediate way」, 并补了一句现实:这个「错误」永远不会被规范改回去了——非严格行为已被冻结, 能改的是「让默认成为严格」:TypeScript 和 Babel 默认吐 "use strict", ESM 模块天然严格,所以今天的新代码几乎活在严格世界里。1

由此得到本章规则的一个更精确版本:

「this 看调用」这句话,只在非箭头、非绑定的 function 函数上成立; 箭头函数看定义,bind 产物看绑定。三种函数、三套规则,混用前先问「它是哪种函数」。2

10.2 方法调用的「点号识别」,一个值得会心的细节

书里的方法调用陷阱(方括号写法)揭示了 JavaScript 解析的一个基本面: 方法调用的身份由「调用表达式的形态」决定,不由函数挂在哪个对象上决定。 ninja1.getMyThis() 和 ninja1["getMyThis"]() 在执行语义上是一回事, 原书把后者列为「行为如同普通调用」的边界案例,是为了强调「点号识别」的教学点。 日常代码里点号是绝对主流,这个细节知道即可;真正要刻进肌肉的是: 换调用形态(存变量、传参数、包一层)之前,先想 this 会不会跟着变。3

11. 深读二:构造器机制,从「普通函数」到「类」的完整演化

11.1 「任何函数都能 new」的来龙去脉

书里有一个 JavaScript 独有的怪癖值得正视:凡 function 声明/表达式,前面都能加 new—— 哪怕那个函数根本没打算当构造器。new whatsMyContext() 造出一个对象, 它的原型是 whatsMyContext.prototype,然后被丢弃。这带来两个历史现象:4

  • 老库的「可加可不加 new」设计:Object() 不加 new 和加 new 等价; 库为了迁就用户,让函数内部自己判断「我是不是被 new 调的」;
  • ES2015 的 new.target:可靠的自判手段——new.target !== undefined 即为构造调用。 书里的评价很干脆:这个机制存在,但你应该用 class, 让「我是不是构造器」这个问题干脆不存在。

11.2 构造器里的 return,两条怪规则的机制解释

「return 对象则顶替 this、return 原始值被无视」为什么长成这样?机制上说得通: new 的语义是「构造并返回一个对象」,如果函数体 return 了一个对象, 那它就是这个操作的结果——语义自洽;return 一个数字则不是「对象」, 与 new 的返回类型承诺冲突,于是被无视、回落到 this。 但「说得通」不等于「该用」:原书的建议直白——构造器里别写 return; 非写不可时,用 TypeScript 的 construct signature 告诉检查器你的意图。5

12. 深读三:bind 与箭头函数的对照使用学

12.1 bind 的三个易错点

bind 用起来简单,坑在三个细节上(书里占了两个,补一个使用学):6

  1. bind 不动原函数——每次都产新函数。fn.bind(o) === fn 是 false; 指望「绑定原函数」是常见误会,忘了接返回值等于没绑;
  2. 绑定是终身的——bind 产物的 this 对一切调用方式免疫(普通调、方法调、call/apply), 这正是它的用途,也是它的代价:你放弃了让调用方指定 this 的灵活性;
  3. 类字段是 bind 的原生替代——ES2022 的 clickHandler = () => {...} 在语义上等价于「构造时给每个实例绑一份箭头函数」,区别只在写法。 书里注了一句「technically a special kind of arrow function」—— 类字段与箭头函数共享同一套 this 规则。

12.2 箭头函数的两个「免疫」与一个大坑,再确认

两个免疫(书里明列):call/apply 改不动它的 this;bind 也绑不动 (产物和原函数的 this 一模一样)。一个大坑(书里单列警告):对象字面量不产生词法上下文—— 只有函数(以及 ES2022 起的类)产生。所以对象里写 clickHandler: () => { this.clickCount++ }, this 抓的是外层的 this(模块里是 undefined),跟这个对象毫无关系。7

「包一层」修复之所以推荐,是因为它同时满足三件事: 箭头函数捕获定义处的 this(它本来就不需要)、把参数原样转交 (用 (...args) 透传防漏)、以及不改动原方法的 this 语义 (方法还是方法,每次以 buttonObject.clickHandler() 的方法形态调用)。8

13. 深读四:一张决策表收尾

遇到 this 问题时的诊断流程:

  这个函数的 this 不对?
      │
      ├─ 它是箭头函数/类字段? ──是──► 去它的定义处外层找 this,调用方式无关
      ├─ 它是 bind 产物?      ──是──► this 恒为绑定的对象
      └─ 普通 function? ──► 看调用形态:
            点号调用 → this = 点号前的对象
            new 调用 → this = 新建的空对象
            call/apply → this = 第一参数
            裸调用   → this = undefined(严格)

四行之外的全部怪现象(事件回调里 this 变成按钮、setTimeout 里 this 变全局), 都是「你以为它是方法调用、其实是裸调用」的变体。学会这一个诊断流程, JavaScript 最高产的 this bug 家族就对你失业了。9

14. 原文地图

主题 原书章 原文位置
this 作为隐式参数/函数上下文 Chapter 6: Functions for the journeyman: understanding function invocation text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:45(搜「implicit parameter」)
Ninja 类 this 的正常用法 同上 :53(搜「Listing 6.1」)
存进变量翻车 同上 :87(搜「trainNinja」)
「赋值不是病根」变体 同上 :105(搜「the reassignment isn't the problem」)
四种调用清单 同上 :118(搜「four ways」)
普通调用的 this 与严格模式 同上 :160(搜「standalone function」) · :187(搜「strict mode」)
方法调用实验 同上 :201(搜「Listing 6.2」)
方法调用两陷阱 同上 :237(搜「traps with method invocation」)
构造器三步 同上 :285(搜「SUPERPOWERS OF CONSTRUCTORS」) · :316(搜「special actions」)
return 覆盖规则 同上 :330(搜「USING EXPLICIT RETURNS」)
new.target 与现代约定 同上 :350(搜「CHECKING IF A FUNCTION WAS CALLED AS A CONSTRUCTOR」)
call/apply 与 juggle 同上 :380(搜「apply and call methods」) · :395(搜「Listing 6.4」)
apply 的展开旧用途 同上 :428(搜「spread operator」)
按钮 NaN 事故 同上 :452(搜「Listing 6.5」) · :498(搜「unbound method」)
bind 修复 同上 :510(搜「Using the bind method」)
类字段语法 同上 :536(搜「ES2022 introduced」)
词法 this 与 _this 前身 同上 :554(搜「lexical context」) · :566(搜「_this」)
箭头函数免疫清单 同上 :587(搜「won't have any effect」)
对象字面量的坑 同上 :595(搜「ordinary objects don't create a lexical context」)
包一层修复 同上 text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:609(搜「wrap the entire」)
全章总结 同上 :629(搜「Summary」)

  1. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 187–200 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:187,搜「Strict mode was introduced」)。strict mode 修正 this 行为的历史与「fail in a clear, immediate way」的判词。 ↩↩

  2. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 176–180 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:178,搜「non-arrow function」)。普通调用 this=undefined 的规则与两个例外(bind、非严格)。 ↩↩

  3. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 237–245 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:244,搜「bracket notation」)。点号/方括号的方法调用识别讨论。 ↩↩

  4. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 261–279 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:274,搜「new whatsMyContext」)。任何函数可当构造器的演示。 ↩↩

  5. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 330–348 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:335,搜「anything other than a primitive」)。return 对象/原始值的两条规则与「best to avoid」的建议。 ↩↩

  6. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 510–551 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:533,搜「creates a new function every time」)。bind 的语义与「常见误会」原文。 ↩↩

  7. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 554–608 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:595,搜「don't create a lexical context」)。词法上下文的定义与普通对象不产生它的警告。 ↩↩

  8. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 609–626 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:622,搜「rest parameters syntax」)。包一层修复与 rest 参数透传建议。 ↩↩

  9. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 629–657 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:645,搜「influences the value」)。全章总结中四种调用与 this 的对应表。 ↩↩

  10. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 237–245 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:239,搜「Only functions defined with the function declaration」)。方法调用的两条陷阱(箭头/bind 例外;点号写法)。 ↩

  11. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 285–328 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:316,搜「A new empty object is created」)。new 三步与 Figure 6.1。 ↩

  12. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 285–304 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:293,搜「const ninja1 = new Ninja(); #B」)。Ninja 构造器与两个 ninja 实例的 assert。 ↩

  13. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 330–343 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:335,搜「anything other than a primitive」)。return 对象/原始值的两种命运与「best to avoid explicit returns」。 ↩

  14. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 350–377 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:362,搜「new.target」)。new.target 判定;「avoid the new keyword」的现代约定见第 375–376 段。 ↩

  15. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 380–393 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:386,搜「Yes, we said methods of functions. As first-class」)。call/apply 的定义与第一参数语义。 ↩

  16. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 395–419 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:395,搜「Listing 6.4」)。juggle 带数例子:10 与 26 两个 assert 原文。 ↩

  17. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 428–445 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:434,搜「spread operator」)。apply 的旧绝技与展开运算符接管;「borrow a method」的遗留用途见第 443–444 段。 ↩

  18. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 452–509 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:455,搜「Listing 6.5」)。按钮事故完整代码与 NaN 输出(第 501–503 段)。 ↩

  19. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 505–508 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:506,搜「unbound method」)。「It has no way of knowing what object it was attached to」——解绑方法定义的原文。 ↩

  20. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 510–534 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:519,搜「bind(buttonObject)」)。bind 的修复代码与「regardless of the way we invoke it」。 ↩

  21. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 533–549 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:533,搜「creates a new function every time」)。「bind 每次造新函数、常见误会」的原文强调。 ↩

  22. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 535–552 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:540,搜「class Button」)。类字段语法与「special kind of arrow function」的定性(第 550–551 段)。 ↩

  23. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 554–564 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:560,搜「lexical context」)。词法上下文的定义与箭头函数「没有自己的 this」。 ↩

  24. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 585–593 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:586,搜「won't have any effect」)。call/apply 与 bind 对箭头函数无效的两条保证。 ↩

  25. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 593–594 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:593,搜「This feature of arrow functions is quite powerful」)。TypeScript 可推断箭头函数 this 类型的隐性好处。 ↩

  26. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 595–608 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:595,搜「ordinary objects don't create a lexical context」)。对象字面量箭头函数陷阱与「no way to directly define a bound method on a normal JavaScript object」。 ↩

  27. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 609–626 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:609,搜「wrap the entire」)。包一层修复与 rest 参数透传建议(第 622–624 段)。 ↩

  28. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 629–657 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:645,搜「influences the value」)。全章总结里四种调用与 this 对应关系的原文表述。 ↩

  29. 出处:「Chapter 6: Functions for the journeyman: understanding function invocation」第 296–303 段(text/09-ch06-chapter-6-functions-for-the-journeyman-understan.txt:297,搜「The 1st ninja is skulking」);与 :587(搜「won't have any effect」)。全部 assert 可复现。 ↩