[关闭]
@yellowhouse 2016-07-20T01:47:00.000000Z 字数 6321 阅读 1273

Node基础学习

Node


JS是脚本语言,脚本语言都需要一个解析器才能运行。对于写在HTML页面里的JS,浏览器充当了解析器的角色。而对于需要独立运行的JS,NodeJS就是一个解析器。
每一种解析器都是一个运行环境,不但允许JS定义各种数据结构,进行各种计算,还允许JS使用运行环境提供的内置对象和方法做一些事情。运行在NodeJS中的JS的用途是操作磁盘文件或搭建HTTP服务器,NodeJS就相应提供了fs、http等内置对象。

模块化
在编写每个模块时,都有require、exports、module三个预先定义好的变量可供使用。一般是require引入其他模块,exports导出模块(一般是函数)
windows:模块名可使用相对路径(以./开头),或者是绝对路径(以/或C:之类的盘符开头)。另外,模块名中的.js扩展名可以省略
module对象可以访问到当前模块的一些相关信息,但最多的用途是替换当前模块的导出对象,可以把对象换成函数,或者改写原来的内容?

模块初始化
一个模块中的JS代码仅在模块第一次被使用时执行一次,并在执行过程中初始化模块的导出对象。之后,缓存起来的导出对象被重复利用。例如调用两次某模块,就可以得到连续运行两次的结果,会影响其变量的值。

NodeJS定义了一个特殊的node_modules目录用于存放模块,优先从这里加载模块

JS模块的基本单位是单个JS文件,但复杂些的模块往往由多个子模块组成(依赖性强),一般把由多个子模块组成的大模块称做包(package),并把所有子模块放在同一个目录里

如果想自定义入口模块的文件名和存放位置,就需要在包目录下包含一个package.json文件,并在其中指定入口模块的路径。

- /home/user/lib/
    - cat/
        + doc/
        - lib/
            head.js
            body.js
            main.js
        + tests/
        package.json

package.json(可以使用require('/home/user/lib/cat')的方式加载模块。NodeJS会根据包目录下的package.json找到入口模块所在位置。)

{
    "name": "cat",
    "main": "./lib/main.js"
}

当模块的文件名是index.js,加载模块时可以使用模块所在目录的路径代替模块文件路径

var cat = require('/home/user/lib/cat');
var cat = require('/home/user/lib/cat/index');

入口模块是什么?
组成一个包的所有子模块中,需要有一个入口模块,入口模块的导出对象被作为包的导出对象。

- /home/user/lib/
    - cat/
        head.js
        body.js
        main.js

cat目录定义了一个包,其中包含了3个子模块。main.js作为入口模块

命令行程序
$ node /home/user/bin/node-echo.js Hello World
也可以cd 命令进入到目录后直接调用

在Linux系统下,我们可以把JS文件当作shell脚本来运行

工程目录

- /home/user/workspace/node-echo/   # 工程目录
    - bin/                          # 存放命令行相关代码
        node-echo
    + doc/                          # 存放文档
    - lib/                          # 存放API相关代码
        echo.js
    - node_modules/                 # 存放三方包
        + argv/
    + tests/                        # 存放测试用例
    package.json                    # 元数据文件
    README.md                       # 说明文件

文件操作
文件拷贝

var fs = require('fs');

function copy(src, dst) {
    fs.writeFileSync(dst, fs.readFileSync(src));
}

function main(argv) {
    copy(argv[0], argv[1]);
}

main(process.argv.slice(2));
//大文件时,则要用fs.createReadStream创建了一个源文件的只读数据流,并使用fs.createWriteStream创建了一个目标文件的只写数据流,并且用pipe方法把两个数据流连接了起来。
function copy(src, dst) {
fs.createReadStream(src).pipe(fs.createWriteStream(dst));}

Buffer(数据块)
JS语言自身只有字符串数据类型,没有二进制数据类型,因此NodeJS提供了一个与String对等的全局构造函数Buffer来提供对二进制数据的操作。也可以与字符串相互转化。有些像指针,会修改原来的数据块,如.slice()方法返回的修改会作用于原buffer。拷贝的话,要先创建一个新的buffer(长度一样,[0]为原buffer第一位)然后再拷贝过去。

var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);

