node相关知识点整理

188 阅读11分钟

一、基础

  1. 安装

    • nvm、n:切换版本
    curl -ohttps://raw.githubusercontent.com/nvmsh/nvm/v0.35.3/install.sh | bash
    

    原理:切换时改变命令的指向

    • npm:包管理⼯具
    npm i -g create-react-app
    create-react-app some-repo
    
    • npx:npm@5 之后新增的⼀个命令,它使得我们可以在不安装模块到当前环境的前提下,使⽤⼀些 cli 功能
    npx create-react-app some-repo
    
  2. 底层依赖

    • V8 引擎: 主要是 JS 语法的解析,有了它才能识别 JS语法
    • libuv: c 语⾔实现的⼀个⾼性能异步⾮阻塞 IO 库,⽤来实现 node.js 的事件循环
    • http-parser/llhttp: 底层处理 http 请求,处理报⽂,解析请求包等内容
    • openssl: 处理加密算法,各种框架运⽤⼴泛
    • zlib: 处理压缩等内容
  3. 内置模块

    • fs: ⽂件系统,能够读取写⼊当前安装系统环境中硬盘的数据
    const fs = require('fs');
    const path = require('path');
    const pathTo = path.resolve(__dirname, './demo.js')
    // 异步读取
    fs.readFile(pathTo, 'utf8', function(err, data) {
        if(err){
            console.log(err, 'err')
            return err;
        }
        console.log(data, 'data')
    })
    // 同步读取
    const content = fs.readFileSync(pathTo, 'utf8')
    console.log(content, 'content')
    // 封装promisefy
    function promisefy(func){
    return function(...args){
        return new Promise(()=>{
            args.push(function(err, result){
                if(err) return reject(err)
                return resolve(result)
            })
            return func.apply(this, args)
            })
        }
    }
    const readFileAsync = promisefy(fs.readFile);
    readFileAsync(pathTo, 'utf-8')
        .then(content=>{
            console.log(content)
        })
        .catch(e=>{
            console.log(e, 'e')
        })
    
    • path: 路径系统,能够处理路径之间的问题
    const path = require('path')
    //  把参数变成当前系统合法的文件路径
    const re = path.resolve('a','..','b','c/') // 返回绝对路径
    const jo = path.join('a','..','b','c/') // 直接拼接命令
    
    • crypto: 加密相关模块,能够以标准的加密⽅式对我们的内容进⾏加解密
    • dns: 处理 dns 相关内容,例如我们可以设置 dns 服务器等等
    • http: 设置⼀个 http 服务器,发送 http 请求,监听响应等等
    const http = require('http');
    const proxy = http.createServer((req, res)=>{
        // 写入请求头
        res.writeHead(200, {'name' : 'hi-wo'})
        // 写入页面内容
        res.end('hi')
    })
    proxy.listen(8888, '127.0.0.1',()=>{
        console.log('server')
    })
    
    • readline: 读取 stdin 的⼀⾏内容,可以读取、增加、删除我们命令⾏中的内容
    • os: 操作系统层⾯的⼀些 api,例如告诉你当前系统类型及⼀些参数
    • vm: ⼀个专⻔处理沙箱的虚拟机模块,底层主要来调⽤ v8 相关 api 进⾏代码解析 **
    const vm = require('vm');
    const path = require('path');
    const fs = require('fs');
    
    function r(filename){
        const pathTo = path.resolve(__dirname,filename);
        const content = fs.readFileSync(pathTo, 'utf-8')
        
        const wrapper = [
            '(function(require, module, exports) {',
            '})'
        ]
        const wrapperCon = wrapper[0] + content + wrapper[1]
        const script = new vm.Script(wrapperCon, {
            filename: 'index.js'
        })
    
        const module ={
            exports: {}
        }
        const result = script.runInThisContext()
        result(r, module, module.exports)
        return module.exports
    }
    
    global.r = r
    
  4. 拓展

    • quickjs quickjs 是⼀个 JS 的解析引擎,轻量代码量也不⼤,与之功能类似的就是 V8 引擎。
    • deno deno 是⼀类类似于 node.js 的 JS 运⾏时环境,同时他也是由 node.js 之⽗⼀⼿打造出来的。 deno 也是基于 V8 ,上层封装⼀些系统级别的调⽤我们的 deno 应⽤也可以使⽤ JS 开发。 deno 基于 rust 和 typescript 开发⼀些上层模块,所以我们可以直接在 deno 应⽤中书写 ts。 deno ⽀持从 url 加载模块,同时⽀持 top level await 等特性

