JavaScript 核心原理解析
周爱民
《JavaScript 语言精髓与编程实践》作者,南潮科技(Ruff)首席架构师
32699 人已学习
新⼈⾸单¥59
登录后,你可以任选3讲全文学习
课程目录
已完结/共 28 讲
开篇词 (1讲)
JavaScript 核心原理解析
15
15
1.0x
00:00/00:00
登录|注册

03 | a.x = a = {n:2}:一道被无数人无数次地解释过的经典面试题

知识回顾
《JavaScript权威指南》的解释
连续赋值表达式
主题:连续赋值表达式的深入理解
参考文章标题:十年来让无数人折戟沉沙的JavaScript经典面试题
作者:周爱民
JavaScript经典面试题知识关系脑图

该思维导图由 AI 生成,仅供参考

你好,我是周爱民。
在前端的历史中,有很多人都曾经因为同一道面试题而彻夜不眠。这道题出现在 9 年之前,它的提出者“蔡 mc(蔡美纯)”曾是 JQuery 的提交者之一,如今已经隐去多年,不复现身于前端。然而这道经典面试题仍然多年挂于各大论坛,被众多后来者一遍又一遍地分析。
在 2010 年 10 月,Snandy于 iteye/cnblogs 上发起对这个话题的讨论之后,淘宝的玉伯(lifesinger)也随即成为这个问题早期的讨论者之一,并写了一篇“a.x = a = { }, 深入理解赋值表达式”来专门讨论它。再后来,随着它在各种面试题集中频繁出现,这个问题也就顺利登上了知乎,成为一桩很有历史的悬案。
蔡 mc 最初提出这个问题时用的标题是“赋值运算符:"=", 写了 10 年 javascript 未必全了解的"="”,原本的示例代码如下:
var c = {};
c.a = c = [];
alert(c.a); //c.a是什么?
蔡 mc 是在阅读 JQuery 代码的过程中发现了这一使用模式:
elemData = {}
...
elemData.events = elemData = function(){};
elemData.events = {};
并质疑,为什么elemData.events需要连续两次赋值。而 Snandy 在转述的时候,换了一个更经典、更有迷惑性的示例:
var a = {n:1};
a.x = a = {n:2};
alert(a.x); // --> undefined
Okay,这就是今天的主题。
接下来,我就为你解释一下,为什么在第二行代码之后a.x成了 undefined 值。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

