1.前言

从大学到现在,接触前端已经有几年了,感想方面,就是对于程序员而言,想要提高自己的技术水平和编写易于阅读和维护的代码,我觉得不能每天都是平庸的写代码,更要去推敲,去摸索和优化代码,总结当中的技巧,积极听取别人的建议,这样自己的技术水平会提高的更快。那么今天,我在这里就分享一下关于javascript方面的写作的实用技巧和建议,这些技巧和建议是我平常在开发项目上会用到的,希望能让大家学到知识,更希望能起到一个交流意见的作用,也就是说大家有什么好的技巧或者建议,欢迎分享,或者觉得我的想法存在什么问题,欢迎指出!

2.更短的数组去重写法

  1.    [...new Set([2,"12",2,12,1,2,1,6,12,13,6])]

  2.    //[2, "12", 12, 1, 6, 13]

  3.    //es6的新特性

3.对象深浅拷贝

关于对象的深浅拷贝,我个人见解就是有一下几点:

深拷贝和浅拷贝只针对像Object, Array这样的引用类型数据。

浅拷贝是对对象引用地址进行拷贝,并没有开辟新的栈,也就是拷贝后的结果是两个对象指向同一个引用地址,修改其中一个对象的属性,则另一个对象的属性也会改变。

深拷贝则是开启一个新的栈,两个对象对应两个不同的引用地址,修改一个对象的属性,不会改变另一个对象的属性。

浅拷贝

  1.    var myInfo={name:'守候',sex:'男'};

   var newInfo=myInfo;

clone浅拷贝_object.assign()是深拷贝还是浅拷贝_浅拷贝实现

  1.    newInfo.sex='女';

clone浅拷贝_object.assign()是深拷贝还是浅拷贝_浅拷贝实现

  1.    console.log(myInfo)   //{name: "守候", sex: "女"}

假-深拷贝

假-深拷贝这个是自己随性命名的,大家看看就好,别当真!

  1.    var myInfo={name:'守候',sex:'男'};

  1.    var newInfo=Object.assign({},myInfo)

clone浅拷贝_object.assign()是深拷贝还是浅拷贝_浅拷贝实现

  1.    newInfo.sex='女';

浅拷贝实现_clone浅拷贝_object.assign()是深拷贝还是浅拷贝

  1.    console.log(myInfo)   //{name: "守候", sex: "男"}

  2.    console.log(newInfo)   //{name: "守候", sex: "女"}

真-深拷贝

真-深拷贝这个是自己随性命名的,大家看看就好,别当真!

看着深浅拷贝,区别写法很简单,但是那个上面的深拷贝写法是有问题的。看下面案例

  1.    var arr=[{a:1,b:2},{a:3,b:4}]

  2.    var newArr=Object.assign([],arr)

  3.    //截断数组

  4.    newArr.length=1

  5.    console.log(newArr)//[{a:1,b:2}]

  6.    console.log(arr)//[{a:1,b:2},{a:3,b:4}]

  7.    //操作newArr,这里看着对arr没影响,实际上已经挖了一个坑,下面就跳进去

  8.    newArr[0].a=123

  9.    //修改newArr[0]这个对象,也是影响了arr[0]这个对象

  10.    console.log(arr[0])//{a: 123, b: 2}

为什么会这样呢,因为Object.assign并不是深拷贝,是披着深拷贝外衣的浅拷贝。最多也是Object.assign会课拷贝第一层的值,对于第一层的值都是深拷贝,而到第二层的时候就是 复制引用。类似的情况还有,slice方法和concat方法等。

要解决这个问题,就得自己封装方法!如下

  1.    //利用递归来实现深拷贝,如果对象属性的值是引用类型(Array,Object),那么对该属性进行深拷贝,直到遍历到属性的值是基本类型为止。  

  2.    function deepClone(obj){    

  3.      if(!obj&& typeof obj!== 'object'){      

  4.        return;    

  5.      }    

  6.      var newObj= obj.constructor === Array ? [] : {};    

  7.      for(var key in obj){      

  8.        if(obj[key]){          

  9.          if(obj[key] && typeof obj[key] === 'object'){  

  10.            newObj[key] = obj[key].constructor === Array ? [] : {};

  11.            //递归

  12.            newObj[key] = deepClone(obj[key]);          

  13.          }else{            

  14.            newObj[key] = obj[key];        

  15.          }      

  16.        }    

  17.      }    

  18.      return newObj;

  19.    }

  20.    var arr=[{a:1,b:2},{a:3,b:4}]

  21.    var newArr=deepClone(arr)

  22.    console.log(arr[0])//{a:1,b:2}

  23.    newArr[0].a=123

  24.    console.log(arr[0])//{a:1,b:2}

