首页 文章 精选 留言 我的

精选列表

搜索[javascript],共10000篇文章
优秀的个人博客,低调大师

【JavaScript框架封装】实现一个类似于JQuery的CSS样式框架的封装

版权声明:本文为博主原创文章,未经博主允许不得转载。更多学习资料请访问我爱科技论坛:www.52tech.tech https://blog.csdn.net/m0_37981569/article/details/81123779 // CSS 样式框架 (function (xframe) { // 需要参与链式访问的(必须使用prototype的方式来给对象扩充方法)【只要是需要使用到this获取到的元素集合这个变量的时候,这里就是需要进行链式访问的】 xframe.extend({ /** * 给DOM元素设置/取值CSS样式 * @return {*} */ css: function () { // 分为两种情况,一种是取值模式,一种是设置模式 var arg = arguments, len = arg.length, j = this.length - 1; if (len === 0) { // 没有参数的话,就直接返回这个DOM集合 return this; } else if (len === 1) { // 取值模式 if (typeof arg[0] === 'string') { if (this[0].currentStyle) { // w3c return this[0].currentStyle[arg[0]]; } else { // 其他IE return getComputedStyle(this[0], false)[arg[0]]; } } else if (typeof arg[0] === 'object') { // 如果要获取一系列对象的属性信息, 如果传过来的一个参数是一个json对象的话,这里也采用这种方式 // {name : xiugang, age : 18} for (var item in arg[0]) { // 从后向前开始遍历,设置模式 for (; j >= 0; j--) { // 由于CSS在设置值的时候的取值模式和设置模式的不同,这里需要先使用驼峰表示法进行处理一下 // 先把item转换为:backgroundcolor --> backgroundColor item = $.camelCase(item) this[j].style[item] = arg[0][item]; } } } } else if (len === 2) { // 设置模式 for (; j >= 0; j--) { // 第一个参数是我们需要设置的值 this[j].style[$.camelCase(arg[0])] = arg[1]; } } return this; }, /** * 隐藏一个元素 * @return {hide} */ hide: function () { var j = this.length - 1; for (; j >= 0; j--) { this[j].style.display = 'none'; } return this; // 方法二:使用之前封装好的框架进行遍历 this.each(function () { this.style.display = 'none'; }) }, /** * 显示元素 * @return {show} */ show: function () { this.each(function () { this.style.display = 'block'; }) return this; }, /** * 获取元素的宽度 * @return {*} */ width: function () { return this[0].clientWidth; }, /** * 获取元素的高度 * @return {*} */ height: function () { return this[0].clientHeight; }, /** * //当元素出现滚动条时候,这里的高度有两种:可视区域的高度 实际高度(可视高度+不可见的高度) * 获取元素的滚动宽度 * @return {*} */ scrollWidth: function () { return this[0].scrollWidth; }, /** * 获取元素的滚动高度 * @return {*} */ scrollHeight: function () { return this[0].scrollHeight; }, /** * 元素滚动的时候 如果出现滚动条 相对于左上角的偏移量 * @return {*} */ scrollTop: function () { return this[0].scrollTop; }, /** * 元素滚动的时候相对于左上角的距离 * @return {*} */ scrollLeft: function () { return this[0].scrollLeft; }, }); // 不需要参与链式访问的 xframe.extend(xframe, { getThis: function () { console.log(xframe, typeof this); // function, 这里的this指向的实际上是一个函数function (selector, context) }, /** * 获取屏幕的高度 * @return {number} */ screenHeight: function () { return window.screen.height; }, /** * 虎丘屏幕的款U盾 * @return {number} */ screenWidth: function () { return window.screen.width; }, /** * 获取浏览器窗口文档显示区域的宽度,不包括滚动条 * @return {number} */ wWidth: function () { return document.documentElement.clientWidth; }, /** * 获取浏览器窗口文档显示区域的高度,不包括滚动条 * @return {number} */ wHeight: function () { return document.documentElement.clientHeight; }, /** * 文档滚动区域的整体的高 * @return {number} */ wScrollHeight: function () { return document.body.scrollHeight; }, /** * 文档滚动区域的整体的宽度 * @return {number} */ wScrollWidth: function () { return document.body.scrollWidth; }, /** * 获取滚动条相对于其顶部的偏移 * @return {number} */ wScrollTop: function () { var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; return scrollTop; }, /** * 获取整个文档窗口的距离整个窗口的宽度和高度(滚动条相对于顶部和左边的距离) * @return {number} */ wScrollLeft: function () { var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft; return scrollLeft; } }); })(xframe);

优秀的个人博客,低调大师

【JavaScript框架封装】实现一个类似于JQuery的动画框架的封装

版权声明:本文为博主原创文章,未经博主允许不得转载。更多学习资料请访问我爱科技论坛:www.52tech.tech https://blog.csdn.net/m0_37981569/article/details/81123854 // 动画框架 (function (xframe) { // 需要参与链式访问的(必须使用prototype的方式来给对象扩充方法) xframe.extend({}); // 不需要参与链式访问的 xframe.extend(xframe, {}); // 实现动画框架的封装 xframe.Animate = (function (xframe) { // 1. 定义需要的API接口(API内部用于放置属性) var api = { timer: null,// 这是一个动画循环句柄 queen: [] // 多个对象同时运行的一个数组队列 }; // 运行部门------------------------------------------------- /** * 在把需要的运行参数都准备好了之后(多个对象),就开始执行这个运行函数 */ api.run = function () { // 定义一个定时器,用于不断地执行我自己定义的动画函数信息 api.timer = setInterval(function () { // 由于所有的参数都已经准备好了,因此这里只需要直接进行循环操作即可 api.loop(); }, 16); // 这里循环的周期设置的是16mm } /** * 执行动画循环操作 */ api.loop = function () { // obj里面存储了obj = {id, now, pass, tween, duration, style} api.queen.forEach(function (obj) { // 遍历队列中的每一项参数,开始执行移动操作 api.move(obj); }); } /** * 实现物体的移动 */ api.move = function (obj) { // 1. 计算当前的时间 obj.pass = +new Date(); // 2. 获取动画时间进程(这里的动画样式默认是一个弹簧的显示样式) var tween = api.getTween(obj.now, obj.pass, obj.duration, 'easeOutBounce'); // 注意我们再每一次移动这个物体对象之前需要把这个物体对象的动画时间进程更新一下,这样到了后面的修改对象的属性的时候这个参数的数值才会动态改变 obj.tween = tween; //console.log(tween); // 3. 设置属性信息 if (tween >= 1) { // 如果动画时间进程结束了(百分比信息) api.stop(); } else { // 4. 通过设置对象的属性信息来移动每一个对象 api.setManyProperty(obj); } } // 添加部门------------------------------------------------- /** * @param 获取用户输入的参数,开始对参数进行解析,开始添加参数,然后实现动画的开始运行 */ api.add = function () { var args = arguments, id = args[0], json = args[1], duration = args[2]; // 获取输入的参数,然后开始使用适配器解析数据 try { // 1. 调用适配器准备参数 api.adapterMany(id, json, duration); // 2. 开始运行动画 api.run(); } catch (e) { console.error(e.message); } } /** * 这是一个适配器,用于解析一个对象的参数信息(只能处理一个对象) * @param id * @param json * @param duration */ api.adapterOne = function (id, json, duration) { var obj = {} // 这里的OBj就是一个字面量格式, 用于存储需要的参数信息 obj.id = id // ID编号 obj.now = +new Date() // 开始时间 obj.pass = 0 // 当前时间 obj.tween = 0 // 动画时间进程 obj.duration = duration // 动画的持续时间 obj.styles = [] // 用于存放所有的样式信息 // 根据用户输入的参数信息选择不同的动画速度 if ($.isString(duration)) { switch (duration) { case 'slow': case '慢': duration = 8000; break; case 'normal': case '普通': duration = 4000; break; case 'fast': case '快': duration = 1000; break; } } // 设置样式信息 obj.styles = api.getStyles(id, json); return obj; } /** * 这个适配器针对的是处理多个对象的动画信息 * @param id * @param json * @param data */ api.adapterMany = function (id, json, data) { // 处理多个对象的参数信息(同样的参数,但是需要处理不同的信息,针对的是多个对象的参数) var obj = this.adapterOne(id, json, data); // 开始向我已有的队列中添加数据信息(此时queen队列里面就是存放了我所有的数据信息) api.queen.push(obj); } /** * 获取样式信息 * @param id * @param json */ api.getStyles = function (id, json) { // animate('#sun', {left: 200, top : 500}, 7000); // 把用户传递过来的参数信息转换我需要的格式 var styles = []; // 开始解析json数据信息 for (var item in json) { var style = {}; // 这里的item就是下面的:left, top style.name = item; // 获取物体开始的位置 style.start = parseFloat($(id).css(item).toString()); // 计算物体的偏移量(移动的距离) style.length = parseFloat(json[item]) - style.start; styles.push(style); } return styles; } /** * 用于获取一个动画时间进程 * @param now 开始时间 * @param pass 当前时间 * @param all 持续时间 * @param ease 动画效果 */ api.getTween = function (now, pass, all, ease) { // 1.定义常见的动画效果 var eases = { //线性匀速 linear: function (t, b, c, d) { return (c - b) * (t / d); }, //弹性运动 easeOutBounce: function (t, b, c, d) { if ((t /= d) < (1 / 2.75)) { return c * (7.5625 * t * t) + b; } else if (t < (2 / 2.75)) { return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b; } else if (t < (2.5 / 2.75)) { return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b; } else { return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b; } }, //其他 swing: function (t, b, c, d) { return this.easeOutQuad(t, b, c, d); }, easeInQuad: function (t, b, c, d) { return c * (t /= d) * t + b; }, easeOutQuad: function (t, b, c, d) { return -c * (t /= d) * (t - 2) + b; }, easeInOutQuad: function (t, b, c, d) { if ((t /= d / 2) < 1) return c / 2 * t * t + b; return -c / 2 * ((--t) * (t - 2) - 1) + b; }, easeInCubic: function (t, b, c, d) { return c * (t /= d) * t * t + b; }, easeOutCubic: function (t, b, c, d) { return c * ((t = t / d - 1) * t * t + 1) + b; }, easeInOutCubic: function (t, b, c, d) { if ((t /= d / 2) < 1) return c / 2 * t * t * t + b; return c / 2 * ((t -= 2) * t * t + 2) + b; }, easeInQuart: function (t, b, c, d) { return c * (t /= d) * t * t * t + b; }, easeOutQuart: function (t, b, c, d) { return -c * ((t = t / d - 1) * t * t * t - 1) + b; }, easeInOutQuart: function (t, b, c, d) { if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b; return -c / 2 * ((t -= 2) * t * t * t - 2) + b; }, easeInQuint: function (t, b, c, d) { return c * (t /= d) * t * t * t * t + b; }, easeOutQuint: function (t, b, c, d) { return c * ((t = t / d - 1) * t * t * t * t + 1) + b; }, easeInOutQuint: function (t, b, c, d) { if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b; return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; }, easeInSine: function (t, b, c, d) { return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; }, easeOutSine: function (t, b, c, d) { return c * Math.sin(t / d * (Math.PI / 2)) + b; }, easeInOutSine: function (t, b, c, d) { return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; }, easeInExpo: function (t, b, c, d) { return (t == 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; }, easeOutExpo: function (t, b, c, d) { return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; }, easeInOutExpo: function (t, b, c, d) { if (t == 0) return b; if (t == d) return b + c; if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b; return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; }, easeInCirc: function (t, b, c, d) { return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; }, easeOutCirc: function (t, b, c, d) { return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; }, easeInOutCirc: function (t, b, c, d) { if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; }, easeInElastic: function (t, b, c, d) { var s = 1.70158; var p = 0; var a = c; if (t == 0) return b; if ((t /= d) == 1) return b + c; if (!p) p = d * .3; if (a < Math.abs(c)) { a = c; var s = p / 4; } else var s = p / (2 * Math.PI) * Math.asin(c / a); return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; }, easeOutElastic: function (t, b, c, d) { var s = 1.70158; var p = 0; var a = c; if (t == 0) return b; if ((t /= d) == 1) return b + c; if (!p) p = d * .3; if (a < Math.abs(c)) { a = c; var s = p / 4; } else var s = p / (2 * Math.PI) * Math.asin(c / a); return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b; }, easeInOutElastic: function (t, b, c, d) { var s = 1.70158; var p = 0; var a = c; if (t == 0) return b; if ((t /= d / 2) == 2) return b + c; if (!p) p = d * (.3 * 1.5); if (a < Math.abs(c)) { a = c; var s = p / 4; } else var s = p / (2 * Math.PI) * Math.asin(c / a); if (t < 1) return -.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * .5 + c + b; }, easeInBack: function (t, b, c, d, s) { if (s == undefined) s = 1.70158; return c * (t /= d) * t * ((s + 1) * t - s) + b; }, easeOutBack: function (t, b, c, d, s) { if (s == undefined) s = 1.70158; return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; }, easeInOutBack: function (t, b, c, d, s) { if (s == undefined) s = 1.70158; if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; }, easeInBounce: function (t, b, c, d) { return c - this.easeOutBounce(d - t, 0, c, d) + b; }, easeInOutBounce: function (t, b, c, d) { if (t < d / 2) return this.easeInBounce(t * 2, 0, c, d) * .5 + b; return this.easeOutBounce(t * 2 - d, 0, c, d) * .5 + c * .5 + b; } }; // 2. 计算每一次动画循环的小号时长 var yongshi = pass - now; // 3. 获取相应的动画效果 return eases[ease](yongshi, 0, 1, all); } /** * 通过设置一个对象的属性信息来实现物体的运动效果(如果只有一个属性信息的话) * @param obj */ api.setOneProperty = function (obj) { // 用于设置一个对象的属性信息(obj.id, obj.json) // 【注意点】:这里是动画实现的一个核心要点,通过修改对象的属性信息来移动物体 if (obj.name === 'opacity') { $(obj.id).css(obj.name, (obj.start + obj.length * obj.tween)); } else { // 对于设置对象的其他属性信息都是需要添加一个px,像素值信息 $(obj.id).css(obj.name, (obj.start + obj.length * obj.tween) + 'px'); } } /** * 用于设置一个对象的锁哥属性信息 obj.json = {width : '200px', height : '500px', 'opacity' : '0.1'} */ api.setManyProperty = function (obj) { // 由于obj.styles里面是一个数组 obj.styles.forEach(function (style) { // 遍历当前对象的所有样式属性信息 obj.name = style.name; obj.start = style.start; obj.length = style.length; api.setOneProperty(obj); console.log(obj.tween); }); // 由于styles里面只存储了style.name, style.start, style.length三个属性信息, 因此需要处理一下 } /** * 结束动画的执行 */ api.stop = function () { clearInterval(api.timer); } // 后勤部门---------------------------------------------------- api.destory = function () { } // 用户只需要把需要的参数添加进来们就可以执行一个动画 // 用户只需要传进来三个参数,id, json, duration就可以实现一个动画 xframe.animate = api.add; })(xframe); })(xframe);

