Node.js简介

Node是一个可以让JavaScript运行在服务器端的平台,抛弃了传统平台依靠多线程来实现高并发的设计思路,而采用单线程、异步式I/O、事件驱动式的程序设计模型。

安装和配置Node.js

安装配置简单,无需多说。

Node.js快速入门

异步式I/O与事件式编程

回调函数

用异步的方式读取一个文本内容为“test content”的文件,代码如下:

var fs = require("fs");

fs.readFile("test.txt", "utf-8", function(err, data) {
console.log(data);
}); console.log("end");

运行的结果:

end
test content

使用同步的方式读取文件:

var fs = require("fs");

console.log(fs.readFileSync("test.txt", "utf-8"));
console.log("end");

运行结果:

test content
end

同步的方式是阻塞线程等待文件读取完毕后,将文件内容输出,然后才继续执行下一步代码,因此控制台先打印“test content”,然后再打印“end”。

而异步式I/O是通过回调函数来实现的,通过将控制台输出文件内容的函数作为参数传给fs.readFile函数,fs.readFile调用时只是将异步式I/O请求发送给操作系统,然后立即返回并执行后面的语句,当fs接收到I/O请求完成的事件时,才会调用回调函数。因此我们先看到“end”,再看到“test.txt”的内容。

事件

Node.js所有的异步I/O操作在完成时都会发送一个事件到事件队列。事件由EventEmitter提供,前面提到的fs.readFile的回调函数就是通过EventEmitter来实现。

模块和包

Node提供了require函数来调用其他模块。node中的模块和包并没有本质的区别,包可以理解为某个功能模块的集合。

什么是模块

模块是node应用程序的基本组成部分,文件和模块是一一对应的。一个node.js文件就是一个模块,这个文件可能是JavaScript代码、JSON或者编译过的C/C++扩展。

创建及加载模块

node提供了exports和require两个对象,其中exports是模块的公开接口,require用于从外部获取一个模块的接口。

新建一个myModule.js文件,输入如下代码:

var text;

function setText(myText){
text = myText;
} function printText(){
console.log(text);
} exports.setText = setText;
exports.printText = printText;

再新建一个test.js文件:

var myModule = require("./myModule");

myModule.setText("Hello world!");
myModule.printText();

运行test.js,结果为:

Hello world!

单次加载

模块的调用与创建一个对象不同,require不会重复加载模块。

修改test.js:

var myModule1 = require("./myModule");
myModule1.setText("Hello world!"); var myModule2 = require("./myModule");
myModule2.setText("Hi baby!"); myModule1.printText();
myModule2.printText();

运行结果:

Hi bayby!
Hi bayby!

这是因为模块不会重复加载,myModule1和myModule2指向的都是同一个实例,因此myModule1的text值就被myModule2覆盖了。

创建包

包是模块基础上更深一步的抽象,类似于C/C++的函数库或者java/.net的类库。

node包是一个目录,其中包括一个JSON格式的包说明文件package.json。node对包的要求并不严格,但还是建议制作包时遵循CommonJS规范。

建立一个名为mypackage的文件夹,在文件夹中新建index.js:

exports.hello = function(){
console.log("Hello world");
};

然后在mypackage之外建立test.js:

var mypackage = require("./mypackage");

mypackage.hello();

这就是一个简单的包了,通过定制package.json,我们可以创建更复杂的包。

定制package.json:

  1. 在mypackage文件夹下创建一个package.json文件和一个lib子文件夹。

  2. 将index.js重命名为interface.js并放入lib文件夹。

  3. 打开package.json并输入如下json文本:

    {

    "main": "./lib/interface.js"

    }

运行test.js,依然可以看到控制台输出“Hello world”。

node调用某个包时,会首先检查package.json文件的main字段,将其作为包的接口模块,如果package.json或main字段不存在,就会去寻找index.js或index.node作为包的接口。

node.js包管理器

本地模式和全局模式

npm默认为本地模式,会从http://npmjs.org搜索或下载包 ,将包安装到当前目录的node_modules

子目录下。

也可以使用全局安装,它会将包安装在系统目录:

“npm [install/i] -g [package_name]”

本地模式可以直接通过require使用,但是不会注册环境变量。

全局模式不能直接通过require使用,但是它会注册环境变量。

npm link

通过npm link命令可以在本地包和全局包之间创建符号链接,使全局包可以通过require使用,例如要require全局模式安装的express,可以在工程目录下运行:

npm link express

使用npm link命令还可以将本地包链接到全局,但是npm link命令不支持windows

调试

命令行调试

例如要调试test.js,则在命令行中输入:

node debug test.js

以下为基本的调试命令:

run: 执行脚本,在第一行暂停

restart: 重新执行脚本

cont, c: 继续执行,直到遇到下一个断点

next, n: 单步执行

step, s: 单步执行并进入函数

out, o: 从函数中步出

setBreakpoint(), sb(): 在当前行设置断点

