• [技术干货] WPS JS宏入门案例集锦-转载
    批量创建工作表/簿 批量创建工作表:  function 批量创建工作表(){     for (var city of ["成都","上海","北京"]){         let sht = Worksheets.Add();         sht.Name = city;     } } function 批量创建工作簿(){     for (var city of ["成都","上海","北京"]){         book = Workbooks.Add();         book.SaveAs(`E:/tmp/${city}`);         book.Close();     } } 若book.SaveAs不传入全路径,则默认可能保存到我的文档文件夹中。 简单的方法就是直接筛选再复制粘贴,但是现在我们需要完全基于js宏的语法实现:  function 分数筛选(){     var Arr1=[];     var Arr2=Range("a2:b13").Value();     for (var row of Arr2){         if (row[1]>=100){             Arr1.push(row);         }     }     Range("d4").Resize(Arr1.length,2).Value2=Arr1 } function 工资条制作() {     Application.DisplayAlerts=false;     let src=Sheets("工资表");     for(let sht of Sheets){         if(sht.Name=="结果") sht.Delete();     }     Application.DisplayAlerts=true;     var sht=Worksheets.Add();     sht.Name="结果";     for(i=0;i<10;i++) {         src.Range("a1:m4").Copy();         sht.Cells.Item(5*i+1,1).PasteSpecial();         src.Range(`A${i+5}:M${i+5}`).Copy();         sht.Cells.Item(5*i+5,1).PasteSpecial();     }     sht.Activate() } 注意:如果原表不止10人,则修改循环次数即可。  如果允许直接在原表上修改,可以使用相对引用录制宏得到代码:  function 工资条制作2() {     Range("a1:m4").Select();     for (var i=1;i<=9;i++){             Selection.Copy();             ActiveCell.Offset(5, 0).Range("A1:M4").Insert(xlShiftDown, undefined);             Application.CutCopyMode = false;             ActiveCell.Offset(5, 0).Range("A1:M4").Select();     } } 任意多列SN号合并到一列 原数据都是如下格式的SN号: 下面我们考虑通过宏代码合并到一列,代码如下:  function 单表多列合并到一列新文件() {     var vs=Range("A1").CurrentRegion.Value().flat();     // 过滤掉空值并去重     vs=Array.from(new Set(vs.filter(v=>v!=undefined)));     // 一维数组默认赋值给一行,赋值给一列需要先转置二维     vs = WorksheetFunction.Transpose(vs);     var wb = Workbooks.Add();     wb.Sheets(1).Range("A1").Resize(vs.length,1).Value2 = vs; } 注意:上面的代码使用JavaScript的语法对数组进行了去重。  Array.from(new Set(arr)) arr.filter(v=>v!=undefined) 批量合并工作表并添加来源表名 function 合并工作表数据(){     var NewArr=[],n=1;     for (var ws of Sheets){         var Arr=ws.Range("a1").CurrentRegion.Value();         if (n++ ==1){var title=Arr[0].concat("工作表名")};         delete Arr[0]         Arr.forEach(ar=>NewArr.push(ar.concat(ws.Name)));     }     NewArr.unshift(title);     var wb=Workbooks.Add();     wb.Sheets(1).Range("a1").Resize(NewArr.length,NewArr[0].length).Value2=NewArr; } 语音朗读 代码如下:  function Workbook_Open() {     Application.Speech.Speak("美好的一天就从这一刻开始吧!",true) }  function Workbook_NewSheet(Sh) {     Application.Speech.Speak("果然狠人老表,区区几张sheet是不够你消遣的!",true) }  function Application_WorkbookBeforeClose(Wb, Cancel) {     Application.Speech.Speak("就想问老板,可以下班了吗?") }  function Application_SheetSelectionChange(Sh, Target){     if(Sh.Name!="词汇"|Target.Value()==undefined) return;     Application.Speech.Speak(Target.Value()); } 保存上面的代码后,当打开或关闭该文件或新建工作表都会朗读对应的文字。  对于词汇这张工作表,点击任何有值的单元格都会对其进行朗读。  Application.Speech.Speak第二个参数传入true表示异步,默认为同步。  富文本弹窗 alert函数支持传入HTML:  function testAlert_CSS(){     let foo = `         <h3 style="color:red">Hi</h3>         <p style="color:green;text-shadow: 1px 1px 2px red, 0 0 1em blue, 0 0 0.2em blue;">             Hi ${new Date().toLocaleDateString()}         </p>`;     alert(foo) } 判断目标是否在指定区域内 function _m_isInArea(uArea,cell){     uArea = typeof(uArea) =="string" ? Range(uArea):uArea;     cell = typeof(cell) =="string" ? Range(cell):cell;     if(uArea.Parent.Name!=cell.Parent.Name) return false;     let s_row=uArea.Row,e_row=s_row+uArea.Rows.Count-1;     let s_col=uArea.Column,e_col=s_col+uArea.Columns.Count-1;     let t_row = cell.Row,t_col = cell.Column; //    Console.log(`${s_row}-${e_row},${s_col}-${e_col},(${t_row},${t_col})`);     return s_row<=t_row && t_row<=e_row && s_col<=t_col && t_col<=e_col; }  function _m_test(){     Console.log(_m_isInArea("A2:C20","B3"));     Console.log(_m_isInArea("A2:C20","D3")); } 结果:  true false 本地文件读写 function read_write_data_txt(){     let txtFile = "E:/tmp/a.txt";          let f = FreeFile() ;     Open(txtFile,f,jsOutput,jsWrite);     Write(f,"123");     Write(f,"456,789");     Write(f,"aaa,bbb");     Close();          Console.clear()     let fNumber = FreeFile()     Open(txtFile, fNumber,jsInput)     while(!EOF(fNumber)) {         let p=LineInput(fNumber);         Console.log(p)     }     Close(fNumber) } "123" "456,789" 生成文件:  JavaScript语法补充 undefined和null的区别 undefined和null的区别(值相等,但类型不等) :  typeof undefined             // undefined typeof null                  // object null === undefined           // false null == undefined            // true typeof操作符 typeof 操作符可以检测变量的数据类型 :  typeof "John"                 // 返回 string  typeof 3.14                   // 返回 number typeof NaN                    // 返回 number typeof false                  // 返回 boolean typeof [1,2,3,4]              // 返回 object typeof {name:'John', age:34}  // 返回 object typeof new Date()             // 返回 object typeof function () {}         // 返回 function typeof myCar                  // 返回 undefined (如果 myCar 没有声明) typeof null                   // 返回 object 解构赋值 var [x, y, z] = ['hello', 'JavaScript', 'ES6']; console.log('x = ' + x + ', y = ' + y + ', z = ' + z); // x = hello, y = JavaScript, z = ES6 [x, [y, z]] = ['hello', ['JavaScript', 'ES6']]; console.log('x = ' + x + ', y = ' + y + ', z = ' + z); // x = hello, y = JavaScript, z = ES6 常用方法 方法    描述 concat()    连接两个或更多的数组,并返回结果。 copyWithin()    从数组的指定位置拷贝元素到数组的另一个指定位置中。 entries()    返回数组的可迭代对象。 every()    检测数值元素的每个元素是否都符合条件。 fill()    使用一个固定值来填充数组。 filter()    检测数值元素,并返回符合条件所有元素的数组。 find()    返回符合传入测试(函数)条件的数组元素。 findIndex()    返回符合传入测试(函数)条件的数组元素索引。 forEach()    数组每个元素都执行一次回调函数。 from()    通过给定的对象中创建一个数组。 includes()    判断一个数组是否包含一个指定的值。 indexOf()    搜索数组中的元素,并返回它所在的位置。 isArray()    判断对象是否为数组。 join()    把数组的所有元素放入一个字符串。 keys()    返回数组的可迭代对象,包含原始数组的键(key)。 lastIndexOf()    搜索数组中的元素,并返回它最后出现的位置。 map()    通过指定函数处理数组的每个元素,并返回处理后的数组。 pop()    删除数组的最后一个元素并返回删除的元素。 push()    向数组的末尾添加一个或更多元素,并返回新的长度。 reduce()    将数组元素计算为一个值(从左到右)。 reduceRight()    将数组元素计算为一个值(从右到左)。 reverse()    反转数组的元素顺序。 shift()    删除并返回数组的第一个元素。 slice()    选取数组的的一部分,并返回一个新数组。 some()    检测数组元素中是否有元素符合指定条件。 sort()    对数组的元素进行排序。 splice()    从数组中添加或删除元素。 unshift()    向数组的开头添加一个或更多元素,并返回新的长度。 splice:  splice()方法可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素:  var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle']; // 从索引2开始删除3个元素,然后再添加两个元素: arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL', 'Excite'] arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle'] // 只删除,不添加: arr.splice(2, 2); // ['Google', 'Facebook'] arr; // ['Microsoft', 'Apple', 'Oracle'] // 只添加,不删除: arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素 arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle'] concat:  concat()方法把当前的Array和另一个Array连接起来,并返回一个新的Array:  var arr = ['A', 'B', 'C']; var added = arr.concat([1, 2, 3]); added; // ['A', 'B', 'C', 1, 2, 3] arr; // ['A', 'B', 'C'] 请注意,concat()方法并没有修改当前Array,而是返回了一个新的Array。  实际上,concat()方法可以接收任意个元素和Array,并且自动把Array拆开,然后全部添加到新的Array里:  var arr = ['A', 'B', 'C']; arr.concat(1, 2, [3, 4]); // ['A', 'B', 'C', 1, 2, 3, 4] join:  join()方法把当前Array的每个元素都用指定的字符串连接起来,然后返回连接后的字符串:  var arr = ['A', 'B', 'C', 1, 2, 3]; arr.join('-'); // 'A-B-C-1-2-3' 如果Array的元素不是字符串,将自动转换为字符串后再连接。  map函数遇到的问题 map的语法:  array.map(function(currentValue,index,arr), thisValue) currentValue: 当前元素的值 index : 当前元素的索引值 arr : 前元素属于的数组对象 thisValue:用作 “this” 的值。如果省略了 thisValue,或者传入 null、undefined,那么回调函数的 this 为全局对象。 利用map()把字符串变成整数 :  var arr = ['1', '2', '3']; var r = arr.map(parseInt); console.log(r); //结果却是1, NaN, NaN 这是因为parseInt接受两个参数(string, radix),第二个参数指定被转换的“数字”多少进制。  当arr = [1,2,3]时,arr.map(parseInt)实际为:  parseInt('1', 0);  // 按十进制转换'1' parseInt('2', 1);  // 按一进制转换'2',但一进制中只有0没有1 parseInt('3', 2);  // 按二进制转换3,但二进制中只有0和1没有2 解决办法:  var arr = ['1', '2', '3']; var r = arr.map(str=>parseInt(str)); console.log(r); // 结果:1,2,3 模板字符串 var name = '小华'; var age = 20; var message = `你好, ${name}, 你今年${age}岁了!`; 1 2 3 循环语句 for循环 语法:  for (语句 1; 语句 2; 语句 3) {     被执行的代码块 } 语句1 (代码块)开始前执行 语句2 定义运行循环(代码块)的条件 语句3 在循环(代码块)已被执行之后执行 for (var i=0,len=cars.length; i<len; i++) {      document.write(cars[i] + "<br>"); } // 或 var i=2,len=cars.length; for (; i<len; i++) {      document.write(cars[i] + "<br>"); } for…in循环 for/in 语句循环遍历对象的属性:  var person={fname:"John",lname:"Doe",age:25};  for (x in person)  // x 为属性名 {     txt=txt + person[x]; } 循环遍历Array的索引:  var a = ['A', 'B', 'C']; for (var i in a) {     console.log(i); // '0', '1', '2'     console.log(a[i]); // 'A', 'B', 'C' } 注意:for ... in对Array的循环得到的是String而不是Number。  for…of循环 具有iterable类型的集合还可以通过for ... of循环来遍历,它是ES6引入的新的语法。  var a = ['A', 'B', 'C']; var s = new Set(['A', 'B', 'C']); var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]); for (var x of a) { // 遍历Array     console.log(x); } for (var x of s) { // 遍历Set     console.log(x); } for (var x of m) { // 遍历Map     console.log(x[0] + '=' + x[1]); } for of循环和for in循环的区别 for ... in循环遍历的实际是对象的属性名称,手动给Array对象添加了额外的属性后:  当我们手动给Array对象添加了额外的属性后,for ... in循环将带来意想不到的意外效果:  var a = ['A', 'B', 'C']; a.name = 'Hello'; for (var x in a) {     console.log(x); // '0', '1', '2', 'name' } for ... of循环则只循环集合本身的元素:  var a = ['A', 'B', 'C']; a.name = 'Hello'; for (var x of a) {     console.log(x); // 'A', 'B', 'C' } iterable内置的forEach方法 var a = ['A', 'B', 'C']; a.forEach(function (element, index, array) {     // element: 指向当前元素的值     // index: 指向当前索引     // array: 指向Array对象本身     console.log(element + ', index = ' + index); }); var s = new Set(['A', 'B', 'C']); //Set没有索引,因此回调函数的前两个参数都是元素本身 s.forEach(function (element, sameElement, set) {     console.log(element); }); var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]); //Map的回调函数参数依次为value、key和map本身 m.forEach(function (value, key, map) {     console.log(value); }); 回调函数可以省略参数:  var a = ['A', 'B', 'C']; a.forEach(function (element) {     console.log(element); }); while 循环 语法:  while (条件) {     需要执行的代码 } // 或 do {     需要执行的代码 } while (条件); 例子:  while (i<5) {     x=x + "The number is " + i + "<br>";     i++; } do {     x=x + "The number is " + i + "<br>";     i++; } while (i<5); 箭头函数与装饰器 ES6 新增了箭头函数,语法 :  (参数1, 参数2, …, 参数N) => { 函数声明 } 单一参数 => {函数声明} () => {函数声明} 例子:  const x = (x, y) => x * y; const x = (x, y) => { return x * y }; 箭头函数简化了函数定义,如果只包含一个表达式,可以省略{ ... }和return。  如果要返回一个对象,单表达式会报错:  // SyntaxError: x => { foo: x } 因为和函数体的{ ... }有语法冲突,所以要改为:  // ok: x => ({ foo: x }) 箭头函数看上去是匿名函数的一种简写,但有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定。  箭头函数的this总是指向词法作用域,也就是外层调用者obj:  var obj = {     birth: 1990,     getAge: function () {         var b = this.birth; // 1990         var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象         return fn();     } }; obj.getAge(); // 25 由于this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者ap***()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略:  var obj = {     birth: 2000,     getAge: function (year) {         var b = this.birth; // 1990         var fn = (y) => y - this.birth; // this.birth仍是2000         return fn.call({birth:2010}, year);     } }; obj.getAge(2015); // 15 在一个独立的函数调用中,根据是否是strict模式,this指向undefined或window。  要指定函数的this指向哪个对象,可以用函数本身的ap***方法,它接收两个参数,第一个参数就是需要绑定的this变量,第二个参数是Array,表示函数本身的参数。  用ap***修复getAge()调用:  function getAge() {     var y = new Date().getFullYear();     return y - this.birth; } var xiaoming = {     name: '小明',     birth: 1990,     age: getAge }; xiaoming.age(); // 25 getAge.ap***(xiaoming, []); // 25, this指向xiaoming, 参数为空 利用ap***()可实现装饰器,比如想统计一下代码一共调用了多少次:  'use strict';  var count = 0; var oldParseInt = parseInt; // 保存原函数  window.parseInt = function () {     count += 1;     return oldParseInt.ap***(null, arguments); // 调用原函数 }; parseInt('10'); parseInt('20'); parseInt('30'); console.log('count = ' + count); // 3 call()与ap***()类似,唯一区别是:  ap***()把参数打包成Array再传入; call()把参数按顺序传入。 比如调用Math.max(3, 5, 4),分别用ap***()和call()实现如下:  Math.max.ap***(null, [3, 5, 4]); // 5 Math.max.call(null, 3, 5, 4); // 5 对普通函数调用,通常把this绑定为null。 ———————————————— 版权声明:本文为CSDN博主「小小明-代码实体」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/as604049322/article/details/127097880 
  • [技术干货] JS中的事件监听-转载
     JavaScript中的事件监听是Web开发中非常重要的一个概念。它允许我们在特定的事件发生时执行特定的代码,从而实现交互效果和动态行为。本篇博客将详细介绍JavaScript事件监听的用法,并举例说明。  事件监听的基本概念 在JavaScript中,事件监听是通过addEventListener()方法实现的。该方法的基本语法如下:  element.addEventListener(event, function, useCapture); 1 其中,element指的是要监听的HTML元素,event指的是要监听的事件类型,function指的是在事件发生时要执行的函数,useCapture是一个可选的布尔值,用于指定事件是否在捕获阶段处理。  例如,我们可以在一个按钮上添加一个点击事件监听器,如下所示:  <button id="myButton">Click me!</button> 1 var button = document.getElementById("myButton"); button.addEventListener("click", function() {   alert("Button clicked!"); }); 在上述代码中,我们使用getElementById()方法获取了ID为myButton的按钮元素,并使用addEventListener()方法在其上添加了一个点击事件监听器。当用户点击该按钮时,会弹出一个警告框,显示"Button clicked!"。  事件类型 在JavaScript中,有很多不同的事件类型可供监听。以下是一些常见的事件类型及其描述:  click:用户单击了某个元素。  dblclick:用户双击了某个元素。  mousedown:用户按下了鼠标按钮。  mouseup:用户释放了鼠标按钮。  mousemove:用户移动了鼠标。  mouseover:鼠标移到某个元素上。  mouseout:鼠标从某个元素移开。  keydown:用户按下了键盘上的某个键。  keyup:用户释放了键盘上的某个键。  focus:某个元素获得了焦点。  blur:某个元素失去了焦点。  load:某个元素完成加载。  unload:某个元素被卸载。  以上仅是一部分常用的事件类型,还有很多其他的事件类型可供选择。在实际开发中,我们需要根据具体的需求选择合适的事件类型。  事件处理函数 在事件监听器中,我们需要定义一个事件处理函数,用于在事件发生时执行特定的代码。事件处理函数可以是任何JavaScript函数,包括匿名函数和已命名函数。  例如,以下代码定义了一个名为myFunction的函数,该函数用于在按钮被点击时改变按钮的文本:  <button id="myButton">Click me!</button> 1 var button = document.getElementById("myButton"); button.addEventListener("click", myFunction);  function myFunction() {   button.innerHTML = "Button clicked!"; } 在上述代码中,我们使用addEventListener()方法在按钮上添加了一个点击事件监听器,并将事件处理函数设置为myFunction。当用户点击该按钮时,myFunction函数会被调用,并将按钮的文本更改为"Button clicked!"。  事件传播 在JavaScript中,事件传播分为三个阶段:捕获阶段、目标阶段和冒泡阶段。默认情况下,事件处理函数会在冒泡阶段执行。但是,我们可以使用useCapture参数来将事件处理函数设置为在捕获阶段执行。  以下是事件传播的详细描述:  捕获阶段:事件从文档根节点开始向下传播,直到到达事件目标的父级元素。 目标阶段:事件到达事件目标元素。 冒泡阶段:事件从事件目标的父级元素开始向上冒泡,直到到达文档根节点。  例如,以下代码演示了事件传播的过程:  <div id="outer">   <div id="inner">     <button id="myButton">Click me!</button>   </div> </div> var outer = document.getElementById("outer"); var inner = document.getElementById("inner"); var button = document.getElementById("myButton");  outer.addEventListener("click", function() {   console.log("Outer clicked!"); }, true);  inner.addEventListener("click", function() {   console.log("Inner clicked!"); }, true);  button.addEventListener("click", function() {   console.log("Button clicked!"); }, true); 在上述代码中,我们在三个元素上分别添加了一个点击事件监听器,并将事件处理函数设置为在捕获阶段执行。当用户单击按钮时,控制台会输出以下内容:  Outer clicked! Inner clicked! Button clicked!  从输出结果可以看出,事件从外到内依次经过了outer、inner和button三个元素,并在每个元素上执行了事件处理函数。  事件对象 在事件监听器中,事件对象是一个非常重要的概念。事件对象包含了关于事件的所有信息,例如事件类型、事件目标和鼠标位置等。我们可以使用事件对象来获取这些信息,并在事件处理函数中进行处理。  以下是一些常用的事件对象属性:  type:事件类型。 target:事件目标元素。 currentTarget:当前正在处理事件的元素。 clientX/clientY:鼠标相对于浏览器窗口左上角的坐标。 pageX/pageY:鼠标相对于文档左上角的坐标。 keyCode:按下的键盘键的键码值。  例如,以下代码演示了如何使用事件对象获取鼠标位置:  <div id="myDiv">学习事件监听</div> 1 var div = document.getElementById("myDiv");  div.addEventListener("mousemove", function(event) {   console.log("X: " + event.clientX + ", Y: " + event.clientY); }); 在上述代码中,我们在一个div元素上添加了一个鼠标移动事件监听器,并使用事件对象获取了鼠标相对于浏览器窗口左上角的坐标。当用户在该div元素上移动鼠标时,控制台会输出鼠标位置信息。  移除事件监听器 在JavaScript中,我们可以使用removeEventListener()方法来移除已添加的事件监听器,以避免出现意外的事件触发。该方法的语法如下:  element.removeEventListener(event, function, useCapture); 1 与addEventListener()方法类似,removeEventListener()方法需要指定要移除的事件类型、事件处理函数和是否在捕获阶段处理。  例如,以下代码演示了如何移除一个事件监听器:  <button id="myButton">Click me!</button> 1 var button = document.getElementById("myButton"); var handleClick = function() {   alert("Button clicked!"); };  button.addEventListener("click", handleClick);  setTimeout(function() {   button.removeEventListener("click", handleClick); }, 5000); 在上述代码中,我们在按钮上添加了一个点击事件监听器,并将其保存在一个变量handleClick中。然后,使用setTimeout()方法在5秒后移除该事件监听器。  总结 事件监听是Web开发中非常重要的一个概念,掌握了它的用法,可以让我们实现更加丰富和动态的交互效果。在实际开发中,我们需要根据具体的需求选择合适的事件类型和事件处理函数,以及注意事件传播和事件对象的相关问题。 ———————————————— 版权声明:本文为CSDN博主「爱学习的Akali King」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/dyk11111/article/details/129813690 
  • [技术干货] 常见的JS数据存储方式及其特点
    在前端开发中,经常需要在浏览器端存储和管理数据。为了实现数据的持久化存储和方便的访问,JavaScript提供了多种数据存储方式。本文将介绍几种常见的前端JS数据存储方式及其特点。1. CookieCookie是一种小型的文本文件,由浏览器保存在用户计算机上。使用JavaScript可以读取和写入Cookie。Cookie主要用于存储少量的数据,并且会在每次HTTP请求中自动发送到服务器。但是,Cookie的存储容量有限,通常不超过4KB,而且受浏览器设置的限制。2. LocalStorageLocalStorage是HTML5引入的一种持久化的本地存储机制。它允许在浏览器中存储大量的数据,并且在浏览器关闭后数据仍然保持不变。LocalStorage使用简单的键值对来存储数据,可以通过JavaScript的localStorage对象进行访问和操作。LocalStorage的存储容量通常为5MB或更大,但是由于是在用户浏览器上存储数据,因此需要注意不要存储敏感或重要的信息。3. SessionStorageSessionStorage与LocalStorage类似,也是一种HTML5提供的本地存储机制。不同的是,SessionStorage中存储的数据在浏览器会话结束后就会被清除,而不是持久化存储。SessionStorage适用于临时保存会话数据,比如在同一浏览器选项卡或窗口之间共享数据。4. IndexedDBIndexedDB是一种高级的客户端数据库,用于在浏览器中存储结构化数据。它提供了事务性的存储机制和丰富的查询功能。IndexedDB适用于需要存储大量结构化数据或进行复杂查询的场景。与其他存储方式相比,IndexedDB的学习曲线较陡峭,需要使用JavaScript API进行操作。5. Web Storage APIWeb Storage API是一个统一的API,包括LocalStorage和SessionStorage。它提供了一套简单的接口,用于访问和操作这两种存储方式。通过Web Storage API,可以轻松地读取、写入和删除数据。6.其他存储方式除了上述提到的存储方式,还有一些其他的存储方式可供选择,如WebSQL、FileSystem API等。然而,由于这些存储方式的兼容性和使用限制等问题,它们的使用已经逐渐减少,不再被广泛推荐。总结开发人员可以根据具体需求选择合适的存储方式。Cookie适合存储小型数据和会话相关信息,LocalStorage和SessionStorage适合在浏览器中进行持久化或临时存储,IndexedDB适合处理大量结构化数据和复杂查询。使用这些数据存储方式,可以有效管理和利用前端的数据。如果你想要查看当前网站存储了哪些数据,可以点击“F12”,在开发者工具中,切换到 “存储” 选项卡 其中,会话存储就是 SessionStorage;本地存储就是localStorage
  • [技术干货] JavaScript获取http,http://请求协议头,域名,端口,url
    window.location.protocol 获取http,http://请求协议头  window.location.host 获取域名并带端口 xxx.com 如果有端口就是 xxx.com:8080  window.location.hostname; 获取域名: xxx.com  window.location.href; 获取整个url地址:http://xxx.com/xxx.html  window.location.pathname; 获取域名后面请求路径url: /xxx/xxx.html  window.location.port 获取端口比如 8080 ———————————————— 原文链接:https://blog.csdn.net/u012834688/article/details/125640832 
  • [问题求助] 坐席二次开发问题
    【问题来源】    汉口银行    【问题简要】    座席二次开发问题【问题类别】    CC-Gateway【AICC解决方案版本】    AICC版本:AICC 22.200    UAP版本:UAP9600 V100R005C00SPC113    CTI版本:  ICD V300R008C25spc012【期望解决时间】    尽快【问题现象描述】您好,二次开发CCGateway接续接口需要使用轮询接口做会话保活。这个是在坐席前端轮询调用接口:https://ip:port/agentgateway/resource/agentevent/{agentid}?calldata=XXX做会话保持吗?
  • [技术干货] javaScript获取键值对的三种方法?keys 、entire.....
    Javascript获取数据对象里面的键key和value大家都知道在后端接口的数据处理后在返回是一件非常重要的事情,根据返回的方法不同,返回的格式也就有所区别,比如通过json的数据格式来返回,再或者通过键值对的方式来返回等等,所以为了更好的让大家理解和区别开键值对是怎么来工作的,这里介绍几种方法让大家更好的了解。方法一:使用 keys值的方法来获取对象的键值key方法二:使用entries方法来获取对象的键值key方法三:使用for循环获取对象的键值key三种方法都可以获取到数组中的key值,同时对象是键值的集合,我们需要一个键值来获取对象中的值是多少,键值对在java、javascrtipt中都有着广泛的应用。那问题来了,如果我们在检索值的过程中只要有他的键值就可以 但是我们如果没有key是多少呢?方法一介绍:Object.keys()Object.keys()使用函数返回对象键的数组值是多少我们以javascript为例,javascript对象作为参数传递给了该函数,如果我们在输出一个数组的时候他的键值对的顺序是不会随机排列的,而是和原始的对象中顺序相同,通用我们将数组传递给给了 对象的键值对,它也会通过数组的索引作为输出返回该结果,并且返回的参数中同样带有索引,所以keys的函数会返回这些索引的数组。代码演示:var zeyuli1 = ["1", "2", "3"];var zeyuli2 = { 0: "1", 4: "0", 2: "3", 3: "0"};var zeyuli3 = { "id": "1", "name": "2", "old": "3"};console.log(Object.keys(zeyuli1));console.log(Object.keys(zeyuli2));console.log(Object.keys(zeyuli3));大家可以手动练习一下看看结果是多少呢?方法二介绍:Object.entries(OBJ)Object.entries(OBJ)的方法运用起来要比keys的函数要灵活的多方法也多的多,在整个对象划分为每个不同的数组时候用key和value形式来表示键值对的形成,使用方法二我们可以得到一个对象中的所有键值对,这就是和方法一的本质区别。语法:Object.entries(OBJ)参数:和方法一的本质区别就是 Object.entries(OBJ)的参数是OBJ注意:和方法一的函数同样具备对象的属性顺序。代码演示:var  zeyuli1 = { "id": "1", "name": "2", "old": "24"};console.log(JSON.stringify(Object.entries(zeyuli1)));方法三介绍:for循环的使用遍历大家都知道经常使用到for循环,那么如何使用for才能让数组都经历一次对象遍历呢?学过的小伙伴都知道 使用的是for和in的固定搭配,使用i来保存对象的key值,而object[i]保存与对象中的key相对应的值。代码展示如下:var a = { "id": "1", "name": "1", "old": {"name": "2", "appearance": "3"}};for(let i in a){  console.log(i);  console.log(a[i]);}使用的嵌套结构中也可以用console.log(i)来获取键值。
  • [技术干货] JS 防抖与节流
    滚动对页面渲染的影响web页面渲染会经历如下步骤:js加载 —> style加载 —> layout(确定布局) —> paint(页面绘制) —> composite(组合控件)网页生成的时候,至少会渲染(Layout+Paint)一次。用户访问的过程中,还会不断重新的重排(reflow)和重绘(repaint),用户scroll行为和resize行为会导致页面不断的进行重新渲染,如果间隔频繁,容易造成浏览器卡帧。为什么滚动scroll、窗口resize需要优化滚动事件的应用很频繁:图片懒加载、下滑自动加载数据、侧边浮动导航栏等。在绑定scroll、resize事件时,但它发生的时候,它被触发的频率非常高,间隔很近。在日常开发的页面中,事件中涉及到的大量的位置计算、DOM操作、元素重绘等等这些都无法在下一个scroll事件出发前完成的情况下,浏览器会卡帧。防抖与节流在进行窗口的resize、scroll、输出框内容校验等操纵的时候,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致浏览器卡顿。为了用户更好的体验,就可以采用防抖(debounce)和节流(throttle)的方式来减少调用的频率,减轻浏览器的负担。防抖Debounce使用情景有些场景事件触发的频率过高(mousemove onkeydown onkeyup onscroll)回调函数执行的频率过高也会有卡顿现象。 可以一段时间过后进行触发去除无用操作原理当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。代码示例以下是一个简单的JavaScript代码示例,它实现了一个基本的防抖功能:function debounce(func, delay) { let timer = null; return function() { const context = this; const args = arguments; clearTimeout(timer); timer = setTimeout(function() { func.apply(context, args); }, delay); } }这个代码定义了一个名为debounce的函数,它接受两个参数:要防抖的函数和防抖的时间间隔。当调用debounce函数时,它会返回一个新的函数,这个新函数将被用来代替原来的函数。每次调用新函数时,它会清除之前设置的定时器,并创建一个新的定时器。如果在指定时间间隔内再次调用新函数,定时器将被清除并重新设置。只有在指定时间间隔内没有再次调用新函数时,定时器才会在最后一次调用后触发,并调用原始函数。这个防抖函数可以用于限制某些操作的执行频率,例如在用户连续输入时进行搜索操作。这可以减少不必要的网络请求和计算资源消耗。节流Throttle使用情景图片懒加载ajax数据请求加载原理当持续触发事件时,保证一定时间段内只调用一次事件处理函数。代码示例以下是一个简单的JavaScript代码示例,它实现了一个基本的节流功能:function throttle(func, delay) { let timer = null; return function() { const context = this; const args = arguments; if (!timer) { timer = setTimeout(function() { func.apply(context, args); timer = null; }, delay); } } }这个代码定义了一个名为throttle的函数,它接受两个参数:要节流的函数和节流的时间间隔。当调用throttle函数时,它会返回一个新的函数,这个新函数将被用来代替原来的函数。每次调用新函数时,它会检查是否已经设置了一个定时器。如果没有,它会创建一个新的定时器,并在指定的时间间隔后调用原始函数。如果已经设置了定时器,则新函数将不执行任何操作。这个节流函数可以用于限制某些操作的执行频率,例如在用户滚动页面时更新页面元素的位置。
  • [技术干货] this.$toast() 了解一下?
    在平时的开发过程中,我们总是先写好一个组件,然后在需要的页面中用 import 引入即可,但如果是下面这种类型的组件呢 上面这种类型的浮层提示有一个很大的特点,就是使用频率特别高,几乎每个页面都会用到它,于是乎我们就要在每个页面中去引入该组件,并且在每个页面都得通过一个变量来控制它的显隐,这显然不是我们想要的。。。那我们想要的是什么样呢?用过一些 UI 框架的同学们应该知道有这样一种用法:this.$toast({ duration: 3000, content: '这是一条消息提示'});复制代码没错,就是这么简单的一句话就万事大吉了(就是用 js 调用组件而已啦)。那这种效果究竟是怎么实现的呢?今天就让我们来(手把手 )一探究竟吧!前置知识不知道小伙伴们有没有用过 Vue.extend() 这个东东,反正我是很少碰过,印象不深,所以这里我们先来短暂了解一下 Vue.extend() 主要是用来干嘛的。先来个官方说明(不多的,坚持下):  没怎么看懂?没关系,不重要,你只要记住(加少许理解)以下用法即可:// 导入以往的普通组件import Main from './main.vue';// 用 Vue.extend 创建组件的模板(构造函数)let mainConstructor = Vue.extend(Main);// 实例化组件let instance = new mainConstructor();// 挂载到相应的元素上instance.$mount('#app');复制代码不知道你看懂没有,上面的 Vue.extend(Main) 就是一个基于 main.vue 的组件模板(构造函数),instance 是实例化的组件,$mount() 是手动挂载的意思。其中 Vue.extend() 和 $mount() 就是我们通过 js 调用、渲染并挂载组件的精髓所在,相当于早前的 createElement 和 appendChild,有异曲同工之效。这个点需要我们好好熟悉一下,所以你可以先停下来屡屡思路。补充一下:$mount() 里面如果没有参数,说明组件只是渲染了但还没有挂载到页面上,如果有正确的(元素)参数则直接挂载到元素下面。写一个 toast 组件js 调用归调用,最原始的组件还是要有的,只是我们不通过 import 来引入到页面中而已。ok,我们就以最开始的那个 toast 图片来简单写一下这个 vue 组件(message 和 alert 也是一样的)。这里就直接上代码啦,毕竟它的结构简单到爆了,也不是本章节的重点:<!-- main.vue --><template> <div class="toast"> <p>服务器错误,请稍后重试</p> </div></template><script>export default { name: "Toast", mounted() { setTimeout(() => { // 3s 后通过父级移除子元素的方式来移除该组件实例和 DOM 节点 this.$destroy(true); this.$el.parentNode.removeChild(this.$el); }, 3000); }};</script><style lang="scss" scoped>.toast { display: flex; align-items: center; justify-content: center; position: fixed; top: 0; bottom: 0; left: 0; right: 0; color: #fff; z-index: 9999; background: transparent; > p { padding: 12px 22px; font-size: 18px; border-radius: 4px; background: rgba(17, 17, 17, 0.7); }}</style>复制代码上面的内容想必大家应该都能看懂,所以这里就直接讲下面的重点了。写一个 main.js我们在 main.vue 的同级目录下新建一个 main.js 文件。我们先瞟一眼文件内容(也不多,已经是个最简版了):// main.jsimport Vue from "vue"; // 引入 Vue 是因为要用到 Vue.extend() 这个方法import Main from "./main.vue"; // 引入刚才的 toast 组件let ToastConstructor = Vue.extend(Main); // 这个在前面的前置知识内容里面有讲到let instance;const Toast = function() { instance = new ToastConstructor().$mount(); // 渲染组件 document.body.appendChild(instance.$el); // 挂载到 body 下};export default Toast;复制代码上面的代码暴露了一个 Toast 函数。为什么要暴露一个函数呢?原因很简单:你想想,我们最终是不是要根据 this.$toast() 来调用一个组件,说白了,通过 js 调用,本质就是调用一个 函数。也就是说 this.$toast() 就是执行了上面代码中导出的 export default Toast,也就是执行了 Toast 函数(const Toast = function() {}),所以当我们调用 this.$toast() 的时候其实就是执行了 Toast() 函数。而 Toast() 函数只做了一件事情:就是通过手动挂载的方式把组件挂载到 body 下面。补充一下:一般来说我们常见的是 $mount("#app"),也就是把组件挂载到 #app 下面,<router-view /> 也包含在 #app 中,但是我们这种 toast 提示是放在 body 下面的,也就是说它不受 #app 和 <router-view /> 的管控,所以当我们切换页面(路由)的时候,这个 toast 组件是不会跟着立马消失的,这点要注意哦。这里顺便给个组件的目录结构开始调用调用方式很简单,首先我们在入口文件 main.js(和上面不是同一个) 里加上两行代码,这样我们就能在需要的地方直接用 js 调用它了,如下图所示:  然后在页面中测试一下,就像下面这样子:  运行一下代码:  嗯,挺好,小有成就的 feel 。支持可传参数别急,我们好像还漏了点什么。。。对了,现在还不支持传参呢,直接调用 this.$toast() 就只能显示————服务器错误,请稍后重试(这下全都是后端的锅了)。但我们可是个有追求的前端,不能局限于此,所以现在让我们来尝试增加下两个可配置参数,这里拿 duration 和 content 举个栗子。首先我们要修改 main.vue 组件里面的内容(其实没啥大变化),就像下面这样:<!-- main.vue 可配置版 --><template> <div class="toast"> <p>{{ content }}</p> </div></template><script>// 主要就改了 dataexport default { name: "Toast", data() { return { content: "", duration: 3000 }; }, mounted() { setTimeout(() => { this.$destroy(true); this.$el.parentNode.removeChild(this.$el); }, this.duration); }};</script>复制代码上面的代码应该算是浅显易懂了,接下来我们看下 main.js 里面改了啥:// main.js 可配置版import Vue from "vue";import Main from "./main.vue";let ToastConstructor = Vue.extend(Main);let instance;const Toast = function(options = {}) { // 就改了这里,加了个 options 参数 instance = new ToastConstructor({ data: options // 这里的 data 会传到 main.vue 组件中的 data 中,当然也可以写在 props 里 }); document.body.appendChild(instance.$mount().$el);};export default Toast;复制代码其实 main.js 也没多大变化,就是在函数里面加了个参数。要注意的是 new ToastConstructor({ data: options }) 中的 data 就是 main.vue 组件中的 data,不是随随便便取的字段名,传入的 options 会和组件中的 data 合并(Vue 的功劳)。em。。。是的,就这么简单,现在让我们继续来调用一下它:<script>export default { methods: { showToast() { this.$toast({ content: "哈哈哈哈,消失的贼快", duration: 500 }); } }};</script>复制代码运行一下就可以看到:  当然,这还没完,我们继续添加个小功能点。。支持 this.$toast.error()这里我们打算支持 this.$toast.error() 和 this.$toast.success() 这两种方式,所以我们第一步还是要先去修改一下 main.vue 文件的内容(主要就是根据 type 值来修改组件的样式),就像下面这样:<!--main.vue--><template> <div class="toast" :class="type ? `toast--${type}` : ''"> <p>{{ content }}</p> </div></template><script>export default { ... data() { return { type: "", content: "", duration: 3000 }; }, ...};</script><style lang="scss" scoped>.toast { ... &--error p { background: rgba(255, 0, 0, 0.5); } &--success p { background: rgba(0, 255, 0, 0.5); }}</style>复制代码其次,this.$toast.error() 其实就等价于 Toast.error(),所以我们现在的目的就是要给 Toast 函数扩充方法,也比较简单,就先看代码再解释吧:// main.jsconst Toast = function(options = {}) { ...};// 以下就是在 Toast 函数中拓展 ["success", "error"] 这两个方法["success", "error"].forEach(type => { Toast[type] = options => { options.type = type; return Toast(options); };});export default Toast;复制代码我们可以看到 Toast.error() 和 Toast.success() 最终还是调用 Toast(options) 这个函数,只不过在调用之前需要多做一步处理,就是将 ["success", "error"] 作为一个 type 参数给合并进 options 里面再传递,仅此而已。那就试试效果吧:<script>export default { methods: { showToast() { this.$toast({ content: "这是正常的" }); }, showErrorToast() { this.$toast.error({ content: "竟然失败了" }); }, showSuccessToast() { this.$toast.success({ content: "居然成功了" }); } }};</script>复制代码 大赞无疆,大。赞。。无。。。疆。。。。。结语至此,一个通过 js 调用的简单 toast 组件就搞定啦,短短的几行代码还是挺考验 js 功底的。当然这只是个超简易版的 demo,显然不够完善和健壮,所以我们可以在此基础上扩充一下,比如当 duration <= 0 的时候,我们让这个 toast 一直显示,然后扩展一个 close 方法来关闭等等之类的。不过还是那句老话,实践才是检验真理的唯一标准。纸上得来终觉浅,绝知此事要躬行。step by step, day day up !作者:尤水就下链接:https://juejin.cn/post/6844903825711562766
  • [技术干货] JS 判断数组是否包含某个元素方法集合
    1、JavaScript indexOf() 方法 定义和用法 indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。 语法 stringObject.indexOf(searchvalue,fromindex) 参数    描述 searchvalue    必需。规定需检索的字符串值。 fromindex    可选的整数参数。规定在字符串中开始检索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的首字符开始检索。 说明 该方法将从头到尾地检索字符串 stringObject,看它是否含有子串 searchvalue。开始检索的位置在字符串的 fromindex 处或字符串的开头(没有指定 fromindex 时)。如果找到一个 searchvalue,则返回 searchvalue 的第一次出现的位置。stringObject 中的字符位置是从 0 开始的。 提示和注释 注释:indexOf() 方法对大小写敏感! 注释:如果要检索的字符串值没有出现,则该方法返回 -1。 2、jQuery.inArray()方法 定义和用法 $.inArray() 函数用于在数组中查找指定值,并返回它的索引值(如果没有找到,则返回-1) 提示:源数组不会受到影响,过滤结果只反映在返回的结果数组中。 语法 $.inArray( value, array [, fromIndex ] ) 参数    描述 value    任意类型 用于查找的值。 array    Array类型 指定被查找的数组。 fromIndex    可选。Number类型 指定从数组的指定索引位置开始查找,默认为 0 3、JavaScript find() 方法 定义和用法 find() 方法返回通过测试(函数内判断)的数组的第一个元素的值。 find() 方法为数组中的每个元素都调用一次函数执行:  当数组中的元素在测试条件时返回 true 时, find() 返回符合条件的元素,之后的值不会再调用执行函数。 如果没有符合条件的元素返回 undefined  注意:find() 对于空数组,函数是不会执行的。 注意:find() 并没有改变数组的原始值。 注意:JavaScript 版本为ECMAScript 6 用法  array.find(function(currentValue, index, arr),thisValue) 参数    描述 function    必需。数组每个元素需要执行的函数。 currentValue    必需。当前元素 函数参数 index    可选。当前元素的索引值函数参数 arr    可选。当前元素所属的数组对象函数参数 thisValue    可选。 传递给函数的值一般用 “this” 值。如果这个参数为空, “undefined” 会传递给 “this” 值 返回值 返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined。 4、JavaScript findIndex() 方法 定义和用法 findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。 findIndex() 方法为数组中的每个元素都调用一次函数执行:  当数组中的元素在测试条件时返回 true 时, findIndex() 返回符合条件的元素的索引位置,之后的值不会再调用执行函数。 如果没有符合条件的元素返回 -1  注意:find() 对于空数组,函数是不会执行的。 注意:find() 并没有改变数组的原始值。 注意:JavaScript 版本为ECMAScript 6 用法 array.findIndex(function(currentValue, index, arr), thisValue) 参数    描述 function    必需。数组每个元素需要执行的函数。 currentValue    必需。当前元素 函数参数 index    可选。当前元素的索引值函数参数 arr    可选。当前元素所属的数组对象函数参数 thisValue    可选。 传递给函数的值一般用 “this” 值。如果这个参数为空, “undefined” 会传递给 “this” 值 返回值 返回符合测试条件的第一个数组元素索引,如果没有符合条件的则返回 -1。 5、for 循环和 if 判断var arr = [1, 5, 10, 15];/* 传统for */for(let i=0; i<arr.length; i++) { if(arr[i] === 查找值) { //则包含该元素 }}/* for...of */for(v of arr) { if(v === 查找值) { //则包含该元素 }}/* forEach */arr.forEach(v=>{ if(v === 查找值) { //则包含该元素 }})———————————————— 原文链接:https://blog.csdn.net/github_38336924/article/details/83089873 
  • 使用canvas制作炫彩背景【转】
    1.定义canvas标签:1<canvas id="canvas"></canvas>2.开始js部分,先定义变量:123456789101112131415161718/* 获取画布 */var canvas = document.querySelector("#canvas");var ctx = canvas.getContext('2d');/* 定义一个字符串数组,后面字符串会从里随机选值 */var text = "SAFAF1D56FLK43F7PHM76VC9XNJ23";/* 定义 w为窗口宽度,h为窗体高度 */var w=window.innerWidth;var h=window.innerHeight;/* 设置len为20,其为背景里每条字符串的长度 */var len = 20;/* 设置num为100,窗口一共显示100条字符串 */var num = 100;/* 定义数组,里面存取每条字符串的字符与位置 */var arr=[];/* 画布宽等于窗口宽 */canvas.width=window.innerWidth;/* 画布高等于窗口高 */canvas.height=window.innerHeight;3.初始化字符串数组,先给每条字符串位置,字符先不给:123456789101112/* 初始化字符串数组 */        for(let i=0;i<num;i++){            /* 用.push方法给arr数组添加值 */            arr.push({                /* 字符先为空 */                str:[],                /* x轴位置为窗口宽度乘上一个0带1的随机数 */                x: parseInt(w*Math.random()),                /* y轴位置为窗口高度乘上一个0带1的随机数,再减个150把,可以为负数 */                y: parseInt(h*Math.random()-150)            })        }4.绘制每条字符串:123456789101112131415161718192021222324252627282930/* 绘制每条字符串 */       function txt(){           /* 给个循环,共绘制num条 */           for(let i=0;i<num;i++){               /* 设置变量letter为当前arr数组里的第i条字符串 */               var letter = arr[i];               /* 让字符串的字符为空 */               letter.str = [];               /* 给个循环,一个字符一个字符的拼接成字符串 */               for(let k=0;k<len;k++){                   /* 随机选取text里的一个字符 */                   letter.str.push(text[Math.floor(Math.random() * text.length)]);               }               /* 再来循环,开始绘制渲染字符串的每个字符 */               for(let j=0;j<len;j++){                   if(j==len-1){                       /* 第一个字符为白色 */                       ctx.fillStyle = `rgb(255, 255, 255)`;                   }else{                       /* 后面的为绿色,慢慢变不透明 */                       ctx.fillStyle = `rgba(0, 255, 21,${j*0.15})`;                   }                        /* 渲染字符 */                    ctx.font = "20px FangSong";                    ctx.fillText(letter.str[j],letter.x,letter.y+j*15);               }           }           /* 调用更新 */           move();       }5.更新字符串:12345678910111213141516/* 让字符串移动,若某字符串出了可视区,则重新生成 */       function move(){           /* 来个循环,给全部字符串更新位置 */           for(let j=0;j<num;j++){               /* y轴位置加3 */               arr[j].y=arr[j].y+3;               /* 如果改字符已经走出窗口了重新赋值 */               if(arr[j].y>=h){                   arr[j]={                       str:[],                x: parseInt(w*Math.random()),                y: parseInt(h*Math.random()-150)                   }               }           }       }6.设置动画过程:12345678setInterval(function(){            /* 清屏 */           ctx.clearRect(0,0,w,h);           /* 渲染 */           move();           /* 更新 */           txt();        },50); 7.在窗口大小改变时,设置canvas画布能实时铺满屏幕:123456789101112131415/* 绑定窗口大小发生改变事件,重新绘制字符串数组,让canvas随时铺满浏览器可视区 */        window.onresize=resizeCanvas;        function resizeCanvas(){            w=canvas.width=window.innerWidth;            h=canvas.height=window.innerHeight;            /* 重新给全部字符串赋值 */            for(let j=0;j<num;j++){                    arr[j]={                        str:[],                 x: parseInt(w*Math.random()),                 y: parseInt(h*Math.random()-150)                    }                }            }               resizeCanvas();转载自https://www.jb51.net/article/279297.htm
  • [技术干货] 两个函数相互调用如何防止死循环【转】
    最近碰到了一个问题,就是两个函数相互调用遭遇死循环的问题,想了半天终于想出了一个算法破解,姑且叫它熵递减算法。问题的抽象代码如下1234567891011/**  methodA 和 methodB 循环调用,是死循环* */function methodA() {    console.log('A的事情');    methodB();}function methodB() {    console.log('B的事情');    methodA();}不论调用哪个方法,都会产生死循环。我想要的效果是:如果触发A方法时,也执行一下B方法,到此为止不再循环下去,反之亦然。因此,必须能判断方法是主动发起的,还是被动的。抽象代码如下:12345678910111213141516171819/**  解决问题的关键在于,判断方法是主动发起的,还是被动的* */function methodA() {    console.log('A的事情');    if('A是主动的'){        methodB();    }else{        // 不再调用下去    }}function methodB() {    console.log('B的事情');    if('B是主动的'){        methodA();    }else{        // 不再调用下去    }}解法,就是熵递减算法,如下1234567891011121314151617181920212223/** 熵递减算法* */var pairMethodStep = 2;function methodA() {    pairMethodStep --;    console.log('A的事情');    if(pairMethodStep === 1){        methodB();    }else{        pairMethodStep = 2;    }}function methodB() {    pairMethodStep --;    console.log('B的事情');    if(pairMethodStep === 1){        methodA();    }else{        pairMethodStep = 2;    }}熵递减算法的说明:给一个全局变量,叫做总步数pairMethodStep ,初始值为2。任一个方法执行时,做完自己的事情后,把pairMethodStep减成1。然后,判断此时的pairMethodStep,如果是1, 就调用另一个方法;如果是0了,就不再继续调用了,而是把pairMethodStep恢复成2。我们分析一下代码执行的过程。主动的方法执行前,pairMethodStep的值是2,它做完自己的事后,把pairMethodStep的值变成了1,紧跟着就会执行被动的方法;被动的方法执行前,pairMethodStep的值是1,被动的方法做完自己的事情后,把pairMethodStep的值减成了0,不会再调用另一个方法了(不会发生死循环了),而仅仅是把pairMethodStep还原成2。目的达到。熵递减算法,能完美地解决两个函数相互调用的问题。js函数互相调用碰到的问题项目场景两个函数互相调用时(当一个系统比较大时,尤其是涉及到一些复杂的算法时,很有可能会碰到死循环的情况发生,造成系统的CPU飙升)12345678        function a1() {            console.log("a1");            b1();        }        function b1() {            console.log("b1");            a1();        }问题描述会进入死循环原因分析就类似for循环,或者递归函数如果没有退出条件就会一直执行解决方案1234567891011121314151617181920        let flagNum = 1;          function a1() {            flagNum--;            console.log("a1");            if (flagNum === 0) {                b1();            } else {                flagNum = 1;            }        }        function b1() {            flagNum--;            console.log("b1");            if (flagNum === 0) {                a1();            } else {                flagNum = 1;            }        }总结以上为个人经验,希望能给大家一个参考转载自https://www.jb51.net/article/279298.htm
  • [技术干货] JS内存泄漏的原因及解决办法
    什么是内存泄漏?程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存。对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能:变慢,延迟大等 ,重则导致进程崩溃。我自己是一名从事了多年开发的web前端老程序员,目前辞职在做自己的web前端私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的web前端学习干货,各种框架都有整理,送给每一位前端小伙伴,想要获取的可以关注我的头条号并在后台私信我:前端,即可免费获取。不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。内存泄漏的识别方法1、使用快捷键 F12 或者 Ctrl+Shift+J 打开 Chrome 浏览器的「开发者工具」。2、选择 Performance(老版为Timeline) 选项卡,在 Capture 选项中,只勾选 Memory。3、设置完成后,点击最左边的 Record 按钮,然后就可以访问网页了。4、打开一个网站,例如:www.taobao.com,当网页加载完成后,点击 Stop,等待分析结果。5、然后在 ChartView 上寻找内存急速下降的部分,查看对应的 EventLog,可以从中找到 GC 的日志。具体过程如下图所示:内存泄露的常见原因及处理方式常见原因:1. 意外的全局变量下面代码中变量bar在foo函数内,但是bar并没有声明.JS就会默认将它变为全局变量,这样在页面关闭之前都不会被释放.function foo(){ bar=2 console.log('bar没有被声明!')}b 没被声明,会变成一个全局变量,在页面关闭之前不会被释放.使用严格模式可以避免.2. dom清空时,还存在引用很多时候,为了方便存取,经常会将 DOM 结点暂时存储到数据结构中.但是在不需要该DOM节点时,忘记解除对它的引用,则会造成内存泄露.var element = { shotCat: document.getElementById('shotCat')};document.body.removeChild(document.getElementById('shotCat'));// 如果element没有被回收,这里移除了 shotCat 节点也是没用的与此类似情景还有: DOM 节点绑定了事件, 但是在移除的时候没有解除事件绑定,那么仅仅移除 DOM 节点也是没用的3. 定时器中的内存泄漏var someResource = getData();setInterval(function() { var node = document.getElementById('Node'); if(node) { node.innerHTML = JSON.stringify(someResource)); }}, 1000);如果没有清除定时器,那么 someResource 就不会被释放,如果刚好它又占用了较大内存,就会引发性能问题. 但是 setTimeout ,它计时结束后它的回调里面引用的对象占用的内存是可以被回收的. 当然有些场景 setTimeout 的计时可能很长, 这样的情况下也是需要纳入考虑的.4. 不规范地使用闭包例如下面的例子: 相互循环引用.这是经常容易犯的错误,并且有时也不容易发现.function foo() { var a = {}; function bar() { console.log(a); }; a.fn = bar; return bar; };bar和a形成了相互循环引用.可能有人说bar里不使用console.log(a)不就没有引用了吗就不会造成内存泄露了.NONONO,bar作为一个闭包,即使它内部什么都没有,foo中的所有变量都还是隐式地被 bar所引用。 即使bar内什么都没有还是造成了循环引用,那真正的解决办法就是,不要将a.fn = bar.避免策略:减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收(即赋值为null);注意程序逻辑,避免“死循环”之类的 ;避免创建过多的对象 原则:不用了的东西要记得及时归还。减少层级过多的引用
  • [问题求助] 预约会议后的回调函数缺少对应的返回参数
    返回的对象里,对应param属性里缺少confBaseInfo属性,这个属性里会有对应的会议号等信息
  • [技术干货] 基于Java+SpringBoot+vue+element驾校管理系统设计和实现-转载
     一、前言介绍:         随着社会的发展和科学技术的进步,互联网技术越来越受欢迎。网络传播的生活方式逐渐受到广大人民群众的喜爱。越来越多的互联网爱好者开始在互联网上满足他们的基本需求,同时逐渐进入各个用户的生活起居。互联网具有许多优点,例如便利性,速度,高效率和低成本。因此,类似于驾校管理,满足用户工作繁忙的需求,不仅是方便用户随时查看信息的途径,而且还能提高管理效率。本文首先以驾校管理过程的基本问题作为研究对象。在开发系统之前,我们对现有状况进行了详细的调查和分析。最后,我们利用计算机技术开发了一套完整合适的驾校管理。该系统的实现主要优势是:该系统主要采用计算机技术开发,它方便快捷;系统可以通过管理员界面查看系统所涉及的驾校管理所有信息管理。          驾校管理软件是一款方便、快捷、实用的信息服务查询软件。随着智能网络在全球市场的不断普及以及各种智能平台的使用,作为中国主流智能的技术开发系统,自然需要这样的软件来满足更多用户的需求和体验。系统的开发与人们的日常需求相关,如通过管理系统获取到个人中心、学员管理、驾校教练管理、预约教练管理、预约教练管理、评价教练管理、考试成绩管理、考试通知管理、报考信息管理、练车通知管理、预约练车管理、驾考套餐管理、报名信息管理、车辆信息管理等信息详细情况,了解最新资讯信息等。  二、主要技术:  2.1  Spring Boot框架介绍:         Spring框架是Java平台上的一种开源应用框架,提供具有控制反转特性的容器。尽管Spring框架自身对编程模型没有限制,但其在Java应用中的频繁使用让它备受青睐,以至于后来让它作为EJB(EnterpriseJavaBeans)模型的补充,甚至是替补。Spring框架为开发提供了一系列的解决方案,比如利用控制反转的核心特性,并通过依赖注入实现控制反转来实现管理对象生命周期容器化,利用面向切面编程进行声明式的事务管理,整合多种持久化技术管理数据访问,提供大量优秀的Web框架方便开发等等。Spring框架具有控制反转(IOC)特性,IOC旨在方便项目维护和测试,它提供了一种通过Java的反射机制对Java对象进行统一的配置和管理的方法。Spring框架利用容器管理对象的生命周期,容器可以通过扫描XML文件或类上特定Java注解来配置对象,开发者可以通过依赖查找或依赖注入来获得对象。Spring框架具有面向切面编程(AOP)框架,SpringAOP框架基于代理模式,同时运行时可配置;AOP框架主要针对模块之间的交叉关注点进行模块化。Spring框架的AOP框架仅提供基本的AOP特性,虽无法与AspectJ框架相比,但通过与AspectJ的集成,也可以满足基本需求。Spring框架下的事务管理、远程访问等功能均可以通过使用SpringAOP技术实现。Spring的事务管理框架为Java平台带来了一种抽象机制,使本地和全局事务以及嵌套事务能够与保存点一起工作,并且几乎可以在Java平台的任何环境中工作。  2.2 MYSQL数据库:                 数据库是系统开发过程中不可或缺的一部分。 在WEB应用方面,MySQL AB开发了一个具有很大优势的MySQL关系数据库管理系统。 MySQL可以将数据存储在不同的表中,这非常灵活,并且还可以提高系统在实际应用中的速度。 数据库访问最常用于标准SQL语言,MySQL用于SQL语言,因此它具有高度兼容性。数据库的操作是必不可少的,包括对数据库表的增加、删除、修改、查询等功能。现如今,数据库可以分为关系型数据库和非关系型数据库,Mysql属于关系性数据库,Mysql数据库是一款小型的关系型数据库,它以其自身特点:体积小、速度快、成本低等,Mysql数据库是目前最受欢迎的开源数据库。          在WEB应用技术中, Mysql数据库支持不同的操作系统平台,虽然在不同平台下的安装和配置都不相同,但是差别也不是很大,Mysql在Windows平台下两种安装方式,二进制版和免安装版。安装完Mysql数据库之后,需要启动服务进程,相应的客户端就可以连接数据库,客户端可通过命令行或者图形界面工具登录数据库。  三、系统设计: 3.1 系统架构设计: ​  3.2 登录时序图设计: ​  四、功能截图:  4.1 登录注册:  4.2 前端页面: 4.2.1 系统首页:   4.2.2 教练模块:   4.2.3 考试须知:   4.2.4 预约练车:   4.2.5 车辆信息:  4.2.6 系统公告信息:  4.2.7 个人中心模块:   4.2.8 用户后台模块:  4.3 后端管理:  4.3.1 教练管理:  4.3.2 考试通知:  4.3.3 报名管理:  4.3.4 车辆信息:  4.3.5 系统公告等  五、代码实现:     /**  * 登录相关  */ @RequestMapping("users") @RestController public class UserController{          @Autowired     private UserService userService;          @Autowired     private TokenService tokenService;       /**      * 登录      */     @IgnoreAuth     @PostMapping(value = "/login")     public R login(String username, String password, String captcha, HttpServletRequest request) {         UserEntity user = userService.selectOne(new EntityWrapper<UserEntity>().eq("username", username));         if(user==null || !user.getPassword().equals(password)) {             return R.error("账号或密码不正确");         }         String token = tokenService.generateToken(user.getId(),username, "users", user.getRole());         return R.ok().put("token", token);     }          /**      * 注册      */     @IgnoreAuth     @PostMapping(value = "/register")     public R register(@RequestBody UserEntity user){ //        ValidatorUtils.validateEntity(user);         if(userService.selectOne(new EntityWrapper<UserEntity>().eq("username", user.getUsername())) !=null) {             return R.error("用户已存在");         }         userService.insert(user);         return R.ok();     }       /**      * 退出      */     @GetMapping(value = "logout")     public R logout(HttpServletRequest request) {         request.getSession().invalidate();         return R.ok("退出成功");     }          /**      * 密码重置      */     @IgnoreAuth     @RequestMapping(value = "/resetPass")     public R resetPass(String username, HttpServletRequest request){         UserEntity user = userService.selectOne(new EntityWrapper<UserEntity>().eq("username", username));         if(user==null) {             return R.error("账号不存在");         }         user.setPassword("123456");         userService.update(user,null);         return R.ok("密码已重置为:123456");     }          /**      * 列表      */     @RequestMapping("/page")     public R page(@RequestParam Map<String, Object> params,UserEntity user){         EntityWrapper<UserEntity> ew = new EntityWrapper<UserEntity>();         PageUtils page = userService.queryPage(params, MPUtil.sort(MPUtil.between(MPUtil.allLike(ew, user), params), params));         return R.ok().put("data", page);     }       /**      * 列表      */     @RequestMapping("/list")     public R list( UserEntity user){            EntityWrapper<UserEntity> ew = new EntityWrapper<UserEntity>();           ew.allEq(MPUtil.allEQMapPre( user, "user"));          return R.ok().put("data", userService.selectListView(ew));     }       /**      * 信息      */     @RequestMapping("/info/{id}")     public R info(@PathVariable("id") String id){         UserEntity user = userService.selectById(id);         return R.ok().put("data", user);     }          /**      * 获取用户的session用户信息      */     @RequestMapping("/session")     public R getCurrUser(HttpServletRequest request){         Long id = (Long)request.getSession().getAttribute("userId");         UserEntity user = userService.selectById(id);         return R.ok().put("data", user);     }       /**      * 保存      */     @PostMapping("/save")     public R save(@RequestBody UserEntity user){ //        ValidatorUtils.validateEntity(user);         if(userService.selectOne(new EntityWrapper<UserEntity>().eq("username", user.getUsername())) !=null) {             return R.error("用户已存在");         }         userService.insert(user);         return R.ok();     }       /**      * 修改      */     @RequestMapping("/update")     public R update(@RequestBody UserEntity user){ //        ValidatorUtils.validateEntity(user);         UserEntity u = userService.selectOne(new EntityWrapper<UserEntity>().eq("username", user.getUsername()));         if(u!=null && u.getId()!=user.getId() && u.getUsername().equals(user.getUsername())) {             return R.error("用户名已存在。");         }         userService.updateById(user);//全部更新         return R.ok();     }       /**      * 删除      */     @RequestMapping("/delete")     public R delete(@RequestBody Long[] ids){         userService.deleteBatchIds(Arrays.asList(ids));         return R.ok();     } }      @Configuration public class InterceptorConfig extends WebMvcConfigurationSupport{          @Bean     public AuthorizationInterceptor getAuthorizationInterceptor() {         return new AuthorizationInterceptor();     }          @Override     public void addInterceptors(InterceptorRegistry registry) {         registry.addInterceptor(getAuthorizationInterceptor()).addPathPatterns("/**").excludePathPatterns("/static/**");         super.addInterceptors(registry);     }          /**      * springboot 2.0配置WebMvcConfigurationSupport之后,会导致默认配置被覆盖,要访问静态资源需要重写addResourceHandlers方法      */     @Override     public void addResourceHandlers(ResourceHandlerRegistry registry) {         registry.addResourceHandler("/**")         .addResourceLocations("classpath:/resources/")         .addResourceLocations("classpath:/static/")         .addResourceLocations("classpath:/admin/")         .addResourceLocations("classpath:/front/")           .addResourceLocations("classpath:/public/");         registry.addResourceHandler("/upload/**").addResourceLocations("file:D:/work/");           super.addResourceHandlers(registry);     } }  ———————————————— 版权声明:本文为CSDN博主「java李杨勇」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_39709134/article/details/128781905 
  • [技术干货] Object.assign详解
    一、Object.assign是什么?       首先了解下Object.assign()是什么。我们先看看ES6官方文档是怎么介绍的?       Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。       简单来说,就是Object.assign()是对象的静态方法,可以用来复制对象的可枚举属性到目标对象,利用这个特性可以实现对象属性的合并。 二、用法:       Object.assign(target, ...sources)   参数:target--->目标对象       source--->源对象   返回值:target,即目标对象 三、使用示例: 1、目标对象和源对象无重名属性 var target={name:'guxin',age:18}; var source={state:'single'} var result=Object.assign(target,source); console.log(target,target==result); 我们可以看到source上的state属性合并到了target对象上。如果只是想将两个或多个对象的属性合并到一起,不改变原有对象的属性,可以用一个空的对象作为target对象。像下面这样: var result=Object.assign({},target,source); 2、目标对象和源对象有重名属性 上面的示例目标对象和源对象是没有重名属性的,那么如果他们有重名属性又会怎样呢?是后面的属性覆盖前面的还是前面的属性覆盖后面的呢?我们接下来看下一个例子: var target={name:'guxin',age:18} var source={state:'signle',age:22} var result=Object.assign(target,source) console.log(target)  可以看到如果有同名属性的话,后面的属性值会覆盖前面的属性值。 3、有多个源对象 前面的示例都是只有一个源对象,那么如果有多个源对象情况会不会不同呢?我们继续看下面的例子:         var target={name:'guxin',age:18}         var source1={state:'signle',age:22}         var source2={mood:'happy',age:25}         var result=Object.assign(target,source1,source2)         console.log(target) 可以看到有多个源对象情况也是和一个源对象一样的。没有同名的属性会直接复制到目标对象上,同名的属性后面的属性值会覆盖前面的同名属性值。  四、注意事项: 1、Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象,继承属性和不可枚举属性是不能拷贝的。 2、针对深拷贝,需要使用其他办法,因为 Object.assign()拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用。 3、目标对象自身也会改变 4、异常会打断后续拷贝任务  五、兼容性 目前IE浏览器不兼容Object.assign(),如果需要兼容IE的话最好不要直接使用这个方法。  六、与$.extend()的比较 我们通过一个简单的示例来比较两者有什么不同,          var target={name:'guxin',age:18}         var source1={state:'signle',age:22}         var source2={mood:'happy',age:25}         var result=Object.assign(target,source1,source2)         console.log(target,'assign')         var targetObj={name:'guxin',age:18}         var sourceObj1={state:'signle',age:22}         var sourceObj2={mood:'happy',age:25}         var result=$.extend(targetObj,sourceObj1,sourceObj2)         console.log(targetObj,'extend') 可以看到两者得到的结果是一样的。所以,我认为这两个方法,除了兼容性应该是一样的。 ———————————————— 原文链接:https://blog.csdn.net/guxin_duyin/article/details/88916106 
总条数:85 到第
上滑加载中