从大学到如今,接触前端已经有几年了,感想方面,就是对于程序员而言,想要提升本身的技术水平和编写易于阅读和维护的代码,我以为不能天天都是平庸的写代码,更要去推敲,去摸索和优化代码,总结当中的技巧,积极听取别人的建议,这样本身的技术水平会提升的更快。那么今天,我在这里就分享一下关于javascript方面的写做的实用技巧和建议,这些技巧和建议是我日常在开发项目上会用到的,但愿能让你们学到知识,更但愿能起到一个交流意见的做用,也就是说你们有什么好的技巧或者建议,欢迎分享,或者以为个人想法存在什么问题,欢迎指出!javascript
[...new Set([2,"12",2,12,1,2,1,6,12,13,6])] //[2, "12", 12, 1, 6, 13] //es6的新特性
关于对象的深浅拷贝,我我的看法就是有一下几点:html
1.深拷贝和浅拷贝只针对像Object, Array这样的引用类型数据。前端
2.浅拷贝是对对象引用地址进行拷贝,并无开辟新的栈,也就是拷贝后的结果是两个对象指向同一个引用地址,修改其中一个对象的属性,则另外一个对象的属性也会改变。java
3.深拷贝则是开启一个新的栈,两个对象对应两个不一样的引用地址,修改一个对象的属性,不会改变另外一个对象的属性。node
var myInfo={name:'守候',sex:'男'};
var newInfo=myInfo;
newInfo.sex='女';
console.log(myInfo) //{name: "守候", sex: "女"}
假-深拷贝这个是本身随性命名的,你们看看就好,别当真!jquery
var myInfo={name:'守候',sex:'男'};
var newInfo=Object.assign({},myInfo)
newInfo.sex='女';
console.log(myInfo) //{name: "守候", sex: "男"} console.log(newInfo) //{name: "守候", sex: "女"}
真-深拷贝这个是本身随性命名的,你们看看就好,别当真!程序员
看着深浅拷贝,区别写法很简单,可是那个上面的深拷贝写法是有问题的。看下面案例es6
var arr=[{a:1,b:2},{a:3,b:4}] var newArr=Object.assign([],arr) //截断数组 newArr.length=1 console.log(newArr)//[{a:1,b:2}] console.log(arr)//[{a:1,b:2},{a:3,b:4}] //操做newArr,这里看着对arr没影响,实际上已经挖了一个坑,下面就跳进去 newArr[0].a=123 //修改newArr[0]这个对象,也是影响了arr[0]这个对象 console.log(arr[0])//{a: 123, b: 2}
为何会这样呢,由于Object.assign并非深拷贝,是披着深拷贝外衣的浅拷贝。最多也是Object.assign会课拷贝第一层的值,对于第一层的值都是深拷贝,而到第二层的时候就是 复制引用。相似的状况还有,slice方法和concat方法等。
要解决这个问题,就得本身封装方法!以下数组
//利用递归来实现深拷贝,若是对象属性的值是引用类型(Array,Object),那么对该属性进行深拷贝,直到遍历到属性的值是基本类型为止。 function deepClone(obj){ if(!obj&& typeof obj!== 'object'){ return; } var newObj= obj.constructor === Array ? [] : {}; for(var key in obj){ if(obj[key]){ if(obj[key] && typeof obj[key] === 'object'){ newObj[key] = obj[key].constructor === Array ? [] : {}; //递归 newObj[key] = deepClone(obj[key]); }else{ newObj[key] = obj[key]; } } } return newObj; } var arr=[{a:1,b:2},{a:3,b:4}] var newArr=deepClone(arr) console.log(arr[0])//{a:1,b:2} newArr[0].a=123 console.log(arr[0])//{a:1,b:2}
还有一个方法就是简单粗暴法,我如今在用的一个方法!原理很简单,就是先把对象转成字符串,再把字符串转成对象!也能实现一样效果浏览器
var newArr2=JSON.parse(JSON.stringify(arr)); console.log(arr[0])//{a:1,b:2} newArr2[0].a=123 console.log(arr[0])//{a:1,b:2}
上面所说的浅拷贝,真假深拷贝(本身随性命名的),这几种状况,在开发上都有可能要用到,至于要使用哪种方式,视状况而定!
一个简单的需求,好比想给ul下面的li加上点击事件,点击哪一个li,就显示那个li的innerHTML。这个貌似很简单!代码以下!
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <ul id="ul-test"> <li>0</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> </ul> </body> <script type="text/javascript"> var oUl=document.getElementById("ul-test"); var oLi=oUl.getElementsByTagName("li"); for(var i=0,len=oLi.length;i<len;i++){ oLi[i].addEventListener("click",function(){ alert(this.innerHTML) }) } </script> </html>
很简单,这样就实现了,实际上这里有坑,也待优化!
1.for循环,循环的是li,10个li就循环10次,绑定10次事件,100个就循环了100次,绑定100次事件!
2.若是li不是原本就在页面上的,是将来元素,是页面加载了,再经过js动态加载进来了,上面的写法是无效的,点击li是没有反应的!
因此就者须要用事件委托(即便不考虑上面的第二种状况,也是建议使用事件委托)!代码以下
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <ul id="ul-test"> <li>0</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> </ul> </body> <script type="text/javascript"> var oUl=document.getElementById("ul-test"); oUl.addEventListener("click",function(ev){ var ev=ev||window.event; var target=ev.target||ev.srcElement; //若是点击的最底层是li元素 if(target.tagName.toLowerCase()==='li'){ alert(target.innerHTML) } }) </script> </html>
这样写,即便是动态添加进来的li点击也有反应,还有一个就是ul只有一个,事件绑定在ul上,不管li多少个,都是添加一次事件!可是也是可能会有问题,若是li下面还有子元素,那么点击的时候,target可能不是li,而是鼠标点击那个位置的最底层元素!以下图,若是鼠标点击白色区域,那个target就是body元素,鼠标点击绿色区域target就是div元素,鼠标点击蓝色区域target就是ul,点击橙色就是li。
你们试想下这样一个函数--函数接受几个参数,可是这几个参数都不是必填的,函数该怎么处理?是否是下面这样
function personInfo(name,phone,card){ ... } //以上函数,能够任意传参数。好比我想传card等于1472586326。这下是否是这样写 personInfo('','','1472586326')
有没有以为上面写法奇怪,不太优雅?下面这里看着舒服一点!
function personInfo(opt){ ... } personInfo({card:'1472586326'})
再想一下,若是一个函数,参数不少,怎么处理?
function test(arg1,arg2,arg3,arg4,arg5,arg6,arg7){ ... }
密集恐惧症复发没有复发?下面这样看着会舒服一点!
function personInfo(opt){ ... }
最后再想一下,若是需求改了,操做函数也要改!函数也要增长一个参数。
//原来函数 function personInfo(name,phone,card){ ... } //修改后 function personInfo(name,age,phone,card){ ... }
这样就是参数修改一次,函数的参数就要修改一次!若是是用对象,就不会出现这样问题!
//修改先后都是这样,变得是函数的操做内容和调用时候的传参! function personInfo(opt){ ... }
看了上面的几个栗子,总结来讲,就是当函数的参数不固定的时候,参数多(三个或者三个以上)的时候,建议用一个对象记录参数,这样会比较方便,也为之后若是参数要改留了条后路!
合并数组这个已是老生常谈的话题了,方法也是多种多样!
var arr1=[1,2,3,4,5],arr2=[6,7,8,9,10]; arr1=arr1.concat(arr2) console.log(arr1)//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
concat会一个全新的数组,表示arr1和arr2两个数组的组合,并让arr1和arr2不变。简单吧?
但若是arr1和arr2的长度都很长,那就产生了一个很长很长的数组,内存又被占用了那么多。可是数组长度没限制!
var arr1=[1,2,3,4,5],arr2=[6,7,8,9,10]; for(var i=0,len=arr2.length;i<len;i++){ arr1.push(arr2[i]) } console.log(arr1)//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
这里是往arr1循环添加arr2的元素,可是有一个状况,arr1的长度远小于arr2的长度,是否是循环arr1性能更好,循环次数更少。处理这个很简单,可是万一不知道arr1和arr2到底哪一个长度更少呢?并且,for循环不够优雅!(固然,这个能够用迭代方法来替代)
var arr1=[1,2,3,4,5],arr2=[6,7,8,9,10]; arr1 = arr2.reduce( function(coll,item){ coll.push( item ); return coll; }, arr1 ); console.log(arr1)//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
逼格高了一点,并且用ES6的箭头函数还能够减小一些代码量,但它仍然须要一个函数,每一个元素都须要调用一次。
var arr1=[1,2,3,4,5],arr2=[6,7,8,9,10]; arr1.push.apply(arr1,arr2); console.log(arr1)//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
逼格看着高,代码少,也不会产生新的数组,也不难理解,就是调用arr1.push
这个函数实例的apply
方法,同时把arr2
看成参数传入,这样arr1.push
这个方法就会遍历arr2
数组的全部元素,达到合并的效果。至关于arr1.push.apply(arr1,[6,7,8,9,10]);
,最后至关于arr1.push(6,7,8,9,10)
。遗憾的就是,这个方法对数组长度有限制,网上说法是不一样浏览器,不一样的长度限制,通常不超过10万!
以前是建议用push.apply,可是如今保留意见,就是你们以为哪一个方式用哪一个方式!这个没有必定的对错!
在开发上,常常会遇到最多保留多少位小数或者相似的问题,针对这个,使用toFixed能够很简单的解决问题,可是若是数据是要和后台交互的,并且后台存储的数据通常是保存数字类型,而使用toFixed后生成的是一个字符串,这下,就须要把toFixed生成的是一个字符串转成数字类型,转发不少。今天我说一个最简单--+。代码以下
var a=123.36896335.toFixed(2) console.log(a)//'123.37' a=+a console.log(a)//123.37
PS:a=a|0和~~a也能够实现,可是生成的是一个整数,以下
var a=123.36896335.toFixed(2) console.log(a)//'123.37' a=a|0 console.log(a)//123 //---------------------------------分割线 var a=123.36896335.toFixed(2) console.log(a)//'123.37' a=~~a console.log(a)//123
下面的转换,你们一看就明白了,很少说。
console.log(!!'123') //true !!12 //true !!-1 //true !![] //true !!'' //false !!null //false
var arr=[1,2,3,4,5,6] for(var i=0,i<arr.length;i++){ ... } //------------------------分割线 var arr=[1,2,3,4,5,6] for(var i=0,len=arr.length;i<len;i++){ ... }
第一段就是每一次循环的时候,都要查询一次arr.length。第二段代码就是缓存了arr.length,每次对比len就好,理论上是第二段代码的写法比较好,性能比较高!可是随着浏览器的发展,这个细节的性能上的影响貌似远远小于预期,如今仍是建议缓存!我写了下面的测试用例(谷歌浏览器测试)!
var arr100=[], arr10000=[]; for(var i=0;i<100;i++){ arr100.push(i) } for(var i=0;i<10000;i++){ arr10000.push(i) } //缓存状况 function testCache(arr){ console.time(); for(var i=0,len=arr.length;i<len;i++){ } console.timeEnd() } //不缓存状况 function testNoCache(arr){ console.time(); for(var i=0,len=arr.length;i<len;i++){ } console.timeEnd() } testCache(arr100)//default: 0.007ms testCache(arr10000)//default: 0.035ms testNoCache(arr100)//default: 0.012ms testNoCache(arr10000)//default: 0.109ms //这只是一个最简单的数组,若是遍历的是一个nodeList(元素列表),效果可能会更明显。
这里我用jquery来说解,比较容易理解,原生js也是这个道理!以下代码
$('.div1').click(function(){ ... }) //--------------------------分割线 var $div1=$('.div1'); $div1.click(function(){ ... })
上面的代码,改变的也是缓存了$('.div1'),可是这里就建议是第二种写法了,由于第一种点击一次就要查询一次.div1,Dom的操做仍是能减小就减小!
好比有一个需求,往ul
里面添加10个li
,两种方法,以下代码
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <ul id="ul-test"> </ul> </body> <script type="text/javascript"> var oUl=document.getElementById("ul-test"); //createElement方式 console.time(); for(var i=0;i<10;i++){ var oLi=document.createElement('li'); oLi.innerHTML=i; oUl.appendChild(oLi); } console.timeEnd(); //innerHTML方式 console.time(); var _html=''; for(var i=0;i<10;i++){ _html+='<li>'+i+'</li>' } oUl.innerHTML=_html; console.timeEnd(); </script> </html>
你们把代码用浏览器打开,发现基本是第二种方式更快,第8点也说了,DOM操做能少就少!第一种要操做10次DOM,第二种只须要操做1次DOM。还有一个就是,这个只是很简单的li,若是是下面的列表呢?用第一种方式,得createElement多少次,innerHTML多少次,appendChild多少次?代码多,各个节点的逻辑和嵌套关系也乱!用第二种方式就是一个拼接字符串的操做,比第一种方式好多了,若是用es6的模板字符串,就更简单了!
函数里的arguments,虽然拥有length属性,可是arguments不是一个数组,是一个类数组,没有push,slice等方法。有些时候,须要把arguments转成数组,转的方法也不止一个,推荐的是是下面的写法!
var _arguments=Array.prototype.slice.apply(arguments)
这里拿一个栗子说,好比mousemove,onscroll,onresize这些事件触发的时候,可能已经触发了60次事件,这样很消耗性能,并且实际上,咱们并不须要这么频繁的触发,只要大约100毫秒触发一次就好!那么这样就须要函数节流了!
普通写法
var count = 0; function beginCount() { count++; console.log(count); } document.onmousemove = function () { beginCount(); };
效果
节流写法
var count = 0; function beginCount() { count++; console.log(count); } function delayFn(method, thisArg) { clearTimeout(method.props); method.props = setTimeout(function () { method.call(thisArg) },100) } document.onmousemove = function () { delayFn(beginCount) };
效果
这种方式,实际上是有问题的,在不断触发停下来等待100ms才开始执行,中间操做得太快直接无视。因而在网上找到下面这种方案!
第二种节流写法
function delayFn2 (fn, delay, mustDelay){ var timer = null; var t_start; return function(){ var context = this, args = arguments, t_cur = +new Date(); //先清理上一次的调用触发(上一次调用触发事件不执行) clearTimeout(timer); //若是不存触发时间,那么当前的时间就是触发时间 if(!t_start){ t_start = t_cur; } //若是当前时间-触发时间大于最大的间隔时间(mustDelay),触发一次函数运行函数 if(t_cur - t_start >= mustDelay){ fn.apply(context, args); t_start = t_cur; } //不然延迟执行 else { timer = setTimeout(function(){ fn.apply(context, args); }, delay); } }; } var count=0; function fn1(){ count++; console.log(count) } //100ms内连续触发的调用,后一个调用会把前一个调用的等待处理掉,但每隔200ms至少执行一次 document.onmousemove=delayFn2(fn1,100,200)
我如今函数节流用得不多,这两个写法是比较基础的,但愿你们能共享下本身的比较好的方法!
关于其它的一些写法技巧和建议,都是比较老生常谈的,好比命名规范,函数单一性原则等。这一部份内容我本身总结和别人写的基本一致!我就不展开说了(感受展开说也基本是复制粘贴别人的文章,这事我不干),因此我推荐你们去看这篇文章(如何优雅的编写 JavaScript 代码)。有些知识我也是从这里得到的!
好了,关于我本身总结的一些实用技巧和建议,就到这里了!关于javascript的技巧和建议,这点你们仍是要多看网上的资源,也要本身多总结,毕竟我本身总结的只是我本身发现的,只是冰山一角。但仍是但愿这篇文章能帮到你们,让你们学习到知识。固然,更但愿的是能起到一个交流意见的做用。若是你们有什么建议,技巧。也欢迎分享。以为我哪里写错了,写得不够好,也欢迎指出!让你们一块儿互相帮助,互相学习!
-------------------------华丽的分割线--------------------
想了解更多,关注关注个人微信公众号:守候书阁