首页 文章 精选 留言 我的

精选列表

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

【JavaScript框架封装】使用Prototype给Array,String,Function对象的方法扩充

版权声明:本文为博主原创文章,未经博主允许不得转载。更多学习资料请访问我爱科技论坛:www.52tech.tech https://blog.csdn.net/m0_37981569/article/details/81055991 /* * @Author: 我爱科技论坛 * @Time: 20180705 * @Desc: 实现一个类似于JQuery功能的框架 * V 1.0: 实现了基础框架、事件框架、CSS框架、属性框架、内容框架、动画框架的搭建 * V 2.0:实现了框架的进一步优化,具有良好的扩展性, 可以支持链式访问 * V 3.0:种子模块:命名空间、对象扩展、数组化、类型的判定、domReady,无冲突处理 * V 4.0: 数据类型的检测、正则表达式的基本用法,数据绑定的知识(模板的使用) * */ /** * 用于给js中内置的对象进行扩充方法 */ (function () { // 为了使得下面定义的扩充函数执行,祥和里需要调用一下 stringExtend() arrayExtend() functionExtend() // String对象方法的扩充 function stringExtend() { // str = 'name: @(name), age:@(age)' // data = {name : 'xiugang', age : 18} /** * 实现一个简单的数据绑定 * @param str * @param data * @return {*} */ String.prototype.formateString = function (data) { return this.replace(/@\((\w+)\)/g, function (match, key) { // 注意这里找到的值必须返回出去(如果是undefined,就是没有数据) // 注意:判断一个值的类型是不是undefined,可以通过typeof判断 console.log(typeof data[key] === 'undefined'); return data[key] === 'undefined' ? '' : data[key]; }); } /** * 去掉坐标的空格 * @param str * @return {*} */ String.prototype.ltrim = function () { return this.replace(/^\s*/g, ''); } /** * 去掉右边的空格 * @param str * @return {*} */ String.prototype.rtrim = function () { return this.replace(/\s*$/g, ''); } /** * 去掉两边的空格 * @param str * @return {*} */ String.prototype.trim = function () { return this.replace(/(^\s*)|(\s*$)/g, ''); } // red===>Red /** * 将第一个字母小写,其他字母大写 * @param str * @return {*} */ String.prototype.camelCase = function () { // .*?是非贪婪的匹配,点可以匹配任意字符,星号是前边的字符有0-n个均匹配,问号是则是0-1; // (^\w{1}): 用于匹配第一个首字母 // (.*):用于匹配任意个的前面的字符 // - param 1: 匹配到的字符串 // - param 2: 匹配的的子字符串 // - param 3: 匹配的子字符串 // - param 4: 匹配到的字符串在字符串中的位置 // - param 5: 原始字符串 return this.replace(/(^\w{1})(.*)/g, function (match, g1, g2) { return g1.toUpperCase() + g2.toLowerCase(); }); } /** * 将一个字符串的下划线转换为中划线 * @param str * @return {*} */ String.prototype.dashString = function () { // 这里面的this实际上指向的就是我们自己定义的一个变量字符串 return this.replace(/\_/g, '-'); } /** * 检测一个字符串是不是为空 * @return {boolean} */ String.prototype.isEmpty = function () { return this.length === 0; } /** * 判断字符串是不是包含一个字符串 * @param target * @return {boolean} */ String.prototype.contains = function (target) { // 只要这个indexOf的下标不是-1的话,就说明包含这个目标字符串,否则的话就是不包含 // indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置,如果没找到的话,就返回-1 return this.indexOf(target) !== -1; } /** * 对一个字符串中的特殊字符进行转义 * @return {string} */ String.prototype.escapeHTML = function () { /*显示结果 描述 实体名称 实体编号 空格 < 小于号 < < > 大于号 > > & 和号 & & " 引号 " " ' 撇号 ' (IE不支持) ' ¢ 分 ¢ ¢ �0�5 镑 £ £ �0�6 日圆 ¥ ¥ € 欧元 &euro * **/ // 先进行字符串分割, 得到一个数组 var strArr = this.split(''); for (var pos = 0, l = strArr.length, tmp; pos < l; pos++) { // 拿到数组中的每一个元素 tmp = strArr[pos]; // 对字符串中的每一个元素进行判断, 如果是特殊字符的话就进行处理 switch (tmp) { // pos始终为1, 表示要替换的项是1项 case '<': replaceArr(strArr, pos, '<'); break; case '>': replaceArr(strArr, pos, '>'); break; case '\'': replaceArr(strArr, pos, '''); break; case '\"': replaceArr(strArr, pos, '"'); break; case '&': replaceArr(strArr, pos, '&'); break; default: ; } } // join() 方法用于把数组中的所有元素放入一个字符串。 return strArr.join(''); // 专门用于替换掉数组中的元素 /** * 替换数组中指定的项 * @param arr * @param pos * @param item * @return {*} */ function replaceArr(arr, pos, item) { // Splice: splice主要用来对JS中的数组进行操作,包括删除,添加,替换等,原来的数组会被改变 // 删除数据:array.splice(index,num),返回值为删除内容,array为结果值。index为起始项,num为删除元素的的个数。 // 插入数据:array.splice(index,0,insertValue),index要插入的位置,insertValue要插入的项 // 替换数据:array.splice(index,num,insertValue),index起始位置,num要被替换的项数,insertValue要替换的值 return arr.splice(pos, 1, item); } } /** * 忽略HTML中的一些内置的特殊字符 * @return {string} */ String.prototype.escapeHTML = function () { return this.replace(/$/g, '&') .replace(/\</g, '<') .replace(/\>/g, '>') .replace(/\'/g, ''') .replace(/\"/g, '"'); } /** * 对字符串进行反转义 * @return {string} */ String.prototype.unescapeHTML = function () { return this.replace(/&/, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/'/g, '\'') .replace(/"/g, '\"') .replace(/&#(\d+)/g, function ($0, $1) { return String.fromCharCode(parseInt($1, 10)); }); } /** * 把一个字符串进行反转操作 * @return {string} */ String.prototype.reverse = function () { // 1. 先获得我需要的字符串,然后进行分割处理 var arr = this.toString().split(''); // 2. 对我分割后得到的数组元素进行逆序处理 arr = arr.reverse(); // 3.把数组中的元素变为一个字符串 return arr.join(); //return (this.toString()).split('').reverse().join(); } } // Array对象方法的扩充 function arrayExtend() { /** * 将一个数组元素清空 * @return {Array} */ Array.prototype.clear = function () { this.length = 0; return this; } /** * 计算一个数组的长度 * @return {*} */ Array.prototype.size = function () { return this.length; } /** * 返回数组里面的第一个元素 * @return {*} */ Array.prototype.first = function () { return this[0]; } /** * 返回数组的最后一个元素 * @return {*} */ Array.prototype.last = function () { return this[this.length - 1] } function cacl(arr, callback) { // 变量的初始化(治理在使用的时候进行初始化) var ret; for (var i = 0, len = arr.length; i < len; i++) { ret = callback(arr[i], ret); } return ret; } /** * 对数组的所有元素进行求和 * @return {*} */ Array.prototype.sum = function () { // 1. 一般的方法 /*var ret = 0; for (var i = 0, len = this.length; i < len; i++){ ret = ret + this[i]; } return ret;*/ // 2.使用上面的计算类 /** * @param:item 数组的每一项 * @param:sum 数组求和的结果 */ return cacl(this, function (item, sum) { // 如果刚开始没有初始化的话,就直接使用第一项作为sum(ret)的初始值 if (typeof sum === 'undefined') { return item; } else { return sum += item; } }) } /** * 找出数组中的最大值 * @return {*} */ Array.prototype.max = function () { // 1. 一般的方式求出最大值 /*var ret = 0; for (var i = 0, len = this.length; i < len; i++){ if (ret < this[i]){ ret = this[i]; } } return ret;*/ // 2. 第二种方式 return cacl(this, function (item, max) { if (typeof max === 'undefined') { return item; } else { if (max < item) { return item; } else { return max; } } }) } /** * 找出一个数组中的最小值 * @return {*} */ Array.prototype.min = function () { return cacl(this, function (item, min) { if (typeof min === 'undefined') { return item; } else { // 只要每一项的值都不比最小值小的话 if (!(min < item)) { return item; } else { return min; } } }) } /** * 求出一个数组中所有元素的平均值 * @return {*} */ Array.prototype.avg = function () { // 1. 先对数组中的元素个数组进行判断一下,防止计算出现无穷的情况 if (this.length === 0) { return; } var sum = this.sum(); return sum / this.length; /*return cacl(this, function (item, avg) { // 1. 先求和(进入到这个函数里面, this指向的是window对象,此时window对象是没有sum方法的,故执行错误) //var sum = this.sum(); // 2.求出平均值 if (typeof avg === 'undefined'){ return item; } else{ avg = sum / (this.length); } return avg; })*/ } // 去除数组中的重复项 /* * 实现思路: 遍历原始数组中的每一项元素,让每次遍历的这一个元素和后面的每一个元素进行比较 * 【只要相同的话就直接跳过继续向下寻找】 * */ Array.prototype.unique = function () { var a = [], len = this.length; for (var i = 0; i < len; i++) { for (var j = i + 1; j < len; j++) { if (this[i] === this[j]) { // 如果找到了相邻的两个元素是相同的,i直接向后移动一位 // 然后j开始从i的位置继续向后寻找元素 j = ++i; } } a.push(this[i]); } ; return a; } /** * 去除数组中的重复项 * 【实现思路】:先对数组进行排序,然后比较相邻的元素是否相同 * @return {Array} */ Array.prototype.unique = function () { var tmp = [], len = this.length; // 1.先对原始的数组进行排序 this.sort(); // 2.比较相邻的元素 for (var i = 0; i < len; i++) { // 只要相邻的元素相同,就直接跳过 if (this[i] === this[i + 1]) { continue; } // 由于tmp.length初始的位置一直是0, 添加一个元素之后变为1,因此下标和长度每次相差1, 实现了实时插入数据的功能 tmp[tmp.length] = this[i]; } return tmp; } /** * 实现两个数组的并集,然后去除重复元素 * @param target * @return {*} */ Array.prototype.union = function (target) { // concat() 方法用于连接两个或多个数组。 // 连接数组之后然后去除数组中的重复项 return this.concat(target).union(); } /** * 求出两个数组的交集 * @param target * @return {Array|*[]} */ Array.prototype.intersect = function (target) { // 1.先去除原始数组和目标数组中的重复元素 var originArr = this.unique(), targetArr = target.unique(); // filter()的作用是返回某一数组中满足条件的元素,该方法返回的是一个新的数组 // 2.开始使用条件过滤 /** * @param element(必选):当前元素的值 @param index(可选): 当前元素的索引 @param array(可选):当前元素所属的数组 */ return originArr.filter(function (element, index, array) { // filter函数默认会把所有的返回false的元素去掉 for (var i = 0, len = targetArr.length; i < len; i++) { if (element === targetArr[i]) { // 只要是返回满足true的所有条件,基本上都会被过滤掉 return true; } //return false; } // 只有找到相同的元素的时候返回的是true,其他情况都是返回的是false return false; }); } /** * 找出两个数组中的不同元素 * @param target * @return {Array|*[]} */ Array.prototype.diff = function (target) { // 1. 获取原始数组和目标数组,去除重复项 var orignArr = this.unique(), targetArr = target.unique(); // 2. 开始使用filter函数过滤条件 return orignArr.filter(function (element, index, array) { for (var i = 0, len = targetArr.length; i < len; i++) { // 只要元素相等的话,就全部过滤掉 if (element === targetArr[i]) { return false; } } return true; }); } /** * 对数组的每一项遍历的时候设置一个回调函数(没有返回结果) * @param fn * @param ctx */ Array.prototype.forEach = function (fn, ctx) { var i = 0, len = this.length; for (; i < len; i++) { // element, index, array // call 的第一个参数也就是this的指向, 其他参数表示需要传递给回调函数的的参数 fn.call(ctx || null, this[i], i, this); } } } /** * * 对数组的每一项执行回调,返回由回调函数的结果组成的数组 * @param fn * @param ctx * @return {Array} */ Array.prototype.map = function (fn, ctx) { // 初始化变量 var ret = [], i = 0, len = this.length; // 遍历数组的每一项元素, 返回由回调函数的结果组成的数组 for (; i < len; i++) { // 调用回调函数, 返回指向结果 res = fn.call(ctx || null, this[i], i, this); // 将每一项执行的结果放入到一个新的数组里面 ret.push(res); } return ret; } /** * 对数组的每一项执行回调函数, 返回回调函数执行结果为true的数组集合 * @param fn * @param ctx */ Array.prototype.filter = function (fn, ctx) { var ret = [], i = 0, len = this.length; // 遍历每一项,把执行结果为true的所有元素集合存起来 for (; i < len; i++) { // 注意这里的这种运算方式只会返回所有的回调函数返回true的计算结果集 fn.call(ctx || null, this[i], i, this) && ret.push(this[i]); } return ret; } /** * 对数组的【每一项】执行回调函数,必须每一项回调函数返回true, 就返回true * @param fn * @param ctx */ Array.prototype.every = function (fn, ctx) { var i = 0, len = this.length; // 遍历数组中所有的元素, 只要有一个函数回调函数为false就返回false,只要所有的都是true才会返回true for (; i < len; i++) { // 如:a默认是undefined,!a是true,!!a则是false,所以b的值是false,而不再是undefined。这样写可以方便后续判断使用。 // 所以,!!(a)的作用是将a强制转换为布尔型(boolean)。 // 如果a = null, !!(a) 的结果就是假, 可以直接把一个弱类型强制转换为一个新的类型 // 下面的代码就是强制将一个函数转换为bool的类型 if(!!fn.call(ctx || null, this[i], i, this) === false) return false; // 上面的代码等价于 /*if (fn.call(ctx || null, this[i], i, this)) { return true; }*/ } return true; } /** * 对数组中的每一项执行回调函数,只要有一项为true的话,就是true,否则就是false * @param fn * @param ctx */ Array.prototype.some = function (fn, ctx) { var i = 0, len = this.length; // 循环遍历每一项,只要有一项为true,就是true for (; i < len; i++) { /* * // 强制转换为Boolean 用 !! var bool = !!"c"; console.log(typeof bool); // boolean // 强制转换为Number 用 + var num = +"1234"; console.log(typeof num); // number // 强制转换为String 用 ""+ var str = ""+ 1234; console.log(typeof str); // string * */ if(!!fn.call(ctx || null, this[i], i, this) === true) return true; } return false; } /** * 从左向右执行回调函数(第二个元素开始) * 其中包含了上一次回调的返回值 * @param callback */ Array.prototype.reduce = function (callback) { var i = 0, len = this.length, callbackRet = this[0]; // 这个变量保存着上一次回到的函数的返回结果, 默认存储的是第一个元素 for (; i < len; i++) { // this的指向,element, index, 数组对象本身 // callbackRet 里面存储了数组上一次计算的处理结果 callbackRet = callback.call(null, callbackRet, this[i], i, this); } return callbackRet; } /** * 从右向左处理每一项元素,倒数第二项开始执行 * @param callback */ Array.prototype.reduceRight = function (callback) { var len = this.length, i = this[len - 2], callbackRet = this[len - 1]; // 保存着最后一项 // 从倒数第二项开始向前遍历数组的每一项 for (; i >= 0; i--) { //this指向, prev, element, index, arr callbackRet = callback.call(null, callbackRet, this[i], i, this); } return callbackRet; } /** * 返回目标值target在数组中第一次出现的位置, 搜索默认会从左向右执行 * @param target * @param start */ Array.prototype.indexOf = function (target, start) { /* * 其实是一种利用符号进行的类型转换,转换成数字类型 ~~true == 1 ~~false == 0 ~~"" == 0 ~~[] == 0 ~~undefined ==0 ~~!undefined == 1 ~~null == 0 ~~!null == 1 * */ var len = this.length, start = ~~start; // 如果start不传过来,这里就是undefined,指向后面的就会保存,这里使用了~~把其他类型强制转换为数字类型 if (start < 0) { // 如果指定搜索的起始位置小于0的话, 默认就从0的位置开始向后搜索 start = 0; } // 从用户指定的起始位置开始向后搜索 for (; start < len; start++) { if (this[start] === target) { return start; } } // 如果没找到的话,就返回-1 return -1; } /** * 返回指定的目标值在数组中最后一次出现的位置 * @param target * @param start */ Array.prototype.lastIndexOf = function (target, start) { // 这里相当于是typeof start ==== 'undefined' if (start === void 0) { start = this.length; } else if (start < 0) { start = 0; } // 开始从数组的最后面向前遍历 for (; start >= 0; start--) { // 找到目标元素target在数组中最后一次出现的位置(从后向前找) if (this[start] === target) { return start; } } return -1; } /** * 数组去重方法加强版本 * 局限性:只适用于数组中存放的是单一的数据类型,如果是多种数据类型并存的话,就会去重失败 * ['ff', 1, '1'] */ Array.prototype.enhanceUnique = function () { var ret = [], tempMap = {}, i = 0, len = this.length, temp; // 遍历数组的每一项 for (; i < len; i++) { temp = this[i]; // 只要这个tempMap中没有这一项的话,就直接放入到数组中去 if (tempMap[temp] === void 0) { ret.push(temp); // {}数据的存储格式为{1 : true, 2 : false, 3 : false} tempMap[temp] = true; } } return ret; } /** * 删除数组中的指定元素, 通过arguments伪数组的方式来接受传递过来的参数 * 经过测试,只能删除数组中重复的多余的元素 * @return {Array} */ Array.prototype.without = function () { // slice(start, end) 方法可从已有的数组中返回选定的元素。 // 如果slice()这个函数没有指定结束的位置的话,默认是会返回数组中的start之后的所有元素 // 1. 获取用户传过来的参数, 去掉数组中重复的元素 //var args = [].slice.call(arguments).unique(); /* * Array.prototype.slice.call({ 0:"likeke", 1:12, 2:true, length:3 }); * */ //1. 由于arguments实际上是一个伪数组,不能直接使用数组里面的方法 // 因此先要把arguments转换为数组 var arr = Array.prototype.slice.call(arguments) || [].slice.call(arguments); // 2. 把数组中的重复元素去重 var args = arr.unique(), len = this.length, aLength = args.length, i = 0, j = 0; // 遍历原始的数组(由于后面每次删除掉一个元素之后,这里的this.length的长度就是已经都改变了, 因此每次在执行完毕之后都要重新计算一下length) for (; i < len; i++) { for (; j < aLength; j++) { if (this[i] === args[j]) { // 只要删除的数组在我的这个里面,就直接去掉 // i 为起始的值,1为要删除的项, 也就是删除i位置的元素 // splice 返回的是删除的元素, this内容是已经修改过之后的项 this.splice(i, 1); // 为了避免删除数组的元素之后的数组长度的变化,这里需要重新计算一下数组的新的长度 // len = this.length; } } // 将j下标复位,以便下一次循环(注意是在每一次j循环完毕之后然后再把j初始化到原始的状态) j = 0; } return this; } /** * 去掉数组中的目标元素 */ Array.prototype.enhanceWithout = function () { // 用于去除数组中指定的的多余的元素 var ret = [], len = this.length, args = ([]).slice.call(arguments), argsLength = args.length, i = 0, j = 0; for (; i < len; i++){ for(; j < argsLength; j++){ if (args[j] !== this[i]){ ret.push(this[i]); } } // 由于这里的j使用的是局部变量,因此这里需要进行处理 j = 0; } return ret; } /** * 实现一个数组的扁平化(可以解决数组里面存放数组的问题)【递归处理调用】 * [[], [], [], [[], [], []]] * @return {Array} */ Array.prototype.flatten = function () { // 实现一个flatten函数,将一个嵌套多层的数组 array(数组) (嵌套可以是任何层数)转换为只有一层的数组 // 数组中元素仅基本类型的元素或数组, var ret = [], len = this.length, // 注意当下一次执行递归调用之后,这里的this指向的是tmp i = 0, tmp; for (; i < len; i++) { // 注意这里先取出来数组中的每一项元素 tmp = this[i]; // 判断一下数组里面存放的还是不是数组类型(数组里面的每一项) if (({}).toString.call(tmp) === '[object Array]' || Object.prototype.toString.call(tmp) === '[object Array]') { // 继续递归调用(递归调用的时候需要把结果存起来哦) // 1. 对当前数组里面的数组进行扁平化处理, tmp.flatten()得到的就是一个普通的数组类型 // 2. 由于ret是一个数组类型,使用concat之后可以把两个数组里面的元素链接起来 // 下一次执行递归的时候上面的this就是指向了这里的tmp数组 ret = ret.concat(tmp.flatten()) //tmp.flatten(); } else { // 如果不是数组类型的话,就直接放入到我的新数组里面 ret.push(tmp); } } return ret; } /** * 删除数组中的指定位置的项 * @param pos * @return {Array} */ Array.prototype.removeAt = function (pos) { // 移出数组中指定位置的项 // slice() 函数调用的执行结果返回的是删除掉的项, 这个this就是修改之后的项 this.splice(pos, 1); return this; } /* 【经验话语1】 直接用等号 (==) 判断时,变量必须要声明(包括不用var 的隐式声明),否则出错。 不管变量有没有声明,都可用typeof 判断,注意typeof 返回结果为字符串,所以是与"undefined"做比较。 所以,判断类型最好用typeof ,因为当判断的变量是在其他js 文件中定义的全局变量时, 执行此判断时,定义该变量所在的js 文件可能还未加载完成,用== 判断就会报错:is not defined 【经验话语2】 注意slice()和splice() 这两者的区别 * */ /** * 检测数组中是不是包含某一项 * @param target * @return {boolean} */ Array.prototype.contains = function (target) { // 可以调用自己之前申明好的some方法,数组中只要有一项,就会返回true return this.some(function (element, index, self) { // 调用this.some()方法实际上会返回遍历数组元素的每一项 return element === target; }) } /** * 随机返回数组中的某一项(把数组中的任意一项返回) * @param n * @return {*} */ Array.prototype.random = function (n) { //Math.floor():向下取整。Math.floor(1.8) -> 1 //Math.ceil():向上取整。Math.ceil(1.1) -> 2 //v = Math.random() * n:会产生一个 0 < v < nv的数 //v2 = Math.floor(Math.random() * n):v2为一个大于等于0,小于n的整数 var index = (Math.floor(Math.random()) * n); return this[index] || this[this.length-1]; } // Function对象方法的扩充 function functionExtend(func) { Function.prototype.before = function () { // 一般来说加下划线的变量为私有变量,这是常规都比较遵守的一种代码规范。 var __self = this; // 私有的属性用下划线 return function () { // 重新把我需要传递的参数传递过去, 如果目标函数返回的是false, 就是false if (func.apply(this, arguments) === false){ return false; } // 否则就把我的自己的参数传递过去 return __self.apply(this, arguments); } } /** * AOP 切面编程的函数扩充 * @param func * @return {Function} */ Function.prototype.after = function (func) { var __self = this; return function () { var ret = __self.apply(this, arguments); // //返回一个函数,相当于一个代理函数,也就是说,这里包含了原函数和新函数,原函数指的是myFunc,新函数指的是fn if (ret === false){ return false; } func.apply(this, arguments); return ret; } } } })();

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