还有一个方法就是简单粗暴法,我现在在用的一个方法!原理很简单,就是先把对象转成字符串,再把字符串转成对象!也能实现同样效果

  1.    var newArr2=JSON.parse(JSON.stringify(arr));

  2.    console.log(arr[0])//{a:1,b:2}

  3.    newArr2[0].a=123

  4.    console.log(arr[0])//{a:1,b:2}

上面所说的浅拷贝,真假深拷贝(自己随性命名的),这几种情况,在开发上都有可能要用到,至于要使用哪一种方式,视情况而定!

4.使用事件委托

一个简单的需求,比如想给ul下面的li加上点击事件,点击哪个li,就显示那个li的innerHTML。这个貌似很简单!代码如下!

  1.    

  2.    

  3.        

  4.            <meta charset="UTF-8">

  5.            

  6.        

  7.        

  8.            <ul id="ul-test">

  9.                

  10. 0
  11.                

  12. 1
  13.                

  14. 2
  15.                

  16. 3
  17.                

  18. 4
  19.                

  20. 5
  21.                

  22. 6
  23.                

  24. 7
  25.                

  26. 8
  27.                

  28. 9
  29.            

  30.        

  31.        <script type="text/javascript">

  32.            var oUl=document.getElementById("ul-test");

  33.            var oLi=oUl.getElementsByTagName("li");

  34.            for(var i=0,len=oLi.length;i<len;i++){

  35.                oLi[i].addEventListener("click",function(){

  36.                    alert(this.innerHTML)

  37.                })

  38.            }

  39.        

  40.    

很简单,这样就实现了,实际上这里有坑,也待优化!

for循环,循环的是li,10个li就循环10次,绑定10次事件,100个就循环了100次,绑定100次事件!

如果li不是本来就在页面上的,是未来元素,是页面加载了,再通过js动态加载进来了,上面的写法是无效的,点击li是没有反应的!

所以就者需要用事件委托(即使不考虑上面的第二种情况,也是建议使用事件委托)!代码如下

  1.    

  2.    

  3.        

  4.            <meta charset="UTF-8">

  5.            

  6.        

  7.        

  8.            <ul id="ul-test">

  9.                

  10. 0
  11.                

  12. 1
  13.                

  14. 2
  15.                

  16. 3
  17.                

  18. 4
  19.                

  20. 5
  21.                

  22. 6
  23.                

  24. 7
  25.                

  26. 8
  27.                

  28. 9
  29.            

  30.        

  31.        <script type="text/javascript">

  32.            var oUl=document.getElementById("ul-test");

  33.            oUl.addEventListener("click",function(ev){

  34.                var ev=ev||window.event;

  35.                var target=ev.target||ev.srcElement;

  36.                //如果点击的最底层是li元素

  37.                if(target.tagName.toLowerCase()==='li'){

  38.                    alert(target.innerHTML)

  39.                }

  40.            })

  41.        

  42.    

这样写,即使是动态添加进来的li点击也有反应,还有一个就是ul只有一个,事件绑定在ul上,无论li多少个,都是添加一次事件!但是也是可能会有问题,如果li下面还有子元素,那么点击的时候,target可能不是li,而是鼠标点击那个位置的最底层元素!如下图,如果鼠标点击白色区域,那个target就是body元素,鼠标点击绿色区域target就是div元素,鼠标点击蓝色区域target就是ul,点击橙色就是li。

object.assign()是深拷贝还是浅拷贝_clone浅拷贝_浅拷贝实现

5.使用对象作为函数参数

大家试想下这样一个函数–函数接受几个参数,但是这几个参数都不是必填的,函数该怎么处理?是不是下面这样

  1.    function personInfo(name,phone,card){

  2.        ...

  3.    }

  4.    //以上函数,可以任意传参数。比如我想传card等于1472586326。这下是不是这样写

  5.    personInfo('','','1472586326')

