Nodejs后端开发(一)-原生nodejs搭建后台服务器

Node基础

  1. 目的:相当于nodejs语法,nodejs代码该如何写以及如何执行
  2. 要注意nodejs与浏览器js的区别:nodejs是运行在node环境中,浏览器js是运行在浏览器环境中
  3. nodejs不仅仅可以用来创建web端后台服务器,也可以搭建任何后台服务器跟java一样的东西

Node简介

  1. Node是什么
    1. Node是一个基于Chrome V8引擎的JavaScript代码运行环境
    2. node就是一个软件,能够运行js代码的软件
  2. 运行环境
    1. 浏览器(软件)能够运行JavaScript代码,浏览器就是JavaScript代码的运行环境
    2. Node(软件)能够运行JavaScript代码,Node就是JavaScript代码的运行环境
    3. 运行环境指的就是一个软件,一个软件能够运行某种语言,那么这个软件就是这个语言的运行环境
  3. Chrome V8引擎
    1. 是谷歌公司创造的一款js代码执行引擎,作用就是用来执行js代码
    2. 之所以node软件能够运行js就是因为node内部包含了这个V8引擎
  4. Node.js运行环境安装
    1. 官网:https://nodejs.org/en/
    2. LTS = Long Term Support 长期支持版 稳定版
    3. Current 拥有最新特性 实验版
  5. Node.js快速入门
    1. JavaScript 由三部分组成,ECMAScript,DOM,BOM。
    2. Node.js是由ECMAScript及Node 环境提供的一些附加API组成的,包括文件、网络、路径等等一些更加强大的 API
      1. ECMAScript
      2. Node环境API
    3. Node.js基础语法
      1. 所有ECMAScript语法在Node环境中都可以使用。
      2. 在Node环境下执行代码,使用Node命令执行后缀为.js的文件即可

         //test.js代码
         var first = 'hello nodejs';
         console.log(first);
         //2. 执行命令
         node /Users/mac/Desktop/test.js 
         //3. 输出
         hello nodejs
        
    4. Node.js全局对象global
      1. 在浏览器中全局对象是window,在Node中全局对象是global。
      2. Node中全局对象下有以下方法,可以在任何地方使用,global可以省略。

         console.log() 在控制台中输出
         setTimeout() 设置超时定时器
         clearTimeout() 清除超时时定时器
         setInterval() 设置间歇定时器
         clearInterval() 清除间歇定时器
        

模块加载及第三方包

Node.js模块化开发

  1. JavaScript开发弊端
    1. JavaScript在使用时存在两大问题,文件依赖和命名冲突。
    2. 文件引入有前后顺序,文件之前依赖文件引入的前后顺序
    3. js文件之间各自的变量可以相互访问,导致冲突,替换
  2. 软件中的模块化开发
    1. 一个功能就是一个模块,多个模块可以组成完整应用,抽离一个模块不会影响其他功能的运行。
    2. 文件与文件之间是半封闭状态,可以设置每个文件中哪些内容被别的文件访问到,不设置的不允许别其他文件访问
  3. Node.js中模块化开发规范
    1. Node.js规定一个JavaScript文件就是一个模块,模块内部定义的变量和函数默认情况下在外部无法得到
    2. 模块内部可以使用exports对象进行成员导出,使用require方法导入其他模块。
    3. 模块成员导出

       // a.js
       // 在模块内部定义变量 
       let version = 1.0;
       // 在模块内部定义方法
       const sayHi = name => `您好, ${name}`; 
       // 向模块外部导出数据
       //相当于给exports对象设置属性
       exports.version = version; 
       exports.sayHi = sayHi;
      
    4. 模块成员的导入

       // b.js
       // 在b.js模块中导入模块a
       //require返回的就是exports对象
       let a = require('./a.js');
       // 输出b模块中的version变量 
       console.log(a.version);
       // 调用b模块中的sayHi方法 并输出其返回值 
       console.log(a.sayHi('rose'));
      
      1. 导入模块时后缀可以省略:let a = require('./a');
    5. 模块成员导出的另一种方式

       module.exports.version = version;
       module.exports.sayHi = sayHi;
      
      1. exports是module.exports的别名(地址引用关系),导出对象最终以module.exports为准
    6. 模块导出两种方式的联系与区别
      1. 如果没有修改都指向同一个地址,则二者相等,
      2. 如果module.exports指向修改,则以module.exports的指向为准

         exports.version = version;
         module.exports.version = version;
         //修改
         module.exports.version ={name:'张三'}
        

