
简介压缩包为一份围绕ECMAScript 5ES5语法实践与机器人控制场景的大学课程练习材料对应Robotic university exercise, part n°5。资源以源代码.cc/.h和头文件为主同时包含配置文件.inc/.cfg、仿真世界模型.world、位图素材.png、实验说明文档.doc/.txt等共22个文件整体仅67KB轻量且结构清晰便于快速定位模块。已有270人学习浏览。练习涉及ES5严格模式、数组新方法、bind、getter/setter等关键特性结合移动目标、避障和局部定位等机器人任务可帮助读者理解ES5在数据处理、状态管理和事件监听中的实际用法。包内LocalizzazioneEKF、MoveToGoal、AvoidObstacle等模块展示了完整的工程组织方式适合作为机器人编程或JavaScript进阶学习的实验参考也能为构建更复杂的自动化控制系统打下基础。1. 从 Es5.rar 里解压出的 es5到底是什么从资源站或同事网盘里拎回来的压缩包经常是这种命名Es5.rar 解压出来一个文件就叫 es5。它可能是老工程的源码快照也可能是 ECMAScript 5 规范原文的离线拷贝。es5 在 Web 开发里指的就是 2009 年定稿的 ECMAScript 5 语言规范也是今天几乎所有浏览器都能原生运行的一版 JavaScript。你大概率写了好几年 ES6但难免要维护内网旧系统里的前端、给系统版本锁死的安卓 WebView 做页面、接手十年前留下的库存代码这些环境不认识 let、箭头函数和模板字符串只认 ES5。把 ES5 的语法边界、内置 API、构建降级和验证手段捡回来不是考古是刚需。下面按「规范要点 → API 实现 → 降级构建 → 验证方法」的顺序把整套方案讲完。2. ES5 的三个地基特性严格模式、JSON 与属性描述符ES5 相比 ES3 的改动最值得记住的不是某个具体方法而是三个改变语言模型的东西严格模式、JSON 全局对象、属性描述符。这三样决定了后面大量 API 的设计也决定了现代框架比如 Vue2的实现方式逐个过一遍配合最小可运行代码。2.1 用 use strict 切换语言语义开启位置与 4 个行为变化严格模式是一句字符串指令写在脚本或函数体的第一行。注意「第一行」是硬性要求前面可以有注释但不能有任何语句否则整段代码悄悄退回非严格模式这是排查严格模式不生效时最先要确认的事。// 整个脚本开启必须是第一行语句 use strict; var version es5; // 函数内开启只影响该函数及内部嵌套函数 function transform(input) { use strict; return input.toString(); }逻辑说明严格模式按「脚本级」和「函数级」两个粒度生效写在 JS 文件开头则整个文件进入严格模式写在函数体内第一行则只对该函数生效。老引擎不识别这条字符串会把它当成无副作用的表达式忽略掉代码即使落到不支持严格模式的浏览器里也不会报错只是不生效——这是 ES5 为向后兼容特意做的设计。严格模式改变了 4 个最容易踩的行为行为非严格模式严格模式给未声明变量赋值悄悄创建全局变量抛 ReferenceError普通函数调用时的 thiswindow / globalundefinedwith 语句可用SyntaxErrorarguments.callee可用TypeError前两行是工作中遇到最多的漏写 var 的赋值在严格模式下直接让脚本挂掉而不是把变量挂到全局回调函数被直接调用时 this 从 window 变成 undefined这就是「严格模式下 this 丢失」报错的根源。后两行属于语言层面的收紧ES5 这么做是为了让引擎能做更多编译期优化——运行时代码的变量作用域不再被 with 和 arguments 别名机制动态改写。除此之外严格模式还禁用 eval 向外层作用域泄漏变量这也是它一直推荐被开启的原因。实际落地时的建议新写的 ES5 代码一律在文件头部开启严格模式老代码迁移时先开函数级严格模式逐函数验证行为差异再整文件开启。IE9 对严格模式只有部分实现个别边界行为不对齐真正完整的严格模式要到 IE10。2.2 JSON.parse / JSON.stringifyES5 首次内置的序列化 APIES5 之前浏览器之间没有统一的 JSON 实现每个页面都得引一份 json2.js。ES5 把 JSON 做成了全局对象IE9 起原生支持IE8 在标准模式下也能用IE7 及以下仍需要外部库。var user { name: es5, tags: [javascript, compat] }; var text JSON.stringify(user, null, 2); console.log(text); var restored JSON.parse(text); console.log(restored.name);参数说明stringify 的第二参数 replacer 可以是数组只序列化列出的字段或函数对每个键值对做加工第三参数 space 是缩进用的空格数或字符串parse 的第二参数 reviver 可以在还原时对每个键值做二次处理。这两个回调平时用得少但在做接口字段裁剪和数据清洗时非常有用比如把后端返回的字段名统一改成驼峰就写在 replacer 里。用的时候要记住 JSON.stringify 的过滤规则值为 undefined 的属性、函数属性会被整个跳过但数组里的 undefined 会被写成 nullDate 会先走内部的 toJSON 然后被转成 ISO 字符串。这些规则在对接老接口时直接表现为「字段莫名消失」排错时先怀疑这里。另外 ES5 的 JSON.parse 只接受合法 JSON 文本单引号、尾逗号、注释统统抛 SyntaxError——老后端拼出来的「伪 JSON」是解析失败的重灾区。2.3 Object.defineProperty 与存取器Vue2 响应式的地基ES5 之前对象的属性只有「有没有值」这一层概念ES5 引入属性描述符一个属性由数据描述符value、writable、enumerable、configurable或访问器描述符get、set来定义。这不仅是语法层面的扩充它第一次让 JavaScript 可以在运行时拦截属性的读取和赋值。var data { _v: 0 }; Object.defineProperty(data, value, { get: function () { console.log(读取 value); return this._v; }, set: function (next) { console.log(value 被改写为, next); this._v next; }, enumerable: true, configurable: false }); data.value 1; // 触发 set console.log(data.value); // 触发 get逻辑说明get/set 拦截对 data.value 的读写这就是 Vue2 对 data 做响应式拦截的原理依赖收集发生在 get 里派发更新发生在 set 里。注意 set 内部不能再写 this.value next否则会无限递归所以实际数据存在备用字段 _v 上这是 ES5 时代写存取器最常见的死循环。属性描述符的默认值很容易记反一次记牢描述符字段作用默认值value属性值undefinedwritable是否可改写falseenumerable是否出现在 Object.keys / for...infalseconfigurable能否再次 defineProperty 或 deletefalseget / set读取与赋值拦截undefined这里有个连带坑数据描述符和访问器描述符必须二选一不能同时给 value 和 get混写会直接抛 TypeError。另外 Vue2 响应式「新增属性检测不到、直接按下标改数组检测不到」两个知名限制根源也在这里——defineProperty 只能拦截已存在的属性这是 ES5 的能力边界不是 Vue 自己犯懒。ES5 还配套了 Object.create、Object.keys、Object.getOwnPropertyDescriptor 和 Object.getPrototypeOf查对象结构时都从这些 API 入手。3. 把 ES5 数组方法与 bind 用熟含兼容实现的写法ES5 给 Array.prototype 加了 9 个遍历相关方法forEach、map、filter、some、every、reduce、reduceRight、indexOf、lastIndexOf。它们在 IE9 全部可用但 IE8 一个都没有。这批方法的设计高度统一回调签名为 (item, index, array)绝大多数方法还接受第二个参数 thisArg 来指定回调里的 this返回值各有分工方法回调签名返回值典型用途forEach(item, index, array)undefined遍历副作用map(item, index, array)新数组长度不变数据加工filter(item, index, array)新数组保留回调为 true 的项过滤some(item, index, array)布尔值任一 true 即短路存在性检查every(item, index, array)布尔值全部 true 才 true全量校验indexOf无回调直接传值索引或 -1查找reduce(acc, item, index, array)累计结果求和、聚合3.1 回调与 thisArg 的实际用法var nums [1, 2, 3, 4, 5, 5]; var doubled nums.map(function (n) { return n * 2; }); var evens nums.filter(function (n) { return n % 2 0; }); var total nums.reduce(function (acc, n) { return acc n; }, 0); var firstFive nums.indexOf(5);逻辑说明map 返回一个新数组、长度和原数组一致filter 返回的数组长度只会更短reduce 通过回调第一个参数 acc 累积结果第二个参数 0 是初始值。indexOf 用的是严格相等所以 indexOf(NaN) 恒等于 -1要找 NaN 得自己写 some 加 isNaN 判断。reduce 不传初始值时第一次回调的 acc 取数组第一个元素、item 取第二个元素如果数组为空且没给初始值直接抛 TypeError报错信息是著名的 Reduce of empty array with no initial value很多线上事故就是空数据接口触发的。forEach 没有返回值想在遍历里提前终止是不可能的——回调里 return 只相当于 continue要像 break 一样跳出只能改用 for 循环或者用 some 返回 true 短路。回调第二参数 thisArg 传入后回调内的 this 指向它不传时非严格模式下 this 是全局对象严格模式下是 undefined写库代码时回调里别依赖 this。3.2 手写 Function.prototype.bind 的兼容实现Function.prototype.bind 是 ES5 规范内容但 IE9 上是缺失的到 IE10 才有这是 ES5 浏览器兼容问题里补丁率最高的一项。标准 polyfill 写法if (typeof Function.prototype.bind ! function) { Function.prototype.bind function (thisArg) { var fn this; var preArgs Array.prototype.slice.call(arguments, 1); return function () { var args preArgs.concat(Array.prototype.slice.call(arguments)); return fn.apply(thisArg, args); }; }; }逻辑说明bind 的核心是「预置 this 和预置参数」。返回的新函数被调用时把 bind 时的参数preArgs和实际调用时的参数拼接成完整参数列表再用 apply 以 thisArg 作为 this 调用原函数。Array.prototype.slice.call(arguments, 1)把 arguments 这个类数组转成真数组是 ES5 时代的标配写法——今天的 Array.from 当年就是这么顶班的。这个版本是「够用版」没有处理 bound 函数被当作构造函数 new 的场景。ES5 规范里 bound function 同样可以 new此时 thisArg 被忽略实例的原型链来自原函数要完整支持需要在返回的函数里判断this instanceof fn来走 new 分支。日常业务代码用简化版即可但要给别人当工具库请换完整版。另外把 bind 写在 if 检测里是必须的老引擎里这个属性是 undefined直接覆盖会破坏未来原生实现先检测再补丁是 ES5 时代所有 polyfill 的统一姿势。3.3 稀疏数组与空洞跳过遍历方法最容易忽略的差异ES5 的遍历方法有一个和 for 循环截然不同的行为它们会跳过数组里的「空洞」。空洞指数组中没有值的位置最常见的是var arr [1, , 3]这种字面量写法或者delete arr[1]之后留下的坑。var arr [1, , 3]; var count 0; arr.forEach(function () { count; }); console.log(count); // 2空洞不被访问 var mapped arr.map(function (n) { return n 1; }); console.log(mapped[1]); // 空洞不是 undefined逻辑说明forEach 只会对 1 和 3 执行回调map 出来的新数组在第 1 个位置依然是空洞。如果业务逻辑认为数组每个位置都应该被处理回调式遍历的结果会和预期差一截而 for 循环读空洞位置得到的是 undefined。排查这类问题可以统一改用 for 循环按索引遍历或者先用 filter 把空洞清掉。空洞不是 undefined 值是位置上根本没有元素两种遍历模型在同一份数据上的结论可以完全不同。4. 工程实战现代代码降级到 ES5 的 Babel 配置与 lint 校验现在不会有人手写大段 ES5 业务代码更常见的诉求是代码用 ES6 写构建产物要在只支持 ES5 的环境里跑。核心是两条线——语法降级交给 BabelAPI 缺失交给 core-js最后用 lint 兜底防住不该出现的现代语法。4.1 Babel preset-env 针对 ES5 的 targets 配置preset-env 的行为完全由 targets 决定理解这一点比抄配置更重要{ presets: [ [babel/preset-env, { targets: { ie: 9, chrome: 33 }, useBuiltIns: usage, corejs: 3 }] ] }参数说明targets.ie 设为 9 表示产物要兼容到 IE9preset-env 会把箭头函数、for...of、展开运算符、默认参数等 ES6 语法转成 ES5 能解析的写法chrome: 33 是安卓 WebView 常用基线Android 4.1 自带 WebKit 大致对应这个级别。useBuiltIns: usage 表示按需注入 core-js 的 polyfillcorejs: 3 指定 core-js 主版本。反过来如果把 targets 改成 chrome: 60preset-env 可能一行都不转——Chrome 60 原生支持当年几乎所有 ES6 语法——这是初次配置的人最容易误判的点Babel 没坏是目标环境不需要转。4.2 core-js 与 polyfill 的取舍语法降级解决「能不能被解析」polyfill 解决「调用的 API 在不在」。IE9 没有 bind、没有 Object.assignES6、更没有 Promise语法转得再干净运行时一调就报 not a function。useBuiltIns 的三个取值要按项目体量选取值做法适用场景false不注入任何 polyfill自己手写特征检测或引第三方库entry入口处 import core-js/stable 全量引入多页面老项目图省事usage按代码实际用到的 API 自动引入对应模块绝大多数新项目// usage 模式下下面这行会让 Babel 把 Array.from 的 polyfill 打进产物 var items Array.from(document.querySelectorAll(li));逻辑说明Array.from 是 ES6 APIpreset-env 发现代码里用了它、且 targets 里有 ie: 9就会单独注入 core-js 里对应的实现而不是整个 core-js 一锅端。选择 usage 时务必把 corejs 版本写上否则 Babel 默认按 core-js 2 处理和已装的 core-js 3 对不上排查起来很费时间。还有一个细节Babel 的语法降级不负责转换 Symbol、WeakMap 这些「运行时能力」它们仍依赖 polyfill。ES5 环境下 Promise 是非常典型的依赖项老代码迁移时先扫描代码里有没有 Promise 和 fetch这两个往往是第一批缺失项。4.3 用 eslint-plugin-es 卡住现代语法如果项目硬性要求「只能写 ES5」单靠人和 code review 拦不住手滑要在 lint 阶段把 ES6 语法挡在门外。我一般这样配npm i -D eslint eslint-plugin-es{ plugins: [es], parserOptions: { ecmaVersion: 5 }, extends: [plugin:es/restrict-to-es5] }逻辑说明eslint-plugin-es 提供了一组 no-let、no-const、no-arrow-functions、no-class、no-template-literals、no-default-parameters 等规则restrict-to-es5 预设把它们全部打开作用是「ES6 加进来的语法一律报错」。parserOptions.ecmaVersion: 5 让 ESLint 内置的 espree 解析器在词法层面就拒绝 let、箭头函数等关键字双重保险解析器负责语法门槛插件负责语义禁令。另外 ES5 没有模块系统团队封装一般用 IIFE 加揭示模块模式配合这条 lint 规则可以保证整个代码库只出现 var 和 function 两种声明方式。跑npx eslint src/之后所有越界语法会一次列全修完再合并。4.4 老浏览器兼容性自检清单给一份检查表按 ES5 时代浏览器梯队从上往下过特性IE8IE9IE10 / 现代浏览器严格模式不支持指令被当普通字符串部分支持完整支持JSON标准模式可用IE7 无原生支持原生支持Object.defineProperty仅 DOM 对象可用普通对象可用完整支持Function.prototype.bind无无IE10 起支持ES5 数组方法全部缺失全部支持完整支持实际工程里IE8 基本到了要单独评估的地步——它连 defineProperty 都只对 DOM 对象开放强行做 ES5 降级收益很低。更现实的目标是「IE9 原生 ES5」只需要给 bind 补一个 polyfill、确认严格模式下没有 this 丢失问题就够了。另外 Date.now 也是 ES5 新增IE9 支持IE8 没有老代码里常见new Date()这种写法就是它的降级替代。如果线上真有 IE8 流量优先做的是流量统计和降级页而不是为了它重写整套框架。5. 收尾验证用 espree 和特征检测确认产物是「真 ES5」5.1 语法层校验espree 的 ecmaVersion 参数构建完毕之后最怕的是「以为降级了产物里混进箭头函数」。验证语法最直接的方式是用 espree——ESLint 内置的解析器它支持把 ecmaVersion 显式设成 5const fs require(fs); const espree require(espree); const code fs.readFileSync(process.argv[2], utf8); try { espree.parse(code, { ecmaVersion: 5, sourceType: script }); console.log(OK: 纯 ES5 语法); } catch (e) { console.error(NOT ES5:, e.message); process.exit(1); }运行方式npm i -D espree node check-es5.js dist/app.js逻辑说明ecmaVersion: 5 是 espree 支持的合法取值解析器遇到 let、箭头函数、模板字符串、class 会直接抛错得到与运行环境无关的静态结论。sourceType: script 用于禁止 import / export——ES5 没有模块语法只认脚本和函数作用域。把这条命令接到 CI 里每次发版先过语法关卡再部署能拦住绝大多数「降级配置失效」的回归。5.2 运行时层校验特征检测而不是版本号判断语法合规只能证明「能解析」运行环境有没有对应 API 是另一回事。按 userAgent 判断浏览器版本是 ES5 时代最不靠谱的做法UA 可以改、兼容模式会骗人。正确姿势是直接检测能力function es5RuntimeCheck() { var features { JSON: typeof JSON ! undefined !!JSON.parse, bind: typeof Function.prototype.bind function, Object.keys: typeof Object.keys function, Array.isArray: typeof Array.isArray function, Object.defineProperty: typeof Object.defineProperty function }; var missing Object.keys(features).filter(function (name) { return !features[name]; }); return missing.length 0 ? ES5 runtime OK : missing: missing.join(, ); }逻辑说明这里全部用 typeof 判断因为缺失时属性值是 undefinedtypeof 不会抛错直接 if (JSON) 判断在老引擎里会因 JSON 未定义而抛 ReferenceError。整段代码本身也是 ES5 语法可以打进页面加载完成后打印环境能力。如果检测到 bind 缺失但代码里没引 polyfill直接给出明确的 missing: bind 提示比运行时才爆一个栈要友好得多。5.3 把「真 ES5」变成发版门槛把两条校验串进发版命令形成固定动作node check-es5.js dist/app.js npm run test从 Es5.rar 里解压出来的那份东西不管是规范文档、示例代码还是老工程快照都能用同一套流程验证语法交给 espree 卡 ecmaVersion 5能力交给运行时特征检测polyfill 交给 core-js 按需补。三条线各管一段ES5 兼容从「靠记忆写代码」变成可执行、可回归的工程动作。把这条 check-es5.js 放进 package.json 的 prepublish 脚本或 CI 的 prebuild 阶段每次构建先验证再打包是最低成本的 ES5 守门方式。本文还有配套的精品资源点击获取