Node.js基础

1.Node.js介绍

1.1Node.js是什么

  • node.js是一个JavaScript运行时环境,可以解析和执行JavaScript代码
  • 也就是说现在的JavaScript可以万千脱离于浏览器运行,一切归功于,Node.js
  • Node.js中的JavaScript
    • 没有BOM、DOM
    • 在Node这个JavaScript执行环境中为JavaScript提供了一些服务器级别的操作API
      • 例如文件读写
      • 网络服务的构建
      • 网络通信
      • http服务器
      • 等处理。。。

1.2Node.js能做什么

  • web服务器后台
  • 命令行工具
    • npm
    • git
    • hexo博客
    • 。。。
  • 对于前端来讲,接触node最多的是它的命令行工具
    • 自己写的少,主要使用第三方的
    • webpack
    • gulp
    • npm

1.3学习资源

http://javascript.ruanyifeng.com/

ECMAScript语法

在Node中采用采用EcmaScript进行编码

< https://www.w3cschool.cn/ecmascript/8j1d1q68.html>

2.Node.js环境安装及使用

2.1Hello World例子

1.编写JavaScript脚本文件

2.打开终端,定位到脚本文件所在目录

3.输入node 文件名 执行对应的文件

注意:脚本文件命名不能用node.js命名,最好不要使用中文

  • 解析执行JavaScript
  • 读写文件
  • http

3.Node.js语法

3.1Node.js文件系统

3.1.1如何执行脚本文件

注意:浏览器不认识具有特定Node API(如:fs文件系统模块的代码)的代码,但是浏览器认识ECMAScript语法的的JavaScript语言部分,如 consle.log(),BOM,DOM的代码

浏览器中的JavaScrip没有操作文件的能力

//NOde中的JavaScript具有文件操作的能力

//fs 是file-system的简写,就是文件系统的意思

//在Node中要想进行问及那操作,必须引入fs这个核心模块

//在fs这个核心模块中,就提供了所有文件相关的API

//例如:fs.readFile就是用来读取文件

//1.使用require方法加载fs核心模块

var fs=require('fs')

//2.读取文件

//  第一个参数是要读取的文件路径

//  第二个参数的一个回调函数

//  成功  

//      data 数据

//      error null

//  失败

//      data null

//      error 错误对象

var fs=require('fs')

fs.readFile('./data/hello.txt',function(error,data){

//<Buffer 73 74 75 64 79 20 6e 6f 64 65 6a 73>

//文件中存储的其实都是二进制数据

//为什么这里看到的不是0和1呢?原因是二进制转为十六进制了

//通过 toString转为我们能够认识的字符

//  console.log(data)

​    console.log(data.toString())

})
3.1.2写文件
var fs=require('fs')

//  第一个参数:文件路径

//  第二个参数:文件内容

//  第三个参数:回调函数

//  error形参

//  成功:

//      文件写入成功

//      error是null

//  失败:

//      文件写入失败

//      error就是错误对象

fs.writeFile('./data/你好>.md','大家好,我是Node.js',function(error){

//console.log('文件写入成功')

//console.log(error)

   if(error){

​       console.log('写入失败')

​       console.log(error)

   }else{

​       console.log('写入成功')

​       console.log(error)

   }

})


let fs = require('fs');
fs.writeFile('1.txt','hello',(err)=>{
    console.log('完成');
})
3.1.3 读文件
let fs = require('fs');
fs.readFile('./1.txt','utf8',(err,data) => {
    if(err){
        console.log(err);
    }
    else{
        console.log(data);
        // console.log(data.toString());
    }
});

3.2最简单的http服务

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

//使用Node构建一个web服务器
//在Node中提供了一个核心模块:http
//http这个模块的作用:帮我们创建编写服务器

//1.加载http核心模块
var http=require('http')

//2.使用http.createServer()方法创建一个web服务器
//返回一个 Server实例
var server=http.createServer()

//3.服务器
//  提供服务:对 数据的服务
//  发请求
//  接收请求
//  处理请求
//  给个反馈(发送响应)  
//  注册request请求事件
//  当客户端请求过来,就会自动触发服务器的request请求事件,然后执行第二个参数,回调处理函数

server.on('request',function(){
    console.log('收到客户端请求')
})

//4.绑定端口号,启动服务器
server.listen(3000,function(){
    console.log('服务器启动成功,可以通过http://127.0.0.1:3000/或者localhost来进行访问')
})
3.2.1抓取接口数据
 // 抓取数据

// 导入http模块
let http = require('http');

// 获取别人的数据(自带模块,只能做get,不能做post)
// 给别人提供数据

http.get('http://81.68.77.84/wy_shop/category/searchTreeLv1CategoryName.php',(res)=>{
    // console.log(res);

    let html = '';
    // on 等价于addEventListener
    res.on('data',(content)=>{
        // 当有数据变化时,触发该函数
        html+= content;
    })

    res.on('end',()=>{ // 数据全部接收完毕
        console.log(html)
    })
})
3.2.2抓取页面
// 抓取页面

let http = require('http');

let fs = require('fs');

// 获取别人的数据(自带模块,只能做get,不能做post)
// 给别人提供数据

http.get('http://www.hscoder.top/',(res)=>{
    // console.log(res);

    let html = '';
    // on 等价于addEventListener
    res.on('data',(content)=>{
        // 当有数据变化时,触发该函数
        html+= content;
    })

    res.on('end',()=>{ // 数据全部接收完毕
        // console.log(html)
        fs.writeFile('hs.html',html,(err)=>{
            console.log('完成');
        })
    })
})

3.3Node中的JavaScript

  • Ecmascript

    • 没有DOM、DOM
  • 核心模块

  • 第三方模块

  • 用户自定义模块

3.3.1核心模块

Node为JavaScript提供了很多服务器级别的API,这些API绝大多数都被包装到一个具名的核心模块中。

例如文件操作的fs核心模块,http构建的http模块,path路径操作模块,os系统信息操作模块

如果模块是核心模块,要是用,必须使用require引用加载模块

var fs=require('fs')//require加载模块
var http=req
  • http
    • 端口号定位具体的应用程序
3.3.2Content-Type
  • Content-Type服务器最好把每次响应的数据类型,告诉客户端,正确告诉
  • 不同资源对应的类型是不一样的,具体参考: http://tool.oschina.net/commons
  • 对于文本类型的数据,最好加上乱码,防止中文解析乱码
  • 通过网络发送文件
    • 发送的其实不是文件,本质上是文件的内容
    • 当浏览器接收到服务器响应内容之后,根据Content-Type类型进行解析
3.3.3模块作用域与通信
  • 在Node中没有全局作用域的概念

  • 在Node中,只能通过require方法加载多个JavaScript脚本文件

  • require加载只能是执行其中的代码,文件与文件之间是由于模块作用域,所以不会有污染

    外部无法访问内部

    内部也无法访问外部

    模块作用域的好处:可以加载执行多个文件,可以完全避免变量命名冲突问题

    但是在某些情况下,模块之间需要进行通信

    在每一个模块中,都提供了一个对象:exports

    该对象默认是一个空对象

    把需要被外部访问的成员,手动挂载到exports,这个借口对象上,然后谁来require这个模块,通过对象成员访问其他模块内部,得到其他模块内部的exports接口对象

3.4web服务器开发

3.4.1在Node中使用模板引擎

安装:

npm install art-template --save

使用:

var template=require('art-template')

var ret=template=template.render('hello' {{ name }},{

	name:'honor'

})

console.log(ret) //=> hello honor
//art-template

//art-template不仅可以在浏览器使用,也可以在node使用



//安装

//  npm install art-template --save

//  该命令在哪执行,把包下载到哪里,默认下载到node_modules

//  下载目录不要更改,也不支持改



//  在Node中使用art-template模板引擎

//  模板引擎最早诞生于服务器端,后发=发展到前端

//  1.安装npm install art-template --save

//  2.在需要使用的文件模块中加载art-tempplate

//  require方法加载:require('art-template')

//  参数中的art-template的名字,就是你下载的包的名字

//  即:install的名子是什么,require的名子就是什么

//  3.查文档,使用模板引擎的API





var fs=require('fs')

//node把模板字符串存放到文件里面

// 这里不是浏览器,这样行不通 template('script标签id',{对象})

// var tplStr=`

// <!DOCTYPE html>

//     <html lang='en'>

//         <head>

//             <meta chars  et="UTF-8">

//             <meta name="viewport" content="width=device-width, initial-scale=1.0">

//             <meta http-equiv="X-UA-Compatible" content="ie=edge">

