首页 文章 精选 留言 我的

精选列表

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

JavaScript事件机制——记一次认真准备的技术分享

先问几个问题,你是否能快速闪过答案? 自下而上(冒泡)事件怎么写,自上而下(捕获)又是怎么写? 捕获型和冒泡型同时存在,谁生效? jquery的on或bind是冒泡,还是捕获? 冒泡能够阻止,那捕获能够阻止吗? stopPropagation 和 stopImmediatePropagation的区别 Event.bubbles,Event.eventPhase Event.cancelable,Event.cancelBubble,event.defaultPrevented 常用技巧 js事件的捕获和冒泡图 举个例子: 点击s2,s1分别会打印什么? <div id="s1">s1 <div id="s2">s2</div> </div> <script> s1.addEventListener("click",function(e){ console.log("s1 冒泡事件"); },false); s2.addEventListener("click",function(e){ console.log("s2 冒泡事件");},false); s1.addEventListener("click",function(e){ console.log("s1 捕获事件");},true); s2.addEventListener("click",function(e){ console.log("s2 捕获事件");},true); </script> //s1 捕获事件 //s2 冒泡事件 //s2 捕获事件 //s1 冒泡事件 点击s2,click事件从document->html->body->s1->s2(捕获前进)这里在s1上发现了捕获注册事件,则输出"s1 捕获事件"到达s2,已经到达目的节点。 s2上注册了冒泡和捕获事件,先注册的冒泡后注册的捕获,则先执行冒泡,输出"s2 冒泡事件" 再在s2上执行后注册的事件,即捕获事件,输出"s2 捕获事件" 下面进入冒泡阶段,按照s2->s1->body->html->documen(冒泡前进) 在s1上发现了冒泡事件,则输出"s1 冒泡事件" jQuery的on事件是冒泡 下面贴上jquery的on事件的源码 常用技巧 onclick -->事件冒泡,重写onlick会覆盖之前属性,没有兼容性问题 ele.onclik = null; //解绑单击事件,将onlick属性设为null即可 复制代码 阻止默认事件(href=""链接,submit表单提交等) return false; 阻止独享属性(通过on这种方式)绑定的事件的默认事件 ele.onclick = function() { …… //你的代码 return false; //通过返回false值阻止默认事件行为 } 复制代码 但是,在jQuery中,我们常用return false来阻止浏览器的默认行为,那”return false“到底做了什么?当你每次调用”return false“的时候,它实际上做了3件事情: event.preventDefault(); event.stopPropagation(); 停止回调函数执行并立即返回。 这3件事中用来阻止浏览器继续执行默认行为的只有preventDefault,除非你想要停止事件冒泡,否则使用return false会为你的代码埋下很大的隐患。 下面贴上jQuery的源码 event.preventDefault( ); 阻止通过 addEventListener( ) 添加的事件的默认事件 element.addEventListener(“click”, function(e){ var event = e || window.event; …… event.preventDefault( ); //阻止默认事件 },false); event.returnValue = false; 阻止通过 attachEvent( ) 添加的事件的默认事件(此事件为ie浏览器特有) element.attachEvent(“onclick”, function(e){ var event = e || window.event; …… event.returnValue = false; //阻止默认事件 },false); 把事件绑定以及事件解绑封装成为一个函数,兼容浏览器,包括IE6及以上(虽然现在基本上都放弃了IE9以下了hhhh) // 事件绑定 function addEvent(element, eType, handle, bol) { if(element.addEventListener){ //如果支持addEventListener element.addEventListener(eType, handle, bol); }else if(element.attachEvent){ //如果支持attachEvent element.attachEvent("on"+eType, handle); }else{ //否则使用兼容的onclick绑定 element["on"+eType] = handle; } } // 事件解绑 function removeEvent(element, eType, handle, bol) { if(element.addEventListener){ element.removeEventListener(eType, handle, bol); }else if(element.attachEvent){ element.detachEvent("on"+eType, handle); }else{ element["on"+eType] = null; } } 事件停止传播 stopPropagation 和 stopImmediatePropagation // 事件传播到 element 元素后,就不再向下传播了 element.addEventListener('click', function (event) { event.stopPropagation(); }, true); // 事件冒泡到 element 元素后,就不再向上冒泡了 element.addEventListener('click', function (event) { event.stopPropagation(); }, false); 但是,stopPropagation方法只会阻止 【该元素的当前事件(冒泡或者捕获)】 的传播,不会阻止该节点的其他click事件的监听函数。也就是说,不是彻底取消click事件,它还可以正常创建一个新的click事件。 element.addEventListener('click', function (event) { event.stopPropagation(); console.log(1); }); element.addEventListener('click', function(event) { // 会触发 console.log(2); }); 如果想要彻底阻止这个事件的传播,不再触发后面所有click的监听函数,可以使用stopImmediatePropagation方法。 注意:是针对该事件,比如你在click里写了这个方法,那【使用该方法之后】的该元素上绑定的方法将失效,但是别的mousedown,mouseover方法等还是生效的。亲测过~ element.addEventListener('click', function (event) { // 会触发 console.log(‘改方法内的可以执行’); event.stopImmediatePropagation(); // 会触发 console.log(1); }); element.addEventListener('click', function(event) { // 不会被触发 console.log(2); }); // Jquery同理 $(element).click(function() { // 不会触发 console.log(‘jquery click’) }) $(element).hover(function() { // 会触发 console.log(‘jquery click’) }) Event.bubbles,Event.eventPhase Event.bubbles属性返回一个布尔值,表示当前事件是否会冒泡。该属性为只读属性,一般用来了解 Event 实例是否可以冒泡。前面说过,除非显式声明,Event构造函数生成的事件,默认是不冒泡的。可以根据下面的代码来判断事件是否冒泡,从而执行不同的函数。 function goInput(e) { if (!e.bubbles) { passItOn(e); } else { doOutput(e); } } 专门查了一下不支持冒泡的事件有: UI事件(load, unload, scroll, resize) 焦点事件(blur, focus) 鼠标事件(mouseleave, mouseenter) Event.eventPhase属性返回一个整数常量,表示事件目前所处的阶段。该属性只读。 var phase = event.eventPhase; 复制代码 Event.eventPhase的返回值有四种可能。 0,事件目前没有发生。 1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中。 2,事件到达目标节点,即Event.target属性指向的那个节点。 3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。 Event.cancelable,Event.cancelBubble,event.defaultPrevented Event.cancelable属性返回一个布尔值,表示事件是否可以取消。该属性为只读属性,一般用来了解 Event 实例的特性。 大多数浏览器的原生事件是可以取消的。比如,取消click事件,点击链接将无效。但是除非显式声明,Event构造函数生成的事件,默认是不可以取消的。 var evt = new Event('foo'); evt.cancelable // false 复制代码 当Event.cancelable属性为true时,调用Event.preventDefault()就可以取消这个事件,阻止浏览器对该事件的默认行为。 注意,该方法只是取消事件对当前元素的默认影响,不会阻止事件的传播。如果要阻止传播,可以使用stopPropagation()或stopImmediatePropagation()方法。 function preventEvent(event) { if (event.cancelable) { event.preventDefault(); } else { console.warn('This event couldn\'t be canceled.'); console.dir(event); } } Event.cancelBubble属性是一个布尔值,该属性可以自行设置。如果设为true,相当于执行Event.stopPropagation(),可以阻止事件的传播。 注意:MDN里说该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。请使用 event.stopPropagation() 方法来代替该不标准的属性.cancelBubble-MDN Event.defaultPrevented属性返回一个布尔值,表示该事件是否调用过Event.preventDefault方法。该属性只读。 if (event.defaultPrevented) { console.log('该事件已经取消了'); } 主要参考文档: 详解JS中的事件机制(带实例) 事件模型 原文发布时间为:2018年06月20日 原文作者:闲淡超人 本文来源: 掘金 如需转载请联系原作者

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

