• 初识NodeJS


    1.NodeJS是什么?

    官网给出的解释是:基于Chrome V8引擎构建的javascript运行环境。

    计算机只能识别机器代码(machine code或者native code)。C/C++作为低级语言,可以直接被机器识别。

    但是javascript作为一种高级语言,是不能直接被识别的,需要一个东西将它转为机器语言,这个东西就是google公司提供的v8引擎。

    v8引擎执行javascript代码非常快,性能非常好。

    因为它使用JIT编译器(Just-In-Time Compiler),就是直接js->AST(抽象语法树)->机器码。

    没有其他诸如JAVA等语言,有一个中间层转换。所以它很快。

    原来只有chrome浏览器使用了v8引擎,所以javascript代码要在浏览器环境下才能运行。

    v8引擎是通过C++语言编写的。

    nodeJS也是通过C++语言编写的,它嵌入了更适合服务器代码开发的优化过的v8引擎,所以它可以运行javascript代码。

    另外nodeJS内置了libuv库, 提供了很多API,诸如读写文件,网络请求,系统信息等。

    通俗的解释可以是: 是javascript可以脱离浏览器环境运行的一个javascript运行环境。

    2.NodeJS 的作用和优点

    主要的作用是:可以让前端人员使用javascript语言开发服务端功能。并运行在服务器上。

    应用: 

    1. 开发工具--webpack
    2. 做中间层---可以解决跨域
    3. 服务端渲染--react,vue都是js,nodejs可以直接解析js;还可以连接mongo,redis,mysql数据库

    优点:

    • 可以创建多线程(子线程),用pm2管理线程
    • 异步非阻塞I/o操作,采用事件环驱动

    PS: 异步/同步针对被调用者的描述;阻塞/非阻塞是针对调用者(调用的方法)的描述。

    3. Node的安装

    官网下载安装。

    可以使用nvm(node version manager)来进行node版本管理。安装如下(MAC,其他系统见官网):

    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash

    如果安装完成后, -bash: nvm: command not found

    则可能是缺少bash.profile文件,需要创建

    touch ~/.bash_profile

    4.NodeJS 基础内容

    nodeJs中的全局对象是global。global上有一些隐藏属性(如console, eval等),直接查看看不到。想要查看所有属性,可以通过console.dir(global, { showHidden: true });

    nodeJS在文件中直接打印this,不是global, 是{}

    // 文件中启动运行代码的时候,为了实现模块化,代码最外层自动包裹一层函数,this指向被改变。
    console.log(this); // {} --module.exports
    
    // 自执行函数的this指向全局对象
    (function() {
        console.log(this); // global
    })()

    另外在命令行中直接输入node,会出现一个REPL(read-eval-print-loop)环境,可以直接运行node代码(类似浏览器的控制台)。

    通过该环境,打印的this,就是global对象。

    global常见的可遍历属性有:

    1.process(进程)

    常用的参数有: 

    • argv 命令行参数

    当前node进程中,node命令运行时的参数

    node app.js  --config 1.config.js --port 3000
    console.log(process.argv);
    // [ '/usr/local/bin/node',
    //   '/Users/lyralee/Desktop/MyStudy/Event/server/app.js',
    //   '--config',
    //   '1.config.js',
    //   '--port',
    //   '3000' ]

    取到的参数是一个数组,可以将其转化成一个对象,使用tj/commander工具。

    具体用法如下:

    // npm install commander 
    const program = require('commander');
    // 1.设定版本号;
    // .parse(process.argv)必须有!!
    program.version('1.0.0');
    program.parse(process.argv); // 这一行代码必须有!!!
    // node app.js -V
    // 输出1.0.0
    // 2.添加进程参数可选项
    program.version('1.0.0')
        .option('-p, --pepper', 'add pepper')
        .option('-a --add', 'add sth')
        .option('-p|--pepper', 'add pepper')
    program.parse(process.argv); // 这一行代码必须有
    /* --help
    Options:
      -V, --version  output the version number
      -p, --pepper   add pepper
      -a --add       add sth
      -p|--pepper    add pepper
      -h, --help     output usage information
    */
    // 使用的时候node app.js -a 20
    // console.log(program);
    /*
        {
             ...
            add: true,
            args: [20]
        }
    */
    3. 添加参数并要求赋值
    program.version('1.0.0')
        .option('-a -add <value>', 'add sth')
    program.parse(process.argv);
    // node app.js -a 20
    console.log(program);
    /**
     * {
     *      ...,
     *      config: 20,
     *      args: []
     * }
     */
    4. 将命令绑定操作
    program
        .command('rm <dir>')
        .option('-r, --recursive', 'remove sth')
        .action(function(dir, cmd) {
            console.log('remove ' + dir + (cmd.recursive ? 'recursively' : ''));
            //  remove XXXX
        })
    program.parse(process.argv); // 注意parse一定不能在action后面连写
    5. 丰富打印日志
    program.on('--help', function() {
        console.log('');
        console.log('Example:');
        console.log('   ${custom-help} --help'); // 每个空格都有确实占一个空位
        console.log('   ${custom-help} -h');
    })
    program.parse(process.argv); // 注意parse一定不能在action后面连写
    /*
    Example:
       ${custom-help} --help
       ${custom-help} -h
    */
    View Code
    • env

    可以设置环境当前变量。

    其中在Mac中使用export

    // 在命令行窗口中输入以下命令
    export NODE_ENV=production
    node app.js
    //  或者
    export NODE_ENV=production && node app.js
    // 在app.js中查看下面的值
    console.dir(process.env.NODE_ENV); // “production”

    在window中,使用set命令。用法如上。

    也可以安装cross-env命令,可以兼容Mac和Windows

    • cwd()

    current working directory当前工作目录。获取文件运行时所在的目录(即在哪个目录下运行的命令)。

    // 对于右键直接run code, 默认其当前目录cwd()是文件所在根文件夹
    console.log(process.cwd());// /Users/lyralee/Desktop/MyStudy/Event
    process.chdir('./server'); // 目标文件夹名称
    console.log(process.cwd()); // /Users/lyralee/Desktop/MyStudy/Event/server
    • nextTick()

    微任务

    2.Buffer(16进制)

    用于缓存。

    1. Buffer.from(str)

    将字符串转为16进制。

    let buffer = Buffer.from("你好");  // utf8格式一个汉字占3个字节
    console.log(buffer);
    //<Buffer e4 bd a0 e5 a5 bd>
    2. 可以和字符串之间转换(utf8)

    1. node支持utf8格式的字符串转换,不支持gbk格式(可以通过iconv-lite的库将gbk/二进制, 转为utf8)。

    let buffer = Buffer.from("你好"); 
    console.log(buffer)
    // <Buffer e4 bd a0 e5 a5 bd>
    let str = buffer.toString();
    let str1 = buffer.toString('utf8');
    console.log(str === str1); // true
    console.log(str); //"你好"

    2. 可以将16进制转为base64格式的字符串。

    base64基于(A-Za-z0-9+/)的64个字符组成。其每个字节都是00xxxxxx。最小值是00000000(0)。最大值是00111111(63)。

    let buffer = Buffer.from("你好"); 
    console.log(buffer)
    // <Buffer e4 bd a0 e5 a5 bd>
    let base64 = buffer.toString('base64');
    console.log(base64); //5L2g5aW9

    代码中可以使用url的位置都可以使用base64编码(img.src, background)

    base64编码的原理:

    let buffer = Buffer.from("你");  //<Buffer e4 bd a0>
    
    // 遍历buffer并取出其对应的二进制的值,将去按照base64的要求重新拼接
    let strTwo = ''
    buffer.forEach(item => {
      strTwo+=item.toString(2);
    });
    // 00111001 00001011 00110110 00100000
    let base64Index = [];
    for(let i=0; i< strTwo.length; i+=6) {
      base64Index.push(parseInt('00' + strTwo.slice(i, i+6), 2));
    }
    // [ 57, 11, 54, 32 ]
    let str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    str+='abcdefghijklmnopqrstuvwxyz';
    str+='0123456789+/';
    
    let base64 = '';
    base64Index.forEach(item => {
      base64+= str[item];
    })
    console.log(base64);
    3. base的声明
    // 1. 参数是字符串
    let buffer = Buffer.from('你好');
    console.log(buffer); //<Buffer e4 bd a0 e5 a5 bd>
    // 2. alloc按字节大小分配内存
    let buffer1 = Buffer.alloc(3); 
    console.log(buffer1); //<Buffer 00 00 00>
    // 3. 参数是数组
    let buffer2 = Buffer.from([255,255,255]);
    console.log(buffer2);//<Buffer ff ff ff>
    4. buffer的方法和属性

    1. length: 字节的长度

    let buffer = Buffer.from('你好');
    console.log(buffer.length); // 6

    2. slice():  按字节截取

    let buffer = Buffer.from('你好');
    console.log(buffer.slice(0,3).toString()); //"你"

    3. forEach(): 遍历每个字节

    4.source.copy(target, targetStart[, sourceStart, sourceEnd])

    // buffer一旦声明就不能更改长度;可以在新的buffer中进行拼接
    let buffer1 = Buffer.from('你');
    let buffer2 = Buffer.from('好');
    
    // 分配一段新的内存用于存储最终结果
    let target = Buffer.alloc(buffer1.length + buffer2.length);
    buffer1.copy(target, 0);
    buffer2.copy(target, buffer1.length);
    
    console.log(target.toString()); //你好

    5. Buffer.concat([buffer1, buffer2...])

    let buffer1 = Buffer.from('你');
    let buffer2 = Buffer.from('好');
    
    let target = Buffer.concat([buffer1, buffer2])
    
    console.log(target.toString()); //你好

    用copy方法实现Buffer.concat()

    Buffer.concat = function(arr) {
      let totalLength = arr.reduce((a,b) => a+ b.length,0);
      let targetBuffer = Buffer.alloc(totalLength);
      let i = 0;
      arr.forEach(item => {
        item.copy(targetBuffer, i);
        i+=item.length;
      })
      return targetBuffer;
    }

    6. Buffer.alloc(length)

    分配初始值都是0x00的内存

    3.计时器/定时器类

    setTimeout/ clearTimeout

    setInterval/ clearInterval

    setImmediate/ clearImmediate

    5.node模块

    模块通过在一个文件中使用自执行函数,函数内部返回内容。

    // moduleA
    (   
        function sum() {
        }
        module.exports = sum;
        return module.exports; 
    )()
    // app.js
    
    const a = require('./moduleA.js); // 同步读取文件
    // a = (...)();===sum

    node模块有三类:

    1. 核心模块

      其中有三个主要的核心模块:fs, path, vm

      还有一些工具模块:querystring,crypto

    1.fs模块

    1. 同步读取文件:

    const buffer = fs.readFileSync('./1.txt');

    2. 判断文件是否存在

    const fs = require('fs');
    try {
        fs.accessSync('/.XXXX.xx'); // 判断一个文件是否存在
    } catch(err) {
        // 文件不存在抛出异常;原来的exists因为回调中不是异常参数,被废弃
    }

    3. 判断路径对应的是文件还是文件夹

      fs.stat(absolutePath, function(err, statObj) {
        if(err) { //不存在
          res.statusCode = 404;
          res.end('Not Found');
          return;// 结束
        }
        if (statObj.isFile()) {// 是文件则直接读取返回对应路径下文件
          const rs = fs.createReadStream(absolutePath);
          // 设置返回的响应头Content-Type
          res.setHeader('Content-Type', mime.getType(absolutePath)+";charset=utf-8");
          rs.pipe(res); //包含 res.end(...)逻辑
        } else {// 是文件夹;默认查找index.html
          const indexPath = path.join(absolutePath, 'index.html');
          fs.access(indexPath, function(err) {
            if(err) {
              res.statusCode = 404;
              res.end('Not Found');
              return;// 结束
            }
            res.setHeader('Content-Type', 'text/html;charset=utf-8');// utf-8中间要有间隔符,IE兼容
            fs.createReadStream(indexPath).pipe(res);
          })
        }
     }
    2. path模块
    • path.join(),path.resolve()--拼接路径
    // 1)path.join(), path.resolvec
    path.join('a','b'); // a/b
    path.resolve('a', 'b'); // 绝对路径Users/lyralee/Desktop/MyStudy/Event/a/b
    
    // __dirname表示离当前文件最近的文件夹目录
    path.join(__dirname,'a','b'); // /Users/Event/server/a/b
    path.resolve('a', 'b');// /Users/Event/server/a/b
    path.resolve('a', 'b', '/'); // /
    // 从上面的示例可以看出,当路径最后有/时,不能使用path.resolve
    • path.dirname(dir)--取父路径
    path.dirname(__dirname); // /Users/lyralee/Desktop/MyStudy/Event
    • path.extname(fullname)--文件扩展名
    console.log(path.extname('1.min.js')); // .js
    • path.basename(fullname[, extname])---基础文件名
    // 如果指定了扩展名,会返回除扩展名以外的文件名
    console.log(path.basename('1.min.js')); // 1.min.js
    console.log(path.basename('1.min.js', '.js')); //1.min
    3.vm模块--虚拟机--运行字符串代码

    提供一个沙箱--纯净的执行环境;不受外面变量的影响。

    const vm = require('vm');
    var hello = 'morning';
    vm.runInThisContext(`console.log(hello)`); // hello is not defined
    eval(`console.log(hello)`); // morning eval()会受向上层作用域查找
    4. querystring

    用于将字符串解析成对象

    querystring.parse(req.headers.cookie, "; ");
    5. crypto

    用于进行摘要算法(md5, 不可逆)和加密算法(sha256/sha1, 加盐-密钥,可逆)。

    1. md5的特点:

    1. 相同的内容摘要后的结果相同。
    2. 不同的内容结果完全不同。
    3. 所有的结果长度相同。
    4. 不可逆。即不能反向推导。

    示例:

    let crypto = require('crypto');
    
    // 通过md5算法将“lyra”进行加密,最后以base64的格式输出
    let str = crypto.createHash('md5').update('lyra').digest('base64');
    console.log(str); //rABzfUdIpCoSSnWA+y2jTA==

    但是这种算法可以通过“撞库”(海量数据)来解码,不再安全。为了避免这种情况,可以通过多次摘要。

    let crypto = require('crypto');
    
    let str = crypto.createHash('md5').update('lyra').digest('base64');
    str = crypto.createHash('md5').update(str).digest('base64');
    str = crypto.createHash('md5').update(str).digest('base64');
    console.log(str);

    2. sha256

    比md5的摘要算法要安全,它需要一个盐(密钥)来生成最终的结果。

    let crypto = require('crypto');
    let secret = 'secret'; //密钥
    let str = crypto.createHmac('sha256', secret).update('lyra').digest('base64');
    console.log(str);//LAYvIrbnDGYBH2Kebzgz2yvclRqTiNFaPDKYxXEGbrg=

    2. 文件(自定义)模块

    用户自己编写的模块

    3. 第三方模块

    通过npm安装的模块,require的参数只能是名称,不能是相对路径或者绝对路径(即不能有./或者/出现)。

    响应的,可以理解成如果require的参数是一个字符串,会启用第三方模块的查找规则。(自定义的第三方模块,也可以按照规则,在对应的文件夹添加文件)

    加载文件的时候的路径查找规则如下:

    // 查找默认路径的先后顺序
    console.log(module.paths);
    
    // 结果如下(示例),即从最近的node_modules开始查找:
    [ '/Users/lyralee/Desktop/MyStudy/nodeJS/node_modules',
      '/Users/lyralee/Desktop/MyStudy/node_modules',
      '/Users/lyralee/Desktop/node_modules',
      '/Users/lyralee/node_modules',
      '/Users/node_modules',
      '/node_modules' ]

    分为两类:

    •  1. 全局安装的模块: 可以在命令行中使用
    // 查看全局包的安装位置
    npm root -g

    可以手动在node_modules下创建一个自己的模块对应的文件夹,文件夹中必须包含package.json和入口文件(main字段指定的文件或者index.js)。

    如果想要自己实现一个全局命令

    1. 在node_modules中添加一个自定义的第三方模块,包含package.json和入口文件a.js。
    2. 在package.json的main字段指定入口文件为a.js
    ` "main": "a.js" `
    3. 添加bin字段
    ```
    bin: {
      "lyra": "./a.js" // 在命令行中运行lyra, 运行对应的文件
    }
    ```
    4. 指定使用node命令运行文件
    在a.js文件的头部添加命令
    `#! /usr/bin/env node`
    5. 将本地的文件链接到全局命令中
    `npm link` 

    发布包的步骤

    1. 确认当前源是npm的源(nrm current)
    2. 登录 npm login(npm addUser)
    3. 发布 npm publish
    4. 卸载 npm unpublish --force
    • 2. 局部安装的模块: 本地安装,可以在代码中使用   

    常见的第三方模块

    1. mime

    获取文件的Content-Type

    res.setHeader('Content-Type', mime.getType(absolutePath)+";charset=utf-8");
    2. mz/fs

    将fs操作Promise化。可以使用async...await 

    6.nodejs代码调试方法

    • 用浏览器调试:
    1)node --inspect-brk app.js
    // 其中brk表示从第一行就打断点
    2) 在浏览器地址栏中输入chrome://inspect,单击inspect
    • 用vsCode调试
    program: "${file}"
    cwd: "${cwd}"
    1)// 打开vsCode的debugger工具,点击设置,进行路径设置
    2)// 点运行进行调试
  • 相关阅读:
    js基础之弹性运动(四)
    js基础之动画(三)
    js基础之动画(二)
    js基础之动画(一)
    js基础之ajax
    js基础之COOKIE
    4.27日软件开发日记我今天做了什么
    4.25日
    4.24日自学成果
    错排公式及其化简
  • 原文地址:https://www.cnblogs.com/lyraLee/p/11605807.html
Copyright © 2020-2023  润新知