Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

osdream/learn-nodejs

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 

Repository files navigation

Node.js简介

Node.js 安装

基础

任何程序都是从 Hello World 开始的,那我们先来整一个。

Hello World

第一个Node.js程序

// demo/hello_world/hello_world.js
console.log('Hello World!\n');

Node.js的发展简史

  • 诞生于2009年,创始人:Ryan Dahl

Ryan Dahl: 我经常需要写一些基于事件的程序,我喜欢设计开发基于事件的服务器,因为我觉得他们很容易理解,状态被保存,有可以不断的修改状态。你可以无限的接收socket请求,我可以只使用非阻塞IO就可以使服务器的延迟非常低。

  • 拥有活跃的社区:https://npmjs.org/
  • 发展简史:
    • 2009.2 - Ryan Dahl在博客上宣布准备基于V8创建一个轻量级的Web服务器并提供一套库
    • 2009.5 - Ryan Dahl在GitHub上发布了最初版本的部分Node.js包,随后几个月里,有人开始使用Node.js开发应用
    • 2009.11/2010.4 - JSConf大会安排Node.js讲座
    • 2010年底 - Node.js获得云计算服务商Joyent资助
    • 2011.7 - windows版本发布
    • 2012 - NPM作为内置工具提供/cluster模块

什么是Node.js

  • C++写的

据Node.js创始人Ryan Dahl回忆,他最初希望采用Ruby来写Node.js,但是后来发现Ruby虚拟机的性能不能满足他的要求,后来他尝试采用V8引擎,所以选择了C++语言。

  • 服务器端的JavaScript

    • 为什么选择 JavaScript? 单线程、没有服务端IO处理、无历史包袱(这里的无包袱是指 JavaScript 在服务端的发展几乎是空白)
    • 基于V8引擎的JavaScript运行环境
    • 单线程,高并发:事件驱动、异步I/O
    • CommonJS模块系统:可以像其他语言一样同步的引入外部依赖
  • Node.js 架构图

node.js constructure

Libeio 和 Libev 分别支持的是 Linux、Unix、Mac 等 POSIX 的异步式 I/O 和事件触发,IOCP 是 Windows 下的相关库,Libuv 对以上三者进行了一层封装。IOCP 是 Input/Output Completion Port,输入输出完成接口

都有哪些公司用了Node.js

  • LinkedIn: 全球最大的职业社交网站
    • 用Node代替Rails,减少了27台服务器,速度提升20倍
  • 淘宝
    • cnodejs.org
    • nodejs-kissy: 前端应用框架
    • node-myfox: mysql分布式集群查询系统
  • Microsoft
    • Windows Azure: 基于云计算的操作系统
    • Skype
  • 百度
    • edp: 百度前端开发平台,各种开发工具集
  • 腾讯朋友网: 长连接
  • 新浪网: mysql代理层
  • ...

Node.js基础

  • 跟浏览器端JavaScript的区别

    浏览器端JS Node.js
    ECMAScript + DOM + BOM组成 基于ECMAScript,借助于V8引擎,使用C++编写扩展
  • 跟其他编程语言的区别

    • Node.js不是一门独立语言
    • 基于JavaScript基础的服务端语言
    • 可以跳过Web服务器这一层直接面向前端开发

node.js vs php

事件循环

Node.js的高并发的原因之一是事件循环,什么是事件循环呢?[TODO]

那么事件循环为啥能提高并发量呢?

  • 使用事件循环来处理I/O,而不是采用多线程,避免了线程切换的开销

event loop

事件循环的例子:请求index.html的过程

event loop example

过程

  • 收到对 index.html 的请求
  • 堆栈逐层执行,然后进入事件循环(ev_loop),并睡眠等待
  • 从操作系统读取 index.html 文件内容,触发事件循环中的回调,然后发送给浏览器端

异步I/O

Node.js 高并发的另一个原因是异步I/O

  • 服务器做除了I/O以外的事情,从缓慢的I/O等待中解放出来
  • Node利用JS天生的事件驱动,为I/O绑定回调

同步I/O:

<?php
$result = mysql_query('SELECT * FROM ...');
while ($r = mysql_fetch_array($result)) {
    // do something
}
?>