有没有觉得上面写法奇怪,不太优雅?下面这里看着舒服一点!

  1.    function personInfo(opt){

  2.        ...

  3.    }

  4.    personInfo({card:'1472586326'})

再想一下,如果一个函数,参数很多,怎么处理?

  1.    function test(arg1,arg2,arg3,arg4,arg5,arg6,arg7){

  2.        ...

  3.    }

密集恐惧症复发没有复发?下面这样看着会舒服一点!

  1.    function personInfo(opt){

  2.        ...

  3.    }

最后再想一下,如果需求改了,操作函数也要改!函数也要增加一个参数。

  1.    //原来函数

  2.    function personInfo(name,phone,card){

  3.        ...

  4.    }

  5.    //修改后

  6.    function personInfo(name,age,phone,card){

  7.        ...

  8.    }

这样就是参数修改一次,函数的参数就要修改一次!如果是用对象,就不会出现这样问题!

  1.    //修改前后都是这样,变得是函数的操作内容和调用时候的传参!

  2.    function personInfo(opt){

  3.        ...

  4.    }

看了上面的几个栗子,总结来说,就是当函数的参数不固定的时候,参数多(三个或者三个以上)的时候,建议用一个对象记录参数,这样会比较方便,也为以后如果参数要改留了条后路!

6.使用push和apply合并数组

合并数组这个已经是老生常谈的话题了,方法也是多种多样!

concat

  1.    var arr1=[1,2,3,4,5],arr2=[6,7,8,9,10];

  2.    arr1=arr1.concat(arr2)

  3.    console.log(arr1)//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

concat会一个全新的数组,表示arr1和arr2两个数组的组合,并让arr1和arr2不变。简单吧?

但如果arr1和arr2的长度都很长,那就产生了一个很长很长的数组,内存又被占用了那么多。但是数组长度没限制!

for

  1.    var arr1=[1,2,3,4,5],arr2=[6,7,8,9,10];

  2.    for(var i=0,len=arr2.length;i<len;i++){

  3.        arr1.push(arr2[i])

  4.    }

  5.    console.log(arr1)//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

这里是往arr1循环添加arr2的元素,但是有一个情况,arr1的长度远小于arr2的长度,是不是循环arr1性能更好,循环次数更少。处理这个很简单,但是万一不知道arr1和arr2到底哪个长度更少呢?而且,for循环不够优雅!(当然,这个可以用迭代方法来替代)

reduce

  1.    var arr1=[1,2,3,4,5],arr2=[6,7,8,9,10];

  2.    arr1 = arr2.reduce( function(coll,item){

  3.         coll.push( item );

  4.         return coll;

  5.     }, arr1 );

  6.     console.log(arr1)//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

逼格高了一点,而且用ES6的箭头函数还可以减少一些代码量,但它仍然需要一个函数,每个元素都需要调用一次。

push.apply

  1.    var arr1=[1,2,3,4,5],arr2=[6,7,8,9,10];

  2.    arr1.push.apply(arr1,arr2);

  3.    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,但是现在保留意见,就是大家觉得哪个方式用哪个方式!这个没有一定的对错!

7.toFixed保留整数

在开发上,经常会遇到最多保留多少位小数或者类似的问题,针对这个,使用toFixed可以很简单的解决问题,但是如果数据是要和后台交互的,而且后台存储的数据一般是保存数字类型,而使用toFixed后生成的是一个字符串,这下,就需要把toFixed生成的是一个字符串转成数字类型,转发很多。今天我说一个最简单–+。代码如下

  1.    var a=123.36896335.toFixed(2)

  2.    console.log(a)//'123.37'

  3.    a=+a

  4.    console.log(a)//123.37

PS:a=a|0和~~a也可以实现,但是生成的是一个整数,如下

  1.    var a=123.36896335.toFixed(2)

  2.    console.log(a)//'123.37'

  3.    a=a|0  

  4.    console.log(a)//123

  5.    //---------------------------------分割线

  6.    var a=123.36896335.toFixed(2)

  7.    console.log(a)//'123.37'

  8.    a=~~a  

  9.    console.log(a)//123

