前端面试提问(4)

1、手撕防抖与节流、树与对象的转换、递归调用,链表头插法

1.1、防抖

防抖函数用于延迟执行某个函数,直到过了一定的间隔时间(例如等待用户停止输入)后再执行。

即后一次点击事件发生时间距离一次点击事件至少间隔一定时间。

function debounce(fn, wait) {let timer = nullreturn function () {if (timer) {clearTimeout(timer)timer = null}timer = setTimeout(() => {fn.call(this, arguments)}, wait)}
}

1.2、节流

节流函数用于限制函数的执行频率,确保一定时间内只执行一次。

//时间戳版
function throttle(fn, wait) {let date = Date.now()return function () {let now = Date.now()if (now - date > wait) {fn.call(this, arguments)date = now}}
}
//定时器版
function throttle(fn, wait) {let timer = nullreturn function () {if (!timer) {timer = setTimeout(() => {fn.call(this, arguments)timer = null}, wait)}}
}

 1.3、树与对象的转换

参考作者之前的文章

2、水平垂直居中方法

①flex布局,父元素display:flex; justify-content:center; align-items:center;

②父元素position:relative; 子元素position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);

③父元素position:relative; 子元素position:absolute; left:0; top:0; bottom:0; right:0; margin:auto;

④文字的话, text-align:center; line-height 和 height 相等

3、 手写ajax(使用promise封装)