Stream(数据流)
当内存中无法一次装下需要处理的数据时,或者一边读取一边处理更加高效时,我们就需要用到数据流。Stream基于事件机制工作,所有Stream的实例都继承于NodeJS提供的EventEmitter。如果不加回调或其他限制,就会不断的执行,导致缓存爆仓,这一点要注意

var rs = fs.createReadStream(src);

rs.on('data', function (chunk) {
    rs.pause();
    doSomething(chunk, function () {
        rs.resume();
    });
});

rs.on('end', function () {
    cleanUp();
});

以上代码给doSomething函数加上了回调(多加了一行re.pause()就是回调了?),因此我们可以在处理数据前暂停数据读取,并在处理数据后继续读取数据。

File System(文件系统)
fs模块提供的API基本上可以分为以下三类:
文件属性读写:fs.stat、fs.chmod、fs.chown
文件内容读写:fs.readFile、fs.readdir、fs.writeFile、fs.mkdir
底层文件操作:fs.open、fs.read、fs.write、fs.close

以上都是异步API,通过回调函数传递结果。基本上所有fs模块API的回调参数都有两个。第一个参数在有错误发生时等于异常对象,第二个参数始终用于返回API方法执行结果。

fs.readFile(pathname, function (err, data) {
    if (err) {
        // Deal with error.
    } else {
        // Deal with data.
    }
});

同步API除了方法名的末尾多了一个Sync之外,异常对象与执行结果的传递方式也有相应变化

Path(路径)

遍历

function travel(dir, callback) {
    fs.readdirSync(dir).forEach(function (file) {
        var pathname = path.join(dir, file);

        if (fs.statSync(pathname).isDirectory()) {
            travel(pathname, callback);
        } else {
            callback(pathname);
        }
    });
}
travel('/home/user', function (pathname) {
console.log(pathname);
});

------------------------
/home/user/foo/x.js
/home/user/bar/y.js
/home/user/z.css

以某个目录作为遍历的起点。遇到一个子目录时,就先接着遍历子目录。遇到一个文件时,就把文件的绝对路径传给回调函数。
也有异步的,稍后再说

网络操作
'http'模块提供两种使用方式:
作为服务端使用时,创建一个HTTP服务器,监听HTTP客户端请求并返回响应。
作为客户端使用时,发起一个HTTP客户端请求,获取服务端响应。
HTTP请求本质上是一个数据流,由请求头(headers)和请求体(body)组成。HTTP响应本质上也是一个数据流,同样由响应头(headers)和响应体(body)组成

https模块与http模块极为类似,区别在于https模块需要额外处理SSL证书。与创建HTTP服务器相比,多了一个options对象,通过key和cert字段指定了HTTPS服务器使用的私钥和公钥

处理HTTP请求时url模块使用率超高,因为该模块允许解析URL、生成URL,以及拼接URL。可以使用.parse方法来将一个URL字符串转换为URL对象,不一定要是个完整的URL才能解析

url.parse('http://user:pass@host.com:8080/p/a/t/h?query=string#hash');
/* =>
{ protocol: 'http:',
  auth: 'user:pass',
  host: 'host.com:8080',
  port: '8080',
  hostname: 'host.com',
  hash: '#hash',
  search: '?query=string',
  query: 'query=string',
  pathname: '/p/a/t/h',
  path: '/p/a/t/h?query=string',
  href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash' }
*/

format方法允许将一个URL对象转换为URL字符串;.resolve方法可以用于拼接URL

querystring模块用于实现URL参数字符串与参数对象的互相转换

querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' });
/* =>
'foo=bar&baz=qux&baz=quux&corge='
*/

进程管理:NodeJS可以感知和控制自身进程的运行环境和状态,也可以创建子进程并与其协同工作

异步编程*
开发者需要按异步方式编写代码才能使用异步编程。当然他也提供了一些异步API
在代码中,异步编程的直接体现就是回调。验证是否异步的方法就是,自己的回调函数是否先于后续代码执行,如果是,则说明还是同步

同步 异步之间代码设计模式的差别
(1)函数返回值:使用一个函数的输出作为另一个函数的输入
同步 var output = fn1(fn2('input'));
异步 由于函数执行结果不是通过返回值,而是通过回调函数传递

