关于 js:3. 闭包、作用域、内存模型
一、闭包的本质:函数 + 其词法作用域环境
闭包(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); // undefineda = 10;
}
注意:
-
let
和const
不会被提升,访问前会报错(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)内存:用于存储原始类型变量和执行上下文信息
-
原始类型:
Number
、String
、Boolean
、undefined
、null
、Symbol
、BigInt
-
执行上下文(函数调用时的作用域、参数)
-
存取速度快,生命周期短,先进后出(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) -
b
、c
也变成不可达 -
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);
→ obj
被 setInterval
捕获,永远不会释放
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 算法限制缓存大小
-
使用
WeakMap
或WeakSet
做缓存(会自动 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 - Memory | Heap 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.encode
、tool.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 加密/混淆中,闭包是用于隐藏关键逻辑和变量的核心手段,掌握闭包边界识别、变量提取、函数重构,就能有效逆向还原被包裹的核心逻辑。
相关文章:
关于 js:3. 闭包、作用域、内存模型
一、闭包的本质:函数 其词法作用域环境 闭包(Closure)的本质可以概括为: 闭包是一个函数,以及它定义时捕获的词法作用域中的变量集合。 这意味着:即使外部函数已经返回或作用域结束,只要有内…...

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

基于DQN的自动驾驶小车绕圈任务
1.任务介绍 任务来源: DQN: Deep Q Learning |自动驾驶入门(?) |算法与实现 任务原始代码: self-driving car 最终效果: 以下所有内容,都是对上面DQN代码的改进&#…...
terraform resource创建了5台阿里云ecs,如要使用terraform删除其中一台主机,如何删除?
在 Terraform 中删除阿里云 5 台 ECS 实例中的某一台,具体操作取决于你创建资源时使用的 多实例管理方式(count 或 for_each)。以下是详细解决方案: 方法一:使用 for_each(推荐) 如果创建时使…...

【Linux】Linux工具(1)
3.Linux工具(1) 文章目录 3.Linux工具(1)Linux 软件包管理器 yum什么是软件包关于 rzsz查看软件包——yum list命令如何安装软件如何卸载软件补充——yum如何找到要安装软件的下载地址 Linux开发工具Linux编辑器-vim使用1.vim的基…...
探索大语言模型(LLM):词袋法(Bag of Words)原理与实现
文章目录 引言一、词袋法原理1.1 核心思想1.2 实现步骤 二、数学公式2.1 词频表示2.2 TF-IDF加权(可选) 三、示例表格3.1 构建词汇表3.2 文本向量化(词频) 四、Python代码实现4.1 基础实现(手动计算)4.2 输…...
vue引入物理引擎matter.js
vue引入物理引擎matter.js 在 Vue 项目中集成 Matter.js 物理引擎的步骤如下: 1. 安装 Matter.js npm install matter-js # 或 yarn add matter-js2. 创建 Vue 组件 <template><div ref="physicsContainer" class="physics-container"><…...

基于 Spring Boot 瑞吉外卖系统开发(十一)
基于 Spring Boot 瑞吉外卖系统开发(十一) 菜品启售和停售 “批量启售”、“批量停售”、操作列的售卖状态绑定单击事件,触发单击事件时,最终携带需要修改售卖状态的菜品id以post请求方式向“/dish/status/{params.status}”发送…...
支持鸿蒙next的uts插件
*本文共四个功能函数,相当于四个插件。作者为了偷懒写成了一个插件,调对应的函数即可。 1、chooseImageHarmony函数:拉起相册选择图片并转为Base64 2、takePhotoAndConvertToBase64函数:拉起相机拍照并转为Base64 3、openBrows…...

深入理解负载均衡:传输层与应用层的原理与实战
目录 前言1. 传输层(Layer 4)负载均衡1.1 工作层级与核心机制1.2 实现方式详解1.3 优缺点分析1.4 典型实现工具 2. 应用层(Layer 7)负载均衡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. 实际应用…...
极狐GitLab 如何将项目共享给群组?
极狐GitLab 是 GitLab 在中国的发行版,关于中文参考文档和资料有: 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 共享项目和群组 (BASIC ALL) 在极狐GitLab 16.10 中,更改为在成员页面的成员选项卡上显示被邀请群组成员…...

企业微信自建消息推送应用
企业微信自建应用来推送消息 前言 最近有个给特定部门推送消息的需求,所以配置一个应用专门用来推送消息。实现过程大致为:服务器生成每天的报告,通过调用API来发送消息。以前一直都是发邮件,整个邮箱里全是报告文件,…...
【React】Hooks useReducer 详解,让状态管理更可预测、更高效
1.背景 useReducer是React提供的一个高级Hook,没有它我们也可以正常开发,但是useReducer可以使我们的代码具有更好的可读性,可维护性。 useReducer 跟 useState 一样的都是帮我们管理组件的状态的,但是呢与useState不同的是 useReducer 是集…...

日志之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 测试创建数据库和…...
亚远景-ASPICE vs ISO 21434:汽车软件开发标准的深度对比
ASPICE(Automotive SPICE)和ISO 21434是汽车软件开发领域的两大核心标准,分别聚焦于过程质量与网络安全。以下从核心目标、覆盖范围、实施重点、协同关系及行业价值五个维度进行深度对比分析: 一、核心目标对比 ASPICE࿱…...
51单片机快速成长路径
作为在嵌入式领域深耕18年的工程师,分享一条经过工业验证的51单片机快速成长路径,全程干货无注水: 一、突破认知误区(新手必看) 不要纠结于「汇编还是C」:现代开发90%场景用C,掌握指针和内存管…...
使用 NGINX 实现 HTTP Basic 认证ngx_http_auth_basic_module 模块
一、前言 在 Web 应用中,对部分资源进行访问控制是十分常见的需求。除了基于 IP 限制、JWT 验证、子请求校验等方式外,最经典也最简单的一种方式便是 HTTP Basic Authentication。NGINX 提供的 ngx_http_auth_basic_module 模块支持基于用户名和密码的基…...

解构与重构:自动化测试框架的进阶认知之旅
目录 一、自动化测试的介绍 (一)自动化测试的起源与发展 (二)自动化测试的定义与目标 (三)自动化测试的适用场景 二、什么是自动化测试框架 (一)自动化测试框架的定义 &#x…...

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

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

docker操作镜像-以mysql为例
Docker安装使用-CSDN博客 docker操作镜像-以mysql为例 当安装一个新的镜像时可以登录https://hub.docker.com/直接搜索想要安装的镜像,查看文档 1)拉取镜像 docker pull mysql 或者 docker pull mysql:版本号 然后直接跳到第4)步即可 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 登录验证的最小可运行示例,包含: React 前端:登录、保存 Token、获取用户数据。模拟后端:用 mock API(你也可以接真后端)。 🧱 技术栈 React(使用 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的预处理器,早年使用node-sass,也就是vue2最初默认的编译器。 sass官方推出了dart-sass来替代。 node-sass已经停维很久了。 vue3默认使用的是dart-sass。 Uniapp的官方文档截图 从 HBuilderX 4.56 ,vue2 …...
淘宝按图搜索商品(拍立淘)Java 爬虫实战指南
在电商领域,按图搜索商品功能为用户提供了更直观、便捷的购物体验。淘宝的拍立淘功能更是凭借其强大的图像识别技术,成为许多开发者和商家关注的焦点。本文将详细介绍如何利用 Java 爬虫技术实现淘宝按图搜索商品功能,包括注册账号、上传图片…...
安卓基础(封装引用)
情况 1:普通 Java 项目(非 Android) src/ ├── com/ │ ├── example/ │ │ ├── utils/ │ │ │ └── A.java // 工具类 A │ │ └── main/ │ │ └── B.java // 主类 B A…...
深入理解 Docker 网络原理:构建高效、灵活的容器网络
在现代软件开发中,Docker 已经成为了容器化技术的代名词,广泛应用于开发、测试和生产环境。Docker 使得开发者能够将应用及其依赖打包成一个轻量级的容器,并通过 Docker 容器化技术来实现高效的部署与管理。 然而,在日常使用 Dock…...

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