Node.js 基础

  1. NodeJS是什么?
    简单的说Node.js就是运行在服务端的JavaScript
    Node.js是一个基于ChromeV8引擎的JavaScript运行时。Node.js使用高效、轻量级的事件驱动、非阻塞I/O模型。

  2. 与前端的不同
    JS核心语法不变
    前端 BOM DOM
    后端 fs http buffer event os

  3. 运行node程序
    node 01-runnode.js
    每次修改js文件需重新执行才能生效,安装 nodemon可以watch文件改动,自动重启。

    npm i -g nodemon
    nodemon 01-runnode.js
    

    调试node程序:Debug - Start Debugging

  4. 模块module

    // node内建模块
    const os = require('os')
    const mem= (os.freemem() / os.totalmem()) * 100
    console.log(`内存占用率${mem}%`)
    
    // 第三方模块
    const cpuStat = require('cpu-stat')
    cpuStat.usagePercent((err, percent) => console.log(`CPU占用:${percent.toFixed(2)}%`))
    
    // 回调方法 
    const util = require('util')
    const getCpu = util.promisify(cpuStat.usagePercent)
    getCpu().then(percent => { console.log(`CPU占用:${percent.toFixed(2)}%`) })
    
    //加入定时器定时显示统计
    const showStat = async () => {
        const mem = (os.freemem() / os.totalmem()) * 100
        const percent = await getCpu()
        console.log(`CPU占用:${percent.toFixed(2)}% 内存: ${mem} %`)
    }
    setInterval(showStat, 1000)
    
    // 自定义模块
    // currency.js
    // 保存汇率
    let rate;
    function rmbToDollar(rmb) {
        return rmb / rate;
    }
    
    function dollarToRmb(dollar) {
        return dollar * rate;
    }
    module.exports = function (r) {
        rate = r;
        return {
            rmbToDollar,
            dollarToRmb
        };
    };
    
    const { rmbToDollar, dollarToRmb } = require('./currency')(6)
    console.log(rmbToDollar(10), dollarToRmb(10))
    
  5. 核心API

    • fs:文件系统
     // fs - 文件系统
     const fs = require('fs');
    
     // 同步调用
     const data = fs.readFileSync('./currency.js'); // 代码会阻塞在这里
    
     // 下面是同步调用,实际中建议使用异步版本
     fs.readFile('./currency.js', (err, data) => {
         if (err) throw err;
         console.log(data);
     });
    
     // fs常常搭配path api使用
     const path = require('path')
     fs.readFile(path.resolve(__dirname, './currency.js'), (err, data) => {
         if (err) throw err;
         console.log(data);
     });
    
     // 异步调用
     // promisify
     const { promisify } = require('util')
     const readFile = promisify(fs.readFile)
     readFile('./currency.js').then(data => console.log(data))
    
     // Promises
     const fsp = require('fs').promises;
     fsp
         .readFile('./currency.js')
         .then(data => console.log(data))
         .catch(err => console.log(err));
    
     // async/await
     (async () => {
         const fs = require('fs')
         const { promisify } = require('util')
         const readFile = promisify(fs.readFile)
         const data = await readFile('./currency.js')
         console.log('data', data)
     })()
    
    • Buffer:用于在TCP流、文件系统操作、以及其他上下文中与八位字节流进行交互。 八位字节组成的数组,可以有效的在JS中存储二进制数据
     Buffer.from(data).toString('utf-8')
    
     // 创建一个长度为10字节以0填充的Buffer
     const buf1 = Buffer.alloc(10);
     console.log(buf1);
    
     // 创建一个Buffer包含ascii
     const buf2 = Buffer.from('a')
     console.log(buf2, buf2.toString())
    
     // 创建Buffer包含UTF-8字节
     const buf3 = Buffer.from('Buffer创建方法');
     console.log(buf3);
    
     // 写入Buffer数据
     buf1.write('hello');
     console.log(buf1);
    
     // 读取Buffer数据
     console.log(buf3.toString());
    
     // 合并Buffer
     const buf4 = Buffer.concat([buf1, buf3]);
     console.log(buf4.toString());
    
    • http:用于创建web服务的模块
     // localhost:3000
     const http = require('http');
     const server = http.createServer((request, response) => {
        console.log('there is a request');
        response.end('a response from server');
     });
     server.listen(3000);
    
     const http = require('http');
     const fs = require('fs');
     http.createServer((request, response) => {
         const { url, method } = request;
    
         // 返回html,http://localhost:3000
         if (url === '/' && method === 'GET') {
             fs.readFile('index.html', (err, data) => {
                 if (err) {
                     response.writeHead(500, { 'Content-Type': 'text/plain;charset=utf8'   });
                     response.end('500,服务器错误');
                     return ;
                 }
                 response.statusCode = 200;
                 response.setHeader('Content-Type', 'text/html');
                 response.end(data);
             });
         } else {
             response.statusCode = 404;
             response.setHeader('Content-Type', 'text/plain;charset=utf-8');
             response.end('404, 页面没有找到');
       }
    
         // 实现接口
         // http://localhost:3000/users
         // if (url === '/users' && method === 'GET') {
         //     response.writeHead(200, { 'Content-Type': 'application/json' });
         //     response.end(JSON.stringify({ code: 0, data: [{name:'tom',age:20}], message: 'success'}));
         // }
     }).listen(3000); 
    
     // 打印原型链
     function getPrototypeChain(object) {
         let protoChain = [];
         // Object.getPrototypeOf() 方法返回指定对象的原型
         while (object = Object.getPrototypeOf(object)) {
             protoChain.push(object);
         }
         protoChain.push(null);
         return protoChain;
     }
     console.log(getPrototypeChain(new String(''))) // [ [String: ''], {}, null ]
     console.log(getPrototypeChain(function(){})) //  [Function], {}, null ]
    
    • stream:是用于与node中流数据交互的接口
     const fs = require('fs')
    
     /*
         createReadStream(path, option): 用来打开一个可读的文件流,返回一个fs.ReadStream对象
         @params: path 指定文件的路径
         @params: options 可选,是一个JS对象,可以指定一些选项如:
         let option = {
             flags: 'r', // 指定用什么模式打开文件,’w’代表写,’r’代表读,类似的还有’r+’、’w+’、’a’等
             encoding: 'utf8', // 指定打开文件时使用编码格式,默认就是“utf8”,你还可以为它指定”ascii”或”base64”
             fd: null, // fd属性默认为null,当你指定了这个属性时,createReadableStream会根据传入的fd创建一个流,忽略path。另外你要是想读取一个文件的特定区域,可以配置start、end属性,指定起始和结束(包含在内)的字节偏移
             mode: 0666,
             autoClose: true // autoClose属性为true(默认行为)时,当发生错误或文件读取结束时会自动关闭文件描述符
         }
     */
    
     // 读取文件
     let readStream = fs.createReadStream('./01-useModule.js', { encoding: 'utf8' });
    
     // 读取文件发生错误事件
     readStream.on('error', (err) => {
         console.log('发生异常:', err);
     });
    
     // 已打开要读取的文件事件
     readStream.on('open', (fd) => {
         console.log('文件已打开:', fd);
     });
    
     // 文件已经就位,可用于读取事件
     readStream.on('ready', () => {
         console.log('文件已准备好...');
     });
    
     // 文件读取中事件
     readStream.on('data', (chunk) => {
         console.log('读取文件数据:', chunk);
     });
    
     // 文件读取完成事件
     readStream.on('end', () => {
         console.log('读取已完成...');
     });
    
     // 文件已关闭事件
     readStream.on('close', () => {
         console.log('文件已关闭!');
     });
    
     /*
         createWriteStream():对文件流进行写入
         @params: path 指定文件的路径
         @params: options 可选,是一个JS对象,可以指定一些选项如:
         let option = {
             flags: 'w', // 指定用什么模式打开文件,’w’代表写,’r’代表读,类似的还有’r+’、’w+’、’a’等
             encoding: 'utf8', // 指定打开文件时使用编码格式,默认就是“utf8”,你还可以为它指定”ascii”或”base64”
             fd: null, // fd属性默认为null,当你指定了这个属性时,createReadableStream会根据传入的fd创建一个流,忽略path。另外你要是想读取一个文件的特定区域,可以配置start、end属性,指定起始和结束(包含在内)的字节偏移
             mode: 0666,
             autoClose: true // autoClose属性为true(默认行为)时,当发生错误或文件读取结束时会自动关闭文件描述符
         }
     */
     let writeStream = fs.createWriteStream('./index.html', { encoding: 'utf8' });
    
     // 读取文件发生错误事件
     writeStream.on('error', (err) => {
         console.log('发生异常:', err);
     });
    
     // 已打开要写入的文件事件
     writeStream.on('open', (fd) => {
         console.log('文件已打开:', fd);
     });
    
     // 文件已经就写入完成事件
     writeStream.on('finish', () => {
         console.log('写入已完成...');
     });
    
     // 文件关闭事件
     writeStream.on('close', () => {
         console.log('文件已关闭!');
     });
    
     writeStream.write(fs.readFileSync('./02-fs.js', 'utf8'));
     writeStream.end();
    
  6. 仿写一个简版Express

