黑马Nodejs笔记01

Node是什么?

nodejs官方网站

学习Node.js的目的就是帮助大打开服务器这个黑盒子,只有了解服务端才能更好的配合服务端开发人员进行协同开发。

nodejs不是一门语言,不是库,不是框架,是一个js运行时环境,简单来讲就是nodejs可以解析和执行javascript代码,以前只有浏览器可以解析执行javascript代码,也就是说现在的javascript可以完全脱离浏览器来运行,一切都归功于Nodejs

Nodejs特性

没有BOM和DOM、事件驱动、非阻塞IO模型(异步)、轻量和高效

npm是世界上最大的开源生态系统,绝大多数javascript相关的包都存放在了npm上,这样做的目的是为了让开发人员更方便的去下载使用包

构建于Chrom 的 V8 引擎之上, V8引擎是目前公认的解析执行javascript代码最快的

Node环境安装

下载node

查看电脑中node的版本号 命令行输入 node --versionh或node -v

环境变量

hello world

​ 1、创建编写javascript脚本文件

​ 2、打开终端

​ 3、输入 node 文件名,执行对应文件

读取文件

浏览器中的javascript 是没有文件操作的能力的,但是node中的的javascript具有文件操作的能力

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

var fs = require('fs')

2、读取文件

//第一个参数就是要读取的文件路径
//第二个参数是一个回调函数
//				error
//					如果读取失败,error就是错误对象
//					如果读取成功,error 就是 null
//				data
//					如果读取成功,data 就是读取到的数据
//					如果读取失败,data 就是 undefined
fs.readFile('./data/hello.txt',function(error,data){
    console.log(data) //读取到的是2进制转16进制的数字
    console.log(data.toString()) //我们可以通过 toString 方法将其转换为我们认识的字符
})

读取目录

注意readdir这里的d是小写的不是大写的

var fs = require('fs')
fs.readdir('D:\webStudy\Code',function(err,files){
    if(err){
        return console.log('目录不存在')
    }
    console.log(files)	//['Ajax','html5','node']
})

写文件和简单的错误处理

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

var fs = require('fs')

2、写文件

//第一个参数:文件路径
//第二个参数:文件内容
//第三个参数:回调函数 
//				参数error
//					成功:文件写入成功,error就是null
//					失败:文件写入失败,error就是错误对象
fs.writeFile('./data/hello.md','hello world',function(error){
   if(error == null){
       console.log('文件写入成功')
   }else{
       console.log('文件写入失败')
   } 
})

简单的http服务

接下来,我们要干一件使用Node很有成就感的一件事儿,你可以使用 Node 非常轻松的构建一个 web 服务器。

在 Node 中专门提供了一个核心模块:http,http 这个模块的职责就是帮你创建编写服务器的

1、加载 http 核心模块

var http = require('http')

2、使用 http.createServer() 方法创建一个 Web 服务器

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/来进行访问') 
})

5、以上四步可以进行简写,如下代码所示:

var http = require('http')
http.createServer(function(req,res){
    console。log('hello')
})
.listen(3000,function(){
    console.log('running...')
})

发送响应

request 请求事件处理函数,需要接收两个参数:request请求对象和response响应对象。request请求对象可以用来获取客户端的一些请求信息,例如请求路径。response响应对谢可以用来给客户端发送响应消息。

server.on('request',function(request,response){
    console.log('收到客户端的请求了,请求路径是:'+response.url)
     
    //response 对象有一个方法:write可以用来给客户端发送响应数据
    //write 可以使用多次,但是最后一定要使用 end 来结束响应,否则客户端会一直等待
    response.write('hello')
    response.write(' nodejs')
    //告诉客户端,我的话说完了,你可以呈递给用户了
    response.end()
    
    //上面的方式比较麻烦,推荐使用更简单的方式,直接end的同时,发送响应数据。
    response.end('hello nodejs')
})

现在我们的服务器的能力还非常的弱,无论是什么请求,都只能响应hello nodejs

根据不同的请求路径返回不同的数据

注意:浏览器默认以80端口访问服务器

server.on('request',function(req,res){
	var url = req.url;
    if(url === '/product'){
        var products = [
            {
                name:'苹果 X',
                price: 8888
            },
            {
                name:'菠萝 X',
                price: 6666
            },
            {
                name:'诺基亚 X',
                price: 6666
            }
        ];
        //响应的内容只能是二进制数据或者字符串
        //数字、对象、数组、布尔值均不能作为响应的内容
        res.end(JSON.stringify(products))
    }
})

