- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
本篇技术文章围绕现代 JavaScript 教程(Modern JavaScript Tutorial)数组章节中的经典习题「Calling in an array context」展开,系统讲解arr[2]()这种「以数组为宿主对象、以数字索引为方法名」的调用语法为何能正常工作、this为何指向数组本身,并深入到底层的 Reference Type 机制,帮助读者彻底理解 JavaScript 中obj.method()与obj["method"]()的等价关系及this的动态绑定规则。读完本文,你将能准确预测数组元素为函数时的调用结果,并能够解释(method = obj.go)()、(obj.go || obj.stop)()等派生表达式丢失this的原因。
题目回顾:数组里的函数,通过下标调用
本习题位于 1-js/05-data-types/04-array/3-call-array-this/task.md,原始题目如下:
let arr = ["a", "b"]; arr.push(function() { alert( this ); }); arr[2](); // ?题目问的是:最后一行arr[2]()的结果是什么?为什么?
答案:this 指向数组,输出 a,b,function(){...}
答案(见 1-js/05-data-types/04-array/3-call-array-this/solution.md)非常简洁:
调用
arr[2]()在语法上就是经典的obj[method]()—— 其中obj的角色由arr扮演,method的角色由数字索引2扮演。
因此,arr[2]所引用的函数是以「对象方法」的方式被调用的,它自然而然地收到this指向对象arr本身,最终输出整个数组:
let arr = ["a", "b"]; arr.push(function() { alert( this ); }) arr[2](); // a,b,function(){...}运行结果之所以是字符串a,b,function(){...},是因为此刻数组一共有 3 个值:初始的两个字符串"a"、"b",再加上末尾 push 进去的那个函数。alert(this)在展示对象时会把数组通过其内置的toString转换成逗号分隔的列表(详见下文「输出格式说明」一节)。
关键前提:数组本质上是对象,可以存放任意类型的值
要理解这个结论,必须先回到数组本身的性质。在 1-js/05-data-types/04-array/article.md 中明确指出:
- 数组是一种特殊形态的对象,
arr[0]这种方括号访问本质上就是对象语法obj[key],其中arr是对象、数字充当键名; - 数组可以存放任意类型的元素,包括对象和函数;
- 数组中已有的元素个数由
length属性记录,push会把新元素追加到末尾,length自动 +1。
文章中的原型示例甚至直接演示了「通过索引取出函数并执行」:
// mix of values let arr = [ 'Apple', { name: 'John' }, true, function() { alert('hello'); } ]; // get the object at index 1 and then show its name alert( arr[1].name ); // John // get the function at index 3 and run it arr[3](); // hello回到本题:arr.push(function(){...})执行后,函数被追加到索引2的位置,数组变为["a", "b", function],length为 3。arr[2]取出的正是这个函数,arr[2]()则调用它。
核心机制:点语法与方括号语法在方法调用上等价
为什么arr[2]()中的this会是arr而不是undefined?关键在于 JavaScript 对「方法调用」语法的判定规则。
在 1-js/04-object-basics/04-object-methods/article.md 中有这样一条重要说明:当函数以obj.f()形式被调用时,调用期间this的值就是obj;至于obj是来自点语法还是方括号语法,并不重要。文章中的示例:
let user = { name: "John" }; let admin = { name: "Admin" }; function sayHi() { alert( this.name ); } // use the same function in two objects user.f = sayHi; admin.f = sayHi; // these calls have different this // "this" inside the function is the object "before the dot" user.f(); // John (this == user) admin.f(); // Admin (this == admin) admin['f'](); // Admin (dot or square brackets access the method – doesn't matter)注意最后一行admin['f']():用方括号['f']访问并调用方法,this依然是admin。把「属性名是字符串'f'」换成「属性名是数字2」,就得到本题的arr[2]()——两者在语法层面完全同构。
也就是说,this的绑定不取决于方法是怎么被「取」出来的,而取决于它是否紧跟调用括号()且构成了成员访问表达式。arr[2]()满足这一条件,因此this === arr。
底层原理:Reference Type 如何传递 this
this能够被正确传递,其底层秘密在于 ECMAScript 规范中的 Reference Type(引用类型)机制。这部分内容在 1-js/99-js-misc/04-reference-type/article.md 中有专门讲解。
一个obj.method()语句在引擎内部被拆成两步:
- 点
.(或方括号[])取出属性obj.method; - 括号
()立即执行它。
问题在于:信息(对象是谁、方法叫什么)如何从第一步传递到第二步?如果拆成两行,this就会丢失:
let user = { name: "John", hi() { alert(this.name); } }; // split getting and calling the method in two lines let hi = user.hi; hi(); // Error, because this is undefined为了让user.hi()正常工作,JavaScript 使用了一个技巧:点(或方括号)返回的并不是普通的函数值,而是一个特殊的规范类型值——Reference Type。它是一个三元组合:
base:宿主对象;name:属性名;strict:当前是否处于严格模式。
例如严格模式下读取user.hi,得到的是内部值(user, "hi", true)。当()直接作用在这样一个 Reference Type 值上时,调用方拥有宿主对象与属性名的完整信息,就能把this正确设置为user。
任何其他操作(赋值=、逻辑或||、条件运算符等)都会丢弃 Reference Type、只保留其中的普通函数值,于是后续调用便失去this。这正是本习题的对立面:arr[2]()是成员访问语法 + 紧跟调用括号,属于 Reference Type 被正确消费的情形,所以this完整保留。
边界对照:哪些写法会丢失 this
理解了 Reference Type 后,可以对照 1-js/99-js-misc/04-reference-type/3-why-this/task.md 中的习题,把「数组上下文」扩展到一般对象,体会写法差异:
let obj, method; obj = { go: function() { alert(this); } }; obj.go(); // (1) [object Object] (obj.go)(); // (2) [object Object] (method = obj.go)(); // (3) undefined (obj.go || obj.stop)(); // (4) undefined对应解释见 1-js/99-js-misc/04-reference-type/3-why-this/solution.md:
- 调用
(1)是常规对象方法调用,this为obj; - 调用
(2)中的括号并不改变运算顺序——点访问依然先发生,(obj.go)()与obj.go()等价; - 调用
(3)中括号左侧是表达式method = obj.go,赋值操作把 Reference Type 退化成了普通函数值,随后( )以无宿主的方式调用,this为undefined; - 调用
(4)同理,obj.go || obj.stop中的||运算同样丢弃了宿主信息。
同理可对照 1-js/99-js-misc/04-reference-type/2-check-syntax/task.md 中的(user.go)()——由于括号不改变「点先求值、括号直接调用」的顺序,它依然能正常输出"John";但若把赋值、||等运算插入括号与点之间,this就会丢失。
把这一规律映射回本题:arr[2]()左侧是纯粹的成员访问arr[2]且紧跟(),没有任何中间运算介入,所以等价于arr.2风格的方法调用(尽管数字键名无法用点语法书写),this === arr是必然结果。
输出格式说明:数组的 toString 与 alert
alert( this )最终显示a,b,function(){...}的原因,同样在 1-js/05-data-types/04-array/article.md 的「toString」一节有交代:数组实现了自己的toString,返回元素以逗号分隔的字符串。
let arr = [1, 2, 3]; alert( arr ); // 1,2,3 alert( String(arr) === '1,2,3' ); // true因此本题中this(即["a", "b", function(){...}])被alert展示时,会先被转成"a,b,function(){...}"——函数元素以其源码文本形式参与拼接。
实战延伸:函数数组的应用模式
本习题揭示的「函数可以作为数组元素、并通过索引以方法形式调用」的特性,在实际开发中有不少应用场景:
1. 回调注册表 / 命令队列
const tasks = []; tasks.push(() => console.log("task A")); tasks.push(() => console.log("task B")); // 顺序执行注册的每个任务 for (let i = 0; i < tasks.length; i++) { tasks[i](); // this === tasks }2. 策略分派表
const actions = { 0: () => "left", 1: () => "right" }; function move(dir) { return actions[dir](); // 用动态索引调用,this === actions }注意:一旦把函数从宿主上「拆下来」再调用,this就会丢失,例如const fn = tasks[0]; fn();中的this为undefined(严格模式)。需要保留宿主时,应使用tasks[0].call(tasks)、tasks[0].apply(tasks)或bind(参考 1-js/06-advanced-functions/10-bind/article.md 中的解法 2)。
3. 与箭头函数的对比
若数组元素是箭头函数,则它没有自己的this,无论怎么调用都会从外层词法作用域取this(见 1-js/04-object-basics/04-object-methods/article.md 中「Arrow functions have no this」一节)。因此本题若把function(){...}换成() => {...},alert(this)显示的就是外层作用域的this,而非数组本身——这也是面试中常见的变体追问。
小结
arr[2]()是obj[method]()的数组形态:数组充当宿主对象、数字索引充当属性名,this指向数组本身,输出a,b,function(){...};- 数组本质是对象,可以存放函数等任意类型值,
push追加后length自动更新; - 点语法与方括号语法在方法调用上等价:只要成员访问紧跟
(),this就能正确绑定; - 底层机制是 Reference Type
(base, name, strict),它把宿主信息从属性访问传递给调用括号;赋值、||、条件运算符等中间操作会丢弃它,导致this丢失; alert(数组)经由数组的toString输出逗号分隔的元素列表。
本习题是理解 JavaScriptthis动态绑定的重要跳板,建议结合 数组章节、对象方法与 this 以及 Reference Type 专题 三篇材料通读,即可构建完整的知识闭环。
- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
相关推荐
Claude Code 仓库实战:用 /dedupe 命令与多 Agent 并行协作自动发现 GitHub 重复 Issue
Claude Code 仓库实战:用 /dedupe 命令与多 Agent 并行协作自动发现 GitHub 重复 Issue Claude Code 是运行在终
文档/教程前端Modern JavaScript Tutorial 方法链式调用实战:理解 return this 的魔力
Modern JavaScript Tutorial 方法链式调用实战:理解 return this 的魔力 导读 在 Modern JavaScript Tu
文档/教程前端JavaScript 对象方法与 this 关键字详解(Modern JavaScript Tutorial 实战篇)
JavaScript 对象方法与 this 关键字详解(Modern JavaScript Tutorial 实战篇) 导读 在 JavaScript 中,对象
文档/教程前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考