function getJSON(url) {let promise = new Promise((resolve, reject) => {let xhr = new XMLHttpRequest()xhr.open("GET", url, true)xhr.onreadystatechange = function () {if (this.readyState !== 4) returnif (this.status === 200) {resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.onerror = function () {reject(new Error(this.statusText))}xhr.responseType = "json"xhr.setRequestHeader("Accept", "application/json")xhr.send(null)})return promise
}

4、扁平数组

const flatten = (arr) => {return arr.reduce((pre, cur) => {return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);}, []);
};arr1 = [1, [2, 3], [4, [5, [6, 7], 8]]];

5、setTimeout实现setInterval

function myInterval(func, time) {let ids = [];function fn() {let id = setTimeout(() => {func();fn();}, time);ids.push(id);}fn();return ids;
}let id = myInterval(() => {console.log("Hello World");
}, 500);function clearMyInterval(idList) {idList.forEach((id) => {clearTimeout(id);});
}setTimeout(() => {clearMyInterval(id);
}, 3000);

6、输入url发生了什么?

  1. URL 解析: 浏览器解析输入的 URL。

  2. DNS 解析: 如果域名需要解析,进行 DNS 解析。如果已经有缓存的 DNS 记录,可以跳过此步骤。

  3. 检查缓存: 浏览器检查缓存,看是否已经有了之前请求过的资源的副本。这包括检查浏览器缓存和可能存在的代理服务器缓存。

  4. 有缓存: 如果资源已经存在于缓存中,并且没有过期,浏览器可以跳过后续的步骤,直接使用缓存中的资源渲染页面。

  5. 无缓存或缓存过期: 如果资源不存在于缓存中,或者缓存已经过期,浏览器将按照正常的流程发起网络请求,

  6. TCP 连接:拿到IP地址后,三次握手建立TCP连接,https的话还需要进行TLS加密协议的握手过程

  7. 发送请求,获取响应:连接建立成功之后,浏览器会构建请求行、cookie等数据附加到请求头中,发给服务器,服务器接受请求并解析,如果没有对应的资源就404;否则检查HTTP请求头有没有包含协商缓存信息(前面命中强缓存且已过期的话就会走这个步骤),如果验证缓存没有更新,过期的缓存依然可以使用,就返回304和空响应体;如果没有缓存或者资源更新了,就读取完整请求并准备http响应,进行查询数据库等操作,返回200和查询到的资源

  8. TCP 连接:  浏览器接收到响应数据之后,如果是http1.1以下则直接关闭连接,否则双方都可以根据情况选择关闭TCP连接或者保留重用,现在浏览器默认都会保持连接(keep-alive)

  9. 浏览器渲染: 浏览器使用获取到的资源渲染页面。

缓存是一种重要的性能优化手段,可以减少网络请求,加快页面加载速度。缓存策略通常由服务器端和浏览器端一起决定,可以通过 HTTP 头部信息来进行配置。例如,使用 Cache-Control 头部可以控制缓存的行为,而 ETagLast-Modified 头部可以用于验证缓存是否过期。

7、加载js和css会不会阻塞页面渲染

css用link和@import的情况

link标签引入css资源时在火狐浏览器中是异步加载的,在谷歌浏览器中是同步加载的。

但如果是通过style标签引入样式,则不论何种浏览器,均为同步加载。

@import是在网页完全载入后才加载,在关键路径上创造了更多的网络请求,阻塞渲染时间,影响浏览器的并行下载,多个@import导致下载顺序紊乱。

8、重排重绘

重排:当渲染树的一部分必须更新并且节点的尺寸发生了变化,浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。
①添加、删除可见的dom

②元素的位置改变

③元素的尺寸改变(外边距、内边距、边框厚度、宽高等几何属性)
④页面渲染初始化
⑤浏览器窗口尺寸改变
重绘:是在一个元素的外观被改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。
如何减少reflow、repaint?
①不要一条一条的修改DOM的样式,可以先定义好css的class,然后修改DOM的className。
②不要把DOM结点的属性值放在一个循环里当成循环里的变量。
③为动画的HTML元件适用fixed或absolute的position,那么修改他们的css是不会reflow

9、深浅拷贝

浅拷贝:基本数据类型、扩展运算符()、slice()、concat()、Object.assign()

深拷贝:JSON.parse(JSON.stringify())、手写深拷贝、lodash

手撕深拷贝

let obj = {lili: { name: "lili", person: ["lisan", "zhangsan"] },arr: [1, 2, 3, 4],fruit: "apple",
};
function deepclone(obj) {// 检查是否是基本数据类型,如果是则直接返回if (obj === null || typeof obj !== "object") {return obj;}if (Array.isArray(obj)) {let newArray = [];for (let i = 0; i < obj.length; i++) {newArray[i] = deepclone(obj[i]);}return newArray;} else {let newObj = {};for (const key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] = deepclone(obj[key]);}}return newObj;}
}
console.log(deepclone(obj));

10、手撕合并函数

function deepMerge(target, source) {// 检查参数类型if (typeof target !== "object" || typeof source !== "object") {throw new Error("Both target and source must be objects");}// 遍历source对象的属性for (const key in source) {if (source.hasOwnProperty(key)) {// 如果属性是对象且存在于target中,递归深度合并if (typeof source[key] === "object" &&source[key] !== null &&target.hasOwnProperty(key) &&typeof target[key] === "object" &&target[key] !== null) {if (Array.isArray(target[key])) {target[key] = [].concat(target[key], source[key]);} else {target[key] = deepMerge(target[key], source[key]);}} else {// 否则直接赋值// target[key] = source[key];target[key] = [].concat(target[key], source[key]);}}}return target;
}// 使用例子
const targetObject = {name: "John",age: 30,address: {city: "New York",zip: "10001",people: { class: "1班" },},hobbies: ["shopping"],
};const sourceObject = {age: 31,address: {zip: "10002",},hobbies: ["reading", "traveling"],
};const resultObject = deepMerge(targetObject, sourceObject);
console.log(resultObject);

11、Object和map

共同点:键值对的动态集合,支持增删

不同点:

①构造方式不同

//map
const map = new Map()
const map1 = new Map([['a',1],['b',2]])
//obj
const obj = new Object()
const obj1 = Object.create()

②object键的类型必须是String或者Symbol、map键的类型可以是任意类型

③object中key是无序的,map中可以是有序的,按照插入的顺序返回

④object只能通过Object.key()方法或for in统计数量,map有map.size

⑤object可以通过点或中括号访问属性,map用map.get()

⑥object不具备Iterator特性,不能for of遍历,map的keys()、values()、entries()都具有迭代器

⑦object可以用JSON.stringify()进行序列化,map只能转化成JSON,不能被parse解析

⑧应用场景:object做数据存储,需要序列化时使用;map频繁更新键值对,key类型未知时使用

12、http和https的区别

①https协议需要到CA申请证书,一般免费证书较少,因而需要一定费用。
②http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl/tls加密传输协议。
③http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
④http的连接很简单,是无状态的;HTTPS协议是由SSL/TLS+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
 

13、堆和栈

13.1、区别

①堆栈空间分配区别(操作系统):

栈由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;
堆 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
②堆栈缓存方式区别:
栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放;
堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
③堆栈数据结构区别:
栈是一种先进后出(FILO)的数据结构;
堆可以被看成是一棵树,如:堆排序。

13.2、最大堆和最小堆?

最大堆:根结点的键值是所有堆结点键值中最大者,且每个结点的值都比其孩子的值大。
最小堆:根结点的键值是所有堆结点键值中最小者,且每个结点的值都比其孩子的值小。

13.3、 堆栈溢出

  • JavaScript 的函数调用栈有一定大小限制,当函数调用的嵌套层数过多时,会导致栈溢出错误。
function recursive() {// 递归出现栈溢出recursive();
}
recursive();
  • JavaScript 的堆也有大小限制。堆是用来存储变量和对象等数据的一段内存空间,当我们创建了大量数据或者数据太大而超过了堆的容量时,就会触发堆溢出错误。
let arr = [];
while (true) {// 堆溢出arr.push('a');
}

14、进程和线程

进程:一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程。

线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。

进程和线程的区别:

①进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)
②进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。
而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
③线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC) 进行。
④但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