fn2('input', function (output2) {
    fn1(output2, function (output1) {
        // Do something.
    });
});
//把fn2()的返回值放在output2里面,传给fn1?话说这语法是什么意思

(2)遍历数组
同步

var len = arr.length,
    i = 0;

for (; i < len; ++i) {
    arr[i] = sync(arr[i]);
}

异步(承接上文的声明):数组成员必须一个接一个串行处理

(function next(i, len, callback) {
    if (i < len) {
        async(arr[i], function (value) {
            arr[i] = value;
            next(i + 1, len, callback);
        });
    } else {
        callback();
    }
}(0, arr.length, function () {
    // All array items have processed.
}));
//在异步函数执行一次并返回执行结果后才传入下一个数组成员并开始下一轮执行,直到所有数组成员处理完毕后,通过回调的方式触发后续代码的执行(递归?)

异常处理
同步:try..catch..对于异步而言,由于异步函数会打断代码执行路径,异步函数执行过程中以及执行之后产生的异常冒泡到执行路径被打断的位置时,如果一直没有遇到try语句,就作为一个全局异常抛出
异步:推荐使用 域(domain)
简单的讲,一个域就是一个JS运行环境,在一个运行环境中,如果一个异常没有被捕获,将作为一个全局异常被抛出。NodeJS通过process对象提供了捕获全局异常的方法

什么是回调
写好一个函数,让系统来调用。相对的,程序员去调用系统的函数(API),就是直调
被认为是一种被作为参数(一般写作callback)传递给另一个函数(在这称作"otherFunction")的高级函数,回调函数会在otherFunction内被调用(或执行)。那么这个函数如果是当做匿名函数,且有参数时,它的参数是怎么被传递的?--一般会在下面有调用它的时候,那是后再给出参数(命名函数),一般是主函数的另一参数
当我们作为参数传递一个回调函数给另一个函数时,我们只传递了这个函数的定义,并没有在参数中执行它。
当包含(调用)函数拥有了在参数中定义的回调函数后,它可以在任何时候调用(也就是回调)它。说明回调函数并不是立即执行,而是在包含函数的函数体内指定的位置“回调”它。匿名函数将延迟在click函数的函数体内被调用,即使没有名称,也可以被包含函数通过 arguments对象访问。

回调函数是闭包的。闭包函数可以访问包含函数的作用域,所以,回调函数可以访问包含函数的变量,甚至是全局变量。
最为常见的是jQuery里的click方法:

$("#btn_1").click(function() {
  alert("Btn 1 Clicked");
});

在执行之前确保回调是一个函数,否则会语法出错

if (typeof callback === "function")

使用this对象的回调函数
当回调函数是一个含有this对象的方法时,我们必须修改执行回调函数的方法以保护this对象的内容。否则this对象将会指向全局的window对象(this会默认指向全局的window对象)

使用Call或Apply函数保护this对象

function getUserInput(firstName, lastName, callback, callbackObj)  {
    …………
    callback.apply (callbackObj, [firstName, lastName]);
}//用apply直接指定callbackObj,即this对象

遇到回调地狱时(里面一万个函数)
1、命名并定义你的函数,然后传递函数名作为回调,而不是在主函数的参数列表里定义一个匿名函数。
2、模块化:把你的代码划分成一个个模块,这样你可以空出一部分代码块做特殊的工作。然后你可以将这个模型引入到你的大型应用程序中。

有以下需求时你可以考虑使用回调:
1、避免重复代码 (DRY—Do Not Repeat Yourself)
2、在你需要更多的通用功能的地方更好地实现抽象(可处理各种类型的函数)。
3、增强代码的可维护性
4、增强代码的可读性
5、有更多定制的功能

那么有了回调函数又如何实现异步呢?
首先JS是单线程的,不管回调函数要花费多长的时间去运行,在这段代码还没有结束运行的时候,是不会去运行别的代码。但如果某个函数做的事是创建一个别的进程,与主线并行的做一些事情,做完后再通知主线。(例如setTimeout、setInterval、fs.readFile)

异步编程的含义只有一个:

你不能在函数返回时,立即获得你想要执行的结果。传入一个callback用以等待任务完成时,把结果告诉你

或者使用事件监听来改变函数执行顺序

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注