setBreakpoint(‘f()’), sb(...): 在函数f的第一行设置断点

setBreakpoint(‘script.js’, 20), sb(...): 在 script.js 的第20行设置断点

clearBreakpoint, cb(...): 清除所有断点

backtrace, bt: 显示当前的调用栈

list(5): 显示当前执行到的前后5行代码

watch(expr): 把表达式 expr 加入监视列表

unwatch(expr): 把表达式 expr 从监视列表移除

watchers: 显示监视列表中所有的表达式和值

repl: 在当前上下文打开即时求值环境

kill: 终止当前执行的脚本

scripts: 显示当前已加载的所有脚本

version: 显示 V8 的版本

远程调试

使用以下语句之一可以打开调试服务器:

node --debug[=port] test.js	//脚本正常执行,调试客户端可以连接到调试服务器。
node --debug-brk[=port] test.js //脚本暂停执行等待客户端连接。

如不指定端口,则默认为5858。

客户端:

node debug 127.0.0.1:5858

node-inspector

安装node-inspector:

npm install -g node-inspector

在终端连接调试服务器:

node --debug-brk=5858 test.js

启动node-inspector:

node-inspector

在浏览器中打开http://127.0.0.1:8080/debug?port=5858。

Node.js核心模块

全局对象

在浏览器javascript中,通常window就是全局对象,而在node中全局对象是global,它是全局变量的宿主,全局变量是它的属性。

process

用于描述当前node进程状态,提供一个与操作系统的简单接口。

常用成员方法

process.argv:

命令行参数数组,第一个元素是node,第二个元素是脚本文件名,从第三个元素开始每个元素都是一个运行参数。

process.stdout:

标准输出流,通常我们使用的console.log()向标准输出打印字符,而process.stdout.write()函数提供更底层的接口。

process.stdin:

标准输入流。

其他成员方法

process.platform; process.pid; process.execPath; process.memoryUsage()

console

用于提供控制台标准输出。

常用成员方法

console.log():

向标准流打印字符并以换行符结束,可以使用类似C语言printf()命令的格式输出。

console.error():

向标准错误流输出。

console.trace():

向标准错误流输出当前的调用栈。

常用工具util

用于提供常用函数集合。

util.inherits(constructor, superConstructor)

实现对象间原型继承的函数。

var util = require("util");

function Base() {
this.name = "base";
this.base = 1991; this.sayHello = function(){
console.log("hello " + this.name);
};
} Base.prototype.showName = function(){
console.log(this.name);
}; function Sub(){
this.name = "sub";
} util.inherits(Sub, Base); var objBase = new Base();
objBase.showName();
objBase.sayHello();
console.log(objBase); var objSub = new Sub();
objSub.showName();
console.log(objSub);

输出结果:

base
Hello base
{ name: 'base', base: 1991, sayHello: [Function] }
sub
{ name: 'sub' }

Sub只继承了Base在原型中定义的函数,而函数内部的base属性和sayHello函数都没有被继承。而且,在原型中定义的属性不会被console.log()作为对象的属性输出。

util.inspect(object, [showHidden], [depth], [colors])

将任意对象转换为字符串,通常用于调试和错误输出。

object:要转换的对象。

showHidden:如果为true,将会输出更多隐藏信息。

depth:表示最大递归的层数,默认会递归2层,指定为null表示不限制最大递归数完整遍历对象。

color:如果为true,输出格式将会以ANSI颜色编码。

util.inspect不会简单的直接将对象转换成字符成,即便是对象定义了toString方法也不会调用。

其他函数

util.isArray(); util.isRegExp(); util.isDate(); util.isError(); util.format(); util.debug();

事件驱动events

events是node最重要的模块,因为node本身架构就是事件式的,而它提供了唯一的接口。

事件发射器

events.EventEmitter的核心就是事件发射与事件监听器功能的封装。

var events = require("events");

var emitter = new events.EventEmitter();

emitter.on("testEvent", function( arg1, arg2){
console.log(arg1, arg2);
}); emitter.emit("testEvent", "test", 234);

以上为EventEmitter基本用法。

常用API:

EventEmitter.on(event, listener):

为指定事件注册一个监听器,接收一个字符串event和一个回调函数listener。

EventEmitter.emit(event, [arg1], [arg2], [...]):

发射event事件。

EventEmitter.once(event, listener):

为指定事件注册一个单次监听器,监听器触发后立刻解除。

EventEmitter.removeListener(event, listener):

移除指定事件的某个监听器。

EventEmitter.removeAllListener([event]):

移除所有事件的所有监听器,如果指定event,则移除指定事件的所有监听器。

error事件

遇到异常时通常会发射error事件,当error被发射时,如果没有响应的监听器,Node会把它当做异常,退出程序并打印调用栈。

继承EventEmitter

大多数时候不要直接使用EventEmitter,而是在对象中继承它。这样做使某个实体功能的对象实现事件符合语义,而且javascript的对象机制是基于原型的,支持部分多重继承。