javascript成神之路(4):深入理解this关键字,是的就是this

摘要:如果你真的理解了this是什么,那么你的web几乎所向披靡(文章后面有惊喜,不要错过) 很多程序员会这么认为,this关键字与面向对象程序开发紧密相关,其完全指向由构造器新创建的对象。在ECMAScript规范中也是这样实现的,但正如我们将看到那样,在ECMAScript中,this并不限于只用来指向新创建的对象。还有很多新功能特性 一、定义 this代表当前对象,说明this是在某种特定的情况下才是成立的,它是执行上下文的一个属性。 activeExecutionContext = { AEC: {...}, this: thisVal }; 这里AEC是我们讨论的变量对象。this与上下文中可执行代码的类型有直接关系,this值在进入上下文时确定,并且在上下文运行期间永久不变。 二、全局代码中的this 在全局代码中,this始终是全局对象本身,这样就有可能间接的引用到它了。 // 显示定义全局对象的属性 this.ab = 10; // global.ab = 10 alert(ab); // 10 // 通过赋值给一个无标示符隐式 bb = 20; alert(this.bb); // 20 // 也是通过变量声明隐式声明的 // 因为全局上下文的变量对象是全局对象自身 var cc = 30; alert(this.cc); // 30 三、函数代码中的this 在这时候this值的首要特点是它不是静态的绑定到一个函数。this是进入上下文时确定,在一个函数代码中,这个值在每一次完全不同,但是在代码运行时的this值是不变的,也就是说,因为它不是一个变量,就不可能为其分配一个新值。 var F = {y: 10}; var Bar = { y: 20, test: function () { alert(this === Bar); // true alert(this.y); // 20 this = F; // 错误,任何时候不能改变this的值 alert(this.y); // 如果不出错的话,应该是10,而不是20 } }; // 在进入上下文的时候 // this被当成Bar对象 B.test(); // true, 20 F.test = Bar.test; // 不过,这里this依然不会是foo // 尽管调用的是相同的function F.test(); // false, 10 在我们通常的函数调用中,this是由激活上下文代码的调用者来提供的,即调用函数的父上下文(parent context )。this取决于调用函数的方式,正是调用函数的方式影响了调用的上下文中的this值,没有别的什么可以看到,即使是正常的全局函数也会被调用方式的不同形式激活,这些不同的调用方式导致了不同的this值。例如: function F() { alert(this); } F(); // global alert(F === F.prototype.constructor); // true // 但是同一个function的不同的调用表达式,this是不同的 F.prototype.constructor(); // F.prototype 那么,调用函数的方式如何影响this值?请看下面 四、引用类型 引用类型的值只有两种情况: 1、当我们处理一个标示符时,2、或者一个属性访问器 在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。如果调用括号()的左边是引用类型的值,this将设为引用类型值的base对象(base object),在其他情况下(与引用类型不同的任何其它属性),这个值为null。不过,实际不存在this的值为null的情况,因为当this的值为null的时候,其值会被隐式转换为全局对象,例如: function F() { return this; } F(); // global 我们看到在调用括号的左边是一个引用类型值(因为F是一个标示符) var fooReference = { base: global, propertyName: 'foo' }; 相应地,this也设置为引用类型的base对象。即全局对象 var F = { B: function () { return this; } }; F.B(); // F 我们再次拥有一个引用类型,其base是F对象,在函数B激活时用作this。 var FB = { base: F, propertyName: 'B' }; 五、作为构造器调用的函数中的this 还有一个与this值相关的情况是在函数的上下文中,这是一个构造函数的调用。 unction A() { alert(this); // "a"对象下创建一个新属性 this.x = 10; } var a = new A(); alert(a.x); // 10 new运算符调用“A”函数的内部的[[Construct]] 方法,接着,在对象创建后,调用内部的[[Call]] 方法。 所有相同的函数“A”都将this的值设置为新创建的对象。 六、函数调用中手动设置this 在函数原型中定义的两个方法允许去手动设置函数调用的this值。它们是.apply和.call方法。他们用接受的第一个参数作为this值,this 在调用的作用域中使用。这两个方法的区别很小,对于.apply,第二个参数必须是数组(或者是类似数组的对象,如arguments,反过来,.call能接受任何参数。两个方法必须的参数是第一个——this。 var dog = { say() { console.log(this) } } var cat = {} dog.say.call(cat) 其中this通过call被绑定到了cat上 七、箭头函数 它的this指向与普通函数有很大的不同。箭头函数内部的 this 是词法作用域,由上下文确定。简单说就是箭头函数中的 this 只和定义它时候的作用域的 this 有关,而与在哪里以及如何调用它无关,同时它的 this 指向是不可改变的。 var obj = { x: 10, foo: function() { var fn = () => { return () => { return () => { console.log(this); //Object {x: 10} console.log(this.x); //10 } } } fn()()(); } } obj.foo(); 对于箭头函数还需要注意一点,就是它的this确定后不会改变。使用call、apply、bind也无法改变它的this,因此使用它们传入的第一个参数无效。但是后面添加的参数值还是有效的。 原文发布时间:2018年01月10日 作者:技术金三胖 本文来源:开源中国如需转载请联系原作者

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