8.其它类型数据转布尔数据

下面的转换,大家一看就明白了,不多说。

  1.    console.log(!!'123')

  2.    //true

  3.    !!12

  4.    //true

  5.    !!-1

  6.    //true

  7.    !![]

  8.    //true

  9.    !!''

  10.    //false

  11.    !!null

  12.    //false

9.缓存变量

for循环缓存length

  1.    var arr=[1,2,3,4,5,6]

  2.    for(var i=0,i<arr.length;i++){

  3.        ...

  4.    }

  5.    //------------------------分割线

  6.    var arr=[1,2,3,4,5,6]

  7.    for(var i=0,len=arr.length;i<len;i++){

  8.        ...

  9.    }

第一段就是每一次循环的时候,都要查询一次arr.length。第二段代码就是缓存了arr.length,每次对比len就好,理论上是第二段代码的写法比较好,性能比较高!但是随着浏览器的发展,这个细节的性能上的影响貌似远远小于预期,现在还是建议缓存!我写了下面的测试用例(谷歌浏览器测试)!

  1.    var arr100=[], arr10000=[];

  2.    for(var i=0;i<100;i++){

  3.        arr100.push(i)

  4.    }

  5.    for(var i=0;i<10000;i++){

  6.        arr10000.push(i)

  7.    }

  8.    //缓存情况

  9.    function testCache(arr){

  10.        console.time();

  11.        for(var i=0,len=arr.length;i<len;i++){

  12.        }

  13.        console.timeEnd()

  14.    }

  15.    //不缓存情况

  16.    function testNoCache(arr){

  17.        console.time();

  18.        for(var i=0,len=arr.length;i<len;i++){

  19.        }

  20.        console.timeEnd()

  21.    }

  22.    testCache(arr100)//default: 0.007ms

  23.    testCache(arr10000)//default: 0.035ms

  24.    testNoCache(arr100)//default: 0.012ms

  25.    testNoCache(arr10000)//default: 0.109ms

  26.    //这只是一个最简单的数组,如果遍历的是一个nodeList(元素列表),效果可能会更明显。

元素事件

这里我用jquery来讲解,比较容易理解,原生js也是这个道理!如下代码

  1.    $('.div1').click(function(){

  2.       ...

  3.    })

  4.    //--------------------------分割线  

  5.    var $div1=$('.div1');

  6.    $div1.click(function(){

  7.       ...

  8.    })

上面的代码,改变的也是缓存了$(‘.div1’),但是这里就建议是第二种写法了,因为第一种点击一次就要查询一次.div1,Dom的操作还是能减少就减少!

10.使用innerHTML添加元素

  1. 比如有一个需求,往`ul`里面添加10`li`,两种方法,如下代码

  2.    

  3.    

  4.        

  5.            <meta charset="UTF-8">

  6.            </span><span class="pun" style="line-height: 20px;font-size: 13px !important">

  7.        

  8.        

  9.            <ul id="ul-test">

  10.            

  11.        

  12.        <script type="text/javascript">

  13.            var oUl=document.getElementById("ul-test");

  14.            //createElement方式

  15.            console.time();

  16.            for(var i=0;i<10;i++){

  17.                var oLi=document.createElement('li');

  18.                oLi.innerHTML=i;

  19.                oUl.appendChild(oLi);

  20.            }

  21.            console.timeEnd();

  22.            //innerHTML方式

  23.            console.time();

  24.            var _html='';

  25.            for(var i=0;i<10;i++){

  26.                _html+='

  27. '+i+'
  28. '

  29.            }

  30.            oUl.innerHTML=_html;

  31.            console.timeEnd();

  32.        

  33.    

大家把代码用浏览器打开,发现基本是第二种方式更快,第8点也说了,DOM操作能少就少!第一种要操作10次DOM,第二种只需要操作1次DOM。还有一个就是,这个只是很简单的li,如果是下面的列表呢?用第一种方式,得createElement多少次,innerHTML多少次,appendChild多少次?代码多,各个节点的逻辑和嵌套关系也乱!用第二种方式就是一个拼接字符串的操作,比第一种方式好多了,如果用es6的模板字符串,就更简单了!

