node.js 一个简单的页面输出
最近决定重拾node.js,用它来做一个合并JS文件的东西。由于忘得差不多了,先看能不能输出一个页面来再说。以下是我的一些笔记,省得以后又忘净光……
安装过程就不说了。如果成功是能使用node的命令。node.js调试是非常方便的。每种后台语言都有一个向那个黑黢黢的控制台团输出语用的命令。node.js沿用FF那套东西,也就是console对象与其方法。我们首先建一个example.js文件,内容如下,然后在控制台打开它。
console.log( "hello node.js" ) for ( var i in console){ console.log(i+ " " +console[i]) } |
node example.js。 |
你千万不要在node.js使用alert进行调试,那是浏览器带的全局方法,不报错才怪。
输出结果如下:
var log = function () { process.stdout.write(format.apply( this , arguments) + '\n' ); } var info = function () { process.stdout.write(format.apply( this , arguments) + '\n' ); } var warn = function () { writeError(format.apply( this , arguments) + '\n' ); } var error = function () { writeError(format.apply( this , arguments) + '\n' ); } var dir = function (object) { var util = require( 'util' ); process.stdout.write(util.inspect(object) + '\n' ); } var time = function (label) { times[label] = Date.now(); } var timeEnd = function (label) { var duration = Date.now() - times[label]; exports.log( 'undefined: NaNms' , label, duration); } var trace = function (label) { // TODO probably can to do this better with V8's debug object once that is // exposed. var err = new Error; err.name = 'Trace '; err.message = label || ' '; Error.captureStackTrace(err, arguments.callee); console.error(err.stack); } var assert = function (expression) { if (!expression) { var arr = Array.prototype.slice.call(arguments, 1); require(' assert').ok( false , format.apply( this , arr)); } } |
通过这些函数,我们大概了解到node.js在全局作用域添加了些什么,如require, process。但也不能武断说是,因为它们可能是某个作用域的私有对象。不过,了解这些全局对象,并从这些对象上出发去了解其他对象,非常有助于我们了解node.js的生态结构。在前端,每当浏览器升级,我就遍历一下window对象以及其个元素节点就得知它又增加了什么方法与属性,然后再查文档。那些更新日志不可能把全部细节都告诉你的,必须自己动手遍历一下,这样你就比别人知道得更多。好了,我们去找node.js的全局对象。
node.js的文档告诉我们,有如下几个全局对象:
global, process, require,__filename,__dirname, module |
但我们为什么能直接使用console.log呢?经验告诉我们,console肯定是某全局对象的成员,正如我们可以alert, 也可以window.alert。好了,我们选遍历一下global这个名字取得非常霸气的对象
for ( var i in global){ console.log( "var " + i+ " = " +global[i]) } |
结果如下:
var global = [object global] var process = [object EventEmitter] var GLOBAL = [object global] var root = [object global] var Buffer = function Buffer(subject, encoding, offset) { //太长了,省略 } var setTimeout = function () { var t = NativeModule.require( 'timers' ); return t.setTimeout.apply( this , arguments); } var setInterval = function () { var t = NativeModule.require( 'timers' ); return t.setInterval.apply( this , arguments); } var clearTimeout = function () { var t = NativeModule.require( 'timers' ); return t.clearTimeout.apply( this , arguments); } var clearInterval = function () { var t = NativeModule.require( 'timers' ); return t.clearInterval.apply( this , arguments); } var console = [object Object] |
发现global与浏览器的window一样,都有个指向自身的同名成员。window === window.window, global === global.global。但node.js早期设计得不好,又一搞了个多余的GLOBAL成员。
console.log(global === global.global) //true console.log(global === global.GLOBAL) //true |
我们再遍历module对象:
for ( var i in module){ console.log( "var " + i + " = " +module[i]) } |
结果如下:
var id = . var exports = [object Object] var parent = null var filename = /home/cheng19840218/node/example.js var loaded = false var exited = false var children = var paths = /home/cheng19840218/node/node_modules,/home/cheng19840218/node_modules,/home/node_modules,/node_modules var load = function (filename) { //太长了,省略 } var _compile = function (content, filename) { //太长了,省略 } |
原来那个著名的exports是在此提供的,__filename大概也是filename的引用。只要遍历一下,你就发现许多有趣的东西。但别以为一下秘密就暴光在你眼皮下,还有许多不可遍历属性。比如上面我遍历global对象,只有尞尞可数几个成员,我们可以使用ecma262v5新增的方法去考察一下:
console.log(Object.getOwnPropertyNames(global)) |
结果如下:
[ 'clearInterval' , 'TypeError' , 'decodeURI' , 'Buffer' , 'parseFloat' , 'Number' , 'URIError' , 'encodeURIComponent' , 'RangeError' , 'ReferenceError' , 'RegExp' , 'Array' , 'isNaN' , 'setTimeout' , 'console' , 'Date' , 'Infinity' , 'Boolean' , 'Error' , 'root' , 'NaN' , 'String' , 'Function' , 'Math' , 'undefined' , 'encodeURI' , 'escape' , 'unescape' , 'process' , 'decodeURIComponent' , 'EvalError' , 'clearTimeout' , 'GLOBAL' , 'setInterval' , 'SyntaxError' , 'Object' , 'eval' , 'global' , 'parseInt' , 'JSON' , 'isFinite' ] |
许多人学node.js就立即看其文档,殊不知node.js本身所依赖的V8引擎就拥有许多要学的东西,这其中包括ecma262v5带来的新方法新对象,还有效仿firefox的一些语法:
__defineGetter__ __defineSetter__ __lookupGetter__ __lookupSetter__ set get __proto__ |
不过以"__"开头的东西我是不建议用的,像set与get现在最新的浏览器都支持,如IE9,可以在其开发人员工具下试试下面的脚本:
var a = { get latest () { if ( this .log.length > 0) { return this .log[ this .log.length - 1]; } else { return null ; } }, log: [] } a.log[0] = "a" ; a.log[1] = "b" ; console.log(a.latest) |
在node.js基本上没有兼容问题(如果你不是从早期的node.js玩起来),而且原生对象又加了这么多扩展,再加上node.js自带的库,每个模块都提供了花样繁多的API,如果还嫌不够,github上还有上千个插件。对于想向尝试一下后端编程的JSer来说,这是极具诱惑力的。可能有人说,后端不是涉及数据库操作吗?这与比前端的DOM兼容比起来,不值一提。还有什么文件夹与文件操作 ,你就当成是一种特殊的数组操作就是。因此你完全可以愤愤不平!
好了,我们来点实质的内容吧。node.js本来就是一个http服务器,它是要与前端交互的,因此少不了两个对象:请求(request)与响应(response)。请求与响应显然一种异步的东西,因为我们 不知道前端什么时候发请求过来,响应也不能立即给前端,还要做日志,读写数据库等操作呢。因此对于javascript来说,这用回调函数来实现最好。那么由誰来接受这个回调呢?一个服务器对象!
var http = require( "http" ); http.createServer( function (request, response) { response.writeHead(200, { "Content-Type" : "text/plain" }); response.write( "Hello node.js" ); response.end(); }).listen(8888); |
node.js有个特殊的require,用于同步加载其他模块的对象,这与其他语言的require, import差不多。能同步就是好,不像前端那样一层套一层。然后利用一个函数去实例化一个服务器对象,然后监听8888端口。这是node.js官网最初的例子,大家都写烂了。但这样的程序在现实中一无是处,我们在地址栏上输入URL,你起码要返回一个完整页面给我吧!
对此,我们首先要进行模块化。模块化是以文件为单位的,把example.js更名为server.js,然后再把里面的内容改为一个模块。对于一个node.js的文件,其实它里面的内容是在一个封闭的环境中执行。要想共享给其他模块使用,就必须绑定在exports对象上。
var http = require( "http" ); exports.start = function (){ http.createServer( function (request, response) { console.log( "Request received..." ); response.writeHead(200, { "Content-Type" : "text/plain" }); response.write( "Hello node.js" ); response.end(); }).listen(8888); console.log( "server start..." ); } |
然后我们再建一个index.js作为入口(index.js与server.js放在同一目录下)。
var server = require( "./server" ); server.start(); |
然后建一个index.html页面。
<! doctype html> < html > < head > < title >index</ title > < meta content="IE=8" http-equiv="X-UA-Compatible"/> < meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> </ head > < body > < h2 >这是首页</ h2 > </ body > </ html > |
现在我们就在要请求过来时,把此页的内容读出来,返给用户。这时我们就要用到fs模块的方法了。
var http = require( "http" ); var fs = require( 'fs' ); exports.start = function (){ http.createServer( function (request, response) { fs.readFile( './index.html' , 'utf-8' , function (err, data) { //读取内容 if (err) throw err; response.writeHead(200, { "Content-Type" : "text/html" }); //注意这里 response.write(data); response.end(); }); }).listen(8888); console.log( "server start..." ); } |
好了,这时我们重启再次输入地址,就看到一个完整的页面了。
但一个页面除了HTML结构层外,还有javascript与css。那么,我们在当前目录建一个文件夹javascripts, 里面建index.js,内容如下:
window.onload = function (){ var p = document.createElement( "p" ); p.innerHTML = "这是动态添加的" document.body.appendChild(p); } |
再建一个styles目录,里面建index.css,内容如下:
html,body{ background: #3671A5; height: 100% } |
然后在index.html引入这两个文件:
<! doctype html> < html > < head > < title >index</ title > < meta content="IE=8" http-equiv="X-UA-Compatible"/> < meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> < link type="text/css" rel="stylesheet" href="styles/index.css"/> < script src="/javascripts/index.js"></ script > </ head > < body > < h2 >这是首页</ h2 > </ body > </ html > |
重新打开,发现没有改变,google,说要处理js与css文件的请求。没有办法,取得request.url属性,再判定后缀名,为它进行文件读取与设置首部。
var http = require( "http" ); var fs = require( 'fs' ); var url = require( 'url' ); exports.start = function (){ http.createServer( function (request, response) { var pathname = url.parse(request.url).pathname; var ext = pathname.match(/(\.[^.]+|)$/)[0]; //取得后缀名 switch (ext){ case ".css" : case ".js" : fs.readFile( "." +request.url, 'utf-8' , function (err, data) { //读取内容 if (err) throw err; response.writeHead(200, { "Content-Type" : { ".css" : "text/css" , ".js" : "application/javascript" , }[ext] }); response.write(data); response.end(); }); break ; default : fs.readFile( './index.html' , 'utf-8' , function (err, data) { //读取内容 if (err) throw err; response.writeHead(200, { "Content-Type" : "text/html" }); response.write(data); response.end(); }); } }).listen(8888); console.log( "server start..." ); } |

至此,本文的目的达到了。三个node.js文件,一个普通的js文件,一个css文件,一个html文件。下一个目的就是多页了,一个网站是由多个目的构成的。它包含如下内容,能处理ajax请求,上传文件,Session与Cookie支持,日志,MIME识别,路由派发,缓存系统......要做的事多得吓人,因此有人一上来就框架,与学JS那样,连API还没有摸熟就用jQuery了,那学个毛!回顾一下我们上面的server.js中间的部分,其实就要把MIME与路由拆分出来的。但最重要的事还有一样,如何处理这无穷的函数嵌套?本人觉得这与我的模块加载系统还没有什么两样,下次就从这里动手吧。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
2009-11-20 jQuery源码学习笔记二
2009-11-20 jQuery源码学习笔记一