文件系统

fs模块是文件操作的封装,fs模块所有操作都提供了异步和同步两个版本。

fs.readFile(filename, [encoding], [callback(err, data)]):

读取文件,默认以二进制模式读取。

fs.readFileSync(filename, [encoding]):

以同步方式读取文件,读取的文件内容以返回值的形式返回。

fs.open(path, flags, [mode], [callback(err, fd)]):

其中flags可以是以下值:

  • r:以读取模式打开文件。
  • r+:读写。
  • w:写入,如果文件不存在则创建。
  • w+:读写,如果文件不存在则创建。
  • a:追加,如果文件不存在则创建。
  • a+:读取追加,如果文件不存在则创建。

mode参数用于创建文件时给文件指定权限,默认为0666。回调函数将会传递一个文件描述符fd。

fs.read(fd, buffer, offset, length, postion, [callback(err, bytesRead)]):

从指定的文件描述符fd中读取数据并写入buffer指向的缓冲区对象。offset是buffer的写入偏移量。length是要从文件中读取的字节数。position是文件读取的起始位置,如果为null,则从当前文件指针的位置读取。回调函数传递bytesRead和buffer,分别表示读取的字节数和缓冲区对象。

HTTP服务器和客户端

node提供了http模块。http.server是一个基于事件的HTTP服务器,http.request则是一个HTTP客户端工具。

HTTP服务器

http.server是HTTP服务器对象。

http.createServer(callback(request, response)):

创建一个http.server的实例,将一个函数作为HTTP请求处理函数。

http.sever的事件

request:

当客户端请求到来时,该事件被触发,提供两个参数http.ServerRequest和http.ServerResponse的实例。事实上http.createServer的显示实现方式就是request事件:

var http = require('http');
var server = new http.Server();
server.on('request', function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>Node.js</h1>');
res.end('<p>Hello World</p>');
});
server.listen(3000);

connection:

当TCP连接建立时,该事件被触发,提供一个socket参数,是net.Socket的实例。客户端在Keep-Alive模式下可能会在同一个连接内发送多次请求。

close:

当服务器关闭时,该事件触发。

http.ServerRequest

是HTTP请求的信息。一般由http.server的request事件发送,作为第一个参数传递。

HTTP请求一般可以分为两部分:请求头(Reqeust Header)和请求体(Request Body)。http.ServerRequest提供了以下3个事件用于控制Request Body传输:

  • data:当请求体数据到来时,该事件触发。事件提供一个参数chunk,表示接收到的数据。该事件可能被调用多次。
  • end:当请求体数据传输完成时,该事件触发。
  • close:用户当前请求结束时,该事件触发。当用户强制终止传输,也还是调用close。

ServerRequest的属性:

  • complete:客户端请求是否已经完成。
  • httpVersion:HTTP协议版本。
  • method:HTTP请求方法,如GET、POST、PUT等。
  • url:原始的请求路径。
  • headers:HTTP请求头。
  • trailers:HTTP请求尾。
  • connection:当前HTTP连接套接字,为net.Socket的实例。
  • socket:connection属性的别名。
  • client:client属性的别名。

http.ServerResponse

返回给客户端的信息,由http.Server的request事件发送,作为第二个参数传递。

成员函数:

** response.writeHead(statusCode, [headers]):**

向请求的客户端发送响应头。statusCode是HTTP状态码。该函数在一次请求中最多只能调用一次。

response.write(data, [encoding]):

向请求的客户端发送响应内容。data是一个buffer或字符串,表示要发送的内容。如果data是字符串,需要指定encoding来说明它的编码方式,默认为utf-8。

response.end([data],[encoding]):

结束响应,当所有要返回的内容发送完毕的时候,该函数必须被调用一次,否则客户端永远处于等待状态。

HTTP客户端

http.request(options, callback):

发起HTTP请求,返回一个http.ClientRequest的实例。callback是请求的回调函数,传递一个参数为http.ClientResponse的实例。option常用的参数如下:

  • host:请求网站的域名或IP地址。
  • port:请求网站的端口,默认80。
  • method:请求方法,默认GET。
  • path:请求相对于根的路径,默认是“/”,包含QueryString,例:“/search?s=sb”。
  • headers:请求头对象。

http.get(options, callback):

http.request的简化版,用于处理GET请求,同时不需要手动调用req.end()。

http.clientRequest:

表示一个已经产生而且正在进行的HTTP请求。提供一个response事件。http.clientRequest函数:

  • request.abort():终止正在发送的请求。
  • request.setTimeout(timeout, [callback]):设置请求超时事件,超时后callback将被调用。

http.clientResponse:

与http.ServerResponse类似,提供了三个事件data、end和close。http.clientResponse特殊函数:

  • response.setEncoding([encoding]):设置编码方式,默认null,以buffer形式存储。
  • response.pause():暂停接收数据和发送事件。
  • response.resume():从暂停状态恢复。

