Node基础和内置模块
Node.js是什么
Node.js® 是一个基于 Chrome V8 引擎 的 JavaScript 运行时环境
(Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.)
名词解释
- chrome V8引擎: 在chrome浏览器用来解析和执行js代码的工具;
- 运行时:理解为一个容器,用来运行代码的环境;
Node.js是:一个采用chrome浏览器V8引擎来跑JS代码的环境。
浏览器是javascript的前端运行环境;
Node.js是javascript的后端运行环境;
- 前端运行环境能给我们带来什么能力?
- 展示和操作页面上的dom元素的能力
- 后端运行环境能给我们带来什么能力?
- 文件读写
- 开启web服务器
- 数据库操作
理解
- Node全名是Node.js(也叫Node, nodejs, node.js),但它不是一个js文件,而是一个软件
- Node.js是一个基于Chrome V8引擎的javascript的运行环境,在这个环境中可以执行js代码
- Node.js提供了大量的内置模块及丰富的第三方模块,能够让我们完成文件读写、Web服务器、操作数据库等功能
小结
nodejs是什么?
- 基于chrome v8引擎构建的一个js的后端运行环境
nodejs能给给我们提供什么样的能力?
- 文件读写
- 数据库操作能力
- web服务器能力
我们js可以运行在哪两种环境上了?
- 浏览器
- node.js
为什么前端要学习Node.js
-
在Node环境下,写js代码实现后端的功能(web服务器,写接口,读写数据库.....)
-
了解后端程序员的工作,增加职场竞争力
-
它是很多前端框架(vue, react,angular)的运行基础,学好Node.js有助于为后续框架的学习打下基础
Node.js的学习内容
它只是一个环境,不是一门语言(不需要学习新语言),我们要学习它的:
- 模块系统。能用不同的模块来完成不同的功能,例如:创建web服务器,写接口,连接操作数据库
- NPM包管理工具。通过npm来管理我们的需要的第三方包,为后续学习框架打下基础
下装安装Node.js
下载
英文官网
点击左侧的按钮(有LTS标识的那个),会立即下载。
版本说明:
- LTS: 长期稳定版(Long Term Support))。 项目开发建议使用长期稳定版
- Current: 最新版。最新版包含了一些新功能,如果想学习最新的功能,则可以使用该版本。最新版可能会有一些未知的bug。
中文网
安装
找到你下载的安装包,
-
双击安装文件开始安装(不同系统选择对应的安装文件)
-
傻瓜式安装,一路 'next' 即可
注意:
- 建议安装目录所使用
英文路径(不要安装在类似于 d:/软件/node) - 安装完成之后, 它不会在桌面出现快捷图标
测试是否安装成功
打开任意一个小黑窗,输入node -v能够看到Nodejs版本号即为安装成功。
- cmd窗口(window+R, --->运行-->录入cmd,回车)
-
powershell(window10操作系统)
任意位置,按下shift,右键
注意:安装nodejs之后,并不会像其它应用程序一样产生桌面的图标,或者是双击打开
nodejs安装注意一点
- 安装路径不能有中文或者其他特殊字符,否则nodejs运行不了
- win10 可以随意安装任何版本,但是win7只能安装12及其以下的版本
- 下载以往版本的链接:nodejs.org/zh-cn/downl…
在Node环境下运行js代码
前面的学习中,js代码都是在浏览器中运行的,现在开始学习nodejs后,我们有了第二个环境中可以运行js代码。下面来学习如何去运行。
步骤:
- 准备好要被执行的js文件
- 在命令行工具中写命令来运行这个文件
准备一个JS文件
请事先准备好一个js文件。例设这里的路径是:d:/src/index.js
具体内容是
var a = 1;
console.info(a + 2);
打开命令行工具,运行这个文件
格式
node 要执行的文件的路径
注意:node 的后面有一个空格
示例
例如:
node 01.js # 01.js就是当前目录下
node a/01.js # 01.js在目录a下面
- 最好是在当前文件所在目录下来运行这个js文件
打开命令行工具的方式
如何快速在某个目录下打开命令行工具(呢称:小黑窗), 有三种方式:
方法1:在资源管理器中按下shift,同时点击鼠标右键,可以选择在此处打开powershell/命令行窗口。
方法2:在资源管理器中,打开这个目录,直接在地址栏中写入 cmd
方法3: vscode中的右键,在命令行中运行
苹果电脑操作:
Node.js和浏览器端的区别
在浏览器端
js由三部分组成:ECMAScript + BOM + DOM
es6 ==> ECMAScript 6.0
在nodejs端
有ECMAScript + 内置模块(fs, http, path,.....)。在nodejs中是没有BOM、DOM、window,但使用ECMAScript是通用的
NodeJS中没有DOM,也没有BOM,也没有window对象。
ECMAScript
ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会,European Computer Manufacturers Association)通过ECMA-262标准化的脚本程序设计语言。是javascript语言的一个标准。它约定了:如何定义变量,函数,运算,数组,内置对象等等。
小结
相同点:都是可以运行js代码的容器,更严格一点说:都可以运行ECMAScript
不同点:各有不同的API: nodejs运行js时,不能写DOM,BOM,也不能用window对象了
学习常用的命令行下的命令及按键
考虑到后面一段时间我们将会与这个小窗
命令及键盘按键
node 空格 某个js文件 // 调用 node 程序,运行某个js文件
clear 或者 cls // 清空界面
ls/list/dir // 查看列表(list)
cd 目录名 // 进入到目录中去
cd .. // 返回上一级目录
cd \ // 直接回到根目录
Ctrl+C // 停止 Node 程序
输入部分文件名后按下 Tab 键 // 补全文件名 或 目录名, 多次tab会进行切换
↑ ↓ 上下箭头 // 切换历史输入
复制粘贴
在小黑窗中复制内容:选中内容,再点鼠标右键
把粘贴板中的内容复制到小黑窗: 点鼠标右键
nodejs中的模块分类
每个模块都是一个独立的文件。每个模块都可以完成特定的功能,我们需要时就去引入它们,并调用。
nodejs模块的分类:
- 核心模块
- 就是nodejs自带的模块,在安装完nodejs之后,就可以任意使用啦。相当于学习js时使用的Array对象。
- 源代码
- 自定义模块
- 程序员自己写的模块。相当于我们在学习js时的自定义函数。
- 第三方模块
- 其他程序员写好的模块。nodejs生态提供了一个专门的工具npm来管理第三方模块,后面我们会专门讲到。
- 相当于别人写好的函数或者库。例如我们前面学习的JQuery库,artTemplate等。
学习核心模块fs
fs是file system的简写
文档
理解核心模块
核心模块就是 Node 内置的模块,需要通过唯一的标识名称来进行获取。每一个核心模块基本上都是暴露了一个对象,里面包含一些方法供我们使用。一般在加载核心模块的时候,变量(或常量)的起名最好就和核心模块的标识名同名。
例如:const fs = require('fs')
const path = require('path')
console.log(path)
fs模块
fs模块(fs是 FileSystem的简写)是Node.js用来进行文件操作的模块,它属于核心模块。你引入之后就可以直接使用了。
核心模块的使用步骤:
-
引入模块
// 引入模块 const fs = require('fs'); // 可以使用var、let,但是建议使用const,因为我们不希望它被改变。 // 名字不必大写成FS,一般也就叫fs这个名字。 -
调用api实现自己的要求
fs.apiName()
fs模块中操作文件(或者文件夹)的方法,大多都提供了两种选择:
- 同步版本的
- 异步版本的
fs-readFile-异步格式
目标: 写代码去读出文件内容,并打印在屏幕上
格式
fs.readFile('文件路径'[,可选选项], function (err, data) {
if (err) throw err;
console.log(data);
});
说明:
-
参数1:文件路径。 相对路径和绝对路径均可。
-
参数2: 配置项,可选参数,可不写。主要用来配置字符集。一般可设置为'utf8',如果不设置该参数,文件内容会Buffer形式返回。
-
参数3: 读取完成后触发的回调函数。这个回调函数在读完文件后自动被nodejs自动调用,并传入 err 和 data
-
如读取成功,回调函数中的两个参数分别是:
-
err: null
-
data: 文件内容,如果不设置参数2,则返回二进制数据。可以使用 toString() 方法将二进制数据
转为正常字符串
-
-
如读取失败,回调函数中的两个参数分别是:
- err: 错误对象
- data: undefined
-
示例1:读文本内容
const fs = require("fs")
fs.readFile('文件路径',(err, data) => {
if (err) throw err;
console.log(data);
});
上面的data将会是一个Buffer对象类似于数组,它的元素为16进制的两位数,它表示读出来的内容在计算机中的二进制格式。它有一个toString()方法,可以用来把内容以UTF-8的格式转成字符串。
示例2:读文本内容-指定编码格式
const fs = require("fs")
fs.readFile('文件路径', "utf8", (err, data) => {
if (err) throw err;
console.log(data);
});
示例3:读入图片文件
确保你的目录下有图片
const fs = require("fs")
fs.readFile('./img/1.jpg', (err, data) => {
if (err) throw err;
console.log(data);
});
并不是它所有的文件都应该转成字符串的
示例4:体会异步的效果
异步的
const fs = require("fs")
console.log(1)
fs.readFile('文件路径', "utf8", (err, data) => {
if (err) throw err;
console.log(data);
});
console.log(2)
并不是它所有的文件都应该转成字符串的
fs-readFileSync-同步格式
格式
const fs = require("fs")
let rs = fs.readFileSync('文件路径',"utf8");
console.log(rs)
- api的名字后面有Sync(async是异步的,sync表示同步的)
- 不是通过回调函数来获取值,而是像一个普通的函数调用一样,直接获取返回值
捕获同步格式中的错误对象
如果读成功,则会获取读出来的数据,如果失败了,则会中间后续所有的代码执行。
console.log(1)
let res = fs.readFileSync('errorPath.js')
console.log(res)
console.log(2)
解决方案:用try. catch 结构
try {
const fs = require("fs")
let rs = fs.readFileSync('文件路径',"utf8");
console.log(rs)
} catch(err) {
console.log(err)
}
fs-writeFile-文件覆盖写入
覆盖写入 writeFile
功能:向指定文件中写入字符串, 如果没有该文件则尝试创建该文件。它是覆盖写入:会把文件中的内容全部删除,再填入新的内容。
格式:
fs.writeFile(pathName, content, option, (err)=>{});
// 参数1: 要写入的文件路径 --- 相对路径和绝对路径均可,推荐使用绝对路径
// 参数2: 要写入文件的内容
// 参数3: 配置项,设置写入的字符集,默认utf-8
// 参数4: 写入完成后触发的回调函数,有一个参数 --- err (错误对象)
示例1: 写入.txt文件
const fs = require('fs')
fs.writeFile('./a.txt', 'hello world! \n 换一行', err => {
if (err) {
console.info(err)
throw err
}
})
示例2: 写入json
稍微把问题提升一下,问: 如何把数据写入文件中?
const fs = require('fs')
const data = [{name: '小王', age: 20}]
fs.writeFile('./a.txt', data, err => {
if (err) {
console.info(err)
throw err
}
})
上面的写法会出错: data不是一个字符串或者是Buffer
const fs = require('fs')
const data = [{name: '小王', age: 20}]
fs.writeFile('./a.txt', JSON.stringify(data), err => {
if (err) {
console.info(err)
throw err
}
})
附:fs模块中的常用方法
| API | 作用 | 备注 |
|---|---|---|
| fs.access(path, callback) | 判断路径是否存在 | |
| fs.appendFile(file, data, callback) | 向文件中追加内容 | |
| fs.copyFile(src, callback) | 复制文件 | |
| fs.mkdir(path, callback) | 创建目录 | |
| fs.readDir(path, callback) | 读取目录列表 | |
| fs.rename(oldPath, newPath, callback) | 重命名文件/目录 | |
| fs.rmdir(path, callback) | 删除目录 | 只能删除空目录 |
| fs.stat(path, callback) | 获取文件/目录信息 | |
| fs.unlink(path, callback) | 删除文件 | |
| fs.watch(filename[, options][, listener]) | 监视文件/目录 | |
| fs.watchFile(filename[, options], listener) | 监视文件 | |
| fs.existsSync(absolutePath) | 判断路径是否存在 |
web服务器
http是nodejs的核心模块,它能让我们能够通过简单的代码创建一个Web服务器,处理http请求。
服务器相关概念
服务器与客户端
提供网络服务的一台机器,通过在自己的电脑上安装特殊的软件(或者是运行某段特殊的代码)来提供服务。
服务器 = 电脑 + 能给其它电脑提供服务的软件
客户端与服务器:提供服务的是服务器,享受服务的是客户端
服务器的类型
根据服务不同,服务器的类型也不同:
-
web服务器。安装apache, tomcat, iis, 或者在nodejs环境写代码 来提供:图片浏览,新闻浏览....等服务的服务器。
-
ftp服务器。安装serv-U软件,为其它电脑提供文件下载,共享服务。
-
数据库服务器。安装mysql软件,为其它电脑提供数据库服务。
....
web服务器:
- 用户通过浏览器来享受web服务器提供的服务
- 我们用url地址来访问某个web服务器上的资源
- 浏览器端发起请求,web服务器收到请求后,响应这个请求,并将处理结果返回给浏览器
- 浏览器端与web服务器是通过http(或者是https)协议来进行请求和响应的
ip地址
全称:Internet Protocol Address。
作用:标识一个网络设备(计算机、手机、电视)在某一个具体的网络当中的地址。要访问某个电脑上的资源,先要找到它的ip。
分类:ipV4 ipV6
格式:[0-255].[0-255].[0-255].[0-255] 即为四个 0-255 的数字组成(以ip4为例)。在同一个网络中,计算机的IP是不允许相同的,都是唯一的。
127.0.0.1 特指本机ip地址。(只能自己访问)
192.168.106.2 (这种地址叫做局域网地址)(可以在当前局域网中的所有电脑都可以访问)
这个 http://220.181.38.149/ (外网IP)会指向哪里?(在整个互联网上都可以访问)
*域名
域名:ip地址的别名,由于ip地址不好记忆,我就给它们取个好记的别名。localhost这个域名特指127.0.0.1这个地址。
域名解析系统:把域名翻译成Ip地址的系统。
端口
一个IP地址的端口可以有65536个,范围是从[0,65535])。不同的端口被不同的软件占用,以提供不同的服务。
一台电脑可以通过安装多个服务器端软件来提供服务,比如Web服务、FTP服务、SMTP服务等。显然,仅仅通过ip地址是无法区分不同的服务的,这里就需要用到 “IP地址+端口号”来区分不同的服务。
理解
如果理解IP地址(一台服务器)是一栋大商场,端口就是商场中的商铺的编号。
如果理解IP地址(一台服务器)是公司的前台电话,端口就是公司中各个部门的分机号。
-
服务器要提供服务必须要通过指定的端口
-
服务器与客户端都需要通过端口要进行通信
-
端口是可以编程分配
-
有一些端口号是被预定了的。
- http: 80
- https:443
- mysql:3306
通过netstat -a -n -o 查看端口使用情况
协议
制定客户端与服务器之间的通讯规则。不同的协议的作用也不同。
http协议:
- HTTP(HyperText Transfer Protocol) 超文本传输协议。
- 协议双方: 浏览器与web服务器都要遵守的协议
- 请求通常是由像浏览器发起的
- HTTP 协议中明确规定了
请求数据和响应数据的格式(报文)- 浏览器 请求 资源 要遵守 http 协议: 请求报文(请求行,请求头,请求体)
- 服务器 返回 资源 要遵守 http 协议: 响应报文(响应行,响应头,响应体)
用http 模块写一个简单的web服务器
要点
-
引入http核心模块
-
使用createServer来创建服务
-
使用listen来启动服务
操作
第一步:新建一个文件,名为 d:/src/http.js( 文件名及路径名可以自行设置,建议均不使用中文字符), 内容如下
// 创建我们第一个web服务器
// 1.0 导入http模块
const http = require('http');
// 2.0 创建一个服务
let server = http.createServer((req, res) => {
console.log('接收到了客户端请求');
res.end('OK');
})
// 3.0 监听8001端口并启动web服务器等待客户端请求
server.listen(8001, () => {
console.log('web服务器准备就绪:127.0.0.1:8001可以访问');
})
第二步:运行js代码。
在小黑窗中进入到d盘根目录,键入命令 node http.js,此时会弹出一个小黑窗,不要关。
第三步:本地验收
打开一个浏览器页面,输入'http://localhost:8001', 观察效果:
- 浏览器中的效果
- 小黑窗中的效果
第四步:共享地址
把localhost改成你自己电脑的ip地址,再把这个路径发你的同学(同一个局域网)来访问。
第五步:停止服务
ctrl + c
如果不能访问,有可能你需要手动关闭你自己计算机的防火墙。
💥注意:浏览器输入的地址,冒号不能是中文格式的
http://127.0.0.1:8001/ 错误的写法
工作原理
使用http模块在本机上创建一个虚拟服务器,它来接收浏览器的请求,并给出响应。
注意:
- 小黑窗不要关,它就是服务器,它不会有主动行为(看起来没有任何变化),它在时刻等待客户端的访问。
代码解析
-
引入核心模块,得到的http是一个对象。
-
http.createServer方法创建一个http服务。参数是一个回调函数:当有http请求进来时,它会自动被调用。
请求一次,它就被调用一次。- 第一个参数:
客户端的请求。 - 第二个参数:
设置对本次请求的响应。- res.end() :设置响应体,结束请求。
- 第一个参数:
-
server.listen() 用来监听端口。
- 格式:server.listen(端口号,[回调函数])。回调是可选的。
- 说明:
- 如果监听成功,则回调函数会执行一次。
- 如果不成功(例如端口被占用),会报错。
修改代码后要重启
更改res.end()的内容,重启后,再次观察。
- 停止服务: 在小黑窗中按下ctrl+c 停止服务。
- 重启服务:就是重新运行程序(按下向上的箭头,再回车)。
修改了服务器的代码要重启。
理解请求和响应
请求
当web服务器就绪之后,如果没有客户端来访问它,它也是不会有任何效果的。也就是说回调函数不会执行。
而每一次的请求,都会导致回调函数要执行一次。
服务器的响应内容格式
res.end()的格式只能是buffer或者是String
不同URL返回不同的内容-认识URL
全称
Uniform Resource Locator,统一资源定位符。
作用
定位资源(css,html,js,png, avi,接口......)。
格式
协议://主机地址[:端口]/路径?查询字符串#锚点
- 协议: http 或者是 https
- 主机地址: IP地址 或者 域名
- 端口号
- http请求,默认端口80(可以省略)
- https请求,默认端口443(可以省略)
- MySQL默认端口3306
- 路径
- 服务器文件夹上的资源。(.html/.css/.images/.js/接口)
- 参数(查询字符串)
- ? 后面的部分,是键值对的形式
- 锚点
- 网页内部的锚点链接
经典用法:访问文件时传递参数。
// index.html
<a href='detail.html?id=1'>新闻1</a>
<a href='detail.html?id=2'>新闻2</a>
上面的两个地址在指向detail.html时,分别传入了不同的参数
// detail.html
<script>
// 1. 获取id
// 2. 根据id值去获取这个新闻的详情
</script>
不同的URL返回不同的内容-问题分析
思路:在服务器端收到客户端发的请求之后,分析url是什么,然后分别对应处理
不同的URL返回不同的内容-req.url
req.url属性
首先就需要知道浏览器请求的url是什么。
涉及到和请求相关的信息,都是通过请求响应处理函数的第一个参数完成的。
代码示例
const http = require('http');
// 创建服务
const server = http.createServer(function(req, res) {
console.log(req.url)
res.end(req.url)
});
// 启动服务
server.listen(8081, function() {
console.log('success');
});
req.url用来获取本次请求的资源地址。在请求行中可以看到这一点。
小结
| 序号 | 浏览器中的url | 服务器中的req.url |
|---|---|---|
| 1 | http://localhost:8080 | / |
| 2 | http://localhost:8080/a.html | /a.html |
| 3 | http://localhost:8080/js/jquery.js | /js/jquery.js |
| 4 | http://localhost:8080/1.jpg | /1.jpg |
| 5 | http://localhost:8080/api?a=1&b=2 | /api?a=1&b=2 |
req.url一定是以/开头的。
在现代浏览器中,它们会自动去请求服务器上的favicon.ico
不同的URL返回不同的内容-读取文件内容并返回
目录结构
|-index.html
|-style.css
|-01.png
|-js/jquery.js
|-server.js
请求与响应的对应的关系
| 用户的请求地址 | 服务器的动作 |
|---|---|
| http://localhost:8000 | 读出index.html的内容并返回 |
| http://localhost:8000/index.html | 读出index.html的内容并返回 |
| http://localhost:8000/style.css | 读出style.css的内容并返回 |
| http://localhost:8000/01.png | 读出01.png的内容并返回 |
| http://localhost:8000/js/jquery.js | 读出jquery.js的内容并返回 |
| http://localhost:8000/xxxx 或者不是上面的地址 | 返回404 |
示例代码
// 1.0 导包
const http = require('http');
const fs = require('fs');
const path = require('path');
// 2.0 创建服务器对象并且设置好回调函数
let server = http.createServer((req, res) => {
// 1.0 根据不同路径响应不同的数据内容
// 如果浏览器请求的是 /index.html 这个路径,那么就读取这个index.html中的内容通过res.end()响应即可
// 如果浏览器请求的是 /style.css 这个路径,那么久读取style.css通过res.end()响应即可
// 获取路径
let urlPath = req.url;
console.log('urlPath==>', urlPath);
if (urlPath === '/index.html') {
// 读取index.html文件的内容响应回去
let htmlString = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');
// 防止低版本浏览器不能自动解析内容,则手动设置
res.setHeader('content-type', 'text/html;charset:utf8');
// res.end() 响应
res.end(htmlString)
} else if (urlPath === '/style.css') {
// 读取到了当前目录下的style.css的内容
let styleString = fs.readFileSync(path.join(__dirname, 'style.css'), 'utf8');
// 防止低版本浏览器不能自动解析内容,则手动设置
res.setHeader('content-type', 'text/css;charset:utf8');
// 响应
res.end(styleString);
} else {
// 资源找不到,状态码应该设置为404,同时响应一段文本告诉用户资源找不到
res.statusCode = 404;
res.setHeader('content-type', 'text/html;charset=utf8')
res.end('您要的资源去了火星,找不到咯');
}
})
// 3.0 启动服务器
server.listen(8000, () => {
console.log('服务器已经在8000端口准备就绪,等待客户端访问');
})
综合使用fs, path, __dirname
注意
-
url地址与服务器上的文件地址并不是一一对应的。url的作用是确定用户要访问的资源的位置,在地址栏中输入回车之后,这个请求会到web服务器中来,然后由web服务器来决定此时返回什么数据给用户。但是,我们能够根据url来推测服务器会返回什么信息吗?
url:http://nodejs.cn/api/querystring.html 请求一个页面,名是querystring.html url:http://oa.itcast.cn/seeyon/main.do?method=main url:https://mail.qq.com/cgi-bin/frame_html?sid=aLqnlljMxF54DgtW&r=d281ced83329f34caae9786fcb5d4934显然,不能,你能从服务器上获得什么,完全是由服务器决定的。
-
如果用户请求的是静态资源(静态资源指的是html文件中链接的外部资源,如.html, css、js、image文件等等),服务器的处理方法就是:读出文件内容,返回给用户。
不同的URL返回不同的内容-设置content-type
content-type的作用
在http协议中,content-type用来告诉对方本次传输的数据的类型是什么。
-
在请求头中设置content-type来告诉服务器,本次请求携带的数据是什么类型的
-
在响应头中设置content-type来告诉服务器,本次返回的数据是什么类型的
通过使用res对象中的setHeader方法,我们可以设置content-type这个响应头。这个响应头的作用是告诉浏览器,本次响应的内容是什么格式的内容,以方便浏览器进行处理。
常见的几种文件类型及content-type
- .html:
res.setHeader('content-type', 'text/html;charset=utf8') - .css:
res.setHeader('content-type', 'text/css;charset=utf8') - .js:
res.setHeader('content-type', 'application/javascript') - .png:
res.setHeader('content-type', 'image/png') - json数据:
res.setHeader('content-type', 'application/json;charset=utf-8')
其它类型,参考这里:developer.mozilla.org/en-US/docs/…
如果读出来的是.html的文件,但是content-type设置为了css。则浏览器将不会当作是html页面来渲染了。
不同的URL返回不同的内容-设置statusCode
正确使用statusCode
res.statusCode = 301;
// res.setHeader('location','http://www.qq.com')
res.statusCode = 404
res.statusCode = 500
res.end()
处理.html文件中的二次请求
二次请求
从服务器获取html文件之后,如果这个html文件中还引用了其它的外部资源(图片,样式文件等),则浏览器会重新再发请求。
假设在index.html中还引入了 style.css 1.png 或者 .js文件,则:浏览器请求localhost:8000/index.html之后,得到的从服务器反馈的内容,解析的过程中还发现有外部的资源,所以浏览器会再次发出第二次请求,再去请求相应的资源。
思路
一个最朴素的想法是枚举不同的请求来返回不同的文件。
代码
<!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>Document</title>
<style>
h1{
text-align: center;
}
</style>
+ <link rel="stylesheet" href="./style.css">
</head>
<body>
<h1>index.html</h1>
+ <img src="./01.png" alt="">
</body>
</html>
上面的+号只是表示这里的代码需要修改。
const http = require('http');
const fs = require('fs');
const path = require('path');
//创建服务器
const app = http.createServer((req, res) => {
if (req.url === '/index.html') {
let htmlString = fs.readFileSync(path.join(__dirname, 'index.html'));
res.end(htmlString);
}
else if (req.url === '/style.css') {
let cssString = fs.readFileSync(path.join(__dirname, 'style.css'));
res.setHeader('content-type', 'text/css');
res.end(cssString);
} else if (req.url === '/1.png') {
let pngString = fs.readFileSync(path.join(__dirname, '/1.png'));
res.end(pngString);
} else {
res.setHeader('content-type', 'text/html;charset=utf-8');
res.statusCode = 404;
res.end('<h2>可惜了, 找不到你要的资源' + req.url + '</h2>');
}
});
//启动服务器,监听8082端口
app.listen(8082, () => {
console.log('8082端口启动');
});
作业
const http = require('http');
const fs = require('fs');
const path = require('path');
let server = http.createServer((req, res) => {
let urlPath = req.url;
if (urlPath === '/index.html') {
let htmlString = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');
// 防止低版本浏览器不能自动解析内容,则手动设置
res.setHeader('content-type', 'text/html;charset=utf8');
// res.end() 响应
res.end(htmlString)
} else if (urlPath === '/style.css') { // 读取css文件
// 读取到了当前目录下的style.css的内容
let styleString = fs.readFileSync(path.join(__dirname, 'style.css'), 'utf8');
// 防止低版本浏览器不能自动解析内容,则手动设置
res.setHeader('content-type', 'text/css;charset=utf8');
// 响应
res.end(styleString);
} else if (urlPath === '/images/bg.jpg') {
// 读取图片内容 ,img 格式:
let img = fs.readFileSync(path.join(__dirname, '/images/bg.jpg'));
res.setHeader('content-type', 'image/jpg');
// 将img响应回去
res.end(img);
}
else if (urlPath === '/images/fm.jpg') {
// 读取图片内容 ,img 格式:
let img = fs.readFileSync(path.join(__dirname, '/images/fm.jpg'));
res.setHeader('content-type', 'image/jpg');
// 将img响应回去
res.end(img);
}
else {
res.statusCode = 404;
res.setHeader('content-type', 'text/html;charset=utf8')
res.end('您要的资源去了火星,找不到咯');
}
})
server.listen(8002, () => {
console.log('启动');
})
批量处理请求
问题分析
由于我们无法事先得知一个.html文件中会引用多少个静态资源(.png, .css, .js....),所以,我们不能像处理某个页面一样去处理它们。
我们的解决办法是:
- 把所有的静态资源(.html,.png,.css,.js)全放在一个指定的目录里;
- 收到用户的请求之后,去指定的目录下去找对应的文件
- 找到,把内容读出来返回给用户。
- 找不到,报404。
目录如下:
|-public
|-public/index.html
|-public/stye.css
|-public/01.png
|-server.js
在上面的目录结构中,我们把所有的静态资源全放在public下面,然后使用server.js来启动web服务器。
参考代码
// 当收到用户的请求之后
// 在public下去读这个文件
// 读到:返回
// 读不到:返回404
const http = require('http')
const path = require('path')
const fs = require('fs')
// 放一个对象,其中保存所有的 后缀名与content-type的一一对应关系
const mapExtToContentType = {
'.html': 'text/html;charset=utf8',
'.jpg': 'image/jpg',
'.css': 'text/css;charset=utf8',
'.js': 'application/javascript',
'.ico': 'image/ico'
}
const server = http.createServer((req, res) => {
const { url } = req
// 获取后缀名
const ext = path.extname(url)
console.log(url, ext)
// 1. 拼接地址
const filePath = path.join(__dirname, 'public', url)
// 2. 读资源文件
try{
const content = fs.readFileSync(filePath)
console.log(content)
// 根据不同的后缀名,补充不同的content-type
// .html ---> text/html;charset=utf8
// .jpg ---> image/jpg
if(mapExtToContentType[ext]) {
res.setHeader('content-type', mapExtToContentType[ext])
}
// mapExtToContentType[ext] && res.setHeader('content-type', mapExtToContentType[ext])
res.end(content)
} catch (err) {
console.log(err)
res.statusCode = 404
res.end('404')
}
})
server.listen(8001, () => {
console.log('你的服务器在8001就绪');
})
理解静态资源与接口的区别
服务器上有很多的资源,每个资源都有自己的url。客户端浏览器想要访问某个资源就要向服务器发起对应的请求。
资源的分类
- 静态资源
- 它们一般表现为一个一个的文件。例如index.html, style.css, index.js, mp4, .png....。
- 处理请求静态资源时,服务器一般就直接读出资源的内容,再返回给客户端浏览器
- 动态资源:接口
- 它们不是以某个具体的文件存在的,而是服务器上的一段代码,访问接口时,服务器会执行这段代码,然后把代码的执行结果返回给客户端浏览器。
发送请求的途径
- 在地址栏中直接访问这个url
- 通过某个a标签进行进行跳转
- 通过表单进行提交
- 通过ajax技术访问这个url
发送请求的类型
- get:在地址栏中直接访问这个url就是get方式。对于静态资源,我们通过的处理方式就是get请求。
- post: 通过表单提交,可以设置form的method为post
- delete
- put
- patch
- options
- ......
写一个不带任何参数的get类型接口
目录结构
|-db
|---data.json # 数据
|-server.js # 你的代码
目标
目标
在server.js中写代码,提供一个名为getList的接口(http://localhost:8083/getList) 它以json字符串格式返回db/data.json的内容。并通过使用postman软件进行测试。
参考代码
// server.js
const http = require('http');
const app = http.createServer((req, res) => {
if (req.url === '/getmsg' && req.method=== 'GET') {
const filePath = path.join(__dirname, 'db', 'data.json')
let content = fs.readFileSync(filePath, 'utf8')
res.end(content);
} else {
res.end('error');
}
});
app.listen(8083, () => {
console.log(8083);
});
说明:
- 注意:类型
- req.method 可以判断请求的类型
- res.end()的参数只能是字符串(或者是buffer),而不能是对象
postman的使用说明
postman的作用
前端人员一定要会用它!它用来去测试接口。
下载地址+汉化:gitee.com/hlmd/Postma…
带参数的get请求-获取查询字符串中的数据
目标
提供一个名为getList的接口(http://localhost:8083/getList?name=xxxx) 它以json字符串格式返回db/data.json中name为xxxx的数据
使用postman软件进行测试。
目录结构
|-db
|---data.json # 数据
|-server.js # 你的代码
参考代码
// server.js
const http = require('http');
const app = http.createServer((req, res) => {
const [url, queryStr] = req.url.split('?')
if (url === '/getmsg' && req.method=== 'GET') {
// 从queryStr中拆解出来name
// ....
} else {
res.end('error');
}
});
app.listen(8083, () => {
console.log(8083);
});
思路:
1、首先测试的请求的地址是: http://127.0.0.1:8004/getHeroSkin?name=后裔
为了实现上面的测试地址能得到正确结果,需要编写
- 三个步骤开启nodejs服务器
- 在第二步的回调函数中编写逻辑
- 解析出url中的查询参数字符串 let [pathname,query] = req.url.split('?')
- 利用 let urlsearch = new URLSearchParams(query) 实例化参数对象
- 利用 urlsearch.get() 方法获取参数值
- 读取data.json文件内容,使用JSON.parse() 转换成js数组
- 使用filter方法结合2.3中获取到的参数过滤出需要的数据通过res.end()响应回去
nodejs中的querystring模块
用来对url中的查询字符串这部分进行处理。nodejs中提供了querystring这个核心模块来帮助我们处理这个需求。
示例
const qs= require('querystring');
let obj = qs.parse('id=18&name=zs');
console.log(obj) // {id:18, name:"zs"}
URLSearchParams 解析参数
URLSearchParams可以将查询字符串解析成一个对象,通过get方法获取到指定参数值
参考网址:developer.mozilla.org/zh-CN/docs/…
示例
let query = 'name=后裔'
// 通过urlSearchParams解析参数值
let urlSearch = new URLSearchParams(query);
// 通过get方法获取到查询字符串name参数的值
let namevalue = urlSearch.get('name');
post接口
目录结构
|-db
|---data.json # 数据 [{"name": "小李"},{"name": "小张"}]
|-server.js # 你的代码
目标
在server.js中写代码,提供一个名为add的接口(http://localhost:8083/add) 它以post的方式请求接口,并传入name值,把数据保存到db/data.json 中去。
使用postman软件进行测试。
预备知识
post类型与get类型的接口区别较大,主要在两个方面:
-
类型不同: 可以通过 req.method 来获取(GET,POST)
-
传参不同
- get请求参数在
请求行中(附加在url后面),内容比较少。 - post请求参数在
请求体中。内容比较大(上传图片,上传文件....)。
- get请求参数在
对于获取post参数就相对复杂一些。它的特点是:
- 参数在请求体中发给后端
- 后端是一段一段接收数据的,并不像get在请求行中传递的数据:直接写在url中的查询字符串内,可以立即通过req.url来解析出来。
- 在接收参数的过程中,会涉及req对象的两个事件
data,end。- data事件,每次收到一部分参数数据就会触发一次这个事件。
- end事件,全部的参数数据接收完成之后会执行一次。
基本流程
基本流程是:
- 定义一个空容器result来装参数。
- 在req对象上监听data事件。这个事件触发一次,就把当次收到的数据向result中填充一些。
- 在req对象上监听end事件。这个事件触发时,就表示整个参数数据接收完成,此时取出容器result中的内容,进行解析,以取出参数。
参考代码
// 提供一个名为getList的接口(http://localhost:8083/getList?name=xxxx),
// 它以json字符串格式返回`db/data.json`中name为xxxx的数据
// 1. 引用
const http = require('http')
const qs = require('querystring')
const fs = require('fs')
const path = require('path')
// console.log('qs', qs)
// 2. 创建服务
const server = http.createServer((req, res) => {
let result = ''
// 添加事件监听
req.on('data', (chunk) => {
console.log('收到一小段数据,格式是buffer', chunk)
// 把当前收到这一段数据,收集起来
result += chunk
})
req.on('end', () => {
console.log('只会执行一次回调,本次数据全部接收完成,数据如下:')
})
res.end('post接口')
})
// 3. 启动
server.listen(8083, () => { console.log('你的服务器已经就绪了')})
在发post请求时,传递的数据会在请求体中,它也是字符串格式,并且是一点一点上传到web服务器的(是积小成多,而不是一蹴而就)每上传一部分就会触发data事件,而最后全部上传完成之后,会触发end事件。
下面是一个示例代码,用来模拟使用post请求发送大量的数据,以观察req.on('data', chunk => {})多次触发的现象。
var xhr =new XMLHttpRequest();
xhr.open('post','http://localhost:8083/add');
xhr.setRequestHeader('content-type','application/x-www-form-urlencoded');
xhr.send("name="+"imissyou".repeat(100000));
模块化与包管理工具
模块化
-
远古时代: 立即执行函数表达式(IIFE:Immediately Invoked Function Expressions)---自调用。
// a.js (function() { var obj = xxxxx // 你自己的模块 window.obj = obj // 挂到window暴露出来 })(window)// a1.js (function() { var obj1 = xxxxx // 你自己的模块 window.obj.新功能 = obj1 // 挂到window暴露出来 })(window) -
第三方的库 - sea.js , require.js
曲线救图 --- 借助第三方库,来实现模块化
a.js 定义模块 b.js 根据sea.js的语法去引入a.js的模块 -
工具: webpack----在node.js的环境中运行,它可以支持nodejs的模块化(或者是es6模块化),再把代码打包成es5的代码
-
es6中直接有模块化/ nodejs中也有
模块化: 对前端的意义,如果没有模块化,前端就不可能做大!
模块化-理解模块化
提问:在浏览器中,我们写代码时,
- index.html # 主页的页面
- index.js # 主页需要用到的js代码
---- getData()
- tool.js # 为整个项目提供公共方法
---- doSomething(){ }
对于如上的代码结构,如何让index.js中的getData去使用tool.js中的doSomething()函数?
- 上面的代码写法,会有什么问题?
- 为什么不能直接让index.js来直接使用tool.js的函数,而要通过index.html来统一管理一下呢?
原因很简单:es5中不支持模块化(不能直接在一个js文件中去引用另一个js文件的方法,必须要通过第三个文件.html来引入)
模块化的定义
一个js文件可以引入其他的js文件,能使用引入的js文件的中的变量、数据,这种特性就称为模块化。
使用模块化开发可以很好的解决变量、函数名冲突问题,也能灵活的解决文件依赖问题。
模块化的发展
-
以前
es5不支持模块化,让前端人员很为难。为了让支持模块化,我们一般会借用第三方库来实现:
- sea.js. www.zhangxinxu.com/sp/seajs/
- require.js. requirejs.org/
-
现在
- es6原生语法也支持模块化(并不表示浏览器也直接支持模块化 --- 需要单独设置一下)
- Nodejs内部也支持模块化(与es6的模块化有些不同之处),具体的语法在后面来介绍。
nodejs中的模块分类-复习
每个模块都是一个独立的js文件。每个模块都可以完成特定的功能,我们需要时就去引入它们,并调用。不需要时也不需要管它。(理解于浏览器的js中的Math对象)
nodejs模块的分类
- 核心模块
- 就是nodejs自带的模块,在安装完nodejs之后,就可以随意使用啦。相当于学习js时使用的Array对象。
- 例:fs, http, querystring, path
- 全部模块的源代码 github.com/nodejs/node…
- 自定义模块
- 程序员自己写的模块。就相当于我们在学习js时的自定义函数。
- 第三方模块
- 其他程序员写好的模块。nodejs生态提供了一个专门的工具npm来管理第三方模块,后面我们会专门讲到。
- 相当于别人写好的函数或者库。例如我们前面学习的JQuery库,arttemplate等。
自定义模块
程序员自己写的模块
我们对代码的封装是以模块(一个独立的.js文件)为单位进行的。一般的做法是实现好某一个功能之后,封装成一个模块,然后在其它文件中使用这个模块。
类比于js自定义函数,自定义模块的使用场景是:
- 代码需要在项目重用
- 代码需要提供给他人使用
- 代码虽然不需要重用,但封装成模块有利于优化代码结构,方便后期维护与扩展
一共有两步:
-
定义模块。就是创建一个js文件,有导出。
-
使用模块。在需要使用的地方去导入模块文件。
定义模块
所谓定义模块,就是新建一个js文件。文件取名时,要注意一下:
- 一般会用模块名给它命名。类比于核心模块,例如,你的模块叫myModule,则这个js文件最好叫myModule.js
- 不要与核心模块的名字重复了。就像我们定义变量不要与核心关键字重名,你自己定义的模块也不要叫fs.js,因为nodejs有一个核心模块就叫fs.js。
- 要记得导出模块
示例:我们定义一个模块,文件名是tools.js。 在js文件内我们定义一些函数,变量,它们会根据我们的业务要求做一些不同的工作。最后根据情况导出这些函数,变量。
//tools.js
const relativeTime = (oldTime) => {
const t = new Date(oldTime)
// Date.now():现在的时间戳(毫秒)
// t.getTime():旧时间的时间戳(毫秒)
const diff = Date.now() - t.getTime() // 相隔多少毫秒
// Math.floor 向下取整: 1.7年 ---> 1年前
const year = Math.floor(diff / (1000 * 3600 * 24 * 365))
if (year) {
return `${year}年前`
}
const month = Math.floor(diff / (1000 * 3600 * 24 * 30))
if (month) {
return `${month}月前`
}
const day = Math.floor(diff / (1000 * 3600 * 24))
if (day) {
return `${day}天前`
}
const hour = Math.floor(diff / (1000 * 3600))
if (hour) {
return `${hour}小时前`
}
const minute = Math.floor(diff / (1000 * 60))
if (minute) {
return `${minute}分钟前`
} else {
return '刚刚'
}
}
const formatDate = (dateTime) => {
// console.log(date)
// date = new Date();
const date = new Date(dateTime) // 转换成Data();
console.log(date)
var y = date.getFullYear()
console.log(y)
var m = date.getMonth() + 1
m = m < 10 ? '0' + m : m
var d = date.getDate()
d = d < 10 ? ('0' + d) : d
return y + '-' + m + '-' + d
}
// 通过module.exports来导出模块
module.exports = {
formatDate,
relativeTime
};
导出模块
在文件尾部,使用module.exports来导出模块。
// 格式
module.exports = 要导出的内容
注意:
- module.exports 是固定写法,一般放在文件的最末尾,也只用一次。
- module.exports表示当前模块要暴露给其它模块的功能。
- 它可以导出对象,数组,函数等等类型。为了方便组织代码,导出对象的情况比较多。
- 不必要导出所有函数,对象,数组等。那些没有导出的部分就相当于这个模块的内部变量了。
导入模块
完成了模块定义之后,我们就可以在另一个文件中使用这个模块了。
基本步骤是
1. 导入模块;
2. 先打出来看看;
格式:
const 模块名 = require('./模块路径')
当一个模块被成功引入之后,就可以类比使用核心模块的过程一样去使用它们了。
// index.js
// 1. 导入模块
// 注意这里使用的是相对路径。可省略.js.
const myMath = require('./myMath');
// 在使用之前请先打印出来看看
console.log(myMath);
// 2. 使用模块中的方法
let rs = myMath.add(23,45);
console.log(rs)
注意:
- 使用require语句引入你定义好的模块
- 这里必须使用
相对路径的格式去引入自定义模块。"./" 也不能省略。
导出模块的两种方式
- exports
- module.exports
// 定义方法,常量
const myPI = 3.14
function add(a,b) {return a + b ;}
// 导出,两种方法任意都可以
// 方法一:
exports.myPI = myPI
exports.add = add
// 方法二:
module.exports.myPI = myPI
module.exports.add = add
// 方法二(变形)
module.exports = {
myPI,
add
}
在阅读其它人的代码时,可能会遇到这两种不同的写法。所以我们还是有必要了解一下的。
cookie模块,body-parser模块,arry-flatten模块中的导出均采用不同的方式。
两个对象的关系
- 初始exports和module.exports是指向同一块内存区域,其内容都是一个空对象。(exports是module.exports的别名)即:
exports === module.exports // 输出是 true
所以下面两种写法的效果是一样的:
//1 mymodule.js
exports.f = function(){ }
exports.pi = 3.1415926
//2 mymodule.js
module.exports.f = function(){ }
module.exports.pi = 3.1415926
-
在定义模块时:
如果直接给exports对象赋值(例如:exports={a:1,b:2}),此时,exports就不会再指向module.exports,而转而指向这个新对象,此时,exports与module.exports不是同一个对象。
在引入某模块时:以该模块代码中
module.exports指向的内容为准。
图示
结论
在导出模块过程中,建议只用一种方式(建议直接使用module.exports)
了解npm
npm
npm全称Node Package Manager(node 包管理器),它的诞生是为了解决 Node 中第三方包共享的问题。npm不需要单独安装。在安装Node的时候,会连带一起安装npm。npm -v检查安装的情况。- 官网
当我们谈到npm时,我们在说两个东西:
- 命令行工具。这个工具在安装node时,已经自动安装过了。
- npm网站。这是一个第三方模块的"不花钱的超市",我们可以自由地下载,上传模块。
包(package)与模块关系
npm网站收集了前端的各种工具.
之前学习过:
jQuery, bootStrap, flexible.js, arttemplate.js, layui.js, echarts.js........
你是如何下载的?
官网下载
有没有一个想法:在一个地方下载所有的库(模块.....)
npm网站上去下载我们的需要的代码时,它们是以"包"这种结构放在npm网站上的。先来了解下包和模块的关系。
- nodejs中一个模块就是一个单独的js文件
- 包是多个模块的集合。一个模块的功能比较单一,所以一个包一般会包含多个模块。
- npm 管理的单位是包。类似于网站和网页的区别:一个网站一般会包含多个网页。
npm下载使用包
分成三步:
- 初始化项目。npm init 如果之前已经初始化,则可以省略。
- 💥重点 安装包。 npm install 包名。
- 引入模块,使用。
保持联网的状态哈
第一步:初始化项目
这里提到的项目并不是某个具体的功能,只是要创建一个空文件夹即可(💥注意,不要起中文名字哈)。
进入到项目所在的根目录下,启动小黑窗(按下shift键,点击右键,在弹出的菜单中选择 “在此处打开命令行”)
输入如下命令:
npm init --yes
// --与yes之间没有空格, -- 与init之间有空格
// 或者是 npm init -y
init命令用来在根目录下生成一个package.json文件,这个文件中记录了我们当前项目的基本信息,它是一切工作的开始。
第二步:安装包
npm 这个超市上有好的代码,我们想下载来用 ------ 安装包
生成了package.json文件之后,我们就可以来安装第三方包了。在npm官网中,有上百万个包,供我们使用(你需要在npm网上注册帐号,登陆上去,才可以看到如下的数据,如果只是下载安装包,则并不需要注册)。
根据我们遇到的实际问题,我们来引入相应的包来解决它们。例如,我们在开发一个项目,其中涉及一些对日期时间的处理可以安装dayjs包。
安装day.js包
⭐ day.js是一个专门用来处理日期时间的一个包
npm install dayjs
第三步:使用包
当我们已经下载好一个包之后,就可以像使用核心模块一样去使用它。
格式是:const 常量名 = require('包名') 这个格式与引入核心模块的格式是一样的。
// 从npm下载 别人写的好代码,在本地引入,并使用
const dayjs = require('dayjs')
console.log( dayjs()
.startOf('month')
.add(1, 'day')
.set('year', 2018)
.format('YYYY-MM-DD HH:mm:ss') );
console.log(dayjs);
npm init 命令
在某个目录下开启小黑窗,输入如下命令:
# init 初始化
npm init
它会启动一个交互式的程序,让你填入一些关于本项目的信息,最后生成一个package.json文件。
如果你希望直接采用默认信息,可以使用:
npm init --yes
// 或者是 npm init -y
说明:
- 这个命令只需要运行一次,它的目的仅仅是生成一个package.json文件。
- 如果项目根目录下已经有了package.json文件,就不需要再去运行这个命令了。
- 这个package.json文件后期是可以手动修改的。
package.json文件
它一般是由npm init命令创建出来的,它的整体内容是一个json字符串,用来对当前项目进行整体描述。其中最外层可以看作是一个js的对象(每一个属性名都加了"",这就是一个典型的json标记)。这个文件中有非常多的内容,我们目前学习如下几个:
-
name: 表示这个项目的名字。如是它是一个第三方包的话,它就决定了我们在require()时应该要写什么内容。
-
version:版本号
-
keywords:关键字
-
author: 作者
-
descrption: 描述
其它可参考
1.javascript.ruanyifeng.com/nodejs/pack…
node_modules文件夹
作用
在使用npm install 命令时,会从npm网站下载对应的包到这个文件夹中,这个文件夹中保存着我们从npm中下载来的第三方包。
执行逻辑
当键入npm install XXX之后(这里假设这个XXX包是存在的,也没有出现任何的网络错误):
-
如果有package.json
(1) 修改package.json文件。根据开发依赖和生产依赖的不同,决定把这句记录在加在devDependencies或者是dependencies列表中。
(2) 修改node_modules文件夹
- 如果有node_modules文件夹,则直接在下面新建名为XXX的文件夹,并从npm中下来这个包。如果这个包还有其它的依赖,则也会下载下来。
- 如果没有node_modules,则先创建这个文件夹,再去下载相应的包
-
如果没有package.json。会给一个警告信息。
说明:
- 建议先用init命令创建package.json之后,再去install
- 在分享代码时,我们一般不需要把node_modules也给别人(就像你不需要把jquery.js给别人,因为他们可以自己去下载)。对方拿到我们的代码之后,先运行
npm install(后面不接任何的包名),自己去安装这些个依赖包。
全局安装包和本地安装包
我们通过npm install 命令来安装包,简单说就是把包从npm的官网(或者是指定的镜像源)下载到我们自己的电脑中。那具体这个包下载到哪里了,还是有一点讲究的。
分成两类:
-
全局安装: 包被安装到了系统目录(一般在系统盘的node_modules中)。
-
命令:
npm install -g 包名或者npm install 包名 -g -
辅助提示:
-
npm root -g // 查看全局包的安装目录 npm list -g --depth 0 //查看全局安装过的包
-
-
-
局部安装(或者叫本地安装),包安装在当前项目的根目录下(与package.json同级)的node_modules中。
- 命令:
npm install 包名
- 命令:
全局包与本地包的区别
-
全局安装的包一般可提供直接执行的命令。我们通过对一些工具类的包采用这种方式安装,如:
gulp, nodemon, live-server,nrm等。
-
本地安装的包是与具体的项目有关的, 我们需要在开发过程中使用这些具体的功能。
一个经验法则:
- 要用到该包的命令执行任务的就需要全局安装。
- 要通过require引入使用的就需要本地安装。
全局安装nrm包
作用
⭐nrm 这个工具是帮助我们切换安装包的来源的。 因为下载包时,默认是从npm官网(国外的网站)下载,速度可能会比较慢,我们可以手动去切换****安装来源。
不应该只限于某个具体的项目,所以我们采用全局安装的方式来安装它。
nrm包的地址:www.npmjs.com/package/nrm
nrm的使用方法
操作步骤
共三步
// 第一步: 全局安装
npm install nrm -g
// 第二步:列出所有的源信息
// (*)标注的就是当前使用的源
nrm ls
// 第三步:根据需要切换源
// 例如:指定使用taobao源
nrm use taotao
// 接下来,正常安装你需要的包
- nrm ls 的查看讲解
如果nrm 命令不能用,会出现以下的报错
解决方案:
Win 键 + Q ,在搜索框内输入 Powershell 。
点击以管理员身份运行。
输入代码 set-execut i onpolicy remotesigned 按回车键执行命令。
输入 A,按回车键执行。
const NRMRC = path.join(process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'], '.nrmrc');
全局安装nodemon包
作用
⭐ 它能帮我们自动检测到我们的代码的修改,并自动重新运行我们的代码
我们每次修改了代码,要想代码生效都需要重启http服务器:
- 进入小黑窗
- 按下ctrl+c,停止已有http服务器。
- 手动运行:node index.js 来重启服务器。
这有点麻烦哈。
有没有一个工具会自动检测到我们的修改并自动重新运行我们的代码呢?有,它叫nodemon**。地址
安装 nodemon
通过npm包管理工具来进行安装。
步骤:
在任意位置 打开一个小黑窗,输入如下命令
npm install -g nodemon
回车。
此操作需要联网,根据网络速度所耗时间不同。如果这个命令执行完成并没有报错,就是说明安装成功了。
对上面的命令说明如下:
-
npm是一个工具,用来管理node代码中要使用的第三方模块。它是随着node的安装而自动安装的:如果你安装node,则npm也已经安装过了,你可以直接使用。
-
-g 表示全局安装。它也可以写在nodemon后面。即
npm install nodemon -g。
删除终端
使用nodemon
等待安装成功之后,使用方法也非常简单:在命令中,使用nodemon来代替node。
例如,原来是:
node server.js
现在是:
// 改成 nodemon server.js
nodemon server.js
它的好处在于会自动监听server.js这个文件的变化,如果变化了,就会重新自动再去运行。相当于是:
while(server.js 变化了){
node server.js
}
说明:
-
它是一个第三方的包(其它程序员写的工具)
-
之前的node server.js还是可以用的。
注意:出现系统禁止执行 nodemon.ps1文件的解决办法
解决办法:
https://blog.csdn.net/weixin_38289787/article/details/108352666
1. 以管理员身份运行PowerShell
2. 执行:get-ExecutionPolicy,回复Restricted,表示状态是禁止的
3. 执行:set-ExecutionPolicy RemoteSigned
4. 输入Y
卸载包
当一个包不使用的时候,我们要将其卸载
卸载包分为两种:本地包和全局包的卸载
-
本地包卸载:
-
- 进入到你想要卸载的包所在的文件夹,到package.json这一层即可
- 打开cmd小黑窗
- 在小黑窗中执行
npm uninstall 包名
-
-
全局包的卸载:
- 1.在任意地方打开小黑窗
-
- 输入
npm uninstall 全局包名 -g
- 输入
-
卸载简写为:npm un 包名
开发依赖和生产依赖(了解)
在本地安装包时,表示我们这个项目要用到这个包,换句话说,我们这个项目要想成功运行,要依赖于这些个包。
但在,具体在项目进行的哪一阶段依赖于这些包呢?也有具体的差异。
理解
举个生活中建房子的场景:
在建房子时,我们依赖:
- 辅助工具:尺子,水平仪,脚手架
- 原材料:钢筋,水泥
在住房子时,我们依赖:
- 原材料:钢筋,水泥
在房子进入到了使用阶段时,我们就不再需要尺子,水平仪,脚手架等这些个辅助工具了。我们买一所房子时,也不应该支付巨型脚手架的费用。 在开发前端项目的过程中也存在类似的问题:我们的开发过程和使用过程是分开的,开发项目时需要用到的包可能在使用项目时就不需要用到了。
假设有这么两个包:
- gulp-htmlmin。这个工具是用来把html代码进行压缩的(去掉空格,换行等),我们需要在开发时使用它,而项目一旦上线,我们就不再需要它了。,因此将它归类为"开发依赖"。
- jquery。在开发时参与源码编写,在发布上线的生产环境中也是需要它的。不仅在开发环境编写代码时要依赖它、线上环境也要依赖它,因此将它归类为"生产依赖"。
这个差异就表现在,我们在打包项目时,就不需要打包“开发依赖”的包,这样减少成本。
---webpack
操作
这两种依赖关系,在具体操作的过程中,有如下区别
- 保存到开发依赖(devDependencies) --开发时的插件包,项目上线的代码不需要
npm install 包名 --save-dev
// 或者 npm install 包名 -D
通过这种方式安装的包出会现在package.json文件中的devDependencies字段中。
- 保存到生产依赖(dependencies):
npm install 包名
// 或者 npm install 包名
// 或者 npm install 包名 -S
技巧
- 加了-D : 开发依赖,这就表示这个工具包只在开发项目时候要用,项目开发完成就不需要
- 不加-D: 生产依赖,这就表示这个工具包在项目做完了之后也要用。
什么包加上-D,什么包不要加?------- 看文档
npm包的问题
如何去下载包
命令:npm i 包名
-
在另一个项目中去下载包
-
为了提升下载速度,我们会切换镜像到taobao。
虽然我们上传是传到npm官网,但是,它会自动同步(例如:每隔15分钟就会通过其它镜像最新的包的信息)给其它的镜像 --- taobao, cnpm.....
require的加载机制
在我们使用一个模块时,我们会使用require命令来加载这个模块。以加载一个自定义模块为例,require(文件名)的效果是:
- 执行这个文件中的代码
- 把这个文件中的module.exports对象中的内容返回出来。
以如下代码为例:
// moudule1.js
var a = 1;
var b = 2;
console.log(a+b);
var c = a+b;
module.exports = {
data: c
}
在index.js中使用模块
// index.js
const obj = require('./moudule1.js');
console.log(obj);
//这里的obj对象就是moudule1.js中的module.exports对象
require加载规则:
-
require优先加载缓存中的模块。同一个模块第一次require之后,就会缓存一份,下一次require时就直接从缓存中去取。 -
如果是加载核心模块,直接从内存中加载,并缓存
- 加载核心模块的格式是
const xxx = require("模块名")。不能写相对路径!
- 加载核心模块的格式是
-
如果是相对路径,则根据路径加载自定义模块,并缓存
- 以
require('./main')为例( 省略扩展名的情况) - 先加载
main.js,如果没有再加载main.json,如果没有再加载main.node(c/c++编写的模块),找不到就报错。
- 以
-
如果不是自定义模块,也不是核心模块,则加载第三方模块
以
require('XXX')为例:- node 会去本级 node_modules 目录下的xxx文件夹中找xxx.js ----> xxx.json ----> xxx.node(找到一个即返回),找到并缓存。
- 如果找不到,node 则取上一级目录中的node_modules下找 ,
node_modules/xxx目录,规则同上 - 如果一直找到代码文件的文件系统的根目录还找不到,则报错:模块没有找到。
在module.paths命令中可以看到搜索路径中包含node_modules这个文件夹
附:npm 常用命令
-
查看
npm -v // 查看npm 版本 where node // 查看node的安装目录 where npm // 查看npm的安装目录 npm root -g // 查看全局包的安装目录 npm list -g --depth 0 // 查看全局安装过的包 -
升级 npm
npm install npm --global // 简写成 -g npm install npm -g -
初始化
package.jsonnpm init -y // 或者是npm init --yes -
安装第三方包
// 安装当前目录下package.json中列出的所有的包 // 如果之前安装了包,又在package.json中手动删除依赖 // 它相当是删除包 npm install // 全局安装 npm install 包名 -g // npm install -g 包名 // 本地安装,没有指定版本,默认安装最新的版本 npm install 包名 // 一次安装多个包,空格隔开 npm install 包名1 包名2 包名3 // 安装指定版本的包 npm install 包名@版本号 // 简写。把install简写成 i npm i 包名 -
删除已安装的包
npm uninstall 本地安装的包名 npm uninstall 全局安装的包名 -g uninstall 可简写为 un -
设置npm的register
如果你不想用
nrm,下面这个原生的命令也可以切换镜像源(从哪里下载包)。npm config set registry https://registry.npm.taobao.org ## 所有npm i 包 都会从taobao的镜像去下载。 ## 配置后可通过下面方式来验证是否成功 npm config get registry
总结:目前学的包
webPack
yarn包管理器(必须)
快速、可靠、安全的依赖管理工具。和 npm 类似, 都是包管理工具, 可以用于下载包, 就是比npm快
中文官网地址: yarn.bootcss.com/
下载yarn
下载地址: yarn.bootcss.com/docs/instal…
-
windows - 软件包(在笔记文件夹里)
-
mac - 通过homebrew安装(看上面地址里)
-
mac如果没安装过homeBrew先运行这个命令
/usr/bin/ruby -e "$(curl -fsSL http://cdn.jsdelivr.net/gh/ineo6/homebrew-install/install)"
-
-
上面命令不行: 试试这个: curl -o- -L yarnpkg.com/install.sh | bash (直接安装yarn)
==不要安到带中文的路径下, 建议在C盘/==
也可以通过npm全局安装:
npm i yarn -g
使用yarn
与npm类似, 可以试试, 新建一个空白文件夹, 执行以下命令尝试一下
# 1. 初始化, 得到package.json文件(终端路径所在文件夹下)
yarn init
# 2. 添加依赖(下包)
# 命令: yarn add [package]
# 命令: yarn add [package]@[version]
yarn add jquery
yarn add jquery@3.5.1
# 3. 移除包
# 命令: yarn remove [package]
yarn remove jquery
# 4. 安装项目全部依赖(一般拿到别人的项目时, 缺少node_modules)
yarn
# 会根据当前项目package.json记录的包名和版本, 全部下载到当前工程中
# 5. 全局
# 安装: yarn global add [package]
# 卸载: yarn global remove [package]
# 注意: global一定在add左边
yarn global add @vue/cli
# 如何使用, 为明天学习vue做铺垫
1. webpack基本概念
目标: webpack本身是, node的一个第三方模块包, 用于打包代码
-
现代 javascript 应用程序的 静态模块打包器 (module bundler)
-
为要学的 vue-cli 开发环境做铺垫
==webpack能做什么==
把很多文件打包整合到一起, 缩小项目体积, 提高加载速度(演示准备好的例子)
其中功能:
-
less/sass -> css
-
ES6/7/8 -> ES5
-
html/css/js -> 压缩合并
2. webpack的使用步骤
2.0_webpack基础使用
目标: 把src下的2个js文件, 打包到1个js中, 并输出到默认dist目录下
默认入口: ./src/index.js
默认出口: ./dist/main.js
==💥注意:路径上, 文件夹, 文件名不能叫webpack/其他已知的模块名==
-
初始化包环境
npm init # 或者 yarn init -
安装依赖包(一共装了两个包)
npm install webpack webpack-cli -D # 或者 yarn add webpack webpack-cli -D
3. 配置scripts(自定义命令)
scripts: {
"build": "webpack"
}
-
新建目录src
-
新建src/add/add.js - 定义求和函数导出
export const addFn = (a, b) => a + b -
新建src/index.js导入使用
import {addFn} from './add/add' console.log(addFn(10, 20)); -
运行打包命令
npm run build #或者 yarn build
总结: src并列处, 生成默认dist目录和打包后默认main.js文件
2.1_webpack 更新打包
目标: 以后代码变更, 如何重新打包呢
-
新建src/tool/tool.js - 定义导出数组求和方法
export const getArrSum = arr => arr.reduce((sum, val) => sum += val, 0) -
src/index.js - 导入使用
import {addFn} from './add/add' import {getArrSum} from './tool/tool' console.log(addFn(10, 20)); console.log(getArrSum([1, 2, 3])); -
重新打包
npm run build
总结1: src下开发环境, dist是打包后, 分别独立
总结2: 打包后格式压缩, 变量压缩等
3. webpack的配置
3.0_webpack-入口和出口
目标: 告诉webpack从哪开始打包, 打包后输出到哪里
默认入口: ./src/index.js
默认出口: ./dist/main.js
webpack配置 - webpack.config.js(默认)
- 在src并列处,新建文件 webpack.config.js
- 填入配置项
const path = require("path")
module.exports = {
entry: "./src/main.js", // 入口
output: {
path: path.join(__dirname, "dist"), // 出口路径
filename: "bundle.js" // 出口文件名
}
}
- 修改package.json, 自定义打包命令 - 让webpack使用配置文件
"scripts": {
"build": "webpack"
},
- 打包观察效果
3.1_打包流程图
==重点: 所有要被打包的资源都要跟入口产生直接/间接的引用关系==
3.2_案例-webpack隔行变色
目标: 工程化模块化开发前端项目, webpack会对ES6模块化处理
-
回顾从0准备环境
-
初始化包环境
-
下载依赖包
-
配置自定义打包命令
-
-
下载jquery, 新建public/index.html
npm install jquery
3. index.html 准备一些li
- ==因为import语法浏览器支持性不好, 需要被webpack转换后, 再使用JS代码==
<!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>Document</title>
</head>
<body>
<div id="app">
<!-- ul>li{我是第$个li}*10 -->
<ul>
<li>我是第1个li</li>
<li>我是第2个li</li>
<li>我是第3个li</li>
<li>我是第4个li</li>
<li>我是第5个li</li>
<li>我是第6个li</li>
<li>我是第7个li</li>
<li>我是第8个li</li>
<li>我是第9个li</li>
</ul>
</div>
</body>
</html>
-
在src/main.js引入jquery
import $ from 'jquery' -
src/main.js中编写隔行变色代码
// 引入jquery import $ from 'jquery' $(function() { $('#app li:nth-child(odd)').css('color', 'red') $('#app li:nth-child(even)').css('color', 'green') }) -
执行打包命令观察效果
-
可以在dist下把public/index.html引入过来
在index.html中==手动==引入js
<script src="../dist/bundle.js"></script>
总结: 前端工程化模块化, 需要的包yarn下, 被webpack打包后引入到html中使用
3.3_插件-自动生成html文件
目标: html-webpack-plugin插件, 让webpack打包后生成html文件并自动引入打包后的js
-
下载插件
npm install html-webpack-plugin -D
2. webpack.config.js配置
```js
// 引入自动生成 html 的插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// ...省略其他代码
// 插件列表
plugins: [
// 自动生成html文件并导入打包后的js文件
new HtmlWebpackPlugin()
]
}
```
3. 重新打包后观察dist下是否多出html并运行看效果
==打包后的index.html自动引入打包后的js文件==
总结: webpack就像一个人, webpack.config.js是人物属性, 给它穿什么装备它就干什么活
3.4_加载器 - 处理css文件问题
目标: 自己准备css文件, 引入到webpack入口, 测试webpack是否能打包css文件
1.新建 - src/css/index.css
2.编写去除li圆点样式代码
3.(重要) 一定要引入到入口才会被webpack打包
4.执行打包命令观察效果
💥 总结: 保存原因, 因为webpack默认只能处理js类型文件
3.5_加载器 - 处理css文件
目标: loaders加载器, 可让webpack处理其他类型的文件, 打包到js中
原因: webpack默认只认识 js 文件和 json文件
-
安装依赖
npm install style-loader css-loader -D -
webpack.config.js 配置
const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { // ...其他代码 module: { rules: [ // loader的规则 { // 通过正则匹配.css 结尾的文件 test: /\.css$/i, // 匹配所有的css文件 // use数组里从右向左运行 // 先用 css-loader 让webpack能够识别 css 文件的内容并打包 // 再用 style-loader 将样式, 把css插入到dom中 use: [ "style-loader", "css-loader"] } ] } } -
新建src/css/li.css - 去掉li默认样式
ul, li{ list-style: none; } -
引入到main.js (因为这里是入口需要产生关系, 才会被webpack找到打包起来)
import "./css/index.css" -
运行打包后dist/index.html观察效果和css引入情况
总结: 万物皆模块, 引到入口, 才会被webpack打包, css打包进js中, 然后被嵌入在style标签插入dom上
3.6_加载器 - 处理less文件
目标: less-loader让webpack处理less文件, less模块翻译less代码
-
下载依赖包
npm install less less-loader -D -
webpack.config.js 配置
module: { rules: [ // loader的规则 // ...省略其他 { // 通过正则匹配.less 结尾的文件 test: /\.less$/i, // 使用less-loader, 让webpack处理less文件, 内置还会用less翻译less代码成css内容 use: [ "style-loader", "css-loader", 'less-loader'] } ] }💥注意点:覆盖问题,要从第三个花括号{ } 开始复制
3. src/less/index.less - 设置li字体大小24px
@size:24px;
ul, li{
font-size: @size
}
-
引入到main.js中
import "./less/index.less" -
打包运行dist/index.html 观察效果
总结: 只要找到对应的loader加载器, 就能让webpack处理不同类型文件
3.7_加载器 - 处理图片文件
目标: 用asset module方式(webpack5版本新增)
如果使用的是webpack5版本的, 直接配置在webpack.config.js - 的 rules里即可
{
test: /\.(png|jpg|gif|jpeg)$/i,
type: 'asset'
}
如果你用的是webpack4及以前的, 请使用者里的配置
-
下载依赖包
npm install url-loader file-loader -D -
webpack.config.js 配置
{ test: /\.(png|jpg|gif|jpeg)$/i, use: [ { loader: 'url-loader', // 匹配文件, 尝试转base64字符串打包到js中 // 配置limit, 超过8k, 不转, file-loader复制, 随机名, 输出文件 options: { limit: 8 * 1024, }, }, ], }图片转成 base64 字符串
- 好处就是浏览器不用发请求了,直接可以读取
- 坏处就是如果图片太大,再转
base64就会让图片的体积增大 30% 左右
-
src/assets/准备老师发的2个图文件
-
在css/less/index.less - 把小图片用做背景图
body{ background: url(../assets/logo_small.png) no-repeat center; } -
在src/main.js - 把大图插入到创建的img标签上, 添加body上显示
// 引入图片-使用 import imgUrl from './assets/1.gif' const theImg = document.createElement("img") theImg.src = imgUrl document.body.appendChild(theImg) -
打包运行dist/index.html观察2个图片区别
总结: url-loader 把文件转base64 打包进js中, 会有30%的增大, file-loader 把文件直接复制输出
3.8_webpack加载文件优缺点
图片转成 base64 字符串
-
好处就是浏览器不用发请求了,直接可以读取
-
坏处就是如果图片太大,再转
base64就会让图片的体积增大 30% 左右
3.9_加载器 - 处理字体文件
目标: 用asset module技术, asset/resource直接输出到dist目录下
webpack5使用这个配置
{ // webpack5默认内部不认识这些文件, 所以当做静态资源直接输出即可
test: /\.(eot|svg|ttf|woff|woff2)$/,
type: 'asset/resource',
generator: {
filename: 'font/[name].[hash:6][ext]'
}
}
webpack4及以前使用下面的配置
-
webpack.config.js - 准备配置
{ // 处理字体图标的解析 test: /\.(eot|svg|ttf|woff|woff2)$/, use: [ { loader: 'url-loader', options: { limit: 2 * 1024, // 配置输出的文件名 name: '[name].[ext]', // 配置输出的文件目录 outputPath: "fonts/" } } ] } -
src/assets/ - 放入字体库fonts文件夹
-
在main.js引入iconfont.css
// 引入字体图标文件 import './assets/fonts/iconfont.css' -
在public/index.html使用字体图标样式
<i class="iconfont icon-weixin"></i> -
执行打包命令-观察打包后网页效果
总结: url-loader和file-loader 可以打包静态资源文件
3.10_加载器 - 处理高版本js语法
目标: 让webpack对高版本 的js代码, 降级处理后打包
写代码演示: 高版本的js代码(箭头函数), 打包后, 直接原封不动打入了js文件中, 遇到一些低版本的浏览器就会报错
原因: webpack 默认仅内置了 模块化的 兼容性处理 import export
babel 的介绍 => 用于处理高版本 js语法 的兼容性 babel官网
解决: 让webpack配合babel-loader 对js语法做处理
-
安装包
npm install babel-loader @babel/core @babel/preset-env -D -
配置规则
module: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] // 预设:转码规则(用bable开发环境本来预设的) } } } ] } -
在main.js中使用箭头函数(高版本js)
// 高级语法 const fn = () => { console.log("你好babel"); } console.log(fn) // 这里必须打印不能调用/不使用, 不然webpack会精简成一句打印不要函数了/不会编译未使用的代码 // 没有babel集成时, 原样直接打包进lib/bundle.js // 有babel集成时, 会翻译成普通函数打包进lib/bundle.js -
打包后观察lib/bundle.js - 被转成成普通函数使用了 - 这就是babel降级翻译的功能
总结: babel-loader 可以让webpack 对高版本js语法做降级处理后打包
4. webpack 开发服务器
4.0_webpack开发服务器-为何学?
文档地址: webpack.docschina.org/configurati…
抛出问题: 每次修改代码, 都需要重新 yarn build 打包, 才能看到最新的效果, 实际工作中, 打包 yarn build 非常费时 (30s - 60s) 之间
为什么费时?
- 构建依赖
- 磁盘读取对应的文件到内存, 才能加载
- 用对应的 loader 进行处理
- 将处理完的内容, 输出到磁盘指定目录
解决问题: 起一个开发服务器, 在电脑内存中打包, 缓存一些已经打包过的内容, 只重新打包修改的文件, 最终运行加载在内存中给浏览器使用
==4.1_webpack-dev-server自动刷新==
目标: 启动本地服务, 可实时更新修改的代码, 打包变化代码到内存中, 然后直接提供端口和网页访问
-
下载包
npm install webpack-dev-server -D -
配置自定义命令
scripts: { "build": "webpack", "serve": "webpack serve" } -
运行命令-启动webpack开发服务器
yarn serve #或者 npm run serve
总结: 以后改了src下的资源代码, 就会直接更新到内存打包, 然后反馈到浏览器上了
4.2_webpack-dev-server配置
-
在webpack.config.js中添加服务器配置
更多配置参考这里: webpack.docschina.org/configurati…
module.exports = { // ...其他配置 devServer: { port: 3000 ,// 端口号 open:true, // 自动打开浏览器 } }
补充知识
git初始忽略提交 node_modules 文件夹
导出语法
ES6的模块化语法
导入
import { 名称 } form “模块”
import { arr } from "./utils/utils";
import dayjs form “dayjs”
导出
-
export const 名称 = 数据 需要配合解构{ } 使用
export const arr = [1, 2, 3, 4]; -
export default 名称 默认导出,一个js文件只能有一个默认导出