二、原理

  1. 全局对象

    • __filename:表示当前正在执⾏的脚本的⽂件名。它将输出⽂件所在位置的绝对路径。
    • __dirname:dirname 表示当前执⾏脚本所在的⽬录。
    • setTimeout(cb, ms)、clearTimeout、setInterval、clearInterval、console
    • process:Process 是⼀个全局变量,即 global 对象的属性。它⽤于描述当前Node.js 进程状态的对象。
    • require:实现模块的加载
    • module、exports:处理模块的导出
  2. 核心模块(其他全局对象)

    • Buffer:该类用来创建一个专门存放二进制数据的缓存区,为 Node.js 带来了一种存储原始数据的方法,可以让 Node.js 处理二进制数据。
    // Buffer 类是全局的对象,可以直接使⽤,官⽅推荐显式引⽤
    import { Buffer } from 'buffer';
    
    // new Buffer() 建议弃⽤的
    
    // Buffer.from() 从字符串或者数组创建一个buffer
    Buffer.from(array)
    Buffer.from(arrayBuffer, byteOffset, length)
    Buffer.from(buffer)
    Buffer.from(object, offsetOrEncoding, length)
    Buffer.from(string, encoding)
    
    // Buffer.alloc 创建一个指定大小的buffer
    //fill:默认0, encoding默认值: 'utf8'。
    Buffer.alloc(size ,fill, encoding)
    // 创建⻓度为 10 的以零填充的缓冲区。
    const buf1 = Buffer.alloc(10);
    // 创建⻓度为 10 的缓冲区,
    // 使⽤值为 `1` 的字节填充。
    const buf2 = Buffer.alloc(10, 1);
    // 没有做处理,有内存泄露的风险,但速度更快
    const buf3 = Buffer.allocUnsafe(10);
    
    // Buffer 的应⽤场景1
    const fs = require('fs');
    const path = require('path');
    // function to encode file data to base64 encoded string
    function base64_encode(file) {
    let bitmap = fs.readFileSync(file);
    return Buffer.from(bitmap).toString('base64');
    }
    // function to create file from base64 encoded string
    function base64_decode(base64str, file) {
    let bitmap = Buffer.from(base64str, 'base64');
    fs.writeFileSync(file, bitmap);
    console.log('******** File created from base64 encoded string********');
    }
    let base64str = base64_encode(path.join(__dirname, './play.png'));
    console.log(base64str);
    base64_decode(base64str, path.join(__dirname, 'copy.png'));
    
    
    // Buffer 的应⽤场景2
    const fs = require('fs');
    const path = require('path');
    const rs = fs.createReadStream(path.join(__dirname, '1.txt'));
    let data = '';
    rs.on('data', function (chunk) {
    data += chunk;
    });
    rs.on('end', function () {
    console.log(data);
    });
    
    // 乱码
    // 解决⽅式1
    // 可读流有⼀个设置编码的⽅法setEncoding(): 默认情况下没有设置字符编码,流数据返回的是 Buffer 对象。 如果设置了字符编码,则流数据返回指定编码的字符串
    let rs = fs.createReadStream('test.md', {highWaterMark: 11});
    rs.setEncoding('utf8');
    
    // 解决⽅式2
    // 将⼩Buffer 拼接成⼤Buffer 对象
    let chunks = [];
    let size = 0;
    res.on('data', function (chunk) {
    chunks.push(chunk);
    size += chunk.length;
    });
    res.on('end', function () {
    let buf = Buffer.concat(chunks, size);
    let str = iconv.decode(buf, 'utf8');
    console.log(str);
    });
    
    • Stream:Stream 是一个抽象接口,使⽤Stream可实现数据的流式处理。
    // Readable - 可读操作。 Writable - 可写操作。 Duplex - 可读可写操作. Transform - 操作被写⼊数据,然后读出结果。
    // Duplex 与 Transform 的异同:Duplex可读可写独立,Transform关联,对可读流做了一些操作,输出一些可写流
    // Stream 的优势:形成管道,方便处理文件
    
    // src/pipe.js
    var fs = require("fs");
    var zlib = require('zlib');
    // 压缩 input.txt ⽂件为 input.txt.gz
    fs.createReadStream('input.txt')
    .pipe(zlib.createGzip())
    .pipe(fs.createWriteStream('input.txt.gz'));
    console.log("⽂件压缩完成。");
    
    // pipe实现原理
    Readable.prototype.pipe = function(dest, options) {
        const src = this;
        src.on('data', ondata);
        function ondata(chunk) {
        const ret = dest.write(chunk);
        if (ret === false) {
        // ...
        src.pause();
        }
        }
        // ...
        // 保持链式调⽤
        return dest;
    };
    
    • Events:Events模块是node的核⼼模块之⼀,⼏乎所有常⽤的node模块都继承了events模块。
    // 常⽤API
    emitter.on(eventName, listener)
    emitter.once(eventName, listener)
    emitter.off(eventName, listener)
    emitter.emit(eventName[, ...args])
    /* 
    emitter.addListener(eventName, listener) 
    emitter.removeListener(eventName, listener) 
    */
    
    //简单实现上述⽅法
    class MyEventsEmitter{
        constructor(){
        this.events = {};
        }
        // 绑定事件
        on(event, cbFn){
            if(!this.events[event]){
            this.events[event] = [];
            }
            this.events[event].push(cbFn);
            return this;
        }
        // 解绑事件
        off(event, cbFn){
            if (this._events[type]) {
            // 过滤掉该事件
            this._events[type] =
            this._events[type].filter(fn => {
            return fn !== cbFn
            });
            }
        }
        // 只响应⼀次
        once(event, cbFn){
            const onlyonce = (...args)=> {
            this.off(event, cbFn);
            cbFn.apply(this, args);
            }
            this.on(event, onlyonce);
            return this;
        }
        // 触发监听事件
        emit(event, ...args){
            if (this.events[event]) {
            this.events[event].forEach(fn => fn.call(this, ...args));
            }
        }
    }
    