使用Node.js进行Web开发

Express

安装

运行命令:

npm install -g express

如果npm安装慢的话可以指定中国镜像(更新比官方慢,发布包时需要切回来):

npm config set registry https://registry.npm.taobao.org
npm info underscore

windows下可能还需要安装express-generator:

npm install -g express-generator

建立工程

进入工程目录,建立工程:

express -e projectName

控制台输出命令提示还要进入工程目录,执行npm install,按照提示执行后,node根据package.json文件自动安装了ejs。无参数的 npm install 的功能就是检查当前目录下的 package.json,并自动安装所有指定的依赖。

启动服务器。express 4.x已经不能用之前的node app.js来启动了,而应该使用:

npm start

要使用supervisor监控文件更改自动重启服务器,可以使用:

supervisor ./bin/www

工程结构

app.js

app.js是工程的入口,用var app = express()创建一个应用实例,app.set(key, value)是Express的参数设置工具,可用参数如下:

  • basepath:基础地址。
  • views:视图文件的目录,存放模板文件。
  • view engine:视图模板引擎。
  • view options:全局视图参数对象。
  • view cache:启用视图缓存。
  • case sensitive routes:路径区分大小写。
  • strict routing:严格控制,启用后不会忽略路径末尾的“/”。
  • jsonp callback:开启透明的JSONP支持。

Express依赖于connect,提供大量中间件,可以通过app.use启用。

routes/index.js

路由文件,相当于MVC中的控制器。

index.ejs

模板文件。

路由控制

路径匹配

可以给路由配置规则,如:

router.get('/:username', function(req, res, next) {
res.send(req.params.username);
});

路径规则还支持JavaScript正则表达式,如:app.get(/user/([^/]+)/?, callback),但是这种方式参数是匿名的,因此需要通过req.params[0]这样的方式获取。

REST风格的路由规则

HTTP协议定义了以下8种标准的方法:

  • GET:请求获取指定资源。
  • HEAD:请求指定资源的响应头。
  • POST:向指定资源提交数据。
  • PUT:请求服务器存储一个资源。
  • DELETE:请求服务器删除指定资源。
  • TRACE:回显服务器收到的请求,主要用于测试或诊断。
  • CONNECT:HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。
  • OPTIONS:返回服务器支持的HTTP请求方法。

我们常用到的是四种,根据REST设计模式,这四种分别用于以下功能:

  • GET:获取
  • POST:新增
  • PUT:更新
  • DELETE:删除

Express支持所有HTTP请求的绑定函数,如:app.get(path, callback)、app.post(path, callback)等。

其中还包括一个app.all(path, callback)函数,它支持吧所有的请求方式绑定到同一个响应函数。

Express还支持同一路径绑定多个路由响应函数,例:

var express = require('express');
var router = express.Router(); /* GET users listing. */
router.all('/:username', function(req, res, next) {
res.send("all");
}); router.get('/:username', function(req, res, next) {
res.send(req.params.username);
}); module.exports = router;

如果访问路径同时匹配多个规则时,会优先匹配先定义的路由规则,请求总会被前一条路由规则捕获,后一条被忽略,因此上面代码将输出“all”。

但是也可以调用next,会将路由优先给后面的规则:

var express = require('express');
var router = express.Router(); /* GET users listing. */
router.all('/:username', function(req, res, next) {
console.log("all");
next();
res.send("all");
}); router.get('/:username', function(req, res, next) {
res.send(req.params.username);
}); module.exports = router;

这时回先在控制台打印“all”,然后被next()函数转移,接着被第二条规则捕获,向浏览器发送信息。

这一点非常有用,可以让我们非常轻松实现中间件,提高代码的复用,如:

var users = {
'byvoid': {
name: 'Carbo',
}
};
app.all('/user/:username', function (req, res, next) {
// 检查用户是否存在
if (users[req.params.username]) {
next();
} else {
next(new Error(req.params.username + ' does not exist.'));
}
});
app.get('/user/:username', function (req, res) {
// 用户一定存在,直接展示
res.send(JSON.stringify(users[req.params.username]));
});
app.put('/user/:username', function (req, res) {
// 修改用户信息
res.send('Done');
});

模板引擎

这里使用的ejs引擎,设置视图的路径及模板引擎的类型:

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

在routers/index.js下使用render调用:

  res.render('index', { title: 'Index' });

ejs只有3种标签:

  • <% code %>:JavaScript 代码。
  • <%= code %>:显示替换过 HTML 特殊字符的内容。
  • <%- code %>:显示原始 HTML 内容。

MongoDB

开源的NoSQL数据库,适合数据规模大、事务性不强的场合。

NoSQL是Not Only SQL的简称,主要指非关系型、分布式、不提供ACID的数据库系统。

