关于 js:3. 闭包、作用域、内存模型

news2025/5/9 23:41:35

一、闭包的本质:函数 + 其词法作用域环境

闭包(Closure)的本质可以概括为:

闭包是一个函数,以及它定义时捕获的词法作用域中的变量集合。

这意味着:即使外部函数已经返回或作用域结束,只要有内部函数引用了外部变量,那么这些变量依然不会被销毁。

1. 从语言底层解释闭包本质

1)JS 是“函数作用域 + 词法作用域”的语言

举例:

function outer() {
    let a = 1;

    return function inner() {
        console.log(a);  // inner 引用了 outer 中的 a
    }
}
  • inner() 定义时,“捕获”了外层 outer() 的作用域(这叫词法作用域,定义时决定,而非运行时)。

  • outer() 执行完毕时,a 本应被销毁。

  • 但由于 inner 函数还在引用 a,这个变量不会被垃圾回收 —— 这就是闭包机制产生的根源

2)JS 的函数创建过程:生成执行上下文 + 闭包绑定环境

简化版的机制如下:

function outer() {
    var a = 10;
    function inner() {
        console.log(a);
    }
    return inner;
}

编译器会将 inner 和其引用到的外部变量 a 绑定在一起,称为闭包。

可以理解为内部函数携带了一个“包裹环境”:

inner = {
    code: "console.log(a)",
    environment: { a: 10 }
}

当调用 inner(),JS 引擎会用它绑定的 environment 来解析变量。

3)闭包保持变量的“引用”,而非值的“拷贝”

function counter() {
    let count = 0;
    return function() {
        count++;
        console.log(count);
    };
}

const c = counter();
c(); // 1
c(); // 2

注意,这里 count 并不是每次调用时初始化,而是闭包中的变量仍保持引用,即使函数执行多次,这些变量仍然是同一个内存空间中的变量。

4)内部函数“维持”外部函数活着

function outer() {
    let secret = 42;
    return function inner() {
        console.log(secret);
    };
}

const fn = outer();  // outer() 返回 inner,secret 被闭包捕获
// outer 执行完,但 secret 仍未被 GC
fn();  // 能访问 secret

这是闭包的关键特性:

  • 外部函数执行完毕,正常变量应销毁

  • 但因内部函数持有其引用,变量得以“继续存在”

所以闭包本质是一种变量持久化机制

2. 闭包的运行机制图(逻辑结构)

可以把闭包看作下图的结构(抽象视角):

┌────────────────────────────┐
│ function inner() { ... }   │
│     ↑                      │
│     └── [[Environment]] → {a: 1}  <-- outer 的作用域环境
└────────────────────────────┘

3. 闭包为何能用于 JS 加密/混淆?

因为:

  • 私有变量不可从外部直接访问

  • 可以构建仅在闭包中可见的“密钥、算法、状态”

  • 逆向时必须“打断作用域”,把这些“封起来的变量”还原出来

例如:

(function(){
    var key = "abc123";
    window.encrypt = function(data) {
        return data + key;
    };
})();

加密逻辑隐藏在闭包中,你无法直接从 window.encrypt 看到 key,只能通过分析闭包提取变量。

总结

闭包是函数 + 它定义时所“记住的外部作用域”,是 JS 中保持私有状态、构建封闭模块、模拟 OOP 的基础机制。


二、闭包的作用和泄漏问题

1. 闭包的作用(正面用途)

闭包是 JavaScript 的核心能力,具有很多正向作用

1)模拟私有变量(JS 没有真正的 private)

function createCounter() {
    let count = 0;  // 私有变量
    return {
        increment() { count++; return count; },
        decrement() { count--; return count; }
    };
}
const counter = createCounter();
console.log(counter.increment());  // 1
console.log(counter.count);       // undefined,外部无法访问

作用:封装状态,防止变量被外部随意修改(很多加密代码用这种方式隐藏“key”或“状态”)。

2)工厂函数、函数柯里化(提升复用性)

function makeAdder(x) {
    return function(y) {
        return x + y;
    }
}
const add5 = makeAdder(5);
console.log(add5(10));  // 15

