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

09 | (...x):不是表达式、语句、函数,但它却能执行

可受用户代码控制的循环体
循环语义的一种实现
与函数执行无冲突
简单自然的语义概念
Symbol.iterator
.throw()
.return()
.next()
迭代
递归
结果
执行体
参数
yield* x的影响
continue触发tor.return的情况
展开语法的命名原因
tor.throw的设计目的
异常处理
迭代过程的结束
迭代过程的开启
管理多次函数调用之间的关系
逻辑的映射
语法概念
用于展开可迭代对象
可迭代对象
迭代器
递归与迭代
函数执行
语句执行
思考题
内部迭代过程
展开语法
迭代器与生成器
ECMAScript语言设计
知识关系脑图

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

你好,我是周爱民,欢迎回到我的专栏。
从之前的课程中,你应该已经对语句执行和函数执行有了基本的了解。事实上,这两种执行其实都是对顺序分支循环三种逻辑在语义上的表达。
也就是说,不论一门语言的语法有什么特异之处,它对“执行逻辑”都可以归纳到这三种语义的表达方式上来。这种说法事实上也并不特别严谨,因为这三种基本逻辑还存在进一步抽象的空间(这些也会是我将来会讨论到的内容,今天暂且不论)。
今天这一讲中,主要讨论的是第二种执行的一些细节,也就是对“函数执行”的进一步补充。
在上一讲中,我有意将函数分成三个语义组件来讲述。我相信在绝大多数的情况下,或者在绝大多数的语言教学中,都是不必要这样做的。这三个语义组件分别是指参数、执行体和结果,将它们分开来讨论,最主要的价值就在于:通过改造这三个语义组件的不同部分,我们可以得到不同的“函数式的”执行特征与效果。换而言之,可以通过更显式的、特指的或与应用概念更贴合的语法来表达新的语义。与所谓“特殊可执行结构”一样,这些语义也用于映射某种固定的、确定的逻辑。
语言的设计,本质就是为确定的语义赋以恰当的语法表达。

递归与迭代

如果循环是一种确定的语义,那么如何在函数执行中为它设计合适的语法表达呢?
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

JavaScript中的展开语法(...x)是一种新的概念,它在函数执行中起到重要作用。本文从函数执行的角度深入探讨了展开语法的原理和作用。作者首先介绍了函数执行的基本概念,包括参数、执行体和结果,并讨论了递归和迭代在函数执行中的应用。在JavaScript中,迭代通过迭代器来实现,作者详细介绍了迭代器的使用和可迭代对象的概念。展开语法的出现是为了解决函数只能返回单个值的限制,它用于展开可迭代对象,使得可以有效地表达多个值的处理。作者强调了展开语法在处理集合数据时的重要性,包括对象、数组、集和图等数据的处理。文章还探讨了异常处理和迭代过程中的退出行为,以及迭代过程中的管理和通知机制。总的来说,本文深入探讨了函数执行的细节,以及展开语法在JavaScript中的重要作用。

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