系统模块

  1. 什么是系统模块
    1. Node运行环境提供的API. 因为这些API都是以模块化的方式进行开发的, 所以我们又称Node运行环境提供的API为系统模块
  2. 系统模块fs 文件操作
    1. f:file 文件 ,s:system 系统,文件操作系统。
    2. 导入模块

       const fs = require('fs');
      
    3. 读取文件内容

       fs.reaFile('文件路径/文件名称'[,'文件编码'], callback);
      
    4. 写入文件内容
      1. 监控错误日志,写入到文件中,便于查看
       fs.writeFile('文件路径/文件名称', '数据', callback);
      
    5. 代码举例:

       const content = '<h3>正在使用fs.writeFile写入文件内容</h3>';
       fs.writeFile('../index.html', content, err => { 
           if (err != null) {
               console.log(err); return;
           }
       console.log('文件写入成功'); 
       });
      
  3. 系统模块path 路径操作
    1. 为什么要进行路径拼接
      1. 不同操作系统的路径分隔符不统一
      2. /public/uploads/avatar
      3. Windows 上是 \ /(正反斜杠都可以)
      4. Linux 上是 /
    2. 路径拼接语法
      1. path.join('路径', '路径', ...)

         // 导入path模块
         const path = require('path');
         // 路径拼接
         let finialPath = path.join('itcast', 'a', 'b', 'c.css');
         // 输出结果 itcast\a\b\c.css
         console.log(finialPath);
        
    3. 相对路径VS绝对路径
      1. 大多数情况下使用绝对路径,因为相对路径有时候相对的是命令行工具的当前工作目录
      2. 在读取文件或者设置文件路径时都会选择绝对路径
      3. 使用__dirname获取当前文件所在的绝对路径

         const fs = require('fs');
         const path = require('path');
         console.log(__dirname);
         console.log(path.join(__dirname, '01.helloworld.js'));
                    
         fs.readFile(path.join(__dirname, '01.helloworld.js'), 'utf8', (err, doc) => {
         	console.log(err)
         	console.log(doc)
         });
        

第三方模块

  1. 什么是第三方模块
    1. 别人写好的、具有特定功能的、我们能直接使用的模块即第三方模块,由于第三方模块通常都是由多个文件组成并且被放置 在一个文件夹中,所以又名
  2. 第三方模块有两种存在形式:
    1. 以js文件的形式存在,提供实现项目具体功能的API接口。
    2. 以命令行工具形式存在,辅助项目开发
  3. 获取第三方模块
    1. npmjs.com网站
      1. npmjs.com:第三方模块的存储和分发仓库
      2. 这个网站存储了80多万个第三方模块
      3. 这个网站提供了一个命令行工具叫npm,用于下载这些第三方模块
      4. 简单来说就是:npm是一个网站存储了很多nodejs的第三方模块,然后又提供了一个npm命令行来操作这些第三方模块
      5. npm本身也是一个第三方模块,但是这个第三方模块在安装nodejs时,自动安装了,不需要我们手动再安装
    2. npm命令行:
      1. npm (node package manager) : node的第三方模块管理工具
      2. 下载:npm install 模块名称
        1. 默认下载到当前命令执行的文件夹下面
        2. 下载完成,当前文件目录会多出一个node_modules文件夹,该文件夹内部存放的就是下载的第三方模块的名称
        3. 还会多出一个packge-lock.json文件
      3. 卸载:npm unintall package 模块名称
        1. 将第三方模块从当前目录删除
      4. 全局安装与本地安装
        1. 命令行工具:全局安装
          1. 所有项目都能使用
        2. 库文件:本地安装
          1. 只有当前项目才能使用
    3. 第三方模块 nodemon
      1. nodemon是一个命令行工具,用以辅助项目开发。
      2. 作用:
        1. 在Node.js中,每次修改文件都要在命令行工具中重新执行该文件(node newtest.js),非常繁琐。
        2. nodemon作用就是每次修改文件后自动重新执行该文件
        3. 本质是监控文件的保存操作,一旦文件保存就会执行文件
      3. 使用步骤
        1. 使用npm install nodemon -g 下载它
        2. 在命令行工具中用nodemon命令替代node命令执行文件

           nodemon test.js
          
        3. 一旦执行,终端会一直在监控状态,可以使用ctrl+c终止命令
    4. 第三方模块 nrm
      1. nrm ( npm registry manager ):npm下载地址切换工具
      2. npm默认的下载地址(npmjs.com)在国外,国内下载速度慢
        1. 因此国内第三方公司专门建立了服务器用于存储npm的第三方模块
        2. 该网站每隔10分钟就会同步一下npmjs.com网站的内容
      3. 使用步骤
        1. 使用npm install nrm -g 下载它
        2. 查询国内可用下载地址列表 nrm ls

           npm -------- https://registry.npmjs.org/
           yarn ------- https://registry.yarnpkg.com/
           cnpm ------- http://r.cnpmjs.org/
           //*代表当前默认的下载地址
           * taobao ----- https://registry.npm.taobao.org/
           nj --------- https://registry.nodejitsu.com/
           npmMirror -- https://skimdb.npmjs.com/registry/
           edunpm ----- http://registry.enpmjs.org/
          
        3. 切换npm下载地址 nrm use 下载地址名称

           nrm use taobao
          