//             <title>Document</title>

//         </head>

//             <body>

//                 <p>大家好,我的名字是:{{ name }}</p>

//                 <p> 我今年{{ age }}岁了</p>

//                 <h1> 我来自{{ province }}<h1>

//                 <p> 我喜欢{{ each hobbies }} {{ $value }} {{ /each }}</p>

//             </body>

//         </html>

// `



fs.readFile('./tpl.html',function(err,data){

​    if(err){

​        return console.log('读取失败')

​    }

//默认读取到的data,默认是二进制数据

//而模板引擎的render方法,需要接收的是字符串

//在这里需要把data二进制数据转为字符串,才可以给模板引擎使用

var ret=template.render(data.toString(),{

​    name: 'honor',

​    age: 18,

​    province: '湖北省',

​    hobbies: [

​        '写代码',

​        '喝水',

​        '上厕所'

​        ],

​        title:'个人信息'

​    })

console.log(ret)

})

反引号之间的字符串,高亮显示:honor

3.4.2jQuery的each和JavaScript原生的forEach

each是art-tempalte的模板语法,只有art-tempalte专属模板语法

{{each 遍历项}}

<li>{{ $value }}</li>

{{/each}}这是art-tempalte模板引擎支持的语法,只能在模板字符串中使用

jquery中的each语法

$.each(数组,function)

$(‘div’).each(function),一般用于jQuery选择器选择到的伪数组实例对象

区别:

jQuery的each

  • jQuery的each由第三方库提供

  • jQuery 2以下的版本是兼容IE8的

  • 它的each方法主要用来遍历jQuery 实例对象(伪数组)

  • 同时,它可以作为低版本浏览器中forEach的替代品

  • jQuery的实例对象,即伪数组不能使用forEach的实例方法

  • 可以通过;[].slice.call($(‘div’)).forEach(function(item){

    ​ //;[].slice.call(‘jQuery实例对象’),slice是截取返回数组,不传参,就从0截到最后一个

    ​ console.log(item)

    ​ })

    slice() 方法可从已有的数组中返回选定的元素。
    语法
    	arrayObject.slice(start,end)
    start 必需参数。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推。
    end	可选参数规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。
    
    ['a','b','c'].slice()//不穿参数,全部截取
    ["a", "b", "c"]
    
    ['a','b','c'].slice(1)//传一个参数,不设置end参数,从start一直截取到尾部
    ["b", "c"]
    
    ['a','b','c'].slice(2)
    ["c"]
    
    //负值从数组的尾部选取元素
    ['a','b','c'].slice(-1)//-1 指最后一个元素,-2 指倒数第二个元素,依次类推
    ["c"]
    
    ['a','b','c'].slice(-2,-1)
    ["b"]
    
    ['a','b','c'].slice(-3,-1)
    ["a", "b"]
    
    Array.prototype.mySlice=function(){
        var start=0
        var end=this.length
        //this就是下面的fakeArr
        if(arguments.length===1){//传一个参数
            start=arguments[0]
        }else if(arguments.length===2){
            start=arguments.[0]
            end=arguments.[1]
    	}
        var temp=[]
        for(var i=start;i<end;i++){
         // fakeArr[0]
         // fakeArr[1]
         // fakeArr[2]
            temp.push(this[i])
    	}
        return temp
    }
    
    var=fakeArr:{//伪数组
    	0:'abc',
        1:'efg',
        2:'haha',
        length:3
    }
    //得到真正的数组
    [].mySlice.call(fakeArr)//这样就将伪数组转成了数组
    

    将伪数组转成数组,就可以使用forEach的实例方法了

JavaScript的forEach

  • foreach是EcmaScript5中提供的
  • 不兼容IE8,及其以下的IE版本

forEach是EcmaScript5中的数组遍历函数,是JavaScript原生支持的遍历语法,可以被遍历任何被遍历的成员

***注意:***jQuery的each方法几乎和ForEach一致

低版本浏览器不支持

3.4.3代码无分号风格
function say() {

​    console.log('hello world')

}



say()



// ;(function (){//匿名函数自执行

//     console.log('hello')

// })()



// ;['苹果','香蕉'].forEach(function (item)){

//     console.log(item)

// }



//  `反引号是EcmaScript中新增的一种字符串的包裹方式,交:模块字符串

//  它支持换行和非常拼接变量

//  反引号里面写什么格式就是什么格式 类似于html里面的pre标签

// var foo=`

// 大家好

// 我是渣渣辉

// `

// console.log(foo)



;`hello`.toString()



//  当使用无分号风格,注意以下情况:

//  当一行代码是以:

//  (

//  [

//  `

//  开头的时候 ,在前面补一个分号 ,避免语法解析错误

//  在第三方的代码中以分号开头

推荐书籍:<<编写可维护的 JavaScript>>

《JavaScript 高级编程》第3版

《JavaScript 语言精粹》

3.4.4服务端与客户端渲染

区别:

电商网站,商品评论列表大多数是客户端渲染,为了用户体验,采用客户端渲染,爬虫抓不到,不利于SEO搜索引擎优化。

商品列表采用服务端渲染,目的是为了SEO搜索引擎优化

客户端最少两次请求,发起ajax在客户端使用模板引擎渲染

客户端拿到的就是服务端已经渲染好的

3.4.5初步实现Apache功能
//1.加载http核心模块

var http=require('http')



var fs=require('fs')



//2.创建Server

var server=http.createServer()



//3.监听Server的request请求事件,设置请求处理函数



var wwwDir='E:\www'//动态转移www目录,其下面的文件,有与之对应的相对路径

//即:www文件夹路径改变,只需要改变wwwDir的值即可



server.on('request',function(req,res){

​    var url=req.url

​    //  请求/,都index.html

​    //  请求/a.txt,都a.txt

​    //  请求哪个,读哪个 即:wwwDir+请求的url

​    var filePath='/index.html'

​    if(url!=='/'){

​        filePath=url//请求的url

​    }

​    fs.readFile(wwwDir+filePath,function(err,data){

​        if(err){

​            return res.end('404 Not Found')

​        }

​        res.end(data)

​    })

})



//4.绑定端口号,启动服务

server.listen(3000,function(){

​    console.log('running......')

})

Apache文件目录列表功能

var http = require('http')

var fs = require('fs')



var server = http.createServer()



var wwwDir = 'E:/www'



server.on('request', function (req, res) {

  var url = req.url

  fs.readFile('./template.html', function (err, data) {

​    if (err) {

​      return res.end('404 Not Found.')

​    }

​    // 1. 如何得到 wwwDir 目录列表中的文件名和目录名

​    //    fs.readdir

​    // 2. 如何将得到的文件名和目录名替换到 template.html 中

​    //    2.1 在 template.html 中需要替换的位置预留一个特殊的标记(就像以前使用模板引擎的标记一样)

​    //    2.2 根据 files 生成需要的 HTML 内容

​    // 只要你做了这两件事儿,那这个问题就解决了

​    fs.readdir(wwwDir, function (err, files) {

​      if (err) {

​        return res.end('Can not find www dir.')

​      }



​      // 2.1 生成需要替换的内容

​      var content = ''

​      files.forEach(function (item) {

​        // 在 EcmaScript 6 的 ` 字符串中,可以使用 ${} 来引用变量

​        content += `

​          <tr>

​            <td data-value="apple/"><a class="icon dir" href="/D:/Movie/www/apple/">${item}/</a></td>

​            <td class="detailsColumn" data-value="0"></td>

​            <td class="detailsColumn" data-value="1509589967">2017/11/2 上午10:32:47</td>

​          </tr>

​        `

​      })



​      // 2.3 替换

​      data = data.toString()

​      data = data.replace('^_^', content)



​      // 3. 发送解析替换过后的响应数据

​      res.end(data)

​    })

  })

})

server.listen(3000, function () {

  console.log('running...')

})
3.4.6Node中的Console控制台

REPL

  • read
  • eval
  • print
  • loop

作用:辅助性API测试

1567672756364

进入:cmd 输入node

退出:按两次ctrl+c

3.4.7设计请求路径

在PHP中,使用$GET直接查询字符串数据

在Node中,通过url.parse()解析

如果没有url.parse(),这个模块,自己解析,例如:

  • /pinglun?name=jack&message=hello
  • split(‘?’)按照不同的方式来分割字符串
  • name=jack&message=hello
  • split(‘&’)
  • name=jack message=hello
  • forEach()
  • name=jack.split(‘=’)
  • 0 key
  • 1 value