优秀的个人博客,低调大师

身份证号码的正则表达式及验证详解(JavaScript,Regex)

简言 在做用户实名验证时,常会用到身份证号码的正则表达式及校验方案。本文列举了两种验证方案,大家可以根据自己的项目实际情况,选择适合的方案。 身份证号码说明 居民身份证号码,正确、正式的称谓应该是“公民身份号码”。根据【中华人民共和国国家标准 GB 11643-1999】中有关公民身份号码的规定,公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成。排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。 以北京市朝阳区一女性身份证号码为例,身份证号码所表示的含义如下图所示: 注:该身份证号码来源于国标【GB 11643-1999】。 下面我们就从零开始完成一个完整的身份证号码校验过程。 方案1 (简单) 1.1 分部规则 我们首先提出方案1,并分步做如下规则定义: 1.1.1 地址码规则: 地址码长6位 以数字1-9开头 后5位为0-9的数字 根据以上规则,写出地址码的正则表达式: /^[1-9]\d{5}/ 1.1.2 年份码规则: 年份码长4位 以数字18,19或20开头 剩余两位为0-9的数字 根据以上规则,写出年份码的正则表达式: /(18|19|20)\d{2}/。如果不需要18开头的年份,可以去掉18。 1.1.3 月份码规则: 月份码长2位 第一位数字为0,第二位数字为1-9 或者第一位数字为1,第二位数字为0-2 根据以上规则,写出月份码的正则表达式: /((0[1-9])|(1[0-2]))/。 1.1.4 日期码规则: 日期码长2位 第一位数字为0-2,第二位数字为1-9 或者是10,20,30,31 根据以上规则,写出日期码的正则表达式 :/(([0-2][1-9])|10|20|30|31)/。 1.1.5 顺序码规则: 顺序码长3位 顺序码是数字 根据以上规则,写出顺序码的正则表达式 :/\d{3}/。 1.1.6 校验码规则: 校验码长1位 可以是数字,字母x或字母X 根据以上规则,写出校验码的正则表达式 :/[0-9Xx]/。 1.2 方案1正则表达式 综合以上6条规则,给出完整的正则表达式及测试程序如下: var p = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/; //输出 true console.log(p.test("11010519491231002X")); //输出 false 不能以0开头 console.log(p.test("01010519491231002X")); //输出 false 年份不能以17开头 console.log(p.test("11010517491231002X")); //输出 false 月份不能为13 console.log(p.test("11010519491331002X")); //输出 false 日期不能为32 console.log(p.test("11010519491232002X")); //输出 false 不能以a结尾 console.log(p.test("11010519491232002a")); 演示代码 1.3 方案1分析 方案1只是做了基本的格式判定,存在三个主要的不足: 地址码判定不够精确。例:我国并不存在16,26开头的地区,却可通过验证 日期判定不够精确。例:19490231也可通过验证,而2月并不存在31日 校验码是由17位本体码计算得出,方案1并未校验此码 方案2 (全面) 根据方案1的不足,引入方案2进而改进方案1的不足。 2.1 省级地址码校验 华北:北京11,天津12,河北13,山西14,内蒙古15 东北: 辽宁21,吉林22,黑龙江23 华东: 上海31,江苏32,浙江33,安徽34,福建35,江西36,山东37 华中: 河南41,湖北42,湖南43 华南: 广东44,广西45,海南46 西南: 四川51,贵州52,云南53,西藏54,重庆50 西北: 陕西61,甘肃62,青海63,宁夏64,新疆65 特别:台湾71,香港81,澳门82 根据上述地址码做身份证号码的前两位校验,进一步的提高准确率。当前的地址码以2013版的行政区划代码【GB/T2260】为标准。由于区划代码的历史演变,使得地址码后四位校验变得不太可能。以三胖的身份证号为例,本人号码是2321开头,而当前行政区划代码表中并无此代码。因此本文只做前两位省级地址码的校验。 也有说法表述91开头是外国人取得中国身份证号码的前两位编码,但本人并未得到证实。如有持91开头身份证或认识马布里的,请帮忙确认相关信息。 根据以上分析,给出省级地址码校验及测试程序如下: var checkProv = function (val) { var pattern = /^[1-9][0-9]/; var provs = {11:"北京",12:"天津",13:"河北",14:"山西",15:"内蒙古",21:"辽宁",22:"吉林",23:"黑龙江 ",31:"上海",32:"江苏",33:"浙江",34:"安徽",35:"福建",36:"江西",37:"山东",41:"河南",42:"湖北 ",43:"湖南",44:"广东",45:"广西",46:"海南",50:"重庆",51:"四川",52:"贵州",53:"云南",54:"西藏 ",61:"陕西",62:"甘肃",63:"青海",64:"宁夏",65:"新疆",71:"台湾",81:"香港",82:"澳门"}; if(pattern.test(val)) { if(provs[val]) { return true; } } return false; } //输出 true,37是山东 console.log(checkProv(37)); //输出 false,16不存在 console.log(checkProv(16)); 演示代码 2.2 出生日期码校验 出生日期码的校验不做解释,直接给出如下函数及测试程序: var checkDate = function (val) { var pattern = /^(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)$/; if(pattern.test(val)) { var year = val.substring(0, 4); var month = val.substring(4, 6); var date = val.substring(6, 8); var date2 = new Date(year+"-"+month+"-"+date); if(date2 && date2.getMonth() == (parseInt(month) - 1)) { return true; } } return false; } //输出 true console.log(checkDate("20180212")); //输出 false 2月没有31日 console.log(checkDate("20180231")); 演示代码 2.3 校验码校验 校验码的计算略复杂,先给出如下公式: 其中 ai 表示身份证本体码的第 i 位值,而 Wi 表示第 i 位的加权因子值。 加权因子表 【表1】: i 1 2 3 4 5 6 7 8 Wi 7 9 10 5 8 4 2 1 9 10 11 12 13 14 15 16 17 6 3 7 9 10 5 8 4 2 X与校验码换算表 【表2】 X 0 1 2 3 4 5 6 7 8 9 10 a18 1 0 X 9 8 7 6 5 4 3 2 算法过程: 根据身份证主体码(前17位)分别与对应的加权因子(表1)计算乘积再求和,根据所得结果与11取模得到X值。 根据 X 值查询表2,得出a18即校验码值。 校验码计算程序及测试见如下代码: var checkCode = function (val) { var p = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/; var factor = [ 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 ]; var parity = [ 1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2 ]; var code = val.substring(17); if(p.test(val)) { var sum = 0; for(var i=0;i<17;i++) { sum += val[i]*factor[i]; } if(parity[sum % 11] == code.toUpperCase()) { return true; } } return false; } // 输出 true, 校验码相符 console.log(checkCode("11010519491231002X")); // 输出 false, 校验码不符 console.log(checkCode("110105194912310021")); 演示代码 2.4 方案2整体代码 var checkID = function (val) { if(checkCode(val)) { var date = val.substring(6,14); if(checkDate(date)) { if(checkProv(val.substring(0,2))) { return true; } } } return false; } //输出 true console.log(checkID("11010519491231002X")); //输出 false,校验码不符 console.log(checkID("110105194912310021")); //输出 false,日期码不符 console.log(checkID("110105194902310026")); //输出 false,地区码不符 console.log(checkID("160105194912310029")); 演示代码 以上为三胖对身份证号码验证的理解和分析,如有不足请大家予以指正。 知名物理学家史蒂芬?霍金于2018年3月14日去世,享年76岁。一个博学又有趣的人,一路走好! 世间再无霍金,时间永留简史! 原文地址

优秀的个人博客,低调大师

JavaScript 中 structuredClone 的深拷贝:为什么有时比 JSON.parse(JSON.stringify()) 更可靠