clone浅拷贝_浅拷贝实现_object.assign()是深拷贝还是浅拷贝

11.将参数转成数组

函数里的arguments,虽然拥有length属性,但是arguments不是一个数组,是一个类数组,没有push,slice等方法。有些时候,需要把arguments转成数组,转的方法也不止一个,推荐的是是下面的写法!

  1.    var _arguments=Array.prototype.slice.apply(arguments)

12.函数节流

这里拿一个栗子说,比如mousemove,onscroll,onresize这些事件触发的时候,可能已经触发了60次事件,这样很消耗性能,而且实际上,我们并不需要这么频繁的触发,只要大约100毫秒触发一次就好!那么这样就需要函数节流了!

普通写法

  1.    var count = 0;

  2.    function beginCount() {

  3.        count++;

  4.        console.log(count);

  5.    }

  6.    document.onmousemove = function () {

  7.       beginCount();

  8.    };

效果

clone浅拷贝_浅拷贝实现_object.assign()是深拷贝还是浅拷贝

节流写法

  1.    var count = 0;

  2.    function beginCount() {

  3.        count++;

  4.        console.log(count);

  5.    }

  6.    function delayFn(method, thisArg) {

  7.        clearTimeout(method.props);

  8.        method.props = setTimeout(function () {

  9.            method.call(thisArg)

  10.        },100)

  11.    }

  12.    document.onmousemove = function () {

  13.        delayFn(beginCount)

  14.    };

效果

浅拷贝实现_clone浅拷贝_object.assign()是深拷贝还是浅拷贝

这种方式,其实是有问题的,在不断触发停下来等待100ms才开始执行,中间操作得太快直接无视。于是在网上找到下面这种方案!

第二种节流写法

  1.    function delayFn2 (fn, delay, mustDelay){

  2.         var timer = null;

  3.         var t_start;

  4.         return function(){

  5.             var context = this, args = arguments, t_cur = +new Date();

  6.             //先清理上一次的调用触发(上一次调用触发事件不执行)

  7.             clearTimeout(timer);

  8.             //如果不存触发时间,那么当前的时间就是触发时间

  9.             if(!t_start){

  10.                 t_start = t_cur;

  11.             }

  12.             //如果当前时间-触发时间大于最大的间隔时间(mustDelay),触发一次函数运行函数

  13.             if(t_cur - t_start >= mustDelay){

  14.                 fn.apply(context, args);

  15.                 t_start = t_cur;

  16.             }

  17.             //否则延迟执行

  18.             else {

  19.                 timer = setTimeout(function(){

  20.                     fn.apply(context, args);

  21.                 }, delay);

  22.             }

  23.         };

  24.    }

  25.    var count=0;

  26.    function fn1(){

  27.        count++;

  28.        console.log(count)

  29.    }

  30.    //100ms内连续触发的调用,后一个调用会把前一个调用的等待处理掉,但每隔200ms至少执行一次

  31.    document.onmousemove=delayFn2(fn1,100,200)

object.assign()是深拷贝还是浅拷贝_浅拷贝实现_clone浅拷贝

我现在函数节流用得很少,这两个写法是比较基础的,希望大家能共享下自己的比较好的方法!

13.其他写作建议

关于其它的一些写法技巧和建议,都是比较老生常谈的,比如命名规范,函数单一性原则等。这一部分内容我自己总结和别人写的基本一致!我就不展开说了(感觉展开说也基本是复制粘贴别人的文章,这事我不干),所以我推荐大家去看这篇文章(如何优雅的编写 JavaScript 代码)。有些知识我也是从这里获得的!

14.小结

好了,关于我自己总结的一些实用技巧和建议,就到这里了!关于javascript的技巧和建议,这点大家还是要多看网上的资源,也要自己多总结,毕竟我自己总结的只是我自己发现的,只是冰山一角。但还是希望这篇文章能帮到大家,让大家学习到知识。当然,更希望的是能起到一个交流意见的作用。如果大家有什么建议,技巧。也欢迎分享。觉得我哪里写错了,写得不够好,也欢迎指出!让大家一起互相帮助,互相学习!

限时特惠:本站每日持续更新海量各大内部网赚创业教程,会员可以下载全站资源点击查看详情
站长微信:11082411

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。