第三方模块 Gulp

  1. Gulp是什么
    1. 基于node平台开发的前端构建工具
    2. 将机械化操作编写成任务, 想要执行机械化操作时执行一个命令行命令任务就能自动执行了
    3. 用机器代替手工,提高开发效率
    4. 举例:
      1. 当项目开发完成,部署到线上运时,为了加快网站的访问速度,通常都会将HTML/CSS/JS文件进行合并压缩
      2. 以前没有构建工具时,都是手动做这些事情,这些操作即繁琐浪费时间,又不需要动脑,此时构建工具就开始了
      3. 构建工具将这些操作编写成一个一个的任务,当想要做某个操作的时候,在命令行执行这个命令就可以了
  2. Gulp能做什么
    1. 项目上线,HTML、CSS、JS文件压缩合并
    2. 语法转换(es6、less …)
      1. es6转化为es5,less转为css语法
    3. 公共文件抽离
    4. 修改文件浏览器自动刷新
  3. Gulp使用
    1. 使用npm install gulp下载gulp库文件
      1. 此时项目会生成一个node_modules文件夹存放gulp这个第三方库
      2. package-lock.json文件
    2. 在项目根目录下建立gulpfile.js文件
    3. 重构项目的文件夹结构
      1. src目录放置源代码文件
      2. dist目录放置构建后文件
    4. 在gulpfile.js文件中编写任务.
    5. 全局安装gulp命令行执行工具npm install gulp-cli -g
    6. 在命令行工具中执行gulp任务

       //执行first任务
       gulp first
      
  4. Gulp中提供的方法
    1. gulp.src():获取任务要处理的文件
    2. gulp.dest():输出文件
    3. gulp.task():建立gulp任务
    4. gulp.watch():监控文件的变化
    5. .pipe()
     const gulp = require('gulp');
     // 使用gulp.task()方法建立任务
     gulp.task('first', () => {
         // 获取要处理的文件 
         gulp.src('./src/css/base.css')
         // 将处理后的文件输出到dist目录 
         .pipe(gulp.dest('./dist/css')); 
     });
    
  5. Gulp插件
    1. gulp这个第三方模块仅仅提供了5个方法,这5个方法只能做一些基本的操作:获取、输出等操作
    2. 文件压缩等功能都是通过gulp插件来实现的,常用gulp插件如下:
    3. 打开这些插件的官网,查看如何下载,以及如何使用
      1. 在项目目录下,通过npm命令下载到node_modules文件夹中
       gulp-htmlmin :html文件压缩
       gulp-csso :压缩css
       gulp-babel :JavaScript语法转化
       gulp-less: less语法转化
       gulp-uglify :压缩混淆JavaScript
       gulp-file-include 公共文件包含
       browsersync 浏览器实时同步
      
    4. 公共文件抽取gulp-file-include插件的使用
      1. npm安装库到当前项目下:npm install gulp-file-include
      2. 在gulpfile.js编写任务
      3. 将源码src文件中的所有重复代码手动抽取到src文件夹下common文件夹的header.html文件中
      4. 在原来抽取掉的地方引入抽取出来的共同代码,引入方式采用gulp方式:@@include('')

         <body>
             //引入功能部分:这一部分html代码重复,被抽取到common文件夹中的header.html文件中了
             @@include('./common/header.html')
             <!-- 文章列表开始 -->
             <ul class="list w1100">
             ...
         </body>
        
      5. 在命令行中执行任务

         gulp htmlmin
        
  6. 代码举例:

     //gulpfile.js
     // 引用gulp模块
     const gulp = require('gulp');
     const htmlmin = require('gulp-htmlmin');
     const fileinclude = require('gulp-file-include');
     const less = require('gulp-less');
     const csso = require('gulp-csso');
     const babel = require('gulp-babel');
     const uglify = require('gulp-uglify');
     // A:使用gulp.task建立任务
     // 1.任务的名称
     // 2.任务的回调函数
     gulp.task('first', () => {
         console.log('我们人生中的第一个gulp任务执行了');
         // 1.使用gulp.src获取要处理的文件
         gulp.src('./src/css/base.css')
         .pipe(gulp.dest('dist/css'));
     });
        
     // B:html任务
     // 1.html文件中代码的压缩操作
     // 2.抽取html文件中的公共代码
     gulp.task('htmlmin', () => {
         gulp.src('./src/*.html')
         //抽取html中的公共代码
         .pipe(fileinclude())
         // 压缩html文件中的代码,collapseWhitespace:是否压缩空格。
         .pipe(htmlmin({ collapseWhitespace: true }))
         .pipe(gulp.dest('dist'));
     });
        
     // C:css任务
     // 1.less语法转换
     // 2.css代码压缩
     gulp.task('cssmin', () => {
         // 选择css目录下的所有less文件以及css文件
         gulp.src(['./src/css/*.less', './src/css/*.css'])
         // 将less语法转换为css语法
         .pipe(less())
         // 将css代码进行压缩
         .pipe(csso())
         // 将处理的结果进行输出
         .pipe(gulp.dest('dist/css'))
     });
        
     // D:js任务
     // 1.es6代码转换
     // 2.代码压缩
     gulp.task('jsmin', () => {
         gulp.src('./src/js/*.js')
         .pipe(babel({
             // 它可以判断当前代码的运行环境 将代码转换为当前运行环境所支持的代码
             presets: ['@babel/env']
         }))
         //混淆压缩js代码
         .pipe(uglify())
         // 将处理的结果进行输出
         .pipe(gulp.dest('dist/js'))
     });
        
     // 复制文件夹,不处理,直接复制
     gulp.task('copy', () => {
         gulp.src('./src/images/*')
         .pipe(gulp.dest('dist/images'));
            
         gulp.src('./src/lib/*')
         .pipe(gulp.dest('dist/lib'))
     });
        
     // 构建任务:任务合并,执行一个任务可以执行多个任务
     gulp.task('default', ['htmlmin', 'cssmin', 'jsmin', 'copy']);
    