日常开发里经常需要把一个对象完整复制出来,避免修改副本时影响原数据。过去最常见写法是 `JSON.parse(JSON.stringify(obj))`,它简单、兼容性好,但遇到 `Date`、`RegExp`、`Map`、`Set`、`ArrayBuffer`、循环引用时会直接失效:日期变成字符串,正则变为空对象,`Map` 和 `Set` 直接变成 `{}`,循环引用会抛出异常。浏览器和 Node.js 从较新版本开始提供了全局方法 `structuredClone`,它基于结构化克隆算法,能处理这些类型,还支持循环引用、`File`、`Blob`、`ImageData` 等。这篇文章不会罗列 API 文档,而是从真实业务场景出发,看它和 JSON 方案的差异、边界、错误处理,以及什么情况下仍然要手写递归或使用第三方库。 ## 从一次配置合并说起 假设有一个图表配置对象,里面包含颜色、格式化函数、正则校验和时间范围: ```js const baseConfig = { color: '#4f46e5', dateRange: { start: new Date('2024-01-01'), end: new Date('2024-01-31') }, pattern: /^\d{4}-\d{2}-\d{2}$/, tooltipFormatter(value) { return `${value}`; }, meta: new Map([['source', 'api']]), }; ``` 用 JSON 方案复制: ```js const copy = JSON.parse(JSON.stringify(baseConfig)); console.log(copy.dateRange.start); // "2024-01-01T00:00:00.000Z" 字符串,不是 Date console.log(copy.pattern); // {} 空对象 console.log(copy.tooltipFormatter); // undefined,函数被丢掉 console.log(copy.meta); // {} 空对象 ``` 这些变化往往不会立刻报错,而是等到下游用 `instanceof Date` 判断、调用 `pattern.test()`、执行 `tooltipFormatter` 时才暴露,排查起来很绕。用 `structuredClone`: ```js const copy = structuredClone(baseConfig); console.log(copy.dateRange.start instanceof Date); // true console.log(copy.pattern instanceof RegExp); // true console.log(copy.meta instanceof Map); // true console.log(copy.tooltipFormatter); // undefined,函数依然不能被克隆 ``` 函数、`Symbol`、`WeakMap`、`WeakSet`、DOM 节点仍然无法克隆,遇到这些会抛出 `DataCloneError`。`structuredClone` 不是万能的,但它在数据类对象的复制上比 JSON 方案可靠得多。 ## 循环引用不再爆栈 JSON 方案对循环引用会直接抛错: ```js const node = { name: 'root' }; node.self = node; JSON.parse(JSON.stringify(node)); // TypeError: Converting circular structure to JSON ``` `structuredClone` 能正确处理: ```js const cloned = structuredClone(node); console.log(cloned.self === cloned); // true ``` 在树形菜单、图结构、双向链表这类数据里,循环引用很常见,以前要么手动递归加 `WeakMap` 记录已拷贝对象,要么引入 lodash 的 `cloneDeep`。现在对于纯数据部分,一行就够了。 ## 和 lodash.cloneDeep 的关系 lodash 的 `cloneDeep` 支持函数、原型链、Symbol 等更复杂的情况,也能在很老的运行时里工作。`structuredClone` 是平台内置,没有额外包体积,但限制也很硬:不能克隆函数和 DOM,不会保留原型链,克隆后的对象是普通对象。如果你的对象里混了类实例,`structuredClone` 会把它降级成普通对象,方法全部丢失: ```js class User { constructor(name) { this.name = name; } greet() { return `hi ${this.name}`; } } const u = new User('alice'); const c = structuredClone(u); console.log(c.greet); // undefined ``` 这种场景要么自己实现带原型恢复的深拷贝,要么继续用 lodash。选择依据很简单:纯数据、只关心值、不上生产老环境,选 `structuredClone`;需要保留类行为或兼容旧浏览器,用成熟库。 ## 一个容易被忽略的坑:transfer `structuredClone` 的第二个参数可以传入 `transfer` 数组,把 `ArrayBuffer` 这类可转移对象的所有权转走,而不是复制。转移后原对象会被置空: ```js const buffer = new ArrayBuffer(8); const view = new Uint8Array(buffer); view[0] = 42; const cloned = structuredClone(buffer, { transfer: [buffer] }); console.log(buffer.byteLength); // 0,原 buffer 已被 detach console.log(new Uint8Array(cloned)[0]); // 42 ``` 这在处理大块二进制数据时能避免复制开销,但如果你后面还要用原 buffer,就会拿到一个长度为 0 的空壳。transfer 适合明确知道原数据不再使用的场景,比如把图片数据交给 Worker 后不再在主线程操作。 ## 兼容性和打包注意事项 `structuredClone` 在 Chrome 98、Firefox 94、Safari 15.4 之后可用,Node.js 从 17 开始提供全局方法。如果项目还要支持更老的浏览器,直接调用会得到 `structuredClone is not defined`。常见的降级写法是: ```js function deepClone(value) { if (typeof structuredClone === 'function') { try { return structuredClone(value); } catch (err) { // 遇到不可克隆类型时回退 } } return JSON.parse(JSON.stringify(value)); } ``` 但要注意,回退到 JSON 方案会丢失前面提到的那些类型。更稳妥的做法是判定数据类型,明确不支持时抛出可读错误,而不是悄悄降级造成数据形状变化。 ## 克隆后修改的隔离性 `structuredClone` 是真正的深拷贝,嵌套对象、数组、Map 里的值都会独立。 ```js const original = { list: [{ id: 1, tags: ['a'] }], meta: new Map([['count', 1]]), }; const copy = structuredClone(original); copy.list[0].tags.push('b'); copy.meta.set('count', 2); console.log(original.list[0].tags); // ['a'] console.log(original.meta.get('count')); // 1 ``` 这一点和 JSON 方案一致,但 `structuredClone` 能保留 `Map` 的结构,不会把 `Map` 变成普通对象,这对依赖 `Map` 做缓存的代码很重要。 ## 什么时候不要用它 如果对象里包含函数、类实例、DOM 节点、Symbol 键,`structuredClone` 要么丢弃要么抛错。Vue 的响应式代理对象也不能直接克隆,因为代理背后指向原始对象,克隆后可能丢失响应式追踪。在框架里做状态复制时,优先用框架提供的工具,比如 Vue 的 `toRaw` 配合深拷贝,或者直接重新构造状态。 `structuredClone` 解决的是“结构化数据”的复制问题,不是通用对象复制。把它用在配置对象、接口返回数据、缓存快照、Worker 消息传递这些场景,代码会干净很多;碰到类实例和函数,还是得用更重的方案。 推荐查看天气:https://www.0dtq.com/heshuo/40tian.html 推荐查看天气:https://www.0dtq.com/bohu/ 推荐查看天气:https://www.0dtq.com/bohu/zuotian.html 推荐查看天气:https://www.0dtq.com/bohu/7tian.html 推荐查看天气:https://www.0dtq.com/bohu/10tian.html 推荐查看天气:https://www.0dtq.com/bohu/15tian.html 推荐查看天气:https://www.0dtq.com/bohu/40tian.html 推荐查看天气:https://www.0dtq.com/akesu/ 推荐查看天气:https://www.0dtq.com/akesu/zuotian.html 推荐查看天气:https://www.0dtq.com/akesu/7tian.html 推荐查看天气:https://www.0dtq.com/akesu/10tian.html 推荐查看天气:https://www.0dtq.com/akesu/15tian.html 推荐查看天气:https://www.0dtq.com/akesu/40tian.html 推荐查看天气:https://www.0dtq.com/wensu/ 推荐查看天气:https://www.0dtq.com/wensu/zuotian.html 推荐查看天气:https://www.0dtq.com/wensu/7tian.html 推荐查看天气:https://www.0dtq.com/wensu/10tian.html 推荐查看天气:https://www.0dtq.com/wensu/15tian.html 推荐查看天气:https://www.0dtq.com/wensu/40tian.html 推荐查看天气:https://www.0dtq.com/kuche/ 推荐查看天气:https://www.0dtq.com/kuche/zuotian.html 推荐查看天气:https://www.0dtq.com/kuche/7tian.html 推荐查看天气:https://www.0dtq.com/kuche/10tian.html 推荐查看天气:https://www.0dtq.com/kuche/15tian.html 推荐查看天气:https://www.0dtq.com/kuche/40tian.html 推荐查看天气:https://www.0dtq.com/shaya/ 推荐查看天气:https://www.0dtq.com/shaya/zuotian.html 推荐查看天气:https://www.0dtq.com/shaya/7tian.html 推荐查看天气:https://www.0dtq.com/shaya/10tian.html 推荐查看天气:https://www.0dtq.com/shaya/15tian.html 推荐查看天气:https://www.0dtq.com/shaya/40tian.html 推荐查看天气:https://www.0dtq.com/xinhe/ 推荐查看天气:https://www.0dtq.com/xinhe/zuotian.html 推荐查看天气:https://www.0dtq.com/xinhe/7tian.html 推荐查看天气:https://www.0dtq.com/xinhe/10tian.html 推荐查看天气:https://www.0dtq.com/xinhe/15tian.html 推荐查看天气:https://www.0dtq.com/xinhe/40tian.html 推荐查看天气:https://www.0dtq.com/baichengxian/ 推荐查看天气:https://www.0dtq.com/baichengxian/zuotian.html 推荐查看天气:https://www.0dtq.com/baichengxian/7tian.html 推荐查看天气:https://www.0dtq.com/baichengxian/10tian.html 推荐查看天气:https://www.0dtq.com/baichengxian/15tian.html 推荐查看天气:https://www.0dtq.com/baichengxian/40tian.html 推荐查看天气:https://www.0dtq.com/wushi/ 推荐查看天气:https://www.0dtq.com/wushi/zuotian.html 推荐查看天气:https://www.0dtq.com/wushi/7tian.html 推荐查看天气:https://www.0dtq.com/wushi/10tian.html 推荐查看天气:https://www.0dtq.com/wushi/15tian.html 推荐查看天气:https://www.0dtq.com/wushi/40tian.html 推荐查看天气:https://www.0dtq.com/awati/ 推荐查看天气:https://www.0dtq.com/awati/zuotian.html 推荐查看天气:https://www.0dtq.com/awati/7tian.html 推荐查看天气:https://www.0dtq.com/awati/10tian.html 推荐查看天气:https://www.0dtq.com/awati/15tian.html 推荐查看天气:https://www.0dtq.com/awati/40tian.html 推荐查看天气:https://www.0dtq.com/keping/ 推荐查看天气:https://www.0dtq.com/keping/zuotian.html 推荐查看天气:https://www.0dtq.com/keping/7tian.html 推荐查看天气:https://www.0dtq.com/keping/10tian.html 推荐查看天气:https://www.0dtq.com/keping/15tian.html 推荐查看天气:https://www.0dtq.com/keping/40tian.html 推荐查看天气:https://www.0dtq.com/kezhou/ 推荐查看天气:https://www.0dtq.com/kezhou/zuotian.html 推荐查看天气:https://www.0dtq.com/kezhou/7tian.html 推荐查看天气:https://www.0dtq.com/kezhou/10tian.html 推荐查看天气:https://www.0dtq.com/kezhou/15tian.html 推荐查看天气:https://www.0dtq.com/kezhou/40tian.html 推荐查看天气:https://www.0dtq.com/atushi/ 推荐查看天气:https://www.0dtq.com/atushi/zuotian.html 推荐查看天气:https://www.0dtq.com/atushi/7tian.html 推荐查看天气:https://www.0dtq.com/atushi/10tian.html 推荐查看天气:https://www.0dtq.com/atushi/15tian.html 推荐查看天气:https://www.0dtq.com/atushi/40tian.html 推荐查看天气:https://www.0dtq.com/aketao/ 推荐查看天气:https://www.0dtq.com/aketao/zuotian.html 推荐查看天气:https://www.0dtq.com/aketao/7tian.html 推荐查看天气:https://www.0dtq.com/aketao/10tian.html 推荐查看天气:https://www.0dtq.com/aketao/15tian.html 推荐查看天气:https://www.0dtq.com/aketao/40tian.html 推荐查看天气:https://www.0dtq.com/aheqi/ 推荐查看天气:https://www.0dtq.com/aheqi/zuotian.html 推荐查看天气:https://www.0dtq.com/aheqi/7tian.html 推荐查看天气:https://www.0dtq.com/aheqi/10tian.html 推荐查看天气:https://www.0dtq.com/aheqi/15tian.html 推荐查看天气:https://www.0dtq.com/aheqi/40tian.html 推荐查看天气:https://www.0dtq.com/wuqia/ 推荐查看天气:https://www.0dtq.com/wuqia/zuotian.html 推荐查看天气:https://www.0dtq.com/wuqia/7tian.html 推荐查看天气:https://www.0dtq.com/wuqia/10tian.html 推荐查看天气:https://www.0dtq.com/wuqia/15tian.html 推荐查看天气:https://www.0dtq.com/wuqia/40tian.html 推荐查看天气:https://www.0dtq.com/kashi/ 推荐查看天气:https://www.0dtq.com/kashi/zuotian.html 推荐查看天气:https://www.0dtq.com/kashi/7tian.html 推荐查看天气:https://www.0dtq.com/kashi/10tian.html 推荐查看天气:https://www.0dtq.com/kashi/15tian.html 推荐查看天气:https://www.0dtq.com/kashi/40tian.html 推荐查看天气:https://www.0dtq.com/shufu/ 推荐查看天气:https://www.0dtq.com/shufu/zuotian.html 推荐查看天气:https://www.0dtq.com/shufu/7tian.html 推荐查看天气:https://www.0dtq.com/shufu/10tian.html 推荐查看天气:https://www.0dtq.com/shufu/15tian.html 推荐查看天气:https://www.0dtq.com/shufu/40tian.html 推荐查看天气:https://www.0dtq.com/shule/ 推荐查看天气:https://www.0dtq.com/shule/zuotian.html 推荐查看天气:https://www.0dtq.com/shule/7tian.html 推荐查看天气:https://www.0dtq.com/shule/10tian.html 推荐查看天气:https://www.0dtq.com/shule/15tian.html 推荐查看天气:https://www.0dtq.com/shule/40tian.html 推荐查看天气:https://www.0dtq.com/yingjisha/ 推荐查看天气:https://www.0dtq.com/yingjisha/zuotian.html 推荐查看天气:https://www.0dtq.com/yingjisha/7tian.html 推荐查看天气:https://www.0dtq.com/yingjisha/10tian.html 推荐查看天气:https://www.0dtq.com/yingjisha/15tian.html 推荐查看天气:https://www.0dtq.com/yingjisha/40tian.html 推荐查看天气:https://www.0dtq.com/zepu/ 推荐查看天气:https://www.0dtq.com/zepu/zuotian.html 推荐查看天气:https://www.0dtq.com/zepu/7tian.html 推荐查看天气:https://www.0dtq.com/zepu/10tian.html 推荐查看天气:https://www.0dtq.com/zepu/15tian.html 推荐查看天气:https://www.0dtq.com/zepu/40tian.html 推荐查看天气:https://www.0dtq.com/shache/ 推荐查看天气:https://www.0dtq.com/shache/zuotian.html 推荐查看天气:https://www.0dtq.com/shache/7tian.html 推荐查看天气:https://www.0dtq.com/shache/10tian.html 推荐查看天气:https://www.0dtq.com/shache/15tian.html 推荐查看天气:https://www.0dtq.com/shache/40tian.html 推荐查看天气:https://www.0dtq.com/yecheng/ 推荐查看天气:https://www.0dtq.com/yecheng/zuotian.html 推荐查看天气:https://www.0dtq.com/yecheng/7tian.html 推荐查看天气:https://www.0dtq.com/yecheng/10tian.html 推荐查看天气:https://www.0dtq.com/yecheng/15tian.html 推荐查看天气:https://www.0dtq.com/yecheng/40tian.html 推荐查看天气:https://www.0dtq.com/maigaiti/ 推荐查看天气:https://www.0dtq.com/maigaiti/zuotian.html 推荐查看天气:https://www.0dtq.com/maigaiti/7tian.html 推荐查看天气:https://www.0dtq.com/maigaiti/10tian.html 推荐查看天气:https://www.0dtq.com/maigaiti/15tian.html 推荐查看天气:https://www.0dtq.com/maigaiti/40tian.html 推荐查看天气:https://www.0dtq.com/yuepuhu/ 推荐查看天气:https://www.0dtq.com/yuepuhu/zuotian.html 推荐查看天气:https://www.0dtq.com/yuepuhu/7tian.html 推荐查看天气:https://www.0dtq.com/yuepuhu/10tian.html 推荐查看天气:https://www.0dtq.com/yuepuhu/15tian.html 推荐查看天气:https://www.0dtq.com/yuepuhu/40tian.html 推荐查看天气:https://www.0dtq.com/jiashi/ 推荐查看天气:https://www.0dtq.com/jiashi/zuotian.html 推荐查看天气:https://www.0dtq.com/jiashi/7tian.html 推荐查看天气:https://www.0dtq.com/jiashi/10tian.html 推荐查看天气:https://www.0dtq.com/jiashi/15tian.html 推荐查看天气:https://www.0dtq.com/jiashi/40tian.html 推荐查看天气:https://www.0dtq.com/bachu/ 推荐查看天气:https://www.0dtq.com/bachu/zuotian.html 推荐查看天气:https://www.0dtq.com/bachu/7tian.html 推荐查看天气:https://www.0dtq.com/bachu/10tian.html 推荐查看天气:https://www.0dtq.com/bachu/15tian.html 推荐查看天气:https://www.0dtq.com/bachu/40tian.html 推荐查看天气:https://www.0dtq.com/tashikuergan/ 推荐查看天气:https://www.0dtq.com/tashikuergan/zuotian.html 推荐查看天气:https://www.0dtq.com/tashikuergan/7tian.html 推荐查看天气:https://www.0dtq.com/tashikuergan/10tian.html 推荐查看天气:https://www.0dtq.com/tashikuergan/15tian.html 推荐查看天气:https://www.0dtq.com/tashikuergan/40tian.html 推荐查看天气:https://www.0dtq.com/hetian/ 推荐查看天气:https://www.0dtq.com/hetian/zuotian.html 推荐查看天气:https://www.0dtq.com/hetian/7tian.html 推荐查看天气:https://www.0dtq.com/hetian/10tian.html 推荐查看天气:https://www.0dtq.com/hetian/15tian.html 推荐查看天气:https://www.0dtq.com/hetian/40tian.html 推荐查看天气:https://www.0dtq.com/moyu/ 推荐查看天气:https://www.0dtq.com/moyu/zuotian.html 推荐查看天气:https://www.0dtq.com/moyu/7tian.html 推荐查看天气:https://www.0dtq.com/moyu/10tian.html 推荐查看天气:https://www.0dtq.com/moyu/15tian.html 推荐查看天气:https://www.0dtq.com/moyu/40tian.html 推荐查看天气:https://www.0dtq.com/pishan/ 推荐查看天气:https://www.0dtq.com/pishan/zuotian.html 推荐查看天气:https://www.0dtq.com/pishan/7tian.html 推荐查看天气:https://www.0dtq.com/pishan/10tian.html 推荐查看天气:https://www.0dtq.com/pishan/15tian.html 推荐查看天气:https://www.0dtq.com/pishan/40tian.html 推荐查看天气:https://www.0dtq.com/luopu/ 推荐查看天气:https://www.0dtq.com/luopu/zuotian.html 推荐查看天气:https://www.0dtq.com/luopu/7tian.html 推荐查看天气:https://www.0dtq.com/luopu/10tian.html 推荐查看天气:https://www.0dtq.com/luopu/15tian.html 推荐查看天气:https://www.0dtq.com/luopu/40tian.html 推荐查看天气:https://www.0dtq.com/cele/ 推荐查看天气:https://www.0dtq.com/cele/zuotian.html 推荐查看天气:https://www.0dtq.com/cele/7tian.html 推荐查看天气:https://www.0dtq.com/cele/10tian.html 推荐查看天气:https://www.0dtq.com/cele/15tian.html 推荐查看天气:https://www.0dtq.com/cele/40tian.html 推荐查看天气:https://www.0dtq.com/yutian/ 推荐查看天气:https://www.0dtq.com/yutian/zuotian.html 推荐查看天气:https://www.0dtq.com/yutian/7tian.html 推荐查看天气:https://www.0dtq.com/yutian/10tian.html 推荐查看天气:https://www.0dtq.com/yutian/15tian.html 推荐查看天气:https://www.0dtq.com/yutian/40tian.html 推荐查看天气:https://www.0dtq.com/minfeng/