异步I/O

mysql.query('SELECT * FROM ...', function(err, result) {
    // do something
});
// do something else without waiting db result
  • 同步I/O和异步I/O对比

sync io vs async io

Node.js 的应用场景

适合做什么

  • 具有复杂逻辑的网站;
  • 基于社交网络的大规模 Web 应用;
  • Web Socket 服务器;
  • 命令行工具;
  • 单元测试工具;

不适合做什么

  • 计算密集型程序

     


实践

优化一下前面的 Hello World 程序,需求:

  • 能支持命令行参数
  • 能被其他 Node.js 程序复用
  • 能记录被调用次数

我们先让它支持命令行参数:

// demo/hello_world/hello_world_with_input.js
var name = process.argv[2];
console.log('Hello ' + (name || 'World') + '!\n');

如果要让我们的代码能被其他 Node.js 程序调用,那么我们需要暴露一些函数出来:

// demo/hello_world/hello.js
var name = 'World';

exports.setName = function(newName) {
    name = newName;
};

exports.sayHello = function() {
    console.log('Hello ' + name + '!');
    // 记录调用次数
    increase();
}

var fs = require('fs');
function increase() {
    var count = fs.readFileSync('count.txt', 'utf8');
    count = parseInt(count);
    fs.writeFileSync('count.txt', ++count);
}

我们写一个调用脚本试试:

// demo/hello_world/use_hello.js
var hello = require('./hello');

hello.setName('Node.js');
hello.sayHello();

通过上面的程序我们接触到了一些新东西:

  • 全局对象

process 是个什么东西,为啥能直接使用?process是全局对象,稍后会详细介绍常用的全局对象。

  • 模块

直接通过require就可以使用我们定义的hello模块,同时也可以require内部模块(例如: fs)

全局对象

  • global

全局变量,等同于浏览器里的 window 对象,在同一次脚本运行中该 global 都是指向同一个对象。

可以用 console.log 输出出来看看

console.log(global);
  • process

当前运行 Node.js 相关的运行环境,常用的属性或函数包括:

* process.argv
* process.stdin
* process.stdout
* process.nextTick
  • console

打印日志的 console 对象,跟浏览器里的类似

* console.log
* console.error
* console.trace

还包括不少全局对象,这里就不一一例举了。

模块

模块是 Node.js 中组织功能代码的一个基本封装单元,通过exports暴露变量或方法供其他模块调用

下面会依次介绍:

  • 模块(Module)和包(Package)
  • 模块加载机制
  • 核心模块
  • 模块列表

模块和包

那么什么是模块呢?

模块:一个Node.js文件就是一个模块

  • JS文件 ( *.js )
  • JSON文件 ( *.json )
  • 编译过的C/C++扩展 ( *.node )
  • 包含index.js的文件夹

当我们实现一个较复杂功能时,往往会开发了一堆模块,提供给别人使用的时候,会发现一个问题,因为不是一个整体,使用者使用的时候会很麻烦,那么有啥解决办法吗?包可以解决,包通过将模块整合在一块,对外提供统一的入口。

那么什么是包呢?

包:实现了某个功能的模块集合

  • 包 = 模块集合 + Meta Data(package.json)
  • 模块和包经常混用,没有本质区别

Node.js 有很好的社区:npmjs.org,开发者在上面共享自己开发的包,怎么去使用他们提供的包呢,这就需要包管理器 (npmjs)

包管理器

NPM:Node Package Manager

安装包的命令:

  • npm install somepack
  • npm install -g somepack

其中 -g 参数用于指明是否是安装到全局环境里,对于一些提供了命令行的包来说,安装到全局环境可以使用其提供的命令

模块中的变量

可在模块里直接使用的变量:

  • exports 暴露方法和变量的对象
  • require 引入其他模块的函数
  • module 模块相关信息
  • __filename:当前脚本路径
  • __dirname:当前脚本所处文件夹路径
// demo/module/hello.js
// 可直接使用的变量
console.log('module.exports === exports: ' + (module.exports === exports));
console.log('__filename: ' + __filename);
console.log('__dirname: ' + __dirname);
console.log('module: ');
console.log(module);

var name = 'World';

