笔记来源:拉勾教育 - 大前端就业集训营
文章内容:学习过程中的笔记、感悟、和经验
前端工程化概述、Node.js基础
前端工程化
简介
工程就是一个项目,例如一个网站或者一个app
一个工程的生命周期
工程立项 — 需求分析 — 产品原型 — 开发实施 — 测试部署 — 上线运行
前端工程化
通过各种工具和技术,提高开发效率
- 内容:各种工具和技术
- 作用:提高工作效率,解决原生的繁琐性
简单来说,前端工程化就是学习使用各种工具,解决前端开发中的各种问题
前端开发中面临的问题
- 代码压缩:有时候可以通过压缩代码来缩小文件体积,这样就能实现更快的访问速度和加载速度
- 要使用ES6和CSS3新特性,要解决兼容性问题:兼容性查询 - caniuse.com/
- 想要使用less增强css,但浏览器不能直接支持less:在项目上线之前需要转换
- 多人协作开发,代码风格无法统一
- ......
工程化解决问题方案
- 项目上线之前压缩代码
- 对ES6和css3新特性进行转换
- less编译工具
- 格式化代码工具
- ......
前端工程化内容
- 格式化工具
- 转换工具
- 压缩工具
- 自动化工具
- 脚手架工具
- ......
注意:
- 工程化不等于某个工具
- Node.js是前端工程化的基础
Node.js基础
Node.js
基于Chrome V8引擎的js的运行时
- 运行时:运行环境 - Runtime
- Chrome V8引擎:谷歌浏览器内核的js引擎(解释器)
本质上就是一个封装了谷歌浏览器V8-js引擎的js运行环境,是除了浏览器之外,可以运行js的环境
Node.js既不是一门新的语言,也不是js框架
作用
浏览器端的js负责前端的功能
- 响应浏览器事件:click、keyUp ...
- 数据验证:正则表达式 ...
- Dom操作:删除、添加节点 ...
- ......
浏览器端js不能做文件操作,没有相关操作的API
node.js负责后端的内容
- 适合开发服务器端应用层:为网站、app、小程序提供数据服务
- 适用于开发前端方向的各种工具:前端工程化工具
- 可以做桌面应用开发:各种跨平台的桌面应用 - vscode等
node.js给js插上了全栈编程的翅膀
Node.js - API
WEB端API:ES + DOM + BOM
node.js端:ES + nodeAPI(fs操作文件、path解析文件路径、os操作系统相关、http网络服务 ......)
安装node.js
- 官网下载node.js安装包,推荐LTS稳定版
- 双击安装,一路next
- 命令行中验证是否安装成功
node -v,如果现实版本号即成功安装
使用node.js
通过node运行js代码两种模式
- 脚本模式:
node 文件路径回车 - 交互模式:
node回车进入交互模式,此时不能输入其他命令- js代码 回车运行js代码
.exit或 ctrl+c按两次 退出交互模式
脚本模式:
- vscode创建脚本index.js(js文件)
- 在vscode中可以使用ctrl+反引号快速打开终端
- 输入node ./index.js即可使用脚本模式执行
交互模式:
- 终端直接输入node 回车进入交互模式
- 输入
console.log(' hello node ')回车运行- 打印输出
hello node- 同时按住ctrl+c退出交互模式
交互模式中特殊功能:
- 使用tab自动补全命令
- 探索js对象,例如:
math.+两次tab可以查看math下所有方法 - 点命令:例如输出
.help回车可以获得帮助信息 - ......
全局对象 - global
node提供一个全局对象global,但是在两种模式下效果会有所区别
- 交互模式下,声明的变量、函数可以使用
global.访问到 - 脚本模式下,声明的变量函数都不能被global访问到
注意:node无法访问浏览器的对象,例如:window、document等,只能访问js交集内的对象,例如object、String等
全局函数
js提供的全局函数,在js下面依旧可用
- parseInt、parseFloat、isNaN、isFinite ......
- 定时器、计时器等
node环境提供的全局函数
立即执行定时器(setImmediate/clearImmediate)
进程立即执行函数(process.nextTick)
// 主程序执行
console.log(1)
// 异步队列执行,在异步队列最开始执行
setImmediate(function () {
console.log(2)
})
// 在所有主程序结束时候执行,之后才执行异步队列内的代码
process.nextTick(function () {
console.log(4)
})
// 主程序执行
console.log(3)
//执行结果 1 3 4 2
node模块(包)
node.js中具有特定功能的对象,是node应用的基本组成部分,大部分的工程化工具都以模块的形式存在
node模块分类
- 内置模块:官方提供,跟随node一起安装nodejs.cn/api/
- 自定义模块:工程师自己写的
- 第三默模块:社区维护的,需要单独下载www.npmjs.com/
内置/核心模块console
console常用方法
- log:打印输出
- table:打印输出(以表格的形式)
- time:计时器
// console模块
//log方法:打印输出,和浏览器端用法相同
// 先从打印结果来看,不同的打印数据打印出来的效果不同,最明显的就是数字和字符串打印出来的颜色不同
console.log('打印输出', '1', 1)
// table方法,以表格的方式打印,在浏览器端也可以使用
console.table({
namr: 'zs',
age: 19
})
// time计时方法:把要计时的代码放在time和timeend之间,注意这里里面的参数必须相同否则会报错
// 计时会返回中间程序运行的时间
console.time('for')
for (let i = 0; i < 10000000; i++) {
}
// console.timeEnd('for123') //不可以这么写,必须参数相同
console.timeEnd('for')
// 在创建一个while计时,可以发现同样的次数while的时间比for循环时间短,说明while效率更高
console.time('while')
let x = 0
while (x < 10000000) {
x++
}
console.timeEnd('while')
内置/核心模块process进程
process进程模块是一个全局变量,可以写写成global.process
因为是全局变量,所有process不需要使用require()关键字引入(非全局变量需要使用require关键字引入)可以直接使用
process常见标识信息
| 标识 | 说明 |
|---|---|
| process.version | 当前node版本号 |
| process.arch | 系统架构(x86/x64) |
| process.platform | 当前操作系统平台 |
| process.cwd() | 当前工作目录 |
| process.env | 当前环境变量信息 |
// process模块 - 进程
// process是全局变量,不需要使用require关键字引入
// process = require('process')
// 打印process变量
// console.log(process)
// 打印node版本号 - version
console.log(process.version) //v14.15.4
// 打印系统架构 - arch
console.log(process.arch) //x64
//打印操作系统平台 - platform
console.log(process.platform) //darwin(mac os操作系统)
// 使用方法
//打印当前工作目录 - cwd()
console.log(process.cwd()) // /Users/chengweinan/Desktop/拉勾教育/lagou
// 打印环境变量 - env - 运行环境信息,每台电脑都可能不同
console.log(process.env)
// 自定义环境变量
// NODE_EVN后面可能会经常用,属于自定义属性,可以用来标识当前环境(线上、开发....)
process.env.NODE_EVN = 'develop'
console.log(process.env)
进程操作
| 操作 | 说明 |
|---|---|
| process.pid | 查询进程id编号 |
| Process.kill( 进程编号 ) | 杀死指定编号进程 |
// process模块 - 进程
// process 进程操作
// 查看某个进程编号 - pid,此时进程可以在操作系统的任务管理器中查看到
// 注意:开启一个进程之后,进程编号是随机生成的,所以两次程序运行可能分配不同的进程id
console.log(process.pid) //随机编号
// 使用交互模式开启一个node进行可以在任务管理器中查看到(mac中打开资源监视器 - pid)
// 关闭交互模式或者杀死进程之后之后,会停止进程,在自建管理器中就看不到了
// 杀死进程 - process.kill(进程id)
// 可以在资源管理器中随便寻找一个进程id,杀死他
// 我们在这里杀死自己的进程
console.log('我要杀死我的进程了')
process.kill(process.pid)
console.log('如果你看到了我说明没有杀死进程,否则就杀死了')
内置/核心模块 - path路径
提供有关路径操作的函数
- 当前目录
./ - 上级目录
../
使用path需要使用require引入
常用path方法
| 方法 | 说明 |
|---|---|
| path.extname( 完整路径 ) | 获取文件扩展名 |
| path.dirname( 完整路径 ) | 获取路径 |
| path.base( 完整路径 ) | 获取文件名.扩展名 |
| path.join( 多个参数 ) | 合并路径 |
// path模块 - 路径
// path想要使用之前必须先引入
const path = require('path')
// 补充两个获取路径的方法
// 获取路径
console.log(__dirname) ///Users/chengweinan/Desktop/拉勾教育/lagou
// 获取完成路径(带上文件)
console.log(__filename) ///Users/chengweinan/Desktop/拉勾教育/lagou/index.js
// 使用path获取路径的一部分
// 获取路径文件扩展名
console.log(path.extname(__filename)) //.js
// 获取路径路径,类似 ___dirname
console.log(path.dirname(__filename)) ///Users/chengweinan/Desktop/拉勾教育/lagou
// 获取文件名
console.log(path.basename(__filename)) //index.js
//合并路径
// 直接把多个路径拼接在一块,默认使用/拼接
const a = path.join('D:', 'b', 'c', 'd.jpg')
console.log(a) // D:/b/c/d.jpg
// 获取上一级路径,..表示上一级
const b = path.join(__filename, '..')
console.log(b) ///Users/chengweinan/Desktop/拉勾教育
内置/核心模块 - fs文件系统
提供操作文件api
- 文件操作:增删改查
- 目录(文件夹)操作:增删改查
使用fs之前必须使用require引入
文件操作(写、读、删)
| 方法 | 说明 |
|---|---|
| fs.writeFile(文件路径,内容,回调函数(err)) | 清空写入 |
| fs.appendFile(文件路径,内容,回调函数(err)) | 追加写入 |
| fs.readFile(文件路径,回调函数(err,data)) | 读取文件 |
| fs.unlink(文件路径,回调函数(err)) | 删除文件 |
// fs模块 - 文件系统
// 引入fs模块
const fs = require('fs')
// 写入文件 - write
// fs.writeFile(__dirname + '/1.txt', '111111', (err) => {
// // 判断是否返回错误信息
// if (err) throw err
// console.log('写入成功')
// })
// 增量写入 - appendWrite
// \n代表换行符
// fs.appendFile(__dirname + '/1.txt', '22222\n', (err) => {
// // 判断是否返回错误信息
// if (err) throw err
// console.log('写入成功')
// })
// 读取文件 - readfile
// 读取文件接收两个参数,一个是错误信息,一个是获取到的数据
// fs.readFile(__dirname + '/1.txt', (err, data) => {
// // 判断是否返回错误信息
// if (err) throw err
// // 如果没有返回错误信息,打印数据
// // 返回的data是二进制格式,但是呈现出来使用十六进制,所以我们使用toString转换成字符串
// console.log(data.toString())
// })
// 删除文件 - unlinkFile
// fs.unlink(__dirname + '/1.txt', (err) => {
// //判断是否返回错误
// if (err) throw err
// console.log('删除成功')
// })
// 实例:把数组中的每一项数据通过增量写入写入到文件中,并且使用换行
// 创建数组
const arr = ['数据1', '数据2', '数据3', '数据4', '数据5']
// 遍历数组
for (const i of arr) {
// 调用增量写入函数appendFile方法写入数据,并使用换行
fs.appendFile(__dirname + '/1.txt', i + '\n', (err) => {
if (err) throw err
console.log('写入成功')
})
}
写入方法如果没有文件会自动创建文件并写入
目录操作(新建、读取、重命名、删除)
| 方法 | 说明 |
|---|---|
| fs.mkdir(路径,回调函数(err)) | 创建目录 |
| fs.readdir(路径,回调函数(err,data)) | 读取指定路径下的所有文件和目录 |
| fs.rename(路径,回调函数(err)) | 重命名目录 |
| fs.rmdir(路径,回调函数(err)) | 删除目录 |
// fs模块 - 文件系统
// 引入fs模块
const fs = require('fs')
// 创建目录 - kmdir
// fs.mkdir(__dirname + '/d1', (err) => {
// // 判断是否报错
// if (err) throw err
// // 如果没有报错,证明创建成功,输出创建成功
// console.log('创建成功')
// })
// 读取目录 - readdir
// fs.readdir(__dirname, (err, data) => {
// if (err) throw err
// // data是数组的形式展现所有内部内容
// // console.log(data)
// // 我们可以比遍历这个数组,区分是否为文件夹
// data.map((d) => {
// // fs的统计方法
// fs.stat(__dirname + '/' + d, (err, stat) => {
// // 是否错误
// if (err) throw err
// // 判断是否是目录isDirectory()
// if (stat.isDirectory()) {
// console.log('目录:' + d)
// } else {
// console.log('文件:' + d)
// }
// })
// })
// })
// 重命名 - rename
// fs.rename(__dirname + '/d1', __dirname + '/d2', (err) => {
// if (err) throw err
// console.log('重命名成功')
// })
// 删除目录 - rmdir
fs.rmdir(__dirname + '/d2', (err) => {
if (err) throw err
console.log('删除成功')
// 注意
// rmdir方法只能删除空目录,非空目录无法删除
// 可以先删除目录内全部文件使目录变空,然后再删除
})
fs同步函数 - synchronization
上面的文件和目录操作都是异步的,也有对应的同步函数,同步函数一般没有回调函数函数,和异步函数功能相同
同步函数一般适用于必须要存在的先后顺序的情况,比如:我们先要判断文件夹是否存在,才能决定是否删除文件夹
同步函数一般是其对应的异步函数后面+Sync后缀,例如:readdir的同步函数就是readdirSync
// 同步函数
const fs = require('fs')
// 判断是否存在
// existsSync是exists的同步函数,判断是否存在,返回布尔值
if (fs.existsSync(__dirname + '/1.txt')) {
// 存在,删除
fs.unlinkSync(__dirname + '/1.txt')
} else {
// 不存在,提示不存在
console.log('文件不存在')
}
案例:压缩和复制文件
// 引入path和fs
const fs = require('fs'),
path = require('path')
// 使用path的join方法创建一个目录的字符串
const newDir = path.join(__dirname, 'new')
// 先读取要复制的文件
fs.readFile(__dirname + '/index.css', (err, data) => {
// 判断是否报错
if (err) throw err
// 转换复制的内容,使用正则表达式,/\/\*.*\*\/|\s+/g表示匹配所有空格和注释
data = data.toString().replace(/\/\*.*\*\/|\s+/g, '')
// 判断是否存在目标目录,如果没有创建目录
if (fs.existsSync(newDir)) {
console.log('存在')
} else {
fs.mkdir(newDir, (err) => {
if (err) throw err
console.log('创建成功')
})
}
// 在目录中写文件,没有文件会自动创建
fs.writeFile(newDir + '/index.css', data, (err) => {
if (err) throw err
console.log('写入成功')
})
})
文件流 - 缓冲和流
文件操作 - 缓冲
数据会先将数据存到内存缓冲中,当内存缓冲满了之后才会移动到目标文件
文件操作 - 流
使用一条虚拟管子吧两个文件连通,a直接把数据流到b中
为什么选择流
- 内存效率提高
- 无需加载大量数据
- 流会把大数据切割成小块,占用内存更少
- 时间效率更高
- 接收到数据立刻处理
- 无需等待内存缓冲填满
流函数操作复制文件
// 引入fs
const fs = require('fs')
// 创建读取流fs.createReadStream(文件路径)
const read = fs.createReadStream(__dirname + '/index.css'),
// 创建写入流fs.createWriteStream(文件路径)
witer = fs.createWriteStream(__dirname + '/index2.css')
// 调用读取流的管子方法:读取流.pipe(写入流)
read.pipe(witer)
内置/核心模块 HTTP - WEB服务
可以发布WEB服务,使用前需要require引入模块
// 内置模块 - http
// 引入http模块
const http = require('http')
// 创建服务器,req:requset请求,res:response响应
const server = http.createServer((req, res) => {
// 返回
res.statusCode = 200
// 设置响应头,返回的数据类型,plain表示普通文本,charset编码格式
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
// 返回内容
res.end('我是返回的内容')
})
// 发布服务器
// 端口号
const port = 3000
// 域名(不可以随意,相信我,就写这个吧)
const host = 'localhost'
// 调用服务器的listen方法发布服务器,带入端口号和域名
server.listen(port, host, () => {
// 开启服务器后在控制台输出的内容
console.log('服务器运行在http://localhost:3000')
})
自定义模块
- Node.js中每个单独的js文件,都是一个模块
- 每个模块都有一个module变量,代表当前模块
- module的exports属性是对外的接口
- 只有通过module.exports导出的方法或者属性才能在外部调用
- 未导出的内容是模块私有化的,不能在外部被访问到
使用时先使用require引入才能使用(需要使用路径引入)
//新建js文件a.js
// 创建一个常亮
const PI = 3.14
// 创建函数求周长
function perimeter(r) {
return 2 * PI * r
}
// 创建函数求面积
function area(r) {
return PI * r * r
}
// 导出周长和面积的函数,外面引用的只能是导出的内容,如果连个函数有一个没有导出,外面引用就会报错
module.exports = {
perimeter,
area
// 这里没有导出常量PI,所以外部不能直接访问PI,只能通过调用perimeter和area才能使用PI
}
// 引入模块,自定义模块使用路径导入,可以不加.js
const a = require('./a')
// 直接调用即可
console.log(a.area(10))
模块加载逻辑
按组织方式划分模块
- 文件模块:一个独立的js文件
- 目录模块:将多个js文件放在一个目录中,使用时引入目录(文件夹)
加载逻辑:
- 文件模块:官方核心模块直接写模块名即可,引入自定义模块需要写路径
- 目录模块:
- 指定了目录路径地址:在目录中寻找入口文件,在入口文件中引入其他模块
- 引入时会优先在目录下寻找package.json文件,json文件里面可以使用main属性指定入口文件
- 如果找不到package.json或者文件没有使用main指定入口文件,会在目录下寻找index.js文件作为入口文件
- 如果上面两种都找不到,那么就没有入口文件可用,也就无法引入
- 指定了目录路径地址:在目录中寻找入口文件,在入口文件中引入其他模块
// 引入模块的文件 index.js
// 引入模块,自定义模块使用路径导入,可以不加.js
const dist = require('./dist')
// 调用引入的info方法
dist.info()
//创建文件夹dist,下面添加四个文件分别是a/b/c.js三个模块和package.json文件
// 下面是a.js内容
// 引入模块b和模块c
const b = require('./b')
const c = require('./c')
// a内部方法,调用b和c的info方法
function info() {
console.log('我是a')
b.info()
c.info()
}
// 导出模块
module.exports = {
info
}
// 下面是b.js内容
// 创建函数
function info() {
console.log('我是b')
}
// 导出函数
module.exports = {
info
}
// 下面是c.js内容
// 创建函数
function info() {
console.log('我是c')
}
// 导出函数
module.exports = {
info
}
//下面是package.json文件,使用main指定入口文件地址
{
"main":"a.js"
}
// 上面是使用json文件设置入口文件的情况,如果没有json文件或者没有使用main指定入口文件,那么程序会在文下面寻找index.js作为默认入口文件,index使用和a一样的方法引入b和c即可
- 不指定目录:会寻找node_modules目录,在node_modules目录下寻找指定名字的目录,优先在同级目录下寻找,找不到去上层目录找,以此类推
第三方模块
社区维护的模块,是别人写好的,前端工程化大部分工具都是第三方模块,使用的时候必须先安装才能引入使用
第三方模块平台:www.npmjs.com/,可以搜索、查看、下载…
NPM - Node包管理工具
- 包就是一坨代码,就是node模块
- npm是一个命令,跟随node一起安装,在命令行中使用
npm -v可以查看npm版本 - npm可以下载/安装包和包的依赖
下载方法
- 手动下载:打开网站 - 找到资源 - 点击下载
- npm安装:命令行输入命令
npm install 包的名字
npm镜像源- npm管理包的资源地址-npmjs.com
修改镜像源
国外镜像下载速度较慢,可能无法访问,我们可以把镜像源地址更改为国内淘宝镜像源
npm config set registry https://registry.npm.taobao.org更改镜像源之后可以查看修改的镜像源地址
npm config get registry
使用NPM
命令行执行npm install 包名字即可安装包,可简写成npm i 包名字
安装方式:
- 全局安装:多个项目都可以使用,将包作为全局工具使用
npm i 包名字 --global或-g- 明确需求 - 找到合适的包 - 通过npm安装包 - 包使用
- 局部安装:只有在当前项目中使用,
npn install 包名字 --save或-S- 创建项目目录(mkdir 目录名) - 进入目录(cd 目录名) - 初始化项目(npm init) - 在项目中安装包 - 使用包
- 注意:局部安装的包,不能直接使用全局调用,包默认在
node_modules下的.bin文件夹下面,所以应该写成.\node_modules\.bin\包使用方法
卸载方式:
全局卸载:npm uninstall 包名字 --global或-g
--save和--save-dev
npm install 包名字 --save-dev 简写npm i包名字 -D
区别:
- --save/-S:安装的包开发和上线都需要使用,例如jquery
- --save-dev/-D:安装的包只在开发环境中使用,例如minify
将来如果明确知道上线之后就不会使用的包可以使用-D安装