3.4.8在Node中服务器重定向
  • header(‘location’)
    • 301 永久重定向 浏览器记住
      • 访问a.com,跳到b.com
      • 下次,浏览器不请求a,直接跳到b.com(缓存)
    • 302 临时重定向 浏览器不记忆
      • 访问a.com,跳到b.com
      • 下次请求a.com,先请求服务器,先到服务器
      • 还会请求a
      • a告诉你往b

3.5Node中的模块系统

使用Node编写应用程序,主要是在使用

  • EcmaScript语言
    • 和浏览器不一样,在Node中没有BOM、DOM,即:没有window,没有document
  • 核心模块(内置)
    • 文件操作的fs
    • http服务的http
    • url路径处理模块
    • path路径处理模块
    • os操作系统信息
  • 第三方模块(npm下载使用)
    • art-tempalte
  • 自己写的模块(加载使用)
    • 自己创建的文件
3.5.1 什么是模块化
  • 具有文件作用域
  • 具有通信规则
    • 加载 require
    • 导出
3.5.2CommomJS模块规范

在Node中的JavaScript有一个很重要的概念:模块系统

  • 模块作用域
  • 使用require方法加载模块
  • 使用exports接口对象来导出模块中的成员
3.5.2.1加载require

语法:

var 自定义变量名称=require('模块')

两个作用:

  • 执行被加载模块中的代码
    • 得到被加载模块中exports导出的接口对象
3.5.2.2导出exports
  • Node中是模块作用域,默认文件中所有成员只在当前文件模块有效

  • 对于希望被其他模块访问的成员,就需要把这些公开的成员都挂载到exports接口对象中就可以了

    导出多个成员(必须在对象中)

    exports.a=123//挨个导出
    exports.b='hello'
    exports.c=function(){
    	console.log('ccc')
    }
    exports.d={
        foo:'bar'
    }
    

导出单个成员(拿到的就是,函数、字符串)

module.exports='hello'

以下情况会覆盖:

module.exports='hello'

module.exports=function(){
    return x+y
}
//后面覆盖前面,最终导出function,以后者伪准

也可以通过这样来导出多个成员:

module.exports={//一次性导出
    add:function(){
        return x-y
    },
    str:'hello'
}

3.5.2.3原理解析

exports是modele exports的一个引用

console.log(exports===module.exports)//结果为:true

exports.foo='bar'

//等价于
module.exports.foo='bar'
3.5.2.4exports和module.exports的区别
  • 每个模块中都有一个module对象
  • module对象中有一个exports对象
  • 可以把需要导出的成员挂载到module.exports接口对象中
  • 即:modelue.exports.xxx=xxx的方式,过于麻烦
  • 为了方便,Node在每一个模块中提供了一个成员叫:exports
  • exports===module.exports结果为true
  • 所以对于:module.exports.xxx=xxx的方式完全可以:exports.xxx
  • 当一个模块需要导出单个成员的时候(非对象,例如:当导出字符串、数组、函数的时候),这个时候必须使module.exports.xxx的方式
  • 不要使用exports=xxx不管用,但是exports.xxx=xxx可以正常使用
  • 因为每个模块最终向外·return的是module.exports
  • exports只是module.exports的一个引用
  • 即便,你为exports=xx重新赋值,不会影响module.exports
  • 但是有一种赋值方式比较特殊:exports=module.exports,用来重新建立引用关系
module.exports={  //已经指向新的对象
    a:123
}

exports=module.exports//重新建立exports和module.exports引用关系,即可生效

exports.foo='bar'//还是原来的对象
3.5.2.5require方法加载规则

注意:更多底层细节 参考:朴灵《深入浅出Node.js》中的模块系统章节

深入浅出 Node.js(三):深入 Node.js 的模块机制

https://www.infoq.cn/article/nodejs-module-mechanism/

https://www.bilibili.com/video/av27670326/?p=44

  • 核心模块

    • 模块名
  • 第三方模块

    • 模块名
  • 用户自己写的

    • 路径
  • 优先从缓存加载

a.js

    console.log('a.js被加载')
    var fn=require('./b')
    console.log(fn)//输出b.js里面的function

b.js

    console.log('b.js被加载')
    module.exports=function(){
        console.log('hello b')
    }

main.js

    require('./a')
    var fn=require('./b')//这句话不会重复加载b,只是为了得到里面的接口对象
    console.log(fn)//由于已经在a中加载过b了,
    //可以将缓存中b.js的 接口对象function拿过来用,但是不会重复执行里面的代码,,避免重复加载,提高模块加载效率


最终输出:a.js被加载
        b.js被加载
        [Function]
        [Function]

blog
	a
    	node-module
			art-template
		foo.js
	b
    //一旦b中找不到,就会返回上一级目录查找,就不会去同级的兄弟目录a去找
    	../a.foo.js //但是可以通过路径加载兄弟目录的的文件
		a中的第三方包不能通过require加载
        除非require('../a/node_modules/art-template/index.js')
  • 判断模块标识
    • 核心模块
    • 第三方模块
    • 自己写的模块
//如果是非路径形式的模块标识,即第三方模块和用户自定义模块
//  路径形式的模块
//  ./ 当前目录
//  ../ 上一级目录
//  /xxx     首位的/ 表示当前文件模块所属磁盘跟路径
//  F:\Node.js\node.js\03\code\04-require标识符分析 这样的绝对路径也不用
//  /xxx的路径方式几乎不用
//  .js后缀名可以省略
// require('./foo.js')

//核心模块的本质也是文件
//核心模块的文件已经被内置编译到二进制文件中了,只需要根据名字加载即可
//require('fs')
//require('http')

//第三方模块
//凡是第三方模块都必须通过npm下载
//使用的时候 通过require('包名')的方式进行加载使用
//不可能有第一个第三方包和核心模块的名字一样
//既不是核心模块,也不是路径形式的模块,即:第三方模块
//  先找到当前文件所处目录中的node_modules目录
//  node_modules/art-template
//  node_modules/art-template/package.json文件
//  node_modules/art-template/package.json文件中的main属性
//  main属性就记录了template的入口模块
//  然后加载使用第三方包
//  实际上加载的还是文件

//如果package.json文件不存在或者main指定的入口模块也是没有的
//Cannot find module 'a'
///则node会自动找该目录下的index.js
//也就是说当main.js没有或指定是错的、或者没有package.json的时候,index.js会作为一个默认备选项,如果备选项index.js都没有,报错://  Cannot find module 'xxx'
//如果以上任何一个条件都不成立(即:当前同级目录下,没有node_modules文件夹),则会进入上一级目录中的node_modules目录查找,规则同上面一样
//如果上一级还没有,继续向上上级查找
//如果直到磁盘根目录还找不到,最后报错:
//  Cannot find module 'xxx'

// var template=require('art-template')

/*注意:在一个项目中,有且仅有一个node_modules,放在项目根目录
这样子目录的代码,都可以加载第三方包
模块查找机制
    优先从缓存加载
    路径形式的文件模块(用户自己写的)
    第三方模块
        node_modules/art-template
        node_modules/art-template/package.json
        node_modules/art-template/package.json里面的main
        如果这两个不成立,找index.js备选项。
        如果index.js备选项也不成立,进入上一级目录找node_modules
        如果上一级目录,没有node_modules,再去上一级,按这个规则,依次往上,直到磁盘根目录,
        还找不到,报错: Cannot find module 'xxx'
        在一个项目中,有且仅有一个node_modules,放在项目根目录
*/

require('a')//这句 就是当做第三方模块
/*
    在node_module里面的这些包,是通过包名加载的,而不是文件目录

*/
//当前文件所处目录中的node_modules目录
//  a目录
//  a目录/package.json
//  a目录/package.json  main属性
//  真正加载的是main属性里面的foo.js

3.5.3 npm
  • node package manager

    –save 会保存dependencies依赖项

3.5.3.1 npm网站
nmpjs.com
3.5.3.2 npm 命令行工具

查看npm 版本

npm --version

升级npm(自己升级自己)

npm install --global npm
3.5.3.3 常用命令
  • npm init 生成package.json文件
    • npm init -y 跳过向导,快速生成
  • npm install
    • 一次性把dependencies选项中的依赖项全部安装
    • npm i
  • npm install 包名
    • 只下载
    • npm i 包名
  • npm install --save 包名
    • 下载并且保存依赖项到package.json文件的dependencies
    • npm i -S 包名
  • npm uninstall 包名
    • 只删除,依赖项依然存在
    • npm un 包名
  • npm uninstall
    • 删除的同时,去除依赖信息
    • npm un -S 包名
  • npm help
    • 查看使用帮助
  • npm 命令 --help
    • 查看指定命令的使用帮助
    • 例如忘记uninstall命令简写,使用npm uninstall --help,查看使用帮助