全部留言(11)

  • 最新
  • 精选
  • leslee
    1. 他可以手动调用..., 在you dont know js 里面说过这个 .throw 可以委托异常. 2. 这是由他的语义决定的, 如果他是一个语句, 那他就不能跟表达式连用, 如果他是一个表达式, 那他的返回值又显得有点多余. 3. ```js function foo4(x = 5) { return { next: () => { return { done: !x, value: x && x-- }; }, "return": () => { return { done: true, value: '恭喜发财' } }, "throw": () => { console.log("THROW!") } } } let x = new Object; x[Symbol.iterator] = foo4 aaa: for (let item of x) { console.log(item, 'w') for (let item of x) { console.log(item, 'i') continue aaa; } } // echo 'return' x2 // 当return 函数返回undefined 的时候会报这个错 Iterator result undefined is not an object ``` 被我试出来了, 当return函数返回undefined , 且嵌套循环且continue带有外层循环语句的标签的时候, 他会触发两次return, 缺一个条件都不行. 当return函数返回一个正常的迭代器对象`{done:true, value: 'xxx'}`, 他会输出5个return, 这个return应该由内层的forof 发出, 因为内层的循环直接被打断了, 继续下去的是外层循环, 单层循环不行是因为 continue 的语义并不是打断/结束, 是这样理解么老师, 这里面还有其他的豆子么,老师. 4. 把x展开, 返回迭代值 , 如果 没有 * 返回的将是迭代器函数, .

    作者回复: 关于1和3,老实说,这个东西确实灰常灰常麻烦,你得自己去体会tor.return和tor.throw的含义与用法。 我只能提示说:谁创建了tor,那么就该是谁去调用tor.return和tor.throw。这两个方法不是事件触发,而是“tor的持有者”调用。 关于2,你的答案没有对或不对的问题,但我觉得还有深思的余地。不妨多思考一下,不着急有结论。另外,这个问题没有标准答案。 关于4,你“可能”是对的,但似乎表达得不太清楚。^^.

    2019-12-26
    4
  • HoSalt
    老师,为什么fn(...args)怎么变成了一个个的参数,而在{...args}怎么变成了对象属性的一部分,这是谁控制的?

    作者回复: ...x 这个称为“展开语法”,是在语法分析阶段就被替换成了硬代码的。所以简单的说,在执行期的时候就是一个循环/迭代处理,并且根据上下文的不同,来决定是展开成参数,还是属性。 类似于用字符串替换重写了代码,硬写入的。^^.

    2020-05-18
    2
  • Smallfly
    // 迭代函数 function foo(x = 5) { return { next: () => { return {done: !x, value: x && x--}; } } } const tor = foo(); const names = Object.getOwnPropertyNames(tor.constructor.prototype); console.log(names); /* [ 'constructor', '__defineGetter__', '__defineSetter__', 'hasOwnProperty', '__lookupGetter__', '__lookupSetter__', 'isPrototypeOf', 'propertyIsEnumerable', 'toString', 'valueOf', '__proto__', 'toLocaleString' ] */ 请问老师我用 node 执行上面的代码,为什么跟文中的以下输出不一致呢。 > Object.getOwnPropertyNames(tor.constructor.prototype) [ 'constructor', 'next', 'return', 'throw' ]

    作者回复: 这里的foo()是一个模拟生成器函数的界面的,所以它当然得不到“真的”生成器。 你需要的示例是这样: ``` function* foo() {} tor = foo(); names = Object.getOwnPropertyNames(tor.constructor.prototype); ```

    2020-01-16
    2
    1
  • Sam
    根据周老师的指点,谁创建谁调用。以下是我想到一个办法调用throw方法(不知理解是否合理): function* foo() { yield 1 yield 2 yield 3 } foo.prototype.throw = function(e) { console.log("Test Error " + e) } let tor = foo() for(let item of tor) { console.log(item) tor.throw(new Error('迭代器循环休内触发')) }

    作者回复: 看起来这样是能用的,并且事实上通常也需要这样来使用。但是,总之,在这里throw()的原则与应用都是令人困惑的。——我的意思是js语言在这里的设计原本就很令人困惑。由于篇幅, 这一讲并没有关于这个部分的更多讨论,而仅仅是开了个头。如果你有兴趣了解更深的话,建议参阅《javascript语言精髓与编程实践》的5.4.4.3和5.4.5的内容。

    2021-08-12
    2
  • 油菜
    "用户在这里设计异常处理过程,那么 foo2() 中的 touch(x) 管理和涉及的资源都无法处理。" 是指在“console.log() 调用或 for…of 循环中”处理异常么? touch(x)和涉及的资源无法处理,是什么意思呢? 是指touch(x)内部抛出异常,但catch不到异常么? ------------------------------------- function touch(x) { try{ if (x==2) throw new Error("hard break"); }catch(c){ console.log('c:'+ c); } } // 迭代函数 function foo2(x = 5) { return { next: () => { touch(x); // some process methods return {done: !x, value: x && x--}; }, "return": () => console.log("RETURN!"), "throw": () => console.log("THROW!") } } // 示例 var x = new Object; x[Symbol.iterator] = foo2; // default `x` is try{ console.log(...x); }catch(d){ console.log('d:'+ d); } 结果:touch(x)可以处理异常 c:Error: hard break 5 4 3 2 1

    作者回复: 以for..of语句为例,在那个例子中发生异常时,在`console.log(i)`位置是无法捕获异常的;在for..of外层可以加一层异常,但是无法处理touch()过程所分配的(或者所"碰(touch)"过的)资源。例如,如果touch()过程打开了一个文件,那么当异常发生时,在哪里去关闭文件呢?

    2020-11-13
  • Elmer
    这个贴近语义的语法和解析为可执行结构的关系是什么呢

    作者回复: 这个这个,你的这句话我读不太明白意思。 如果你的意思是“语法与可执行结构”的关系,那么可以简单地说:在语法解析之后,会在相应的位置上插入一段硬代码,以支持对应的可执行结构的实际语义。 例如如果是“展开...x作为参数”,那么对应的位置上的代码,其实就是把数组x添加到参数列表的指定位置。这是一段确定的逻辑。

    2020-07-07
  • antimony
    老师,请问一下这个迭代和递归的关系您是从sicp中了解到的吗?

    作者回复: 不是。^^. 循环与递归的关系可能源出于SICP。但我是在读SICP之前听一个朋友谈及的,后来意识到根本问题是抽象模型内在的一致性。所以才有了《程序原本》中说“本质上相同的抽象系统,其解集的抽象本质上也是相同的”。基于此,再观察论述迭代与递归的关系,就是显而易见的事情了。 《程序原本》: https://github.com/aimingoo/my-ebooks

    2020-02-09
  • weineel
    四个思考题,都没能找到很好的答案。。。
    2019-12-02
    1
    1
  • 吉法师
    迭代感觉用不着啊……
    2021-04-06
  • 油菜
    1 语言设计者有考虑到异常处理,但功能上还不够完善。类似写了个todo 2 使用者或设计者需要一种语法,能够接收所有参数。例如剩余参数 function foo(...x){console.log(x)}, 数组x可以接收所有入参。“为什么 ECMAScript 不提供一个表达式 / 语句之外的概念来指代它”(老师可能是想说,之内的概念,文章标题已指出(...x)既不是表达式,也不是语句),可能是和现有的编译逻辑冲突。例如我们写程序,如果出现分支情况,最简单的做法是写个if判断,区分分支。表达式,语句,语法,可能是不同分支处理。 3 本人简单测试,以下几种都会触发tor.return。 for(var i of x ) {console.log(i);break}; for(var i of x ) {console.log(i); throw new Error('test')} for(var i of x ) {console.log(i);continue}; 4 只知其然,不知其所以然 // 迭代函数 function foo6(x = 5) { return { // foo2()中的next next: () => { return {done: !x, value: x && x--}; }, // foo3()中的return和throw "return": () => console.log("RETURN!"), "throw": () => console.log("THROW!") } } var b = foo6(); b.next();// { done: false, value: 5 } b.next();// { done: false, value: 4 } //yield用法 var x = new Object; x[Symbol.iterator] = foo6; function* g1() { yield* x}; var a = g1(); a.next(); // { done: false, value: 5 } a.next(); // { done: false, value: 4 }
    2020-11-13
收起评论
显示
设置
留言
11
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部