• day3:Node.js 基础知识


    day3:Node.js 基础知识

    创建第一个应用

    实例如下,在你项目的根目录下创建一个叫 helloworld.js 的文件,并写入以下代码:

    var http = require('http');
    
    http.createServer(function (request, response) {
    
        // 发送 HTTP 头部 
        // HTTP 状态值: 200 : OK
        // 内容类型: text/plain
        response.writeHead(200, {'Content-Type': 'text/plain'});
    
        // 发送响应数据 "Hello World"
        response.end('Hello World\n');
    }).listen(8888);
    
    // 终端打印如下信息
    console.log('Server running at http://127.0.0.1:8888/');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    使用 node 命令执行以上的代码:

    [root@node3 nodejs]# node helloworld.js 
    Server running at http://127.0.0.1:8888/
    
    • 1
    • 2
    事件循环机制

    Node.js 是单进程单线程应用程序,但是因为 V8 引擎提供的异步执行回调接口,通过这些接口可以处理大量的并发,所以性能非常高。

    Node.js 几乎每一个 API 都是支持回调函数的。

    Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现。

    Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数.

    在这里插入图片描述

    EventEmitter类

    整个事件驱动的流程就是这么实现的,非常简洁。有点类似于观察者模式,事件相当于一个主题(Subject),而所有注册到这个事件上的处理函数相当于观察者(Observer)。

    Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件,如下实例:

    Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件,如下实例:

    // 引入 events 模块
    var events = require('events');
    // 创建 eventEmitter 对象
    var eventEmitter = new events.EventEmitter();
    
    • 1
    • 2
    • 3
    • 4

    以下程序绑定事件处理程序:

    // 绑定事件及事件的处理程序
    eventEmitter.on('eventName', eventHandler);
    
    • 1
    • 2

    我们可以通过程序触发事件:

    // 触发事件
    eventEmitter.emit('eventName');
    
    • 1
    • 2

    实例

    创建 main.js 文件,代码如下所示:

    // 引入 events 模块
    var events = require('events');
    // 创建 eventEmitter 对象
    var eventEmitter = new events.EventEmitter();
     
    // 创建事件处理程序
    var connectHandler = function connected() {
       console.log('连接成功。');
      
       // 触发 data_received 事件 
       eventEmitter.emit('data_received');
    }
     
    // 绑定 connection 事件处理程序
    eventEmitter.on('connection', connectHandler);
     
    // 使用匿名函数绑定 data_received 事件
    eventEmitter.on('data_received', function(){
       console.log('数据接收成功。');
    });
     
    // 触发 connection 事件 
    eventEmitter.emit('connection');
     
    console.log("程序执行完毕。");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    接下来让我们执行以上代码:

    [root@node3 nodejs]# node test1.js 
    连接成功。
    数据接收成功。
    程序执行完毕。
    
    • 1
    • 2
    • 3
    • 4

    Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。

    Node.js 里面的许多对象都会分发事件:一个 net.Server 对象会在每次有新连接时触发一个事件, 一个 fs.readStream 对象会在文件被打开的时候触发一个事件。 所有这些产生事件的对象都events.EventEmitter 的实例。

    让我们以下面的例子解释这个过程:

    //event.js 文件
    var events = require('events'); 
    var emitter = new events.EventEmitter(); 
    emitter.on('someEvent', function(arg1, arg2) { 
        console.log('listener1', arg1, arg2); 
    }); 
    emitter.on('someEvent', function(arg1, arg2) { 
        console.log('listener2', arg1, arg2); 
    }); 
    emitter.emit('someEvent', 'arg1 参数', 'arg2 参数'); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    执行以上代码,运行的结果如下:

    [root@node3 nodejs]# node event.js 
    listener1 arg1 参数 arg2 参数
    listener2 arg1 参数 arg2 参数
    
    • 1
    • 2
    • 3

    以上例子中,emitter 为事件 someEvent 注册了两个事件监听器,然后触发了 someEvent 事件。

    运行结果中可以看到两个事件监听器回调函数被先后调用。 这就是EventEmitter最简单的用法。

    EventEmitter 提供了多个属性,如 onemiton 函数用于绑定事件函数,emit 属性用于触发一个事件。

    EventEmitters 如果你添加的监听器超过 10 个就会输出警告信息, setMaxListeners 函数用于改变监听器的默认限制的数量。

    异步编程

    回调

    在代码中,异步编程的直接体现就是回调。异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了。我们首先可以看看以下代码。

    function heavyCompute(n, callback) {
        var count = 0,
            i, j;
    
        for (i = n; i > 0; --i) {
            for (j = n; j > 0; --j) {
                count += 1;
            }
        }
    
        callback(count);
    }
    
    heavyCompute(10000, function (count) {
        console.log(count);
    });
    
    console.log('hello');
    
    -- Console ------------------------------
    100000000
    hello
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    可以看到,以上代码中的回调函数仍然先于后续代码执行。JS本身是单线程运行的,不可能在一段代码还未结束运行时去运行别的代码,因此也就不存在异步执行的概念。

    function heavyCompute(n) {
        var count = 0,
            i, j;
    
        for (i = n; i > 0; --i) {
            for (j = n; j > 0; --j) {
                count += 1;
            }
        }
        console.log("function heavy:"+count);
    }
    
    var t = new Date();
    
    setTimeout(function () {
        
        console.log("setTimeout:" +(new Date() - t));
    
    }, 1000);
    
    heavyCompute(50000);
    
    -- Console ------------------------------
    function heavy:2500000000
    setTimeout:5329
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    模块系统

    为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统。

    模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码、JSON 或者编译过的C/C++ 扩展。

    引入模块

    在 Node.js 中,引入一个模块非常简单,如下我们创建一个 main.js 文件并引入 hello 模块,代码如下:

    var hello = require('./hello');
    hello.world();
    
    • 1
    • 2

    以上实例中,代码 require(‘./hello’) 引入了当前目录下的 hello.js 文件(./ 为当前目录,node.js 默认后缀为 js)。

    Node.js 提供了 exports 和 require 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。

    接下来我们就来创建 hello.js 文件,代码如下:

    exports.world = function() { #类似Java public修饰词
      console.log('Hello World');
    }
    
    • 1
    • 2
    • 3

    在以上示例中,hello.js 通过 exports 对象把 world 作为模块的访问接口,在 main.js 中通过 require(‘./hello’) 加载这个模块,然后就可以直接访 问 hello.js 中 exports 对象的成员函数了.

    有时候我们只是想把一个对象封装到模块中,格式如下:

    module.exports = function() {
      // ...
    }
    
    • 1
    • 2
    • 3

    例如:

    //hello.js 
    function Hello() { 
        var name; 
        this.setName = function(thyName) { 
            name = thyName; 
        }; 
        this.sayHello = function() { 
            console.log('Hello ' + name); 
        }; 
    }; 
    module.exports = Hello;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这样就可以直接获得这个对象了:

    //main.js 
    var Hello = require('./hello'); 
    hello = new Hello(); 
    hello.setName('BYVoid'); 
    hello.sayHello(); 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Node.js 的 require 方法中的文件查找策略如下

    在这里插入图片描述

    exports 和 module.exports 的使用

    如果要对外暴露属性或方法,就用 exports 就行,要暴露对象(类似class,包含了很多属性和方法),就用 module.exports

    函数与回调函数

    在 JavaScript中,一个函数可以作为另一个函数的参数。我们可以先定义一个函数,然后传递,也可以在传递参数的地方直接定义函数。

    Node.js 中函数的使用与 JavaScript 类似,举例来说,你可以这样做:

    function say(word) {
      console.log(word);
    }
    
    function execute(someFunction, value) {
      someFunction(value);
    }
    
    execute(say, "Hello");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    以上代码中,我们把 say 函数作为 execute 函数的第一个变量进行了传递。这里传递的不是 say 的返回值,而是 say 本身!

    这样一来, say 就变成了execute 中的本地变量 someFunction ,execute 可以通过调用 someFunction() (带括号的形式)来使用 say 函数。

    当然,因为 say 有一个变量, execute 在调用 someFunction 时可以传递这样一个变量。


    匿名函数

    我们可以把一个函数作为变量传递。但是我们不一定要绕这个"先定义,再传递"的圈子,我们可以直接在另一个函数的括号中定义和传递这个函数:

    function execute(someFunction, value) {
      someFunction(value);
    }
    
    execute(function(word){ console.log(word) }, "Hello");
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们在 execute 接受第一个参数的地方直接定义了我们准备传递给 execute 的函数。

    用这种方式,我们甚至不用给这个函数起名字,这也是为什么它被叫做匿名函数 。


    函数传递是如何让HTTP服务器工作的

    带着这些知识,我们再来看看我们简约而不简单的HTTP服务器:

    var http = require("http");
    
    http.createServer(function(request, response) {
      response.writeHead(200, {"Content-Type": "text/plain"});
      response.write("Hello World");
      response.end();
    }).listen(8888);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    现在它看上去应该清晰了很多:我们向 createServer 函数传递了一个匿名函数。

    用这样的代码也可以达到同样的目的:

    var http = require("http");
    
    function onRequest(request, response) {
      response.writeHead(200, {"Content-Type": "text/plain"});
      response.write("Hello World");
      response.end();
    }
    
    http.createServer(onRequest).listen(8888);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    回调函数

    Node.js 异步编程的直接体现就是回调。

    异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了。

    回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数。

    例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。

    阻塞代码实例

    创建一个文件 input.txt ,内容如下:

    菜鸟教程官网地址:www.runoob.com
    
    • 1

    创建 main.js 文件, 代码如下:

    var fs = require("fs");
    
    var data = fs.readFileSync('input.txt');
    
    console.log(data.toString());
    console.log("程序执行结束!");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    以上代码执行结果如下:

    $ node main.js
    菜鸟教程官网地址:www.runoob.com
    
    程序执行结束!
    
    • 1
    • 2
    • 3
    • 4

    非阻塞代码实例

    创建一个文件 input.txt ,内容如下:

    菜鸟教程官网地址:www.runoob.com
    
    • 1

    创建 main.js 文件, 代码如下:

    var fs = require("fs");
    
    fs.readFile('input.txt', function (err, data) {
        if (err) return console.error(err);
        console.log(data.toString());
    });
    
    console.log("程序执行结束!");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    以上代码执行结果如下:

    $ node main.js
    程序执行结束!
    菜鸟教程官网地址:www.runoob.com
    
    • 1
    • 2
    • 3
    路由和全局对象

    路由

    Node.js提供了urlquerystring模块,用于处理和解析URL及查询字符串。

    url模块提供了一些实用函数,用于解析和格式化URL。以下是url模块的一些常用方法:

    • url.parse(urlString[, parseQueryString[, slashesDenoteHost]]): 解析给定的URL字符串并返回一个URL对象。如果parseQueryStringtrue,则将查询字符串解析为查询参数对象。如果slashesDenoteHosttrue,则将双斜杠视为主机名的开始。

    • url.format(urlObject): 将URL对象格式化为URL字符串。

      • url.resolve(from, to): 解析一个目标URL相对于一个基础URL的路径。

      例如,使用url.parse()方法解析一个URL:

      const url = require(‘url’);

      
      const parsedUrl = url.parse('http://example.com/path?query=string#hash');  
      console.log(parsedUrl);
      
      • 1
      • 2
      • 3

      输出结果将是一个URL对象,包含了URL的各个部分的信息。

      querystring模块提供了一些函数,用于解析和格式化查询字符串。以下是querystring模块的一些常用方法:

      • querystring.parse(str[, sep[, eq]]): 解析查询字符串为一个键值对对象。sep参数指定键值对的分隔符,默认为&eq参数指定键和值的分隔符,默认为=
      • querystring.stringify(obj[, sep[, eq]]): 将一个键值对对象格式化为查询字符串。参数意义与querystring.parse()相同。

      例如,使用querystring.parse()方法解析一个查询字符串:

      const querystring = require('querystring');  
        
      const queryString = 'name=John&age=30';  
      const queryParams = querystring.parse(queryString);  
      console.log(queryParams);
      
      • 1
      • 2
      • 3
      • 4
      • 5

      输出结果将是一个包含查询参数的对象。

      变量作用域

      在Node.js中,可以通过变量的声明方式和位置来区分不同的作用域范围。

      1. 全局作用域:在Node.js中,不在任何函数内部直接声明的变量具有全局作用域。这些变量可以在整个应用程序中访问。

      例如:

      var globalVar = "I am global!";
      
      • 1
      1. 局部作用域:在函数内部使用var声明的变量具有局部作用域。这些变量只能在该函数内部访问。

      例如:

      function myFunction() {  
        var localVar = "I am local!";  
      }
      
      • 1
      • 2
      • 3
      1. 块级作用域:使用letconst声明的变量具有块级作用域。块级作用域是指在一对花括号{}内定义的作用域。这些变量可以在块级作用域内访问。

      例如:

      if (true) {  
        let blockVar = "I am block-scoped!";  
      }
      
      • 1
      • 2
      • 3

      需要注意的是,Node.js采用了CommonJS模块系统,每个模块都有自己的作用域。在模块中定义的变量和函数默认情况下是局部的,只能在模块内部访问,但可以通过exports对象将它们导出,使其可以在其他模块中使用。

      在这里插入图片描述

    • 相关阅读:
      谈谈Net-SNMP软件
      【调制解调】QPSK信号的调制解调附matlab代码
      vue3 + antd 图片上传 (精简篇)cv即可
      附录A 程序员工作面试的秘密
      基础图论算法--最小生成树——prim、Kruskal算法
      列出连通集
      CTFHub技能树web之XSS
      [PR] LRTA* 搜索算法
      Mongodb命令行基本操作及增删改查带分页
      (21)多线程实例应用:双色球(6红+1蓝)
    • 原文地址:https://blog.csdn.net/RodJohnsonDoctor/article/details/133885921