作用:创建参数预设的函数,提升函数抽象能力。

3)保持状态

function remember() {
    let history = [];
    return function (val) {
        history.push(val);
        return history;
    };
}
const save = remember();
console.log(save('a')); // ['a']
console.log(save('b')); // ['a', 'b']

作用:实现“记忆型”逻辑,如缓存、历史记录等。

4)模块化封装(IIFE)

const module = (function() {
    let secret = 'xxx';
    function getSecret() { return secret; }
    return { getSecret };
})();

作用:在早期没有模块系统的 JS 中,闭包是模拟模块、隔离变量的唯一方案。

2. 闭包的泄漏问题(负面作用)

虽然闭包强大,但用不好会导致内存泄漏 —— 闭包引用的变量常驻内存,无法释放。

什么是内存泄漏?

内存泄漏指无用的对象仍被引用,无法被垃圾回收(GC)清理,导致内存增长、性能下降甚至崩溃。

常见闭包导致的泄漏场景

1)事件监听器 + 闭包

function bind() {
    let bigData = new Array(1000000).fill('*');
    document.getElementById('btn').addEventListener('click', function() {
        console.log(bigData.length);
    });
}
  • 闭包引用了 bigData

  • addEventListener 持有函数引用

  • bigData 永远不会被回收,除非 removeEventListener

2)定时器引用闭包变量

function startTimer() {
    let largeObj = { ... };
    setInterval(() => {
        console.log(largeObj);
    }, 1000);
}
  • setInterval 会无限引用闭包环境,largeObj 永远存在

  • 必须 clearInterval 才能解除引用

3)闭包 + DOM 引用

function leakDOM() {
    let dom = document.getElementById('element');
    return function() {
        console.log(dom.innerHTML);
    }
}

即使 DOM 从页面移除,dom 变量因闭包存在仍不能释放,形成“悬挂 DOM 引用”

4)缓存未控制生命周期

let cache = (function() {
    let map = {};
    return {
        set(key, val) { map[key] = val },
        get(key) { return map[key] }
    };
})();

闭包中 map 没有限制大小或 TTL(超时),可能无限增长导致内存暴涨

如何避免闭包泄漏?

方法原因
及时解绑事件监听器removeEventListener 释放闭包引用
清除定时器clearInterval / clearTimeout
手动断开闭包引用将变量设为 null
限制缓存生命周期加 TTL 或 LRU 策略控制内存占用
不要过度嵌套闭包函数保持可维护性、易清理性

3. 闭包泄漏在逆向中的意义

很多加密代码:

(function() {
    var _secret = "加密用密钥";
    function encrypt(data) {
        return AES(data, _secret);
    }
    window.myEncrypt = encrypt;
})();

这种结构:

  • _secret 被闭包隐藏

  • debugger 断点进去,encrypt.[[Scopes]] 中可以查到 _secret

  • 或使用 AST / Babel 分析闭包结构,还原所有作用域变量

总结

维度闭包的作用闭包的风险
私有封装隐藏内部变量,形成“模块”外部无法直接访问,逆向难
持久状态保留执行上下文和变量容易导致变量无法被释放
工厂函数构建个性化函数实例可能间接产生大量引用
逆向分析拆出混淆变量、解密逻辑需打破闭包还原环境

三、作用域

1. 作用域 vs 执行上下文:区别与联系

概念说明
作用域(Scope)定义阶段决定的变量可访问范围,是静态结构
执行上下文(Execution Context)函数运行时创建的临时环境,包含作用域链、变量、this 等,属于动态结构

举例:

function foo() {
  var a = 10;
  function bar() {
    console.log(a); // 找 a:bar 执行上下文 → bar 的作用域链 → foo → global
  }
  bar();
}
foo();

执行上下文在函数调用时创建,它持有一条作用域链来决定变量的可见性。

2. 词法作用域(静态作用域)

JS 使用 词法作用域(Lexical Scope):变量查找的路径在代码书写阶段就确定了,跟运行在哪无关。

举例:

var x = 1;
function outer() {
  var x = 2;
  function inner() {
    console.log(x);
  }
  return inner;
}
var fn = outer();
fn(); // 输出 2,而不是 1!

虽然 fn() 是在全局执行,但它的作用域链已经在定义时绑定好了(outer → global)

3. 作用域链(Scope Chain)

当访问一个变量时,JS 引擎会:

  • 先查当前函数的变量对象(Activation Object);

  • 如果找不到,往上一层作用域找(父作用域);

  • 一直找,直到全局作用域或找不到报错。

作用域链就是每个执行上下文中保存的一个链表结构,它链接了当前环境与其父环境。

4. 变量对象 & 作用域链构建

每个函数执行上下文内部维护三个关键组件:

名称内容
变量对象(VO)当前上下文中的变量(包括函数参数)
作用域链(Scope Chain)当前 + 所有上层作用域的链
this当前执行环境绑定的对象

5. 变量提升(Hoisting)和作用域的冲突点

var 变量和函数声明会被提升到作用域顶部:

function demo() {
  console.log(a); // undefined,不是报错
  var a = 10;
}

实际执行相当于:

function demo() {
  var a;
  console.log(a); // undefined
  a = 10;
}

注意:

  • letconst 不会被提升,访问前会报错(TDZ - 暂时性死区);

  • 函数声明会整体提升,但函数表达式不会。

6. 闭包的作用域链快照特性

闭包本质是函数保留了定义时作用域链的快照,哪怕外部函数早就执行完了,内部函数依然能访问其变量。

function outer() {
  var x = 42;
  return function inner() {
    return x;
  }
}
var f = outer();
console.log(f()); // 42

即使 outer 的上下文销毁了,它的变量 x 仍被闭包引用。

总结

内容
🔹 作用域是静态的函数定义时就决定了作用域链
🔹 执行上下文是动态的函数每次调用都会创建新上下文
🔹 作用域链控制变量访问顺序查不到才往上层作用域找
🔹 闭包保留定义时作用域链所以能访问“失效”的父函数变量
🔹 混淆常依赖作用域藏变量熟练追踪作用域链可还原逻辑

四、JS 内存模型、垃圾回收(Mark & Sweep)

1. JavaScript 内存模型(Memory Model)

JavaScript 的运行环境(如 V8)中,内存大体可分为两个区域:

1)栈(Stack)内存:用于存储原始类型变量和执行上下文信息

  • 原始类型:NumberStringBooleanundefinednullSymbolBigInt

  • 执行上下文(函数调用时的作用域、参数)

  • 存取速度快,生命周期短,先进后出(LIFO)

let a = 10;   // 存储在栈中
let b = "hi"; // 字符串是原始值,也在栈中

2)堆(Heap)内存:用于存储复杂对象和函数

  • 对象、数组、函数、闭包、DOM 元素引用等

  • 内存空间大,结构灵活,但访问速度慢

  • GC(垃圾回收器)主要负责这部分的内存回收

let obj = { name: "Tom" };  // obj 是栈变量,指向堆中对象
let arr = [1, 2, 3];        // 数组也存堆

2. 内存生命周期(Memory Lifecycle)

  • 分配阶段

    JS 在变量声明、函数调用、创建对象时会分配内存空间。

  • 使用阶段

    JS 引擎读取变量、执行代码,使用这些内存中的数据。

  • 释放阶段(GC)

    当内存不再被使用(变量“不可达”),由垃圾回收器负责释放。

3. 垃圾回收机制:Mark and Sweep(标记-清除)

JavaScript 的主流 GC 算法是 Mark-and-Sweep,用于回收堆内存中“无用对象”。

步骤详解:

   Step 1:从根对象(Root)出发“标记”可达对象

