JavaScript
本文详细介绍了JavaScript和Node.js的高级特性,包括作用域、垃圾回收、闭包、Promise、模块化、HTTP模块、Express框架等,并深入探讨了Webpack的配置和使用。文章系统地阐述了现代JavaScript开发中的关键技术和最佳实践,涵盖了从基础语法到工程化构建的广泛内容,对于希望深入理解JavaScript生态和提升开发技能的开发者具有重要参考价值。
JavaScript进阶
作用域
作用域规定了变量的访问返回,在js中的作用域分为两大类
- 全局作用域
- 局部作用域
- 函数作用域
- 块作用域
局部:
使用let、const声明变量都会遵循作用域原则,使用var关键字声明则不会,var存在作用域提升。
全局:
在script标签和.js文件的最外层声明的变量就处于全局作用域
全局作用域中的变量在任何地方都可以被访问
不推荐的全局写法:定义在window对象的属性上或者没有使用任何关键字进行声明的变量
作用域链:
作用域链的本质就是变量的查找机制。
函数执行时优先查找最近的作用域,然后逐级向上查找,直到查找到全局作用域。
JS的垃圾回收机制:
内存生命周期: 分配>使用>回收
全局变量一般不会回收,局部变量不再使用将会被自动回收。
内存泄漏:在程序中分配的内存因为某中原因导致无法释放,称为内存泄漏
回收算法:
- 引用计数(淘汰): 记录引用次数,次数为0,则回收
- 问题:循环引用
- 标记清除法:从根部开始查找变量,如果查找不到某个变量,这个变量将会被回收
闭包:
内部函数用到了外部变量,导致该函数和周边的引用进行捆绑,称为闭包。
变量提升:
在代码执行之前,将var声明的变量提升到当前作用域的最前面。(仅声明)
在变量提升中经常会出现各种问题,所以后面引入了let和const定义变量的方式
函数
函数提升:
在代码执行之前,将声明的函数提升到当前作用域的最前面。
函数的动态参数:
在函数中自带arguments变量,变量内容为伪数组。
function getSum() {
let sum = 0;
Array.from(arguments).forEach(item => {
sum += item;
})
console.log(sum);
}
getSum(1, 2, 3)
剩余参数, 特点不是伪数组
function getSum(...arr) {
let sum = 0;
arr.forEach(item => {
sum += item;
})
console.log(sum);
}
getSum(1, 2, 3)
展开运算符: 和剩余参数
function getSum(arr) {
console.log(...arr); // 1 2 3
}
getSum([1, 2, 3])
// 使用场景 某些不支持数组的api可以使用...展开比如
Math.max(...arr)
arr = [...arr1,...arr2]
箭头函数
箭头函数中不包括this,按照作用域链向上查找。普通函数的this指向调用者。
解构赋值
// 数组解构
let arr = [1, 2, 3]
let [a, b, c] = arr
console.log(a,b,c);
// 交换变量
let a = 1
let b = 2
;[a,b] = [b,a]
// 对象解构
let obj={
username:"admin",
password:"123456"
}
let {username,password} = obj
console.log(username);
console.log(password);
数组中的常用函数:


字符串中的常用函数:

