ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

CoffeeScript 0.2.0 里程碑:缩进语法、表达式化、Splats 与存在性运算符

CoffeeScript 0.2.0 里程碑:缩进语法、表达式化、Splats 与存在性运算符 编程语言编译器【免费下载链接】coffeescriptUnfancy JavaScript项目地址https://gitcode.com/gh_mirrors/co/coffeescript点击查看免费下载导读CoffeeScript 0.2.02010-01-05 发布是该语言从 0.1.x 实验期迈向成熟的关键转折点一次真正意义上的 Major Release。本篇文章以 0.2.0 版本变更日志 为核心骨架系统拆解本次发布引入的七大核心能力——有意义的空白Significant Whitespace、语句到表达式的转换、Splats 可变参数、Splice 切片字面量、对象推导式、块Blocks与存在性运算符Existential Operator并结合当前仓库中的官方文档章节、可运行的示例源码与编译器实现逐一验证。读完本文你将理解这些特性的语法形态、编译产物与底层实现脉络并能直接动手验证 CoffeeScript 0.2.0 奠定的语言风格如何延续至今。一、版本背景一次里程碑式的 Major Release变更日志原文如此描述本次发布Major release. Significant whitespace. Better statement-to-expression conversion. Splats. Splice literals. Object comprehensions. Blocks. The existential operator.从版本号演进看0.2.0 紧承 0.1.6 发布0.1.0 为 2009-12-24 的初始版本两者相隔仅十余天却在语言设计上完成了从JavaScript 的轻量包装到自成体系的语言的关键跨越。本次发布包含七项互相咬合的语言特性它们共同构成了我们今天熟知的 CoffeeScript 语法基座特性一句话概括对应官方文档章节Significant whitespace用缩进取代花括号与大括号组织代码块objects_and_arrays.mdStatement-to-expression让if、while、for等语句可以当作表达式使用expressions.mdSplats用...收集可变参数并展开数组/对象splats.mdSplice literals用区间语法对数组切片、替换拼接slices.mdObject comprehensions用for ... of遍历对象键值对生成数组参见示例 object_comprehensions.coffeeBlocksdo块、嵌入 JavaScript 块等块级结构embedded.mdExistential operator?、?.、?等存在性判断与安全访问existential_operator.md以下各节逐一深入。二、有意义的空白用缩进定义结构2.1 语法形态CoffeeScript 的核心设计哲学是有意义的空白significant whitespace代码块通过缩进而非{}界定多行对象字面量可以省略逗号甚至可以直接用换行缩进构造对象——这与 YAML 的表达方式类似。官方 objects_and_arrays.md 章节给出了如下示例原文对应 objects_and_arrays.coffeesong [do, re, mi, fa, so] singers {Jagger: Rock, Elvis: Roll} bitlist [ 1, 0, 1 0, 0, 1 1, 1, 0 ] kids brother: name: Max age: 11 sister: name: Ida age: 9要点每行一个属性时逗号可省略数组bitlist中每一行元素之间没有逗号依然合法对象可用缩进替代花括号kids对象完全没有显式{}靠缩进层级建立嵌套关系对象属性简写shorthand当键名与变量名相同时可以写{name, mask, weapon}注意此写法要求显式花括号见 objects_shorthand.coffeename Michelangelo mask orange weapon nunchuks turtle {name, mask, weapon} output #{turtle.name} wears an #{turtle.mask} mask. Watch out for his #{turtle.weapon}!2.2 底层实现从源码结构看缩进语法由词法分析器lexer负责解析成结构化的 token 流。src/lexer.coffee 中维护了缩进栈通过对缩进级别变化的追踪隐式地插入INDENT/OUTDENT标记从而将缩进层级翻译为代码块的边界src/rewriter.coffee则负责在进入语法分析src/grammar.coffee/src/parser.js前将这些缩进 token 重写为括号化parenthesized的语法结构。换句话说缩进不是编译器的装饰性偏好而是参与语法解析的一等公民。三、更好的语句到表达式转换一切皆表达式3.1 设计目标变更日志中的 Better statement-to-expression conversion 指向 CoffeeScript 最重要的设计信条之一尽可能让所有语句都可以当作表达式使用。官方 expressions.md 章节说明了这一行为函数体即使不写return也会返回其最后一个表达式的值编译器会把return推入每条可能执行的分支。以下示例来自 expressions.coffeegrade (student) - if student.excellentWork A else if student.okayStuff if student.triedHard then B else B- else C eldest if 24 21 then Liz else Ikegrade函数没有显式return但每个分支的字符串都会成为返回值if 24 21 then Liz else Ike则直接作为赋值表达式的右值。3.2 三类典型表达式化场景1赋值出现在表达式中——由于变量声明会被提升到作用域顶部尚未见过的变量也可以在表达式内部直接赋值。见 expressions_assignment.coffeesix (one 1) (two 2) (three 3)2推导式作为表达式——把推导式comprehension的结果直接赋给变量。见 expressions_comprehension.coffee# The first ten global properties. globals (name for name of window)[0...10]3try/catch作为函数参数——语句被转换成表达式时CoffeeScript 会将其包裹进闭包closure中于是可以写出这样的代码见 expressions_try.coffeealert( try nonexistent / undefined catch error And the error is ... #{error} )3.3 边界哪些语句无法表达式化官方文档明确JavaScript 中break、continue、return三个语句无法被有意义地转换为表达式。当它们在代码块中出现时CoffeeScript 不会尝试做闭包包裹转换而是保持语句语义。这是理解表达式化边界的关键约束。3.4 与条件语法的配合条件语句同样受益于表达式化。官方 conditionals.md 指出if/else无需括号和花括号多行条件用缩进界定还支持将if/unless放在行尾的后缀形式。CoffeeScript没有显式的三元运算符单行if语句即充当三元表达式的角色编译为 JS 三元运算。见 conditionals.coffeemood greatlyImproved if singing if happy and knowsIt clapsHands() chaChaCha() else showIt() date if friday then sue else jill四、Splats可变参数与展开语法4.1 定义与调用两个方向的...JavaScript 的arguments对象是处理可变参数函数的主要手段但使用繁琐。CoffeeScript 用...Splat同时覆盖函数定义收集多余实参为数组即 ES2015 的 rest parameters与函数调用将数组展开为实参列表即 spread syntax两个方向。官方示例 splats.coffee 完整展示了这一用法gold silver rest unknown awardMedals (first, second, others...) - gold first silver second rest others contenders [ Michael Phelps Liu Xiang Yao Ming Allyson Felix Shawn Johnson Roman Sebrle Guo Jingjing Tyson Gay Asafa Powell Usain Bolt ] awardMedals contenders... alert Gold: #{gold} Silver: #{silver} The Field: #{rest.join , } 这里others...在参数列表中收集第一、二个实参之后的所有选手调用时awardMedals contenders...把整个数组展开为十个实参。ES2015 采纳了这一设计rest parameters 与 spread syntax从侧面印证了 CoffeeScript 0.2.0 的前瞻性。4.2 数组展开与对象展开Splat 还可以用来省略elide数组元素或对象属性数组展开见 array_spread.coffeepopular [pepperoni, sausage, cheese] unwanted [anchovies, olives] all [popular..., unwanted..., mushrooms]对象展开见 object_spread.coffeeuser name: Werner Heisenberg occupation: theoretical physicist currentUser { user..., status: Uncertain }对象展开在 ECMAScript 中直到 ES2018 才原生支持CoffeeScript 比标准提前了近十年提供这一能力。4.3 解构中的 SplatSplat 与解构赋值结合可以方便地剥离首尾元素。见 patterns_and_splats.coffeetag impossible [open, contents..., close] tag.split()open取第一个字符close取最后一个字符contents收集中间全部内容。4.4 底层实现从源码结构看...的识别与处理贯穿词法与语法两阶段词法层src/lexer.coffee 第 1327 行的正则\.{2,3}同时匹配区间range与 splat 标记由后续上下文决定归属语法层src/nodes.coffee 中定义了独立的Splat节点类并围绕其展开逻辑实现了hasSplat()、getAndCheckSplatProps()、getAndCheckSplatsAndExpansions()等方法约 2485–2694 行负责处理参数收集、数组/对象展开顺序以及多个展开元素不合法等约束检查。可以推断编译器的核心工作是把 splat 语义降级为Array.prototype.slice、.apply调用或arguments处理等普通 JavaScript 结构。五、Splice Literals用区间对数组切片与拼接5.1 区间语法基础官方 slices.md 定义了区间Range规则两个点3..6包含端点表示3, 4, 5, 6三个点3...6不含端点表示3, 4, 5切片索引有默认值省略起始索引默认从 0 开始省略结束索引默认到数组末尾。见 slices.coffeenumbers [1, 2, 3, 4, 5, 6, 7, 8, 9] start numbers[0..2] middle numbers[3...-2] end numbers[-2..] copy numbers[..]负索引从数组尾部倒数numbers[3...-2]得到[4, 5, 6, 7]numbers[-2..]得到最后两个元素numbers[..]则是整数组的副本。5.2 拼接Splice切片赋值替换区间同样的语法用于赋值左侧时会替换数组中的一段区间即拼接splicing语义。见 splices.coffeenumbers [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] numbers[3..6] [-3, -4, -5, -6]执行后numbers的索引 3 到 6 位置被新数组整体替换。官方文档特别提醒JavaScript 字符串不可变因此无法对字符串做拼接操作。5.3 与区间推导式的联动区间常与推导式组合生成序列见 range_comprehensions.coffeecountdown (num for num in [10..1])[10..1]是一个从 10 递减到 1 的区间推导式逐项取出生成倒计时数组。六、对象推导式遍历对象键值对6.1 用法对象推导式Object comprehension用for key, value of object遍历对象返回一个数组。见 object_comprehensions.coffeeyearsOld max: 10, ida: 9, tim: 11 ages for child, age of yearsOld #{child} is #{age}ages将得到[max is 10, ida is 9, tim is 11]。注意这里的对象字面量max: 10, ida: 9, tim: 11本身也演示了 0.2.0 的紧凑对象写法。6.2 与数组推导式的对比数组推导式使用for x in list遍历数组元素可选索引for dish, i in courses并支持when过滤条件见 array_comprehensions.coffee# Eat lunch. eat (food) - #{food} eaten. eat food for food in [toast, cheese, wine] # Fine five course dining. courses [greens, caviar, truffles, roast, cake] menu (i, dish) - Menu Item #{i}: #{dish} menu i 1, dish for dish, i in courses # Health conscious meal. foods [broccoli, spinach, chocolate] eat food for food in foods when food isnt chocolate对象推导式for ... of与数组推导式for ... in一键一值共同构成 CoffeeScript 集合遍历的完整形态。七、Blocksdo块与嵌入 JavaScript7.1do块捕获循环变量的立即执行函数do块是本次发布引入的重要块级结构其作用是为闭包绑定当前循环变量解决 JavaScript 经典的回调闭包共享变量问题。见 do.coffeefor filename in list do (filename) - if filename not in [.DS_Store, Thumbs.db, ehthumbs.db] fs.readFile filename, (err, contents) - compile filename, contents.toString()do (filename) - ...将当前迭代的filename作为参数传入立即执行的匿名函数使异步回调fs.readFile捕获到正确的文件名字而不是循环结束后共享的同一个变量。7.2 嵌入 JavaScript 块官方 embedded.md 说明如果需要在 CoffeeScript 中穿插原生 JavaScript可以使用反引号将其直通pass through到输出。单个反引号包裹行内代码三个反引号包裹整块代码hi function() { return [document.title, Hello JavaScript].join(: ); }对应示例 embedded.coffee。整块嵌入见 embedded_block.coffeefunction time() { returnThe time is ${new Date().toLocaleTimeString()}; }转义规则反斜杠转义反引号\输出反引号前的反斜杠需要用更多反斜杠转义\\\输出\完整示例见 embedded_escaped.coffee。在 JavaScript 块内需要反引号时用三反引号块比逐个转义更省事——这是官方文档给出的实践建议。八、存在性运算符?、?.、?与安全访问8.1 为什么需要它JavaScript 中判断变量是否存在并不容易if (variable)对0、空字符串、false都会误判为不存在。CoffeeScript 的存在性运算符?仅在值为null、undefined或变量未声明时为真语义上等价于 Ruby 的nil?这是官方 existential_operator.md 给出的定义。8.2 三种形态见 existence.coffeesolipsism true if mind? and not world? speed 0 speed ? 15 footprints yeti ? bearmind?/world?存在性判断speed ? 15存在性赋值——仅当speed为null/undefined/未声明时才赋值为15比 JavaScript 的a a || value更安全后者会把0、、false等合法值也替换掉yeti ? bear存在性选择——yeti存在则取其值否则取bear。8.3 已声明变量与未声明变量的编译差异编译器会根据作用域分析区分两种情况变量已声明在作用域内时a?编译为a ! null宽松比较同时覆盖undefined见 existence_declared.coffeemajor Computer Science unless major? signUpForClass Introduction to Wines变量可能未声明时编译器做彻底检查先查作用域再比null见 existence_undeclared.coffeeif window? environment browser (probably)这正是在浏览器环境中安全探测全局对象的标准写法。8.4 访问器变体Soak吸干空引用访问器变体?.用于在属性链中吸收soak up空引用链中任一环节为null/undefined时整体返回undefined而非抛出TypeError。见 soaks.coffeezip lottery.drawWinner?().address?.zipcodedrawWinner?()先确认lottery.drawWinner可调用再调用.address?.zipcode在address为空时安全短路。8.5 完整语义速查表官方文档给出了完整定义表这里完整保留示例定义a?测试a在作用域内且a ! nulla ? ba在作用域内且a ! null时返回a否则返回ba?.b或a?[b]a在作用域内且a ! null时返回a.b否则返回undefineda?(b, c)或a? b, ca在作用域内且可调用时以参数b、c调用a并返回结果否则返回undefineda ? ba不在作用域内或a null时把b赋给a表达式值为a的新值8.6 底层实现从词法层看src/lexer.coffee 第 817 行在识别到?且前一个 token 是索引/访问操作符时将其改写为INDEX_SOAKtoken从而在语法层建立soak 访问节点。这解释了为什么a?.b与a?[b]两种写法能统一处理soak 逻辑挂载在属性访问链上而不是作为独立的二元运算符。九、特性全景验证一个综合示例官方 overview.coffee 示例几乎汇聚了 0.2.0 引入的全部特性可作为本文的总结性验证# Assignment: number 42 opposite true # Conditions: number -42 if opposite # Functions: square (x) - x * x # Arrays: list [1, 2, 3, 4, 5] # Objects: math root: Math.sqrt square: square cube: (x) - x * square x # Splats: race (winner, runners...) - print winner, runners # Existence: alert I knew it! if elvis? # Array comprehensions: cubes (math.cube num for num in list)这段代码同时体现了缩进界定的对象字面量Objects、后缀条件Conditions、-函数Functions、Splats 参数收集runners...、存在性判断elvis?以及数组推导式Array comprehensions。配合 aliases.coffee 中is/isnt/not/unless/in/yes/no等别名运算符0.2.0 所确立的语言面貌已经与现代 CoffeeScript 高度一致。十、版本意义小结CoffeeScript 0.2.0 以七项互相咬合的特性完成了一次语言层面的定型缩进语法定义了代码的组织方式表达式化定义了值的流动方式Splats 与切片定义了集合的伸缩方式对象推导式定义了键值遍历方式do块与嵌入 JavaScript定义了与宿主环境异步回调、原生代码的衔接方式存在性运算符定义了空值的处理方式。其中 Splatsrest/spread与对象展开被后来的 ES2015/ES2018 标准以相近语义采纳体现了该版本设计的前瞻性。变更日志中对社区贡献者的致谢特别是 Liam OConnor-Davis 在空白与表达式转换方面的工作也说明这些特性并非闭门造车的产物而是社区协作打磨的结果。若想继续深入建议按以下路径探索本仓库语言特性官方文档splats.md、slices.md、existential_operator.md、expressions.md、conditionals.md、embedded.md、objects_and_arrays.md全部可运行示例documentation/examples/ 目录下的.coffee文件编译器实现src/lexer.coffeetoken 化与 soak/缩进处理、src/nodes.coffeeSplat 等节点的生成逻辑、src/rewriter.coffee缩进到括号结构的重写版本时间线documentation/sections/changelog/ 目录下的逐版本变更记录。赞分享编程语言编译器【免费下载链接】coffeescriptUnfancy JavaScript项目地址https://gitcode.com/gh_mirrors/co/coffeescript点击查看免费下载相关推荐CoffeeScript 0.9.0 里程碑解读 赋值语法、{} 插值与静态继承的演进CoffeeScript 0.9.0 里程碑解读 赋值语法、 {} 插值与静态继承的演进 本文基于仓库内的版本发布记录 documentation/sec编程语言编译器SystemInformer 悬停提示总挡事一条路径彻底关掉SystemInformer 悬停提示总挡事一条路径彻底关掉 鼠标一挪到 SystemInformer 的进程列表上就弹提示框悬停提示在服务列表、性能图上同桌面应用调试器应用安全驱动开发Symfony ExpressionLanguage表达式语法大全运算符、函数与变量详解Symfony ExpressionLanguage表达式语法大全运算符、函数与变量详解 Symfony ExpressionLanguage组件是一个强大的后端上一篇Instagram-Profilecrawl 完整部署指南从零搭建你的 Instagram 数据自动化采集体系下一篇超强Zephyr设备树覆盖文件语法入门创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表