根对象有:

  • 全局对象(浏览器中是 window,Node 是 global

  • 当前调用栈中的变量、函数参数

  • 闭包中引用的变量(只要有引用链)

let obj = { name: "Tom" }; // obj 是栈变量,引用堆

GC 会从 window.obj 出发,递归遍历其所有属性引用。

  Step 2:“扫描”堆中所有对象

  • 被标记为“可达”的:保留

  • 未被标记的对象:不可达(unreachable)

  Step 3:清除这些“不可达对象”的内存

堆内存对象列表:
├── obj1 
├── obj2 
├── obj3 (未被引用)
└── obj4 (未被引用)

GC 会删除 obj3、obj4 占用的内存。

可达性图示例(引用图)

let a = {
    b: {
        c: 1
    }
};

引用图(根:window):

window
  └── a
        └── b
              └── c

GC 会从 window 出发一路向下,发现这些对象都“可达”,所以不会被回收。

但:

let a = { b: { c: 1 } };
a = null;
  • 此时,原本链条断了(a 赋值为 null)

  • bc 也变成不可达

  • GC 会把整个 { b: { c: 1 } } 回收

4. 为什么 JS 会内存泄漏(GC 不等于万能)

即使有 GC,也不能防止以下 “人为错误造成的强引用”

1)闭包未释放外部引用

function outer() {
    let largeObj = new Array(1000000);
    return function inner() {
        console.log(largeObj.length);
    };
}
const closure = outer(); // closure 保持对 largeObj 的引用

largeObj 永远不会被释放

2)定时器引用未清除

let obj = { name: "leak" };
setInterval(() => {
    console.log(obj.name); // obj 一直被引用
}, 1000);

objsetInterval 捕获,永远不会释放

3)全局变量或 window 属性引用

window.leak = { a: 123 };  // 永远可达

5. 如何在 Chrome DevTools 分析 GC 与内存结构

打开 Chrome 开发者工具 → Memory:

  • Heap snapshot

    • 拍摄堆快照,查找未释放对象

    • 查看对象之间的引用链

  • Allocation instrumentation on timeline

    • 分析哪些函数持续分配内存但未释放

  • Record Allocations

    • 跟踪函数分配内存的行为与时间点

结合 JS 混淆/逆向分析视角看内存

在 JS 加密中:

  • 加密逻辑常藏在闭包里

  • 闭包保持对密钥/算法的堆引用

  • 如果找不到“根引用”,这些变量就会“失联”

  • 所以逆向时必须模拟作用域链、打断闭包结构

总结

JS 内存分为栈(原始 + 上下文)与堆(复杂对象);Mark & Sweep 从“根”出发,标记可达对象,清除其余;闭包、定时器、DOM 残留是常见泄漏来源;逆向必须熟悉内存引用链,才能精准还原变量与逻辑。


五、常见内存泄露场景

内存泄露 = 程序中已经不再使用的内存却没有被释放,仍然被引用。

  • 导致 GC 无法释放

  • 会让堆内存持续增长 → 变慢甚至崩溃

  • 前端中常表现为:页面越来越卡、响应越来越慢、浏览器崩溃

1. JavaScript 中常见的 6 大内存泄漏场景

1)闭包引用外部变量未释放(最常见)

原因:闭包长期持有对外部变量的引用,导致外层函数的局部变量无法被 GC。

示例:

function outer() {
    let largeData = new Array(1e6).fill("data");
    return function inner() {
        console.log(largeData.length);
    };
}

let leaky = outer(); // largeData 被 inner 一直引用

largeData 原本应该在 outer 执行完后释放,但 inner 持有它,导致内存泄露。

解决:

  • 不要长期持有不必要的闭包

  • 用完后设置为 null 或用 WeakRef/WeakMap

2)定时器(setInterval / setTimeout)未清除

原因:函数体内引用了外部对象,如果定时器一直运行,就会导致这些对象无法释放。

示例:

let obj = { data: new Array(1e6).fill("leak") };
setInterval(() => {
    console.log(obj.data[0]);
}, 1000);  // obj 永远被引用

危害:长时间运行页面,内存会持续增长,最终崩溃。

解决:

  • 页面销毁或不再需要时调用 clearInterval

  • WeakMap 保存定时器上下文

3)全局变量(或 window 属性)

原因:全局变量永远不会被 GC,因为它们始终可达。

示例:

window.leak = { bigData: new Array(1e6) }; // 永远不会被回收

