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);

数组中的常用函数:

image-20240608160443275

image-20240608160414512

字符串中的常用函数:

image-20240608160343210

防抖

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

  1. Promise本身是一个构造函数,Promise的实例代表一个异步操作。
  2. Promise的prototype中包含.then() .catch() .finally() 函数
  3. 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 压缩

基本配置

  1. enty(入口)
    指示Webpack从哪个文件开始打包
  2. output(输出)
    指示Webpack打包完的文件输出到哪里去,如何命名等
  3. loader(加载器)
    webpack本身只能处理js、json等资源,其他资源需要信助loader,webpack才能解析
  4. plugins(插件)
    扩展Nebpack的功能
  5. 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')
        }
}