15、promise.all,promise.any,promise.race,promise allsettled

Promise.allSettled 不会在其中一个 Promise 失败时立即 reject,而是等待所有 Promise 完成后再返回结果。

const promise1 = Promise.resolve(42);
const promise2 = Promise.reject("Oops!");
const promise3 = new Promise((resolve) => setTimeout(() => resolve("Done!"), 1000));Promise.allSettled([promise1, promise2, promise3]).then((results) => {console.log(results);// results 包含了每个 Promise 的状态和结果// [{ status: 'fulfilled', value: 42 }, { status: 'rejected', reason: 'Oops!' }, { status: 'fulfilled', value: 'Done!' }]}).catch((error) => {console.error("Error:", error);});

Promise.all 在所有 Promise 全部成功(resolved)时才会成功,但只要有一个 Promise 失败(rejected),整个 Promise.all 就会立即失败。这种行为被称为“一败俱败”。

传递给 Promise.all 的数组为空: 如果传递给 Promise.all 的 Promise 数组为空,返回的 Promise 会立即被解决为一个空数组。

如果在某一个promise中reject后,那一个函数捕捉catch,就不会报错。不然就会走all的catch

16、场景题:100000条数据渲染

const renderList = async () => {const list = await getList()const total = list.lengthconst page = 0const limit = 200const totalPage = Math.ceil(total / limit)const render = (page) => {if (page >= totalPage) returnsetTimeout(() => {for (let i = page * limit; i < page * limit + limit; i++) {const item = list[i]const div = document.createElement('div')div.className = 'sunshine'div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`container.appendChild(div)}render(page + 1)}, 0)}render(page)

17、你做的项目如何部署到服务器

function myNew(constructor, ...args) {// 步骤 1:创建一个新的空对象const obj = {};// 步骤 2:将新对象的 __proto__ 指向构造函数的 prototype 属性obj.__proto__ = constructor.prototype;// 步骤 3:将构造函数的上下文传递给构造函数,并执行构造函数const result = constructor.apply(obj, args);// 步骤 4:如果构造函数返回一个对象,则返回该对象;否则,返回新创建的对象return result instanceof Object ? result : obj;
}

18、路由鉴权

18.1、请求数据

  1. 用户登录: 用户在登录时,通过用户名和密码等方式向后端发起登录请求。后端验证用户身份,并在登录成功后生成一个 Token。

  2. Token 存储: 将生成的 Token 存储在前端,通常是通过 localStorage。axios请求拦截,如果存在token,在每次请求时自动附加到请求头。

  3. 请求时携带 Token: 在每次请求后端受保护资源时,将 Token 携带在请求头中,通常是通过 Authorization 头或自定义头。

  4. 后端验证 Token: 后端接收到请求后,通过解析 Token 验证用户身份和权限。如果 Token 有效且权限足够,则返回相应资源;否则,返回错误状态。

18.2、路由守卫

根据权限决定跳转: 如果用户拥有访问权限,正常放行,让用户访问受保护页面。如果用户没有权限,可以将其重定向到登录页或其他提示页面,或者显示相应的提示信息。

// 路由表
const routes = [{ path: '/', component: Home, meta: { requiresAuth: true } },{ path: '/admin', component: Admin, meta: { requiresAuth: true, requiresAdmin: true } },{ path: '/login', component: Login }
];// 创建路由实例
const router = new VueRouter({routes
});// 路由守卫
router.beforeEach((to, from, next) => {const isAuthenticated = /* 根据用户身份信息判断用户是否已登录 */;const isAdmin = /* 根据用户身份信息判断用户是否是管理员 */;if (to.meta.requiresAuth && !isAuthenticated) {// 用户未登录,重定向到登录页next('/login');} else if (to.meta.requiresAdmin && !isAdmin) {// 用户不是管理员,可以根据需要进行处理,例如重定向到首页next('/');} else {// 用户有权限,放行next();}
});// 在 Vue 实例中使用路由
new Vue({el: '#app',router,render: h => h(App)
});

18.3、不同等级的权限控制

  1. 获取用户权限信息: 在用户登录成功后,获取token与用户的权限信息,存储在Vuex或者localStorage

  2. 定义菜单权限配置: 在前端定义一个菜单权限配置,包含不同权限下允许访问的菜单项。这可以是一个简单的 JSON 对象或数组,其中每个菜单项都包含一个权限属性,表示需要的用户权限。

  3. 根据用户权限过滤菜单项: 根据用户拥有的权限,从菜单权限配置中筛选出符合条件的菜单项。

  4. 动态生成菜单: 使用框架或库的路由功能,根据过滤后的菜单项动态生成菜单。这可以在页面加载时或用户登录成功后执行。

// 菜单权限配置
const menuConfig = [{ path: '/dashboard', name: 'Dashboard', permission: 'view_dashboard' },{ path: '/users', name: 'Users', permission: 'manage_users' },// 其他菜单项
];// 获取用户权限信息(模拟)
const userPermissions = ['view_dashboard', 'manage_users']; // 实际中应该根据登录成功后的用户信息获取权限// 根据用户权限过滤菜单项
const userMenu = menuConfig.filter(item => userPermissions.includes(item.permission));// 在 Vue 实例中使用路由
const router = new VueRouter({routes: userMenu,
});// 示例组件中动态生成菜单
Vue.component('Sidebar', {template: `<div><router-link v-for="item in userMenu" :to="item.path" :key="item.path">{{ item.name }}</router-link></div>`,data() {return {userMenu,};},
});// 在 Vue 实例中使用路由和菜单组件
new Vue({el: '#app',router,render: h => h(App),
});

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

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

相关文章

笙默考试管理系统-MyExamTest----codemirror(49)

笙默考试管理系统-MyExamTest----codemirror&#xff08;49&#xff09; 目录 笙默考试管理系统-MyExamTest----codemirror&#xff08;49&#xff09; 一、 笙默考试管理系统-MyExamTest----codemirror 二、 笙默考试管理系统-MyExamTest----codemirror 三、 笙默考试…

有哪些已经上线的vue商城项目?

前言 下面是一些商城的项目&#xff0c;需要练手的同学可以挑选一些来练&#xff0c;废话少说&#xff0c;让我们直接开始正题~~ 1、newbee-mall-vue3-app 是一个基于 Vue 3 和 TypeScript 的电商前端项目&#xff0c;它是 newbee-mall 项目的升级版。该项目包含了商品列表、…

内网环境下 - 安装linux命令、搭建docker以及安装镜像

一 内网环境安装docker 先在外网环境下载好docker二进制文件docker二进制文件下载&#xff0c;要下载对应硬件平台的文件&#xff0c;否则不兼容 如下载linux平台下的文件&#xff0c;直接访问这里即可linux版本docker二进制文件 这里下载docker-24.0.5.tgz 将下载好的文件…

计算机存储单位 + 程序编译过程

C语言的编译过程 计算机存储单位 头文件包含的两种方式 使用 C/C 程序常用的IDE 常用的C语言编译器&#xff1a; 在选择编译器时&#xff0c;需考虑平台兼容性、性能优化、调试工具和开发人员的个人偏好等因素。 详细教程可转 爱编程的大丙

Java编程中通用的正则表达式(一)

正则表达式&#xff08;Regular Expression&#xff0c;简称RegEx&#xff09;&#xff0c;又称常规表示法、正则表示、正规表示式、规则表达式、常式、表达式等&#xff0c;是计算机科学中的一个概念。正则表达式是用于描述某种特定模式的字符序列&#xff0c;特别是用来匹配、…

持续集成和持续交付

引言 CI/CD 是一种通过在应用开发阶段引入自动化来频繁向客户交付应用的方法。CI/CD 的核心概念是持续集成、持续交付和持续部署。作为一种面向开发和运维团队的解决方案&#xff0c;CI/CD 主要针对在集成新代码时所引发的问题&#xff08;亦称&#xff1a;“集成地狱”&#…

力扣刷题笔记——反转链表

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 经典问题反转链表 这里给出四种解法 1.双指针 这种方法是用一个next指针记录当前节点的下一个节点&#xff0c;一个pre指针记录当前节点的前一个节点。 只需要遍历一遍链表就可以完成链表的反转 c…

idea__SpringBoot微服务05——JSR303校验(新注解)(新的依赖),配置文件优先级,多环境切换

JSR303校验&#xff0c;配置文件优先级&#xff0c;多环境切换 一、JSR303数据校验二、配置文件优先级三、多环境切换一、properties多环境切换二、yaml多环境切换————————创作不易&#xff0c;如觉不错&#xff0c;随手点赞&#xff0c;关注&#xff0c;收藏(*&#x…

电脑待机怎么设置?让你的电脑更加节能

在日常使用电脑的过程中&#xff0c;合理设置待机模式是一项省电且环保的好习惯。然而&#xff0c;许多用户对于如何设置电脑待机感到困扰。那么电脑待机怎么设置呢&#xff1f;本文将深入探讨三种常用的电脑待机设置方法&#xff0c;通过详细的步骤&#xff0c;帮助用户更好地…

【C语言期末】题目+笔记

文章目录 题目1.下面哪个不是C语言的基本数据类型&#xff1f;&#xff08; B &#xff09;2.C语言的标识符应以字母或&#xff08; A &#xff09;开头。3.如果需要在C程序里调用标准函数库中的printf函数&#xff0c;则应该在程序的开头包含哪个头文件&#xff1f;&#xff0…

【数据结构】顺序表的定义和运算

目录 1.初始化 2.插入 3.删除 4.查找 5.修改 6.长度 7.遍历 8.完整代码 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &…

web前端开发html/css练习

目标图&#xff1a; 素材&#xff1a; 代码&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns"http://www.w3.org/1999/xhtml"…

使用RSA工具进行对信息加解密

我们在开发中需要对用户敏感数据进行加解密&#xff0c;比如密码 这边科普一下RSA算法 RSA是非对称加密算法&#xff0c;与对称加密算法不同;在对称加密中&#xff0c;相同的密钥用于加密和解密数据,因此密钥的安全性至关重要;而在RSA非对称加密中&#xff0c;有两个密钥&…

【USRP】5G / 6G OAI 系统 5g / 6G OAI system

面向5G/6G科研应用 USRP专门用于5G/6G产品的原型开发与验证。该系统可以在实验室搭建一个真实的5G 网络&#xff0c;基于开源的代码&#xff0c;专为科研用户设计。 软件无线电架构&#xff0c;构建真实5G移动通信系统 X410 采用了目前流行的异构式系统&#xff0c;融合了FP…

SQLite基本使用

目录 1. 概述2. 引入SQLite3. 连接数据库创建游标4. 创建数据库文件5. 新增单条数据6. 批量新增数据7. 查询单条数据8.查询全部数据9. 查询指定条数的数据10. 修改数据11. 删除数据12. 事务回滚13. 关闭数据库关闭游标1. 概述 SQLite是一个进程内的库,实现了自给自足的、无服务…

【嵌入式开发 Linux 常用命令系列 4.2 -- .repo 各个目录介绍】

文章目录 概述.repo 目录结构manifests/default.xmlManifest 文件的作用default.xml 文件内容示例linkfile 介绍 .repo/projects 子目录配置和管理configHEADhooksinfo/excludeobjectsrr-cache 工作区中的对应目录 概述 repo 是一个由 Google 开发的版本控制工具&#xff0c;它…

使用 OMSA 和 OME 工具管理多个服务器

文章目录 Dell Remote Access Controller (iDRAC)OpenManage Server Administrator&#xff08;OMSA&#xff09;OpenManage EnterpriseSupportAssist Enterprise推荐阅读 在DELL服务器的管理工具中&#xff0c;有多个管理工具&#xff0c;今天我们将分享这几个工具的关联性以及…

2023-12-08 工作心得

1 别名不能作为 同一个sql里的where里条件约束 因为别名是在查询结果生成后才得到的&#xff0c;而 WHERE 子句是在查询结果生成前进行的筛选操作&#xff0c;所以别名不能直接用于 WHERE 子句中的条件筛选。 2 jpa sql里如果是删除或修改&#xff0c;加注解 modifying transa…

STM32的几个深入功能

STM32的几个深入功能 目录 1、时钟源2、锁相环3、备份SRAM4、low power mode5、DMA Flash RAM6、复位类型7、CMSIS8、STM32F4学习方法9、中断10、8080 并行接口11、FSMC12、ADC13、IIC14、SPI15、48516、CAN17、MPU6050六轴传感器18、NRF24L01 2.4G无线模块19、FLASH20、外部SR…

【Git系列】branch和tag

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…