解决:

  • 不要滥用 var(会挂载到 window),用 let/const

  • 不主动在 window 上挂属性

4)DOM 引用未清除(特别常见于 SPA 项目)

原因:JS 对象引用了 DOM 元素,页面虽然删除了该 DOM,但引用没断,导致 DOM 节点无法释放。

示例:

let dom = document.getElementById("btn");
let obj = {
    handler: function () {
        dom.addEventListener("click", () => {
            console.log(dom.id);
        });
    }
};

然后在 HTML 中把 #btn 删除了 → JS 仍然持有它!

解决:

  • 卸载组件时移除监听器 removeEventListener

  • 断开 JS 到 DOM 的引用(dom = null

5)缓存未清理

原因:手动缓存一些数据时忘了清理,特别是在单页应用中。

示例:

let cache = {};
function loadData(key, data) {
    cache[key] = data;
}

长时间运行后 cache 占满内存,GC 无法清除。

解决:

  • 用 LRU 算法限制缓存大小

  • 使用 WeakMapWeakSet 做缓存(会自动 GC)

6)事件监听器引用上下文变量

原因:事件回调函数引用了外部变量或闭包变量,但事件监听没有移除

示例:

function setup() {
    let huge = new Array(1e6).fill("data");
    document.body.addEventListener("click", () => {
        console.log(huge.length);
    });
}

huge 永远被事件回调引用

解决:

  • 页面卸载时手动 removeEventListener

  • 避免闭包 + 事件绑定组合滥用

2. 内存泄露在逆向工程中的实战意义

1)分析闭包引用链:

  • 判断某个混淆逻辑是否依赖上下文变量(如关键密钥)

2)重复运行脚本查看堆快照变化:

  • 找出“变量未被释放”的真实引用来源

3)分析函数是否“绑定事件但未解绑”:

  • 混淆代码中常这样绑定事件,隐藏真实入口

3. 实战工具推荐

工具用法
 Chrome DevTools - MemoryHeap snapshot、Timeline、Detectors
 Chrome Lighthouse分析页面内存使用情况
 LeakCanary(Android)检查 Android APP 的 JSBridge 泄露
 WeakMap/WeakRef用于管理“可自动释放”的引用

4. 总结

闭包 + 定时器 + DOM 引用 + 全局变量,是 JS 中四大内存泄露陷阱;熟悉使用 Chrome Memory 工具找引用链,逆向时能找到隐藏变量、伪闭包和混淆逻辑的真实来源。


六、JS 加密/混淆中的闭包包裹逻辑与拆解技巧

闭包具有两个重要特性:

  • 作用域隔离:变量不会暴露在全局,外部访问不到;

  • 持久引用:内部函数可访问外部函数变量。

加密者利用这些特性,将核心算法、关键字符串、执行逻辑等隐藏在闭包内部,让你看不到、改不了、猜不透。

1. 常见“闭包包裹私有逻辑”模式

模式 1:自执行闭包隐藏函数
(function(){
  var secret = "key123";
  function encode(str) {
    return str.split('').map(c => c + secret[0]).join('');
  }
  window._encode = encode;
})();

分析:

  • secret 是私有变量,外部无法访问;

  • encode 依赖 secret,且被挂到全局;

  • 这是一种常见加密函数包裹模式

拆解方式:

  • 目标是还原 encode 的行为;

  • 思路 1:打断点调试 encode,看 secret 真实值;

  • 思路 2:将闭包修改为可见代码:

var secret = "key123";
function encode(str) {
  return str.split('').map(c => c + secret[0]).join('');
}
window._encode = encode;
模式 2:传参闭包 + 字符串加密
(function(x, y){
  var secret = x + y;
  window.decrypt = function(str) {
    return atob(str).split('').reverse().join('') + secret;
  };
})("abc", "123");

分析:

  • secret 是根据闭包入参构造;

  • 加密者故意通过闭包参数传关键值;

  • 函数体执行后只暴露 decrypt 接口。

拆解方式:

  • 方式 1:追踪 decrypt 输入输出行为;

  • 方式 2:在执行前手动记录闭包传参值:

var secret = "abc123";
function decrypt(str) {
  return atob(str).split('').reverse().join('') + secret;
}
window.decrypt = decrypt;
模式 3:函数工厂 + 多重闭包
var tool = (function(){
  var key = "magic";
  return {
    encrypt: function(s) {
      return s + key;
    },
    decode: (function(){
      let reverse = str => str.split('').reverse().join('');
      return function(s) {
        return reverse(s) + key;
      }
    })()
  };
})();

分析:

  • 闭包返回对象;

  • 多重嵌套闭包隐藏 key

  • 所有函数都能访问 key,但外部不可改。

拆解方式:

  • 利用浏览器调试查看 tool.encodetool.decode 执行结果;

  • 也可以手动复原:

var key = "magic";
var tool = {
  encrypt: function(s) {
    return s + key;
  },
  decode: function(s) {
    return s.split('').reverse().join('') + key;
  }
};
模式 4:混淆 + eval + 闭包组合
(function(){
  var _ = function(a){ return a.split('').reverse().join(''); };
  var code = ")(321'cba'(gol.elosnoc";  // 实际是:console.log('abc123')
  eval(_(code));
})();

分析:

  • 闭包隐藏了 _code

  • 核心逻辑是 eval(_) 执行还原代码;

  • 这种写法多用于混淆加壳或构造函数隐藏关键逻辑。

拆解方式:

  • 打断点在 eval 前,看 _(code) 的返回值;

  • 或者打印中间值:

var _ = function(a){ return a.split('').reverse().join(''); };
var code = ")(321'cba'(gol.elosnoc";
console.log(_(code)); // 打印真实执行代码

2. 进阶闭包混淆:闭包+eval+数组映射

示例:

(function(){
  var _table = ['abc', 'def', 'ghi'];          // 私有字符串映射表
  function map(idx){ return _table[idx]; }     // 映射函数,根据索引取值
  function exec(code){ return eval(map(code)); } // 闭包中的 eval 调用
  window.run = exec;                           // 向外暴露接口函数 run
})();

结构总览:

组成含义
(function(){ ... })()自执行闭包,构造私有作用域,保护 _table
_table映射表,隐藏真实字符串代码
map(idx)通过索引获取字符串的函数,用于解混淆
exec(code)实际的执行函数,调用 eval(map(idx))
window.run = exec暴露接口给外部,但不暴露私有 _table

拆解步骤:

1)还原 _table 内容

var _table = ['abc', 'def', 'ghi'];

2)替代 map() 调用

function map(idx){ return _table[idx]; }
map(1); // → "def"

3)去除闭包作用域保护(展开成全局)

var _table = ['abc', 'def', 'ghi'];
function run(code){
  return eval(_table[code]);
}

4)还原所有 run(n) 的调用逻辑

如果你抓包/调试网页的时候看到:

run(1);

等价于:

eval("def");

补充:

真实场景中,_table 会被:

  • 用 base64 加密:_table = ["YWJj", "ZGVm"]atob(_table[i])

  • 随机变量名:var a = ['xyz']; var b = function(x){ return a[x]; }

  • 混入逻辑判断、防调试语句

  • 多级嵌套闭包、动态构造字符串:_table[i]+_table[j]

3. 如何系统性地“拆闭包”?

技术说明
Beautify 格式化js-beautify / 浏览器 DevTools 让结构清晰
调试打断点找到闭包内函数执行点,查看变量
替换闭包为全局函数复制逻辑,解除作用域限制
使用控制台打印变量插入 console.log,观察变量
Patch + Hook用代码“钩子”截获闭包内函数/变量
AST 分析用 Babel 把闭包结构解析为 AST,逐层提取

总结

JavaScript 加密/混淆中,闭包是用于隐藏关键逻辑和变量的核心手段,掌握闭包边界识别、变量提取、函数重构,就能有效逆向还原被包裹的核心逻辑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2371869.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据链路层(MAC 地址)

