basis of NodeJS

note of 7-days-nodejs

Posted by Chaoran on July 25, 2018

“To be a better girl ”

node

JS是脚本语言–需要一个解析器才能运行。对于写在HTML页面里的JS,浏览器充当了解析器的角色。而对于需要独立运行的JS,NodeJS就是一个解析器

模块

  • 一般将代码合理拆分到不同的JS文件中,每一个文件就是一个模块,文件路径就是模块名
  • 每个模块都有require、exports、module三个预先定义好的变量可供使用

node模块:原生模块+3种文件模块 module

exports

对象是当前模块的导出对象,用于导出模块公有方法和属性

exports && module.exports

exports 是 module.exports 的引用

exports => {} <=module.exports

// 导出
exports.foo = function(){
  console.log('foo')
}

// 模块导出对象默认是一个普通对象,如果想改成一个函数的话,可以使用以下方式
module.exports = function () {
    console.log('Hello World!');
};

// 引入
var foo = require('./foo')
  • NodeJS是一个JS脚本解析器,任何操作系统下安装NodeJS本质上做的事情都是把NodeJS执行程序复制到一个目录,然后保证这个目录在系统PATH环境变量下,以便终端下可以使用node命令。

  • 终端下直接输入node命令可进入命令交互模式,很适合用来测试一些JS代码片段,比如正则表达式。

  • NodeJS使用CMD模块系统,主模块作为程序入口点,所有模块在执行过程中只初始化一次。

  • 除非JS模块不能满足需求,否则不要轻易使用二进制模块,否则你的用户会叫苦连天。

模块路径解析规则

  • 内置模块
    • 不做路径解析,直接返回内部模块的导出对象,例如require(‘fs’)
  • node_modules 目录
    • 在模块中使用require(‘foo/bar’)方式加载模块时,则NodeJS依次尝试使用以下路径

      /home/user/node_modules/foo/bar
      /home/node_modules/foo/bar
      /node_modules/foo/bar
      
  • NODE_PATH 环境变量

    操作系统中都会有一个PATH环境变量,当系统调用一个命令的时候,就会在PATH变量中注册的路径中寻找,如果注册的路径中有就调用,否则就提示命令没找到 NODE_PATH 就是NODE中用来寻找模块所提供的路径注册环境变量

    • NodeJS允许通过NODE_PATH环境变量来指定额外的模块搜索路径。
    • NODE_PATH环境变量中包含一到多个目录路径,路径之间在Linux下使用:分隔,在Windows下使用;分隔
    • 使用require(‘foo/bar’)的方式加载模块时,则NodeJS依次尝试以下路径。
       // 若定义环境变量
      
       NODE_PATH=/home/user/lib:/home/lib
      
       // 
      /home/user/lib/foo/bar
      /home/lib/foo/bar
      

JS模块的基本单位是单个JS文件,但复杂些的模块往往由多个子模块组成。为了便于管理和使用,可以把由多个子模块组成的大模块称做包,并把所有子模块放在同一个目录里

// cat目录定义了一个包,其中包含了3个子模块。index.js作为入口模块,其内容如下:

var head = require('./head');
var body = require('./body');

exports.create = function (name) {
    return {
        name: name,
        head: head.create(),
        body: body.create()
    };
};

// 引用——两者等价
var cat = require('/home/user/lib/cat');
var cat = require('/home/user/lib/cat/index');

package.json

自定义入口模块的文件名和存放位置

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


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

工程化

命令行程序

转化为命令行程序,执行方式如 node-echo Hello World

假设node-echo.js存放在C:\Users\user\bin目录,并且该目录已经添加到PATH环境变量里了。接下来需要在该目录下新建一个名为node-echo.cmd的文件,文件内容如下:

@node "C:\User\user\bin\node-echo.js" %*
工程

标准的工程目录

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

相关文件

/* bin/node-echo */
var argv = require('argv'),
    echo = require('../lib/echo');
console.log(echo(argv.join(' ')));

/* lib/echo.js */
module.exports = function (message) {
    return message;
};

/* package.json */
{
    "name": "node-echo",
    "main": "./lib/echo.js"
}

// package.json之后,node-echo目录也可被当作一个包来使用
NPM

随同NodeJS一起安装的包管理工具

  • 从NPM服务器下载别人编写的三方包到本地使用。

  • 从NPM服务器下载并安装别人编写的命令行程序到本地使用。

  • 将自己编写的包或命令行程序上传到NPM服务器供别人使用。

下载第三方包

npm install argv
npm install argv@0,0,1

// package 改写后npm install

安装命令行程序

  • NPM服务上下载安装一个命令行程序的方法与三方包类似

发布代码

  • 第一次使用NPM发布代码前需要注册一个账号
  • 终端下运行 npm adduser ,之后按照提示做即可
  • 账号搞定后,接着我们需要编辑package.json文件,加入NPM必需的字段
    {
      "name": "node-echo",           # 包名,在NPM服务器上须要保持唯一
      "version": "1.0.0",            # 当前版本号
      "dependencies": {              # 三方包依赖,需要指定包名和版本号
          "argv": "0.0.2"
        },
      "main": "./lib/echo.js",       # 入口模块位置
      "bin" : {
          "node-echo": "./bin/node-echo"      # 命令行程序名和主模块位置
      }
    }
    
  • 在 package.json 所在目录下运行npm publish发布代码

版本更新-X.Y.Z三位

  • 如果只是修复bug,需要更新Z位。

  • 如果是新增了功能,但是向下兼容,需要更新Y位。

  • 如果有大变动,向下不兼容,需要更新X位。

文件操作

NodeJS 提供了基本的文件操作API,但是像文件拷贝这种高级功能就没有提供

Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()