优秀的个人博客,低调大师

JavaScript 正则匹配中的 lastIndex 陷阱:为什么 test 和 exec 会交替返回 true 和 false

用同一个正则对象反复测试不同字符串,明明字符串都匹配,却出现一次 true 一次 false 的诡异现象,这是不少人在表单校验、循环过滤或批量替换时踩过的坑。问题大多不在正则模式本身,而是带了 g 或 y 标志的正则对象会维护 lastIndex 状态。调用 test 和 exec 时,匹配成功会更新这个值,下一次从新位置继续找,匹配失败才把它重置为 0。于是跨字符串复用同一实例时就容易出现交替结果。这篇文章从 JS 引擎的执行规则出发,解释 lastIndex 的行为边界,给出几种可靠的修正写法,并说明为什么在 React 组件、工具函数和模块级常量的场景里尤其容易撞上这个坑,以及如何通过代码审查和测试提前发现。 ## 先看一段会翻车的代码 ```js const pattern = /\d+/g; console.log(pattern.test('abc123')); // true console.log(pattern.test('def456')); // false console.log(pattern.test('ghi789')); // true ``` 三个字符串都包含数字,按理都应该返回 true。结果却是 true、false、true 交替。 换个写法,用 exec: ```js const re = /foo/g; re.exec('foo bar'); // ['foo'],lastIndex 变成 3 re.exec('foo bar'); // 从索引 3 开始找不到,返回 null,并把 lastIndex 重置为 0 re.exec('foo bar'); // ['foo'] ``` 现象一致。根因就是 lastIndex。 ## lastIndex 什么时候生效 lastIndex 是正则实例上的一个可读写属性,默认是 0。它只对带 g(global)或 y(sticky)标志的正则起作用。 - 带 g 或 y 时:exec 和 test 从 lastIndex 指定的位置开始搜索。如果找到匹配,lastIndex 被更新为匹配结束的下一个字符索引;如果没找到,lastIndex 重置为 0。 - 不带 g 或 y 时:exec 和 test 始终从字符串开头搜索,并且不会读写 lastIndex。 验证一下: ```js const reG = /a/g; console.log(reG.lastIndex); // 0 reG.test('aa'); console.log(reG.lastIndex); // 1 const reNoFlag = /a/; reNoFlag.test('aa'); console.log(reNoFlag.lastIndex); // 0 ``` 顺带说一个容易忽略的点:`String.prototype.matchAll` 要求正则必须带 g 标志,否则会抛 TypeError。而 `String.prototype.match` 在高版本规范里如果带 g,会一次性返回所有匹配并把 lastIndex 重置为 0,行为跟 exec 不同。 ## 为什么模块级正则最容易出事 把正则提到模块顶层或者组件外部,是常见的优化手段,能避免每次渲染都新建对象。但在带 g 的情况下,这个对象也就变成了全局可变状态。 看一个 React 场景: ```js const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/g; function EmailList({ emails }) { return ( {emails.map((e) => ( {e} ))} ); } ``` emails 里的每一项单独看都是合法邮箱,但渲染出来会有一半标成 bad。因为 map 每次调用 test 都复用了同一个正则实例,lastIndex 在推进。 类似地,在 utill 文件里导出这类正则给多个模块共用,问题会更隐蔽:不同调用方相互干扰,lastIndex 状态谁都说不清。 ## 直接去修正 lastIndex 可行吗 最直接的方式是在每次调用前把它归零: ```js const pattern = /\d+/g; function isNumeric(str) { pattern.lastIndex = 0; return pattern.test(str); } ``` 能用,但有两个代价。一个是每个调用点都得记得写,一旦漏掉就复现原问题;另一个是当代码在多处并发或异步执行时,归零和 test 之间并非原子操作,虽然 JS 单线程里同步代码没事,但混入异步逻辑之后仍然容易读错状态。 只对带 g 的正则这么做。不带 g 时手动改 lastIndex 不起作用,因为引擎不读它。 ## 更稳的做法:不带 g,或者每次新建 如果目的只是“判断某个字符串是否匹配”,绝大多数场景根本不需要 g 标志。 ```js const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; emailRe.test('a@b.co'); // true emailRe.test('c@d.co'); // true ``` 没有 g,test 永远从头匹配,也不会修改 lastIndex,跨调用完全安全。y 标志在单次校验里更是没必要。 需要循环找所有匹配时,用 `matchAll` 或者 `replace` 这类一次性消费的 API,比持有同一个 exec 反复调用更清楚: ```js const text = 'a1 b2 c3'; const re = /([a-z])(\d)/g; for (const m of text.matchAll(re)) { console.log(m[1], m[2]); } ``` matchAll 内部不会污染你传给它的正则的 lastIndex。注意它要求正则带 g,否则抛错。 如果确实需要在循环里用 exec,就把正则放在循环内部新建: ```js function extractNumbers(text) { const re = /\d+/g; const result = []; let m; while ((m = re.exec(text)) !== null) { result.push(m[0]); } return result; } ``` 每次调用新建实例,lastIndex 从 0 开始,自然不会有残留状态。正则字面量在某些引擎里可以复用已编译的模式,实际开销不算大,先保证正确性。 ## 循环 exec 时也别忘了一个细节 即使正则只在函数内新建,while 循环里的 exec 也有个坑:如果匹配到的是空字符串(比如用 `/a*/g` 这类模式),lastIndex 不会前进,循环会变成死循环。 ```js const re = /a*/g; let m; while ((m = re.exec('bbb')) !== null) { // 匹配到空串,lastIndex 一直是 0,永不结束 } ``` 写法要改成匹配空串时手动推进 lastIndex,或者直接换用不带这种模式的方案: ```js const re = /a*/g; let m; while ((m = re.exec('bbb')) !== null) { if (m[0] === '') { re.lastIndex++; } } ``` 提取内容时基本用不上 `/a*/g` 这种能匹配空的模式,但逻辑里拼出来的时候要意识到这一点。 ## 单元测试可以主动兜住这个坑 写测试时,如果被测函数的内部实现用了模块级 g 正则,仅仅测一次调用是抓不到问题的。 ```js import { isNumeric } from './validators'; test('isNumeric 连续调用保持稳定', () => { expect(isNumeric('abc123')).toBe(true); expect(isNumeric('def456')).toBe(true); expect(isNumeric('ghi789')).toBe(true); }); ``` 只要连续断言同一个函数,lastIndex 残留就会被暴露出来。对于共用正则实例的工具模块,建议这类“重复调用”断言作为固定用例。 ## 检索代码时的一个实用技巧 要排查项目里潜在的问题,可以搜索带 g 或 y 标志且用 test 或 exec 的正则字面量。常见特征: ``` /.../g.test( /.../g.exec( new RegExp(...).test( ``` 重点看这些正则定义的位置。如果是在模块顶层或者组件外部,然后被多个字符串反复 test,基本可以判定为隐患。替换方式通常是去掉 g,或者改用在函数内部新建的写法。 这类 bug 不会报错,运行时也不抛异常,只是结果悄悄不对,线上排查成本很高。理解 lastIndex 是正则实例属性而不是字符串相关状态,基本上就绕开了大半。 推荐查看天气:http://www.362tq.com/changning/40tian.html 推荐查看天气:http://www.362tq.com/zhaotong/ 推荐查看天气:http://www.362tq.com/zhaotong/zuotian.html 推荐查看天气:http://www.362tq.com/zhaotong/7tian.html 推荐查看天气:http://www.362tq.com/zhaotong/10tian.html 推荐查看天气:http://www.362tq.com/zhaotong/15tian.html 推荐查看天气:http://www.362tq.com/zhaotong/40tian.html 推荐查看天气:http://www.362tq.com/zhaoyang/ 推荐查看天气:http://www.362tq.com/zhaoyang/zuotian.html 推荐查看天气:http://www.362tq.com/zhaoyang/7tian.html 推荐查看天气:http://www.362tq.com/zhaoyang/10tian.html 推荐查看天气:http://www.362tq.com/zhaoyang/15tian.html 推荐查看天气:http://www.362tq.com/zhaoyang/40tian.html 推荐查看天气:http://www.362tq.com/ludian/ 推荐查看天气:http://www.362tq.com/ludian/zuotian.html 推荐查看天气:http://www.362tq.com/ludian/7tian.html 推荐查看天气:http://www.362tq.com/ludian/10tian.html 推荐查看天气:http://www.362tq.com/ludian/15tian.html 推荐查看天气:http://www.362tq.com/ludian/40tian.html 推荐查看天气:http://www.362tq.com/qiaojia/ 推荐查看天气:http://www.362tq.com/qiaojia/zuotian.html 推荐查看天气:http://www.362tq.com/qiaojia/7tian.html 推荐查看天气:http://www.362tq.com/qiaojia/10tian.html 推荐查看天气:http://www.362tq.com/qiaojia/15tian.html 推荐查看天气:http://www.362tq.com/qiaojia/40tian.html 推荐查看天气:http://www.362tq.com/yanjin/ 推荐查看天气:http://www.362tq.com/yanjin/zuotian.html 推荐查看天气:http://www.362tq.com/yanjin/7tian.html 推荐查看天气:http://www.362tq.com/yanjin/10tian.html 推荐查看天气:http://www.362tq.com/yanjin/15tian.html 推荐查看天气:http://www.362tq.com/yanjin/40tian.html 推荐查看天气:http://www.362tq.com/daguan/ 推荐查看天气:http://www.362tq.com/daguan/zuotian.html 推荐查看天气:http://www.362tq.com/daguan/7tian.html 推荐查看天气:http://www.362tq.com/daguan/10tian.html 推荐查看天气:http://www.362tq.com/daguan/15tian.html 推荐查看天气:http://www.362tq.com/daguan/40tian.html 推荐查看天气:http://www.362tq.com/yongshan/ 推荐查看天气:http://www.362tq.com/yongshan/zuotian.html 推荐查看天气:http://www.362tq.com/yongshan/7tian.html 推荐查看天气:http://www.362tq.com/yongshan/10tian.html 推荐查看天气:http://www.362tq.com/yongshan/15tian.html 推荐查看天气:http://www.362tq.com/yongshan/40tian.html 推荐查看天气:http://www.362tq.com/suijiang/ 推荐查看天气:http://www.362tq.com/suijiang/zuotian.html 推荐查看天气:http://www.362tq.com/suijiang/7tian.html 推荐查看天气:http://www.362tq.com/suijiang/10tian.html 推荐查看天气:http://www.362tq.com/suijiang/15tian.html 推荐查看天气:http://www.362tq.com/suijiang/40tian.html 推荐查看天气:http://www.362tq.com/zhenxiong/ 推荐查看天气:http://www.362tq.com/zhenxiong/zuotian.html 推荐查看天气:http://www.362tq.com/zhenxiong/7tian.html 推荐查看天气:http://www.362tq.com/zhenxiong/10tian.html 推荐查看天气:http://www.362tq.com/zhenxiong/15tian.html 推荐查看天气:http://www.362tq.com/zhenxiong/40tian.html 推荐查看天气:http://www.362tq.com/yiliang/ 推荐查看天气:http://www.362tq.com/yiliang/zuotian.html 推荐查看天气:http://www.362tq.com/yiliang/7tian.html 推荐查看天气:http://www.362tq.com/yiliang/10tian.html 推荐查看天气:http://www.362tq.com/yiliang/15tian.html 推荐查看天气:http://www.362tq.com/yiliang/40tian.html 推荐查看天气:http://www.362tq.com/weixin/ 推荐查看天气:http://www.362tq.com/weixin/zuotian.html 推荐查看天气:http://www.362tq.com/weixin/7tian.html 推荐查看天气:http://www.362tq.com/weixin/10tian.html 推荐查看天气:http://www.362tq.com/weixin/15tian.html 推荐查看天气:http://www.362tq.com/weixin/40tian.html 推荐查看天气:http://www.362tq.com/shuifu/ 推荐查看天气:http://www.362tq.com/shuifu/zuotian.html 推荐查看天气:http://www.362tq.com/shuifu/7tian.html 推荐查看天气:http://www.362tq.com/shuifu/10tian.html 推荐查看天气:http://www.362tq.com/shuifu/15tian.html 推荐查看天气:http://www.362tq.com/shuifu/40tian.html 推荐查看天气:http://www.362tq.com/lijiang/ 推荐查看天气:http://www.362tq.com/lijiang/zuotian.html 推荐查看天气:http://www.362tq.com/lijiang/7tian.html 推荐查看天气:http://www.362tq.com/lijiang/10tian.html 推荐查看天气:http://www.362tq.com/lijiang/15tian.html 推荐查看天气:http://www.362tq.com/lijiang/40tian.html 推荐查看天气:http://www.362tq.com/gucheng/ 推荐查看天气:http://www.362tq.com/gucheng/zuotian.html 推荐查看天气:http://www.362tq.com/gucheng/7tian.html 推荐查看天气:http://www.362tq.com/gucheng/10tian.html 推荐查看天气:http://www.362tq.com/gucheng/15tian.html 推荐查看天气:http://www.362tq.com/gucheng/40tian.html 推荐查看天气:http://www.362tq.com/yulong/ 推荐查看天气:http://www.362tq.com/yulong/zuotian.html 推荐查看天气:http://www.362tq.com/yulong/7tian.html 推荐查看天气:http://www.362tq.com/yulong/10tian.html 推荐查看天气:http://www.362tq.com/yulong/15tian.html 推荐查看天气:http://www.362tq.com/yulong/40tian.html 推荐查看天气:http://www.362tq.com/yongsheng/ 推荐查看天气:http://www.362tq.com/yongsheng/zuotian.html 推荐查看天气:http://www.362tq.com/yongsheng/7tian.html 推荐查看天气:http://www.362tq.com/yongsheng/10tian.html 推荐查看天气:http://www.362tq.com/yongsheng/15tian.html 推荐查看天气:http://www.362tq.com/yongsheng/40tian.html 推荐查看天气:http://www.362tq.com/huaping/ 推荐查看天气:http://www.362tq.com/huaping/zuotian.html 推荐查看天气:http://www.362tq.com/huaping/7tian.html 推荐查看天气:http://www.362tq.com/huaping/10tian.html 推荐查看天气:http://www.362tq.com/huaping/15tian.html 推荐查看天气:http://www.362tq.com/huaping/40tian.html 推荐查看天气:http://www.362tq.com/ninglang/ 推荐查看天气:http://www.362tq.com/ninglang/zuotian.html 推荐查看天气:http://www.362tq.com/ninglang/7tian.html 推荐查看天气:http://www.362tq.com/ninglang/10tian.html 推荐查看天气:http://www.362tq.com/ninglang/15tian.html 推荐查看天气:http://www.362tq.com/ninglang/40tian.html 推荐查看天气:http://www.362tq.com/puer/ 推荐查看天气:http://www.362tq.com/puer/zuotian.html 推荐查看天气:http://www.362tq.com/puer/7tian.html 推荐查看天气:http://www.362tq.com/puer/10tian.html 推荐查看天气:http://www.362tq.com/puer/15tian.html 推荐查看天气:http://www.362tq.com/puer/40tian.html 推荐查看天气:http://www.362tq.com/simao/ 推荐查看天气:http://www.362tq.com/simao/zuotian.html 推荐查看天气:http://www.362tq.com/simao/7tian.html 推荐查看天气:http://www.362tq.com/simao/10tian.html 推荐查看天气:http://www.362tq.com/simao/15tian.html 推荐查看天气:http://www.362tq.com/simao/40tian.html 推荐查看天气:http://www.362tq.com/ninger/ 推荐查看天气:http://www.362tq.com/ninger/zuotian.html 推荐查看天气:http://www.362tq.com/ninger/7tian.html 推荐查看天气:http://www.362tq.com/ninger/10tian.html 推荐查看天气:http://www.362tq.com/ninger/15tian.html 推荐查看天气:http://www.362tq.com/ninger/40tian.html 推荐查看天气:http://www.362tq.com/mojiang/ 推荐查看天气:http://www.362tq.com/mojiang/zuotian.html 推荐查看天气:http://www.362tq.com/mojiang/7tian.html 推荐查看天气:http://www.362tq.com/mojiang/10tian.html 推荐查看天气:http://www.362tq.com/mojiang/15tian.html 推荐查看天气:http://www.362tq.com/mojiang/40tian.html 推荐查看天气:http://www.362tq.com/jingdong/ 推荐查看天气:http://www.362tq.com/jingdong/zuotian.html 推荐查看天气:http://www.362tq.com/jingdong/7tian.html 推荐查看天气:http://www.362tq.com/jingdong/10tian.html 推荐查看天气:http://www.362tq.com/jingdong/15tian.html 推荐查看天气:http://www.362tq.com/jingdong/40tian.html 推荐查看天气:http://www.362tq.com/jinggu/ 推荐查看天气:http://www.362tq.com/jinggu/zuotian.html 推荐查看天气:http://www.362tq.com/jinggu/7tian.html 推荐查看天气:http://www.362tq.com/jinggu/10tian.html 推荐查看天气:http://www.362tq.com/jinggu/15tian.html 推荐查看天气:http://www.362tq.com/jinggu/40tian.html 推荐查看天气:http://www.362tq.com/simaozhenyuan/ 推荐查看天气:http://www.362tq.com/simaozhenyuan/zuotian.html 推荐查看天气:http://www.362tq.com/simaozhenyuan/7tian.html 推荐查看天气:http://www.362tq.com/simaozhenyuan/10tian.html 推荐查看天气:http://www.362tq.com/simaozhenyuan/15tian.html 推荐查看天气:http://www.362tq.com/simaozhenyuan/40tian.html 推荐查看天气:http://www.362tq.com/jiangcheng/ 推荐查看天气:http://www.362tq.com/jiangcheng/zuotian.html 推荐查看天气:http://www.362tq.com/jiangcheng/7tian.html 推荐查看天气:http://www.362tq.com/jiangcheng/10tian.html 推荐查看天气:http://www.362tq.com/jiangcheng/15tian.html 推荐查看天气:http://www.362tq.com/jiangcheng/40tian.html 推荐查看天气:http://www.362tq.com/menglian/ 推荐查看天气:http://www.362tq.com/menglian/zuotian.html 推荐查看天气:http://www.362tq.com/menglian/7tian.html 推荐查看天气:http://www.362tq.com/menglian/10tian.html 推荐查看天气:http://www.362tq.com/menglian/15tian.html 推荐查看天气:http://www.362tq.com/menglian/40tian.html 推荐查看天气:http://www.362tq.com/lancang/ 推荐查看天气:http://www.362tq.com/lancang/zuotian.html 推荐查看天气:http://www.362tq.com/lancang/7tian.html 推荐查看天气:http://www.362tq.com/lancang/10tian.html 推荐查看天气:http://www.362tq.com/lancang/15tian.html 推荐查看天气:http://www.362tq.com/lancang/40tian.html 推荐查看天气:http://www.362tq.com/ximeng/ 推荐查看天气:http://www.362tq.com/ximeng/zuotian.html 推荐查看天气:http://www.362tq.com/ximeng/7tian.html 推荐查看天气:http://www.362tq.com/ximeng/10tian.html 推荐查看天气:http://www.362tq.com/ximeng/15tian.html 推荐查看天气:http://www.362tq.com/ximeng/40tian.html 推荐查看天气:http://www.362tq.com/lincang/ 推荐查看天气:http://www.362tq.com/lincang/zuotian.html 推荐查看天气:http://www.362tq.com/lincang/7tian.html 推荐查看天气:http://www.362tq.com/lincang/10tian.html 推荐查看天气:http://www.362tq.com/lincang/15tian.html 推荐查看天气:http://www.362tq.com/lincang/40tian.html 推荐查看天气:http://www.362tq.com/linxiang/ 推荐查看天气:http://www.362tq.com/linxiang/zuotian.html 推荐查看天气:http://www.362tq.com/linxiang/7tian.html 推荐查看天气:http://www.362tq.com/linxiang/10tian.html 推荐查看天气:http://www.362tq.com/linxiang/15tian.html 推荐查看天气:http://www.362tq.com/linxiang/40tian.html 推荐查看天气:http://www.362tq.com/fengqing/ 推荐查看天气:http://www.362tq.com/fengqing/zuotian.html 推荐查看天气:http://www.362tq.com/fengqing/7tian.html 推荐查看天气:http://www.362tq.com/fengqing/10tian.html 推荐查看天气:http://www.362tq.com/fengqing/15tian.html 推荐查看天气:http://www.362tq.com/fengqing/40tian.html 推荐查看天气:http://www.362tq.com/yunxian/ 推荐查看天气:http://www.362tq.com/yunxian/zuotian.html 推荐查看天气:http://www.362tq.com/yunxian/7tian.html 推荐查看天气:http://www.362tq.com/yunxian/10tian.html 推荐查看天气:http://www.362tq.com/yunxian/15tian.html 推荐查看天气:http://www.362tq.com/yunxian/40tian.html 推荐查看天气:http://www.362tq.com/yongde/