3.5.3.4 npm被墙问题

npm存储包的服务器在国外,有时候速度很慢

淘宝npm镜像:https://npm.taobao.org/

安装淘宝的cnpm

#在任意目录执行
# --global 表示安装到全局
# --gloabl不能省略
npm install --global cnpm
#走国外的服务器
npm install jquery 

#使用cnpm通过淘宝的服务器下载
cnpm install jquery 

如果不想安装cnpm,又想使用淘宝服务器下载

npm install jquery --registry=https://registry.npm.taobao.org

每次收到加http参数很麻烦,把这个选项加到配置文件中

npm config set registry=https://registry.npm.taobao.org

查看npm配置信息
npm config list

经过上面的命令,以后所有的npm install都会通过淘宝的服务器下载

3.5.4 package.json

每个项目都与要有package.json文件(包描述文件)

可以通过npm init 创建package.json初始化

PS F:\Node.js\node.js\03\code\npm-demo> npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (npm-demo)
version: (1.0.0) 0.0.1
description: 这是一个npm测试
entry point: (index.js) main.js
test command:
git repository:
keywords:
author: honor
license: (ISC)
About to write to F:\Node.js\node.js\03\code\npm-demo\package.json:

{
  "name": "npm-demo",
  "version": "0.0.1",
  "description": "这是一个npm测试",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "honor",
  "license": "ISC"
}


Is this OK? (yes) yes
PS F:\Node.js\node.js\03\code\npm-demo> npm install --save jquery
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN npm-demo@0.0.1 No repository field.

+ jquery@3.4.1
added 1 package from 1 contributor in 8.139s
PS F:\Node.js\node.js\03\code\npm-demo>  

dependencies选项,可以帮我们保存第三方包的依赖信息

如果node_modules删除了,只需要:npm install就会自动把package.json里面的dependencies依赖项重新下下来

  • 每个项目都有一个package.json文件
  • 执行npm install包名的时候,最好加上–save,用来保存依赖项信息
3.5.4.1 package.json和package-lock.json

npm 5以前不会有package-lock.json这个文件的

npm 5以后加入了这个文件

当安装包的时候,npm都会生成或更新package-lock.json这个文件的

  • npm 5以后的版本不需要加--save,自动保存依赖信息
  • 当安装包的时候,会自动创建或者更新package-lock.json文件
  • package-lock.json保存node_modules所有包的下载地址,包名,版本号
    • 这样的话,当重新npm install 的时候会加快下载速度
  • lock称之为锁
    • 这个lock用来锁定包的版本(即使在package.json中手动修改包的版本号,一旦npm install会根据package-lock.json中锁定的版本号安装包,而不是自动更新到最新版本,同时指定安装到x.x.x版本是无效的,只根据上次安装后,package-lock.json中锁定的包的版本进行安装)
    • 锁定版本号,避免包自动升级到新版,出现问题

3.6 Express

原生的http在某些方面不足以应对开发需求,框架的目的就是提高效率,代码高度统一

在Node中,有很多web框架,以express为主

  • http://expressjs.com/
3.6.1 安装
npm install --save express
3.6.2 hello world
const express=require('express')
const app=express()

app.get('/',(req,res)=>res.send('Hello World!'))
app.listen(3000,()=>console.log('Example app listening on port 3000'))
3.6.3基本路由

路由器

  • 请求方法
  • 请求路径
  • 请求处理函数

get:

//当以GET方法请求 / 的时候,执行对应的请求处理函数
app.get('/',function(req,res){
    res.send('get')
})

post:

//当以POST方法请求 /login 的时候,执行对应的请求处理函数
app.post('/login',function(req,res){
    res.send('post')
})

3.6.4 静态服务
//当以 /public开头的时候,去 ./public目录中查找对应的资源
//这种方式,便于辨识
app.use('/public',express.static('./public/'))
//访问输入:120.0.0.1:3000/public/login.html

//必须是 /a/public木中的资源具体路径
//即:/a/ 是 /public/的别名,/a/ 表示/public/
//访问即输入:http://127.0.0.1:3000/a/login.html
// app.use('/a/',express.static('./public/'))

//当省略第一个参数的时候,可以通过省略/public的方式访问,即:直接:120.0.0.1:3000/login.html
// app.use(express.static('./public/'))
//1.安装
//2.引入第三方包
var express =require('express')

//3.创建服务器应用程序
//  也就是原来的http.createServer
var app =express()

//在Express开放资源,就一个API的事
//公开指定目录
//这样就可以直接通过 /public/xx 的方式,访问目录中所有的资源了
app.use('/public/',express.static('./public/'))
app.use('/static/',express.static('./static/'))
app.use('/node_modules/',express.static('./node_modules/'))

//模板引擎,在Express也是一个API的事

//当服务器收到get请求 / 的时候,执行回调处理函数
app.get('/',function(req,res){
    res.send('hello express!')
})

app.get('/about',function(req,res){
    //在Express中,可以使用req.query来获取查询字符串
    console.log(req.query)
    res.send('about')
})

app.get('/pinglun',function(req,res){
    //req.query
    //在Express中使用模板引擎,有更好的方式,res.render('文件名',{模板对象})
    //art-template结合Express使用
})

//相当于server.listen
app.listen(3000,function(){
    console.log('app is running at port 3000')
})
3.6.5 中间件

官网文档:http://www.expressjs.com.cn/guide/using-middleware.html

http://www.expressjs.com.cn/resources/middleware.html

“自来水厂”的图片搜索结果

中间件的本质就是一个请求处理方法,把用户从请求到响应的整个过程分发到多个中间件中去处理,这样做的目的是提高代码的灵活性,动态可扩展

  • 同一个请求,所经过的中间件都是同一个请求对象和响应对象
3.6.5.1 应用程序级别的中间件

万能匹配(不关心任何请求和路径方法)

app.use(function (req,res,next) {// 万能匹配
	console.log('Time',Date.now())
     next()
 })

只关心以/xxx/开头的:

app.use('/a',function (req,res,next) {// 万能匹配
	console.log('Time',Date.now())
     next()
 })
3.6.5.2 路由级别中间件

get:

app.get('/',function (req,res){
res.send('get!')
})

post:

app.post('/',function (req,res){
res.send('post!')
})

put:

app.put('/user',function (req,res){
res.send('put!')
})

delete:

app.delete('/user',function (req,res){
res.send('delete!')
})
3.6.5.3 错误处理中间件
app.use(function (err,req,res,next){
	console.error(err,stack)
	res.status(500).send('Something broke!')
})
3.6.5.4 内置中间件
  • express.static serves static assets such as HTML files, images, and so on.

    开放静态资源

  • express.json parses incoming requests with JSON payloads. NOTE: Available with Express 4.16.0+

    使用JSON有效负载解析传入的请求

  • express.urlencoded parses incoming requests with URL-encoded payloads. NOTE: Available with Express 4.16.0+

    使用URL编码的有效负载解析传入的请求

    3.6.5.5 第三方中间件

    官放文档:http://www.expressjs.com.cn/guide/using-middleware.html

  • body-parser

  • compression

  • cookie-parser

  • morgan

  • response-time

  • serve-static

  • session

3.7 在Express中配置使用art-template模板引擎

安装:

npm install --save art-template
npm install --save express-art-template

配置:

app.engine('art', require('express-art-template'));
//第一个参数表示,当渲染以.art结尾的文件的时候,使用art-template模板引擎
//可以将art改成html:
app.engine('html', require('express-art-template'));
//这样就不必将views目录下的文件,改成以.art结尾

使用:

app.get('/',function(req,res){
// express默认会去项目的views目录下找index.html
    res.render('404.html',{
        title: 'hello world'
    })
})

如果要修改默认的views视图渲染目录,可以

//注意第一个参数views不能写错
app.set('views',目录路径)
//修改后,再次render的时候,就基于这个新的目录路径来找
3.7.1 art-template模板引擎高级语法
  • include:导入header,footer等,形成模板的header,footer

    • {{include 'xxx/xxx/header.html'}}
      
      {{include 'xxx/xxx/footer.html'}}
      
  • block:在模板中留坑,方便在其他页面,这个坑里,写不同的内容

    • <!--给标题title留坑-->
      {{block 'title'}}{{/block}}
      
      <!--给自己的body留坑-->
      {{block 'body'}}{{/blok}}
      
  • extend:在其他页面,使用模板页

    // 在其他页面,使用模板页
    {{extend 'xxx/xxx/模板页.html'}}
    
    // 填坑,在这填,这个页面想要的title部分
    {{block 'title'}}{{'标题'}}{{/block}}
    
    //填坑,在这,填这个页面的body主体
    {{block 'body'}}{{'标题'}}{{/block}}
    