三、事件循环

           ┌───────────────────────────┐
        ┌─>│           timers          │
        │  └─────────────┬─────────────┘
        │           nextTickQueue
        │  ┌─────────────┴─────────────┐
        │  │     pending callbacks     │
        │  └─────────────┬─────────────┘
        │           nextTickQueue
        │  ┌─────────────┴─────────────┐
        |  |     idle, prepare|  └─────────────┬─────────────┘
  nextTickQueue     nextTickQueue
        |  ┌─────────────┴─────────────┐
        |  │           poll            │
        │  └─────────────┬─────────────┘
        │           nextTickQueue
        │  ┌─────────────┴─────────────┐
        │  │           check           │
        │  └─────────────┬─────────────┘
        │           nextTickQueue
        │  ┌─────────────┴─────────────┐
        └──┤       close callbacks     │
           └───────────────────────────┘
  1. 各阶段

    • timers:此阶段执行由 setTimeout 和 setInterval 设置的回调。
    • pending callbacks:执行推迟到下一个循环迭代的 I/O 回调。
    • idle, prepare, :仅在内部使用。
    • poll:取出新完成的 I/O 事件;执行与 I/O 相关的回调(除了关闭回调,计时器调度的回调和 setImmediate 之外,几乎所有这些回调) 适当时,node 将在此处阻塞。
    • check:在这里调用 setImmediate 回调。
    • close callbacks:一些关闭回调,例如 socket.on('close', ...)。
  2. setImmediate 和 setTimeout 的区别

    • setImmediate 设计为在当前轮询 poll 阶段完成后执行脚本
    • setTimeout 计划在以毫秒为单位的最小阈值过去之后运行脚本
    • 在主模块中执行,两者的执行顺序是不固定的(在主代码部分执行 setTimeout 设置定时器不一定写入队列,而setImmediate 会写入 check 队列)
    setTimeout(() => {
    console.log('timeout');
    }, 0);
    
    setImmediate(() => {
    console.log('immediate');
    });
    
    • 在同一个I/O回调里执行,setImmediate总是先执行
    const fs = require('fs');
    fs.readFile(__filename, () => {
        setTimeout(() => {
            console.log('timeout');
        }, 0);
        setImmediate(() => {
            console.log('immediate');
        });
    });
    
  3. process.nextTick 和 setImmediate 的区别

    • process.nextTick 在同一阶段立即触发
    • setImmediate fires on the following iteration or 'tick' of the event loop (在事件循环接下来的阶段迭代中执行 - check 阶段)
  4. Microtasks 微任务 微任务是来自以下对象的回调:

    • process.nextTick()
    • then() 优先级 process.nextTick > promise.then
  5. 输出顺序

    async function async1() {
        console.log('async1 start')
        await async2()
        console.log('async1 end')
    }
    async function async2() {
        console.log('async2')
    }
    console.log('script start')
    setTimeout(function () {
        console.log('setTimeout0')
        setTimeout(function () {
            console.log('setTimeout1');
        }, 0);
        setImmediate(() => console.log('setImmediate'));
    }, 0)
    
    process.nextTick(() => console.log('nextTick'));
    async1();
    new Promise(function (resolve) {
        console.log('promise1')
        resolve();
        console.log('promise2')
    }).then(function () {
        console.log('promise3')
    })
    console.log('script end')
    // script start
    // async1 start
    // async2
    // promise1
    // promise2
    // script end
    // nextTick
    // async1 end
    // promise3
    // setTimeout0
    // setImmediate
    // setTimeout1
    

