Node是一个可让JavaScript运行在服务器端的平台,抛弃了传统平台依靠多线程来实现高并发的设计思路,而采用单线程、异步式I/O、事件驱动式的程序设计模型。javascript
安装配置简单,无需多说。html
用异步的方式读取一个文本内容为“test content”的文件,代码以下:java
var fs = require("fs"); fs.readFile("test.txt", "utf-8", function(err, data) { console.log(data); }); console.log("end");
运行的结果:node
end test content
使用同步的方式读取文件:python
var fs = require("fs"); console.log(fs.readFileSync("test.txt", "utf-8")); console.log("end");
运行结果:web
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来实现。mongodb
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并输入以下json文本:
{
"main": "./lib/interface.js"
}
运行test.js,依然能够看到控制台输出“Hello world”。
node调用某个包时,会首先检查package.json文件的main字段,将其做为包的接口模块,若是package.json或main字段不存在,就会去寻找index.js或index.node做为包的接口。
npm默认为本地模式,会从http://npmjs.org搜索或下载包 ,将包安装到当前目录的node_modules
子目录下。
也可使用全局安装,它会将包安装在系统目录:
“npm [install/i] -g [package_name]”
本地模式能够直接经过require使用,可是不会注册环境变量。
全局模式不能直接经过require使用,可是它会注册环境变量。
经过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:
npm install -g node-inspector
在终端链接调试服务器:
node --debug-brk=5858 test.js
启动node-inspector:
node-inspector
在浏览器中打开http://127.0.0.1:8080/debug?port=5858。
在浏览器javascript中,一般window就是全局对象,而在node中全局对象是global,它是全局变量的宿主,全局变量是它的属性。
用于描述当前node进程状态,提供一个与操做系统的简单接口。
process.argv:
命令行参数数组,第一个元素是node,第二个元素是脚本文件名,从第三个元素开始每一个元素都是一个运行参数。
process.stdout:
标准输出流,一般咱们使用的console.log()向标准输出打印字符,而process.stdout.write()函数提供更底层的接口。
process.stdin:
标准输入流。
process.platform; process.pid; process.execPath; process.memoryUsage()
用于提供控制台标准输出。
console.log():
向标准流打印字符并以换行符结束,可使用相似C语言printf()命令的格式输出。
console.error():
向标准错误流输出。
console.trace():
向标准错误流输出当前的调用栈。
用于提供经常使用函数集合。
实现对象间原型继承的函数。
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()做为对象的属性输出。
将任意对象转换为字符串,一般用于调试和错误输出。
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是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被发射时,若是没有响应的监听器,Node会把它当作异常,退出程序并打印调用栈。
大多数时候不要直接使用EventEmitter,而是在对象中继承它。这样作使某个实体功能的对象实现事件符合语义,并且javascript的对象机制是基于原型的,支持部分多重继承。
fs模块是文件操做的封装,fs模块全部操做都提供了异步和同步两个版本。
fs.readFile(filename, [encoding], [callback(err, data)]):
读取文件,默认以二进制模式读取。
fs.readFileSync(filename, [encoding]):
以同步方式读取文件,读取的文件内容以返回值的形式返回。
fs.open(path, flags, [mode], [callback(err, fd)]):
其中flags能够是如下值:
mode参数用于建立文件时给文件指定权限,默认为0666。回调函数将会传递一个文件描述符fd。
fs.read(fd, buffer, offset, length, postion, [callback(err, bytesRead)]):
从指定的文件描述符fd中读取数据并写入buffer指向的缓冲区对象。offset是buffer的写入偏移量。length是要从文件中读取的字节数。position是文件读取的起始位置,若是为null,则从当前文件指针的位置读取。回调函数传递bytesRead和buffer,分别表示读取的字节数和缓冲区对象。
node提供了http模块。http.server是一个基于事件的HTTP服务器,http.request则是一个HTTP客户端工具。
http.server是HTTP服务器对象。
http.createServer(callback(request, response)):
建立一个http.server的实例,将一个函数做为HTTP请求处理函数。
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请求的信息。通常由http.server的request事件发送,做为第一个参数传递。
HTTP请求通常能够分为两部分:请求头(Reqeust Header)和请求体(Request Body)。http.ServerRequest提供了如下3个事件用于控制Request Body传输:
ServerRequest的属性:
返回给客户端的信息,由http.Server的request事件发送,做为第二个参数传递。
成员函数:
** response.writeHead(statusCode, [headers]):
向请求的客户端发送响应头。statusCode是HTTP状态码。该函数在一次请求中最多只能调用一次。
response.write(data, [encoding]):
向请求的客户端发送响应内容。data是一个buffer或字符串,表示要发送的内容。若是data是字符串,须要指定encoding来讲明它的编码方式,默认为utf-8。
response.end([data],[encoding]):
结束响应,当全部要返回的内容发送完毕的时候,该函数必须**被调用一次,不然客户端永远处于等待状态。
http.request(options, callback):
发起HTTP请求,返回一个http.ClientRequest的实例。callback是请求的回调函数,传递一个参数为http.ClientResponse的实例。option经常使用的参数以下:
http.get(options, callback):
http.request的简化版,用于处理GET请求,同时不须要手动调用req.end()。
http.clientRequest:
表示一个已经产生并且正在进行的HTTP请求。提供一个response事件。http.clientRequest函数:
http.clientResponse:
与http.ServerResponse相似,提供了三个事件data、end和close。http.clientResponse特殊函数:
运行命令:
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是工程的入口,用var app = express()建立一个应用实例,app.set(key, value)是Express的参数设置工具,可用参数以下:
Express依赖于connect,提供大量中间件,能够经过app.use启用。
路由文件,至关于MVC中的控制器。
模板文件。
能够给路由配置规则,如:
router.get('/:username', function(req, res, next) { res.send(req.params.username); });
路径规则还支持JavaScript正则表达式,如:app.get(/user/([^\/]+)/?, callback),可是这种方式参数是匿名的,所以须要经过req.params[0]这样的方式获取。
HTTP协议定义了如下8种标准的方法:
咱们经常使用到的是四种,根据REST设计模式,这四种分别用于如下功能:
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种标签:
开源的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更擅长处理逻辑简单但访问频繁的任务。
不支持完整的Unicode。
不一样于大多类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以数组来接受被调用函数的参数。
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。
使用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还有一个重要功能:绑定参数。
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中很容易写出深层的函数嵌套,所以选择两空格缩进。
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函数。