MongoDB是一个对象数据库,没有表、行等概念,也没有固定的模式和结构,所有数据以文档的形式存储。所谓文档就是一个关联数组式的对象,它的内部由属性组成,一个属性对应的值可能是一个数、字符串、日期、数组,甚至是一个嵌套的文档。例:

{
"_id": ObjectId( "4f7fe8432b4a1077a7c551e8" ),
"uid": 2004,
"username": "byvoid",
"net9": {
"nickname": "BYVoid",
"surname": "Kuo",
"givenname": "Carbo",
"fullname": "Carbo Kuo",
"emails": [
"byvoid@byvoid.com",
"byvoid.kcp@gmail.com"
],
"website": "http://www.byvoid.com",
"address": "Zijing 2#, Tsinghua University"
}
}

Node.js进阶话题

模块加载机制

控制流

Node.js应用部署

Node.js不适合的场景

计算密集型的程序

单线程特性。

单用户多任务型应用

如本地的命令行工具或图形界面。

发挥不出高并发的优势,不擅长处理多进程相互协作。

逻辑十分复杂的事务

Node.js控制流不是线性的,它被一个个事件拆散。Node更擅长处理逻辑简单但访问频繁的任务。

Unicode与国际化

不支持完整的Unicode。

JavaScript的高级特性

作用域

函数作用域

不同于大多类C语言,由一对花括号封闭的代码块就是一个作用域,javascript的作用域是通过函数来定义的,在一个函数中定义的变量只对这个函数内部可见。在函数引用一个变量时,javascript会先搜索当前函数作用域,如果没有则搜索其上层作用域,一直到全局作用域。例:

var scope = 'global';
var f = function () {
console.log(scope); // 输出 undefined
var scope = 'f';
}
f();

最后输出结果不是global,而是undefined。在console.log函数访问变量scope时,javascript会先搜索函数f的作用域,结果找到了var scope = 'f',所以就不会再往上层去找,但执行到console.log时,scope还没有被定义,所以是undefined。

全局作用域

全局作用域中的变量不论在什么函数中都可以被直接引用,而不必通过全局对象。满足以下条件的变量属于全局作用域:

  • 在最外层定义的变量
  • 全局对象的属性
  • 任何地方隐式定义的变量(未定义直接赋值的变量)

需要格外注意的是第三点,在任何地方隐式定义的变量都会定义在全局作用域中,即不通过 var 声明直接赋值的变量。这一点经常被人遗忘,而模块化编程的一个重要原则就是避免使用全局变量,所以我们在任何地方都不应该隐式定义变量。

闭包

闭包是由函数(环境)及其封闭的自由变量组成的集合体。

javascript中每一个函数都是一个闭包,但通常意义上嵌套的函数更能体现闭包的特性。

var generateClosure = function () {
var count = 0;
var get = function () {
count++;
return count;
};
return get;
};
var counter1 = generateClosure();
var counter2 = generateClosure();
console.log(counter1()); // 输出 1
console.log(counter2()); // 输出 1
console.log(counter1()); // 输出 2
console.log(counter1()); // 输出 3
console.log(counter2()); // 输出 2

闭包的特性,当一个函数返回它内部定义的一个函数时,就产生了一个闭包,闭包不但包括被返回的函数,还包括这个函数的定义环境。counter1和counter2分别调用了generateClosure()函数,生成两个闭包实例,它们内部引用的count变量分别属于各自的运行环境。我们可以理解成,在generateClosure()返回get函数时,私下将get可能引用到的generateClosure()函数的内部变量(也就是count变量)也返回了,并在内存中生成了一个副本。

闭包的用途

闭包两个主要用途,一是实现嵌套的回调函数,二是隐藏对象的细节。

嵌套的回调回函

exports.add_user = function (user_info, callback) {
var uid = parseInt(user_info['uid']);
mongodb.open(function (err, db) {
if (err) { callback(err); return; }
db.collection('users', function (err, collection) {
if (err) { callback(err); return; }
collection.ensureIndex("uid", function (err) {
if (err) { callback(err); return; }
collection.ensureIndex("username", function (err) {
if (err) { callback(err); return; }
collection.findOne({ uid: uid }, function (err) {
if (err) { callback(err); return; }
if (doc) {
callback('occupied');
} else {
var user = {
uid: uid,
user: user_info,
};
collection.insert(user, function (err) {
callback(err);
});
}
});
});
});
});
});
};

每一层的嵌套都是一个回调函数,回调函数不会立即执行,而是等待相应请求处理完后由请求的函数回调。我们可以看到在嵌套的每一层都有对callback的引用,而最里层还用到了外层定义的uid变量。由于闭包的存在,即使外层函数已经执行完毕,其作用域内申请的变量也不会释放。

实现私有成员

javascript对象没有私有属性,只能通过闭包实现。