3.8 在Express中获取表单GET请求参数

Express内置了一个API,可以通过req.query来获取

req.query

3.9 在Express中获取表单POST请求体数据

在Express中没有内置获取表单post请求的API,需要使用第三方包body-parser

官网:http://www.expressjs.com.cn/en/resources/middleware/body-parser.html

使用说明:

var express = require('express')
// 0.引包
var bodyParser = require('body-parser')

var app = express()

// 配置body-parser
//加入这个配置,在req请求对象上会多出来一个属性:body
//即:可以直接通过req.body来获取表单post请求体数据
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))

// parse application/json
app.use(bodyParser.json())

app.use(function (req, res) {
  res.setHeader('Content-Type', 'text/plain')
  res.write('you posted:\n')
//通过req.body来获取表单post请求体数据
  res.end(JSON.stringify(req.body, null, 2))
})

安装:

npm install --save body-parser

配置body-parser中间件(插件,专门解析表单post请求体),一定要在挂载路由之前配置:

app.use(bodyParser.urlencoded({ extended: false }))

// parse application/json
app.use(bodyParser.json())

引包:

var bodyParser = require('body-parser')

示例:

app.use(function (req, res) {
  res.setHeader('Content-Type', 'text/plain')
  res.write('you posted:\n')
//通过req.body来获取表单post请求体数据
  res.end(JSON.stringify(req.body, null, 2))
})

提示:默认Session数据内存存储的,服务器一旦重启,就会丢失,真正的生产环境,会把session进行持久化存储。

3.10在Express配置使用express-session插件

参考文档:https://www.npmjs.com/package/express-session

安装:

npm install express-session

引包:

var session = require('express-session')

配置(一定要在挂载路由之前):

// 该插件会为req请求对象添加一个成员:req.session默认是一个对象
app.use(session({
// 配置加密字符串,在原有基础上拼接起来,一起加密
// 增强安全性,防止客户端恶意伪造
  secret: 'keyboard cat',
  resave: false,
// 无论是否使用session,都默认分配一把钥匙
  saveUninitialized: true
}))

使用:

// 添加session数据
req.session.foo = 'bar'

// 获取session数据
req.session.foo

3.11文件操作路径和模块路径

文件操作路径:

//在文件操作的相对路径中
//  ./data/a.txt 相对于当前目录
//  data/a.txt   相对于当前目录
//  /data/a.txt  绝对立即,当前文件模块所处的磁盘根目录
//  F:xx/xx/...  绝对路径
// fs.readFile('./data/a.txt',function(err,data){
//     if(err){
//         console.log(err)
//         return console.log('读取失败')
//     }
//     console.log(data.toString())
// })

模块操作路径:

//这里忽略了 . ,也是磁盘根目录,会报错
require('/data/foo.js')//省略.,则会去找data文件夹的磁盘根目录即:F:data\foo.js

//相对路径
require('./data/foo.js')

//模块加载的路径中的相对路径不能省略.

3.12 path路径操作模块

参考文档:http://nodejs.cn/api/path.html

  • path.basename(path[, ext])
    • 获取一个给定路径中的文件名部分
    • path是路径
    • ext是可选参数,用来指定要去除的后缀名
path.extname('index.html');
// 返回: '.html'
// 从最后一次出现 .(句点)字符到 path 最后一部分的字符串结束

//如果在 path 的最后一部分中没有 . ,或者如果 path 的基本名称,除了第一个字符以外没有 .,则返回空字符串。
path.extname('.index');
// 返回: ''
  • path.dirname(path)
    • 获取一个路径中的目录部分
path.dirname('/foo/bar/baz/asdf/quux');
// 返回: '/foo/bar/baz/asdf'
  • path.extname
    • 获取一个路径中的扩展名部分
  • path.parse
    • 把一个路径,转为对象
      • 对象中包含root根路径
      • dir目录
      • base包含后缀名的文件名
      • ext后缀名
      • name不包含后缀名的文件名
  • path.join
    • 当出现路径拼接的时候用这个方法
  • path.isAbsolute判断一个路径是否是绝对路径

3.13 Node中的其他成员

在每个模块中,除了requireexports等模块相关API之后,还有两个特殊成员:

  • _dirname用来动态获取当前文件模块所属目录的绝对路径
  • _filename用来动态获取当前文件的绝对路径
  • _dirname_filename是不受执行ndoe命令所属路径影响的

在文件操作中,使用相对路径是不可靠的,因为在Node中,文件操作的路径被设计为,相对于执行node命令所处的路径,受执行node命令所处的路径影响。(折样设计,是有使用场景的)

所以,为了解决这个问题,只需要把相对路径变为绝对路径

可以使用_dirname_filename动态获取绝对路径

在拼接路径的过程中,推荐多使用path.json(_dirname,'文件名')来辅助拼接

**注意 :**模块中的路径标识和这里的路径没关系,不受影响(就是相对于文件模块)

3.14 nodemon工具重启服务

第三方命令行工具,nodemon帮助我们解决频繁修改代码,重启服务器问题

nodemon是一个基于Node.js开发的一个第三方命令行工具,独立安装

#在任意目录执行都可以
# --global安装的包,可以在任目录执行
npm install --global nodemon

安装完毕,使用:

node app.js

#现在使用 nodemon
nodemon app.js

只要是通过nodemon

启动的服务,它会监视文件的变化,自动帮你重启服务

4.CRUD 案例

4.1 模块化思想

模块如何划分:

  • 模块职责要单
  • Vue
  • angular
  • React

4.2 起步

  • 初始化

    npm init -y
    
  • 安装依赖

  • 模板处理

4.3 路由设计

请求方法请求路径get参数post参数备注
GET/students渲染首页
GET/students/new渲染添加学生页面
POST/students/newname、age、gender、hobbies处理添加学生请求
GET/students/editid渲染编辑页面
POST/students/editid、name、age、gender、hobies处理编辑页面
GET/students/deleteid处理删除请求

4.4 提取路由模块

router.js

/*
    router.js路由模块
    职责:
        处理路由
        根据不同的方法+请求路径,设置具体的请求函数
    模块职责要单一

*/
var fs=require('fs')

// Express提供了一种更好的方式
//专门用来包装路由
var express=require('express')

// 1.创建一个路由容器
var router=express.Router()

// 2.把路由都挂载到router路由容器中
    router.get('/students',function(req,res){
                // readFile的第二个参数是可选的,传入utf8就是告诉它,把读取的文件直接按照uf8编码转成我们认识的字符
                //  除了这样来转换,也可以通过data.toString()的方式
                    fs.readFile('./db.json','utf8',function(err,data){
                        if(err){
                            return res.status(500).send('Server error')
                        }
                        // console.log(typeof data)//  String类型
                        // console.log(data)
                
                
                        //从文件读取到的是字符串
                        //这里手动转成对象
                        var students=JSON.parse(data).students
                        res.render('index.html',{
                            fruits:[
                                '苹果',
                                '香蕉',
                                '橘子'
                            ],
                            // students:JSON.parse(data).students
                            students:students
                        })
                    })
            })

    router.get('/students/new',function(req,res){
        res.render('new.html')
    })

    router.post('/students/new',function(req,res){
    // 1.获取表单数据
    // 2.处理
    // 由于文件中内容是字符串,只能 先读出来->转成对象->往对象中push追加添加的数据->转成字符串->存进去
    // 将数据保存到db.json中,用以持久化
    // 3.发送响应
    console.log(req.body)
    })

    router.get('/students/edit',function(req,res){
        res.send('get students edit')
    })

    router.post('/students/new',function(req,res){
        res.send('post students edit')
    })

    router.get('/students/delete',function(req,res){
        res.send('get students delete')
    })

// 3.把router导出(用于在下面app.js中的挂载)
    module.exports=router

app.js(入口)

var router=require('./router')

//挂载路由
app.use(router)

4.5 设计操作数据的API文件模块

/*
student.js
数操作文件模块
职责:操作文件中的数据,只处理数据,不关心业务(怎么获取数据,发送响应)
*/ 

/* 
获取所有学生列表
return []
*/
exports.find=function(){

}


/*
添加保存学生
 */

exports.save=function(){

}

/*
更新学生
 */

exports.update=function(){

}

/*
删除学生
 */

exports.delete=function(){

}

1568271330369