一个用JavaScript生成思维导图(mindmap)的github repo

github 地址:https://github.com/dundalek/markmap 作者的readme写得很简单。 今天有同事问作者提供的例子到底怎么跑。这里我就写一个更详细的步骤出来。 首先查看example.parse.js的内容: var fs = require('fs'); var parse = require('../parse.markdown'); var transform = require('../transform.headings'); var text = fs.readFileSync('gtor.md', 'utf-8'); var headings = parse(text); var root = transform(headings); console.log(root); fs.writeFileSync('gtor.json', JSON.stringify(root)); 使用nodejs命名node example.parse.js执行这段代码:代码读取包含思维导图的本地文件gtor.md, 解析并转换生成本地文件gtor.json. 然后查看example.view.js, 发现作者使用了d3来做UI的渲染。 d3.json("gtor.json", function(error, data) { if (error) throw error; markmap('svg#mindmap', data, { preset: 'default', // or colorful linkShape: 'diagonal' // or bracket }); }); 如果直接双击examples文件夹里的index.html文件在浏览器里打开,会出现跨域错误导致本地文件gtor.json无法访问: 必须把这个example部署到服务器上运行才行。 为了简单起见,我做了一个简单的封装。如果您想跑跑例子看看效果,只需要下载我的project到本地:https://github.com/i042416/jerrylist 直接在本地用nodejs 命令行启动服务器: node local.js 然后localhost:3000/mindmap即可看到思维导图的效果 要获取更多Jerry的原创技术文章,请关注公众号"汪子熙"或者扫描下面二维码:

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