exports.setName = function(newName) {
    name = newName;
};

exports.sayHello = function() {
    console.log('Hello ' + name + '!');
}

test new module

覆盖 exports

前面介绍了,可以直接在exports上添加变量或方法来暴露给其他模块,如果我们直接想将一个构造函数输出出来,怎么处理呢?

  • 使用module.exports输出对象

在初始情况下 module.exports === exports 这两个其实是同一个对象,不过对 module.exports 赋值之后,exports就不能再用于对外提供变量和函数了(已经不是同一个对象)

// demo/module/hello_class.js
function Hello() {
    // ...
}
module.exports = Hello;
// demo/module/use_hello_class.js
var Hello = require('./hello_class');
var hello = new Hello();

模块加载机制

require的写法:

  • 原生模块:require('http')
  • 相对路径:require('./mod')
  • 绝对路径:require('/home/work/mod')
  • 非原生模块:require('mod')

查找过程:

  • 尝试添加.js、.json、.node后缀
  • 尝试将require的参数作为一个包来进行查找
  • 取出module.paths数组中的下一个目录作为基准查找

单次加载:同一个模块只会加载一次,这个可以从Node.js核心模块代码里看出来

// nodejs/lib/module.js
Module._load = function(request, parent, isMain) {
    // ...
    var filename = Module._resolveFilename(request, parent);
    var cachedModule = Module._cache[filename];
    if (cachedModule) {
        return cachedModule.exports;
    }
    // ...
}
// demo/module/use_hello.js
var hello = require('./hello');
hello.sayHello();
hello.setName('Node.js');
hello.sayHello();

// 单次加载
var hello2 = require('./hello');
hello2.sayHello();
console.log(hello === hello2);

// 加载方法
//var hello = require('./hello');
//var hello = require('./hello.js');
//var hello = require('/home/users/songao/work/node/learn/module/hello');
//var hello = require('hello_in_node_modules');

核心模块

Node.js提供了不少常用的模块,这些模块可以称为内部模块或核心模块