4.6 编写步骤

  • 处理bootstrap模板(样式资源)
  • 配置开放静态资源
  • 配置模板引擎
  • 简单路由:/students 渲染静态页出来
  • 路由设计
  • 提取路由模块
  • 一系列业务操作需要处理文件数据,封装students.js
  • 写好students.js方法
    • 查询所有学生列表的API find
    • findById
    • save
    • updateById
    • deleteById
  • 实现具体功能
    • 通过路由收到请求
    • 接收请求中的数据(get,post)
      • req.query(get)
      • req.body(post 需要安装配置body-parser第三方包)
    • 调用数据操作API处理数据
    • 根据操作结果给客户端发送响应
  • 业务功能顺序
    • 列表
    • 添加
    • 编辑
    • 删除
  • ES6新特性
    • find
    • findindex5

5.异步编程

5.1 回调函数

不成立的情况:

function add(x,y){
    console.log(1)
    setTimeout(function () {
        console.log(2)//1秒之后打印2
        var result =x+y
        return result
    },1000)
    console.log(3)//执行结束,不会等待前面的定时器,result直接返回默认值undefined
}

console.log(add(10,20)) //->undefined
// 输出顺序:1 3 undefined 2

不成立的情况:

function add(x,y){
    var result
    console.log(1)
    setTimeout(function () {
        console.log(2)//1秒之后打印2
        result =x+y
    },1000)
    console.log(3)//执行结束,不会等待前面的定时器,result直接返回默认值undefined
    return result
}

console.log(add(10,20))
// 输出顺序:1 3 undefined 2

回调函数:

function add(x,y,callback){
    // callback就是回调函数
    // var x = 10
    // var y = 20
    // var calllback = function (a) { consolelog(result) }
    console.log(1)
    setTimeout(function () {// 异步操作
        var result =x + y 
        callback(result)// 实参
    },1000)
}

// 回调函数
add(10,20,function (a) {// a是形参
// 现在拿到这个结果,可以做任何操作
// a才是我们得到的结果
    console.log(result)
})

//注意:凡是需要得到一个函数内部异步操作的结果
//  setTimeout
//  readFile
//  writeFile
//  ajax
//  这种情况,必须通过:回调函数

基于原生 XMLHTTPRequest封装get方法:

注意:chrome浏览器实现全浏览器跨域ajax请求

在较高版本的chrome浏览器,有同源访问策略控制,运行代码会报错:

Access to XMLHttpRequest at ‘file:///F:/Node.js/node.js/05/code/data.json’ from origin ‘null’ has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.

解决办法

在chrome快捷方式上打开属性栏,在‘目标’栏加上后缀--disable-web-security --user-data-dir。即可实现在此浏览器上所有网页的跨域请求
<!DOCTYPE html>
<html lang='en'>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>封装ajax方法</title>
</head>
    <body>
        <script>
            // 这些是异步的:
            // setTimeout
            // readFile
            // writeFile
            // readdir
            // ajax 往往异步API 都伴随着有一个回调函数
            // var result=fn()
            // $.get('abc',function () {})
            
            // 在较新版本的chrome浏览器,跨域ajax请求报错
            /*
            Access to XMLHttpRequest at 'file:///F:/Node.js/node.js/05/code/data.json' 
            from origin 'null' has been blocked by CORS policy:
            Cross origin requests are only supported for protocol schemes:
            http, data, chrome, chrome-extension, https.
            */
           // 解决办法:
           // 在chrome快捷方式上打开属性,在‘目标’栏加上后缀--disable-web-security --user-data-dir。
           // 即可实现在此浏览器上所有网页的跨域请求
            
        function get(url,callback){
            var oReq = new XMLHttpRequest();
            //oReq.addEventListener("load", reqListener);

            // 当请求加载成功之后,要调用指定的函数
            oReq.onload=function () {
                // 现在需要得到这里的 oReq.responseText
                // console.log(oReq.responseText)
                callback(oReq.responseText)
            }
            oReq.open("GET", url);
            oReq.send();
        }
            get('data.json',function (data) {
                console.log(data)
            })
        </script>
    </body>

</html>

5.2 Promise

参考文档:http://es6.ruanyifeng.com/

callback hell 回调地狱

1568774611975

无法保证读取文件的顺序的代码:

// 异步编程,读取文件的顺序,取决于操作系统调度机制
var fs = require('fs')

fs.readFile('./data/a.txt','utf8',function(err,data){
    if(err) {
        //return console.log('读取失败')
        throw err
        // 抛出异常
        //  1.阻止程序执行
        //  2.把错误消息打印到控制台
    }
    console.log(data)
})

fs.readFile('./data/b.txt','utf8',function(err,data){
    if(err) {
        //return console.log('读取失败')
        throw err
        // 抛出异常
        //  1.阻止程序执行
        //  2.把错误消息打印到控制台
    }
    console.log(data)
})

fs.readFile('./data/c.txt','utf8',function(err,data){
    if(err) {
        //return console.log('读取失败')
        throw err
        // 抛出异常
        //  1.阻止程序执行
        //  2.把错误消息打印到控制台
    }
    console.log(data)
})

通过回调嵌套的方式保证顺序:

// 异步编程,读取文件的顺序,取决于操作系统调度机制
var fs = require('fs')

fs.readFile('./data/a.txt','utf8',function(err,data){
    if(err) {
        //return console.log('读取失败')
        throw err
        // 抛出异常
        //  1.阻止程序执行
        //  2.把错误消息打印到控制台
    }
    console.log(data)
    fs.readFile('./data/b.txt','utf8',function(err,data){
        if(err) {
            //return console.log('读取失败')
            throw err
            // 抛出异常
            //  1.阻止程序执行
            //  2.把错误消息打印到控制台
        }
        console.log(data)
        fs.readFile('./data/c.txt','utf8',function(err,data){
            if(err) {
                //return console.log('读取失败')
                throw err
                // 抛出异常
                //  1.阻止程序执行
                //  2.把错误消息打印到控制台
            }
            console.log(data)
        })
    })
})




为了解决以上方式带来的问题(回调地狱嵌套),所以在EcmaScript 6中新增了一个API,Promise

1568778019100

Promise 基本语法

var fs = require('fs')

// 在ES6中新增了一个API Promise
// Promise是一个构造函数

// console.log(1)

// 创建Promise容器
// 当Promise一旦创建,就开始执行里面的代码
// Promise只是一个容器,本身不是异步,里面的任务不是异步
var p1 = new Promise(function (resolve,reject) {
    // console.log(2)
    fs.readFile('./data/a.txt','utf8',function (err,data) {// 异步任务
        if (err) {
            // 失败了,承诺容器中的任务失败
            // 把容器的Pending状态变为Rejected

            // console.log(err)

            // 调用reject就相当于调用then方法的第二个参数函数
            reject(err)
        } else {
            // console.log(3)
            // 承诺容器中的任务成功了
            // console.log(data)
            // 把容器的Pending状态改为成功Resolved
            // 这里调用的resolve方法就是then方法传递的那个function
            resolve(data)
        }
    })
})

// console.log(4)
// 输出结果 1 2 4 3 hello aaa

// p1就是那个承诺
// 当p1成功了,然后(then)做指定的操作
// then方法接收的function就是容器中的resolve函数,resolve传什么,下面就得到什么

p1
    .then(function (data) {
        console.log(data)
    },function (err) {
        console.log('读取文件失败',err)
    })