优秀的个人博客,低调大师

JavaScript 中 structuredClone 的实战坑:为什么你的深拷贝在 class 实例上翻车了?

在维护一个后台管理系统时,我需要把一份包含日期、Map 和自定义 class 实例的配置对象深拷贝到草稿状态里。以前一直用 JSON.parse(JSON.stringify(obj)),但它会把 Date 变成字符串、丢掉 undefined,更麻烦的是遇到 class 实例直接变成普通对象,方法全没了。后来我换成了浏览器原生的 structuredClone,大部分场景确实好使,但有一次拷贝一个带 getter 的 class 实例时,拷贝出来的对象属性直接变成 undefined,排查了半天才发现 structuredClone 虽然能处理循环引用和内置类型,但它不会保留原型链。这篇文章从实际代码出发,讲清楚 structuredClone 和 JSON 深拷贝的边界在哪、哪些数据结构它会静默丢信息、以及怎么用 MessageChannel 或手写递归补上原型和 getter 的坑,顺便对比 lodash.cloneDeep 在 class 场景下的表现。 ```js class User { constructor(name) { this.name = name; this.createdAt = new Date(); } get displayName() { return `用户:${this.name}`; } greet() { return `hello ${this.name}`; } } const u = new User('alice'); const cloned = structuredClone(u); console.log(cloned instanceof User); // false console.log(cloned.displayName); // undefined console.log(typeof cloned.greet); // 'undefined' console.log(cloned.name); // 'alice' ``` 上面这段代码说明问题:structuredClone 会老老实实复制自有属性,但原型上的 getter 和方法完全不会被带过去。它内部用的是结构化克隆算法,只搬运“数据”,不搬运行为。如果你把 class 实例当作纯数据容器,那没问题;一旦代码依赖原型方法,拷贝结果就是残缺的。 再看它和 JSON 方案的差异。structuredClone 能正确处理 Date、RegExp、Map、Set、ArrayBuffer、循环引用,这些是 JSON 方案做不到的。 ```js const source = { date: new Date(), map: new Map([['a', 1]]), set: new Set([1, 2]), regex: /abc/g, buffer: new ArrayBuffer(8), }; source.self = source; // 循环引用 const cloned = structuredClone(source); console.log(cloned.date instanceof Date); // true console.log(cloned.map.get('a')); // 1 console.log(cloned.self === cloned); // true ``` JSON 方案遇到循环引用直接抛 TypeError,遇到 Map 会变成空对象 `{}`,遇到 Date 变成 ISO 字符串。所以只要数据结构里出现这些类型,structuredClone 是首选。但它也有明确不支持的:函数、DOM 节点、Error 的某些子类、以及带原型的对象。函数会直接抛 DataCloneError。 ```js structuredClone({ fn: () => {} }); // Uncaught DOMException: Failed to execute 'structuredClone' on 'Window': () => {} could not be cloned. ``` 实际项目里更隐蔽的坑是 getter。structuredClone 在复制时会对源对象做一次“读属性”操作,如果 getter 内部有副作用或者依赖 this 上的其他状态,读出来的值可能和你预期不同。而且拷贝结果里 getter 会退化成普通数据属性,后续不再动态计算。 ```js class Config { constructor() { this._base = 10; } get total() { return this._base * 2; } } const c = new Config(); const copy = structuredClone(c); console.log(copy.total); // 20,但已经是普通属性 copy._base = 100; console.log(copy.total); // 仍然是 20,不再联动 ``` 如果业务确实需要保留原型和 getter,可以借助 MessageChannel 做一次结构化克隆,再手动把原型接回去。但更直接的办法是给 class 写一个 `clone()` 方法,显式重建实例。 ```js class User { constructor(name, createdAt = new Date()) { this.name = name; this.createdAt = createdAt; } clone() { return new User(this.name, new Date(this.createdAt)); } } const u = new User('bob'); const copy = u.clone(); console.log(copy instanceof User); // true console.log(copy.greet()); // hello bob ``` 对于嵌套层级深、class 类型多的配置对象,lodash 的 `cloneDeep` 会保留原型,但它对 Map、Set 的支持要看版本,早期版本直接当普通对象处理。在 lodash 4.17 之后 `cloneDeep` 能正确复制 Map 和 Set,但循环引用和 Symbol 键仍然有边界情况。所以选型时先看清数据结构:纯 JSON 数据用 JSON 方案最快;含 Date/Map/Set/循环引用用 structuredClone;需要保留原型和方法就手写 clone 或组合使用。 还有一个容易忽略的点:structuredClone 是同步的,如果拷贝的对象特别大,比如几十兆的 ArrayBuffer,主线程会卡住。这种场景可以放到 Web Worker 里做,或者用 `postMessage` 天然走结构化克隆,顺便把计算移出主线程。 ```js // worker.js self.onmessage = (e) => { const cloned = structuredClone(e.data); self.postMessage(cloned); }; ``` 浏览器兼容性方面,Chrome 98+、Firefox 94+、Safari 15.4+ 都支持 structuredClone。Node.js 从 17 开始提供全局 structuredClone,之前版本需要 `require('node:v8').serialize` 做替代,但 v8 序列化不支持循环引用以外的某些类型。如果项目还要兼容旧环境,建议封装一层降级逻辑。 ```js function deepClone(value) { if (typeof structuredClone === 'function') { return structuredClone(value); } return JSON.parse(JSON.stringify(value)); } ``` 这个降级只适合纯数据场景,别指望它处理 Date 和 Map。真正稳妥的做法还是根据数据类型走不同分支,而不是无脑深拷贝。很多情况下,避免深拷贝、改用不可变更新反而更省心。 推荐查看天气:https://www.40tianqi.com/xichuan/ 推荐查看天气:https://www.40tianqi.com/xichuan/zuotian.html 推荐查看天气:https://www.40tianqi.com/xichuan/7tian.html 推荐查看天气:https://www.40tianqi.com/xichuan/10tian.html 推荐查看天气:https://www.40tianqi.com/xichuan/15tian.html 推荐查看天气:https://www.40tianqi.com/xichuan/40tian.html 推荐查看天气:https://www.40tianqi.com/sheqi/ 推荐查看天气:https://www.40tianqi.com/sheqi/zuotian.html 推荐查看天气:https://www.40tianqi.com/sheqi/7tian.html 推荐查看天气:https://www.40tianqi.com/sheqi/10tian.html 推荐查看天气:https://www.40tianqi.com/sheqi/15tian.html 推荐查看天气:https://www.40tianqi.com/sheqi/40tian.html 推荐查看天气:https://www.40tianqi.com/tanghe/ 推荐查看天气:https://www.40tianqi.com/tanghe/zuotian.html 推荐查看天气:https://www.40tianqi.com/tanghe/7tian.html 推荐查看天气:https://www.40tianqi.com/tanghe/10tian.html 推荐查看天气:https://www.40tianqi.com/tanghe/15tian.html 推荐查看天气:https://www.40tianqi.com/tanghe/40tian.html 推荐查看天气:https://www.40tianqi.com/xinye/ 推荐查看天气:https://www.40tianqi.com/xinye/zuotian.html 推荐查看天气:https://www.40tianqi.com/xinye/7tian.html 推荐查看天气:https://www.40tianqi.com/xinye/10tian.html 推荐查看天气:https://www.40tianqi.com/xinye/15tian.html 推荐查看天气:https://www.40tianqi.com/xinye/40tian.html 推荐查看天气:https://www.40tianqi.com/tongbai/ 推荐查看天气:https://www.40tianqi.com/tongbai/zuotian.html 推荐查看天气:https://www.40tianqi.com/tongbai/7tian.html 推荐查看天气:https://www.40tianqi.com/tongbai/10tian.html 推荐查看天气:https://www.40tianqi.com/tongbai/15tian.html 推荐查看天气:https://www.40tianqi.com/tongbai/40tian.html 推荐查看天气:https://www.40tianqi.com/dengzhou/ 推荐查看天气:https://www.40tianqi.com/dengzhou/zuotian.html 推荐查看天气:https://www.40tianqi.com/dengzhou/7tian.html 推荐查看天气:https://www.40tianqi.com/dengzhou/10tian.html 推荐查看天气:https://www.40tianqi.com/dengzhou/15tian.html 推荐查看天气:https://www.40tianqi.com/dengzhou/40tian.html 推荐查看天气:https://www.40tianqi.com/shangqiu/ 推荐查看天气:https://www.40tianqi.com/shangqiu/zuotian.html 推荐查看天气:https://www.40tianqi.com/shangqiu/7tian.html 推荐查看天气:https://www.40tianqi.com/shangqiu/10tian.html 推荐查看天气:https://www.40tianqi.com/shangqiu/15tian.html 推荐查看天气:https://www.40tianqi.com/shangqiu/40tian.html 推荐查看天气:https://www.40tianqi.com/liangyuan/ 推荐查看天气:https://www.40tianqi.com/liangyuan/zuotian.html 推荐查看天气:https://www.40tianqi.com/liangyuan/7tian.html 推荐查看天气:https://www.40tianqi.com/liangyuan/10tian.html 推荐查看天气:https://www.40tianqi.com/liangyuan/15tian.html 推荐查看天气:https://www.40tianqi.com/liangyuan/40tian.html 推荐查看天气:https://www.40tianqi.com/minquan/ 推荐查看天气:https://www.40tianqi.com/minquan/zuotian.html 推荐查看天气:https://www.40tianqi.com/minquan/7tian.html 推荐查看天气:https://www.40tianqi.com/minquan/10tian.html 推荐查看天气:https://www.40tianqi.com/minquan/15tian.html 推荐查看天气:https://www.40tianqi.com/minquan/40tian.html 推荐查看天气:https://www.40tianqi.com/suixian/ 推荐查看天气:https://www.40tianqi.com/suixian/zuotian.html 推荐查看天气:https://www.40tianqi.com/suixian/7tian.html 推荐查看天气:https://www.40tianqi.com/suixian/10tian.html 推荐查看天气:https://www.40tianqi.com/suixian/15tian.html 推荐查看天气:https://www.40tianqi.com/suixian/40tian.html 推荐查看天气:https://www.40tianqi.com/ningling/ 推荐查看天气:https://www.40tianqi.com/ningling/zuotian.html 推荐查看天气:https://www.40tianqi.com/ningling/7tian.html 推荐查看天气:https://www.40tianqi.com/ningling/10tian.html 推荐查看天气:https://www.40tianqi.com/ningling/15tian.html 推荐查看天气:https://www.40tianqi.com/ningling/40tian.html 推荐查看天气:https://www.40tianqi.com/zhecheng/ 推荐查看天气:https://www.40tianqi.com/zhecheng/zuotian.html 推荐查看天气:https://www.40tianqi.com/zhecheng/7tian.html 推荐查看天气:https://www.40tianqi.com/zhecheng/10tian.html 推荐查看天气:https://www.40tianqi.com/zhecheng/15tian.html 推荐查看天气:https://www.40tianqi.com/zhecheng/40tian.html 推荐查看天气:https://www.40tianqi.com/shangqiuyucheng/ 推荐查看天气:https://www.40tianqi.com/shangqiuyucheng/zuotian.html 推荐查看天气:https://www.40tianqi.com/shangqiuyucheng/7tian.html 推荐查看天气:https://www.40tianqi.com/shangqiuyucheng/10tian.html 推荐查看天气:https://www.40tianqi.com/shangqiuyucheng/15tian.html 推荐查看天气:https://www.40tianqi.com/shangqiuyucheng/40tian.html 推荐查看天气:https://www.40tianqi.com/xiayi/ 推荐查看天气:https://www.40tianqi.com/xiayi/zuotian.html 推荐查看天气:https://www.40tianqi.com/xiayi/7tian.html 推荐查看天气:https://www.40tianqi.com/xiayi/10tian.html 推荐查看天气:https://www.40tianqi.com/xiayi/15tian.html 推荐查看天气:https://www.40tianqi.com/xiayi/40tian.html 推荐查看天气:https://www.40tianqi.com/xinyang/ 推荐查看天气:https://www.40tianqi.com/xinyang/zuotian.html 推荐查看天气:https://www.40tianqi.com/xinyang/7tian.html 推荐查看天气:https://www.40tianqi.com/xinyang/10tian.html 推荐查看天气:https://www.40tianqi.com/xinyang/15tian.html 推荐查看天气:https://www.40tianqi.com/xinyang/40tian.html 推荐查看天气:https://www.40tianqi.com/shihe/ 推荐查看天气:https://www.40tianqi.com/shihe/zuotian.html 推荐查看天气:https://www.40tianqi.com/shihe/7tian.html 推荐查看天气:https://www.40tianqi.com/shihe/10tian.html 推荐查看天气:https://www.40tianqi.com/shihe/15tian.html 推荐查看天气:https://www.40tianqi.com/shihe/40tian.html 推荐查看天气:https://www.40tianqi.com/pingqiao/ 推荐查看天气:https://www.40tianqi.com/pingqiao/zuotian.html 推荐查看天气:https://www.40tianqi.com/pingqiao/7tian.html 推荐查看天气:https://www.40tianqi.com/pingqiao/10tian.html 推荐查看天气:https://www.40tianqi.com/pingqiao/15tian.html 推荐查看天气:https://www.40tianqi.com/pingqiao/40tian.html 推荐查看天气:https://www.40tianqi.com/luoshan/ 推荐查看天气:https://www.40tianqi.com/luoshan/zuotian.html 推荐查看天气:https://www.40tianqi.com/luoshan/7tian.html 推荐查看天气:https://www.40tianqi.com/luoshan/10tian.html 推荐查看天气:https://www.40tianqi.com/luoshan/15tian.html 推荐查看天气:https://www.40tianqi.com/luoshan/40tian.html 推荐查看天气:https://www.40tianqi.com/guangshan/ 推荐查看天气:https://www.40tianqi.com/guangshan/zuotian.html 推荐查看天气:https://www.40tianqi.com/guangshan/7tian.html 推荐查看天气:https://www.40tianqi.com/guangshan/10tian.html 推荐查看天气:https://www.40tianqi.com/guangshan/15tian.html 推荐查看天气:https://www.40tianqi.com/guangshan/40tian.html 推荐查看天气:https://www.40tianqi.com/xinyangxinxian/ 推荐查看天气:https://www.40tianqi.com/xinyangxinxian/zuotian.html 推荐查看天气:https://www.40tianqi.com/xinyangxinxian/7tian.html 推荐查看天气:https://www.40tianqi.com/xinyangxinxian/10tian.html 推荐查看天气:https://www.40tianqi.com/xinyangxinxian/15tian.html 推荐查看天气:https://www.40tianqi.com/xinyangxinxian/40tian.html 推荐查看天气:https://www.40tianqi.com/shangcheng/ 推荐查看天气:https://www.40tianqi.com/shangcheng/zuotian.html 推荐查看天气:https://www.40tianqi.com/shangcheng/7tian.html 推荐查看天气:https://www.40tianqi.com/shangcheng/10tian.html 推荐查看天气:https://www.40tianqi.com/shangcheng/15tian.html 推荐查看天气:https://www.40tianqi.com/shangcheng/40tian.html 推荐查看天气:https://www.40tianqi.com/gushi/ 推荐查看天气:https://www.40tianqi.com/gushi/zuotian.html 推荐查看天气:https://www.40tianqi.com/gushi/7tian.html 推荐查看天气:https://www.40tianqi.com/gushi/10tian.html 推荐查看天气:https://www.40tianqi.com/gushi/15tian.html 推荐查看天气:https://www.40tianqi.com/gushi/40tian.html 推荐查看天气:https://www.40tianqi.com/huangchuan/ 推荐查看天气:https://www.40tianqi.com/huangchuan/zuotian.html 推荐查看天气:https://www.40tianqi.com/huangchuan/7tian.html 推荐查看天气:https://www.40tianqi.com/huangchuan/10tian.html 推荐查看天气:https://www.40tianqi.com/huangchuan/15tian.html 推荐查看天气:https://www.40tianqi.com/huangchuan/40tian.html 推荐查看天气:https://www.40tianqi.com/huaibin/ 推荐查看天气:https://www.40tianqi.com/huaibin/zuotian.html 推荐查看天气:https://www.40tianqi.com/huaibin/7tian.html 推荐查看天气:https://www.40tianqi.com/huaibin/10tian.html 推荐查看天气:https://www.40tianqi.com/huaibin/15tian.html 推荐查看天气:https://www.40tianqi.com/huaibin/40tian.html 推荐查看天气:https://www.40tianqi.com/xixian/ 推荐查看天气:https://www.40tianqi.com/xixian/zuotian.html 推荐查看天气:https://www.40tianqi.com/xixian/7tian.html 推荐查看天气:https://www.40tianqi.com/xixian/10tian.html 推荐查看天气:https://www.40tianqi.com/xixian/15tian.html 推荐查看天气:https://www.40tianqi.com/xixian/40tian.html 推荐查看天气:https://www.40tianqi.com/zhoukou/ 推荐查看天气:https://www.40tianqi.com/zhoukou/zuotian.html 推荐查看天气:https://www.40tianqi.com/zhoukou/7tian.html 推荐查看天气:https://www.40tianqi.com/zhoukou/10tian.html 推荐查看天气:https://www.40tianqi.com/zhoukou/15tian.html 推荐查看天气:https://www.40tianqi.com/zhoukou/40tian.html 推荐查看天气:https://www.40tianqi.com/chuanhui/ 推荐查看天气:https://www.40tianqi.com/chuanhui/zuotian.html 推荐查看天气:https://www.40tianqi.com/chuanhui/7tian.html 推荐查看天气:https://www.40tianqi.com/chuanhui/10tian.html 推荐查看天气:https://www.40tianqi.com/chuanhui/15tian.html 推荐查看天气:https://www.40tianqi.com/chuanhui/40tian.html 推荐查看天气:https://www.40tianqi.com/fugou/ 推荐查看天气:https://www.40tianqi.com/fugou/zuotian.html 推荐查看天气:https://www.40tianqi.com/fugou/7tian.html 推荐查看天气:https://www.40tianqi.com/fugou/10tian.html 推荐查看天气:https://www.40tianqi.com/fugou/15tian.html 推荐查看天气:https://www.40tianqi.com/fugou/40tian.html 推荐查看天气:https://www.40tianqi.com/xihua/ 推荐查看天气:https://www.40tianqi.com/xihua/zuotian.html 推荐查看天气:https://www.40tianqi.com/xihua/7tian.html 推荐查看天气:https://www.40tianqi.com/xihua/10tian.html 推荐查看天气:https://www.40tianqi.com/xihua/15tian.html 推荐查看天气:https://www.40tianqi.com/xihua/40tian.html 推荐查看天气:https://www.40tianqi.com/shangshui/ 推荐查看天气:https://www.40tianqi.com/shangshui/zuotian.html 推荐查看天气:https://www.40tianqi.com/shangshui/7tian.html 推荐查看天气:https://www.40tianqi.com/shangshui/10tian.html 推荐查看天气:https://www.40tianqi.com/shangshui/15tian.html 推荐查看天气:https://www.40tianqi.com/shangshui/40tian.html 推荐查看天气:https://www.40tianqi.com/shenqiu/