package.json文件

  1. node_modules文件夹的问题
    1. 文件夹以及文件过多过碎,当我们将项目整体拷贝给别人的时候,传输速度会很慢很慢.
    2. 复杂的模块依赖关系需要被记录,确保模块的版本和当前保持一致,否则会导致当前项目运行报错
  2. package.json文件的作用
    1. 项目描述文件,记录了当前项目信息,例如项目名称、版本、作者、github地址、当前项目依赖了哪些第三方模块等。
    2. 使用npm init -y命令生成。(在当前项目根目录下执行这个命令)
    3. 这样当将web项目复制给别人的时候,就不需要传递node_modules文件夹了,只需要将package.json文件传递过去就行了
  3. 项目依赖dependencies
    1. 在项目的开发阶段(本地)和(远程网络)线上运营阶段,都需要依赖的第三方包,称为项目依赖
    2. 这种依赖指项目运行过程中需要依赖的第三方库文件api
    3. 使用npm install 包名 命令下载的文件会默认被添加到 package.json 文件的 dependencies 字段中

       {
           "dependencies": { 
               "jquery": "^3.3.1“ 
           }
       }
      
  4. 开发依赖devDependencies
    1. 在项目的开发阶段需要依赖,线上运营阶段不需要依赖的第三方包,称为开发依赖,比如gulp仅仅用来上线打包的东西
    2. 使用npm install 包名 --save-dev命令将包添加到package.json文件的devDependencies字段中

       {
           "devDependencies": { 
               "gulp": "^3.9.1“ 
           }
       }
      
  5. 疑问:
    1. 项目如何根据package.json这个文件中记录的项目依赖、开发依赖下载到当前项目中?
      1. 到项目根目录执行npm install即可
      2. 执行这个命令后,npm会自动到当前目录中查找package.json文件,然后下载相应的依赖项
    2. package.json文件中的dependencies、devDependencies依赖是怎么自动添加进去的
      1. 执行npm install 包名 命令下载的第三方模块会自动添加到dependencies中
      2. 执行npm install 包名 --save-dev命令下载的第三方模块会自动添加到devDependencies中
    3. 为何要分dependencies、devDependencies依赖?
      1. dependencies:项目本地开发、线上运行都需要依赖的第三方
      2. devDependencies: 线上运行不需要依赖的第三方
      3. 好处:
        1. 本地开发执行npm install会将dependencies、devDependencies依赖都下载
        2. 线上运行执行npm install --production会将dependencies依赖下载
  6. package-lock.json文件的作用
    1. 详细记录模块与模块之间的依赖关系,每个模块的版本、下载地址
    2. 锁定包的版本,确保再次下载时不会因为包版本不同而产生问题
    3. 加快下载速度,因为该文件中已经记录了项目所依赖第三方包的树状结构和包的下载地址,重新安装时只需下载 即可,不需要做额外的工作
  7. package.json举例:

     {
       "name": "description", //项目名称
       "version": "1.0.0",  //项目版本
       "description": "",   //项目描述
       "main": "index.js",  //项目主入口文件,采用模块化开发中,都有一个主模块,这里就是主模块名字
       "scripts": { //命令别名,当某个命令非常长,可以给这个命令起一个别名,用这个别名执行命令就行了
         "test": "echo \"Error: no test specified\" && exit 1",
         "build": "nodemon app.js" //给nodemon app.js这个命令起了一个别名,直接执行build就可以了
       },
       "keywords": [],//项目关键字
       "author": "",//作者
       "license": "ISC",//项目遵循协议,ISC开放源代码协议
       //下载第三方模块时,正常使用npm install下载就行,只要项目存在package.json这个文件,npm就会自动将这个第三方模块记录这个到这个文件中
       //项目依赖
       "dependencies": {
         "formidable": "^1.2.1",
         "mime": "^2.3.1"
       },
       //开发依赖
       "devDependencies": {
         "gulp": "^3.9.1"
       }
     }
    
    1. 注意:
      1. 执行上面创建的命令别名:npm run build,本质是执行了nodemon app.js