nodejs 文件操作分类:

  • 文件属性读写。
    • 其中常用的有fs.stat、fs.chmod、fs.chown等等。
  • 文件内容读写。
    • 其中常用的有fs.readFile、fs.readdir、fs.writeFile、fs.mkdir等等。
  • 底层文件操作。
    • 其中常用的有fs.open、fs.read、fs.write、fs.close等等。

常用 API

  • fs.stat(path,callback)
    • err为错误信息参数,stats为一个文件状态对象
  • fs.writeFile(path,data[,options],callback)
    • path参数为该文件的绝对物理路径
    • data为需要写入该文件当中的数据内容
    • options参数可选,可以传入编码格式,若不传则默认为utf8
    • callback回调参数当中只有一个错误信息参数err,一般在写入失败时触发调用
  • fs.appendFile(path,data[,options],callback)
  • fs.unlink(path,callback)
    • 完成指定文件的删除
  • fs.readFile(path[,options],callback)
  • fs.rename(oldPath,newPath,callback)
    • 移动或重命名指定文件
    • oldPath参数为该文件原来的路径
    • newPath参数为该文件移动或重命名之后的路径,这两个参数都必须能传入文件完整的绝对物理路径
    • callback回调参数当中只有一个错误信息参数,一般在oldPath当中指定的文件不存在或者该操作失败时触发调用
  • fs.mkdir(path[,model],callback)
    • 创建一个目录文件夹
    • path为该目录的绝对物理路径
    • callback回调函数当中也只有一个错误信息参数,一般在目录创建失败时触发调用
    • 不能完成多级目录的创建,node当中要求要创建的那个文件夹所在的文件夹必须能都存在
  • fs.rmdir(path,callback)
    • 删除一个空目录
  • fs.readdir(path,callback)
    • 读取一个指定目录当中的信息

路径操作

  • path.normalize
    • 将传入的路径转换为标准路径
  • path.join
    • 将传入的多个路径拼接为标准路径
  • path.extname
    • 获取文件扩展名
常用文件操作
// node 导入文件模块
var fs = require('fs')

// 小文件拷贝
function copy(src, dst){
  fs.writeFileSync(dst, fs.readFileSync(src))
}

// 大文件拷贝
function copyFile(src, dst){
  fs.createReadStream(src).pipe(fs.createWriteStream(dst))
}

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

main(process.argv.slice(2))

更多查看:文件系统介绍

buffer

Buffer与字符串有一个重要区别 —— 字符串是只读的 Buffer将JS的数据处理能力从字符串扩展到了任意二进制数据

  • 对字符串的任何修改得到的都是一个新字符串,原字符串保持不变
  • Buffer,更像是可以做指针操作的C语言数组。例如,可以用 [index] 方式直接修改某个位置的字节
  • .slice方法也不是返回一个新的Buffer,而更像是返回了指向原Buffer中间的某个位置的指针
    • 修改该返回值会作用于原buffer
  • 拷贝buffer
    • 首先创建一个新的Buffer
    • 通过.copy方法把原Buffer中的数据复制过去
      var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);
      var dup = new Buffer(bin.length);
      
      bin.copy(dup);
      dup[0] = 0x48;
      console.log(bin); // => <Buffer 68 65 6c 6c 6f>
      console.log(dup); // => <Buffer 48 65 65 6c 6f>
      

stream —— 基于事件机制工作

当内存中无法一次装下需要处理的数据时,或者一边读取一边处理更加高效时,我们就需要用到数据流

编码

BOM字符标记文件编码,其本身却不属于文件内容的一部分,文件合并时需要去掉

  • BOM的移除
    • BOM用于标记一个文本文件使用Unicode编码,其本身是一个Unicode字符(”\uFEFF”),位于文本文件头部
    • 可以根据文本文件头几个字节等于啥来判断文件是否包含BOM,以及使用哪种Unicode编码

      // 去除UTF8 BOM
      function readText(pathname){
        var file = fs.readFileSync(pathname)
      
        if(file[0] === oxEF && file[1] === oxBB && file[2] === oxBF){
          file = file.slice(3)
        }
      
        return file.toString('utf-8')
      }
      
  • GBK转UTF8

    NodeJS支持在读取文本文件时,或者在Buffer转换为字符串时指定文本编码,但GBK编码不在NodeJS自身支持范围内

    借助 iconv-lite

    npm install iconv-lite --save-dev
    
    // 使用
    var iconv = require('iconv-lite')
    
    function readGBKText(pathname){
      var file = fs.readFileSync(pathname)
    
      return iconv.decode(file, 'gbk')
    }
    
  • 单字节编码

    无法预知文件编码类型时,由于部分可能超出编码范围,可以统一使用单字节编码

网络操作

不了解网络编程的程序员不是好前端, oh, nooo

  • 作为服务端使用时,创建一个HTTP服务器,监听HTTP客户端请求并返回响应

  • 作为客户端使用时,发起一个HTTP客户端请求,获取服务端响应

HTTP请求本质上是一个数据流,由请求头(headers)和请求体(body)组成

// 完整的HTTP请求数据内容
// 请求头
POST / HTTP/1.1
User-Agent: curl/7.26.0
Host: localhost
Accept: */*
Content-Length: 11
Content-Type: application/x-www-form-urlencoded

// 请求体
Hello World
  • HTTP请求在发送给服务器时,可以认为是按照从头到尾的顺序一个字节一个字节地以数据流方式发送的
  • http模块创建的HTTP服务器在接收到完整的请求头后,就会调用回调函数
    • 回调函数中可以使用request对象访问请求头数据
    • 还能把request对象当作一个只读数据流来访问请求体数据