封装 Promise API(Promise版本的readFile

var fs = require('fs')

// 封装
function pReadFile (filePath) {
    return new Promise(function (resolve,reject) {
        fs.readFile(filePath,'utf8',function (err,data) {// 异步任务
            if (err) {
                reject(err)
            } else {
                resolve(data)
            }
        })
    })
}

pReadFile('./data/a.txt')
    .then(function (data) {
        console.log(data)
        return pReadFile('./data/a.txt')
    })

    .then(function (data) {
        console.log(data)
        return pReadFile('./data/b.txt')
    })

    .then(function (data) {
        console.log(data)
        return pReadFile('./data/c.txt')
    })

5.3 json-server

JSON-Server 是一个 Node 模块,运行 Express 服务器,可以指定一个 json 文件作为 api 的数据源。

安装

npm install -g json-server

查看是否安=安装成功,显示版本号

json-server --version

启动

json-server可以直接把一个json文件托管成一个具备全RESTful风格的API,并支持跨域、jsonp、路由订制、数据快照保存等功能的 web 服务器。

在code目录打开命令行工具,输入:

json-server --watch data.json

5.4 http-server

http-server是基于node.js的一个简单、零配置的命令行web服务器,可以方便实现跨域资源请求。

安装:

npm install http-server -g

使用:

在code目录打开命令行工具,输入:

http-server
简写:hs

6. MongoDB

资料:https://www.runoob.com/mongodb/mongodb-tutorial.html

官方文档: https://docs.mongodb.com/manual/

6.1 MongoDB数据库的基本概念

  • 可以有多个数据库
  • 一个数据库中可以有多个集合(表)
  • 一个集合中可以有多个文档(表记录)
  • 文档结构很灵活,没有任何限制
  • MongoDB非常灵活,不需要像MySQL一样,先创建数据库、表、设计表结构
    • 在里只需要,当你需要插入数据的时候,只需要指定往哪个数据库的哪个集合插入哪条数据
    • MongoDB自动完成建库建表
{
    qq:{
      users:[//集合
          //每一条记录都是对象
        {name:"张三",age:18},
        {name:"李四",age:19},
        {name:"王五",age:20},
        {name:"赵六",age:21},
        {name:"阮七",age:22}
      ],
      products:[
          
      ],
      ...
    },
    taobao:{
        
    },
    baiud:{
        
    }
}

6.1 安装与使用

下载

https://www.mongodb.com/download-center/community

安装:

https://jingyan.baidu.com/article/09ea3ede5aff37c0aede3919.html
https://www.jb51.net/article/145489.htm

配置环境变量:

将MongDB目录下的bin目录,配置为用户变量,在用户变量的Path中,将bin目录的路径,复制到Path中,新建环境变量

检验是否安装成功

mongod --version

1568639376805

6.2 关系型数据库和非关系型数据库

表就是关系

或者说,表和表之间存在关系

  • 所有的关系型数据库都需要通过sql语言来操作
  • 所有的关系型数据库在操作之前,都需要设计表结构
  • 而且数据表还支持约束
    • 唯一的
    • 主键
    • 默认值
    • 非空
  • 非关系型数据库非常灵活
  • 有的非关系型数据库就是key-value键值对
  • MongoDB是长得最像关系型数据库的非关系型数据库
    • 数据库(关系型数据库中)—》数据库(非关系型数据库中)
    • 数据表(关系型数据库中)—》集合,简单理解就是数组(非关系型数据库中)
    • 表记录(关系型数据库中)—》文档对象(非关系型数据库中)
  • MongoDB不需要设计表结构
  • 也就是说,可以任意的往里面存数据,没有结构性这么一说

6.3 启动和关闭数据库

先启动服务

net  start  MongoDB 

运行:

# mongodb默认使用执行mongod命令所处盘符根目录下的/data/db作为自己的数据存储目录
# 所以在第一次执行命令之前,手动在磁盘根目录/下,新建一个data文件件/db文件夹
mongod

运行成功如下

image-20201016191450590

如果要修改默认的数据存储目录

mongod --dbpath=数据存储目录路径

停止:

在开启服务的控制台,ctrl+c停止
或者直接关闭命令行窗口

查看服务等是否启动成功

// 在浏览器输入
http://127.0.0.1:27017/

image-20201016191508927

6.4 连接和退出连接数据库

注意:启动服务,窗口最小化,才能连接

连接:

#该命令默认连接本机服务
mongo

退出连接:

#在连接状态下输入 exit退出连接
exit

6.5 基本命令

https://www.cnblogs.com/liyonghui/p/mongodb.html

  • show dbs

    • 查看显示所有数据库

      1568684532437

  • db

    • 查看当前操作的数据库(默认连接到test),当show dbs之后,没有显示test,是因为test;里面没有数据,一旦往里面插入数据,test就被自动创建出来了

    1568684715468

  • use 数据库名称

    • 切换到指定的数据库(如果没有,会新建)
  • show collections显示当前数据库中的集合(类似关系数据库中的表)

  • db.students.find()对于当前数据库中的students集合进行数据查找(由于没有条件,会列出所有数据)

1568689141859

  • 插入数据

1568685698210

向集合插入数据

1568686180775

创建集合

db.createCollection("userinfo")

删除表

db.userinfo.drop
6.5.1 查询
  • 查询所有

    db.userinfo.find() 
    db.userinfo.find().pretty() //格式化输出
    
  • 按条件查询

    db.userinfo.find({username:"lili"})
    
  • 范围查询

    • gt大于 gte大于等于
    • lt小于 lte 小于等于
    • : 等于 ne 不等于
  • 查询 (and ) 查询密码(价格 数字)在100到325之间的数据

    db.userinfo.find({userpwd:{$gt:100,$lt:325}).pretty() 
    
  • 查询(or) 查询用户为leson或者为lili的数据

    db.userinfo.find({$or:[{username:"leson"},{username:"lili"}]})
    
  • 查询(and 和or的使用)

     db.userinfo.find({userpwd:{$gt:300},$or:[{username:"lili"},{username:"lulu"}]}).pretty()
    
  • //查询年龄在30到50之间的lii2或者lili3

    //变成价格在3000到5000之间的品牌 手机 华为或者小米

    db.userinfo.find({userage:{$gte:30,$lt:50},$or:[{username:'lili2'},{username:"lili3"}]}).pretty()
    
  • 模糊查询

    // 匹配用户名中含有l这个字符的
    db.userinfo.find({username:/l/})
    
  • 条件模糊查询

    // 查询用户名里面含有l 并且年龄大于30
    db.userinfo.find({username:/l/,age:{$gt:30}})
    
  • 类型匹配

    db.userinfo.find({userpwd:{$type:"string"}})
    
  • 分页

    // skip(5).limit(5)  //跳过五条数据 显示五条数据
    db.userinfo.find().skip(1).limit(2)
    
  • 排序

    // 排序 sort  1为为升序 -1为降序
    // 根据编号升序
    db.userinfo.find().sort({"_id":1})
    
  • 统计总数

    db.userinfo.count()  //所有的总数
    db.userinfo.count({username:"leson"})//满足条件的总数
    
  • 查看所有索引

    db.userinfo.getIndexes() 
    
    [
            {
                    "v" : 2,
                    "key" : {
                            "_id" : 1
                    },
                    "name" : "_id_",
                    "ns" : "1905.userinfo"
            }
    ]
    
  • 删除所有索引(基本不用)

    db.col.dropIndexes()
    
  • 删除指定索引

    db.col.dropIndex("索引名称")
    

注意:查询判断result.data.length即可

6.5.2 新增
  • 新增一条

    db.userinfo.insert({username:"lili",usertel:134,userpwd:123456})
    
     //返回受影响的行数 WriteResult({ "nInserted" : 1 })
    
  • 新增多条

    db.userinfo.insertMany([{username:"leson",usertel:134,userpwd:123},{username:"lulu",usertel:135,userpwd:135}])
    
  • excel批量新增入库

    image-20201023104714119

注意:新增判断result.data.length即可

6.5.3 更新
  • 更新一条

    db.userinfo.update({username:"leson"},{$set:{userpwd:"abc"}})  
    
  • 更新多条

     db.userinfo.update({username:"leson"},{$set:{userpwd:"bbb"}},false,true) //多条更新
    

更新接口

image-20201022162928535

用_id更新_

image-20201022162552930

判断更新是否修改成功

image-20201022162818802

image-20201023112559617

注意:更新 用 result.nModified判断是否生效

6.5.4 删除
  • 删除一条

    // 删除用户名为leson的一条数据
     db.userinfo.remove({"username":"leson"},{justOne:true})
    
  • 删除多条

    // 删除用户名为leson的所有数据
    db.userinfo.remove({"username":"leson"});
    
  • 删除一条数据

    db.userinfo.deleteOne({usertel:"123456"})
    
  • 删除多条数据

    db.userinfo.deleteMany({username:{$in:["lili","beibei"]}})
    
  • 手动释放 删除后的资源

    db.repairDatabase()
    
  • 判断是否删除成功

    https://blog.csdn.net/wolf_soul/article/details/50059191?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param

    image-20201023105829921

6.6 在Node中如何操作MongoDB数据库

6.6.1 使用官方的mongodb包来操作

https://github.com/mongodb/node-mongodb-native

6.6.2 使用第三方mongoose来操作MongoDB数据库

第三方包:mongoose基于MongoDB官方的mongodb包再一次做了封装

网址:https://mongoosejs.com/

安装:

npm i mongoose

mongoose使用:

// 引包
const mongoose = require('mongoose');

// 连接MongoDB数据库
mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true});

