京东前端二面常考手写面试题(必备)
实现发布-订阅模式
class EventCenter{// 1. 定义事件容器,用来装事件数组let handlers = {}// 2. 添加事件方法,参数:事件名 事件方法addEventListener(type, handler) {// 创建新数组容器if (!this.handlers[type]) {this.handlers[type] = []}// 存入事件this.handlers[type].push(handler)}// 3. 触发事件,参数:事件名 事件参数dispatchEvent(type, params) {// 若没有注册该事件则抛出错误if (!this.handlers[type]) {return new Error('该事件未注册')}// 触发事件this.handlers[type].forEach(handler => {handler(...params)})}// 4. 事件移除,参数:事件名 要删除事件,若无第二个参数则删除该事件的订阅和发布removeEventListener(type, handler) {if (!this.handlers[type]) {return new Error('事件无效')}if (!handler) {// 移除事件delete this.handlers[type]} else {const index = this.handlers[type].findIndex(el => el === handler)if (index === -1) {return new Error('无该绑定事件')}// 移除事件this.handlers[type].splice(index, 1)if (this.handlers[type].length === 0) {delete this.handlers[type]}}}
}
手写 new 操作符
在调用 new
的过程中会发生以上四件事情:
(1)首先创建了一个新的空对象
(2)设置原型,将对象的原型设置为函数的 prototype 对象。
(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
function objectFactory() {let newObject = null;let constructor = Array.prototype.shift.call(arguments);let result = null;// 判断参数是否是一个函数if (typeof constructor !== "function") {console.error("type error");return;}// 新建一个空对象,对象的原型为构造函数的 prototype 对象newObject = Object.create(constructor.prototype);// 将 this 指向新建对象,并执行函数result = constructor.apply(newObject, arguments);// 判断返回对象let flag = result && (typeof result === "object" || typeof result === "function");// 判断返回结果return flag ? result : newObject;
}
// 使用方法
objectFactory(构造函数, 初始化参数);
实现apply方法
apply原理与call很相似,不多赘述
// 模拟 apply
Function.prototype.myapply = function(context, arr) {var context = Object(context) || window;context.fn = this;var result;if (!arr) {result = context.fn();} else {var args = [];for (var i = 0, len = arr.length; i < len; i++) {args.push("arr[" + i + "]");}result = eval("context.fn(" + args + ")");}delete context.fn;return result;
};
debounce(防抖)
触发高频时间后n秒内函数只会执行一次,如果n秒内高频时间再次触发,则重新计算时间。
const debounce = (fn, time) => {let timeout = null;return function() {clearTimeout(timeout)timeout = setTimeout(() => {fn.apply(this, arguments);}, time);}
};
防抖常应用于用户进行搜索输入节约请求资源,window
触发resize
事件时进行防抖只触发一次。
判断对象是否存在循环引用
循环引用对象本来没有什么问题,但是序列化的时候就会发生问题,比如调用JSON.stringify()
对该类对象进行序列化,就会报错: Converting circular structure to JSON.
下面方法可以用来判断一个对象中是否已存在循环引用:
const isCycleObject = (obj,parent) => {const parentArr = parent || [obj];for(let i in obj) {if(typeof obj[i] === 'object') {let flag = false;parentArr.forEach((pObj) => {if(pObj === obj[i]){flag = true;}})if(flag) return true;flag = isCycleObject(obj[i],[...parentArr,obj[i]]);if(flag) return true;}}return false;
}const a = 1;
const b = {a};
const c = {b};
const o = {d:{a:3},c}
o.c.b.aa = a;console.log(isCycleObject(o)
查找有序二维数组的目标值:
var findNumberIn2DArray = function(matrix, target) {if (matrix == null || matrix.length == 0) {return false;}let row = 0;let column = matrix[0].length - 1;while (row < matrix.length && column >= 0) {if (matrix[row][column] == target) {return true;} else if (matrix[row][column] > target) {column--;} else {row++;}}return false;
};
二维数组斜向打印:
function printMatrix(arr){let m = arr.length, n = arr[0].lengthlet res = []// 左上角,从0 到 n - 1 列进行打印for (let k = 0; k < n; k++) {for (let i = 0, j = k; i < m && j >= 0; i++, j--) {res.push(arr[i][j]);}}// 右下角,从1 到 n - 1 行进行打印for (let k = 1; k < m; k++) {for (let i = k, j = n - 1; i < m && j >= 0; i++, j--) {res.push(arr[i][j]);}}return res
}
实现节流函数(throttle)
防抖函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
// 手写简化版
// 节流函数
const throttle = (fn, delay = 500) => {let flag = true;return (...args) => {if (!flag) return;flag = false;setTimeout(() => {fn.apply(this, args);flag = true;}, delay);};
};
适用场景:
- 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
- 缩放场景:监控浏览器resize
- 动画场景:避免短时间内多次触发动画引起性能问题
参考 前端进阶面试题详细解答
实现日期格式化函数
输入:
dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01
dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01
dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日
const dateFormat = (dateInput, format)=>{var day = dateInput.getDate() var month = dateInput.getMonth() + 1 var year = dateInput.getFullYear() format = format.replace(/yyyy/, year)format = format.replace(/MM/,month)format = format.replace(/dd/,day)return format
}
实现AJAX请求
AJAX是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。
创建AJAX请求的步骤:
- 创建一个 XMLHttpRequest 对象。
- 在这个对象上使用 open 方法创建一个 HTTP 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。
- 在发起请求前,可以为这个对象添加一些信息和监听函数。比如说可以通过 setRequestHeader 方法来为请求添加头信息。还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发onreadystatechange 事件,可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候就可以通过 response 中的数据来对页面进行更新了。
- 当对象的属性和监听函数设置完成后,最后调用 sent 方法来向服务器发起请求,可以传入参数作为发送的数据体。
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 创建 Http 请求
xhr.open("GET", SERVER_URL, true);
// 设置状态监听函数
xhr.onreadystatechange = function() {if (this.readyState !== 4) return;// 当请求成功时if (this.status === 200) {handle(this.response);} else {console.error(this.statusText);}
};
// 设置请求失败时的监听函数
xhr.onerror = function() {console.error(this.statusText);
};
// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 发送 Http 请求
xhr.send(null);
验证是否是身份证
function isCardNo(number) {var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;return regx.test(number);
}
数组去重
const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];
// => [1, '1', 17, true, false, 'true', 'a', {}, {}]
方法一:利用Set
const res1 = Array.from(new Set(arr));
方法二:两层for循环+splice
const unique1 = arr => {let len = arr.length;for (let i = 0; i < len; i++) {for (let j = i + 1; j < len; j++) {if (arr[i] === arr[j]) {arr.splice(j, 1);// 每删除一个树,j--保证j的值经过自加后不变。同时,len--,减少循环次数提升性能len--;j--;}}}return arr;
}
方法三:利用indexOf
const unique2 = arr => {const res = [];for (let i = 0; i < arr.length; i++) {if (res.indexOf(arr[i]) === -1) res.push(arr[i]);}return res;
}
当然也可以用include、filter,思路大同小异。
方法四:利用include
const unique3 = arr => {const res = [];for (let i = 0; i < arr.length; i++) {if (!res.includes(arr[i])) res.push(arr[i]);}return res;
}
方法五:利用filter
const unique4 = arr => {return arr.filter((item, index) => {return arr.indexOf(item) === index;});
}
方法六:利用Map
const unique5 = arr => {const map = new Map();const res = [];for (let i = 0; i < arr.length; i++) {if (!map.has(arr[i])) {map.set(arr[i], true)res.push(arr[i]);}}return res;
}
验证是否是邮箱
function isEmail(email) {var regx = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/;return regx.test(email);
}
手写 Promise.all
1) 核心思路
- 接收一个 Promise 实例的数组或具有 Iterator 接口的对象作为参数
- 这个方法返回一个新的 promise 对象,
- 遍历传入的参数,用Promise.resolve()将参数"包一层",使其变成一个promise对象
- 参数所有回调成功才是成功,返回值数组与参数顺序一致
- 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。
2)实现代码
一般来说,Promise.all 用来处理多个并发请求,也是为了页面数据构造的方便,将一个页面所用到的在不同接口的数据一起请求过来,不过,如果其中一个接口失败了,多个请求也就失败了,页面可能啥也出不来,这就看当前页面的耦合程度了
function promiseAll(promises) {return new Promise(function(resolve, reject) {if(!Array.isArray(promises)){throw new TypeError(`argument must be a array`)}var resolvedCounter = 0;var promiseNum = promises.length;var resolvedResult = [];for (let i = 0; i < promiseNum; i++) {Promise.resolve(promises[i]).then(value=>{resolvedCounter++;resolvedResult[i] = value;if (resolvedCounter == promiseNum) {return resolve(resolvedResult)}},error=>{return reject(error)})}})
}
// test
let p1 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(1)}, 1000)
})
let p2 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(2)}, 2000)
})
let p3 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(3)}, 3000)
})
promiseAll([p3, p1, p2]).then(res => {console.log(res) // [3, 1, 2]
})
实现数组的push方法
let arr = [];
Array.prototype.push = function() {for( let i = 0 ; i < arguments.length ; i++){this[this.length] = arguments[i] ;}return this.length;
}
模板引擎实现
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {name: '姓名',age: 18
}
render(template, data); // 我是姓名,年龄18,性别undefined
function render(template, data) {const reg = /\{\{(\w+)\}\}/; // 模板字符串正则if (reg.test(template)) { // 判断模板里是否有模板字符串const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段template = template.replace(reg, data[name]); // 将第一个模板字符串渲染return render(template, data); // 递归的渲染并返回渲染后的结构}return template; // 如果模板没有模板字符串直接返回
}
实现字符串的repeat方法
输入字符串s,以及其重复的次数,输出重复的结果,例如输入abc,2,输出abcabc。
function repeat(s, n) {return (new Array(n + 1)).join(s);
}
递归:
function repeat(s, n) {return (n > 0) ? s.concat(repeat(s, --n)) : "";
}
实现发布订阅模式
简介:
发布订阅者模式,一种对象间一对多的依赖关系,但一个对象的状态发生改变时,所依赖它的对象都将得到状态改变的通知。
主要的作用(优点):
- 广泛应用于异步编程中(替代了传递回调函数)
- 对象之间松散耦合的编写代码
缺点:
- 创建订阅者本身要消耗一定的时间和内存
- 多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护
实现的思路:
- 创建一个对象(缓存列表)
on
方法用来把回调函数fn
都加到缓存列表中emit
根据key
值去执行对应缓存列表中的函数off
方法可以根据key
值取消订阅
class EventEmiter {constructor() {// 事件对象,存放订阅的名字和事件this._events = {}}// 订阅事件的方法on(eventName,callback) {if(!this._events) {this._events = {}}// 合并之前订阅的cbthis._events[eventName] = [...(this._events[eventName] || []),callback]}// 触发事件的方法emit(eventName, ...args) {if(!this._events[eventName]) {return}// 遍历执行所有订阅的事件this._events[eventName].forEach(fn=>fn(...args))}off(eventName,cb) {if(!this._events[eventName]) {return}// 删除订阅的事件this._events[eventName] = this._events[eventName].filter(fn=>fn != cb && fn.l != cb)}// 绑定一次 触发后将绑定的移除掉 再次触发掉once(eventName,callback) {const one = (...args)=>{// 等callback执行完毕在删除callback(args)this.off(eventName,one)}one.l = callback // 自定义属性this.on(eventName,one)}
}
测试用例
let event = new EventEmiter()let login1 = function(...args) {console.log('login success1', args)
}
let login2 = function(...args) {console.log('login success2', args)
}
// event.on('login',login1)
event.once('login',login2)
event.off('login',login1) // 解除订阅
event.emit('login', 1,2,3,4,5)
event.emit('login', 6,7,8,9)
event.emit('login', 10,11,12)
发布订阅者模式和观察者模式的区别?
- 发布/订阅模式是观察者模式的一种变形,两者区别在于,发布/订阅模式在观察者模式的基础上,在目标和观察者之间增加一个调度中心。
- 观察者模式是由具体目标调度,比如当事件触发,
Subject
就会去调用观察者的方法,所以观察者模式的订阅者与发布者之间是存在依赖的。 - 发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在。
使用ES5和ES6求函数参数的和
ES5:
function sum() {let sum = 0Array.prototype.forEach.call(arguments, function(item) {sum += item * 1})return sum
}
ES6:
function sum(...nums) {let sum = 0nums.forEach(function(item) {sum += item * 1})return sum
}
实现斐波那契数列
// 递归
function fn (n){if(n==0) return 0if(n==1) return 1return fn(n-2)+fn(n-1)
}
// 优化
function fibonacci2(n) {const arr = [1, 1, 2];const arrLen = arr.length;if (n <= arrLen) {return arr[n];}for (let i = arrLen; i < n; i++) {arr.push(arr[i - 1] + arr[ i - 2]);}return arr[arr.length - 1];
}
// 非递归
function fn(n) {let pre1 = 1;let pre2 = 1;let current = 2;if (n <= 2) {return current;}for (let i = 2; i < n; i++) {pre1 = pre2;pre2 = current;current = pre1 + pre2;}return current;
}
手写深度比较isEqual
思路:深度比较两个对象,就是要深度比较对象的每一个元素。=> 递归
- 递归退出条件:
- 被比较的是两个值类型变量,直接用“===”判断
- 被比较的两个变量之一为
null
,直接判断另一个元素是否也为null
- 提前结束递推:
- 两个变量
keys
数量不同 - 传入的两个参数是同一个变量
- 两个变量
- 递推工作:深度比较每一个
key
function isEqual(obj1, obj2){//其中一个为值类型或nullif(!isObject(obj1) || !isObject(obj2)){return obj1 === obj2;}//判断是否两个参数是同一个变量if(obj1 === obj2){return true;}//判断keys数是否相等const obj1Keys = Object.keys(obj1);const obj2Keys = Object.keys(obj2);if(obj1Keys.length !== obj2Keys.length){return false;}//深度比较每一个keyfor(let key in obj1){if(!isEqual(obj1[key], obj2[key])){return false;}}return true;
}
树形结构转成列表(处理菜单)
[{id: 1,text: '节点1',parentId: 0,children: [{id:2,text: '节点1_1',parentId:1}]}
]
转成
[{id: 1,text: '节点1',parentId: 0 //这里用0表示为顶级节点},{id: 2,text: '节点1_1',parentId: 1 //通过这个字段来确定子父级}...
]
实现代码如下:
function treeToList(data) {let res = [];const dfs = (tree) => {tree.forEach((item) => {if (item.children) {dfs(item.children);delete item.children;}res.push(item);});};dfs(data);return res;
}
相关文章:
京东前端二面常考手写面试题(必备)
实现发布-订阅模式 class EventCenter{// 1. 定义事件容器,用来装事件数组let handlers {}// 2. 添加事件方法,参数:事件名 事件方法addEventListener(type, handler) {// 创建新数组容器if (!this.handlers[type]) {this.handlers[type] …...

如何用AST还原某音的JSVMP
1. 什么是JSVMP vmp简单来说就是将一些高级语言的代码通过自己实现的编译器进行编译得到字节码,这样就可以更有效的保护原有代码,而jsvmp自然就是对JS代码的编译保护,具体的可以看看H5应用加固防破解-JS虚拟机保护方案。 如何区分是不是jsv…...

【蓝桥杯试题】 递归实现指数型枚举例题
💃🏼 本人简介:男 👶🏼 年龄:18 🤞 作者:那就叫我亮亮叭 📕 专栏:蓝桥杯试题 文章目录1. 题目描述2. 思路解释2.1 时间复杂度2.2 递归3. 代码展示最后&#x…...

【用Group整理目录结构 Objective-C语言】
一、接下来,我们看另外一个知识点,怎么用Group把这一堆乱七八糟的文件给它整理一下,也算是封装一下吧, 1.这一堆杂乱无章的文件: 那么,哪些类是属于模型呢,哪些类是属于视图呢,哪些类是属于控制器呢, 我们接下来通过Group的方式,来给它们分一下类, 这样看起来就好…...

JavaScript高级程序设计读书分享之8章——8.1理解对象
JavaScript高级程序设计(第4版)读书分享笔记记录 适用于刚入门前端的同志 创建自定义对象的通常方式是创建 Object 的一个新实例,然后再给它添加属性和方法。 let person new Object() person.name Tom person.age 18 person.sayName function(){//示 this.name…...

代码随想录算法训练营第四十天 | 343. 整数拆分,96.不同的二叉搜索树
一、参考资料整数拆分https://programmercarl.com/0343.%E6%95%B4%E6%95%B0%E6%8B%86%E5%88%86.html 视频讲解:https://www.bilibili.com/video/BV1Mg411q7YJ不同的二叉搜索树https://programmercarl.com/0096.%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90…...

数据结构与算法系列之顺序表的实现
这里写目录标题顺序表的优缺点:注意事项test.c(动态顺序表)SeqList.hSeqList.c各接口函数功能详解void SLInit(SL* ps);//定义void SLDestory(SL* ps);void SLPrint(SL* ps);void SLPushBack(SL* ps ,SLDataType * x );void SLPopBack(SL* ps…...

基于Linux_ARM板的驱动烧写及连接、挂载详细过程(附带驱动程序)
文章目录前言一、搭建nfs服务二、ARM板的硬件连接三、putty连接四、挂载共享文件夹五、烧写驱动程序六、驱动程序示例前言 本文操作环境:Ubuntu14.04、GEC6818 这里为似懂非懂的朋友简单叙述该文章的具体操作由来,我们的主要目的是将写好的驱动程序烧进…...

python-爬虫-字体加密
直接点 某8网 https://*****.b*b.h*****y*8*.com/ 具体网址格式就是这样的但是为了安全起见,我就这样打码了. 抛出问题 我们看到这个号码是在页面上正常显示的 F12 又是这样就比较麻烦,不能直接获取.用requests库也是获取不到正常想要的 源码的,因为字体加密了. 查看页面源代码…...

计算机组成原理4小时速成5:输入输出系统,io设备与cpu的链接方式,控制方式,io设备,io接口,并行串行总线
计算机组成原理4小时速成5:输入输出系统,io设备与cpu的链接方式,控制方式,io设备,io接口,并行串行总线 2022找工作是学历、能力和运气的超强结合体,遇到寒冬,大厂不招人,…...

安全狗受聘成为福州网信办网络安全技术支撑单位
近日,福州市委网信办召开了2022年度网络安全技术支撑单位总结表彰大会。 作为国内云原生安全领导厂商,安全狗也出席了此次活动。 据悉,会议主要对2022年度优秀支撑单位进行表彰,并为2023年度支撑单位举行授牌仪式。 本次遴选工…...
RV1126 在Ubuntu18.04开发环境搭建
1:安装软件终端下输入安装命名:sudo apt install openssh-serversudo apt install android-tools-adbsudo apt install vim net-tools gitsudo apt install cmakesudo apt install treesudo apt install minicomsudo apt install gawksudo apt install bisonsudo ap…...
如何在 C++ 中调用 python 解析器来执行 python 代码(一)?
实现 Python UDF 中的一步就是学习如何在 C 语言中调用 python 解析器。本文根据 Python 官方文档做了一次实验,记录如下: 1. 安装依赖包 $sudo yum install python3-devel.x86_642. 使用 python-config 来生成编译选项 $python3.6-config --cflags -…...

操作系统权限提升(二十三)之Linux提权-通配符(ws)提权
系列文章 操作系统权限提升(十八)之Linux提权-内核提权 操作系统权限提升(十九)之Linux提权-SUID提权 操作系统权限提升(二十)之Linux提权-计划任务提权 操作系统权限提升(二十一)之Linux提权-环境变量劫持提权 操作系统权限提升(二十二)之Linux提权-SUDO滥用提权 利用通配符…...
Zookeeper下载和安装
Zookeeper 1.下载 官方下载地址:https://zookeeper.apache.org/ 版本:apache-zookeeper-3.7.1-bin.tar.gz 2. 安装 2.1 本地安装 2.1.1 安装JDK 见:Hadoop集群搭建 2.1.2 上传安装包 使用远程工具拷贝安装包到Linux指定路径 /opt/s…...

Biomod2 (上):物种分布模型预备知识总结
Biomod11.栅格数据处理1.1 读取一个栅格图片1.2 计算数据间的相关系数1.3 生成多波段的栅格图像1.4 修改变量名称1.4.1 计算多个变量之间的相关性2. 矢量数据处理2.1 提取矢量数据2.2 数据掩膜2.2 栅格计算2.3 拓展插件的使用3. 图表绘制3.1 遥感影像绘制3.2 柱状图分析图绘制3…...

操作指南:如何高效使用Facebook Messenger销售(二)
上一篇文章我们介绍了使用Facebook Messenger作为销售渠道的定义、好处及注意事项,本节我们将详细介绍怎么将Facebook Messenger销售与SaleSmartly(ss客服)结合,实现一站式管理多主页配图来源:SaleSmartly(…...
计算机三级|网络技术|中小型网络系统总体规划与设计方案|IP地址规划技术|2|3
p3 p4一、中小型网络系统总体规划与设计方案网络关键的设备选型路由器技术指标性能指标综述吞吐量背板能力丢包率时延抖动突发处理能力路由表容量服务质量网管能力可靠性和可用性1 吞吐量指路由器的包转发能力,涉及两个内容:端口吞吐量和整机吞吐量&…...

为什么一定要做集成测试?
集成测试,我们都不陌生,几乎我们产品每天都在进行。但是我们真的有好好思考:为什么一定要做集成测试吗?只是为了简单的将“积木”搭起来就行,还是有什么其他的深意? 深意可能不一定会有,但是意…...

前端:CSS
CSS基本语法规则:选择器若干属性声明 style标签:可以放到代码的任意位置处,head/body中都可以 三种写CSS的方式: 1、内部样式:使用style标签,直接把CSS写到html文件中。此时的style标签可以放到任何位置…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...

Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...
uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)
UniApp 集成腾讯云 IM 富媒体消息全攻略(地理位置/文件) 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型,核心实现方式: 标准消息类型:直接使用 SDK 内置类型(文件、图片等)自…...

从零开始了解数据采集(二十八)——制造业数字孪生
近年来,我国的工业领域正经历一场前所未有的数字化变革,从“双碳目标”到工业互联网平台的推广,国家政策和市场需求共同推动了制造业的升级。在这场变革中,数字孪生技术成为备受关注的关键工具,它不仅让企业“看见”设…...