Node.js中模块的加载机制

  1. 模块查找规则-当模块拥有路径但没有后缀时

     require('./find.js');
     require('./find');
    
    1. require方法根据模块路径查找模块,如果是完整路径,直接引入模块。
    2. 如果模块后缀省略,先找同名JS文件再找同名JS文件夹
    3. 如果找到了同名文件夹,找文件夹中的index.js
    4. 如果文件夹中没有index.js就会去当前文件夹(同名文件夹)中的package.json文件中查找main选项中的入口文件
    5. 如果找指定的入口文件不存在或者没有指定入口文件就会报错,模块没有被找到
  2. 模块查找规则-当模块没有路径且没有后缀时

     require('find');
    
    1. Node.js会假设它是系统模块
    2. Node.js会去node_modules文件夹中
    3. 首先看是否有该名字的JS文件
    4. 再看是否有该名字的文件夹
    5. 如果是文件夹看里面是否有index.js
    6. 如果没有index.js查看该文件夹中的package.json中的main选项确定模块入口文件
    7. 否则找不到报错

node环境下web服务器的搭建

目的:如何使用nodejs搭建一个web服务器

快速搭建一个node环境的web服务器

  1. 网站的组成
    1. 网站应用程序主要分为两大部分:客户端和服务器端。
    2. 客户端:在浏览器中运行的部分,就是用户看到并与之交互的界面程序。使用HTML、CSS、JavaScript构建。
    3. 服务器端:在服务器中运行的部分,负责存储数据和处理应用逻辑。
  2. Node网站服务器
    1. 能够提供网站访问服务的机器就是网站服务器,它能够接收客户端的请求,能够对请求做出响应。
  3. 开发过程中客户端和服务器端说明
    1. 在开发阶段,客户端和服务器端使用同一台电脑,即开发人员电脑。
    2. 每台电脑都有一组特殊的域名和IP代表本机
     本机域名:localhost
     本地IP :127.0.0.1
    
  4. 创建一个简单的web服务器
    1. 新建一个文件夹server
    2. 在该文件夹下新建一个app.js用于编写服务端代码

       // 引用系统模块
       const http = require('http');
       // 创建web服务器
       const app = http.createServer();
       // 当客户端发送请求的时候
       app.on('request', (req, res) => {
           // 响应
           res.end('<h1>hi, user</h1>');
       });
       // 监听3000端口
       app.listen(3000);
       console.log('服务器已启动,监听3000端口,请访问 localhost:3000');
      
    3. 运行服务器
      1. 终端执行命令:nodemon app.js
    4. 访问这个服务
      1. 在浏览器中输入http://localhost:3000

HTTP协议

  1. HTTP协议的概念
    1. 超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)规定了如何从网站服务器传输超文本到本地浏览器, 它基于客户端服务器架构工作,是客户端(用户)和服务器端(网站)请求和应答的标准。
  2. 报文
    1. 在HTTP请求和响应的过程中传递的数据块就叫报文,包括要传送的数据和一些附加信息,并且要遵守规定好的格式。
  3. 请求报文
    1. 请求方式 (Request Method)
      1. GET 请求数据
      2. POST 发送数据
    2. 请求地址 (Request URL)

       app.on('request', (req, res) => {
           // 获取请求报文 
           req.headers 
           // 获取请求地址
           req.url 
           // 获取请求方法
           req.method
       });
      
  4. 响应报文
    1. HTTP状态码
      1. 200 请求成功
      2. 404 请求的资源没有被找到
      3. 500 服务器端错误
      4. 400 客户端请求有语法错误
    2. 内容类型
      1. text/html :html文本
      2. text/css: css文本
      3. text/plain:纯文本
      4. application/javascript
      5. image/jpeg
      6. application/json
       app.on('request', (req, res) => {
           // 设置响应报文 参数:状态码,响应数据内容格式
           res.writeHead(200, { 
               'Content-Type': 'text/html;charset=utf8‘ 
           });
       });
      