基于规则评分的密码强度检测算法分析及实现(JavaScript)

简言 用正则表达式做用户密码强度的通过性判定,过于简单粗暴,不但用户体验差,而且用户帐号安全性也差。那么如何准确评价用户密码的强度,保护用户帐号安全呢?本文分析介绍了几种基于规则评分的密码强度检测算法,并给出了相应的演示程序。大家可以根据自己项目安全性需要,做最适合于自己的方案选择。 1 方案1 (简单) 方案1算法通过密码构成分析,结合权重分派,统计得出密码强度得分。得分越高,表示密码强度越大,也就越安全。方案1算法思想简单,实现容易。 1.1 方案1评分标准 一、密码长度: 5 分: 小于等于4 个字符 10 分: 5 到7 字符 25 分: 大于等于8 个字符 二、字母: 0 分: 没有字母 10 分: 全都是小(大)写字母 20 分: 大小写混合字母 三、数字: 0 分: 没有数字 10 分: 1 个数字 20 分: 大于1 个数字 四、符号: 0 分: 没有符号 10 分: 1 个符号 25 分: 大于1 个符号 五、奖励: 2 分: 字母和数字 3 分: 字母、数字和符号 5 分: 大小写字母、数字和符号 1.2 方案1等级划分 根据密码评分,将密码划分成以下7个等级: >= 90: 非常安全(VERY_SECURE) >= 80: 安全(SECURE) >= 70: 非常强(VERY_STRONG) >= 60: 强(STRONG) >= 50: 一般(AVERAGE) >= 25: 弱(WEAK) >= 0: 非常弱( VERY_WEAK) 该评分标准及等级划分,实际使用时,可小做调整,但不建议做大的变动。 1.3 方案1演示程序 演示程序 1.4 方案1测试分析 // 评分 25,纯小写字母无法通过验证 console.log("aaaaaaaa".score()); // 评分 45,纯数字无法通过验证 console.log("11111111".score()); // 评分 47,小写+数字无法通过验证 console.log("aa111111".score()); // 评分 45,小写+大写无法通过验证 console.log("aaaaAAAA".score()); // 评分 50,4位密码不可能通过验证 console.log("11!!".score()); // 评分 70,5位密码可通过验证 console.log("0aA!!".score()); // 评分 67,小写+大写+数字可通过验证(8位) console.log("aA000000".score()); // 评分 70,数字+符号可通过验证 console.log("000000!!".score()); 从以上测试结果中,我们可以看出算法是十分的有效的,基本能够保证密码具有一定的安全性。但是存在的问题也很明显,其中最主要的问题是对重复或连续的字符评分过高。以测试用例中最后一个为例: 000000!! 可以得到70分,但显然并不是一个非常强壮的密码。 另外,方案1最高可以得到95分,也就是说没有100分(绝对安全)的密码,这一点也是很有智慧的设计。 2 方案2 针对方案1中的不足,方案2中引入了减分机制。对于重复出现,连续出现的字符给予适当的减分,以使得密码评分更准确。同时在方案2中密码的评分基数及计算过程都十分的复杂,要想理解其中每一步的含义,请保持足够的耐心。 2.1 方案2加分项 一、密码长度: 公式 :+(n*4),其中n表示密码长度 二、大写字母: 公式:+((len-n)*2),其中n表示大写字母个数,len表示密码长度 三、小写字母: 公式:+((len-n)*2),其中n表示小写字母个数,len表示密码长度 四、数字: 公式:+(n*4),其中n表示数字个数 条件:满足n < len,才能得到加分,len表示密码长度 五、符号: 公式:+(n*6),其中n表示符号个数 六、位于中间的数字或符号: 公式:+(n*2),其中n表示位于中间的数字或符号个数 七、最低条件得分: 公式:+(n*2),其中n表示满足的最低条件条目数 条件:只有满足最低条件,才能得到加分 其中最低条件的条目如下: 1.密码长度不小于8位 2.包含大写字母 3.包含小写字母 4.包含数字 5.包含符号 最低条件要求满足条目1并至少满足条目2-5中的任意三条。 2.2 方案2减分项 一、只有字母: 公式:-n,其中n表示字母个数 二、只有数字: 公式:-n,其中n表示数字个数 三、重复字符数(大小写敏感): 该项描述复杂,具体计算方法见如下示例程序: var pass = "1111aaDD"; //示意密码 var repChar = 0; var repCharBonus = 0; //得分 var len = pass.length; for(var i = 0; i < len; i++) { var exists = false; for (var j = 0; j < len; j++) { if (pass[i] == pass[j] && i != j) { exists = true; repCharBonus += Math.abs(len/(j-i)); } } if (exists) { repChar++; var unqChar = len - repChar; repCharBonus = (unqChar) ? Math.ceil(repCharBonus/unqChar) : Math.ceil(repCharBonus); } } 四、连续大写字母: 公式:-(n*2),其中n表示连续大写字母出现的次数 举例:如输入AUB,则n=2 五、连续小写字母: 公式:-(n*2),其中n表示连续小写字母出现的次数 举例:如输入aub,则n=2 六、连续数字: 公式:-(n*2),其中n表示连续数字出现的次数 举例:如输入381,则n=2 七、正序或逆序字母: 公式:-(n*3),其中n表示连续发生的次数 正序或逆序是指字母表中的顺序 不区分大小写 条件:只有连续3个字母或以上,才会减分, 例1:如输入ABC,则n=1 例2:如输入dcBA,则n=2 八、正序或逆序数字: 公式:-(n*3),其中n表示连续发生的次数 条件:只有连续3个数字或以上,才会减分 例1:如输入123,则n=1, 例2:如输入4321,则n=2 例3:如输入12,则不会减分 九、正序或逆序符号: 公式:-(n*3),其中n表示连续发生的次数 条件:只有连续3个符号或以上,才会减分 2.3 方案2等级划分 根据密码评分,将密码划分成以下5个等级: >= 80: 非常强(VERY_STRONG) >= 60: 强(STRONG) >= 40: 好(GOOD) >= 20: 弱(WEAK) >= 0: 非常弱( VERY_WEAK) 2.4 方案2演示程序 演示程序 2.5 方案2测试分析 // 评分 0 console.log("11111111".score()); // 评分 2 console.log("aa111111".score()); // 评分 38 console.log("000000!!".score()); // 评分 76 console.log("Asdf2468".score()); // 评分 76 console.log("Mary2468".score()); // 评分 60 console.log("@dmin246".score()); 从以上测试可以看出方案2较方案1有了比较大的改进和提升,尤其是对连续或重复字符上表现出色。但是方案2也存在明显的不足,主要缺点包括对人名(Mary)、单词(Story)、键盘上相连的键(Asdf)、L33T(@dmin)没法识别。 L33T:是指把拉丁字母换成数字或是特殊符号的书写形式。例如把E写成3、A写成@、to写成2、for写成4。 3 方案3 zxcvbn 3.1 简要说明 针对方案2中的不足,引入了方案3,进一步的提长密码强度。方案3完全引入一个第三方检验工具zxcvbn。 zxcvbn是一个受密码破解启发而来的密码强度估算器。它通过模式匹配和保守估计,大概可以识别大约30K左右的常规密码。主要基于美国人口普查数据,维基,美国电影,电视流行词以及其它一些常用模式,像日期,重复字符,序列字符,键盘模式和L33T会话等。 从算法的设计思想上,该方案完全秒杀基于构成的统计分析方法(前两种方法)。同时zxcvbn支持多种开发语言。因其模式的复杂及字典的存在,当前版本的zxcvbn.js大约有800多K。 要了解项目的详情及算法见zxcvbn官网: github zxcvbn 3.2 方案3演示程序 演示程序 以上是三胖对密码强度检测算法和方案的理解和分析,不足之处还请大家多多指正! 原文链接

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

JavaScript 中 Array.prototype.sort 的稳定性与比较函数陷阱