可参见:[http://nodejs.org/api/] [TODO]

  • fs
  • http
  • util
  • events
  • child_process
  • cluster
  • ...

下面介绍几个常用的核心模块

核心模块:fs

使用例子见:demo/core_module/test_fs.js

  • fs.exists(path, callback)
  • fs.stat(path, callback)
  • fs.readdir(path, callback)
  • fs.readFile(path, [options], callback)
  • fs.writeFile(filename, data, [options], callback)

核心模块:http

使用例子见:demo/core_module/test_http.js

  • http.createServer([requestListener])
  • http.request
  • http.get

模块列表

package count

     


Node.js调试方法

开发程序必不可少的就是调试功能,Node.js 提供了多种调试方式。

当然我们也可使用 console.log 来进行调试,就像在浏览器中使用 alert 一样。

下面介绍 Node.js 提供的调试方法:

本地/远程 命令行调试

用 node debug 命令去运行要调试的脚本,可以输入一些命令去控制调试的过程

// 本地调试
node debug test_debug.js

// 远程调试
// debug server
node --debug-brk=8112 test_debug.js
// debug client
node debug localhost:8112
// 常用命令
run                             // 执行脚本,第一行暂停
restart                         // 重启脚本
cont, c                         // continue
next, n                         // next
step, s                         // step into
out, o                          // step out
setBreakpoint(line), sb(line)   // 设置断点
backtrace, bt                   // 显示调用栈
watch(expr)                     // 监视表达式
repl                            // 打开求值环境(Read-Eval-Print Loop)
kill                            // 终止脚本

debug in cmd line

使用node-inspector调试

node-inspector 是一个可以利用浏览器界面去调试的模块,你会发现它的调试界面跟 chrome developer tool 一模一样。

// 安装node-inspector
npm install -g node-inspector

// 启动debug server
node --debug-brk=8112 test_debug.js

// 启动node-inspector
node-inspector --web-port=8113

// 在浏览器中访问如下路径
http://localhost:8113/debug?port=8112

debug width node-inspector

监控重启调试

使用supervisor,脚本修改时会自动重启,这种对于用 Node.js 来做 Web 开发会非常方便,修改完代码,服务器会自动重启,免去了手动重启的麻烦。

// 安装supervisor
npm install -g supervisor

// 用supervisor启动脚本
supervisor web.js

debug width supervisor

其他调试方式

  • Eclipse插件

     


开发网站

Node.js 的一个很重要的应用场景就是开发网站,我们从一个简单网站开始:

一个简单的网站

处理过程:

  • 解析URL,根据path不同做不同处理
  • 处理POST
  • 返回响应(状态码,组装内容)
var http = require('http');
var url = require('url');
var fs = require('fs');
var querystring = require('querystring');
var server = http.createServer(function(req, res) {
    var query = url.parse(req.url, true);
    if (query.path == '/') {
        var postData = '';
        req.setEncoding('utf8');
        req.on('data', function(chunk) {
            postData += chunk;
        });
        req.on('end', function() {
            var post = querystring.parse(postData);
            res.writeHead(200, {
                'Content-Type': 'text/html'
            });
            res.write('<h1>Node.js</h1>');
            res.end('<p>postData: text=' + post.text + '</p>');
        });
    }
    else if (query.path == '/form.html') {
        fs.readFile('form.html', function(err, code) {
            res.writeHead(200, {
                'Content-Type': 'text/html'
            });
            res.end(code);
        });
    }
});
server.listen('8111');
console.log('Server is listening port 8111...');

用Express建站

从前面的小结看出,直接写代码去开发一个网站是比较麻烦的,啥事情都得自己处理。 为此 Express 做了很多封装,便于快速建站。

Express是什么

一个MVC框架

Express 是一个简洁而灵活的 node.js Web应用框架, 提供一系列强大特性帮助你创建各种Web应用 丰富的HTTP工具以及来自Connect框架的中间件随取随用,创建强健、友好的API变得快速又简单

// 安装express-generator命令
npm install -g express-generator
// 运行命令,得到一个简单的WEB应用
express htdocs
// 安装依赖
cd htdocs
npm install

用Express开发一个简单博客系统

需求:

  • 能发表博文
  • 能展示博文列表

实现见 demo/express/web

中间件

Express 是基于 Connect 的,很多对请求和响应的处理都是通过Connect的中间件来做的。 中间件(Middleware)可以理解为一个对用户请求进行过滤和预处理的东西,它一般不会直接对客户端进行响应,而是将处理之后的结果传递下去。

Connect提供的可用的中间件:[https://github.com/senchalabs/connect#middleware]

var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(cookieParser());

middleware

我们还可以自定义中间件:

// req 用于获取请求信息, ServerRequest 的实例
// res 用于响应处理结果, ServerResponse 的实例
// next() 函数用于将当前控制权转交给下一步处理,如果给 next() 传递一个参数时,表示出错信息
app.use(function(req, res, next) {
    var startTime = new Date();
    next();
    var endTime = new Date();
    console.log(endTime - startTime);
});

路由

路由是一个特殊的中间件,用于根据 path 和 query 来转发请求到相应的 Controller (即routes/*)

// 创建router
var router = express.Router();

// 配置routes
// 对任何进入这个router的请求都通过这个处理函数:
router.use(function(req, res, next) {
  // 像其他中间件一样处理请求
  next();
});

// 处理任何以/events结尾的GET请求
// 完整的path(包括/events前的部分)依赖于在哪里使用这个router
router.get('/events', function(req, res, next) {
  // ..
});

// 在 app.js使用上面创建的router
// 只会转发符合 /calendar/* 的请求到router
// 所以上面 events 的route 的完整path应该是:/calendar/**/events
app.use('/calendar', router);
  • 常用方法

    • router.get
    • router.post
    • router.param

模板引擎

Express 的 View 层使用模板引擎来实现的,Express 默认支持 ejs/jade/hogan.js 如果想要使用其他模板引擎,需要对其进行适配,好在模块 consolidate 已经对常用的模板引擎都做了适配,使用起来很方便

// 更换模板引擎
var engines = require('consolidate');
app.engine('html', engines.mustache);

app.set('view engine', 'html');

数据库

开发网站经常要用到数据库,在 npmjs.org 上有对连接各种数据库的封装好的模块

  • mysql模块
var mysql = require('mysql');
var connection = mysql.createConnection({
    'host': 'localhost',
    'port': '3306',
    'user': 'root',
    'password': '123456'
});

connection.query('SELECT * FROM ...', function(err, rows, fields) {
    if (err) throw err;

    console.log('The count is: ', rows.length);
});

实践:博客系统

一般开发过程为:

  • 规划router
  • 准备model
  • 开发view
  • 开发controller

将博客代码分享到npmjs.org

  • 创建包
  • 发布包
  • 更新包
  • 安装包

包(Package)

包 = 模块集合 + 包的Meta Data(package.json)

包的作用:独立功能封装起来,用于发布、更新、依赖管理和版本控制

创建包

npm init 创建package.json

{
    "name": "mypack",         // 包名
    "version": "0.0.1",       // 版本
    "description": "a demo",  // 描述
    "main": "index.js",       // 接口模块
    "scripts": {              // 命令
        "test": "make test"
    },
    "repository": {           // 代码库
        "type": "git",
        "url": "osdream.github.com/mypack"
    },
    "keywords": [             // 关键词,用于npm search
        "demo",
        "package"
    ],
    "author": "osdream <osdream.song@gmail.com> (http://osdream.com)",        // 作者
    "contributors": [         // 贡献者
        {
            "name": "songao",
            "email": "songao@baidu.com"
        }
    ],
    "dependencies": {         // 依赖
        "express": "~3.4.2"
    },
    "devDependencies": {      // 开发依赖
        "supertest": "0.6.0"
    },
    "license": "BSD"
}

包的操作

  • 发布包

    • npm adduser
    • npm whoami
    • npm publish
  • 更新包

    • 修改版本号
    • npm publish
  • 取消发布

    • npm unpublish

Semantic Versioning

  • 一个标准的版本号必须是X.Y.Z的形式
  • X是主版本,Y是副版本,Z是补丁版本
  • http://semver.org
1.2.3             // 必须正好匹配此版本
&gt;1.2.3            // 必须大于此版本
&gt;=1.2.3           // 必须大于等于此版本
&lt;1.2.3            // ...
&lt;=1.2.3           // ...
1.2.3 - 2.3.4     // 等价于:&gt;=1.2.3 且 &lt;=2.3.4
~1.2.3            // 约等于此版本,等价于:&gt;=1.2.3-0 且 &lt;1.3.0-0
1.2.x             // 所有以1.2开头的版本,例如1.2.0, 1.2.1等。等价于:&gt;=1.2.0-0 且 &lt;1.3.0-0
*                 // 匹配所有版本
""                // 空字符串,等价于*
range1 || range2  // 或

     


不足:回调陷阱

Node.js 是基于事件回调的,那么经常出现的一个情况是多个回调会多层嵌套,导致代码不易维护、不好阅读。

解决办法:asyncPromise/Deferred(代表模块:Q)等

举例:

// async 的使用示例:
async.parallel(
    [
        function(callback){ ... },
        function(callback){ ... }
    ],
    function(err, results) { ... }
);

// Q 的使用示例
function getData1() {
    var deferred = Q.defer();
    senRequest(function(data) {
        if (data.success) {
            deferred.resolve(data);
        }
        else {
            deferred.reject('获取数据失败');
        }
    });
    return deferred.promise;
};

function getData2() {
    // similar to getData1
}

getData1()
    .then(function(data) {
        // getData1返回的数据
        console.log(data);

        return getData2();
    })
    .then(function(data) {
        // getData2返回的数据
        console.log(data);
    })
    .fail(function(err) {
        console.log(err);
    })

     


总结

平常我们可以用Node.js做什么

  • Web框架:express
  • HTML模板引擎:jade
  • CSS模板引擎:less
  • JavaScript压缩:uglify-js
  • MySQL数据库:mysql
  • MongoDB ORM:mongoose
  • 自动化测试框架:mocha
  • 命令行解析工具:commander
  • 自动编译工具:grunt
  • 基于WebInspector的调试器:node-inspector
  • DOM处理:jsdom
  • ...

About

learning note of node.js

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published
Morty Proxy This is a proxified and sanitized view of the page, visit original site.