HTTP请求与响应处理

  1. 请求参数
    1. 客户端向服务器端发送请求时,有时需要携带一些客户信息,客户信息需要通过请求参数的形式传递到服务器端,比如登录操作。
  2. GET请求参数
    1. 参数被放置在浏览器地址栏中,例如:http://localhost:3000/index?name=zhangsan&age=20
    2. 参数获取需要借助系统模块url,url模块用来处理url地址
      1. url.parse(req.url, true)处理后返回的对象,有很多属性
        1. search:?name=zhangsan&age=20
        2. query: name=zhangsan&age=20
        3. pathname:/index
        4. path:/index?name=zhangsan&age=20
        5. ….等属性
       const http = require('http'); 
       // 导入url系统模块 用于处理url地址 
       const url = require('url'); 
       const app = http.createServer();
       app.on('request', (req, res) => {
           // 将url路径的各个部分解析出来并返回对象 
           // true 代表将参数解析为对象格式 
           let {query} = url.parse(req.url, true); 
           console.log(query);
       }); 
       app.listen(3000);
      
    3. 代码举例:

       //app.js
       // 用于创建网站服务器的模块
       const http = require('http');
       // 用于处理url地址
       const url = require('url');
       // app对象就是网站服务器对象
       const app = http.createServer();
       // 当客户端有请求来的时候
       app.on('request', (req, res) => {
           // 获取请求方式
           // req.method
           // console.log(req.method);
                      
           // 获取请求地址
           // req.url
           // console.log(req.url);
                      
           // 获取请求报文信息
           // req.headers
           // console.log(req.headers['accept']);
                      
           //设置响应头,参数:状态码,相应数据格式
           res.writeHead(200, {
               'content-type': 'text/html;charset=utf8'
           });
                      
           console.log(req.url);
           // 1) req.url:要解析的url地址
           // 2) true:将查询参数解析成对象形式,parse方法需要导入require('url')模块
           let { query, pathname } = url.parse(req.url, true);
           //获取请求参数
           console.log(query.name)
           console.log(query.age)
                      
           //根据客户端发过来的url来判断到底是那个路径
           if (pathname == '/index' || pathname == '/') {
               res.end('<h2>欢迎来到首页</h2>');
           }else if (pathname == '/list') {
               res.end('welcome to listpage');
           }else {
               res.end('not found');
           }
           //监听请求类型
           if (req.method == 'POST') {
               res.end('post')
           } else if (req.method == 'GET') {
               res.end('get')
           }
           // res.end('<h2>hello user</h2>');
       });
       // 监听端口
       app.listen(3000);
       console.log('网站服务器启动成功');
      
  3. POST请求参数
    1. 参数被放置在请求体中进行传输
    2. 获取POST参数需要使用data事件和end事件
    3. 使用querystring系统模块将参数转换为对象格式

       // 导入系统模块querystring 用于将HTTP参数转换为对象格式 
       const querystring = require('querystring');
       app.on('request', (req, res) => {
           let postData = '';
           // 监听参数传输事件 
           req.on('data', (chunk) => postData += chunk;);
           // 监听参数传输完毕事件
           req.on('end', () => {
               console.log(querystring.parse(postData));
           });
       });
      
    4. 代码举例:

       // 用于创建网站服务器的模块
       const http = require('http');
       // app对象就是网站服务器对象
       const app = http.createServer();
       // 处理请求参数模块
       const querystring = require('querystring');
       // 当客户端有请求来的时候
       app.on('request', (req, res) => {
           // post参数是通过事件的方式接收的
           // data 当请求参数传递的时候触发data事件
           // end 当参数传递完成的时候触发end事件
                  	
           let postParams = '';
           req.on('data', params => {
               postParams += params;
           });
                      
           req.on('end', () => {
               //将参数字符串转化为对象
               console.log(querystring.parse(postParams));
           });
           res.end('ok');
       });
       // 监听端口
       app.listen(3000);
       console.log('网站服务器启动成功');
      
  4. 路由

     http://localhost:3000/index 
     http://localhost:3000/login
    
    1. 路由是指客户端请求地址与服务器端程序代码的对应关系。简单的说,就是请求什么响应什么。
    2. 代码举例:不同的路由(路径)对应不同的服务端代码

       // 当客户端发来请求的时候
       app.on('request', (req, res) => {
           // 获取客户端的请求路径
           let { pathname } = url.parse(req.url); 
           if (pathname == '/' || pathname == '/index') {
               res.end('欢迎来到首页'); 
           } else if (pathname == '/list') {
               res.end('欢迎来到列表页页'); 
           } else {
               res.end('抱歉, 您访问的页面出游了'); 
           }
       });
      
  5. 资源
    1. 静态资源
      1. 服务器端不需要处理,可以直接响应给客户端的资源就是静态资源,例如CSS、JavaScript、images文件
      2. html页面中用到的css、js、img是静态的,因此不需要改变,所以是静态资源
    2. 动态资源
      1. 相同的请求地址不同的响应资源,这种资源就是动态资源。
      2. html就是动态资源文件,因为html内部显示的数据内容需要后台接口返回的数据变化而改变