最近在重构一个数据表格组件时,遇到一个看似简单却折腾了半天的 bug:明明按状态和时间排序,结果分页后部分行顺序乱跳。排查后发现根源是 `Array.prototype.sort` 的默认行为与比较函数返回值处理。在 V8 引擎中,数组长度小于等于 10 时使用插入排序,大于 10 时使用快速排序,而快速排序在旧版 V8 中是不稳定的。虽然 ES2019 已经要求 `sort` 必须稳定,但 Node.js 10 及之前、部分旧浏览器仍可能不稳定,并且比较函数返回非数字、返回布尔值、返回浮点数都会导致不可预期的排序结果。这篇文章会从实际代码出发,分析排序不稳定的表现、比较函数的正确写法、数字与字符串排序差异,以及如何用 `Intl.Collator` 处理中文排序。 ## 问题复现:分页后顺序抖动 假设有一个订单列表,按状态(pending、paid、shipped)排序,相同状态内按创建时间倒序。最初代码写得很随意: ```js orders.sort((a, b) => { if (a.status === b.status) { return a.createdAt > b.createdAt; // 返回布尔值,错误 } return a.status > b.status; // 同样返回布尔值 }); ``` 比较函数返回布尔值时,V8 会强制转成 0 或 1,但不会返回负数。这意味着 `a` 小于 `b` 时返回 0,会被当成相等;`a` 大于 `b` 时返回 1,表示 a 排在 b 后面。一旦两个元素比较返回 0,相对顺序就依赖引擎稳定性。在旧版 Node 或某些安卓 WebView 里,快速排序的 partition 过程会把相等元素打乱,导致每次分页请求返回的列表顺序不一致。 修正方式很简单:比较函数必须返回负数、0、正数。 ```js orders.sort((a, b) => { if (a.status !== b.status) { return a.status { const statusDiff = a.status.localeCompare(b.status); return statusDiff !== 0 ? statusDiff : b.createdAt - a.createdAt; }); ``` ## 数字排序的经典陷阱 `[10, 1, 3, 20].sort()` 返回 `[1, 10, 20, 3]`,因为默认把元素转成字符串再按 UTF-16 码元比较。数字排序必须传入比较函数: ```js const nums = [10, 1, 3, 20]; nums.sort((a, b) => a - b); // [1, 3, 10, 20] ``` 注意 `a - b` 可能产生 `NaN`,如果数组里有 `undefined`、`null`、字符串数字混合,结果会不可控。稳妥的做法是先过滤或统一类型: ```js const clean = nums.filter(n => typeof n === 'number' && !Number.isNaN(n)); clean.sort((a, b) => a - b); ``` ## 中文字符串排序不能依赖 localeCompare 默认参数 `localeCompare` 不带参数时使用运行时默认 locale,在 Node.js 和浏览器、不同操作系统上可能不同。中文按拼音排序应该显式指定: ```js const names = ['张三', '李四', '王五', '阿一']; names.sort((a, b) => a.localeCompare(b, 'zh-Hans-CN', { sensitivity: 'base' })); // 阿一, 李四, 王五, 张三 ``` `sensitivity: 'base'` 让大小写和音调不参与比较。如果要做表格列排序,通常还需要处理 `null`、空字符串,把它们固定放在末尾: ```js function compareNullable(a, b, compareFn) { if (a == null && b == null) return 0; if (a == null) return 1; if (b == null) return -1; return compareFn(a, b); } ``` ## 稳定性在业务代码中的实际影响 假设从后端拿到已经按时间倒序的订单,前端只按状态做一次稳定排序,那么相同状态的订单应该保持时间倒序。如果排序不稳定,这个隐含顺序就会丢失。ES2019 之后所有现代浏览器和 Node.js 11+ 都保证 `sort` 稳定,但如果你维护的项目需要兼容旧环境,不要依赖默认稳定性。一个常见的兜底方案是使用索引作为次级比较键: ```js const withIndex = orders.map((item, index) => ({ item, index })); withIndex.sort((a, b) => { const diff = a.item.status.localeCompare(b.item.status); return diff !== 0 ? diff : a.index - b.index; }); const sorted = withIndex.map(entry => entry.item); ``` 这样即使引擎不稳定,相同状态的相对顺序也由原始索引决定,不会乱跳。 ## 性能上的注意点 比较函数会被调用多次,复杂度大约是 O(n log n)。在比较函数里做复杂计算、正则匹配、DOM 查询都会拖慢排序。如果排序键需要多次读取,提前算好缓存起来: ```js const decorated = largeList.map(item => ({ item, sortKey: computeExpensiveKey(item), })); decorated.sort((a, b) => a.sortKey - b.sortKey); const result = decorated.map(d => d.item); ``` 另外,`sort` 会原地修改数组。如果原数组还要用,先 `slice()` 复制一份。React 状态里直接 `sort` 原数组还会引发意外的引用相等问题,导致组件不更新或更新错乱。 ```js // 错误:直接修改 state items.sort(compareFn); setItems(items); // 正确:返回新数组 setItems([...items].sort(compareFn)); ``` 把这些细节处理完,表格排序和分页顺序就稳定了,也不再需要靠刷新页面来碰运气。 推荐查看天气:http://www.362tq.com/danzhou/10tian.html 推荐查看天气:http://www.362tq.com/danzhou/15tian.html 推荐查看天气:http://www.362tq.com/danzhou/40tian.html 推荐查看天气:http://www.362tq.com/wenchang/ 推荐查看天气:http://www.362tq.com/wenchang/zuotian.html 推荐查看天气:http://www.362tq.com/wenchang/7tian.html 推荐查看天气:http://www.362tq.com/wenchang/10tian.html 推荐查看天气:http://www.362tq.com/wenchang/15tian.html 推荐查看天气:http://www.362tq.com/wenchang/40tian.html 推荐查看天气:http://www.362tq.com/wanning/ 推荐查看天气:http://www.362tq.com/wanning/zuotian.html 推荐查看天气:http://www.362tq.com/wanning/7tian.html 推荐查看天气:http://www.362tq.com/wanning/10tian.html 推荐查看天气:http://www.362tq.com/wanning/15tian.html 推荐查看天气:http://www.362tq.com/wanning/40tian.html 推荐查看天气:http://www.362tq.com/dongfang/ 推荐查看天气:http://www.362tq.com/dongfang/zuotian.html 推荐查看天气:http://www.362tq.com/dongfang/7tian.html 推荐查看天气:http://www.362tq.com/dongfang/10tian.html 推荐查看天气:http://www.362tq.com/dongfang/15tian.html 推荐查看天气:http://www.362tq.com/dongfang/40tian.html 推荐查看天气:http://www.362tq.com/dingan/ 推荐查看天气:http://www.362tq.com/dingan/zuotian.html 推荐查看天气:http://www.362tq.com/dingan/7tian.html 推荐查看天气:http://www.362tq.com/dingan/10tian.html 推荐查看天气:http://www.362tq.com/dingan/15tian.html 推荐查看天气:http://www.362tq.com/dingan/40tian.html 推荐查看天气:http://www.362tq.com/tunchang/ 推荐查看天气:http://www.362tq.com/tunchang/zuotian.html 推荐查看天气:http://www.362tq.com/tunchang/7tian.html 推荐查看天气:http://www.362tq.com/tunchang/10tian.html 推荐查看天气:http://www.362tq.com/tunchang/15tian.html 推荐查看天气:http://www.362tq.com/tunchang/40tian.html 推荐查看天气:http://www.362tq.com/chengmai/ 推荐查看天气:http://www.362tq.com/chengmai/zuotian.html 推荐查看天气:http://www.362tq.com/chengmai/7tian.html 推荐查看天气:http://www.362tq.com/chengmai/10tian.html 推荐查看天气:http://www.362tq.com/chengmai/15tian.html 推荐查看天气:http://www.362tq.com/chengmai/40tian.html 推荐查看天气:http://www.362tq.com/lingao/ 推荐查看天气:http://www.362tq.com/lingao/zuotian.html 推荐查看天气:http://www.362tq.com/lingao/7tian.html 推荐查看天气:http://www.362tq.com/lingao/10tian.html 推荐查看天气:http://www.362tq.com/lingao/15tian.html 推荐查看天气:http://www.362tq.com/lingao/40tian.html 推荐查看天气:http://www.362tq.com/baisha/ 推荐查看天气:http://www.362tq.com/baisha/zuotian.html 推荐查看天气:http://www.362tq.com/baisha/7tian.html 推荐查看天气:http://www.362tq.com/baisha/10tian.html 推荐查看天气:http://www.362tq.com/baisha/15tian.html 推荐查看天气:http://www.362tq.com/baisha/40tian.html 推荐查看天气:http://www.362tq.com/changjiang/ 推荐查看天气:http://www.362tq.com/changjiang/zuotian.html 推荐查看天气:http://www.362tq.com/changjiang/7tian.html 推荐查看天气:http://www.362tq.com/changjiang/10tian.html 推荐查看天气:http://www.362tq.com/changjiang/15tian.html 推荐查看天气:http://www.362tq.com/changjiang/40tian.html 推荐查看天气:http://www.362tq.com/ledong/ 推荐查看天气:http://www.362tq.com/ledong/zuotian.html 推荐查看天气:http://www.362tq.com/ledong/7tian.html 推荐查看天气:http://www.362tq.com/ledong/10tian.html 推荐查看天气:http://www.362tq.com/ledong/15tian.html 推荐查看天气:http://www.362tq.com/ledong/40tian.html 推荐查看天气:http://www.362tq.com/lingshui/ 推荐查看天气:http://www.362tq.com/lingshui/zuotian.html 推荐查看天气:http://www.362tq.com/lingshui/7tian.html 推荐查看天气:http://www.362tq.com/lingshui/10tian.html 推荐查看天气:http://www.362tq.com/lingshui/15tian.html 推荐查看天气:http://www.362tq.com/lingshui/40tian.html 推荐查看天气:http://www.362tq.com/baoting/ 推荐查看天气:http://www.362tq.com/baoting/zuotian.html 推荐查看天气:http://www.362tq.com/baoting/7tian.html 推荐查看天气:http://www.362tq.com/baoting/10tian.html 推荐查看天气:http://www.362tq.com/baoting/15tian.html 推荐查看天气:http://www.362tq.com/baoting/40tian.html 推荐查看天气:http://www.362tq.com/qiongzhong/ 推荐查看天气:http://www.362tq.com/qiongzhong/zuotian.html 推荐查看天气:http://www.362tq.com/qiongzhong/7tian.html 推荐查看天气:http://www.362tq.com/qiongzhong/10tian.html 推荐查看天气:http://www.362tq.com/qiongzhong/15tian.html 推荐查看天气:http://www.362tq.com/qiongzhong/40tian.html 推荐查看天气:http://www.362tq.com/xishaqundao/ 推荐查看天气:http://www.362tq.com/xishaqundao/zuotian.html 推荐查看天气:http://www.362tq.com/xishaqundao/7tian.html 推荐查看天气:http://www.362tq.com/xishaqundao/10tian.html 推荐查看天气:http://www.362tq.com/xishaqundao/15tian.html 推荐查看天气:http://www.362tq.com/xishaqundao/40tian.html 推荐查看天气:http://www.362tq.com/nansha/ 推荐查看天气:http://www.362tq.com/nansha/zuotian.html 推荐查看天气:http://www.362tq.com/nansha/7tian.html 推荐查看天气:http://www.362tq.com/nansha/10tian.html 推荐查看天气:http://www.362tq.com/nansha/15tian.html 推荐查看天气:http://www.362tq.com/nansha/40tian.html 推荐查看天气:http://www.362tq.com/zhongshaqundao/ 推荐查看天气:http://www.362tq.com/zhongshaqundao/zuotian.html 推荐查看天气:http://www.362tq.com/zhongshaqundao/7tian.html 推荐查看天气:http://www.362tq.com/zhongshaqundao/10tian.html 推荐查看天气:http://www.362tq.com/zhongshaqundao/15tian.html 推荐查看天气:http://www.362tq.com/zhongshaqundao/40tian.html 推荐查看天气:http://www.362tq.com/xiantao/ 推荐查看天气:http://www.362tq.com/xiantao/zuotian.html 推荐查看天气:http://www.362tq.com/xiantao/7tian.html 推荐查看天气:http://www.362tq.com/xiantao/10tian.html 推荐查看天气:http://www.362tq.com/xiantao/15tian.html 推荐查看天气:http://www.362tq.com/xiantao/40tian.html 推荐查看天气:http://www.362tq.com/qianjiang/ 推荐查看天气:http://www.362tq.com/qianjiang/zuotian.html 推荐查看天气:http://www.362tq.com/qianjiang/7tian.html 推荐查看天气:http://www.362tq.com/qianjiang/10tian.html 推荐查看天气:http://www.362tq.com/qianjiang/15tian.html 推荐查看天气:http://www.362tq.com/qianjiang/40tian.html 推荐查看天气:http://www.362tq.com/tianmen/ 推荐查看天气:http://www.362tq.com/tianmen/zuotian.html 推荐查看天气:http://www.362tq.com/tianmen/7tian.html 推荐查看天气:http://www.362tq.com/tianmen/10tian.html 推荐查看天气:http://www.362tq.com/tianmen/15tian.html 推荐查看天气:http://www.362tq.com/tianmen/40tian.html 推荐查看天气:http://www.362tq.com/shennongjia/ 推荐查看天气:http://www.362tq.com/shennongjia/zuotian.html 推荐查看天气:http://www.362tq.com/shennongjia/7tian.html 推荐查看天气:http://www.362tq.com/shennongjia/10tian.html 推荐查看天气:http://www.362tq.com/shennongjia/15tian.html 推荐查看天气:http://www.362tq.com/shennongjia/40tian.html 推荐查看天气:http://www.362tq.com/yancheng/ 推荐查看天气:http://www.362tq.com/yancheng/zuotian.html 推荐查看天气:http://www.362tq.com/yancheng/7tian.html 推荐查看天气:http://www.362tq.com/yancheng/10tian.html 推荐查看天气:http://www.362tq.com/yancheng/15tian.html 推荐查看天气:http://www.362tq.com/yancheng/40tian.html 推荐查看天气:http://www.362tq.com/xiangshui/ 推荐查看天气:http://www.362tq.com/xiangshui/zuotian.html 推荐查看天气:http://www.362tq.com/xiangshui/7tian.html 推荐查看天气:http://www.362tq.com/xiangshui/10tian.html 推荐查看天气:http://www.362tq.com/xiangshui/15tian.html 推荐查看天气:http://www.362tq.com/xiangshui/40tian.html 推荐查看天气:http://www.362tq.com/binhai/ 推荐查看天气:http://www.362tq.com/binhai/zuotian.html 推荐查看天气:http://www.362tq.com/binhai/7tian.html 推荐查看天气:http://www.362tq.com/binhai/10tian.html 推荐查看天气:http://www.362tq.com/binhai/15tian.html 推荐查看天气:http://www.362tq.com/binhai/40tian.html 推荐查看天气:http://www.362tq.com/funing/ 推荐查看天气:http://www.362tq.com/funing/zuotian.html 推荐查看天气:http://www.362tq.com/funing/7tian.html 推荐查看天气:http://www.362tq.com/funing/10tian.html 推荐查看天气:http://www.362tq.com/funing/15tian.html 推荐查看天气:http://www.362tq.com/funing/40tian.html 推荐查看天气:http://www.362tq.com/sheyang/ 推荐查看天气:http://www.362tq.com/sheyang/zuotian.html 推荐查看天气:http://www.362tq.com/sheyang/7tian.html 推荐查看天气:http://www.362tq.com/sheyang/10tian.html 推荐查看天气:http://www.362tq.com/sheyang/15tian.html 推荐查看天气:http://www.362tq.com/sheyang/40tian.html 推荐查看天气:http://www.362tq.com/jianhu/ 推荐查看天气:http://www.362tq.com/jianhu/zuotian.html 推荐查看天气:http://www.362tq.com/jianhu/7tian.html 推荐查看天气:http://www.362tq.com/jianhu/10tian.html 推荐查看天气:http://www.362tq.com/jianhu/15tian.html 推荐查看天气:http://www.362tq.com/jianhu/40tian.html 推荐查看天气:http://www.362tq.com/dongtai/ 推荐查看天气:http://www.362tq.com/dongtai/zuotian.html 推荐查看天气:http://www.362tq.com/dongtai/7tian.html 推荐查看天气:http://www.362tq.com/dongtai/10tian.html 推荐查看天气:http://www.362tq.com/dongtai/15tian.html 推荐查看天气:http://www.362tq.com/dongtai/40tian.html 推荐查看天气:http://www.362tq.com/dafeng/ 推荐查看天气:http://www.362tq.com/dafeng/zuotian.html 推荐查看天气:http://www.362tq.com/dafeng/7tian.html 推荐查看天气:http://www.362tq.com/dafeng/10tian.html 推荐查看天气:http://www.362tq.com/dafeng/15tian.html 推荐查看天气:http://www.362tq.com/dafeng/40tian.html 推荐查看天气:http://www.362tq.com/jincheng/ 推荐查看天气:http://www.362tq.com/jincheng/zuotian.html 推荐查看天气:http://www.362tq.com/jincheng/7tian.html 推荐查看天气:http://www.362tq.com/jincheng/10tian.html 推荐查看天气:http://www.362tq.com/jincheng/15tian.html 推荐查看天气:http://www.362tq.com/jincheng/40tian.html 推荐查看天气:http://www.362tq.com/qinshui/ 推荐查看天气:http://www.362tq.com/qinshui/zuotian.html 推荐查看天气:http://www.362tq.com/qinshui/7tian.html 推荐查看天气:http://www.362tq.com/qinshui/10tian.html 推荐查看天气:http://www.362tq.com/qinshui/15tian.html 推荐查看天气:http://www.362tq.com/qinshui/40tian.html 推荐查看天气:http://www.362tq.com/yangcheng/ 推荐查看天气:http://www.362tq.com/yangcheng/zuotian.html 推荐查看天气:http://www.362tq.com/yangcheng/7tian.html 推荐查看天气:http://www.362tq.com/yangcheng/10tian.html 推荐查看天气:http://www.362tq.com/yangcheng/15tian.html 推荐查看天气:http://www.362tq.com/yangcheng/40tian.html 推荐查看天气:http://www.362tq.com/lingchuan/ 推荐查看天气:http://www.362tq.com/lingchuan/zuotian.html 推荐查看天气:http://www.362tq.com/lingchuan/7tian.html 推荐查看天气:http://www.362tq.com/lingchuan/10tian.html 推荐查看天气:http://www.362tq.com/lingchuan/15tian.html 推荐查看天气:http://www.362tq.com/lingchuan/40tian.html 推荐查看天气:http://www.362tq.com/zezhou/ 推荐查看天气:http://www.362tq.com/zezhou/zuotian.html 推荐查看天气:http://www.362tq.com/zezhou/7tian.html 推荐查看天气:http://www.362tq.com/zezhou/10tian.html 推荐查看天气:http://www.362tq.com/zezhou/15tian.html

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