道格拉斯-普克 抽稀算法 附javascript实现

道格拉斯-普克抽稀算法,是用来对大量冗余的图形数据点进行压缩以提取必要的数据点。该算法实现抽稀的过程是:先将一条曲线首尾点虚连一条直线,求其余各点到该直线的距离,取其最大者与规定的临界值相比较,若小于临界值,则将直线两端间各点全部舍去,否则将离该直线距离最大的点保留,并将原线条分成两部分,对每部分线条再实施该抽稀过程,直到结束。抽稀结果点数随选取限差临界值的增大而减少,应用时应根据精度来选取限差临界值,以获得最好的效果。 以下转载自:垂距法与道格拉斯-普克法删除冗余顶点效率的比较 道格拉斯- 普克法可描述为:将一条曲线首末顶点虚连一条直线 ,求出其余各顶点到该直线的距离 ,选其最大者与规定的限差相比较 ,若小于等于限差 ,则将直线两端间各点全部删去;若大于限差 ,则离该直线距离最大的顶点保留 ,并以此为界 ,把曲线分为两部分 ,对这两

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

深入理解javascript系列(十九):从Promise开始到async/await

什么是同步与异步的定义,在这里我就不做记录,直接用代码来表示它们之间的区别。 首先使用Promise模拟一个发起请求的函数,该函数执行后,会在1s之后返回数值30。 function fn() { return new Promise(function(resolve, reject) { setTimeout(function() { resolve(30); }, 1000); }) 在该函数的基础上,我们也可以使用async/await语法来模拟同步效果。 var foo = async function() { var t = await fn(); console.log(t); console.log('next'); } foo(); 输出结果为: Promise {<pending>} //1s 之后依次输出 test:11 30 test:12 next 而异步效果则会有不同的输出结果: var foo = function() { fn().then(function(res) { console.log(res); }); console.log('next'); } 输出结果: next // 1s后 30 好了,接下来我们正式开始记录Promise Promise 1. Ajax Ajax是网页与服务端进行数据交互的一种技术。我们可以通过服务端提供的接口,用Ajax向服务端请求我们需要的数据。过程如下: //简单的Ajax原生实现 //服务端接口 var url = 'api/xxxx'; var result; var XHR = new XMLHttpRequest(); XHR.open('GET', url, true); XHR.send(); XHR.onreadystatechange = function() { if(XHR.readyState == 4 && XHR.status == 200) { result = XHR.response; } } 这样看上去并没有什么问题。但是如果这个时候,还需要做另一个Ajax请求,那么这个新的Ajax请求中的一个参数,则必须从上一个Ajax请求中获取,这个时候我们就不得不就得在result得到后在进行一次请求。 当第三个Ajax(甚至更多)仍然依赖上一个请求的时候,此时的代码就变成了一场灾难。我们需要不停地嵌套回调函数,以确保下一个接口所需要的参数的正确性,这样的灾难,我们称为回调地狱。 所以随着发展,就出现了Promise,他能解决这个问题。 我们想要确保某代码在某某之后执行时,可以利用函数调用栈,将想要执行的代码放入回调函数中(这是利用同步阻塞)。 function a(callback) { console.log('先结婚') callback(); } function b() { console.log('再生孩子') } a(b); 插个题外话:“浏览器最早内置的setTimeout与setInterval就是基于回调的思想实现的”。 但是这里也有一个问题,我们想要在a中执行的代码必须现在callback之前才能输出我们想输出的。那该怎么办? 其实问题很好解决,除了利用函数调用栈的执行顺序外,还可以利用队列机制来确保我们想要的代码压后执行。 function a(callback) { //将想要执行的代码放入队列中后,根据事件循环机制, //就不用把它放到最后面了。 callback && setTimeout(callback, 0); console.log('先结婚') } function b() { console.log('再生孩子') } a(b); 与setTimeout类似,Promise也可以认为是一种任务分发器,它将任务分配到Promise队列中,通常的流程是首先发起一个请求,然后等待(等待时间没法确定)并处理请求结果。 var tag = true; var p = new Promise(function(resolve, reject) { if(tag) { resolve('tag is true') } else { reject('tag is false') } }) p.then(function(result) { console.log(result); }) .catch(function(err) { console.log(err); }) 下面简单介绍一下Promise的相关基础知识: new Promise表示创建一个Promise实例对象。 Promise函数中的第一参数为一个回调函数,也可以称之为executor。通常情况下,在这个函数中,会执行发起请求操作,并修改结果的状态值。 请求结果有三种状态,分别是pending(等待中,表示还没有得到结果)、resolved(得到了我们想要的结果,可以继续执行),以及rejected(得到了错误的,或者不是我们期望的结果,拒绝继续执行)。请求结果的默认状态为pending。在executor函数中,可以分别使用resolve与rejected将状态修改为对应的resolved与rejected。resolve、reject是executor函数的两个参数,它们能够将请求结果的具体数据传递出去。 Promise实例拥有的then方法,可以用来处理当请求结果的状态变成resolved时的逻辑。then的第一个参数为一个回调函数,该函数的参数是resolve传递出来的数据。在上面的例子中,result = tag is true。 Promise实例拥有的catch方法,可用来处理当前请求结果的状态变成rejectd时的逻辑。catch的第一个参数为一个回调函数,该函数的参数是一个reject传递出来的数据。在上面的例子中,err = tag is false。 下面通过例子来感受一下Promise的用法。 //demo01.js function fn(num) { //创建一个Promise实例 return new Promise(function(resolve, reject) { if(typeof num == 'number') { //修改结果状态值为resolved resolve(); } else { // 修改结果状态值为rejected reject(); } }).then(function() { console.log('参数是一个number值'); }).catch(function() { console.log('参数不是一个number值'); }) } //修改参数的类型,观察输出的结果 fn('12'); //注意观察该语句的执行顺序 console.log('next code'); then方法可以接收两个参数,第一个参数用来处理resolved状态的逻辑,第二个参数用来处理rejected状态的逻辑。 then方法因为返回的仍是一个Promise实例对象,因此then方法可以嵌套使用。在这个过程中,通过在内部函数末尾return的方式,能够将数据持续往后传递。 下面我们来对Ajax进行一个简单的封装。 var url = 'api/xxxx'; //封装一个get请求的方法 function getJSON(url) { return new Promise(function(resolve, reject) { //利用Ajax发送一个请求 var XHR = new XMLHttpRequest(); XHR.open('GET', url, true); XHR.send(); //等待结果 XHR.onreadystatechange = function() { if(XHR.readyState == 4) { if(XHR.status == 200) { try { var res = JSON.parse(XHR.responseText); // 得到正确的结果修改状态并将数据传递出去 resolve(response); } catch(e) { reject(e) } } else { // 得到错误的结果并抛出异常 reject(new Error(XHR.statusText)); } } } }) } //封装好以后,使用就很简单了 getJSON(url).then(function(res){ console.log(res) }) 2. Promise.all 当有一个Ajax请求,它的参数需要另外两个甚至更多个请求都有返回结果之后才能确定时,就需要用到Promise.all来帮助我们应对这个场景。 Promise.all接收一个Promise对象组成的数组作为参数,当这个数组中所有的Promise对象状态都变成resolved或者rejected时,它才会去调用then方法。 var url1 = 'xxx'; var url2 = 'xxxxx'; function renderAll() { return Promise.all([getJSON(url1), getJSON(url2)]); } renderAll().then(function(value) { console.log(value); }) 3. Promise.race 与Promise.all相似的是,Promise.race也是一个Promise对象组成的数组作为参数,不同的是,只要当数组中的其中一个Promise状态变成了resolved或者rejected时,就可以调用then方法。 async/await 异步问题不仅可以用Promise,还可以用async/await,都说这是终极解决方案。 async/await是ES7中新增的语法,虽然现在有些浏览器已经支持了该语法,但在实际使用中,仍然需要在构建工具中配置对该语法的支持才能放心使用。 在函数声明的前面,加上关键字async,这就是async的具体使用。 async function fn() { return 30; } //或者 const fn = async ()=> { return 30; } console.log(fn()); //打印结果 Promise {<resolved>: 30}__proto__:Promise[[PromiseStatus]]:"resolved"[[PromiseValue]]:30 可以发现打印结果是一个Promise对象,因此可以猜到async其实是Promise的一个语法糖,目的是为了让写法更加简单,因此也可以使用Promise的相关语法来处理后续的逻辑。 fn().then(res=>{ console.log(res); }) await的含义是等待,意思就代码需要等待await后面的函数运行完并且有了返回结果之后,才继续执行下面的代码。这正是同步的效果。 但是需要注意的是,await关键字只能在async函数中使用,并且await后面的函数运行后必须返回一个Promise对象才能实现同步的效果。 当使用一个变量去接收await的返回值时,该返回值为Promise中resolve传递出来的值,也就是PromiseValue。 为了切实感受下async/await的用法。我们结合实际开发中最常遇到的异步请求接口的场景。 //先定义接口请求的方法,由于jQuery封装的几个请求方法都是返回Promise实例。 //因此可以直接使用async/await函数实现同步 const getUserInfo = () => $.get('api/asdsd'); const clickHandler = async ()=>{ try{ const res = await getUserInfo(); console.log(res); // do something } catch(e){ //处理错误逻辑 } 为了保证逻辑的完整性,在实践中try/catch必不可少。 原文发布时间为:2018年06月21日原文作者:Panthon 本文来源:掘金如需转载请联系原作者

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

低门槛彻底理解JavaScript中的深拷贝和浅拷贝

作者:吴胜斌 https://www.simbawu.com/article/search/9 在说深拷贝与浅拷贝前,我们先看两个简单的案例: //案例1var num1 = 1, num2 = num1;console.log(num1) //1console.log(num2) //1 num2 = 2; //修改num2console.log(num1) //1console.log(num2) //2//案例2var obj1 = {x: 1, y: 2}, obj2 = obj1;console.log(obj1) //{x: 1, y: 2}console.log(obj2) //{x: 1, y: 2} obj2.x = 2; //修改obj2.xconsole.log(obj1) //{x: 2, y: 2}console.log(obj2) //{x: 2, y: 2} 按照常规思维,obj1应该和num1一样,不会因为另外一个值的改变而改变,而这里的obj1却随着obj2的改变而改变了。同样是变量,为什么表现不一样呢?这就要引入JS中基本类型和引用类型的概念了。 基本类型和引用类型 ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。而引用类型值是指那些保存堆内存中的对象,意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。 打个比方,基本类型和引用类型在赋值上的区别可以按“连锁店”和“单店”来理解:基本类型赋值等于在一个新的地方安装连锁店的规范标准新开一个分店,新开的店与其他旧店互不相关,各自运营;而引用类型赋值相当于一个店有两把钥匙,交给两个老板同时管理,两个老板的行为都有可能对一间店的运营造成影响。 上面清晰明了的介绍了基本类型和引用类型的定义和区别。目前基本类型有: Boolean、Null、Undefined、Number、String、Symbol,引用类型有:Object、Array、Function。之所以说“目前”,因为Symbol就是ES6才出来的,之后也可能会有新的类型出来。 再回到前面的案例,案例1中的值为基本类型,案例2中的值为引用类型。案例2中的赋值就是典型的浅拷贝,并且深拷贝与浅拷贝的概念只存在于引用类型。 深拷贝与浅拷贝 既然已经知道了深拷贝与浅拷贝的来由,那么该如何实现深拷贝?我们先分别看看Array和Object自有方法是否支持: Array var arr1 = [1, 2], arr2 = arr1.slice();console.log(arr1); //[1, 2]console.log(arr2); //[1, 2] arr2[0] = 3; //修改arr2console.log(arr1); //[1, 2]console.log(arr2); //[3, 2] 此时,arr2的修改并没有影响到arr1,看来深拷贝的实现并没有那么难嘛。我们把arr1改成二维数组再来看看: var arr1 = [1, 2, [3, 4]], arr2 = arr1.slice(); console.log(arr1); //[1, 2, [3, 4]] console.log(arr2); //[1, 2, [3, 4]] arr2[2][1] = 5; console.log(arr1); //[1, 2, [3, 5]] console.log(arr2); //[1, 2, [3, 5]] 咦,arr2又改变了arr1,看来slice()只能实现一维数组的深拷贝。 具备同等特性的还有:concat、Array.from()。 Object 1、Object.assign() var obj1 = {x: 1, y: 2}, obj2 = Object.assign({}, obj1);console.log(obj1) //{x: 1, y: 2}console.log(obj2) //{x: 1, y: 2} obj2.x = 2; //修改obj2.xconsole.log(obj1) //{x: 1, y: 2}console.log(obj2) //{x: 2, y: 2}var obj1 = { x: 1, y: { m: 1 } };var obj2 = Object.assign({}, obj1);console.log(obj1) //{x: 1, y: {m: 1}}console.log(obj2) //{x: 1, y: {m: 1}} obj2.y.m = 2; //修改obj2.y.mconsole.log(obj1) //{x: 1, y: {m: 2}}console.log(obj2) //{x: 2, y: {m: 2}} 经测试,Object.assign()也只能实现一维对象的深拷贝。 2、JSON.parse(JSON.stringify(obj)) var obj1 = { x: 1, y: { m: 1 } };var obj2 = JSON.parse(JSON.stringify(obj1));console.log(obj1) //{x: 1, y: {m: 1}}console.log(obj2) //{x: 1, y: {m: 1}} obj2.y.m = 2; //修改obj2.y.mconsole.log(obj1) //{x: 1, y: {m: 1}}console.log(obj2) //{x: 2, y: {m: 2}} JSON.parse(JSON.stringify(obj))看起来很不错,不过MDN文档的描述有句话写的很清楚: undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成null(出现在数组中时)。 我们再来把obj1改造下: var obj1 = { x: 1, y: undefined, z: function add(z1, z2) { return z1 + z2 }, a: Symbol("foo") };var obj2 = JSON.parse(JSON.stringify(obj1));console.log(obj1) //{x: 1, y: undefined, z: ƒ, a: Symbol(foo)}console.log(JSON.stringify(obj1)); //{"x":1}console.log(obj2) //{x: 1} 发现,在将obj1进行JSON.stringify()序列化的过程中,y、z、a都被忽略了,也就验证了MDN文档的描述。既然这样,那JSON.parse(JSON.stringify(obj))的使用也是有局限性的,不能深拷贝含有undefined、function、symbol值的对象,不过JSON.parse(JSON.stringify(obj))简单粗暴,已经满足90%的使用场景了。 经过验证,我们发现JS 提供的自有方法并不能彻底解决Array、Object的深拷贝问题。只能祭出大杀器:递归 function deepCopy(obj) { // 创建一个新对象 let result = {} let keys = Object.keys(obj), key = null, temp = null; for (let i = 0; i < keys.length; i++) { key = keys[i]; temp = obj[key]; // 如果字段的值也是一个对象则递归操作 if (temp && typeof temp === 'object') { result[key] = deepCopy(temp); } else { // 否则直接赋值给新对象 result[key] = temp; } } return result; }var obj1 = { x: { m: 1 }, y: undefined, z: function add(z1, z2) { return z1 + z2 }, a: Symbol("foo") };var obj2 = deepCopy(obj1); obj2.x.m = 2;console.log(obj1); //{x: {m: 1}, y: undefined, z: ƒ, a: Symbol(foo)}console.log(obj2); //{x: {m: 2}, y: undefined, z: ƒ, a: Symbol(foo)} 可以看到,递归完美的解决了前面遗留的所有问题,我们也可以用第三方库:jquery的$.extend和lodash的_.cloneDeep来解决深拷贝。上面虽然是用Object验证,但对于Array也同样适用,因为Array也是特殊的Object。 到这里,深拷贝问题基本可以告一段落了。但是,还有一个非常特殊的场景: 循环引用拷贝 var obj1 = { x: 1, y: 2 }; obj1.z = obj1;var obj2 = deepCopy(obj1); 此时如果调用刚才的deepCopy函数的话,会陷入一个循环的递归过程,从而导致爆栈。jquery的$.extend也没有解决。解决这个问题也非常简单,只需要判断一个对象的字段是否引用了这个对象或这个对象的任意父级即可,修改一下代码: function deepCopy(obj, parent = null) { // 创建一个新对象 let result = {}; let keys = Object.keys(obj), key = null, temp= null, _parent = parent; // 该字段有父级则需要追溯该字段的父级 while (_parent) { // 如果该字段引用了它的父级则为循环引用 if (_parent.originalParent === obj) { // 循环引用直接返回同级的新对象 return _parent.currentParent; } _parent = _parent.parent; } for (let i = 0; i < keys.length; i++) { key = keys[i]; temp= obj[key]; // 如果字段的值也是一个对象 if (temp && typeof temp=== 'object') { // 递归执行深拷贝 将同级的待拷贝对象与新对象传递给 parent 方便追溯循环引用 result[key] = DeepCopy(temp, { originalParent: obj, currentParent: result, parent: parent }); } else { result[key] = temp; } } return result; }var obj1 = { x: 1, y: 2 }; obj1.z = obj1;var obj2 = deepCopy(obj1);console.log(obj1); //太长了去浏览器试一下吧~ console.log(obj2); //太长了去浏览器试一下吧~ 至此,已完成一个支持循环引用的深拷贝函数。当然,也可以使用lodash的_.cloneDeep噢~。 原文发布时间:2018年05月10日 16:12:21 原文作者:吴胜斌 本文来源CSDN,如需转载请联系原作者

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

javascript知识点总结(二)之 DOM和BOM对象模型

1,字符串创建方式:1,var str = ‘hello word’;字面量方式创建 2,var str = new String(‘hello word’);构造函数方式创建 字符串常见方法: charAt :根据下标获取字符。str.charAt(0) = 'h' indexOf:返回字符串在str中第一次出现的位置,找不到就返回-1;第二个参数,从那个位置开始搜索,默认为0 substring:获取字符串中的子串,第一个参数是开始位置,最后一个是结束位置,默认到尾。 slice:获取字符串中的子串,第一个参数是开始位置,最后一个是结束位置,默认到尾。 substr:截取子串,第一个参数是开始位置,最后一个是结束位置,默认到尾。 split:分隔字符串,使用分隔符分隔子串,会返回一个数组,使用空字符串分隔,会将每个字符串分隔为数组。 replace:传两个参数,第二个参数替换第一个参数,只替换第一个。 字符串常见API: charCodeAt:获取键盘某个位置上的编码 fromCharCode :可以把编码转化成字符 2,常见的Math函数: Math.rondom:随机数[0,1);Math.round:四舍五入;Math.pow(2,3):2的3次方;Math.sqrt():开平方; Math.floor():向下取整;Math.ceil():向上取整;Math.max:最大值;Math.min:最小值;Math.abs:绝对值; 例:随机出n-m之间的数字。Math.ceil(Math.random()*(m-n))+n 3,日期函数: var oData = new Date() //oDate.getDate() 返回一个月中的某一天(1~31) //oDate.getDay() 返回一周中的某一天(0~6) //oDate.getMonth() 返回月份(0~11) //oDate.getFullYear() 以四位数字返回年份 // //oDate.getHours() 返回当前小时(0-23) //oDate.getMinutes() 返回当前分钟 (0 ~ 59) //oDate.getSeconds() 返回当前秒(0 ~ 59) //oDate.getMillisenconds() 返回当前毫秒(0 ~ 999) // //oDate.getTime() 返回1970年1月1日至今的毫秒数 // //oDate.setDate() 设置月中的某一天 (1 ~ 31) // oDate.setMonth() 设置月份 (0 ~ 11) // 例:oDate.setMonth(9); alert(oDate.getMonth()) //oDate.setFullYear()设置年份(四位数) //oDate.setHours() 设置小时(0-23) //oDate.setMinutes() 设置分钟 (0 ~ 59) //oDate.setSeconds() 设置秒(0 ~ 59) //oDate.setMillisenconds() 设置毫秒(0 ~ 999) //oDate.setTime() 设置1970年1月1日至今的毫秒数 判断两个日期相差的天数 //判断两个时间相差的天数 function dateDays(startyear,startmonth,startday,endyear,endmonth,endday){ var start = new Date(startyear,startmonth,startday) var end = new Date(endyear,endmonth,endday) var day = start.getTime()-end.getTime() day = Math.abs(parseInt(day/1000/60/60/24)) } dateDays(2018,2,2,2018,2,10) 4,BOM:浏览器对象模型,提供了页面与浏览器进行交互的对象,核心对象是window 页面跳转:1,location.assign(url) ;url:目标页面; 2,location.replace(url); 3,location.href = “url” 刷新页面:location.reload(); 打开新页面:window.open 关闭页面:window.close() 页面弹出框:alert() prompt() 弹出文本框,会有返回值 confirm() 会返回布尔值,确认为true,取消为false 5,history对象,保存用户上网的历史记录 后退 history.go(-1) = history.back() 前进一页 history.go(1) = history.forward() 前进两页 history.go(2) 6,scrollTop:可视区域距离页面顶部的距离 作用:回顶部,监控页面滚动事件 兼容处理:scrollTop = document.document.Elemrnt.srcollTop ||document.bady.scrollTop scrollLeft:可视区域距离页面左边的距离。 clientWidth:可视区域的宽度。 clientHeight:可视区域的高度。 7,定时器,每隔一段实践执行一次 ;setInterval创建一个定时器,清除定时器 clearInterval 延时器,指定时间间隔后执行一次;setTimeout创建一个延时器,请除延时器 clearTimeout 一般代码按顺序执行称之为同步执行,定时器和延时器则称之为异步执行。 8,Dom:文档对象模型,dom文档包含页面元素 ,dom对象,dom节点类型 通过id查询 var div = document.getElementById('div') 通过class名查询 var li = document.getElementsByClassName('list') 返回伪数组,有数组的length属性还可以通过下标拿到第几个值 例如:document.getElementsByClassName('list')[0] 通过标签名查询 var input = document.getElementsByClassName('input') 同样返回伪数组。 新增查询方法 var res = document.querySelectorAll('#id') document.querySelector('.box') 返回第一个查询到的dom对象 9,dom元素的删除 1,直接删除 :div.remove() 2,通过父元素删除子节点 ul.removeChild(li) 10,dom元素的创建 例如:创建一个div var div = document.createElement('div') 把创建的div添加到body中,body.appendChild('div') 11,don元素的替换 con.replaceChild(新节点,旧节点) 12,dom获取非行内样式 非低ie: getComputedStyle(id).width 低ie:id.currentStyle 13,dom属性节点 attributes 获取所有属性节点 getAttribute() 获取一个属性(可以获取自定义属性(唯一)) setAttribute() 设置一个属性节点(可以设置自定义属性) removeAttribute() 删除一个属性节点 属性节点类型:元素,属性,节点 创建一个文本属性 document.createTextNode('你好') childrenNodes 获取子节点,包含空白节点 box.innerHtml 获取box的页面内容 box.innerText 获取文本,既包含标签也能显示字符串 box.outerHtml 使用后面的内容替换div firstChild/lastChild 获取第一个/最后一个元素,但会获取空节点 firstElementChild 不会获取空节点 lastElementChild 不会获取空节点 parentNode 获取父节点 previousSibling 上一个兄弟节点 nextSibling 下一个兄弟节点 加Element不会获取空白节点 box.insertBefore(newNode,existNode) 在已经存在的节点中插入新的节点 list.inserBefore(oli.list,firstElementChild) 在列表前面插入li 14,文档碎片,作用,加载更多时,创建一个文档碎片,把新加载的内容先添加到文档碎片内,在把文档碎片添加到页面中,这样向页面内新加东西时只用加一次就够了,可以减少页面渲染。 创建文档碎片:var df = createDocumentFagment() 把频繁插入list的内容先插入到文档碎片中 df.appendChild(oli) 最后把文档碎片插入到list里 list.appendChild(df) 15,事件 js可以处理的事件类型分为:鼠标事件,键盘事件,HTML事件 //鼠标事件 //onclick:用户单击鼠标按钮或按下回车键时触发 //ondblclick:当用户双击主鼠标按钮时触发 //onmousedown:当用户按下鼠标还未弹起时触发 //onmouseup:当用户释放鼠标按钮时触发 //onmouseover:当鼠标移到某个元素上方时触发 //onmouseout:当鼠标移出某个元素上方时触发 //onmousemove:当鼠标指针在元素上移动时触发 HTML 事件 //onload:当页面或者资源完全加载后在 window 上面触发,或当框架集加载完毕后在框架集上触发。 //onselect:当用户选择文本框(input 或 textarea)中的一个或多个字符触发 //onchange:当文本框(input 或 textarea)内容改变且失去焦点后触发 //onfocus:当页面或者元素获得焦点时在 window 及相关元素上面触发 //onblur:当页面或元素失去焦点时在 window 及相关元素上触发 //onsubmit:当用户点击提交按钮在<form>元素上触发 //onreset:当用户点击重置按钮在<form>元素上触发 //onresize:当窗口或框架的大小变化时在 window 或框架上触发 //onscroll:当用户滚动带滚动条的元素时触发 键盘事件keyup、keydown、keypress //onkeydown:当用户按下键盘上任意键触发,如果按住不放,会重复触发 //onkeypress:当用户按下键盘上的字符键触发,如果按住不放,会重复触发 //onkeyup:当用户释放键盘上的键触发 16,事件对象,当触发某个事件时,会产生一个事件对象,这个对象包含着所有与事件有关的信息 。包括导致事件的元素、事件的类型、以及其它与特定事件相关的信息。 event:通过事件绑定的执行函数是可以得到一个隐藏参数的 。说明,浏览器会自动分配一个参数,这个参数其实就是 event 对象。 event.button属性 //当前事件触发时哪个鼠标按键被点击 clientX、clientY属性 //鼠标在可视区X坐标和Y坐标,即距离左边框和上边框的距离 screenX、screenY属性 //鼠标在屏幕区X坐标和Y坐标,即距离左屏幕和上屏幕的距离 offsetX、offsetY属性 //鼠标相对于事件源的X坐标和Y坐标 pageX、pageY // 鼠标相对于文档的X坐标和Y坐标 原文发布时间:2018年03月28日 本文来源CSDN博客如需转载请紧急联系作者

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

JavaScript 中 AbortController 的中断请求与事件监听清理实战

最近在排查一个后台管理系统里偶发的内存泄漏问题时,发现几个页面在快速切换菜单后,仍然有未完成的 fetch 请求在持续占用资源,甚至触发了 setState 警告。这类场景其实很常见:用户点击某个按钮发起数据请求,还没等响应回来就跳走了,或者连续触发了多个相同请求,旧请求返回后覆盖了新数据。以前我们习惯用 axios 的 CancelToken 或者自己维护一个 flag 来判断是否忽略结果,但这只是“忽略”,请求本身并没有被真正取消,浏览器的网络连接依然存在。AbortController 提供了一种更标准的取消机制,不仅能中断 fetch,还能移除事件监听器,甚至配合自定义异步任务使用。这篇内容会从实际代码出发,梳理 AbortController 的基本用法、在请求竞态和组件卸载时的处理方式,以及如何结合事件监听器避免重复绑定带来的泄漏。

资源下载

更多资源
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应用均可从中受益。

Rocky Linux

Rocky Linux

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

Sublime Text

Sublime Text

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

用户登录
用户注册