首页 / 资讯中心 / 文章详情

ES6 与 Node.js 群侠传:那些年我们追过的语法,和那些年把我们追杀的 API

ES6 与 Node.js 群侠传:那些年我们追过的语法,和那些年把我们追杀的 API ★ FEATURED ARTICLE
ES6 与 Node.js 群侠传那些年我们追过的语法和那些年把我们追杀的 API如果说 JavaScript 是一个江湖那 ES6 就是一次武林大洗牌而 Node.js 就是那个把江湖搬到服务器上的神秘门派。序章从 var 的混乱说起很久很久以前JavaScript 的世界只有 var。jsvar name 张三 if (true) { var name 李四 } console.log(name) // 李四张三在 if 里走了一遭出来就变成李四了。这不是 bug这是 var 的函数作用域特性——它根本不在乎 if 的{}它只认函数。jsfor (var i 0; i 3; i) { setTimeout(() console.log(i), 0) } // 3 3 3三个 setTimeout打印了三个 3。因为 var 只有一个 i循环结束时 i 已经是 3 了。这就是 ES6 之前的世界变量像脱缰的野马跑得到处都是。直到 ES6 带着let和const降临江湖才终于有了规矩。第一章ES6 三大护法——let、const、箭头函数let有边界的变量jslet name 张三 if (true) { let name 李四 } console.log(name) // 张三let 是块级作用域{} 就是它的边界。出了这个门它就不认识你了。同样的 for 循环jsfor (let i 0; i 3; i) { setTimeout(() console.log(i), 0) } // 0 1 2每次循环的 i 都是独立的。世界终于正常了。const一把锁jsconst PI 3.14159 PI 3.14 // ❌ 报错Assignment to constant variableconst 声明的变量不能重新赋值。但注意——const 锁的是绑定不是值jsconst obj { name: 张三 } obj.name 李四 // ✅ 可以 obj { name: 王五 } // ❌ 报错 const arr [1, 2, 3] arr.push(4) // ✅ 可以 arr [5, 6] // ❌ 报错这就是为什么大家都说“优先用 const需要改再用 let永远别用 var。”箭头函数函数界的瘦身达人js// 传统函数 function add(a, b) { return a b } // 箭头函数 const add (a, b) a b箭头函数不仅短还顺手解决了 this 的世纪难题。jsconst obj { name: 张三, say() { setTimeout(function() { console.log(this.name) // undefined }, 100) } }传统函数的 this 是谁调用我我就指向谁。在 setTimeout 里this变成了 window或 undefined。jsconst obj { name: 张三, say() { setTimeout(() { console.log(this.name) // 张三 }, 100) } }箭头函数没有自己的 this它继承外层的 this。这就是箭头函数最核心的规则它不制造 this它只是 this 的搬运工。第二章ES6 四大神器——解构、模板字符串、展开、模块解构拆包裹的艺术js// 对象解构 const user { name: 张三, age: 18 } const { name, age } user // 数组解构 const [first, second] [1, 2, 3] // 函数参数解构 function greet({ name, age }) { console.log(${name} 今年 ${age} 岁) }以前取个属性要写 user.name、user.age现在一行搞定。最爽的是交换变量jslet a 1, b 2 ;[a, b] [b, a] // a 2, b 1不用再引入第三个变量了。模板字符串字符串拼接的救星js// 以前 var msg 你好 name 你今年 age 岁 // 现在 const msg 你好${name}你今年 ${age} 岁反引号 ${}就是模板字符串的全部。它还支持换行jsconst html div h1${title}/h1 p${content}/p /div 再也不用手写 \n 了。展开运算符三个点的魔法js// 展开数组 const arr [1, 2, 3] const newArr [...arr, 4, 5] // [1, 2, 3, 4, 5] // 展开对象 const user { name: 张三 } const newUser { ...user, age: 18 } // { name: 张三, age: 18 } // 函数传参 Math.max(...arr) // 3... 的核心作用是把东西摊开。它最常用的场景是浅拷贝和合并jsconst state { count: 0, name: 张三 } const newState { ...state, count: 1 } // { count: 1, name: 张三 }React 和 Vue 里的不可变更新全靠它。模块从 require 到 importjs// CommonJSNode.js 传统写法 const fs require(fs) module.exports { foo } // ES ModuleES6 新写法 import fs from fs export { foo } export default fooES Module 是静态的import 必须在顶层不能放在 if 里。这让打包工具能做静态分析和tree-shaking——没用的代码打包时直接删掉。而 CommonJS 是动态的require 可以放在任何地方。Node.js 从 v12 开始支持 ES Module但需要 .mjs 后缀或 type: module。第三章异步三兄弟——Promise、async、await这是 ES6 之后最重要的变化也是新手最容易晕的地方。回调地狱jsgetUser(function(user) { getOrders(user.id, function(orders) { getOrderDetail(orders[0].id, function(detail) { getProduct(detail.productId, function(product) { console.log(product) }) }) }) })这叫回调地狱也叫厄运金字塔。四层嵌套已经算少的真实项目里十层都有。Promise给异步一个承诺jsgetUser() .then(user getOrders(user.id)) .then(orders getOrderDetail(orders[0].id)) .then(detail getProduct(detail.productId)) .then(product console.log(product)) .catch(err console.error(err))Promise 把嵌套变成了链式调用。每个.then返回一个新的 Promise像流水线一样。Promise 有三种状态pending进行中fulfilled成功rejected失败状态一旦改变就不会再变。async / await让异步看起来像同步jsasync function getProductInfo() { try { const user await getUser() const orders await getOrders(user.id) const detail await getOrderDetail(orders[0].id) const product await getProduct(detail.productId) console.log(product) } catch (err) { console.error(err) } }async 函数返回一个 Promiseawait 等待 Promise 完成。看起来像同步代码写起来像同步代码但它是异步的。这就是异步的终极形态——用同步的写法表达异步的逻辑。第四章Node.js——把 JavaScript 搬上服务器ES6 是语法升级Node.js 是战场升级。Node.js 的本质是把 Chrome 的 V8 引擎抽出来加上文件系统、网络、进程等 API让 JavaScript 能跑在服务器上。事件循环Node.js 的心脏jsconsole.log(1) setTimeout(() console.log(2), 0) Promise.resolve().then(() console.log(3)) console.log(4) // 输出1 4 3 2为什么 3 比 2 先输出因为 Node.js 的事件循环分阶段同步代码先执行1、4。微任务Promise.then在当前阶段结束后立即执行3。宏任务setTimeout在下一个阶段执行2。记住一句话微任务永远比宏任务先执行。模块系统require 的秘密js// math.js module.exports { add: (a, b) a b } // index.js const math require(./math) math.add(1, 2)Node.js 的 require 是同步的模块加载后会缓存。第一次 require 会执行模块代码第二次直接读缓存。这就是为什么模块里的单例能生效js// config.js const config { db: mysql } module.exports config // a.js 和 b.js 都 require 同一个 config拿到的是同一个对象Buffer 和 Stream处理大数据的利器jsconst fs require(fs) // 一次性读取适合小文件 const data fs.readFileSync(big.mp4) // 流式读取适合大文件 const stream fs.createReadStream(big.mp4) stream.on(data, chunk { console.log(收到一块数据, chunk.length) }) stream.on(end, () { console.log(读完了) })Buffer 是 Node.js 处理二进制数据的容器。Stream 是分块处理的思想——不把整个文件读进内存而是一块一块处理。这就是为什么 Node.js 能处理几个 G 的日志文件而不爆内存。http 模块手写一个服务器jsconst http require(http) const server http.createServer((req, res) { res.writeHead(200, { Content-Type: application/json }) res.end(JSON.stringify({ message: Hello, Node.js })) }) server.listen(3000, () { console.log(服务器运行在 http://localhost:3000) })这就是一个最基础的 Node.js 服务器。但真实项目里我们通常用框架——比如Express或Koajs// Koa const Koa require(koa) const app new Koa() app.use(async ctx { ctx.body { message: Hello, Koa } }) app.listen(3000)Koa 的中间件是洋葱模型text请求 → 中间件1 → 中间件2 → 中间件3 → 响应 ← ← ←await next() 之前的代码在请求阶段执行之后的代码在响应阶段执行。第五章那些年我们踩过的坑坑一this指向不明jsconst obj { name: 张三, say: () { console.log(this.name) // undefined } }箭头函数的 this 继承外层而外层是全局或模块不是 obj。所以对象的方法别用箭头函数。坑二Promise忘记 catchjsfetchData().then(data { console.log(data) }) // 如果出错会抛出 UnhandledPromiseRejection每个 Promise 链都应该有 .catch或者用 async/await try/catch。坑三forEach里的await不生效js// ❌ 错误 async function process(items) { items.forEach(async item { await doSomething(item) // 不会被等待 }) console.log(完成) // 会先打印 }forEach 不认识 async它只是同步地调用每个回调。正确写法是用 for...ofjs// ✅ 正确 async function process(items) { for (const item of items) { await doSomething(item) } console.log(完成) }坑四require的循环依赖texta.js require b.js b.js require a.js循环依赖时Node.js 返回的是未完成的模块部分导出。这也是为什么 ES Module 更受欢迎——它的静态分析能提前发现问题。终章ES6 和 Node.js 教会我的事写完这一圈我最大的感受是ES6 让 JavaScript 从能跑变成了好写。let/const 解决了变量污染。箭头函数解决了 this 混乱。解构和模板字符串解决了代码啰嗦。Promise 和 async/await 解决了回调地狱。模块解决了代码组织。Node.js 让 JavaScript 从浏览器脚本变成了全栈语言。事件循环让它能高并发。Stream 让它能处理大数据。模块系统让它能组织大型项目。npm 生态让它有无限可能。但工具再好也只是工具。真正的功夫还是在你解决问题的能力上。P.S. 写完这篇文章后我又在 forEach 里写了 await。看来踩过的坑还会再踩一遍。P.P.S. ES6 是 2015 年发布的到现在已经十年了。但每次写 var我还是要愣一下。
阅读完成 · 觉得有帮助?
咨询建站