优秀的个人博客,低调大师

JavaScript 里 Promise 链中 then 回调抛错后,后面的 catch 为什么有时接不住?

写业务代码时经常遇到这种状况:一个接口请求失败后,你在 `.then()` 里做了错误处理,但代码还是走到了全局的 `unhandledrejection` 事件,控制台飘红。或者反过来,明明某个 `.then()` 里 `throw new Error()` 了,后面的 `.catch()` 却像没看见一样,最后错误跑到了另一个分支里。这不是 Promise 实现有 bug,而是很多人对 then 链的返回值传递和错误冒泡机制理解得不够完整。Promise 的错误处理依赖链式调用中每个环节的返回状态,一旦在某个 `then` 里把 rejection 消化掉,或者返回了一个新的 Promise 把错误吞了,后续的 catch 就永远接不到。这篇文章会从几个真实的代码片段出发,梳理 then 链里错误是怎么一级一级往下传的,以及为什么有时 catch 写了却没生效。 ## 一个容易踩的坑:在 then 里 return 了新的 Promise 先看一段常见的登录逻辑: ```js function login(username, password) { return fetch('/api/login', { method: 'POST', body: JSON.stringify({ username, password }) }) .then(res => res.json()) .then(data => { if (data.code !== 0) { // 这里只是返回了一个 rejected 的 Promise return Promise.reject(new Error(data.message)) } return data.token }) .catch(err => { console.error('login failed:', err) throw err }) } login('foo', 'bar') .then(token => { // 期望在这里拿到 token }) .catch(err => { // 期望在这里处理登录错误 }) ``` 这段代码本身没有大问题,`login` 内部的 `.catch` 把错误重新抛出去,所以外层的 `.catch` 能接到。但很多人在写的时候会在 `catch` 里返回 `false` 或者一个默认值,比如: ```js .catch(err => { console.error(err) return null }) ``` 这个时候链式调用的状态从 rejected 变成了 fulfilled,外层的 `.then(token => ...)` 会被执行,token 是 `null`,而外层后面的 `.catch` 根本不会触发。如果你没在 `then` 里检查 token 是否为空,后续逻辑就可能把 null 传下去,引发更难排查的问题。 ## 错误在 then 链里的传递规则 理解错误传递,只要记住两件事: 1. `.then(onFulfilled, onRejected)` 里如果 `onFulfilled` 抛出异常,返回的 Promise 会变成 rejected;如果 `onRejected` 抛出异常,同样变成 rejected;如果 `onRejected` 返回一个普通值,Promise 会变成 fulfilled。 2. `.catch(onRejected)` 本质上是 `.then(undefined, onRejected)`,所以它也只能捕获它前面那个 Promise 的 rejection。 看一个对比: ```js Promise.reject(new Error('a')) .then(() => console.log('then')) .catch(err => { console.log('catch1', err.message) // catch1 a // 没有 throw,返回 undefined }) .catch(err => { console.log('catch2', err.message) // 不会执行 }) ``` 第一个 `catch` 返回了 `undefined`,相当于把错误处理掉了,后面就没有 rejection 再往下传。如果希望后面的 catch 还能拿到,就必须在 catch 里重新 throw: ```js Promise.reject(new Error('a')) .catch(err => { console.log('catch1', err.message) throw err }) .catch(err => { console.log('catch2', err.message) // catch2 a }) ``` 这个规则本身不复杂,但在嵌套 Promise 或者多个 then 串联时容易混乱,因为每个 then 都会产生一个新的 Promise。 ## 嵌套 Promise 里的错误不会自动冒泡到外层链 有时候我们会这样写: ```js fetch('/api/user') .then(res => res.json()) .then(user => { // 又发起一个请求,但没有 return fetch(`/api/orders/${user.id}`) .then(res => res.json()) .then(orders => { renderOrders(orders) }) }) .catch(err => { console.error('something wrong', err) }) ``` 如果 `/api/orders` 那个请求失败了,内层的 Promise 会 reject,但因为没有 `return`,外层链根本不知道这个新 Promise 的存在。内层的 rejection 会变成一个未处理的 Promise rejection,触发 `unhandledrejection`,而外层的 `.catch` 完全接不到。 正确的做法是把这个内层 Promise 返回出去,让链式调用保持扁平: ```js fetch('/api/user') .then(res => res.json()) .then(user => { return fetch(`/api/orders/${user.id}`) .then(res => res.json()) }) .then(orders => { renderOrders(orders) }) .catch(err => { console.error('something wrong', err) }) ``` 这样内层的错误才会沿着 Promise 链传到外层 catch。 ## async/await 里同样的逻辑 用 `async/await` 时,错误传递规则没变,只是写法不同。`await` 一个 rejected Promise 会抛出异常,可以用 `try/catch` 捕获: ```js async function loadUser() { try { const res = await fetch('/api/user') const user = await res.json() return user } catch (err) { console.error('load user failed', err) throw err } } ``` 如果在 `catch` 里没有 `throw err`,那么 `loadUser` 返回的 Promise 会变成 fulfilled,调用方用 `.catch` 是接不到的。这和前面 `.then` 链的行为一致。 还有一个容易忽略的点:在 `async` 函数里,如果你在 `await` 之后同步抛出一个错误,和 `await` 一个 rejected Promise 一样,都会让函数返回 rejected Promise。 ```js async function getData() { const res = await fetch('/api/data') if (!res.ok) { throw new Error('request failed') // 函数返回 rejected Promise } return res.json() } getData() .then(data => console.log(data)) .catch(err => console.error(err)) // 能接住 ``` 但如果调用方没有写 catch,并且也没有用 `try/catch` 包住 await,错误就会变成 `unhandledrejection`。 ## 什么时候该重新 throw,什么时候该消化错误 这其实是一个业务决策,不是语法问题。判断标准很简单:当前这个 catch 能不能把错误处理完整? 比如在某个子流程里,请求配置失败,但可以降级到本地缓存,这种错误就应该被消化掉,不要往外抛: ```js async function getConfig() { try { const res = await fetch('/api/config') return await res.json() } catch (err) { console.warn('remote config failed, fallback to local') return localConfig } } ``` 如果当前层只是记录日志,并不具备恢复能力,就应该把错误继续抛出去,让上层决定怎么处理: ```js async function updateProfile(data) { try { await api.put('/profile', data) } catch (err) { logError(err) throw err // 让调用方知道更新失败 } } ``` 最怕的是在中间层既没有恢复,又默默返回了 `undefined`,导致上层拿到一个“成功但没数据”的结果,后续逻辑全乱。 ## 调试 unhandledrejection 的一个实用手段 Chrome DevTools 的 Console 里可以打开 “Pause on caught exceptions” 和 “Pause on uncaught exceptions”,配合 `window.addEventListener('unhandledrejection', event => { ... })` 来定位是哪个 Promise 没有被处理。 ```js window.addEventListener('unhandledrejection', event => { // event.reason 是 rejection 的原因 console.error('Unhandled rejection:', event.reason) // 可以在这里上报监控 }) ``` 在 Node.js 里类似: ```js process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled rejection at:', promise, 'reason:', reason) }) ``` 但要注意,`unhandledrejection` 只在 rejection 发生后没有任何处理程序时才会触发。如果在一个 Promise 之后很晚才加上 catch,浏览器可能已经触发过这个事件了。所以生产环境里更推荐在 Promise 链的末端统一处理,而不是依赖全局事件兜底。 ## 一个实际场景:多个并发请求里某个失败 用 `Promise.all` 时,只要有一个 Promise reject,整体就会 reject,其他 Promise 的结果会被忽略。如果想让每个请求独立处理错误,可以用 `Promise.allSettled`: ```js const results = await Promise.allSettled([ fetch('/api/a').then(res => res.json()), fetch('/api/b').then(res => res.json()), fetch('/api/c').then(res => res.json()) ]) results.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(`request ${index} success:`, result.value) } else { console.error(`request ${index} failed:`, result.reason) } }) ``` 如果坚持用 `Promise.all`,就需要在单个请求的 catch 里决定是重新抛出让整体失败,还是返回一个占位值让整体继续。两种选择没有对错,取决于业务是否允许部分数据缺失。 Promise 的错误处理看起来只是 `.catch` 的位置问题,实际写起来会因为返回值、嵌套、async 函数的隐式转换产生很多分支。遇到 catch 接不住的情况,先检查前一个环节是不是把错误消化了,再检查有没有漏掉 return。多数时候问题就在这两处。 推荐查看天气:https://www.0dtq.com/dongtou/ 推荐查看天气:https://www.0dtq.com/dongtou/zuotian.html 推荐查看天气:https://www.0dtq.com/dongtou/7tian.html 推荐查看天气:https://www.0dtq.com/dongtou/10tian.html 推荐查看天气:https://www.0dtq.com/dongtou/15tian.html 推荐查看天气:https://www.0dtq.com/dongtou/40tian.html 推荐查看天气:https://www.0dtq.com/yongjia/ 推荐查看天气:https://www.0dtq.com/yongjia/zuotian.html 推荐查看天气:https://www.0dtq.com/yongjia/7tian.html 推荐查看天气:https://www.0dtq.com/yongjia/10tian.html 推荐查看天气:https://www.0dtq.com/yongjia/15tian.html 推荐查看天气:https://www.0dtq.com/yongjia/40tian.html 推荐查看天气:https://www.0dtq.com/pingyang/ 推荐查看天气:https://www.0dtq.com/pingyang/zuotian.html 推荐查看天气:https://www.0dtq.com/pingyang/7tian.html 推荐查看天气:https://www.0dtq.com/pingyang/10tian.html 推荐查看天气:https://www.0dtq.com/pingyang/15tian.html 推荐查看天气:https://www.0dtq.com/pingyang/40tian.html 推荐查看天气:https://www.0dtq.com/cangnan/ 推荐查看天气:https://www.0dtq.com/cangnan/zuotian.html 推荐查看天气:https://www.0dtq.com/cangnan/7tian.html 推荐查看天气:https://www.0dtq.com/cangnan/10tian.html 推荐查看天气:https://www.0dtq.com/cangnan/15tian.html 推荐查看天气:https://www.0dtq.com/cangnan/40tian.html 推荐查看天气:https://www.0dtq.com/wencheng/ 推荐查看天气:https://www.0dtq.com/wencheng/zuotian.html 推荐查看天气:https://www.0dtq.com/wencheng/7tian.html 推荐查看天气:https://www.0dtq.com/wencheng/10tian.html 推荐查看天气:https://www.0dtq.com/wencheng/15tian.html 推荐查看天气:https://www.0dtq.com/wencheng/40tian.html 推荐查看天气:https://www.0dtq.com/taishun/ 推荐查看天气:https://www.0dtq.com/taishun/zuotian.html 推荐查看天气:https://www.0dtq.com/taishun/7tian.html 推荐查看天气:https://www.0dtq.com/taishun/10tian.html 推荐查看天气:https://www.0dtq.com/taishun/15tian.html 推荐查看天气:https://www.0dtq.com/taishun/40tian.html 推荐查看天气:https://www.0dtq.com/ruian/ 推荐查看天气:https://www.0dtq.com/ruian/zuotian.html 推荐查看天气:https://www.0dtq.com/ruian/7tian.html 推荐查看天气:https://www.0dtq.com/ruian/10tian.html 推荐查看天气:https://www.0dtq.com/ruian/15tian.html 推荐查看天气:https://www.0dtq.com/ruian/40tian.html 推荐查看天气:https://www.0dtq.com/leqing/ 推荐查看天气:https://www.0dtq.com/leqing/zuotian.html 推荐查看天气:https://www.0dtq.com/leqing/7tian.html 推荐查看天气:https://www.0dtq.com/leqing/10tian.html 推荐查看天气:https://www.0dtq.com/leqing/15tian.html 推荐查看天气:https://www.0dtq.com/leqing/40tian.html 推荐查看天气:https://www.0dtq.com/jiaxing/ 推荐查看天气:https://www.0dtq.com/jiaxing/zuotian.html 推荐查看天气:https://www.0dtq.com/jiaxing/7tian.html 推荐查看天气:https://www.0dtq.com/jiaxing/10tian.html 推荐查看天气:https://www.0dtq.com/jiaxing/15tian.html 推荐查看天气:https://www.0dtq.com/jiaxing/40tian.html 推荐查看天气:https://www.0dtq.com/xiuzhou/ 推荐查看天气:https://www.0dtq.com/xiuzhou/zuotian.html 推荐查看天气:https://www.0dtq.com/xiuzhou/7tian.html 推荐查看天气:https://www.0dtq.com/xiuzhou/10tian.html 推荐查看天气:https://www.0dtq.com/xiuzhou/15tian.html 推荐查看天气:https://www.0dtq.com/xiuzhou/40tian.html 推荐查看天气:https://www.0dtq.com/jiashan/ 推荐查看天气:https://www.0dtq.com/jiashan/zuotian.html 推荐查看天气:https://www.0dtq.com/jiashan/7tian.html 推荐查看天气:https://www.0dtq.com/jiashan/10tian.html 推荐查看天气:https://www.0dtq.com/jiashan/15tian.html 推荐查看天气:https://www.0dtq.com/jiashan/40tian.html 推荐查看天气:https://www.0dtq.com/jiaxinghaiyan/ 推荐查看天气:https://www.0dtq.com/jiaxinghaiyan/zuotian.html 推荐查看天气:https://www.0dtq.com/jiaxinghaiyan/7tian.html 推荐查看天气:https://www.0dtq.com/jiaxinghaiyan/10tian.html 推荐查看天气:https://www.0dtq.com/jiaxinghaiyan/15tian.html 推荐查看天气:https://www.0dtq.com/jiaxinghaiyan/40tian.html 推荐查看天气:https://www.0dtq.com/haining/ 推荐查看天气:https://www.0dtq.com/haining/zuotian.html 推荐查看天气:https://www.0dtq.com/haining/7tian.html 推荐查看天气:https://www.0dtq.com/haining/10tian.html 推荐查看天气:https://www.0dtq.com/haining/15tian.html 推荐查看天气:https://www.0dtq.com/haining/40tian.html 推荐查看天气:https://www.0dtq.com/pinghu/ 推荐查看天气:https://www.0dtq.com/pinghu/zuotian.html 推荐查看天气:https://www.0dtq.com/pinghu/7tian.html 推荐查看天气:https://www.0dtq.com/pinghu/10tian.html 推荐查看天气:https://www.0dtq.com/pinghu/15tian.html 推荐查看天气:https://www.0dtq.com/pinghu/40tian.html 推荐查看天气:https://www.0dtq.com/tongxiang/ 推荐查看天气:https://www.0dtq.com/tongxiang/zuotian.html 推荐查看天气:https://www.0dtq.com/tongxiang/7tian.html 推荐查看天气:https://www.0dtq.com/tongxiang/10tian.html 推荐查看天气:https://www.0dtq.com/tongxiang/15tian.html 推荐查看天气:https://www.0dtq.com/tongxiang/40tian.html 推荐查看天气:https://www.0dtq.com/huzhou/ 推荐查看天气:https://www.0dtq.com/huzhou/zuotian.html 推荐查看天气:https://www.0dtq.com/huzhou/7tian.html 推荐查看天气:https://www.0dtq.com/huzhou/10tian.html 推荐查看天气:https://www.0dtq.com/huzhou/15tian.html 推荐查看天气:https://www.0dtq.com/huzhou/40tian.html 推荐查看天气:https://www.0dtq.com/wuxing/ 推荐查看天气:https://www.0dtq.com/wuxing/zuotian.html 推荐查看天气:https://www.0dtq.com/wuxing/7tian.html 推荐查看天气:https://www.0dtq.com/wuxing/10tian.html 推荐查看天气:https://www.0dtq.com/wuxing/15tian.html 推荐查看天气:https://www.0dtq.com/wuxing/40tian.html 推荐查看天气:https://www.0dtq.com/nanxun/ 推荐查看天气:https://www.0dtq.com/nanxun/zuotian.html 推荐查看天气:https://www.0dtq.com/nanxun/7tian.html 推荐查看天气:https://www.0dtq.com/nanxun/10tian.html 推荐查看天气:https://www.0dtq.com/nanxun/15tian.html 推荐查看天气:https://www.0dtq.com/nanxun/40tian.html 推荐查看天气:https://www.0dtq.com/deqingxian/ 推荐查看天气:https://www.0dtq.com/deqingxian/zuotian.html 推荐查看天气:https://www.0dtq.com/deqingxian/7tian.html 推荐查看天气:https://www.0dtq.com/deqingxian/10tian.html 推荐查看天气:https://www.0dtq.com/deqingxian/15tian.html 推荐查看天气:https://www.0dtq.com/deqingxian/40tian.html 推荐查看天气:https://www.0dtq.com/changxing/ 推荐查看天气:https://www.0dtq.com/changxing/zuotian.html 推荐查看天气:https://www.0dtq.com/changxing/7tian.html 推荐查看天气:https://www.0dtq.com/changxing/10tian.html 推荐查看天气:https://www.0dtq.com/changxing/15tian.html 推荐查看天气:https://www.0dtq.com/changxing/40tian.html 推荐查看天气:https://www.0dtq.com/anji/ 推荐查看天气:https://www.0dtq.com/anji/zuotian.html 推荐查看天气:https://www.0dtq.com/anji/7tian.html 推荐查看天气:https://www.0dtq.com/anji/10tian.html 推荐查看天气:https://www.0dtq.com/anji/15tian.html 推荐查看天气:https://www.0dtq.com/anji/40tian.html 推荐查看天气:https://www.0dtq.com/shaoxing/ 推荐查看天气:https://www.0dtq.com/shaoxing/zuotian.html 推荐查看天气:https://www.0dtq.com/shaoxing/7tian.html 推荐查看天气:https://www.0dtq.com/shaoxing/10tian.html 推荐查看天气:https://www.0dtq.com/shaoxing/15tian.html 推荐查看天气:https://www.0dtq.com/shaoxing/40tian.html 推荐查看天气:https://www.0dtq.com/yuecheng/ 推荐查看天气:https://www.0dtq.com/yuecheng/zuotian.html 推荐查看天气:https://www.0dtq.com/yuecheng/7tian.html 推荐查看天气:https://www.0dtq.com/yuecheng/10tian.html 推荐查看天气:https://www.0dtq.com/yuecheng/15tian.html 推荐查看天气:https://www.0dtq.com/yuecheng/40tian.html 推荐查看天气:https://www.0dtq.com/xinchang/ 推荐查看天气:https://www.0dtq.com/xinchang/zuotian.html 推荐查看天气:https://www.0dtq.com/xinchang/7tian.html 推荐查看天气:https://www.0dtq.com/xinchang/10tian.html 推荐查看天气:https://www.0dtq.com/xinchang/15tian.html 推荐查看天气:https://www.0dtq.com/xinchang/40tian.html 推荐查看天气:https://www.0dtq.com/zhuji/ 推荐查看天气:https://www.0dtq.com/zhuji/zuotian.html 推荐查看天气:https://www.0dtq.com/zhuji/7tian.html 推荐查看天气:https://www.0dtq.com/zhuji/10tian.html 推荐查看天气:https://www.0dtq.com/zhuji/15tian.html 推荐查看天气:https://www.0dtq.com/zhuji/40tian.html 推荐查看天气:https://www.0dtq.com/shangyu/ 推荐查看天气:https://www.0dtq.com/shangyu/zuotian.html 推荐查看天气:https://www.0dtq.com/shangyu/7tian.html 推荐查看天气:https://www.0dtq.com/shangyu/10tian.html 推荐查看天气:https://www.0dtq.com/shangyu/15tian.html 推荐查看天气:https://www.0dtq.com/shangyu/40tian.html 推荐查看天气:https://www.0dtq.com/chengzhou/ 推荐查看天气:https://www.0dtq.com/chengzhou/zuotian.html 推荐查看天气:https://www.0dtq.com/chengzhou/7tian.html 推荐查看天气:https://www.0dtq.com/chengzhou/10tian.html 推荐查看天气:https://www.0dtq.com/chengzhou/15tian.html 推荐查看天气:https://www.0dtq.com/chengzhou/40tian.html 推荐查看天气:https://www.0dtq.com/jinhua/ 推荐查看天气:https://www.0dtq.com/jinhua/zuotian.html 推荐查看天气:https://www.0dtq.com/jinhua/7tian.html 推荐查看天气:https://www.0dtq.com/jinhua/10tian.html 推荐查看天气:https://www.0dtq.com/jinhua/15tian.html 推荐查看天气:https://www.0dtq.com/jinhua/40tian.html 推荐查看天气:https://www.0dtq.com/jindong/ 推荐查看天气:https://www.0dtq.com/jindong/zuotian.html 推荐查看天气:https://www.0dtq.com/jindong/7tian.html 推荐查看天气:https://www.0dtq.com/jindong/10tian.html 推荐查看天气:https://www.0dtq.com/jindong/15tian.html 推荐查看天气:https://www.0dtq.com/jindong/40tian.html 推荐查看天气:https://www.0dtq.com/wuyi/ 推荐查看天气:https://www.0dtq.com/wuyi/zuotian.html 推荐查看天气:https://www.0dtq.com/wuyi/7tian.html 推荐查看天气:https://www.0dtq.com/wuyi/10tian.html 推荐查看天气:https://www.0dtq.com/wuyi/15tian.html 推荐查看天气:https://www.0dtq.com/wuyi/40tian.html 推荐查看天气:https://www.0dtq.com/jinhuapujiang/ 推荐查看天气:https://www.0dtq.com/jinhuapujiang/zuotian.html 推荐查看天气:https://www.0dtq.com/jinhuapujiang/7tian.html 推荐查看天气:https://www.0dtq.com/jinhuapujiang/10tian.html 推荐查看天气:https://www.0dtq.com/jinhuapujiang/15tian.html 推荐查看天气:https://www.0dtq.com/jinhuapujiang/40tian.html 推荐查看天气:https://www.0dtq.com/panan/ 推荐查看天气:https://www.0dtq.com/panan/zuotian.html 推荐查看天气:https://www.0dtq.com/panan/7tian.html 推荐查看天气:https://www.0dtq.com/panan/10tian.html 推荐查看天气:https://www.0dtq.com/panan/15tian.html 推荐查看天气:https://www.0dtq.com/panan/40tian.html 推荐查看天气:https://www.0dtq.com/lanxi/ 推荐查看天气:https://www.0dtq.com/lanxi/zuotian.html 推荐查看天气:https://www.0dtq.com/lanxi/7tian.html 推荐查看天气:https://www.0dtq.com/lanxi/10tian.html 推荐查看天气:https://www.0dtq.com/lanxi/15tian.html 推荐查看天气:https://www.0dtq.com/lanxi/40tian.html 推荐查看天气:https://www.0dtq.com/yiwu/ 推荐查看天气:https://www.0dtq.com/yiwu/zuotian.html

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册