目录 一、前言&#xff1a; 二、以太网&#xff1a; 三、MAC 地址的作用&#xff1a; 四、ARP协议&#xff1a; 一、前言&#xff1a; 数据链路层主要负责相邻两个节点之间的数据传输&#xff0c;其中&#xff0c;最常见数据链路层的协议有 以太网&#xff08;通过光纤 / 网…

基于DQN的自动驾驶小车绕圈任务

1.任务介绍 任务来源: DQN: Deep Q Learning &#xff5c;自动驾驶入门&#xff08;&#xff1f;&#xff09; &#xff5c;算法与实现 任务原始代码: self-driving car 最终效果&#xff1a; 以下所有内容&#xff0c;都是对上面DQN代码的改进&#…

【Linux】Linux工具(1)

3.Linux工具&#xff08;1&#xff09; 文章目录 3.Linux工具&#xff08;1&#xff09;Linux 软件包管理器 yum什么是软件包关于 rzsz查看软件包——yum list命令如何安装软件如何卸载软件补充——yum如何找到要安装软件的下载地址 Linux开发工具Linux编辑器-vim使用1.vim的基…

基于 Spring Boot 瑞吉外卖系统开发(十一)

基于 Spring Boot 瑞吉外卖系统开发&#xff08;十一&#xff09; 菜品启售和停售 “批量启售”、“批量停售”、操作列的售卖状态绑定单击事件&#xff0c;触发单击事件时&#xff0c;最终携带需要修改售卖状态的菜品id以post请求方式向“/dish/status/{params.status}”发送…

深入理解负载均衡:传输层与应用层的原理与实战

目录 前言1. 传输层&#xff08;Layer 4&#xff09;负载均衡1.1 工作层级与核心机制1.2 实现方式详解1.3 优缺点分析1.4 典型实现工具 2. 应用层&#xff08;Layer 7&#xff09;负载均衡2.1 工作层级与核心机制2.2 实现方式解析2.3 优缺点分析2.4 常用实现工具 3. Layer 4 与…

WPF之Slider控件详解

文章目录 1. 概述2. 基本属性2.1 值范围属性2.2 滑动步长属性2.3 刻度显示属性2.4 方向属性2.5 选择范围属性 3. 事件处理3.1 值变化事件3.2 滑块拖动事件 4. 样式和模板自定义4.1 基本样式设置4.2 控件模板自定义 5. 数据绑定5.1 绑定到ViewModel5.2 同步多个控件 6. 实际应用…

企业微信自建消息推送应用

企业微信自建应用来推送消息 前言 最近有个给特定部门推送消息的需求&#xff0c;所以配置一个应用专门用来推送消息。实现过程大致为&#xff1a;服务器生成每天的报告&#xff0c;通过调用API来发送消息。以前一直都是发邮件&#xff0c;整个邮箱里全是报告文件&#xff0c…

日志之ClickHouse部署及替换ELK中的Elasticsearch

文章目录 1 ELK替换1.1 Elasticsearch vs ClickHouse1.2 环境部署1.2.1 zookeeper 集群部署1.2.2 Kafka 集群部署1.2.3 FileBeat 部署1.2.4 clickhouse 部署1.2.4.1 准备步骤1.2.4.2 添加官方存储库1.2.4.3 部署&启动&连接1.2.4.5 基本配置服务1.2.4.6 测试创建数据库和…

解构与重构:自动化测试框架的进阶认知之旅

目录 一、自动化测试的介绍 &#xff08;一&#xff09;自动化测试的起源与发展 &#xff08;二&#xff09;自动化测试的定义与目标 &#xff08;三&#xff09;自动化测试的适用场景 二、什么是自动化测试框架 &#xff08;一&#xff09;自动化测试框架的定义 &#x…

DockerDesktop替换方案

背景 由于DockerDesktop并非开源软件&#xff0c;如果在公司使用&#xff0c;可能就有一些限制&#xff0c;那是不是除了使用DockerDesktop外&#xff0c;就没其它办法了呢&#xff0c;现在咱们来说说替换方案。 WSL WSL是什么&#xff0c;可自行百度&#xff0c;这里引用WS…