响应的内容只能是二进制数据或者字符串,数字、对象、数组、布尔值均不能作为响应的内容

JSON的两个函数的使用

JSON.stringify()	//将对象、数组转为json字符串
JSON.parse()	//将JSON字符串转为对象

Nodejs中的js——核心模块

Node 为 javascript 提供了很多服务器级别的API,这些API绝大多数都被包装到了一个具名的核心模块中了。例如:文件操作的 fs核心模块、http服务构建的http模块、path路径操作模块、os操作系统信息模块。。。。

以后只要我说这个模块是一个核心模块,你就必须马上想到如果想要使用它,就必须:

var fs = require('fs')
var http = require('http')

下面随便介绍的几个核心模块,更多的查看nodejs官方文档

//用来获取机器信息的
var os = require('os')

//用来操作路径的
var path = require('path')

//获取当前机器的CPU信息
console.log(os.cpus())

//memory 内存
console.log(os.totalmem())

//extname extension name 扩展名
console.log(path.extname('c:/a/b/c/hello.txt'))

Nodejs中的js——模块系统

require是一个方法,它的作用是用来加载模块的,在Node中,模块有三种:

第三方模块,例如 jQuery

具名的核心模块,例如 fs、http

用户自己编写的模块:相对路径必须加 ./ ,可以省略后缀名。

在Node中,没有全局作用域,只有模块作用域。外部访问不到内部,内部也访问不到外部。默认都是封闭的,既然是模块作用域,那如何让模块与模块之间进行通行。又时候,我们加载文件模块的目的不是为了简简单单的执行里面的代码,更重要的是为了使用里面的某个成员。

require 方法有两个作用:

​ 1.加载文件模块并执行里面的代码

​ 2.拿到被加载文件模块导出来的接口对象:exports

​ 在每个文件模块中都提供了一个对象:exports ,exports默认是一个空对象,您要做的就是把所有需要被外部访问的成员放进这个对象里。

如下代码所示,创建一个目录,分别在下面创建两个文件,一个是a.js一个是b.js

a.js

var bExports = require('./b') //可以省略后面的.js后缀名
var fs = require('fs')
console.log(bExports.foo)
console.log(bExports.add(10, 30))
console.log(bExports.age)
bExports.readFile('./a.js')

b.js

var foo = 'bbb'
// console.log(exports)
exports.foo = 'hello'
exports.add = function (x, y) {
  return x + y
}
exports.readFile = function (path, callback) {
  console.log('文件路径:', path)
}
var age = 18
exports.age = age
function add(x, y) {
  return x - y
}

ip地址与端口号的概念

所有联网得到程序都需要进行网络通信,计算机只有一个物理网卡,而且同一个局域网中,网卡的地址必须是唯一的,网卡是通过唯一的ip地址进行定位的。

IP地址用来定位计算机,端口号用来定位具体的应用程序(所有需要通信的软件都必须具有端口号)

拿到请求我的(服务端)的客户端的ip地址和端口号:

console.log(req.socket.remoteAddress)
console.log(req.socket.romotePort)

端口号的范围0 ~ 65536 之间

在计算机中有一些默认的端口号,最好不要去使用,例如:http服务的80端口。我们在开发过程中使用一些简单好记的就可以了,例如 3000、5000 等没有什么含义

响应内容类型Content-type

响应的内容只能是二进制数据或者字符串

在服务端默认发送的数据,其实是utf8编码内容,但是浏览器不知道你是utf8编码内容,浏览器在不知道服务端响应内容的编码的情况下会按照当前操作系统的默认编码去解析,中文操作系统默认是gbk(相当于用汉语词典去翻译中文的意思)

解决方法就是正确的告诉浏览器我给你发送的内容是什么编码的

res.setHeader('Content-type','text/html;charset=utf-8')

在http协议中,Content-type就是用来告知对方我给你发送的数据内容是什么类型

text/plain 普通文本

text/html html文本(默认)

发送文件中的数据以及Content-type内容类型

不同的资源对应的 Content-Type 是不一样的查看网址

图片不需要指定编码,一般只为字符数据才指定编码

结合 fs 发送文件中的数据

var http = require('http')
var fs = require('fs')
var server = http.createServer()