搭建一个web服务器-客户端向服务器请求读取资源案例

  1. 文件目录:

     static
         app.js
         package-lock.json
         public
             css
             js
             images
             lib
             default.html
             article.html
    
  2. app.js代码

     const http = require('http');
     const url = require('url');
     const path = require('path');
     const fs = require('fs');
     //分析资源类型
     const mime = require('mime');
     //创建服务器
     const app = http.createServer();
     //监听客户端请求
     app.on('request', (req, res) => {
         // 获取用户的请求路径
         let pathname = url.parse(req.url).pathname;
         //用户不输入路径,直接域名,也显示default.html
         pathname = pathname == '/' ? '/default.html' : pathname;
         // 将用户的请求路径转换为实际的服务器硬盘路径
         let realPath = path.join(__dirname, 'public' + pathname); 
         //检查这个路径下资源类型
         let type = mime.getType(realPath)
         // 读取文件
         fs.readFile(realPath, (error, result) => {
             // 如果文件读取失败
             if (error != null) {
                 res.writeHead(404, {
                     'content-type': 'text/html;charset=utf8'
                 })
                 res.end('文件读取失败');
                 return;
             }
             //设置相应内容格式类型
             res.writeHead(200, {
                 'content-type': type
             })
             //将文件资源读取结果返回给客户端
             res.end(result);
         });
     });
     //监听请求端口
     app.listen(3000);
     console.log('服务器启动成功')
    
  3. 分析:

    1. 该案例就是一个实际的网页服务器案例
    2. app.js、package-lock.json就是后端代码,这里是js代码则运行在node环境,如果是java代码则运行在后台环境
    3. public就是前端开发人员写的前端代码
    4. 即前端人员将代码写完后上传发布到后台工程中,客户端调用后台接口get请求,后台服务器代码将前端代码返回给客户端,然后通过浏览器展示。

客户端请求途径

  1. GET方式
    1. 浏览器地址栏
    2. link标签的href属性
    3. script标签的src属性
    4. img标签的src属性
    5. Form表单提交
  2. POST方式
    1. Form表单提交
    2. ajax的Post请求

Node.js异步编程(nodejs高级语法)

  1. 同步API, 异步API
    1. 同步API:只有当前API执行完成后,才能继续执行下一个API
    2. 异步API:当前API的执行不会阻塞后续代码的执行
  2. 同步API, 异步API的区别( 获取返回值 )
    1. 同步API可以从返回值中拿到API执行的结果, 但是异步API是不可以的

       // 同步 
       function sum (n1, n2) { 
           return n1 + n2; 
       } 
       const result = sum (10, 20);
              
       // 异步
       function getMsg () { 
           setTimeout(function () { 
               return { msg: 'Hello Node.js' } 
           }, 2000); 
       } 
       const msg = getMsg ();
      
  3. 回调函数
    1. 自己定义函数让别人去调用。

       // getData函数定义
       function getData (callback) {}
       // getData函数调用
       getData (() => {});
      