// my-espress.js
const http = require('http')
const url = require('url')
const router = []
class Application {
    get(path, handler) {
        if (typeof path === 'string') {
            router.push({
                path,
                method: 'get',
                handler
            })
        }
    }
    listen() {
        const server = http.createServer((req, res) => {
            const { pathname } = url.parse(req.url, true)
            for (const route of router) {
                const { path, method, handler } = route
                if (pathname == path && req.method.toLowerCase() == method) {
                    return handler(req, res)
                }
            }
        })
        server.listen(...arguments)
    }
}
module.exports = function createApplication() {
    return new Application()
}
// const express = require('express');
// const app = express();

const myExpress = require('./my-espress');
const app = myExpress();

app.get('/', (req, res) => {
    res.end('Hello World');
});
app.get('/users', (req, res) => {
    res.end(JSON.stringify([{ name: 'tom', age: 20 }]));
});
app.listen(3000, () => {
    console.log('Example app listen at 3000');
});
  1. 事件event
const { EventEmitter } = require('events')
const event = new EventEmitter()

// 监听器 #1
const listener1 = function listener1() {
    console.log('监听器 listener1 执行。');
}

// 监听器 #2
const listener2 = function listener2() {
    console.log('监听器 listener2 执行。');
}

// 绑定 connection 事件,处理函数为 listener1 
event.addListener('connection', listener1);

// 绑定 connection 事件,处理函数为 listener2
event.on('connection', listener2);

// 为指定事件注册一个单次监听器,即 监听器最多只会触发一次,触发后立刻解除该监听器
event.once('onceconnection', () => console.log('onceconnection'));

// 返回指定事件的监听器数量
let eventListeners = event.listenerCount('connection');
console.log(eventListeners + ' 个监听器监听连接事件。');

// 按监听器的顺序执行执行每个监听器,如果事件有注册监听返回 true,否则返回 false。
// 处理 connection 事件 
event.emit('connection');
event.emit('onceconnection');

// 移除指定事件的某个监听器,监听器必须是该事件已经注册过的监听器。需要注意的是,此操作将会改变处于被删监听器之后的那些监听器的索引
// 移除监绑定的 listener1 函数
event.removeListener('connection', listener1);
console.log("listener1 不再受监听。");

// 触发连接事件
event.emit('connection');

eventListeners = event.listenerCount('connection');
console.log(eventListeners + ' 个监听器监听连接事件。');

event.on('customer_event', () => {
    console.log('custom...' + new Date())
})

setInterval(() => {
    event.emit('customer_event')
}, 1000)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354