var generateClosure = function () {
var count = 0;
var get = function () {
count++;
return count;
};
return get;
};
var counter1 = generateClosure();
var counter2 = generateClosure();
console.log(counter1()); // 输出 1
console.log(counter2()); // 输出 1
console.log(counter1()); // 输出 2
console.log(counter1()); // 输出 3
console.log(counter2()); // 输出 2

只有调用counter()才能访问到闭包内的count变量。

对象

javascript中的对象不是基于类的实例的,而是基于原型。

创建和访问

javascript对象实际上就是一个由属性组成的关联数组,属性由名称和值组成,值的类型可以是任何数据类型,或者函数和其他对象。javascript具有函数式编程的特性,所以函数也是一种变量。

javascript中可以用以下方法创建一个简单的对象:

var foo = {};
foo.prop_1 = 'bar';
foo.prop_2 = false;
foo.prop_3 = function(){
return 'hello world';
}
console.log(foo.prop_3());

使用关联数组访问对象成员

var foo = {};
foo['prop_1'] = 'bar';
foo['prop_2'] = false;
foo['prop_3'] = function(){
return 'hello world';
}
console.log(foo['prop_3']);

使用对象初始化器创建对象

var foo = {
prop1: 'bar',
prop2: 'false',
prop3: function (){
return 'hello world';
}
};

构造函数

function User(name, uri) {
this.name = name;
this.uri = uri;
this.display = function() {
console.log(this.name);
}
}

使用new来创建对象:

var user1 = new User('name', 'www.google.com');

上下文对象

javascript中,上下文对象就是this指针,即被调用函数所处的环境。上下文对象的作用是在一个函数内部引用调用它的对象本事。

var someuser = {
name: 'byvoid',
display: function () {
console.log(this.name);
}
};
someuser.display(); // 输出 byvoid
var foo = {
bar: someuser.display,
name: 'foobar'
};
foo.bar(); // 输出 foobar

this指针不属于某个函数,而是函数调用时所属的对象。

javascript的函数式编程特性使得函数可以像一般的变量一样赋值、传递和计算。

var someuser = {
name: 'byvoid',
func: function () {
console.log(this.name);
}
};
var foo = {
name: 'foobar'
};
someuser.func(); // 输出 byvoid
foo.func = someuser.func;
foo.func(); // 输出 foobar
name = 'global';
func = someuser.func;
func(); // 输出 global

使用不同的引用来调用同一个函数,this指针永远是这个引用所属的对象。

call和apply

call和apply的功能是以不同的对象作为上下文来调用某个函数。简而言之,就是允许一个对象去调用另一个对象的成员函数。

call和apply的功能是一致的,两者细微的差别在于call以参数表来接受被调用函数的参数,而apply以数组来接受被调用函数的参数。

func.call(thisArg[, arg1[, arg2[, ...]]])
func.apply(thisArg[, argsArray])

看一个call的例子:

var someuser = {
name: 'byvoid',
display: function (words) {
console.log(this.name + ' says ' + words);
}
};
var foo = {
name: 'foobar'
};
someuser.display.call(foo, 'hello'); // 输出 foobar says hello

someuser.display是被调用的函数,它通过call将上下文改变为foo对象,因此函数体内访问this.name时,实际上访问的是foo.name,因而输出foobar。

bind

使用call和apply方法可以改变被调用函数的上下文,而使用bind可以永久地绑定函数的上下文,使其无论被谁调用,上下文都是固定的。

func.bind(thisArg[, arg1[, arg2[, ...]]])

例:

var someuser = {
name: 'byvoid',
func: function() {
console.log(this.name);
}
};
var foo = {
name: 'foobar'
};
foo.func = someuser.func;
foo.func(); // 输出 foobar
foo.func1 = someuser.func.bind(someuser);
foo.func1(); // 输出 byvoid
func = someuser.func.bind(foo);
func(); // 输出 foobar
func2 = func;
func2(); // 输出 foobar

bind绑定参数

bind还有一个重要功能:绑定参数。

var person = {
name: 'byvoid',
says: function (act, obj) {
console.log(this.name + ' ' + act + ' ' + obj);
}
};
person.says('loves', 'diovyb'); // 输出 byvoid loves diovyb
byvoidLoves = person.says.bind(person, 'loves');
byvoidLoves('you'); // 输出 byvoid loves you

byvoidLoves将this指针绑定到了person,并将第一个参数绑定到loves,之后再调用byvoidLoves时,只需要传入obj参数。这样可以在代码多出调用时省略重复输入相同的参数。

原型

function Person() {
}
Person.prototype.name = 'BYVoid';
Person.prototype.showName = function () {
console.log(this.name);
};
var person = new Person();
person.showName();