server.on('request', function (req, res) {
  var url = req.url
  if (url === '/') {
    // 肯定不这么干
    // res.end('				   Document

首页

/html>')
// 我们要发送的还是在文件中的内容 fs.readFile('./resource/index.html', function (err, data) { if (err) { res.setHeader('Content-Type', 'text/plain; charset=utf-8') res.end('文件读取失败,请稍后重试!') } else { // data 默认是二进制数据,可以通过 .toString 转为咱们能识别的字符串 // res.end() 支持两种数据类型,一种是二进制,一种是字符串 res.setHeader('Content-Type', 'text/html; charset=utf-8') res.end(data) } }) } else if (url === '/xiaoming') { // url:统一资源定位符 // 一个 url 最终其实是要对应到一个资源的 fs.readFile('./resource/ab2.jpg', function (err, data) { if (err) { res.setHeader('Content-Type', 'text/plain; charset=utf-8') res.end('文件读取失败,请稍后重试!') } else { // data 默认是二进制数据,可以通过 .toString 转为咱们能识别的字符串 // res.end() 支持两种数据类型,一种是二进制,一种是字符串 // 图片就不需要指定编码了,因为我们常说的编码一般指的是:字符编码 res.setHeader('Content-Type', 'image/jpeg') res.end(data) } }) } }) server.listen(3000, function () { console.log('Server is running...') })

ext-代码风格以及代码中的分号问题

为了约定大家的代码风格,所以在社区中诞生了一些比较规范的代码风格规范:

javaScript Standard Style官网

Airbnb Javascript Style官网比javaScript Standard Style约束多,更加的严谨

当你采用了无分号的代码风格的时候,只需要注意以下情况:

当以 ()、[]、``开头的时候,则在前面补上一个分号用以避免一些语法解析错误。所以你会发现在一些第三方的代码中能够看到一上来就以一个 ; 开头

结论:无论你的代码是否有分号,都建议如果一行代码是以 (、[、`开头的,则最好都在其前面补上一个分号

有些人也喜欢玩一些花哨的东西,例如前面加 ! ~等

扩展:``是EcmaScript 6 中新增的一种字符串包裹方式,叫做模板字符串,它支持换行和非常方便的拼接变量

初步实现Apache功能

我们以前使用过Apach服务器软件,这个软件默认有一个 www 目录,所有存放在 www 目录下的资源都可以通过网址来浏览。参考移动端检测与调试笔记

我们用nodejs来实现类似的功能

var http = require('http')
var server = http.createServer()
var fs = require('fs')
var wwwDir = 'D:/webStudy/Code/node/www'
server.on('request',function(req,res){
	var url = req.url
	var fileName = '/index.html'
	if (url !== '/') {
		fileName = url
	}
	fs.readFile(wwwDir+fileName,function(err,data){
		if(err){
			return res.end('404 Not Found')
		}else{
			res.end(data)
		}
	})
})
server.listen(3000,function(){
	console.log('running...')
})

模板引擎的实现原理

在一个目录下创建两个文件,index.html和node_test.js

index.html


<html>
<head>
	<title>title>
head>
<body>
    <h1>@_@h1>
body>
html>

node_test.js

var http = require('http')
var server = http.createServer()
var fs = require('fs')
server.on('request',function(req,res){
	var url = req.url
	fs.readFile('./index.html',function(err,data){
		if(err){
			return res.end('404 Not Found')
		}
		data = data.toString()	//读取的内容默认是二进制,这里进行转换成字符串
		data = data.replace('^_^','笑脸')	//进行字符串的替换,符号笑脸转成了文字
		  res.setHeader('Content-Type', 'text/html; charset=utf-8')
		res.end(data)
	})
})
server.listen(3000,function(){
	console.log('running...')
})

如上所示,模板引擎的原理其实就是字符串的解析替换,模板引擎不关心内容

在node中如何使用模板引擎art-template

1.安装 npm install art-template,该命令在哪执行就会下载到哪里,默认会下载到node_modules目录中,node_modules不要改,也不支持改。

2.在需要使用的文件模块中加载art-template,只需要使用require方法加载就可以了:require(‘art-teplate’),参数中的art-template就是你下载的包的名字,也就是说install的名字是什么,则你的require中的就是什么

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

如下的简单使用:

var template = require('art-template')
var ret = template.render('hello {{name}}',{
    name:'Jack'
})
console.log(ret) // 'hello Jack'

art-template不仅可以在web中使用,还可以在服务端中使用,将渲染好的页面直接发给客户端。

服务端渲染和客户端渲染的区别?

  • 客户端渲染不利于SEO搜索引擎优化
  • 服务端渲染是可以被爬虫抓取到的,客户端异步渲染是很难被爬虫抓取到的
  • 所以你会发现真正的网站既不是纯异步的也不是纯服务端渲染出来的而是两者结合来做的
  • 例如京东的商品列表就是采用的是服务端渲染的,目的是为了SEO搜索引擎优化,而它的商品评论列表为了用户体验,而且也不需要SEO优化,所以采用客户端渲染

处理网站中的静态资源

在index.html中,一些标签如script、link、video、audio、img等带有src或者href(link)属性标签的时候,浏览器会自动对这些资源发起新的请求,请求时的路径就不要去写相对路径了,因为这个时候所有的资源都是通过url标识来获取的,我们的服务端开放了 /public/ 目录。所以这里的请求路径都写成:/public/xxx,/ 在这里就是 url 根路径的意思。浏览器在真正发请求的时候会最终把 http://127.0.0.1:3000 拼上,所以在html页面中,不要再写文件路径了,把所有的路径都想象成 url 地址

如 index.html中的head标签内写

<link rel="stylesheet" href="/public/lib/bootstrap/dist/css/bootstrap.css">

在写后端时,我们为了让目录结构保持统一清晰,所以我们约定,把所有的HTML文件都放在 views(视图) 目录中,统一把静态资源都存放在 public 目录中。整个 public 目录中的资源都允许被访问。

在以nodejs写的服务中的app.js中,我们通过以下代码来公开 public 目录中的静态资源

var http = require('http')
var fs = require('fs')
var url = require('url')
var template = require('art-template')
http
    .createServer(function(req,res){
    	var url = req.url
        if(url == '/'){
            fs.readFile('./views/index.html',function(err,data){
                if(err){
                    return res.end('404 Not Found')
                }else{
                    res.end(data)
                }
            })
        }else if(url.indexOf('/public/') === 0){
            //如果请求路径是以 /public/ 开头的,则我认为你要获取 public 中的某个资源
            //所以我们就直接可以把请求路径当作文件路径来直接进行读取
            fs.readfile('.'+url,function(err,data){
                if(err){
                    return res.end('404 Not Found')
                }else{
                    res.end(data)
                }
            })
        }else{
            fs.readfile('./views/404.html',function(err,data){
                if(err){
                    return res.end('404 Not Found')
                }else{
                    res.end(data)
                }
            })
        }
})
	.listen(3000,function(){
    	console.log('running...')
})

上面的代码还进行了404处理。

URL的美化处理

我们还可以进行url的美化处理,在网页的请求中,我们可以写上如下请求代码

a>  

在后端中我们可以判断这样的url,然后返回 post.html页面

if(url == '/public/views/post'){
            fs.readFile('/public/views/post.html',function(err,data){
                if(err){
                    return res.end('404 Not Found')
                }else{
                    res.end(data)
                }
            })
        }

这样我们在浏览器的地址栏中,我们将看到的是 127.0.0.1:3000/public/views/post这样没有html后缀的地址名了。

处理表单get提交和提交重定向

以前是如何处理表单提交的?

表单中需要提交的表单控件元素必须具有 name 属性

表单提交分为:

​ 1.默认的提交行为

​ 2.表单异步提交

如下代码提交:

​ action 就是表单提交的地址,说白了就是请求的 url 地址

​ method 请求方法:post 、get

<form action="/pinglun" method="get">
  <div class="form-group">
    <label for="input_name">你的大名label>
    <input type="text" class="form-control" required minlength="2" maxlength="10" id="input_name" name="name" placeholder="请写入你的姓名">
  div>
    
  <div class="form-group">
    <label for="textarea_message">留言内容label>
    <textarea class="form-control" name="message" id="textarea_message" cols="30" rows="10" required minlength="5" maxlength="20">textarea>
  div>
    
  <button type="submit" class="btn btn-default">发表button>
form>

我们通过以上的代码提交,浏览器的地址栏中将会发起【 http://127.0.0.1:3000/pinglun?name=的撒的撒&message=的撒的撒的撒】类似的提交,对于这种表单提交的请求路径,由于其中具有用户动态填写的内容,所以你不可能通过去判断完整的 url 路径来处理这个请求。

结论:对于我们来讲,其实只需要判定,如果你的请求路径是 /pinglun 的时候,那我就认为你提交表单的请求过来了。

那么我们到底如何处理这种get表单请求呢?

我们首先来学一个核心模块 url,这个模块专门用来处理url的。

var url = require('url')
var parseObj = url.parse('http://127.0.0.1:3000/pinglun?name=的撒的撒&message=的撒的撒的撒',true)
// 使用 url.parse 方法将路径解析为一个方便操作的对象,第二个参数为 true 表示直接将查询字符串转为一个对象(通过 query 属性来访问)
console.log(parseObj)

结果如下:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OEwXBSym-1577194150563)(D:\webStudy\Note\Nodejs\images\url.png)]

通过以上的结果,我么可以发现,我们可以将上面所有代码的url变量,写成如下代码所示

var pathname = parseObj.pathname

在处理get表单提交请求时,我们只需要加上这样的一条路径请求

if (pathname === '/pinglun') {
    //注意:这个时候无论 /pinglun?xxx 之后是什么,我都不用担心了,因为我的 pathname 是不包含 ? 之后的那个路径的
}

如何通过服务器让客户端重定向?

1.状态码设置为 302 临时重定向 statusCode

2.在响应头中通过 Location 告诉客户端往哪儿重定向 setHeader

如果客户端发现收到服务器的响应状态码是 302 ,就会自动去响应头找 Location ,然后对该地址发起新的请求,所以你就能看到客户端自动跳转了

res.statusCode = 302
res.setHeader('Location','/')

301 永久重定向 : 浏览器会记住重定向的位置,比如a网页跳到b,第一次需要发送请求到服务器,第二次或者以后再请求时,不会发送请求到服务器,直接从缓存中拿

302 临时重定向:浏览器不记忆。从a网页每次到b网页时,都会发送请求到服务器

Node中的模块系统

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

  • EcmaScript语言
    • 和浏览器不一样,在Node中没有BOM、DOM
  • 核心模块
    • fs 文件操作
    • http 服务的http
    • url 路径操作模块
    • path 路径处理模块
    • os 操作系统信息
  • 第三方模块
    • art-template
    • 必须通过npm下载使用
  • 咱们自己写的模块
commomjs模块规范

在node中的javascript还有一个重要的概念:模块系统

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

在同一个目录下创建两个文件,如下代码所示:

main.js文件

var fooExports = require('./foo')
console.log(fooExports)

foo.js文件

var foo = 'bar'
function add(x,y){
    return x+y
}
//只能得到我想给你的成员
//这样做的目的是为了解决变量命名冲突的问题
exports.add = add
exports.foo = foo
//exports是一个对象,我们可以通过多次为这个对象添加成员实现对外导出多个内部成员

你可以认为在每个模块的最后 return 了这个exports(module.exports)

我们现在有一个需求:我希望加载得到的直接就是一个:方法、字符串、数字、数组

解答:我们对foo.js文件进行修改

var foo = 'bar'
function add(x, y) {
  return x + y
}
// 这种方式不行。
// exports = add
module.exports = 'hello'
module.exports = function (x, y) {
  return x + y
}

如上代码所示:我们通过module.exports = ‘ hello’ 直接导出字符串 hello

加载和导出的使用规则

加载 require

语法:

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

两个作用:

  • 执行被加载模块中的代码
  • 得到被加载模块中的 exports 导出接口对象

导出 exports

  • Node中是模块作用域,默认文件中所有的成员只在当前文件模块有效
  • 对于希望可以被其他模块访问的成员,我们就需要把这些公开的成员都挂载到exports接口对象就可以了

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

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

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

module.exports = 'hello'

//以这个为准,前者会被覆盖
module.exports = function (x, y) {
  return x + y
}

也可以这样导出多个成员

module.exports = {
	hello:'hello',
	add:function(x,y){
		return x+y
	}
}

模块原理、exports和module.exports的区别

在Node中,每个模块内部都有一个自己的module对象,该module对象中,有一个成员叫:exports,也是一个对象

默认在代码的最后一句:return module.exports(注意,return的是module.exports不是exports,所以你给export重新赋值不管用)

我们发现,每次导出接口成员的时候都通过 module.exports.xxx = xxx 的方式很麻烦,点太多了。所以,Node为了简化操作,专门提供了一个变量:exports等于module.exports

也就是说在模块中还有这么一句默认代码

var exports = module.exports

module.exports和exports是一样的

console.log(exports === module.exports)	//true

两者一致,那就说明,我可以使用任意一方来导出内部成员

但是,如下代码所写,会改变exports的指向地址,会让exports和module.exports指向不同的地址

exports = 'hello' //hello 不会被导出,后面再使用exports.xxx 也不会生效了

如果在程序中写上下面代码会改变module.exports默认指向地址的数据的返回

module.exports = {}

Require方法加载规则

优先从缓存中加载

通过一个实例验证优先从缓存中加载:

在一个目录下创建main.js、a.js、b.js

main.js

require('./a')
var fn = require('./b')

a.js

console.log('a.js被加载了')
require('./b')

b.js

console.log('b.js被加载了')
module.exports.fn = function(){
    console.log('我是b.js')
} 

结果:

a.js被加载了
b.js被加载了

为什么没有输出第二次b.js被加载了呢?因为优先从缓存中加载,由于在a.js中已经加载过了,所以这里不会重复加载了。可以拿到其中的接口对象,但是不会重复执行里面的代码。这样做的目的是为了避免重复加载,提高加载效率

路径形式的模块:

./ 当前目录,不可省略

…/ 上一级目录,不可省略

/xxx 几乎不用,首位的 / 在这里表示的是当前文件模块所属磁盘根路径

d:/a/foo.js 几乎不用

.js 后缀名可以省略

核心模块的本质也是文件

核心模块文件已经被编译到了二进制文件中了,我们只需要按照名字赖加载就可以了

require(‘fs’)、reqiure(‘http’)

第三方模块

凡是第三方模块都必须通过npm下载

使用的时候可以通过require(‘包名’)的方式来进行加载才可以使用

不可能有任何一个第三方包和核心模块的名字一样的

既不是核心模块、也不是路径形式的模块

​ 先找到当前文件所处目录中的 node_modules 目录

​ node_modules/art-template

​ node_modules/art-template/package.json 文件

​ node_modules/art-template/package.json 文件中的 main 属性

​ main 属性中就记录了 art-template 入口模块

​ 然后加载使用这个第三方包

​ 实际上最终加载的还是文件

如果 package.json 文件不存在或者 main 指定的入口模块是也没有

则 node 会自动找该目录下的 index.js

也就是说 index.js 会作为一个默认备选项

如果以上所有任何一个条件都不成立,则会进入上一级目录中的 node_modules 目录查找

如果上一级还没有,则继续往上上一级查找

。。。

如果直到当前磁盘根目录还找不到,最后报错:

can not find module xxx

var template = require(‘art-template’)

注意:我们一个项目有且只有一个 node_modules,放在项目根目录中,这样的话项目中所有的子目录中的代码都可以加载到第三方包

不会出现有多个 node_modules

包说明文件package.json和package-lock.json

安装包的时候,选项加上 --save,会在package.json文件中的dependencies选项中记录下所下载的包

npm install art-template --save

我们建议每一个项目都要有一个package,json文件(包描述文件,就像产品的说明书一样),给人踏实的感觉

这个文件可以通过npm init 的方式来自动初始化出来

如果你的node_modules删除了也不用担心,我们只需要npm install就会自动吧package.json中的dependencies中所有的依赖项都下载回来

npm 5 以后,不需要加 --save 也会自动在package.json生成依赖

npm5以前是不会有package-lock.json这个文件的,npm5以后才加入了这个文件。当你安装包的时候,npm都会生成或者自动更新packkage-lock.json这个文件

package-lock.json这个文件会保存 node_modules中所有包的信息(版本,下载地址),这样的话会加快npm install 的速度。从文件名来看,有一个lock,这个lock是用来锁定版本的。如果项目依赖了1.1.1版本,如果你重新install ,其实会下载最新版本,而不是1.1.1。我们的目的就是希望可以锁住1.1.1版本,因为更新会导致一些原来的API不能用。所以这个 package-lock.json这个文件的另一个作用就是锁定版本号,防止自动升级新版

npm

node package manager

npm网站:npmjs.com

npm的第二层含义就是一个命令行工具,只要你安装了node就已经安装了npm

npm也有版本这个概念

可以通过命令行中输入

npm --version

升级npm(自己升自己)

npm install --global npm

npm常用命令

  • npm init

    • npm init -y 可以跳过向导,快速生成
  • npm install

    • 一次性把dependencies选项中的依赖项全部安装
    • npm i
  • npm install 包名

    • 只下载
    • npm i 包名
  • npm install --save 包名

    • 下载并且保存依赖项(package.json文件中的dependecies选项)
    • npm i -S 包名
  • npm uninstall 包名

    • 只删除,如果有依赖项会依然保存
    • npm un 包名
  • npm uninstall --save 包名

    • 删除的同时也会把依赖信息也去除
    • npm un -S 包名
  • npm help

    • 查看使用帮助
  • npm 命令 --help

    • 查看指定命令的使用帮助

解决npm被墙问题

npm 存储包文件的服务器在国外,有时候会被墙,速度很慢,所以我们需要解决这个问题。

http://npm.taobao.org/淘宝的开发团队把npm在国内做一个备份。

安装淘宝的cnpm:

# 在任意的目录执行都可以
# --global 表示安装到全局,而非当前目录
# --global 不能省略,否则不管用
npm	install --global cnpm

接下来你安装包的时候把之前的 npm 替换成 cnpm

举个例子:

# 这里还是走国外的npm服务器,速度比较慢
npm install jquery

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

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

npm install jquery --registry -http://registry.npm.taobao.org

但是每一次手动这样加参数很麻烦,所以我们可以把这个选项加入配置文件中:

npm config set registry http://registry.npm.taobao.org

# 查看 npm 配置信息
npm config list

只要经过了上面命令的配置,则你以后所有的 npm install 都会默认通过淘宝的服务器来下载

Express

原生的http在某些方面表现不足以应对我们的开发需求,所以我们就需要使用框架来加快我们的开发效率,框架的目的就是提高效率,让我们的代码更高度统一

在Node中,有很多Web开发框架,我们这里以学习express为主

安装 express

npm install express --save

初次使用express

//1.引包
const express = require('express')
//2.创建你服务器的应用程序,也就是原来的http.createServer
const app = express()

//公开指定目录(思考以前是怎么做的)
//只要这样做了,你就可以直接通过 /public/xx 的方式访问 public 目录中的所有资源了
app.use('/public/',express.static('./public'))

//当服务器收到get请求 / 的时候,执行回调函数
app.get('/', function(req,res){
    //在express中可以直接 req.query 来获取查询字符参数
    consolo.log(req.query)
    res.send('hello express')
})

app.listen(3000, () => console.log('Example app listening on port 3000!'))

express会跟响应的内容自动设置Content-Type

以前判断路径的时候,都是一个一个判断的,代码很不好看

模板引擎和开放静态资源都是一个API的事儿

在express中使用模板引擎有更好的方式,res.render(‘文件名’,{模板对象}),可以自己尝试去看 art-template官方文档:如何让art-template结合Express来使用

文件操作路径和模块标识路径问题

var fs = require('fs')
//文件操作中的相对路径可以省略 ./
fs.readfile('data/a.txt',function(err,data){
    if(err){
        return console.log('读取失败')
    }
    console.log(data.toString())
})

//在模块加载中,相对路径中的 ./ 不能省略
require('data/foo.js')
// /表示磁盘根目录 以下表示c:/data/foo.js
require('/data/foo.js')

在文件操作的相对路径中

​ ./data/a.txt 相对于当前目录

​ data/a.txt 相对于当前目录

​ /data/a.txt 绝对路径,当前文件模块说出磁盘根目录

​ c:/xx/xx… 绝对路径

在模块路径标识中

​ ./ 不能省略

​ / 也表示磁盘根目录

​ 其余的和文件操作路径一样

修改完代码自动重启

我们这里可以使用一个第三方命令行工具,nodemon来帮我们解决频繁修改代码重启服务器问题。

nodemon是一个基于Node.js开发的一个第三方命令行工具,我们使用时需要独立安装。

npm install --global nodemon

安装完毕之后,使用

node app.js

# 使用nodemon
nodemon app.js

只要通过nodemon app.js 启动的服务,则它会监视你的文件变化。当文件发生变化的时候,自动帮你重启服务器

Express的中的static-server静态资源服务

// 当以 /public/ 开头的时候,去 ./public/ 目录中找对应的资源
app.use('/public',express.static('./public/'))

// 当省略第一个参数的时候,则可以通过省略 /public 的方式来访问
app.use(express.static('./public/'))

// 必须是/abc/public目录中的资源具体路径,abc相当于public的别名
app.use('/abc',express.static('./public/'))

这里一共有三种方式,推荐使用第一种方式

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

安装:

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

配置和使用:

var express = require('express')
var app = express()
//配置使用art-template模板引擎
//第一个参数,表示当渲染以.art结尾的文档的时候,使用art-template模板引擎
//express-art-template是专门用来在Express中把art-template整合到Express中,虽然外面这里不需要记载art-template但是也必须安装,原因在于express-art-template依赖了art-template
app.engine('art',require('express-art-template'))

app.get('/',function(req,res){
    //Express为Response响应对象提供了一个方法:render,render方法默认是不可以使用,但是如果配置了模板引擎就可以使用了
    //res.render('html模板名',{模板数据})
    //第一个参数不能写路径,默认会去项目中的views目录查找该模板文件,也就是说Express有一个约定:开发人员把所有的视图文件都放到views目录中
    //如果想要修改默认的views目录,则可以app.set('views','render函数的默认路径') 第一个参数是固定项,不能修改
    res.render('index.art',{
        user:{
            name:'aui',
            tags:['art','template','nodejs']
        }
    })
})

在Express中配置解析表单post请求数据

在Express中没有内置获取表单POST请求体的API,这里我们需要使用一个第三方包 body-parser

安装:

npm install --save body-parser

配置和使用:

var express = require('express')
var bodyParser = require('body-parser')
var app = express()

//配置body-parser,只要加入这个配置,则在req请求对象上会多出来一个属性:body,也就是说你就可以直接通过req.body来获取POST请求体数据
//parser application/x-www.urlencoded parser application/json
app.use(bodyParser.urlencoded({extend:false}))
app.use(bodyParser.json())

app.post('/post',function(req,res){ //在express中,请求方式不同的情况下,请求的url可以相同
    console.log(req.body) //返回post请求传来的参数
    res.redirect('/')	//express提供的重定向函数
})

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

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

req.query

路由模块的提取

新建一个router.js的文件,将所有的路由相关的函数存放到这个文件中

如下所示

module.exports = function(app){
	app.get('/',function(req,res){
            console.log('hi hi hi')
	})
    app.get('/about',function(){
        res.end('hello world')
    })
}

在app.js文件下这样调用router.js文件

var express = require('express')
var router = require('./router.js')
var app = express()
router(app)
app.listen(3000, () => console.log('Example app listening on port 3000!'))

以上的方式很不方便,Express提供了更好的方式,专门用来包装路由,如下代码所示:

router.js

/**
 * router.js 路由模块
 * 职责:
 *   处理路由
 *   根据不同的请求方法+请求路径设置具体的请求处理函数
 * 模块职责要单一,不要乱写
 * 我们划分模块的目的就是为了增强项目代码的可维护性
 * 提升开发效率
 */
var express = require('express')
// 1. 创建一个路由容器
var router = express.Router()

// 2. 把路由都挂载到 router 路由容器中
router.get('/', function (req, res) {
    res.end('hello world')
})
router.get('/about',function(){
    res.end('hello world')
})

// 3. 把 router 导出
module.exports = router

app.js

/**
 * app.js 入门模块
 * 职责:
 *   创建服务
 *   做一些服务相关配置
 *     模板引擎
 *     body-parser 解析表单 post 请求体
 *     提供静态资源服务
 *   挂载路由
 *   监听端口启动服务
 */

var express = require('express')
var router = require('./router')

var app = express()

// 把路由容器挂载到 app 服务中
app.use(router)

app.listen(3000, function () {
  console.log('running 3000...')
})

关于Javascript模块化问题

我们现在用的模块化是CMD吧 老师能不能给我们扩展一下AMD

  • PHP 中为什么就可以直接 requireinclude 因为 PHP 当初在设计的时候就加入了这个功能

  • PHP 这门语言天生就支持

  • 模块作用域

  • 可以使用 API 来进行文件与文件之间的依赖加载

  • 在 Node 这个环境中对 JavaScript 进行了特殊的模块化支持 CommonJS

  • JavaScript 天生不支持模块化

    • require
    • exports
    • Node.js 才有的
  • 在浏览器中也可以像在 Node 中的模块一样来进行编程

你可能感兴趣的:(Nodejs)