看到这个题目的时候干后端的别打我。在接触 Socket.io 之前曾经用 PHP + jQuery 写了一个低效的长轮询只有消息同步功能的小聊天室就已经耗尽心力,更不用说利用 PHP 的 Socket 接口写 WebSocket 的聊天室,那更是灾难。
刚才一口气说了一堆大家都困惑的术语,接下来等我解释一下。
原文地址:http://www.toptal.com/nodejs/top-10-common-nodejs-developer-mistakes
原文作者:MAHMUD RIDWAN
转载此译文请注明原文及译文出处,如译文有翻译不当之处还请各位看官指出。
自 Node.js 面世以来,它获得了大量的赞美和批判。这种争论会一直持续,短时间内都不会结束。而在这些争论中,我们常常会忽略掉所有语言和平台都是基于一些核心问题来批判的,就是我们怎么去使用这些平台。无论使用 Node.js 编写可靠的代码有多难,而编写高并发代码又是多么的简单,这个平台终究是有那么一段时间了,而且被用来创建了大量的健壮而又复杂的 web 服务。这些 web 服务不仅拥有良好的扩展性,而且通过在互联网上持续的时间证明了它们的健壮性。
然而就像其它平台一样,Node.js 很容易令开发者犯错。这些错误有些会降低程序性能,有些则会导致 Node.js 不可用。在本文中,我们会看到 Node.js 新手常犯的十种错误,以及如何去避免它们。
1 2 3 4 5 6 7 |
function* generateNaturalNumber() { var i = 0; while(i <= 100) { yield i; i++; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
假设我们有以下目录结构: <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/dir.png"><img class="alignnone size-full wp-image-6423" alt="dir" src="http://www.alloyteam.com/wp-content/uploads/2015/03/dir.png" width="330" height="258" /></a> 用户可能需要打包这个目录下的所有文件,或其中一些文件的组合(在定制组件的场景下)。 我们一般的做法是,提供一个页面,让用户进行选择,然后可以有两种做法: 1. 提交请求到服务器端,服务器对定制化的文件组合进行合并之后打包,返回给客户端进行下载。 2. 在客户端下载所需要的文件,自行进行合并之后打包,保存到本地。 在服务器端合并打包文件应该是比较常见的做法了,这里主要介绍一下浏览器端下载。 <!--more--> 我们先来了解一下 Blob 对象。 <strong>了解 Blob 对象</strong> 一个 Blob 对象一种原生数据的封装,只读,可以用于文件操作。基于 Blob 对象实现的有 File 对象。 创建一个 Blob 对象很简单,使用构造函数: |
1 2 3 4 5 6 7 8 9 10 11 |
// 参数 array 是 ArrayBuffer、ArrayBufferView、Blob、DOMString 对象的一种,或这些对象的混合。 // 参数 options 含两个属性: var array = ['<div id="myId"><a href="http://alloyteam.com">Alloyteam</a></div>'] var options = { type: '', // 默认为空,指定 array 内容的 MIME 类型 endings: 'transparent' // 默认为 'transparent', 指定遇到包含结束符 '/n' 的字符串如何写入 // 'native' 表示结束符转化为与当前用户的系统相关的字符表示, // 'transparent' 表示结束符直接存储到 Blob 中,不做转换。 }; var myBlob = new Blob(array, options); console.log(myBlob); |
1 2 3 4 5 6 7 |
创建了一个 Blob 实例后,它具有两个属性和一个方法 - 两个属性: - size - 大小,单位为字节 - type - MIME 类型,若构造时不指定则默认为空 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// DOMString 类型数据 var array = ['<div id="myId"><a href="http://alloyteam.com">Alloyteam</a></div>'] // 生成 Blob 对象并指定 MIME 类型 var myBlob = new Blob(array, {type: 'text/html'}); // 输出看看有啥 console.log(myBlob); // 大小,单位为字节 var size = myBlob.size; // MIME 类型,创建时不设置则为空 var type = myBlob.type; console.log('size: ', size); console.log('type: ', type); |
1 2 3 4 5 6 |
<a href="http://www.alloyteam.com/wp-content/uploads/2015/03/c.png"><img class="alignnone size-full wp-image-6424" alt="c" src="http://www.alloyteam.com/wp-content/uploads/2015/03/c.png" width="820" height="132" /></a> - 一个 slice 方法 我们可以用这个方法在旧的 Blob 对象基础上切割出一个新的 Blob 对象, 这个方法和 Array.prototype.slice() 用法类似: |
1 2 3 4 5 6 7 8 9 10 |
// 切割, slice 后返回一个新的 blob 对象 // 第一个参数指定切割开始的位置 var myBlob2 = myBlob.slice(10); console.log(myBlob2); // 第二个参数指定切割结束的位置 var myBlob3 = myBlob.slice(10, 30); console.log(myBlob3); // 第三个参数可指定 MIME 类型,若不指定,则继承自 myBlob 的类型 |
1 |
1 2 3 4 5 6 7 8 9 10 11 12 |
<a href="http://www.alloyteam.com/wp-content/uploads/2015/03/e.png"><img class="alignnone size-full wp-image-6425" alt="e" src="http://www.alloyteam.com/wp-content/uploads/2015/03/e.png" width="819" height="152" /></a> slice 方法能干啥? 当我们需要上传一个大文件时,可以用它来将一个文件切割为多个,然后分段上传到服务器。 <strong>使用 Blob 对象</strong> 创建 Blob 对象时,我们传入数据并指定 MIME 类型。 配合 FileReader 我们可以将 Blob 导出几种形式 - 导出为 ArrayBuffer, 定长的二进制数据 |
1 2 3 4 5 |
var myReader1 = new FileReader(); myReader1.onload = function () { console.log('readAsArrayBuffer: ', myReader1.result); }; myReader1.readAsArrayBuffer(myBlob); |
1 2 3 4 5 6 7 8 9 10 |
<a href="http://www.alloyteam.com/wp-content/uploads/2015/03/g.png"><img class="alignnone size-full wp-image-6426" alt="g" src="http://www.alloyteam.com/wp-content/uploads/2015/03/g.png" width="815" height="35" /></a> 打印出来是空的,是因为 console.log 没法显示这种数据类型。 那我们看看这个 ArrayBuffer 的大小,发现它确实是存在的: <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/h.png"><img class="alignnone size-full wp-image-6427" alt="h" src="http://www.alloyteam.com/wp-content/uploads/2015/03/h.png" width="601" height="145" /></a> - 导出为 Text, 纯文本 我们输入的是一个字符串且类型为 text/html, 那输出也自然是原来的字符串文本: |
1 2 3 4 5 |
var myReader2 = new FileReader(); myReader2.onload = function () { console.log('readAsText: ', myReader2.result); }; myReader2.readAsText(myBlob); |
1 2 3 |
<a href="http://www.alloyteam.com/wp-content/uploads/2015/03/j.png"><img class="alignnone size-full wp-image-6428" alt="j" src="http://www.alloyteam.com/wp-content/uploads/2015/03/j.png" width="822" height="37" /></a> - 导出为 DataURL |
1 2 3 4 5 |
var myReader3 = new FileReader(); myReader3.onload = function () { console.log('readAsDataURL: ', myReader3.result); }; myReader3.readAsDataURL(myBlob); |
1 2 3 4 5 6 7 8 |
<a href="http://www.alloyteam.com/wp-content/uploads/2015/03/n.png"><img class="alignnone size-full wp-image-6429" alt="n" src="http://www.alloyteam.com/wp-content/uploads/2015/03/n.png" width="819" height="49" /></a> 我们熟悉的小图片转化为内嵌的 base64 则可以使用 DataURL 来处理 - 导出为 ObjectURL 形式 与 DataURL 不同的是,ObjectURL 创建的是一个躺在内存里的 DOMString, 它不像 DataURL 编码后数据保存到那一串字符串里,DOMString 依赖浏览器环境才能显示 |
1 2 3 4 |
var array = ['<div id="myId"><a href="http://alloyteam.com">Alloyteam</a></div>'] var myBlob = new Blob(array, {type: 'text/html'}); var url = URL.createObjectURL(myBlob); console.log(url); |
1 2 3 4 5 6 7 8 9 10 11 12 |
<a href="http://www.alloyteam.com/wp-content/uploads/2015/03/p.png"><img class="alignnone size-full wp-image-6430" alt="p" src="http://www.alloyteam.com/wp-content/uploads/2015/03/p.png" width="817" height="50" /></a> 我们得到一个以 'blob:' 开头的串,如果我们把它复制到浏览器地址栏中回车,将会发现: <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/q.png"><img class="alignnone size-full wp-image-6431" alt="q" src="http://www.alloyteam.com/wp-content/uploads/2015/03/q.png" width="822" height="185" /></a> 这有什么用呢?下载文件! 比如我们需要下载这个 myBlob 的话,可以配合 a 标签的 download 属性, 将 URL.createObjectURL 返回的数据复制给 a 标签的 href 属性, 再给 a 标签添加 download 属性,则触发点击这个 a 标签后,将会下载文件。 <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/r.png"><img class="alignnone size-full wp-image-6432" alt="r" src="http://www.alloyteam.com/wp-content/uploads/2015/03/r.png" width="1080" height="840" /></a> 若需要指定下载的名字,则给 download 属性赋值,如 |
1 |
<a href="blob:xxx" download="myName">下载</a> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
dataURL 和 objectURL 有啥区别呢?<a href="http://www.alloyteam.com/wp-content/uploads/2015/03/s.png"> <img class="alignnone size-full wp-image-6433" alt="s" src="http://www.alloyteam.com/wp-content/uploads/2015/03/s.png" width="1012" height="526" /> </a> 刷新页面会发现 dataURL 不再变化,而 objectURL 会不断变化: <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/t.png"><img class="alignnone size-full wp-image-6434" alt="t" src="http://www.alloyteam.com/wp-content/uploads/2015/03/t.png" width="1015" height="526" /></a> 原因是,dataURL 创建的是实际的数据,而 objectURL 既然是 DOMString,依赖浏览器环境, 当这个页面一旦关闭(销毁),objectURL 将从内存中删除。 我们可以验证一下: 在浏览器地址栏输入 objectURL: <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/u.png"><img class="alignnone size-full wp-image-6435" alt="u" src="http://www.alloyteam.com/wp-content/uploads/2015/03/u.png" width="755" height="189" /></a> 当我们把创建 objectURL 的页面关闭后,再刷新会发现: <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/v.png"><img class="alignnone size-full wp-image-6436" alt="v" src="http://www.alloyteam.com/wp-content/uploads/2015/03/v.png" width="812" height="171" /></a> 数据不见了!因为浏览器页面关闭后回收了这段内存,那这段 blob: 引用的 DOMString 不再存在。 当然我们也可以手动调用 URL.revokeObjectURL() 的方式来回收。 (当多次调用 URL.createObjectURL 用完后,即时释放内存很重要) 在浏览器地址栏输入 dataURL, 发现 dataURL 是真实的数据,不会随页面关闭而消失: <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/w.png"><img class="alignnone size-full wp-image-6437" alt="w" src="http://www.alloyteam.com/wp-content/uploads/2015/03/w.png" width="1137" height="186" /></a> dataURL 是真实的数据,可以用于对小图片进行编码等操作; objectURL 可以将一个文件转化为 URL 的形式,让我们获得操作文件的能力。 <strong>服务器端与客户端下载文件</strong> 介绍了那么多 Blob, 是不是跑题了。。。 <strong>服务器端下载文件</strong> 服务器端下载文件主要有几步: 1. 根据请求将用户所需要的文件添加到一个临时文件夹 download/xxx 2. 将临时文件夹压缩 download/xxx.zip 3. 将压缩包返回 res.sendFile('download/xxx.zip') 4. 删除临时文件 fs.unlink('download/xxx.zip'); 对于不同的用户,有不同的定制化要求时,生成临时文件夹也必须唯一,所以需要生成唯一的文件夹名。 压缩包生成后,立即删除临时文件夹,并在压缩包成功传回客户端后,立即删除压缩包文件,以节省硬盘空间。 <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/x.png"><img class="alignnone size-full wp-image-6438" alt="x" src="http://www.alloyteam.com/wp-content/uploads/2015/03/x.png" width="425" height="86" /></a> 在这里对每个定制化的请求返回一个压缩包响应后,立即删除了临时文件。 <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/y.png"><img class="alignnone size-full wp-image-6439" alt="y" src="http://www.alloyteam.com/wp-content/uploads/2015/03/y.png" width="865" height="597" /></a> 还有一种做法是对压缩包进行缓存,若发现缓存中存在对应的压缩包,则不再新建。 前者可以立即释放硬盘空间,后者则可以节省计算,各有利弊。 <strong>浏览器端下载文件</strong> 浏览器端下载文件主要有几步: 1. 使用 AJAX 去下载所需要的文件 2. 使用 Blob 对象对文件内容进行存储 3. 使用 JSZip.js 或其他 zip 类库进行压缩 4. 使用 FileSaver.js 或其他 file 接口保存文件 <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/z.png"><img class="alignnone size-full wp-image-6440" alt="z" src="http://www.alloyteam.com/wp-content/uploads/2015/03/z.png" width="865" height="229" /></a> ajax 异步下载文件,如何得知所有文件下载完成呢? 可以自己维护一个计数器,或者使用 Promise 吧! <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/z-a.png"><img class="alignnone size-full wp-image-6441" alt="z-a" src="http://www.alloyteam.com/wp-content/uploads/2015/03/z-a.png" width="790" height="590" /></a> 我这里使用了以下优秀类库 - [jsZip](https://stuk.github.io/jszip/) 用于在浏览器端压缩文件 - [FileSaver](https://github.com/eligrey/FileSaver.js) 用于将文件保存到本地 - [bluebird](https://github.com/petkaantonov/bluebird) 用于控制异步 AJAX 获取文件 <strong>服务器端与浏览器端下载文件的对比</strong> <a href="http://www.alloyteam.com/wp-content/uploads/2015/03/z-b.png"><img class="alignnone size-full wp-image-6442" alt="z-b" src="http://www.alloyteam.com/wp-content/uploads/2015/03/z-b.png" width="862" height="473" /> </a> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
1. 环境依赖: 在服务器端需要部署环境,而对于一些简单的文件合并, 我们可能就只是希望放在 gh-pages 上就能跑, 那在浏览器端进行文件合并与压缩则比较轻量。 2. 可靠程度: 服务器是直接返回一个压缩包, 而浏览器则需要自行下载多个文件后合并, 而其中的文件传输可能会发生错误。 3. 临时文件: 服务器对于不同的定制化请求都会产生临时文件 (是可以把临时文件放到内存里,但是内存比硬盘还贵还小,成本会比较高), 若同一时间定制的请求过多(比如有人恶意 DDOS?), 那么硬盘将会撑满,就只能把躺在硬盘里的苍老师删掉了吧 - -! 而对于浏览器而言,我们可以把文件存放到不同的 CDN 中,加快文件的传输, 并且临时文件存到了用户的硬盘里,就不用删掉服务器里的苍老师了。 4. 缓存功能: 服务器可以做缓存以减少计算,但带来了硬盘的开销。 5. 传输文件: 服务器响应一个请求返回一个压缩包,可以经过 gzip 压缩返回, 而浏览器则要下载多个文件多个 HTTP 请求响应, 但同时可以利用 cdn 进行下载,加快速度。 6. 兼容性: 你懂的。 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<strong>示例代码 </strong> - <a href="http://laispace.github.io/downloadFilesInBrowserAndServer/public/">客户端下载文件</a> - <a href="http://laispace.github.io/downloadFilesInBrowserAndServer/createObjectURL.html">createObjectURL.html</a> - <a href="http://laispace.github.io/downloadFilesInBrowserAndServer/DataURL&ObjectURL.html">DataURL&ObjectURL.html</a> - <a style="font-weight: bold;" href="https://github.com/laispace/downloadFilesInBrowserAndServer">所有代码</a> <strong>参考链接</strong> - <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">[Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob)</a> - <a href="http://en.wikipedia.org/wiki/MIME">[MIME](http://en.wikipedia.org/wiki/MIME)</a> - <a href="https://developer.mozilla.org/en-US/docs/Web/API/File">[File](https://developer.mozilla.org/en-US/docs/Web/API/File)</a> - <a href="https://developer.mozilla.org/en-US/docs/Web/API/FileList">[FileList](https://developer.mozilla.org/en-US/docs/Web/API/FileList)</a> - <a href="https://developer.mozilla.org/en-US/docs/Web/API/FileReader">[FileReader](https://developer.mozilla.org/en-US/docs/Web/API/FileReader)</a> - <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL">[createObjectURL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL)</a> - <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer">[ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)</a> - <a href="https://stuk.github.io/jszip/">[jsZip](https://stuk.github.io/jszip/)</a> - <a href="https://github.com/eligrey/FileSaver.js">[FileSaver](https://github.com/eligrey/FileSaver.js)</a> - <a href="https://github.com/petkaantonov/bluebird">[bluebird](https://github.com/petkaantonov/bluebird) </a> |
NodeJS 对前端来说无疑具有里程碑意义,在其越来越流行的今天,掌握 NodeJS 已经不再是加分项,而是前端攻城师们必须要掌握的技能。本文将与同志们一起完成一个基于 Express+MySQL 的入门级服务端应用,即可以对数据库中的一张表进行简单的 CRUD 操作。但本人还是斗胆认为,通过这个应用,可以让没怎么接触后端开发的同志对使用 Node 进行后端开发有一个大致了解。
Express 工程环境准备
1. 安装 express,和 express 项目种子生成器(什么?你问第 1 步为什么不是安装 NodeJS,我也只能呵呵..)
安装 express
1 |
npm install express -g |
安装 express
1 |
npm install express-generator -g |
2. 创建工程。进入工程目录,运行命令
1 |
express projectName |
目前对于前端工程师而言,如果只针对浏览器编写代码,那么很简单,只需要在页面的 script 脚本中引入所用 js 就可以了。
但是某些情况下,我们可能需要在服务端也跑一套类似的逻辑代码,考虑如下这些情景(以 node 作为后端为例):
1.spa 的应用,需要同时支持服务端直出页面以及客户端 pjax 拉取数据渲染,客户端和服务器公用一套渲染模板并执行大部分类似的逻辑。
2. 一个通过 websocket 对战的游戏,客户端和服务端可能需要进行类似的逻辑计算,两套代码分别用于对用户客户端的展示以及服务端实际数值的计算。
这些情况下,很可能希望我们客户端代码的逻辑能够同时无缝运行在服务端。
LivePool 是一个基于 NodeJS,类似 Fiddler 能够支持抓包和本地替换的 Web 开发调试工具,是 Tencent AlloyTeam 在开发实践过程总结出的一套的便捷的 WorkFlow 以及调试方案。
本篇,主要普及 promise 的用法。
一直以来,JavaScript 处理异步都是以 callback 的方式,在前端开发领域 callback 机制几乎深入人心。在设计 API 的时候,不管是浏览器厂商还是 SDK 开发商亦或是各种类库的作者,基本上都已经遵循着 callback 的套路。