// 创建一个模型
// 就是在设计数据库
// MongDB是动态的,非常灵活,只需要在代码中设计数据库就可以了
// mongoose这个包,可以让设计编写过程变得非常简单
const Cat = mongoose.model('Cat', { name: String });

// 实例化一个Cat(最终是cats)
const kitty = new Cat({ name: 'Zildjian' });

// 持久化保存Kitty实例
kitty.save().then(() => console.log('meow'));

7.mongoose

  • 官网:https://mongoosejs.com/

    中文:http://www.mongoosejs.net/

  • 文档:https://mongoosejs.com/docs/guide.html

    中文:http://www.mongoosejs.net/docs/guide.html

  • API文档:https://mongoosejs.com/docs/api.html

7.1 起步

安装:

npm i mongoose

官方示例:

// 引包
const mongoose = require('mongoose');

// 连接MongoDB数据库
mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true});

// 创建一个模型
// 就是在设计数据库
// MongDB是动态的,非常灵活,只需要在代码中设计数据库就可以了
// mongoose这个包,可以让设计编写过程变得非常简单
const Cat = mongoose.model('Cat', { name: String });

// 实例化一个Cat(最终是cats)
const kitty = new Cat({ name: 'Zildjian' });

// 持久化保存Kitty实例
kitty.save().then(() => console.log('meow'));
// 或者
// kitty.save(function (err){
//     if(err){
//         console.log(err);
//     }else{
//         console.log('meow')
//     }
// })

7.2 官方指南

7.2.1 设计Scheme发布Model
// 引包
var mongoose=require('mongoose')

var Schema = mongoose.Schema;

// 连接MongoDB数据库
// 指定连接的数据库不需要存在,当你插入第一条数据库之后,自动被创建出来
mongoose.connect('mongodb://localhost:27017/itcast', {useNewUrlParser: true});

// 设计文档结构(表结构)
// 字段名称就是表结构中的属性名称
// 值
// 约束的目的是为了保证数据完整性
var userSchema = new Schema({
    username:{
        type:String,
        required:true//不能为空
    },
    password:{
        type:String,
        required:true//不能为空
    },
    email:{
        type:String,
    }
});

//3. 将文档结构发布为模型
//  mongoose.model方法激素用来将一个架构发布为model
//  第一个参数:传入一个 大写单数名词字符串用来表示数据库名词
//   mongoose会自动将 大写单数名词字符串 转为 小写复数 的集合名称
//  User 最终转为 users集合名称
//  第二个参数:接收架构Schema

//  返回值:模型构造函数
var User = mongoose.model('User', userSchema);



// 4.有了模型构造函数之后,就可以使用这个构造函数对users中的数据进行增删改查
7.2.2 增加数据

mongoose 新增数据,先new ,再save

var admin = new User({
    username:'admin',
    password:'123456',
    email:'admin@admin.com'
})
// 持久化存储
admin.save(function (err,result){
    if(err){
        console.log('保存失败')
    }else{
        console.log('保存成功')
        console.log(result)
    }
})
7.2.3 查询数据

查询所有

//查询所有数据
User.find(function (err,result){
    if(err){
        console.log('查询失败')
    }else{
        console.log(result)
    }
})

按条件查询所有

//  按条件查询所有
User.find({ //返回的是一个数组
    username:'admin',// 条件,没有条件,默认查询第一条插入的记录
    password:'123456'
},
    function (err,result){
        if(err){
            console.log('查询失败')
        }else{
            console.log(result)
        }
    })

按条件查询单个

//  按条件查询
User.findOne({// 只找匹配的第一个对象,返回一个对象
    username:'admin',// 条件,没有条件,默认查询第一条插入的记录
    password:'123456'
},
    function (err,result){
        if(err){
            console.log('查询失败')
        }else{
            console.log(result)
        }
    })
7.2.4 删除数据
// 删除数据

User.remove({
    username:'Jack',// 条件,没有条件,默认查询第一条插入的记录
},
    function (err,result){
        if(err){
            console.log('删除失败')
        }else{
            console.log('删除成功')
            console.log(result)
        }
    })
7.2.5 更新数据

根据条件更新所有:

Model.update(conditions,doc, options, callback);

根据指定条件更新一个:

A.findOneAndUpdate(conditions, update, options, callback) 

根据Id更新一个

//  更新数据 
//	根据Id更新数据
User.findByIdAndUpdate('5d8083c813491d38f8b04127',{
    password:'123'
},function (err,result) {
    if (err){
        console.log('更新失败')
    }else{
        console.log('更新成功')
    }
})

8. 使用Node操作MYSQL数据库

https://www.npmjs.com/package/mysql

安装:

npm install mysql

使用Node操作MYSQL之前,开启mysql服务

  • CMD进入mysq目录下的bin目录,输入命令:
net start MySQL
  • 或者 win+r输入services.msc进入服务,开启mysql服务

操作数据库

// 加载mysql包
var mysql = require('mysql');

// 创建连接
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: '',
    database: 'db_users'
});

// 连接数据库
connection.connect();

// 执行数据操作

// 查询
connection.query('SELECT * FROM `users`', function (error, results, fields) {
    if (error) throw error;
    console.log('The solution is: ', results);
});

// 插入
// connection.query('INSERT INTO users VALUES(1,"admin","123456")', function (error, results, fields) {
//     if (error) throw error;
//     console.log('The solution is: ', results);
// });


// 关闭连接
connection.end();

9. JavaScript中和遍历相关的数组方法

https://www.runoob.com/jsref/jsref-obj-array.html

9.1 every

定义和用法

every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。

every() 方法使用指定函数检测数组中的所有元素:

  • 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
  • 如果所有元素都满足条件,则返回 true。

注意: every() 不会对空数组进行检测。

注意: every() 不会改变原始数组。

语法

array.every(function(currentValue,index,arr), thisValue)

1568682224479

9.2 some

定义和用法

some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。

some() 方法会依次执行数组的每个元素:

  • 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
  • 如果没有满足条件的元素,则返回false。

注意: some() 不会对空数组进行检测。

注意: some() 不会改变原始数组。

语法

array.some(function(currentValue,index,arr),thisValue)

1568682384777

9.3 include

定义和用法

includes() 方法用来判断一个数组是否包含一个指定的值,如果是返回 true,否则false。

[1, 2, 3].includes(2);     // true
[1, 2, 3].includes(4);     // false
[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true
[1, 2, NaN].includes(NaN); // true

语法

arr.includes(searchElement)
arr.includes(searchElement, fromIndex)

1568682551070

9.4 map

定义和用法

map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。

map() 方法按照原始数组元素顺序依次处理元素。

注意: map() 不会对空数组进行检测。

注意: map() 不会改变原始数组。

语法

array.map(function(currentValue,index,arr), thisValue)

1568682636333

9.5 reduce

定义和用法

reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

reduce() 可以作为一个高阶函数,用于函数的 compose。

注意: reduce() 对于空数组是不会执行回调函数的。

语法

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

1568682740019

10 综合案例

10.1 目录结构

1568960975478

10.2 模板页

10.3 路由设计

路径方法get参数post参数是否需要登录权限备注
/GET渲染首页
/registerGET渲染注册页面
/registerPOSTemail、nickname、psssword处理注册请求
/loginGET渲染登录页面
/loginPOSTemail、password处理登录请求
/loginoutGET处理退出请求

10.4 模型设计

10.5 功能实现

10.6 编写步骤

  • 创建目录结构

  • 整合静态页-模板页

    • include:导入header,footer等,形成模板的header,footer

      • {{include 'xxx/xxx/header.html'}}
        
        {{include 'xxx/xxx/footer.html'}}
        
    • block:在模板中留坑,方便在其他页面,这个坑里,写不同的内容

      • <!--给标题title留坑-->
        {{block 'title'}}{{/block}}
        
        <!--给自己的body留坑-->
        {{block 'body'}}{{/blok}}
        
    • extend:在其他页面,使用模板页

      // 在其他页面,使用模板页
      {{extend 'xxx/xxx/模板页.html'}}
      
      // 填坑,在这填,这个页面想要的title部分
      {{block 'title'}}{{'标题'}}{{/block}}
      
      //填坑,在这,填这个页面的body主体
      {{block 'body'}}{{'标题'}}{{/block}}
      
  • 设计用户登录、退出、注册的路由

  • 用户注册

    • 先处理好客户端页面内容(表单控件的name,发送请求,能不能收集表单数据、表单验证等)
    • 服务端
      • 先获取客户端表单请求的数据
      • 操作数据库
      • 如果有错,发送500 告诉客户端错了
      • 其他的根据业务发送不同的响应数据
  • 用户登录

  • 用户退出

  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值