Promise

  1. 疑问:如果异步API后面代码的执行依赖当前异步API的执行结果,但实际上后续代码在执行的时候异步API还没有返回 结果,这个问题要怎么解决呢?

     fs.readFile('./demo.txt', (err, result) => {});
     console.log('文件读取结果');
    
  2. 需求:依次读取A文件、B文件、C文件
    1. 解决办法:把读取B文件任务放到A文件回调函数中,把读取C文件放到B文件回调函数中
    2. 缺点:回调函数嵌套太多,代码难以维护
  3. Promise出现的目的是解决Node.js异步编程中回调地域的问题。
    1. Promise将异步api的执行和执行结果进行分离处理
    2. Promise本身是一个构造函数,需要通过构造函数创建实例对象:new Promise((resolve, reject)=>{})
    3. 构造函数传参一个匿名函数,该匿名函数有2个参数。要执行的异步代码放到匿名函数体中
      1. resolve:是一个函数,当异步API执行完成,可以通过这个函数调用将执行结果传出去
      2. reject: 是一个函数,当异步API执行失败,通过这个函数将执行错误传递出去
     let promise = new Promise((resolve, reject) => { 
         setTimeout(() => { 
             if (true) { 
                 resolve({name: '张三'}) 
             }else { 
                 reject('失败了') 
             } 
         }, 2000);
    
     }); 
     //拿到异步API执行的结果,调用resolve()实际执行then参数的函数,同理调用reject()执行catch参数函数
     promise.then(result => console.log(result); // {name: '张三'}) 
     .catch(error => console.log(error); // 失败了)
    
  4. 实现需求:依次读取A文件、B文件、C文件

     const fs = require('fs');
     //回调函数嵌套
     // fs.readFile('./1.txt', 'utf8', (err, result1) => {
     // 	console.log(result1)
     // 	fs.readFile('./2.txt', 'utf8', (err, result2) => {
     // 		console.log(result2)
     // 		fs.readFile('./3.txt', 'utf8', (err, result3) => {
     // 			console.log(result3)
     // 		})
     // 	})
     // });
        
     //Promise实现
     function p1 () {
         return new Promise ((resolve, reject) => {
             fs.readFile('./1.txt', 'utf8', (err, result) => {
                 resolve(result)
             })
         });
     }
        
     function p2 () {
         return new Promise ((resolve, reject) => {
             fs.readFile('./2.txt', 'utf8', (err, result) => {
                 resolve(result)
             })
         });
     }
        
     function p3 () {
         return new Promise ((resolve, reject) => {
             fs.readFile('./3.txt', 'utf8', (err, result) => {
                 resolve(result)
             })
         });
     }
        
     //让3个Promise依次执行
     p1().then((r1)=> {
         console.log(r1);
         return p2();
     })
     .then((r2)=> {
         console.log(r2);
         return p3();
     })
     .then((r3) => {
         console.log(r3)
     })
    
  5. Promise尽管解决了函数嵌套的问题,但是代码仍然很繁琐,那么有没有更好的解决办法呢?

异步函数(es7新增)

  1. 异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得 清晰明了。
    1. 定义异步函数
     const fn = async () => {};
     async function fn () {}
    
  2. async关键字
    1. 普通函数定义前加async关键字 普通函数变成异步函数
    2. 异步函数默认返回promise对象
    3. 在异步函数内部使用return关键字进行结果返回,结果会被包裹的promise对象中,return关键字代替了resolve方法
    4. 在异步函数内部使用throw关键字抛出程序异常
    5. 调用异步函数再链式调用then方法获取异步函数执行结果
    6. 调用异步函数再链式调用catch方法获取异步函数执行的错误信息
    7. 代码举例:

       async function fn () {
           //一旦执行throw,就不会向下执行代码
           throw '发生了一些错误';
           //不再执行
           return 123;
       }
              
       // console.log(fn ()),promise对象
       fn ().then(function (data) {
           //123
           console.log(data);
       }).catch(function (err){ //捕获throw抛出的异常
           console.log(err);
       })
      
  3. await关键字
    1. await关键字只能出现在异步函数中
    2. await promise: await后面只能写promise对象 写其他类型的API是不可以的
    3. await关键字可是暂停异步函数向下执行 直到promise返回结果,再向下执行

       async function p1 () {
           return 'p1';
       }
              
       async function p2 () {
           return 'p2';
       }
              
       async function p3 () {
           return 'p3';
       }
              
       async function run () {
           //等待p1执行完
           let r1 = await p1()
           //等待p2执行完
           let r2 = await p2()
           //等待p3执行完
           let r3 = await p3()
           console.log(r1)
           console.log(r2)
           console.log(r3)
       }
       run();
      
  4. 代码举例:服务器读取文件

     const fs = require('fs');
     // 改造现有异步函数api 让其返回promise对象 从而支持异步函数语法
     const promisify = require('util').promisify;
     // 调用promisify方法改造现有异步API 让其返回promise对象
     const readFile = promisify(fs.readFile);
        
     async function run () {
         let r1 = await readFile('./1.txt', 'utf8')
         let r2 = await readFile('./2.txt', 'utf8')
         let r3 = await readFile('./3.txt', 'utf8')
         console.log(r1)
         console.log(r2)
         console.log(r3)
     }
     run();
    

浏览器和服务器执行JS区别

  1. 如何区分一个js文件是浏览器执行还是服务器执行的呢?
  2. 浏览器执行js特点:
    1. 浏览器先加载html,然后js文件在html文件中引入,运行在浏览器中
    2. js语法:ECMAScript,DOM,BOM
  3. 服务器执行js特点:
    1. 通过node软件(命令)执行,运行在node环境中
    2. js语法: ECMAScript、Node环境APIA
    3. 通过node的系统模块以及第三方模块执行命令
    4. node js文件、nodemon js文件、gulp 命令、webpack,这些命令的本质都是让js代码在node环境下执行。
Table of Contents