• javascript高级技巧篇(作用域安全、防篡改、惰性载入、节流、自定义事件,拖放)


    安全的类型检测

    在任何值上调用Object原生的toString()方法,都会返回一个[object NativeConstructorName]格式字符串。每个类在内部都有一个[[Class]]属性,这个属性中就指定了各个类型的构造函数名。

    使用这种方法判断值属于什么类型最可靠;比如:

        function isArray(value){
                return Object.prototype.toString.call(value)=="[object Array]";
          }
          function isFunction(value){
                return Object.prototype.toString.call(value)=="[object Function]";
          }
          function isRegExp(value){
                return Object.prototype.toString.call(value)=="[object RegExp]";
          }

    判断是不是原生的JSON对象

        function isNativeJSON(value){
                return window.JSON&&Object.prototype.toString.call(value)=="[object JSON]";
          }

    在javascript中,自定义的构造函数上述方法检测都会返回[object object]。

    作用域安全构造函数

        function Person(name,age,job){
                if(this instanceof Person){
                      this.name=name;
                      this.age=age;
                      this.job=job;
                }else{
                      return new Person(name,age,job);
                }
          }

    继承作用域安全的构造函数

        function LittleBoy(classNum,name,age,job){
                Person.call(this,name,age,job);//Person的作用域是安全的,this并不指向Person实例,所以必须让LittleBoy的原型链指向Person的实例
                this.classNum=classNum;
                this.getClassNum=function(){
                      return this.classNum;
                }
          }
          LittleBoy.prototype=new Person();

    惰性载入函数

    在if判断后,将原函数重新定义,再次执行原函数时,就不用if判断了。也就是在一定环境下,只需第一次有if判断,后面就不需要if判断,从而提高性能。

    防篡改对象

        var person={name:"ha"};
          Object.preventExtensions(person);//禁止再添加新属性和方法,但可以修改和删除现有的属性和方法

    调用Object.isExtensible()方法可以判断对象是否可拓展

        console.log(Object.isExtensible(person));//false

    密封对象

        Object.seal(person);//不能添加和删除属性和方法

    判断是否是密封对象

        Object.isSealed(person);

    冻结对象

        Object.freeze(person);//不能修改,不能添加,不能删除

    判断是否被冻结

        console.log(Object.isFrozen(person));//判断是否被冻结

     Yielding Processes(屈服/投降  进程)

    运行在浏览器中的javascript代码都被分配了一个确定数量的资源。如果脚本运行时间过长,浏览器会弹出一个错误对话框。这对用户体验非常不好。

    脚本运行过长的原因通常由两种:

    1、过长的或过深的嵌套的函数的调用。

    2、进行大量处理的循环。

    这两者中后者是比较容易解决的。

    例如:

        for(var i=0,len=data.length;i<len;i++){
                process(data[i]);
          }

    假如一个process程序需要执行100ms才能完成,如果data数组有10个项目,那么循环处理完这个数组需要花费1s,这花费的时间就有些长了,会严重阻塞后续代码的执行,对用户体验非常不好。我们可以用setTimeout定时函数来对数组分块执行,因为setTimeout可以为代码创建一个队列,在合适的时候插入队列,就不会阻塞后续代码的执行了。不过使用这个数组分块的技术,你需要保证在两个条件下使用:

    1、该处理是否应该同步完成。

    2、数据是否需要按顺序完成。

    如果上述两个问题你都回答否,那么你就可以安心的使用数组分块技术了,例子:

        function chunk(array,process,context){
                setTimeout(function(){
                      var item=array.shift();//截取出原数组的第一项,原数组会改变
                      process.call(context,item);//将运行环境抱定在context上,可选参数
                      if(array.length>0){
                            setTimeout(arguments.callee,100);
                      }
                },100);//100毫秒效果很好
                
          }

    chunk()方法接收三个参数:处理的数组,用于处理项目的函数,以及可选的运行该函数的环境(不传就为null);

    使用事例:

        var data=[12,123,1234,453,436,23,23,5,4123,45,346,5634,345,342];
          function printValue(item){
                var div=document.getElementById("myDiv");
                div.innerHTML+=item+"<br>"
          }
          chunk(data,printValue);

    如果想保持原数组不变,可以将原数组拷贝一下再传入使用,用数组的concat()方法就可以拷贝数组:

        chunk(data.concat(),printValue);

    函数节流

    函数节流用于使高频触发的事件,变为指定事件段执行一次,比如说onresize,等等,我们可以将onresize里执行的代码变成低频触发,而不是让onresize变成低频触发,这个是改变不了的。

    同样,是使用定时器函数来实现的。

        var processor={
                timeoutId:null,
                performProcessing:function(){
                      //实际执行的代码
                      console.log(11);//例子
                },
                process:function(){
                      clearTimeout(this.timeoutId);
                      var that=this;
                      this.timeoutId=setTimeout(function(){
                            that.performProcessing();
                      },100);
                }
          }
          //使用
          window.onresize=function(){
                processor.process();
          }

    以上模式可以用throttle()函数来简化     throttle:节流阀

    这个函数可以自动进行定时器的设置和清除:

        function throttle(method,context){
                clearTimeout(method.tId);//清除计时器
                method.tId=setTimeout(function(){
                      method.call(context);//将函数绑定在一定环境上执行
                },100)
          }

    throttle()方法接收两个参数,要执行的函数和在哪个作用域中执行。

    使用:

        //使用
          var resizeDiv=function(){
                var div=document.getElementById("myDiv");
                div.style.height=div.offsetWidth+"px";
          };
          window.onresize=function(){
                throttle(resizeDiv,window);//window可省略
          }

     自定义事件

    事件是javascript与浏览器交互的主要途径。事件是一种叫作观观察者的实际模式,这是一种创建松散耦合代码的技术。对象可以发布事件,用来表示在该对象声明周期中某个有趣的时刻到了。然后其他对象可以观察该对象,等待这些有趣的时刻到来并通过运行代码来响应。

    观察者模式有两类对象组成:主体和观察者。主体负责发布事件,同时观察者通过订阅这些事件来观察该主体。该模式的一个关键概念是主体并不知道观察者的任何事情,也就是说他可以独自存在并正常运作即观察者不存在。从另一方面来说,观察者知道主体并能注册事件的回调函数(事件处理程序)。涉及DOM上时,DOM元素便是主体,你的事件处理代码便是观察者。

    事件是与DOM交互的最常见的方式,但他们也可以用于非DOM代码中----通过定义自定义事件。自定义事件背后的概念是创建一个管理事件的对象,让其他对象监听那些事件。实现此功能的基本模式如下定义:

          function EventTarget(){
                this.handlers={};//存放事件处理程序
          }
    
          EventTarget.prototype={
                constructor:EventTarget,
                //addHandler:用来注册给定类型的事件处理程序
                addHandler:function(type,handler){//接收两个参数,一个事件类型,一个该类型的事件处理程序
                      if(typeof this.handlers[type]=="undefined"){//如果handlers对象中没有这个类型的属性,则添加一个这个属性,为数组类型
                            this.handlers[type]=[];
                      }
                      this.handlers[type].push(handler);
                },
                // fire 用来触发某个事件
                fire:function(event){
                      if(!event.target){
                            event.target=this;
                      }
                      if(this.handlers[event.type] instanceof Array){
                            var handlers=this.handlers[event.type];
                            for(var i=0,len=handlers.length;i<len;i++){
                                  handlers[i](event);
                            }
                      }
                },
                // removeHandler;用于注销某个事件类型的事件处理程序
                removeHandler:function(type,handler){
                      if(this.handlers[type] instanceof Array){
                            var handlers=this.handlers[type];
                            for(var i=0,len=handlers.length;i<len;i++){
                                  if(handlers[i]===handler){
                                        break;//退出循环
                                  }
                            }
                            handlers.splice(i,1);
                      }
                }
          }

    使用

          // 声明一个事件处理程序
          function handleMessage(event){
                console.log("message receive:"+event.message);
          }
          // 创建一个新对象
          var target=new EventTarget();
          // 添加一个事件处理程序(传入事件类型和事件处理程序)
          target.addHandler("message",handleMessage);
          // 触发事件(传入自定义的event对象)
          target.fire({type:"message",message:"hello"});
          //删除事件处理程序
          target.removeHandler("message",handleMessage);
          // 再次,就没有处理程序
          target.fire({type:"message",message:"hello"});

    因为这种功能封装在一种自定义类型中,其他对象可以继承EventTarget并获得这个行为:

          function inheritPrototype(subType,superType){//寄生组合继承方法
                var prototype=Object(superType.prototype);
                prototype.constructor=subType;
                subType.prototype=prototype;
          }
    
          function Person(name,age){//建一个类
                EventTarget.call(this);//改变EventTarget的执行环境
                this.name=name;
                this.age=age;
          }
          inheritPrototype(Person,EventTarget);//Person继承EventTarget
          Person.prototype.say=function(message){
                this.fire({type:"message",message:message});
          }

    上面采用的是寄生组合继承

    然后在Person类的实例上调用自定义方法:

          function handleMessage(event){
                console.log(event.target.name+"say"+event.message);
          }
          // 创建新person
          var person = new Person("Nicholas",29);
          // 添加一个事件处理程序
          person.addHandler("message",handleMessage);
          // 在该对象上调用一个方法,它触发消息事件
          person.say("Hi there");

     拖放

    简单的拖放需要为文档指定一个onmousemove事件处理程序,指定一个绝对定位的元素,然后让元素移动到鼠标指针的位置:

         document.onmousemove=function(event){
                var myDiv=document.getElementById("myDiv");
                myDiv.style.left=event.clientX+"px";
                myDiv.style.top=event.clientY+"px";
          }

    下面实现一个简单的拖放功能,用的是跨浏览器添加事件的方法,所以需要将跨浏览器事件的对象先写在前面,然后再单独写拖放功能:

          var EventUtil={
                addHandler:function(element,type,handler){
                      if(element.addEventListener){
                      element.addEventListener(type,handler,false);
                      }else if(element.attachEvent){
                      element.attachEvent("on"+type,handler);
                      }else{
                      element["on"+type]=handler;
                      }
                },
                removeHandler:function(element,type,handler){
                      if(element.removeEventListener){
                      element.removeEventListener(type,handler,false);
                      }else if(element.detachEvent){
                      element.detachEvent("on"+type,handler);
                      }else{
                      element["on"+type]=null;
                      }
                },
                getEvent:function(event){//获取事件对象
                      return event?event:window.event;
                },
                getTarget:function(event){//获取事件目标对象
                      return event.target||event.srcElement;
                },
                preventDefault:function(event){//阻止事件默认行为
                      if(event.preventDefault){
                      event.preventDefault();
                      }else{
                      event.returnValue=false;//IE
                      }
                },
                stopPropagation:function(event){//阻止事件冒泡
                      if(event.stopPropagation){
                      event.stopPropagation();
                      }else{
                      event.cancelBubble=true;//IE
                      }
                }
          }

    下面来用跨浏览器的方法实现一个拖动案例:

            var DragDrop=function(){
                var dragging=null,
                      diffX=0,//鼠标在元素上的水平位置
                      diffY=0;//鼠标在元素上的垂直位置
                function handlerEvent(event){
                      //获取事件和目标对象
                      event=EventUtil.getEvent(event);
                      var target=EventUtil.getTarget(event);
                      //确定事件类型
                      switch(event.type){
                            case "mousedown":
                                  if(target.className.indexOf("draggable")>-1){
                                        dragging=target;
                                        diffX=event.clientX-target.offsetLeft;
                                        diffY=event.clientY-target.offsetTop;
                                  }
                                  break;
                            case "mousemove":
                                  if(dragging!==null){
                                        //指定位置
                                        dragging.style.left=(event.clientX-diffX)+"px";
                                        dragging.style.top=(event.clientY-diffY)+"px";
                                  }
                                  break;
                            case "mouseup":
                                  dragging=null;
                                  break;
                      }
                };
                // 公共接口
                return {
                      enable:function(){
                            EventUtil.addHandler(document,"mousedown",handlerEvent);
                            EventUtil.addHandler(document,"mousemove",handlerEvent);
                            EventUtil.addHandler(document,"mouseup",handlerEvent);
                      },
                      disable:function(){
                            EventUtil.removeHandler(document,"mousedown",handlerEvent);
                            EventUtil.removeHandler(document,"mousemove",handlerEvent);
                            EventUtil.removeHandler(document,"mouseup",handlerEvent);
                      }
                }
                      
          }

    使用

    主要在页面上包含这些代码并调用enable()。拖放会自动针对所包含“draggable”类的元素启用:

        <div class="box draggable"></div>//元素必须是绝对定位
        DragDrop().enable();//调用

     为拖放添加自定义事件

    拖放功能还不能真正的应用起来,除非能知道什么时候拖动开始了。从这点上看,当前的方法没有提供任何的拖动开始、正在拖动、拖动结束。这时可以使用自定义事件来指定这几个事件的发生,让应用的其它部分与拖动功能进行交互。

    由于DragDrop对象是一个使用了模块模式的单例,所以需要一些更改来使用EventTarget类型。首先,创建一个新的EventTarget对象,然后添加enable()和disable()方法,最后返回这个对象。修改如下:

            var DragDrop=function(){
                var   dragdrop=new EventTarget(),//创建一个自定义事件对象
                      dragging=null,
                      diffX=0,//鼠标在元素上的水平位置
                      diffY=0;//鼠标在元素上的垂直位置
                function handlerEvent(event){
                      //获取事件和目标对象
                      event=EventUtil.getEvent(event);
                      var target=EventUtil.getTarget(event);
                      //确定事件类型
                      switch(event.type){
                            case "mousedown":
                                  if(target.className.indexOf("draggable")>-1){
                                        dragging=target;
                                        diffX=event.clientX-target.offsetLeft;
                                        diffY=event.clientY-target.offsetTop;
                                        //触发dragstart事件
                                        dragdrop.fire({type:"dragstart",target:dragging,
                                                    x:event.clientX,y:event.clientY});
                                  }
                                  break;
                            case "mousemove":
                                  if(dragging!==null){
                                        //指定位置
                                        dragging.style.left=(event.clientX-diffX)+"px";
                                        dragging.style.top=(event.clientY-diffY)+"px";
                                        // 触发拖动中事件
                                        dragdrop.fire({type:"drag",target:dragging,
                                                    x:event.clientX,y:event.clientY});
                                  }
                                  break;
                            case "mouseup":
                                  // 触发拖动结束事件
                                  dragdrop.fire({type:"dragend",target:dragging,
                                              x:event.clientX,y:event.clientY});
                                  dragging=null;
                                  break;
                      }
                };
                // 公共接口
                dragdrop.enable=function(){
                      EventUtil.addHandler(document,"mousedown",handlerEvent);
                      EventUtil.addHandler(document,"mousemove",handlerEvent);
                      EventUtil.addHandler(document,"mouseup",handlerEvent);
                };
                dragdrop.disable=function(){
                      EventUtil.removeHandler(document,"mousedown",handlerEvent);
                      EventUtil.removeHandler(document,"mousemove",handlerEvent);
                      EventUtil.removeHandler(document,"mouseup",handlerEvent);
                };
                return dragdrop;
                      
          }();//自执行

    这段代码新增了三个事件,dragstart、drag、dragend。他们都将被拖动的元素设置为了target,并给出了x和y属性来表示当前的位置。他们触发于dragdrop对象上,之后在返回对象前给对象增加了enable()和disable()方法。这些模块模式中的细小更改令DragDrop对象支持了事件,如:

          DragDrop.enable();//执行拖放效果
          DragDrop.addHandler("dragstart",function(event){
                var status=document.getElementById("status");
                status.innerHTML="Started dragging"+event.target.id;
          });
          DragDrop.addHandler("drag",function(event){
                var status=document.getElementById("status");
                status.innerHTML+="<br/> Dragged"+event.target.id+"to("+event.x+","+event.y+")";
          });
          DragDrop.addHandler("dragend",function(event){
                var status=document.getElementById("status");
                status.innerHTML+="<br/> Dragged"+event.target.id+"at("+event.x+","+event.y+")";
          });  
  • 相关阅读:
    切换路由时中止未返回数据的请求
    elementui 走马灯图片自适应
    覆盖elementui样式
    vue+elementui搭建后台管理界面(5递归生成侧栏路由)
    vue+elementui搭建后台管理界面(4使用font-awesome)
    vue+elementui搭建后台管理界面(3侧边栏菜单)
    vue+elementui搭建后台管理界面(2首页)
    vue+elementui搭建后台管理界面(1登录)
    C排序算法
    各种语言中获取时间戳的方法
  • 原文地址:https://www.cnblogs.com/fqh123/p/10633453.html
Copyright © 2020-2023  润新知