JavaScript 中的 AbortController:如何优雅地取消 fetch 请求与清理副作用

在单页应用里,用户快速切换路由、连续触发搜索或者重复提交表单时,发出去的异步请求往往已经不再需要了。浏览器虽然会继续接收响应,但回调里执行的状态更新可能覆盖掉最新的结果,甚至对已经卸载的组件执行 setState,造成内存泄漏或界面闪烁。过去我们用 XMLHttpRequest 的 abort 方法,或者引入 axios 的 CancelToken,现在浏览器原生提供了 AbortController 和 AbortSignal,可以统一取消 fetch、监听事件流以及清理各种异步副作用。这篇文章不堆砌 API 列表,而是从实际开发中常见的搜索框竞态、组件卸载后清理、超时控制这几个场景出发,说明 AbortController 的工作机制、和 fetch 配合时的注意点,以及如何用同一个 signal 管理多个异步操作,最后给出几个容易踩坑的细节。 ## 搜索框竞态:为什么后发请求不一定先到 搜索建议是 AbortController 最典型的用武之地。用户在输入框里连续敲入字母,每次 input 事件都发送一次请求,但服务端处理速度和网络延迟不同,先发出的请求完全可能后返回。如果不做处理,输入框里显示的就是旧关键词的结果。 ```js const input = document.querySelector('#search') let controller = null input.addEventListener('input', async (e) => { const keyword = e.target.value.trim() if (!keyword) return // 取消上一次还没完成的请求 if (controller) controller.abort() controller = new AbortController() try { const res = await fetch(`/api/suggest?q=${encodeURIComponent(keyword)}`, { signal: controller.signal }) const data = await res.json() renderSuggestions(data) } catch (err) { if (err.name === 'AbortError') { // 主动取消,属于预期行为,不提示用户 return } showErrorMessage(err) } }) ``` 这段代码里每次输入都新建一个 controller,abort 之前那个。fetch 收到已经 abort 的 signal 时会立刻 reject 一个 DOMException,name 是 AbortError。这里的关键是不要把所有错误都当成网络异常弹给用户,AbortError 需要单独识别并安静地忽略。 ## 组件卸载时清理,避免更新已销毁的视图 React 或 Vue 组件里,请求发出后组件被卸载是常有的事。如果没有取消,响应回来时执行 setState 或修改 ref,轻则报 warning,重则内存泄漏。把 signal 和组件生命周期绑定起来,卸载时 abort 掉所有挂起的请求。 以 React 为例: ```jsx import { useEffect, useState } from 'react' function UserProfile({ userId }) { const [user, setUser] = useState(null) useEffect(() => { const controller = new AbortController() fetch(`/api/users/${userId}`, { signal: controller.signal }) .then(res => res.json()) .then(setUser) .catch(err => { if (err.name !== 'AbortError') console.error(err) }) return () => controller.abort() }, [userId]) if (!user) return 加载中… return {user.name} } ``` useEffect 的清理函数在 userId 变化或组件卸载时都会执行,controller.abort() 会取消当前请求。这样即使用户快速切换用户 ID,也不会出现旧用户数据覆盖新用户数据的情况。 ## 超时控制:AbortController 和 setTimeout 组合 fetch 本身没有 timeout 选项,用 AbortController 可以自己实现。常见做法是设一个定时器,到时间就 abort。 ```js function fetchWithTimeout(url, options = {}, timeout = 5000) { const controller = new AbortController() const timer = setTimeout(() => controller.abort(), timeout) return fetch(url, { ...options, signal: controller.signal }) .finally(() => clearTimeout(timer)) } fetchWithTimeout('/api/slow', {}, 3000) .then(res => res.json()) .catch(err => { if (err.name === 'AbortError') { console.warn('请求超时或被取消') } }) ``` 需要注意的是,超时引起的 abort 和用户主动取消引起的 abort 在错误里都表现为 AbortError,如果业务上要区分,得自己加标记,比如在 abort 之前往 controller 上挂一个 reason,或者用 AbortSignal.timeout()(较新的 API,目前 Chrome、Firefox、Safari 都已支持)。 ```js fetch('/api/slow', { signal: AbortSignal.timeout(3000) }) ``` AbortSignal.timeout() 返回的 signal 在超时后会自动 abort,并且抛出的错误是 TimeoutError,比手动 setTimeout 更清晰。 ## 同一个 signal 管理多个请求 AbortController 的 signal 可以同时传给多个 fetch,一次 abort 全部取消。在页面级组件里,可以把一个 controller 实例通过 context 或 props 传下去,子组件发请求时共用这个 signal。 ```js const controller = new AbortController() Promise.all([ fetch('/api/a', { signal: controller.signal }), fetch('/api/b', { signal: controller.signal }), fetch('/api/c', { signal: controller.signal }) ]).then(([a, b, c]) => { // 处理结果 }) // 某个时机统一取消 controller.abort() ``` 这样比维护多个取消令牌简单得多。不过要留意,一个请求被 abort 后,Promise.all 会立刻 reject,其他请求虽然也被 abort 了,但它们的 reject 如果不处理,可能在控制台留下未捕获的 Promise 警告。给每个 fetch 单独 catch,或者用 Promise.allSettled 会更稳。 ## 事件监听里的 signal AbortSignal 不仅可以取消 fetch,还能移除事件监听。addEventListener 的第三个参数可以传 `{ signal }`,abort 时监听器自动移除,不用再手动 removeEventListener。 ```js const controller = new AbortController() window.addEventListener('resize', onResize, { signal: controller.signal }) window.addEventListener('scroll', onScroll, { signal: controller.signal }) // 组件卸载或逻辑结束时 controller.abort() ``` 这个特性在封装弹窗、绘图工具或第三方库初始化时很好用,把多个监听器绑定到同一个 signal 上,清理代码只需要一行。 ## 几个容易忽略的细节 fetch 的 signal 只能在请求发出前或响应体读取前生效。如果响应已经返回,正在读取 body,此时 abort 会中断 body 的读取,抛出的错误同样是 AbortError。所以不要以为 res.json() 已经开始了就没法取消。 另外,AbortController 一旦 abort,就永久处于 aborted 状态,不能复用。需要新请求时得创建新的 controller。有些库(比如 axios)内部用 CancelToken,现在也支持传入 signal,但和 fetch 的行为略有差异,混用时注意看文档。 在 Node.js 里,从 v15 开始也支持 AbortController,可以取消 fs 操作、http 请求等。如果写的是同构代码,signal 的处理方式基本一致。 最后,不要在每个略大的异步操作上都无脑加 AbortController,先判断这个操作是否真的可能产生竞态或需要提前终止。过度使用会让代码里到处是 try/catch 和 AbortError 判断,反而降低可读性。只在用户可感知、可重复触发、且结果有状态覆盖风险的地方用它,收益最明显。 推荐查看天气:http://www.362tq.com/qilian/40tian.html 推荐查看天气:http://www.362tq.com/haiyan/ 推荐查看天气:http://www.362tq.com/haiyan/zuotian.html 推荐查看天气:http://www.362tq.com/haiyan/7tian.html 推荐查看天气:http://www.362tq.com/haiyan/10tian.html 推荐查看天气:http://www.362tq.com/haiyan/15tian.html 推荐查看天气:http://www.362tq.com/haiyan/40tian.html 推荐查看天气:http://www.362tq.com/gangcha/ 推荐查看天气:http://www.362tq.com/gangcha/zuotian.html 推荐查看天气:http://www.362tq.com/gangcha/7tian.html 推荐查看天气:http://www.362tq.com/gangcha/10tian.html 推荐查看天气:http://www.362tq.com/gangcha/15tian.html 推荐查看天气:http://www.362tq.com/gangcha/40tian.html 推荐查看天气:http://www.362tq.com/huangnan/ 推荐查看天气:http://www.362tq.com/huangnan/zuotian.html 推荐查看天气:http://www.362tq.com/huangnan/7tian.html 推荐查看天气:http://www.362tq.com/huangnan/10tian.html 推荐查看天气:http://www.362tq.com/huangnan/15tian.html 推荐查看天气:http://www.362tq.com/huangnan/40tian.html 推荐查看天气:http://www.362tq.com/tongrenxian/ 推荐查看天气:http://www.362tq.com/tongrenxian/zuotian.html 推荐查看天气:http://www.362tq.com/tongrenxian/7tian.html 推荐查看天气:http://www.362tq.com/tongrenxian/10tian.html 推荐查看天气:http://www.362tq.com/tongrenxian/15tian.html 推荐查看天气:http://www.362tq.com/tongrenxian/40tian.html 推荐查看天气:http://www.362tq.com/jianzha/ 推荐查看天气:http://www.362tq.com/jianzha/zuotian.html 推荐查看天气:http://www.362tq.com/jianzha/7tian.html 推荐查看天气:http://www.362tq.com/jianzha/10tian.html 推荐查看天气:http://www.362tq.com/jianzha/15tian.html 推荐查看天气:http://www.362tq.com/jianzha/40tian.html 推荐查看天气:http://www.362tq.com/zeku/ 推荐查看天气:http://www.362tq.com/zeku/zuotian.html 推荐查看天气:http://www.362tq.com/zeku/7tian.html 推荐查看天气:http://www.362tq.com/zeku/10tian.html 推荐查看天气:http://www.362tq.com/zeku/15tian.html 推荐查看天气:http://www.362tq.com/zeku/40tian.html 推荐查看天气:http://www.362tq.com/henanxian/ 推荐查看天气:http://www.362tq.com/henanxian/zuotian.html 推荐查看天气:http://www.362tq.com/henanxian/7tian.html 推荐查看天气:http://www.362tq.com/henanxian/10tian.html 推荐查看天气:http://www.362tq.com/henanxian/15tian.html 推荐查看天气:http://www.362tq.com/henanxian/40tian.html 推荐查看天气:http://www.362tq.com/hainanzhou/ 推荐查看天气:http://www.362tq.com/hainanzhou/zuotian.html 推荐查看天气:http://www.362tq.com/hainanzhou/7tian.html 推荐查看天气:http://www.362tq.com/hainanzhou/10tian.html 推荐查看天气:http://www.362tq.com/hainanzhou/15tian.html 推荐查看天气:http://www.362tq.com/hainanzhou/40tian.html 推荐查看天气:http://www.362tq.com/gonghe/ 推荐查看天气:http://www.362tq.com/gonghe/zuotian.html 推荐查看天气:http://www.362tq.com/gonghe/7tian.html 推荐查看天气:http://www.362tq.com/gonghe/10tian.html 推荐查看天气:http://www.362tq.com/gonghe/15tian.html 推荐查看天气:http://www.362tq.com/gonghe/40tian.html 推荐查看天气:http://www.362tq.com/tongde/ 推荐查看天气:http://www.362tq.com/tongde/zuotian.html 推荐查看天气:http://www.362tq.com/tongde/7tian.html 推荐查看天气:http://www.362tq.com/tongde/10tian.html 推荐查看天气:http://www.362tq.com/tongde/15tian.html 推荐查看天气:http://www.362tq.com/tongde/40tian.html 推荐查看天气:http://www.362tq.com/guide/ 推荐查看天气:http://www.362tq.com/guide/zuotian.html 推荐查看天气:http://www.362tq.com/guide/7tian.html 推荐查看天气:http://www.362tq.com/guide/10tian.html 推荐查看天气:http://www.362tq.com/guide/15tian.html 推荐查看天气:http://www.362tq.com/guide/40tian.html 推荐查看天气:http://www.362tq.com/xinghai/ 推荐查看天气:http://www.362tq.com/xinghai/zuotian.html 推荐查看天气:http://www.362tq.com/xinghai/7tian.html 推荐查看天气:http://www.362tq.com/xinghai/10tian.html 推荐查看天气:http://www.362tq.com/xinghai/15tian.html 推荐查看天气:http://www.362tq.com/xinghai/40tian.html 推荐查看天气:http://www.362tq.com/guinan/ 推荐查看天气:http://www.362tq.com/guinan/zuotian.html 推荐查看天气:http://www.362tq.com/guinan/7tian.html 推荐查看天气:http://www.362tq.com/guinan/10tian.html 推荐查看天气:http://www.362tq.com/guinan/15tian.html 推荐查看天气:http://www.362tq.com/guinan/40tian.html 推荐查看天气:http://www.362tq.com/guoluo/ 推荐查看天气:http://www.362tq.com/guoluo/zuotian.html 推荐查看天气:http://www.362tq.com/guoluo/7tian.html 推荐查看天气:http://www.362tq.com/guoluo/10tian.html 推荐查看天气:http://www.362tq.com/guoluo/15tian.html 推荐查看天气:http://www.362tq.com/guoluo/40tian.html 推荐查看天气:http://www.362tq.com/maqin/ 推荐查看天气:http://www.362tq.com/maqin/zuotian.html 推荐查看天气:http://www.362tq.com/maqin/7tian.html 推荐查看天气:http://www.362tq.com/maqin/10tian.html 推荐查看天气:http://www.362tq.com/maqin/15tian.html 推荐查看天气:http://www.362tq.com/maqin/40tian.html 推荐查看天气:http://www.362tq.com/banma/ 推荐查看天气:http://www.362tq.com/banma/zuotian.html 推荐查看天气:http://www.362tq.com/banma/7tian.html 推荐查看天气:http://www.362tq.com/banma/10tian.html 推荐查看天气:http://www.362tq.com/banma/15tian.html 推荐查看天气:http://www.362tq.com/banma/40tian.html 推荐查看天气:http://www.362tq.com/gande/ 推荐查看天气:http://www.362tq.com/gande/zuotian.html 推荐查看天气:http://www.362tq.com/gande/7tian.html 推荐查看天气:http://www.362tq.com/gande/10tian.html 推荐查看天气:http://www.362tq.com/gande/15tian.html 推荐查看天气:http://www.362tq.com/gande/40tian.html 推荐查看天气:http://www.362tq.com/dari/ 推荐查看天气:http://www.362tq.com/dari/zuotian.html 推荐查看天气:http://www.362tq.com/dari/7tian.html 推荐查看天气:http://www.362tq.com/dari/10tian.html 推荐查看天气:http://www.362tq.com/dari/15tian.html 推荐查看天气:http://www.362tq.com/dari/40tian.html 推荐查看天气:http://www.362tq.com/jiuzhi/ 推荐查看天气:http://www.362tq.com/jiuzhi/zuotian.html 推荐查看天气:http://www.362tq.com/jiuzhi/7tian.html 推荐查看天气:http://www.362tq.com/jiuzhi/10tian.html 推荐查看天气:http://www.362tq.com/jiuzhi/15tian.html 推荐查看天气:http://www.362tq.com/jiuzhi/40tian.html 推荐查看天气:http://www.362tq.com/maduo/ 推荐查看天气:http://www.362tq.com/maduo/zuotian.html 推荐查看天气:http://www.362tq.com/maduo/7tian.html 推荐查看天气:http://www.362tq.com/maduo/10tian.html 推荐查看天气:http://www.362tq.com/maduo/15tian.html 推荐查看天气:http://www.362tq.com/maduo/40tian.html 推荐查看天气:http://www.362tq.com/yushu/ 推荐查看天气:http://www.362tq.com/yushu/zuotian.html 推荐查看天气:http://www.362tq.com/yushu/7tian.html 推荐查看天气:http://www.362tq.com/yushu/10tian.html 推荐查看天气:http://www.362tq.com/yushu/15tian.html 推荐查看天气:http://www.362tq.com/yushu/40tian.html 推荐查看天气:http://www.362tq.com/zaduo/ 推荐查看天气:http://www.362tq.com/zaduo/zuotian.html 推荐查看天气:http://www.362tq.com/zaduo/7tian.html 推荐查看天气:http://www.362tq.com/zaduo/10tian.html 推荐查看天气:http://www.362tq.com/zaduo/15tian.html 推荐查看天气:http://www.362tq.com/zaduo/40tian.html 推荐查看天气:http://www.362tq.com/chenduo/ 推荐查看天气:http://www.362tq.com/chenduo/zuotian.html 推荐查看天气:http://www.362tq.com/chenduo/7tian.html 推荐查看天气:http://www.362tq.com/chenduo/10tian.html 推荐查看天气:http://www.362tq.com/chenduo/15tian.html 推荐查看天气:http://www.362tq.com/chenduo/40tian.html 推荐查看天气:http://www.362tq.com/zhiduo/ 推荐查看天气:http://www.362tq.com/zhiduo/zuotian.html 推荐查看天气:http://www.362tq.com/zhiduo/7tian.html 推荐查看天气:http://www.362tq.com/zhiduo/10tian.html 推荐查看天气:http://www.362tq.com/zhiduo/15tian.html 推荐查看天气:http://www.362tq.com/zhiduo/40tian.html 推荐查看天气:http://www.362tq.com/nanqian/ 推荐查看天气:http://www.362tq.com/nanqian/zuotian.html 推荐查看天气:http://www.362tq.com/nanqian/7tian.html 推荐查看天气:http://www.362tq.com/nanqian/10tian.html 推荐查看天气:http://www.362tq.com/nanqian/15tian.html 推荐查看天气:http://www.362tq.com/nanqian/40tian.html 推荐查看天气:http://www.362tq.com/qumalai/ 推荐查看天气:http://www.362tq.com/qumalai/zuotian.html 推荐查看天气:http://www.362tq.com/qumalai/7tian.html 推荐查看天气:http://www.362tq.com/qumalai/10tian.html 推荐查看天气:http://www.362tq.com/qumalai/15tian.html 推荐查看天气:http://www.362tq.com/qumalai/40tian.html 推荐查看天气:http://www.362tq.com/haixi/ 推荐查看天气:http://www.362tq.com/haixi/zuotian.html 推荐查看天气:http://www.362tq.com/haixi/7tian.html 推荐查看天气:http://www.362tq.com/haixi/10tian.html 推荐查看天气:http://www.362tq.com/haixi/15tian.html 推荐查看天气:http://www.362tq.com/haixi/40tian.html 推荐查看天气:http://www.362tq.com/geermu/ 推荐查看天气:http://www.362tq.com/geermu/zuotian.html 推荐查看天气:http://www.362tq.com/geermu/7tian.html 推荐查看天气:http://www.362tq.com/geermu/10tian.html 推荐查看天气:http://www.362tq.com/geermu/15tian.html 推荐查看天气:http://www.362tq.com/geermu/40tian.html 推荐查看天气:http://www.362tq.com/delingha/ 推荐查看天气:http://www.362tq.com/delingha/zuotian.html 推荐查看天气:http://www.362tq.com/delingha/7tian.html 推荐查看天气:http://www.362tq.com/delingha/10tian.html 推荐查看天气:http://www.362tq.com/delingha/15tian.html 推荐查看天气:http://www.362tq.com/delingha/40tian.html 推荐查看天气:http://www.362tq.com/wulan/ 推荐查看天气:http://www.362tq.com/wulan/zuotian.html 推荐查看天气:http://www.362tq.com/wulan/7tian.html 推荐查看天气:http://www.362tq.com/wulan/10tian.html 推荐查看天气:http://www.362tq.com/wulan/15tian.html 推荐查看天气:http://www.362tq.com/wulan/40tian.html 推荐查看天气:http://www.362tq.com/dulan/ 推荐查看天气:http://www.362tq.com/dulan/zuotian.html 推荐查看天气:http://www.362tq.com/dulan/7tian.html 推荐查看天气:http://www.362tq.com/dulan/10tian.html 推荐查看天气:http://www.362tq.com/dulan/15tian.html 推荐查看天气:http://www.362tq.com/dulan/40tian.html 推荐查看天气:http://www.362tq.com/tianjun/ 推荐查看天气:http://www.362tq.com/tianjun/zuotian.html 推荐查看天气:http://www.362tq.com/tianjun/7tian.html 推荐查看天气:http://www.362tq.com/tianjun/10tian.html 推荐查看天气:http://www.362tq.com/tianjun/15tian.html 推荐查看天气:http://www.362tq.com/tianjun/40tian.html 推荐查看天气:http://www.362tq.com/dachaidan/ 推荐查看天气:http://www.362tq.com/dachaidan/zuotian.html 推荐查看天气:http://www.362tq.com/dachaidan/7tian.html 推荐查看天气:http://www.362tq.com/dachaidan/10tian.html 推荐查看天气:http://www.362tq.com/dachaidan/15tian.html 推荐查看天气:http://www.362tq.com/dachaidan/40tian.html 推荐查看天气:http://www.362tq.com/linghu/

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

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

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

WebStorm

WebStorm

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

用户登录
用户注册