前端工程化概述、Node.js基础

237 阅读13分钟

笔记来源:拉勾教育 - 大前端就业集训营

文章内容:学习过程中的笔记、感悟、和经验

前端工程化概述、Node.js基础

前端工程化

简介

工程就是一个项目,例如一个网站或者一个app

一个工程的生命周期

工程立项 — 需求分析 — 产品原型 — 开发实施 — 测试部署 — 上线运行

前端工程化

通过各种工具和技术,提高开发效率

  • 内容:各种工具和技术
  • 作用:提高工作效率,解决原生的繁琐性

简单来说,前端工程化就是学习使用各种工具,解决前端开发中的各种问题

前端开发中面临的问题

  • 代码压缩:有时候可以通过压缩代码来缩小文件体积,这样就能实现更快的访问速度和加载速度
  • 要使用ES6和CSS3新特性,要解决兼容性问题:兼容性查询 - caniuse.com/
  • 想要使用less增强css,但浏览器不能直接支持less:在项目上线之前需要转换
  • 多人协作开发,代码风格无法统一
  • ......

工程化解决问题方案

  • 项目上线之前压缩代码
  • 对ES6和css3新特性进行转换
  • less编译工具
  • 格式化代码工具
  • ......

前端工程化内容

  • 格式化工具
  • 转换工具
  • 压缩工具
  • 自动化工具
  • 脚手架工具
  • ......

sqLyVK.png

注意:

  • 工程化不等于某个工具
  • 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网络服务 ......)

sqz6at.png

安装node.js

  1. 官网下载node.js安装包,推荐LTS稳定版
  2. 双击安装,一路next
  3. 命令行中验证是否安装成功node -v,如果现实版本号即成功安装

使用node.js

通过node运行js代码两种模式

  • 脚本模式:node 文件路径 回车
  • 交互模式:
    1. node回车进入交互模式,此时不能输入其他命令
    2. js代码 回车运行js代码
    3. .exit 或 ctrl+c按两次 退出交互模式

脚本模式:

  1. vscode创建脚本index.js(js文件)
  2. 在vscode中可以使用ctrl+反引号快速打开终端
  3. 输入node ./index.js即可使用脚本模式执行

交互模式:

  1. 终端直接输入node 回车进入交互模式
  2. 输入console.log(' hello node ')回车运行
  3. 打印输出hello node
  4. 同时按住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

sLEZy6.png

node模块(包)

node.js中具有特定功能的对象,是node应用的基本组成部分,大部分的工程化工具都以模块的形式存在

node模块分类

  • 内置模块:官方提供,跟随node一起安装nodejs.cn/api/
  • 自定义模块:工程师自己写的
  • 第三默模块:社区维护的,需要单独下载www.npmjs.com/

sLV9jP.png

内置/核心模块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

sjFbdS.png

// 同步函数
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('写入成功')
  })
})

文件流 - 缓冲和流

文件操作 - 缓冲

数据会先将数据存到内存缓冲中,当内存缓冲满了之后才会移动到目标文件

sjlxNn.png

文件操作 - 流

使用一条虚拟管子吧两个文件连通,a直接把数据流到b中

sj1u36.png

为什么选择流

  • 内存效率提高
    • 无需加载大量数据
    • 流会把大数据切割成小块,占用内存更少
  • 时间效率更高
    • 接收到数据立刻处理
    • 无需等待内存缓冲填满

流函数操作复制文件

// 引入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文件放在一个目录中,使用时引入目录(文件夹)

sxQ6dH.png

加载逻辑:

  • 文件模块:官方核心模块直接写模块名即可,引入自定义模块需要写路径
  • 目录模块:
    • 指定了目录路径地址:在目录中寻找入口文件,在入口文件中引入其他模块
      • 引入时会优先在目录下寻找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目录下寻找指定名字的目录,优先在同级目录下寻找,找不到去上层目录找,以此类推

sx3HTe.png

第三方模块

社区维护的模块,是别人写好的,前端工程化大部分工具都是第三方模块,使用的时候必须先安装才能引入使用

第三方模块平台:www.npmjs.com/,可以搜索、查看、下载…

NPM - Node包管理工具

  • 包就是一坨代码,就是node模块
  • npm是一个命令,跟随node一起安装,在命令行中使用npm -v可以查看npm版本
  • npm可以下载/安装包和包的依赖

下载方法

  • 手动下载:打开网站 - 找到资源 - 点击下载
  • npm安装:命令行输入命令npm install 包的名字

npm镜像源- npm管理包的资源地址-npmjs.com

sxJJeK.png

修改镜像源

国外镜像下载速度较慢,可能无法访问,我们可以把镜像源地址更改为国内淘宝镜像源

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安装

sxBZes.png