防抖
function debounce(func, delay) {
let timer; // 定时器
return function () {
let context = this; // 记录 this 值,防止在回调函数中丢失
let args = arguments; // 函数参数
//如果定时器存在,则清除定时器(如果没有,也没必要进行处理)
timer ? clearTimeout(timer) : null;
timer = setTimeout(() => {
// 防止 this 值变为 window
func.apply(context, args)
}, delay);
}
}
节流
const throttle = (func, limit) => {
let inThrottle; // 是否处于节流限制时间内
return function() {
const context = this;
const args = arguments;
// 跳出时间限制
if (!inThrottle) {
func.apply(context, args); // 执行回调
inThrottle = true;
// 开启定时器计时
setTimeout(() => inThrottle = false, limit);
}
}
}
Promise
- Promise本身是一个构造函数,Promise的实例代表一个异步操作。
- Promise的prototype中包含.then() .catch() .finally() 函数
- then(result =>{} [, error=>{}] )
特别的情况(解决回调地狱)
在第一个then中 return一个Promise对象,可以连续使用.then()
且可以使用catch函数捕获then()链(任意一个then都可以)中的异常。
Promise.all(Array:Promise) 可以并行执行多个异步对象,然后等待所有异步都结束后执行后续操作then..
Promise.race(Array:Promise) 和all类似,但是执行完成一个之后不会持续等待,而是使用最先执行完成的返回值传递到then函数,且只调用一次。
example:
function getFile(filePath){
return new Promise(()=>{
fs.readFile(...)
})
}
async/await 的使用注意事项
如果在 function 中使用了 await, 则 function 必须被 async 修饰
在 async 方法中,第一个 await 之前的代码会同步执行, await 之后的代码会异步执行
Node JS
fs文件系统模块
readFile:读取文件
writeFile:写文件
const fs = require("fs")
console.log(fs);
fs.readFile('./index.html','utf8', (err,file) => {
if(!err){
console.log(file);
}else{
console.log(err.message);
}
})
log("write:")
fs.writeFile('./index.html', 'testtesttest', (err) => {
// fs.writeFile('./index.html', 'testtesttest', { flag: 'a+' }, (err) => {
if (err) {
console.log(err.message);
}else{
console.log('success');
}
})
Path路径模块
路径拼接时,使用+,很可能会导致拼接出的路径有问题
比如包含 ./ ../ 或者漏掉 / 建议使用join函数完成路径拼接
- join() 将多个路径拼接为一个完整的路径
- basename(dir[, removeStr]) 用来从文件路径中解析出文件名
- 如果是路径则是文件夹的名称
- removeStr 用于移除扩展名
- extname() 获取文件的扩展名
const path = require("path")
let result = path.join("/test/dir1","/e", "../", "d")
console.log(result);
// __dirname 表示当前程序的运行目录
console.log(__dirname);
HTTP请求模块
http模块可以用来创建服务器对外提供服务
const { log } = require('console');
const http = require('http');
const server = http.createServer();
server.on('request',(req,res) =>{
log(req)
log(res)
res.end('end.')
})
server.listen(80,()=>{
console.log("The server is runing on http://127.0.0.1:80");
})
模块化
Node js中的模块一般分为3类
- 内置模块(如fs,path等)
- 自定义模块(js文件)
- 第三方模块(需要自行下载)
加载模块的方式
//加载内置模块
const fs = require('fs')
//加载用户模块
const custom = require('./custom.js')
//加载第三方模块
const axios = require('axios')
模块作用域
在自定义模块中定义的函数变量只能在模块内部使用,这种模块级别的限制称为模块作用域
有效防止了全局变量污染的问题
// 默认导出的对象为{} 我们需要将需要导入的对象添加到exports上去
module.exports.属性 = 值
// 或者
module.exports = {...}
// 注意 后者会覆盖前者
Node提供了exports对象,默认情况下exports和module.exports指向的对象是同一个对象,但是如果发生更改,会以module.exports指向的对象为准
CommonJS规范
每个模块内部,module变量代表当前模块。
module变量是一个对象,它的exports属性(即module.exports)是对外的接口。
加载某个模块,其实是加载该模块的module..exports属性。require0方法用于加载模块。
npm与包
在nodejs中的第三方模块又叫做包,指的是同一个东西
包的来源:由第三方或者团队开发出来的。 https://www.npmjs.com (全球最大的包共享平台)
包由内置模块封装而出,提供了更多方便的api,极大提高了开发效率。
如何获取包:
通过npm工具下载,npm工具默认与node捆绑不需要额外的安装操作。
# 指定版本号安装依赖
npm i moment@版本号
# 不指定默认最新
npm i moment
# 按照package.json中定义的包去下载
npm i
# 卸载包
npm uninstall 包
# 安装到dev分支
npm i .. -D
npm i .. --save-dev
# 管理全局包
npm i .. -g
npm uninstall .. -g
包的语义化:
报的版本号以 点分十进制表示,一共三位数字,例如:2.24.0
第一位:大版本
第二位:功能版本
第三位:Bug修复版本
版本号提升规则:
只要前面的版本号增长了,后面的版本号就要归零
快速创建package.json文件
npm init -y
不要在中文目录执行,目录中不要出现空格
运行npm install时,包信息将会自动记录到package.json中
分析package.json文件
{
"dependencies": {
// 开发和运行都需要用的包
"moment": "^2.30.1"
},
"devDependencies": {
// 只要开发时会使用的包,比如tauri,node-sass等打包编译用到的一些依赖
// 运行时不会包含这些依赖
},
"name": "js",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"dev": "node main.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}
包的分类:
- 项目包
- 开发依赖包
- 核心依赖包
- 全局包
包查找机制
文件名补全:js/json/node
如果没有指定 ./ ../ 则会从根目录的node_modules逐级向上层目录的node_modules中查找
Express
express是基于Nodejs的快速、开放、极简的web开发框架
开发效率相比HTTP模块更上一层楼
安装
npm i express
使用
const express = require('express')
const app = express()
app.listen(80,()=>{
console.log('.....')
})
监听请求
app.get('url',(req,res) => {
...
})
路径动态参数
'/user/:id/:username'
//数据将存在以下对象中
req.params.id
托管静态资源
app.use(express.static('public'))
# 路径前缀
app.use('/static',express.static('public'))
nodemon
热更新插件
nodemon app.js
Example
开启模块化支持 "type": "module"
app.js
import express from 'express'
import userRouter from './controller/user_controller.js'
const app = express()
app.use('/', userRouter)
app.listen(8080, () => {
console.log('server running on port 8080...')
})
user_service
import db from '../utils/db.js'
export async function getAllUser(req, res) {
const [rows] = await db.query('select * from user')
res.send({
code: 200,
msg: 'success',
data: rows
})
}
user_controller
import express from 'express';
import { getAllUser } from '../service/user_service.js';
const router = express.Router()
router.get('/user', getAllUser)
export default router
Webpack
Webpack 的出现是为了应对现代 Web 开发中的复杂性和多样性需求,提供一种高效、统一且可扩展的解决方案。它极大地提升了开发效率和代码质量,使得开发者能够专注于业务逻辑,而不是构建和依赖管理的细节。
提供对es6模块化语法的支持,将其打包为浏览器支持的语法
入门案例
## count
export default function count(x,y){
return x + y;
}
## sum
export default function sum(...args){
return args.reduce((pre,cur) => {
return pre + cur
},0)
}
## main
import count from "./js/count";
import sum from "./js/sum";
console.log(count(2, 3));
console.log(sum(3, 4, 4, 4, 4, 5));
# 安装webpack依赖
npm i webpack webpack-cli -D
# 通过npx 调用项目中bin目录的命令
npx webpack ./src/main.js --mode=devlopment
两种打包方式
--mode=development # 仅编译,不压缩
--mode=production # 编译 and 压缩
基本配置
- enty(入口)
指示Webpack从哪个文件开始打包 - output(输出)
指示Webpack打包完的文件输出到哪里去,如何命名等 - loader(加载器)
webpack本身只能处理js、json等资源,其他资源需要信助loader,webpack才能解析 - plugins(插件)
扩展Nebpack的功能 - mode(模式)
主要由两种模式:- 开发模式:development
- 生产模式:production
const path = require('path') //使用path模块完成路径转换
# 入口(entry)出口(output)mode (开发:development,生产:production)
loader(加载器)plugins(插件)
module.exports = {
entry: './src/main.js', //相对路径
output: {
path: path.resolve(__dirname,'dist'), //绝对路径
filename: 'main.js'
},
module:{
rules:[
],
},
plugins:[
],
mode: 'development'
}
开发者模式介绍
开发者模式顾名思义就是开发阶段所使用的模式
在这个模式中主要做两件事
- 代码编译使浏览器能够运行
- 代码质量检查,树立代码规范
- eslint
- 缩进
webpack-dev-server
webpack-dev-server 是一个基于 Express.js 的开发服务器,它提供了一个用于开发环境的实时重载(live reloading)和热模块替换(Hot Module Replacement,HMR)的解决方案。
npm install webpack-dev-server -D
npx webpack serve
// or
"scripts": {
"dev": "webpack serve"
},
生成的代码在内存中,且每次更新代码自动更新
配置自动启动浏览器
## webpack.config.js module..
devServer:{
open:true,
host: '127.0.0.1',
port: 88
},
html-webpack-plugin
对于html文件进行单独操作,src文件夹下的html不会自动copy的热更新的服务器
可以通过此插件自动配置
具有自动导入entry中的js功能,无需手动导入,如果手动导入将会执行两次
安装
npm install html-webpack-plugin -D
yarn add html-webpack-plugin -D
配置
## webpack.config.js
const HtmlPlugin = require('html-webpack-plugin')
const htmlPlugin = new HtmlPlugin({
template: './public/index.html',
filename: './index.html'
})
...
plugins:[htmlPlugin ],
loader 加载器
加载器 是webpack最重要的部分
默认webpack只能处理js文件,其他文件webpack处理不了,需要调用其他的loader来处理。
- css-loader 打包处理css文件
- less-loader 打包处理less文件
- babel-loader 处理js中webpack无法处理的高级语法
处理css文件
安装加载器
npm install --save-dev style-loader css-loader
配置对应文件使用对应的加载器
## webpack.config.js
rules: [
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
],
对于use中的数组,webpack的调用逻辑是从后往前调用。
对于css文件的处理,loader最终会将css代码打包到入口文件的代码当中。
处理less文件
安装
安装less loader依赖 注意less-loader依赖于less
npm i less-loader less -D
配置
{
test: /\.less$/,
use: [ 'style-loader', 'css-loader', 'less-loader', ]
},
处理过程 less-loader > css-loader > style-loader
先由less编译出css文件,再转交给css-loader,css编译完成后交给style-loader,处理完成后交给webpack。
处理url路径相关的文件
url-loader -依赖于-> file-loader
npm i url-loader file-loader -D
{
test: /\.jpg|png|jpeg$/,
use: [
'url-loader?limit=1024', # 字节
]
}
处理js高级语法
对于一些高级语法如装饰器,webpack也无法处理。
需要借助babel-loader来处理兼容性
注意,需要排除node_modules中的代码,第三方库中的代码的兼容性不需要使用者去关心。
# 安装依赖
yarn add babel-loader @babel/core @babel/plugin-proposal-decorators -D
# 配置loader
{
test: /\.js$/,
use: [
'babel-loader',
],
exclude: /node_modules/
},
# 配置babel
# babel.config.js
module.exports = {
plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]]
}
#babel文档
https://babeljs.io/setup#installation
打包发布
在打包时可以使用script将打包命令记录下来
"scripts": {
"dev": "webpack serve",
"build": "webpack --mode production"
},
--mode production 会覆盖 webpack.config.js的mode配置,这也既不影响dev模式中的配置,也可以是实现不同的配置。
结构化项目打包位置
output: {
path: path.resolve(__dirname,'dist'),
filename: 'js/main.js' # 将js文件打包到js文件夹中
},
{
test: /\.jpg|png|jpeg$/,
use: [
'url-loader?limit=1024&outputPath=images',
]
},
在默认情况下,webpack不会删除旧的文件,只会覆盖dist,而文件名不同就会一直保留,这样打包后的文件就会很乱,每次手动删除又很麻烦,所以我们需要配置打包前自动删除。
需要使用webpack的clean-webpack-plugin插件
npm install --save-dev clean-webpack-plugin
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpackConfig = {
plugins: [
new CleanWebpackPlugin(),
],
};
SourceMap
source map是一个信息文件,文件中存储着压缩混淆的代码的位置信息,有了他可以将报错定位到源代码中的行号,便于排错。
解决默认的sourcemap的问题
## webpack.config.js
// 开发调试阶段建议设置为该值
devtool:'eval-source-map'
// 只展示行号不暴露源码 (在发布时可以使用这个,也可以直接省略不写)
devtool:'noresource-source-map'
一般**@** 在webpack中代表src源码目录,在引入js文件时可以使用@简化路径
如何配置别名
## webpack.config.js
resolve: {
alias:{
'@': path.join(__dirname,'src')
}
}


提供强力驱动