四、网络

  1. 创建TCP 创建tcp服务端

    const net = require('net');
    
    const HOST = '127.0.0.1';
    const PORT = 7777;
    
    // 创建一个TCP服务器实例,调用listen函数开始监听指定端口
    // net.createServer()有一个参数, 是监听连接建立的回调
    net.createServer((socket) => {
        const remoteName = `${socket.remoteAddress}:${socket.remotePort}`;
        // 建立成功了一个连接, 这个回调函数里返回一个socket对象.
        console.log(`${remoteName} 连接到本服务器`);
    
        // 接收消息
        socket.on('data', (data) => {
            console.log(`${remoteName} - ${data}`)
            // 给客户端发消息
            socket.write(`你刚才说啥?是${data}吗?`);
        });
    
        // 关闭
        socket.on('close', (data) => {
            console.log(`${remoteName} 连接关闭`)
        });
    
    }).listen(PORT, HOST);
    
    console.log(`Server listening on ${HOST}:${PORT}`);
    

    创建tcp客户端

    const net = require('net');
    
    const HOST = '127.0.0.1';
    const PORT = 7777;
    
    const client = new net.Socket();
    const ServerName = `${HOST}:${PORT}`;
    let count = 0;
    
    client.connect(PORT, HOST, () => {
        console.log(`成功连接到 ${ServerName}`);
        // 向服务端发送数据
        const timer = setInterval(() => {
            if (count > 10) {
                client.write('我没事了, 告辞');
                clearInterval(timer);
                return;
            }
            client.write('马冬梅' + count++);
        }, 1000)
    });
    
    // 接收消息
    client.on('data', (data) => {
        console.log(`${ServerName} - ${data}`);
        // 关闭连接
        // client.destroy();
    });
    
    // 关闭事件
    client.on('close', () => {
        console.log('Connection closed');
    });
    
    client.on('error', (error) => {
        console.log(error);
    })
    
  2. 创建UDP 创建udp服务端

    const dgram = require('dgram');
    const server = dgram.createSocket('udp4');
    
    server.on('message', (msg, remote) => {
        console.log(`${remote.address}:${remote.port} - ${msg}`)
        server.send(`收到!`, remote.port, remote.address);
    })
    
    server.on('listening', () => {
        const address = server.address()
        console.log(`Server listening on ${address.address}:${address.port}`);
    })
    server.bind(44444);
    

    创建udp客户端

    const dgram = require('dgram')
    const message = Buffer.alloc(5, 'lubai')
    const client = dgram.createSocket('udp4')
    
    client.send(message, 0, message.length, 44444, 'localhost',
        (err, bytes) => {
            console.log(`发送成功${bytes}字节`);
            // client.close()
        }
    )
    
    client.on('message', (buffer) => {
        console.log(buffer.toString())
    })
    
  3. 创建HTTP http-server.js

    const http = require('http')
    http.createServer(function (req, res) {
        res.writeHead(200, {
            'Content-Type': 'text/plain'
        })
        res.end('Hello World')
    }).listen(80, '127.0.0.1')
    
    console.log('Server running at http://127.0.0.1:80/')
    

    请求报文

    // 三次握手
    * Rebuilt URL to: http://127.0.0.1:80/
    *   Trying 127.0.0.1...
    * TCP_NODELAY set
    * Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
    
    // 客户端向服务端发送请求报文
    > GET / HTTP/1.1
    > Host: 127.0.0.1:80
    > User-Agent: curl/7.54.0
    > Accept: */*
    >
    
    // 服务端响应客户端内容
    < HTTP/1.1 200 OK
    < Content-Type: text/plain
    < Date: Wed, 04 Aug 2021 15:55:55 GMT
    < Connection: keep-alive
    < Keep-Alive: timeout=5
    < Transfer-Encoding: chunked
    <
    * Connection #0 to host 127.0.0.1 left intact
    Hello World%
    

    htttp-client.js

    const http = require('http')
    
    const options = {
        hostname: '127.0.0.1',
        port: 80,
        path: '/',
        method: 'GET'
    }
    const req = http.request(options, (res) => {
        console.log(`Status=${res.statusCode}, Headers=${JSON.stringify(res.headers)}`);
        res.setEncoding('utf8')
        res.on('data', (data) => {
            console.log(data)
        })
    })
    req.end()
    

五、框架

  1. koa express 区别
    • express回调地狱,koa1使用generator函数解决,koa2使用await函数解决
  2. http 模块实现简单web服务
    const http = require('http');
    const fs = require('fs');
    http.createServer((request, response)=>{
        fs.readFile(__dirname + '/index.html', 'binary', (error, file)=>{
            if(error){
                console.log(error);
            } else {
                response.writeHead(200, {'Content-Type': 'text/html'});
                response.end(file, 'binary');
            }
        })
    }).listen(9000);
    console.log('http server start')
    
  3. koa2 框架实现web 服务
    const Koa = require('koa')
    const app = new Koa()
    app.use((ctx, next) => {
        ctx.response.type = 'html';
        ctx.response.body = '<p>Koa</p>'
    });
    app.listen(9000)
    console.log('Koa server start')
    
  4. koa2 模板中间件 koa-art-template
    const artTemplate = require('art-template');
    artTemplate.default.import.toLocaleLowerCase = (str) => {
        return str.toLocaleLowerCase();
    }
    
    render(app, {
        root: path.join(__dirname, 'views'),
        extname: '.art',
        debug: process.env.NODE_ENV !== 'production'
    })
    
    router.get('/', async (ctx) => {
        let data = {
            title: 'title',
            context: 'content',
        }
        await ctx.render('index', data);
    })
    
  5. koa2的源码解析 application.js context.js request.js response.js
    • application.js
    class Application extends Emitter {
    constructor(options) {
    super();
        //  ...
    this.middleware = []; // 中间件队列
        //  ...
    }
    // ⼊⼝⽅法
    listen(...args) {
        debug('listen');
        const server = http.createServer(this.callback());
        return server.listen(...args);
    }
    // 添加中间件⽅法
    use(fn) {
        //  ...
        this.middleware.push(fn);
        return this;
    }
    callback(){
        const fn = compose(this.middleware);
        // 换个名字 handleRequest => handleRequestFn
        const handleRequestFn = (req, res) => {
            const ctx = this.createContext(req, res);
            return this.handleRequest(ctx, fn);
        };
        return handleRequestFn;
    }
    // 错误处理
    handleRequest(ctx, fnMiddleware) {
        //  ...
        return fnMiddleware(ctx).then(handleResponse).catch(onerror);
    }
    // 重新封装实例对象,⽅便了多个地⽅同时使⽤
    createContext(req, res) {
        //  ...
        return context;
    }
    }
    
    • context.js 主要是为ctx 增加相关的属性和⽅法
    • request.js 主要是定义request 对象上的属性和⽅法
    • response.js 主要是定义response 对象上的属性和⽅法
    • compose 返回的⽅法
    return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
        if (i <= index) return Promise.reject(new Error('next() called multiple times'))
        index = i
        let fn = middleware[i]
        if (i === middleware.length) fn = next
        if (!fn) return Promise.resolve()
        try {
            // 执⾏当前中间件⽅法,参数为 context 和 下⼀个中间件⽅法。
            // 这就是我们为什么要在中间件中去执⾏ next() ⽅法的原因,不执⾏next,下⼀个中间件就⽆法执⾏。
            return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
        } catch (err) {
            return Promise.reject(err)
        }
    }
    }
    
  6. 中间件
    • 中间件函数
    async function mid1(ctx, next) {
        ...
        await next(); // next() <==> dispatch(1); //即下⼀个中间件 <==> mid2();
    }
    async function mid2(ctx, next) {
        ...
        await next(); // next() <==> dispatch(2) <==> mid3();
    }
    async function mid3(ctx, next) {
        ...
        await next(); // next() <==> dispatch(3) <==> 达到条件 i等于middleware⻓度,返回Promise.resolve();
    }
        app.use(mid1).use(mid2).use(mid3); // ==> middleware = ['mid1', 'mid2', 'mid3']
    
    • 常⻅中间件
      • koa-bodyparser
      • koa-router
      • koa-art-template
      • koa-views + ejs
      • koa-static
      • koa-logger 控制台输出请求⽇志的⽅法
      • koa-log4 koa-log4在log4js-node的基础上做了⼀次包装,是⽀持koa2的⼀个处理⽇志的中间件
      • koa-session
    • 开发中间件
    module.exports = async function (ctx, next) {
        ctx.headers.production = 'myProduction';
        await next();
    };
    
  7. sequelize
    • 我们选择Node的ORM框架Sequelize来操作数据库。这样,我们读写的都是JavaScript对象,Sequelize帮我们把对象变成数据库中的行。
    • 创建
    var Sequelize = require('sequelize');
    // 数据库配置⽂件
    var sqlConfig = {
        host: "127.0.0.1",
        user: "root",
        password: "qaz123456",
        database: "test"
    };
    var sequelize = new Sequelize(sqlConfig.database, sqlConfig.user,sqlConfig.password, {
        host: sqlConfig.host,
        dialect: 'mysql',
        pool: {
        max: 10,
        min: 0,
        idle: 10000
    }
    });
    var User = sequelize.define('user', {
        id: {
            type: Sequelize.BIGINT,
            primaryKey: true
        },
        username: Sequelize.STRING(100),
        birthday: Sequelize.STRING(100),
    }, { 
        freezeTableName: true, // 默认false修改表名为复数,true不修改表名,与数据库表名同步;否则会使⽤加了s 的复数表名
        tableName: 'user', // 表名
        timestamps: false // 是否⾃动添加时间戳createAt,updateAt
    });
    
    • 使用
    // 增
    User.create({
        username: 'Gaffey',
        birthday: '2007.07.07',
        }).then(function (p) {
        console.log('created.' + JSON.stringify(p));
        }).catch(function (err) {
        console.log('failed: ' + err);
    });
    // 查
    (async () => {
        var users = await User.findAll({
            where: {
            username: 'Gaffey'
        }
        });
        console.log(`find ${users.length} user:`);
        for (let user of users) {
            console.log(JSON.stringify(user));
        }
    })();
    // 改
    User.update({
        username: 'lilin'
    }, {
        where: { 'id': 1 }
    })
    // 删
    User.destroy({
        'where': {
        'id': 1
        }
    })
    

六、实战

  1. npm init
  2. yarn add superagent cheerio --registry=registry.npm.taobao.org
  3. 配置
"scripts": {
    "start": "node index.js"
},
  1. 获取百度html
const superagent = require('superagent');
const cheerio = require('cheerio');

superagent.get('http://www.baidu.com').end((err, res) => {
    if(err){
        console.log('访问失败', err);
    }else{
        console.log(res.text)
    }
})
  1. 解析html
const superagent = require('superagent');
const cheerio = require('cheerio');

superagent.get('http://www.baidu.com').end((err, res) => {
    if(err){
        console.log('访问失败', err);
    }else{
        const htmlText = res.text;
        const $ = cheerio.load(htmlText)
        $('meta').each((index, ele)=>{
            console.log('index', index);
            console.log('element', $(ele).attr('content'));
        })
    }
})
  1. 获取百度图片
const superagent = require('superagent');
const cheerio = require('cheerio');
const fs = require('fs');
const path = require('path');

const word = '猫咪'
const headers = {
    'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Connection': 'keep-alive',
    'Host': 'imgstat.baidu.com',
    'Referer': 'https://image.baidu.com/',
    'sec-ch-ua': '"Google Chrome";v="93", " Not;A Brand";v="99", "Chromium";v="93"',
    'sec-ch-ua-mobile':'?0',
    'sec-ch-ua-platform': "macOS",
    'Sec-Fetch-Dest': 'image',
    'Sec-Fetch-Mode': 'no-cors',
    'Sec-Fetch-Site': 'same-site',
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36'
}

// 访问网站
superagent.get(`http://image.baidu.com/search/index?tn=baiduimage&ie=utf-8&word=${encodeURIComponent(word)}`)
.set('Accept', headers['Accept'])
.set('Accept-Encoding', headers['Accept-Encoding'])
.set('Accept-Language', headers['Accept-Language'])
.set('Connection', headers['Connection'])
.set('sec-ch-ua', headers['sec-ch-ua'])
.set('User-Agent', headers['User-Agent'])
.end((err, res) => {
    if(err){
        console.log('访问失败', err);
    }else{
        const htmlText = res.text;
        const imagesUrlList = getRegexp(htmlText, 'thumbURL')
        const titlesUrlList = getRegexp(htmlText, 'fromPageTitle').map(item=>
            item.replace('<strong>','').replace('<//strong>','').replace('<\\/strong>','')
        )
        mkImageDir('img')
        imagesUrlList.forEach((url,index) => {
            downloadImage(url,index,titlesUrlList[index])
        })
    }
})

// 替换正则
const getRegexp = (str, key)=>{
    const reg = new RegExp(`"${key}":"(.*?)"`,'g')
    const matchResult = str.match(reg);
    const result = matchResult.map(item=>{
        const res = item.match(/:"(.*?)"/g);
        return RegExp.$1
    })
    return result
}

// 创建目录
const mkImageDir = (pathname) => {
    const fullPath = path.resolve(__dirname,pathname)
    if(fs.existsSync(fullPath)){
        console.log(pathname,'目录已经存在')
        return;
    }

    fs.mkdirSync(fullPath);
    console.log('创建目录成功')
}

// 下载图片
const downloadImage = (url, name, index) => {
    const fullPath = path.join(__dirname, 'img', `${index}-${name}.png`)
    if(fs.existsSync(fullPath)){
        console.log(name,'文件已经存在')
        return;
    }
    superagent.get(url).end((err, result) => {
        if(err){
            console.log('获取err',err)
            return
        }
        fs.writeFile(fullPath,result.body,'binary',(err)=>{
            if(err){
                console.log('下载err',err)
                return
            }
            console.log('下载成功')
        })
    })
}
  1. 优化
    • 进度条:安装 cli-progress
    • 已存在目录先删除, 再创建
    • 通过cli来输入关键词:安装 inquirer, commander
    • 自定义爬取图片的数量
    • 爬取百度视频
    • 分别配置视频和图片命令:安装 commander
    • 通过npm link, 配置全局命令

七、其他

  • 特点:
  1. 异步IO
  • IO是应用程序的瓶颈所在
  • 异步IO提高性能无采原地等待结果返回
  • IO操作属于操作系统级别,平台都有对应实现
  • Nodejs单线程配合事件驱动架构及libuv实现了异步IO
  1. 事件驱动
const EventEmitter = require('events')
const myEvent = new EventEmitter()
myEvent.on('事件1',() => { console.log('事件1执行了')})
myEvent.on('事件1', () =>{ console.log('事件1-2执行了')})
myEvent.emit('事件1')
  1. 主线程单线程
const http = require('http')
function sleepTime (time) {
const sleep = Date.now() + time 1000 
while(Date.now()<sleep){}
return
}
sleepTime(4)
const server = http.createServer((reg,res)=>{
res.end('server starting......')
})
server.listen(8080,()=>{ console.log('服务启动了')}) // 四秒后才执行
  • 实现:
// 需求:希望有一个服务,可以依据请求的接口内容返回相应的数据 
import express from 'express'
const app = express()

app.get('/',(req,res)=> {
res.end('node环境')
res.json('浏览器环境')
})

app.listen(8080,()=>{
console.log('服务已经开启了')})