上面代码使用原型初始化对象,这样与直接在构造函数内定义属性的区别:

  • 继承方式不同,子对象需要显示调用调用父对象才能继承构造函数内定义的属性。

  • 构造函数内定义的任何属性,包括函数在内都会被重新创建,同一个构造函数产生两个不共享的实例。

  • 构造函数内定义的函数有运行时闭包的开销。

    function Foo() {

    var innerVar = 'hello';

    this.prop1 = 'BYVoid';

    this.func1 = function () {

    innerVar = '';

    };

    }

    Foo.prototype.prop2 = 'Carbo';

    Foo.prototype.func2 = function () {

    console.log(this.prop2);

    };

    var foo1 = new Foo();

    var foo2 = new Foo();

    console.log(foo1.func1 == foo2.func1); // 输出 false

    console.log(foo1.func2 == foo2.func2); // 输出 true

原型和构造函数适用场景:

  • 除非必须用构造函数闭包,否则尽量用原型定义成员函数,因为这样可以减少开销。
  • 尽量在构造函数内定义一般成员,尤其是对象或数组,因为用原型定义的成员是多个实例共享的。

原型链

javascript中有两个特殊对象:Object和Function,它们都是构造函数,用于生产对象。Object.prototype是所有对象的祖先,Function.prototype是所有函数的原型,包括构造函数。

任何对象都有一个_proto_属性,它指向该对象的原型,从任何对象沿着它开始遍历都可以追溯到Object.prototype。

构造函数对象的prototype属性,指向一个原型对象。原型对象有constructor属性,指向它的构造函数。

function Foo() {
}
Object.prototype.name = 'My Object';
Foo.prototype.name = 'Bar';
var obj = new Object();
var foo = new Foo();
console.log(obj.name); // 输出 My Object
console.log(foo.name); // 输出 Bar
console.log(foo.__proto__.name); // 输出 Bar
console.log(foo.__proto__.__proto__.name); // 输出 My Object
console.log(foo.__proto__.constructor.prototype.name); // 输出 Bar

对象的复制

javascript没有C语言中的指针,所有对象类型的变量都是指向对象的引用,两个变量之间的赋值是传递引用。如果需要完整地复制一个对象,就需要手动实现这样的函数,一个简单的做法就是复制对象的所有属性。

Object.prototype.clone = function() {
var newObj = {};
for (var i in this) {
newObj[i] = this[i];
}
return newObj;
}
var obj = {
name: 'byvoid',
likes: ['node']
};
var newObj = obj.clone();
obj.likes.push('python');
console.log(obj.likes); // 输出 [ 'node', 'python' ]
console.log(newObj.likes); // 输出 [ 'node', 'python' ]

上面代码是一个对象的浅拷贝,只复制基本类型的属性。实现深拷贝需要使用递归的方式来实现:

Object.prototype.clone = function () {
var newObj = {};
for (var i in this) {
if (typeof (this[i]) == 'object' || typeof (this[i]) == 'function') {
newObj[i] = this[i].clone();
} else {
newObj[i] = this[i];
}
}
return newObj;
};
Array.prototype.clone = function () {
var newArray = [];
for (var i = 0; i < this.length; i++) {
if (typeof (this[i]) == 'object' || typeof (this[i]) == 'function') {
newArray[i] = this[i].clone();
} else {
newArray[i] = this[i];
}
}
return newArray;
};
Function.prototype.clone = function () {
var that = this;
var newFunc = function () {
return that.apply(this, arguments);
};
for (var i in this) {
newFunc[i] = this[i];
}
return newFunc;
};
var obj = {
name: 'byvoid',
likes: ['node'],
display: function () {
console.log(this.name);
},
};
var newObj = obj.clone();
newObj.likes.push('python');
console.log(obj.likes); // 输出 [ 'node' ]
console.log(newObj.likes); // 输出 [ 'node', 'python' ]
console.log(newObj.display == obj.display); // 输出 false

上面方法在大多数情况下都没有问题,但是遇到相互引用的对象时就会进入死循环,如:

var obj1 = {
ref: null
};
var obj2 = {
ref: obj1
};
obj1.ref = obj2;

遇到这个问题必须设计一套图论算法,分析对象之间的依赖关系,然后分别一次复制每个顶点,并重新建立它们之间的关系。

Node.js编程规范

缩进

因为node.js中很容易写出深层的函数嵌套,因此选择两空格缩进。

行宽

80字符。

语句分隔符

使用分号。

变量定义

永远使用var定义变量。

通过赋值隐式变量总是全局变量,会造成命名空间污染。

变量名和属性名

小驼峰式命名法。

函数

一般的函数使用小驼峰式命名法。但对于对象的构造函数名称,使用大驼峰式命名法。

引号

使用单引号,因为JSON、XML都规定了必须是双引号,这样便于无转义地直接引用。

关联数组的初始化

除非键名之中有空格或非法字符,否则一律不使用引号。

等号

尽量使用=而不是,因为==包含了隐式类型转换,很多时候可能与预期不同。

var num = 9;
var literal = '9';
if (num === literal) {
console.log('9==="9"');
}
var num = 9;
var literal = '9';
if (num == literal) {
console.log('9=="9"');
}

输出

9=="9"

命名函数