力扣热题100之搜索二维矩阵 II

题目 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。 每列的元素从上到下升序排列。 代码 方法一&#xff1a;直接全体遍历 这个方法很直接&#xff0c;但是居然没有超时&#xff0c…

docker操作镜像-以mysql为例

Docker安装使用-CSDN博客 docker操作镜像-以mysql为例 当安装一个新的镜像时可以登录https://hub.docker.com/直接搜索想要安装的镜像&#xff0c;查看文档 1&#xff09;拉取镜像 docker pull mysql 或者 docker pull mysql:版本号 然后直接跳到第4&#xff09;步即可 2…

使用OpenCV 和 Dlib 进行卷积神经网络人脸检测

文章目录 引言1.准备工作2.代码解析2.1 导入必要的库2.2 加载CNN人脸检测模型2.3 加载并预处理图像2.4 进行人脸检测2.5 绘制检测结果2.6 显示结果 3.完整代码4.性能考虑5.总结 引言 人脸检测是计算机视觉中最基础也最重要的任务之一。今天我将分享如何使用dlib库中的CNN人脸检…

React 实现 JWT 登录验证的最小可运行示例

下面是一个用 React 实现 JWT 登录验证的最小可运行示例&#xff0c;包含&#xff1a; React 前端&#xff1a;登录、保存 Token、获取用户数据。模拟后端&#xff1a;用 mock API&#xff08;你也可以接真后端&#xff09;。 &#x1f9f1; 技术栈 React&#xff08;使用 Vi…

Power Query精通指南1:查询结构设计、数据类型、数据导入与迁移(平面文件、Excel、Web)

文章目录 零、Power Query简介0.1 Power Query 主要功能0.2 Power Query 的优势0.3 Power Query 组件 一、Power Query数据处理基本流程1.1 前期准备1.2 提取1.3 转换1.3.1 Power Query 编辑器界面1.3.2 默认转换1.3.3 自定义转换 1.4 加载1.4.1 自动检测数据类型1.4.2 重命名查…

vue2开发者sass预处理注意

vue2开发者sass预处理注意 sass的预处理器&#xff0c;早年使用node-sass&#xff0c;也就是vue2最初默认的编译器。 sass官方推出了dart-sass来替代。 node-sass已经停维很久了。 vue3默认使用的是dart-sass。 Uniapp的官方文档截图 从 HBuilderX 4.56 &#xff0c;vue2 …

使用 Selenium 爬取动态网页数据 —— 实战与坑点详解

本文记录了笔者在爬取网页数据过程中遇到的各种技术挑战&#xff0c;包括页面动态渲染、JavaScript 注入等问题&#xff0c;并最终给出一个可运行的完整方案。 文章目录 网页获取不到数据&#x1f680; 尝试用 Selenium 渲染页面 网页获取不到数据 某网页数据依赖大量 JavaSc…

守护数字家园:个人博客安全防护指南

前言 在之前的文章《WordPress个人博客搭建&#xff08;一&#xff09;》《WordPress个人博客搭建&#xff08;二&#xff09;》《WordPress个人博客搭建&#xff08;三&#xff09;》中&#xff0c;我们已经在非凡云云服务器上&#xff0c;借助1Panel搭建起属于自己的数字庭院…

【网络编程】三、TCP网络套接字编程

文章目录 TCP通信流程Ⅰ. 服务器日志类实现Ⅱ. TCP服务端1、服务器创建流程2、创建套接字 -- socket3、绑定服务器 -- bind&#x1f38f;4、服务器监听 -- listen&#x1f38f;5、获取客户端连接请求 -- acceptaccept函数返回的套接字描述符是什么&#xff0c;不是已经有一个了…

trae ai编程工具

Trae&#xff0c;致力于成为真正的 AI 工程师&#xff08;The Real Al Engineer&#xff09;。Trae 旗下的 AI IDE 产品&#xff0c;以智能生产力为核心&#xff0c;无缝融入你的开发流程&#xff0c;与你默契配合&#xff0c;更高质量、高效率完成每一个任务。 版本差异 国内…