JavaScript经典面试题“a.x = a = { }”一直困扰着无数前端开发者。文章深入探讨了声明语句和赋值表达式的不同之处,强调了在JavaScript中一切都是表达式,一切都是运算的特性。通过对《JavaScript权威指南》中的解释,阐述了赋值表达式的执行顺序和变量作为表达式的计算过程。作者指出了“语句与表达式”的不同,强调了在声明语句中变量名位置上不可能是一个表达式。文章通过深入的技术分析,解释了这个经典面试题的执行过程和相关概念,为读者提供了清晰的认识和理解。文章还提供了复习题,帮助读者巩固所学知识。整体而言,本文通过深入的技术分析,为读者解析了这个经典面试题的执行过程和相关概念,为读者提供了清晰的认识和理解。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《JavaScript 核心原理解析》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(59)

  • 最新
  • 精选
  • blacknhole
    从内容上其实已经说清楚了,不过在内容表达上还是会让人产生困惑,我觉得问题是出在“当前上下文中的那个变量a”和“原始的变量a”这样的表述方式上。或许如下表述在语意上会更加清晰: 1,这里其实只有一个变量,就是a,不存在那个变量a和这个变量a之分,有分别的其实是变量a的值,即“变量a过去的值”和“变量a现在的值”。 2,当发生第一次赋值时,“左操作数a作为一个引用被覆盖”,此时变量a产生了新的值。 3,第二次赋值时,“整个语句行的最左侧‘空悬’了一个已经求值过的‘a.x’”,这是一个表达式结果,这个结果以及其中保留的“a”(即“变量a过去的值”)与变量a已经没有关系了,因为变量a已经有了新的值,即“变量a现在的值”。 4,第二次赋值其实是,在“变量a过去的值”那个对象上,创建一个新属性x,x的值为变量a的值,即“变量a现在的值”。 5,在第二次赋值后,因为“变量a过去的值”那个对象已经不再被任何变量持有,所以它已经无法被访问到了,它“跑丢了”。 是这样吧?

    作者回复: 赞的!就是这个意思。呵呵~

    2019-11-15
    16
    107
  • 青史成灰
    老师上面引用《JavaScript权威指南》中说“JavaScript总是严格按照从左到右的顺序计算表达式”,那为什么下文的2次赋值操作`a.x = a = {n:2}`,是先赋值`a={n:2}`,然后才是`a.x = a`呢

    作者回复: 这个顺序是这样来读的(你仔细看看顺序是不是从左至右): 第一次 ====== a.x = a = {n:2} ^1 ^2 第二次 ====== a = { n: 2 } ^3 ^4 第三次 ====== { n: 2 } ^5 ^6 第四次(以下求值然后回传) ====== 求值传回(4) @4 <= ^5, ^6 第五次 ====== 求值回传(3) @3 = (^4 <= ^5, ^6) 第六次 ====== 求值回传(2) a = @3 = (^4 <= ^5, ^6) 第七次 ====== 求值回传(1) a.x = a = @3 = ...

    2019-11-16
    4
    13
  • 新哥
    画个图最好说明问题了,a和ref 指向同一块内存地址,保存的数据是{n:1}; 执行第二行的时候,a下移指向新的内存地址,保存的数据是{n:2}; 且第一块内存空间 添加新的属性x,因为ref.x被赋值a,所以ref.x指向新的刚添加的那个地址,数据为{n:2}; 这样ref指向原始的内存地址,a指向新的内存地址;

    作者回复: 是的。谢谢~ ^^.

    2020-06-14
    12
  • 天方夜
    1. with ({x:100}) delete x 中 delete 删除的是对象的成员,即 property x; 2. (0, eval) 之中有一步逗号运算; 3. 表达式 (a.x) 的计算结果是 a 对象的属性 x 这个引用,所以可行; 4. with 只指定属性查找的优先级,所以 with 里面 x = 100 还是会泄漏到全局。

    作者回复: 第2个不太完整。不过总体满分😃 第二个涉及的问题到20讲才开讲呢^_^

    2019-11-18
    2
    11
  • 红白十万一只
    老师这题我看过别的文章,不过是与运算符优先级解释。 按照运算符优先级的思路: var a={n1} a.x=a={n:1} =的关联性是从右到左,优先级是3,赋值运算符的返回结果是右边的值 .(成员访问)的关联性是从右到左,优先级是19 a.x的赋值等于a={n:1},而a的赋值等于{n:1}。 按照顺序会先计算a={n:1}的值,但是a.x是成员访问优先级是19。 所以会先进行a.x的解析,解析结果就是变量a对象的引用(引用地址#001)并创建了a.x这个属性,引用被暂存。 这是表达式就是:#001.x=a={n:1} a={n:1}时修改了变量a(例#001)的引用地址为{n:1}(例#002)。 表达式就是#001.x={n:1}(例#002) 也就是#001这个引用地址中x的值被修改为了{n:1} #001这个引用地址的值也就是 { n:1, x:{n:1} } 但是这个引用已经没有任何变量、属性持有了 而变量a的值就是 {n:1} 关于这种解释有没有什么问题,麻烦老师解释一下。

    作者回复: 这种解释是对的。并且跟这一讲的解释是同义的。只是由于两个解释的侧重点不同,所以貌似有不同而已。 这个解释中也引入了一个#001来说明,这个在本讲中被称为“原始的a”,又或者说是“原始的a的一个引用”。其实都是相同的意思,你按照这种关联来对照着看,就明白了。但是本讲侧重于说明表达式和引用,所以是更强调基于“引用(规范类型)”的解释过程。 我刻意没有讨论优先级的问题。在课后的留言评论中提到过按优先级来演算的过程,但也不如你这里的细致。优先级是运算规则的很重要的组成部分,在设计表达式语法时也很重要,但是我们的课程并不特别关注这个部分,所以我是有意不从这个角度入手来讲的。

    2020-02-25
    2
    9
  • 授人以摸鱼
    所以我现在这么理解js中的“值”和“引用”这两个概念了: “引用”保存了两个信息:对象的地址,和要查询的属性名(字符串或symbol) “值”只保存了一个信息:原始值本身,或一个地址 从引用中获取值这个操作是惰性的,只有真正要使用值的时候才会执行getvalue

    作者回复: 是的。都对!赞!

    2019-11-24
    9
  • weineel
    老是您好:我理解的指针和引用是,指针是存储的地址,引用是存储的别名。 在 js 中的“引用”与传统语言中的“指针”有哪些根本性的区别。

    作者回复: 其实我早期也是这么理解的。好象大家理解事物的方式都差不多,就是从相似性出发,从差异性辨别。 但是我后来发现,与其如此,不如为新东西建个体系,然后在新体系中来看待这个新事物。这一下子就不同了。 以至于我现在对引用的认识,就不太依赖与比较或比拟。引用就是引用,它就是一个计算的结果,它存放结果中包括的那几个东西。它是一个数据结构,用在引擎层面来存储计算过程的中间信息,以及在连续计算中传递这些信息。

    2019-11-15
    3
    9
  • 🇧🇪 Hazard🇦🇷
    老师你好,我有一些关于词法环境规范的疑问,可能跟这一讲的内容有点出入,希望能得到您的解答。 1. 环境记录规范有 5 种,但是我没有找到什么资料去告诉我,什么声明会把标识符binding到具体哪个EnvironmentRecord中;还有就是全局变量会放在哪里? 2. ECMAScript中关于环境记录与标识符喜欢用 binding 这个词,我不知道是什么意思?这个变量是存储在环境记录规范中的吗?还是存储在别的地方?在执行上下文的结构中有一个叫 Realms 的东西,不知道是不是跟这个有关。 3. EnvironmentRecord的内部结构其实是怎样的?感觉听到了很多术语,但还是感觉很抽象。 我现在看到了第9讲,发现越来越有点看不懂,于是从头开始学,希望能得到老师的解答,如果解答起来比较复杂,能否提供一些其他资料链接。 谢谢!

    作者回复: 在去年的D2上,我专门讲过一讲《JS 语言在引擎级别的执行过程》,对你提到的问题大都有涉及。并且,有丰富的图示讲解。所以我建议你先听听视频,或者你的许多问题就有解了。 在这里: https://v.youku.com/v_show/id_XNDUwNTc3MjUzMg==.html PPT在这里找: https://github.com/d2forum/14th/tree/master/PPT 还有文字版,在“2020前端工程师必读手册”里面有收录。你搜搜~

    2020-04-15
    8
  • 蓝配鸡
    不明白为什么a.x 这个表达式的result是一个a的引用呢? 不应该是 undefined吗? 没明白...

    作者回复: Result是引用。 value是undefined。 value = GetValue(Result)

    2019-11-18
    3
    8
  • HoSalt
    老师 (test.fn)()和test.fn()的调用this都只想test,为什么前面的括号里面的内容没有返回值而是返回了引用

    作者回复: 一对括号,亦即是所谓分组表达式`()`,这个东西是在JS中极其罕见的在执行中“返回结果(result)”的表达式。因为通常的表达式是“返回值(value)”的,这甚至包括返回所谓ECMAScript规范引用,这也是作为value来返回的。——另一个如此有趣的东西是表达式作为函数体的箭头函数。 在分组表达式中,“返回结果(result)”而不是“返回值(value)”其实是有着非常大的、非常有魔力的不同的。例如说: > (test.fn) 在这个表达式里面,`test.fn`的Result是一个ECMAScript规范中的引用,因此这个引用就被返回了,因此`(test.fn)()`这个函数调用中,fn()就能得到this。也因此(eval)与直接的eval没有区别,都是eval引用。 但是你注意看, > (test.fn) 分组表达式操作的“里面的表达式`test.fn`”是一个属性存取表达式,它返回“ECMAScript规范的引用”。而下面: > (0, test.fn) 代码中在“里面的表达式`0, test.fn`”是什么呢?是用`,`号分隔开的所谓的“连续运算表达式”,而这个连续运算表达式的第二个子表达式,才是`test.fn`,对吧? 连续运算表达式返回什么呢?很不幸,连续运算表达式返回“最后一个子表达式的值(value)”——我们前面说过,所有表达式中目前只有两个是直接返回Result的。其它的情况下,其实都会返回value,包括以value定义的规范类型,或者GetValue(result)。 所以说,事实上 > (0, test.fn) 表达式的返回的是连续运算的最后一个表达式的value,也就是test.fn的getValue(result),也就是fn这个函数。因此,再调用 > (0, test.fn)() 的时候,就丢失掉了test这个对象引用了。

    2020-05-08
    4
    7
收起评论
显示
设置
留言
59
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部