尽量给构造函数和回调函数命名,这样调试时可以看见更清晰的调用栈。

对于回调函数,第一个参数是错误对象err,如果没有错误发生,其值为undefined。

对象定义

尽量将所有的成员函数通过原型定义,将属性在构造函数内定义,然后对构造函数使用new关键字创建对象。

继承

避免使用复杂的继承,尽量使用Node.js的util模块中提供的inherits函数。

《Node.js开发指南》知识整理的更多相关文章

  1. Node.js开发指南中的例子(mysql版)

    工作原因需要用到nodejs,于是找到了<node.js开发指南>这本书来看看,作者BYVoid 为清华大学计算机系的高材生,年纪竟比我还小一两岁,中华地广物博真是人才辈出,佩服. 言归正 ...

  2. 学习Nodejs:《Node.js开发指南》微博项目express2迁移至express4过程中填的坑

    <Node.js开发指南>项目地址https://github.com/BYVoid/microblog好不容易找到的基础版教程,但书中是基于express2的,而现在用的是express ...

  3. 《node.js开发指南》partial is not defined的解决方案

    由于ejs的升级,<node.js开发指南>中使用的 partial 函数已经摒弃,使用foreach,include代替 原来的代码是: <%- partial('listitem ...

  4. NODE.JS开发指南学习笔记

    1.Node.js是什么 Node.js是一个让JS运行在服务器端的开发平台,它可以作为服务器向用户提供服务.Node.js中的javascript只是Core javascript,或者说是ECMA ...

  5. 《node.js开发指南》读书笔记(一)

    在开发时如果修改了js内容,不能通过刷新浏览器直接看到效果,必须通过重启nodejs程序才能看到,这样显然不利于开发调试,supervisor可以实现这个功能,监视对代码的改动,并自动重启nodejs ...

  6. Node.js 开发指南笔记

    第一章:node简介 介绍了node是什么:node.js是一个让javascript运行在服务器端的开发平台, node能做些什么:[书上的] 具有复杂逻辑的网站 基于社交网络的大规模Web应用 W ...

  7. Node.js 开发指南

    1.Node.js 简介 Node.js 其实就是借助谷歌的 V8 引擎,将桌面端的 js 带到了服务器端,它的出现我将其归结为两点: V8 引擎的出色: js 异步 io 与事件驱动给服务器带来极高 ...

  8. node.js开发指南读书笔记(1)

    3.1 开始使用Node.js编程 3.1.1 Hello World 将以下源代码保存到helloworld.js文件中 console.log('Hello World!'); console.l ...

  9. NODE.JS开发指南学习笔记2

    1.核心模块 核心模块是Node.js的心脏,由一些精简高效的库组成,为其提供了基本的API.2.全局对象 global.所有的的全局变量都是其属性.其根本的作用是作为全局变量的宿主.3.全局变量 1 ...

随机推荐

  1. android performClick使用

    performClick 是使用代码主动去调用控件的点击事件(模拟人手去触摸控件) ----------------------------------------- boolean android. ...

  2. WebView中的视频全屏的相关操作

    近期工作中,基本一直在用WebView,今天就把它整理下: WebView 顾名思义,就是放一个网页,一个看起来十分简单,可是用起来不是那么简单的控件. 首先你肯定要定义,初始化一个webview,事 ...

  3. 高仿“点触验证码”做的一个静态Html例子

    先上源码: <html> <head> <title>TouClick - Designed By MrChu</title> <meta htt ...

  4. HNC-局部联想脉络

    局部联想脉络 概念分为:抽象概念.具体概念 对抽象概念用 五元组 和 语义网络 表达 对具体概念用 挂靠展开近似 表达 五元组:动态.静态.属性.值.效应.(u,g,u,z,r),用于表达抽象概念的外 ...

  5. linux命令chown修改文件所有权

      Changing User Ownership To apply appropriate permissions, the first thing to consider is ownership ...

  6. linux groupmems命令

    Because users group membership is defined in two different locations, it can be difficult to find ou ...

  7. java08双重循环打印图形

    // 九九乘法表 外层循环每执行一次,内层循环执行一遍 for (int i = 1; i <= 9; i++) { // 外层控制的是行数 for (int j = 1; j <= i; ...

  8. (@DBRef)spring-data-mongodb

    @DBRef用在哪些地方 已知的有 @DBRefprivate Shop product; @DBRefprivate List<Account> accounts;    如果不加@DB ...

  9. sping注解原理

    持续更新中.. spring注解用的是java注解,用到的是java反射机制. 参考文档如下: http://zxf-noimp.iteye.com/blog/1071765 对应spring源码如下 ...

  10. JavaWeb学习笔记之JSP(一)

    1. JSP: 1.1. 为什么需要 JSP ? 如果使用Servlet程序来输出只有局部内容需要动态改变的网页,但是其中的静态网页内容也需要程序员使用Java语言来进行输出,这就造成了大量代码的冗余 ...