惰性实例化
惰性实例化所要解决的问题是这样的:避免了在页面中js初始化执行的时候就实例化了类,如果在页面中没有使用到这个实例化的对象,那么这就造成了一定的内存的浪费和性能的消耗,那么如果将一些类的实例化推迟到需要使用它的时候才开始去实例化,那么这就避免了刚才说的问题,做到了“按需供应”,简单代码示例如下:
var myNamespace = function(){
  var Configure = function(){
    var privateName = "someone's name";
    var privateReturnName = function(){
      return privateName;
    }
    var privateSetName = function(name){
      privateName = name;
    }
    //返回单例对象
    return {
      setName:function(name){
        privateSetName(name);
      },
      getName:function(){
        return privateReturnName();
      }
    }
  }
  //储存configure的实例
  var instance;
  return {
    getInstance:function(){
      if(!instance){
        instance = Configure();
      }
      return instance;
    }
  }
}();
//使用方法上就需要getInstance这个函数作为中间量了:
myNamespace.getInstance().getName();
//惰性实例化的变体
var myNamespace2 = function(){
  var Configure = function(){
    var privateName = "someone's name";
    var privateReturnName = function(){
      return privateName;
    }
    var privateSetName = function(name){
      privateName = name;
    }
    //返回单例对象
    return {
      setName:function(name){
        privateSetName(name);
      },
      getName:function(){
        return privateReturnName();
      }
    }
  }
  //储存configure的实例
  var instance;
  return {
    init:function(){
      //如果不存在实例,就创建单例实例
      if(!instance){
        instance = Configure();
      }
      //将Configure创建的单例
      for(var key in instance){
        if(instance.hasOwnProperty(key)){
          this[key]=instance[key];
        }
      }
      this.init = null;
      return this;
    }
  }
}();
//使用方式:
myNamespace2.init();
myNamespace2.getName();
分支
分支技术解决的一个问题是处理浏览器之间兼容性的重复判断的问题。普通解决浏览器之间的兼容性的方式是使用if逻辑来进行特性检测或者能力检测,来实现根据浏览器不同的实现来实现功能上的兼容,但问题是,没执行一次代码,可能都需要进行一次浏览器兼容性方面的检测,这个是没有必要的,能否在代码初始化执行的时候就检测浏览器的兼容性,在之后的代码执行过程中,就无需再进行检测了呢?答案是有的,分支技术就可以解决这个问题(同样,惰性载入函数也可以实现Lazy Definied,这个在后面将会讲到),下面以声明一个XMLHttpRequest实例对象为例子:
//分支
var XHR= function(){
  var standard = {
    createXHR : function(){
      return new XMLHttpRequest();
    }
  }
  var newActionXObject = {
    createXHR : function(){
      return new ActionXObject("Msxml2.XMLHTTP");
    }
  }
  var oldActionXObject = {
    createXHR : function(){
      return new ActionXObject("Microsoft.XMLHTTP");
    }
  }
  if(standard.createXHR()){
    return standard;
  }else{
    try{
      newActionXObject.createXHR();
      return newActionXObject;
    }catch(o){
      oldActionXObject.createXHR();
      return oldActionXObject;
    }
  }
}();
惰性载入函数
惰性载入函数就是英文中传说的“Lazy Defined”,它的主要解决的问题也是为了处理兼容性。原理跟分支类似,下面是简单的代码示例:
var addEvent = function(el,type,handle){
  addEvent = el.addEventListener ? function(el,type,handle){
    el.addEventListener(type,handle,false);
  }:function(el,type,handle){
    el.attachEvent("on"+type,handle);
  };
  //在第一次执行addEvent函数时,修改了addEvent函数之后,必须执行一次。
  addEvent(el,type,handle);
}
单例的两种模式
单例模式是家喻户晓的了,也是当前最流行的一种编写方式,注明的模块模式的编写方式也是从这个思想中衍生出来的。单例模式有两种方式:一种是所谓的“门户大开型”,另外一种就是使用闭包来创建私有属性和私有方法的方式。第一种方式跟构造函数的“门户大开型”是一个样的,声明的方法和属性对外都是开放的,可以通过实例来调用。但是使用闭包来实现的单例模式,可以在一个“封闭”的作用域内声明一些不为外部所调用的私有属性和私有方法,而且还有一个很主要的功能,就是私有属性可以作为一个“数据存储器”,在闭包内声明的方法都可以访问这些私有属性,但是外部不可访问。那么重复添加、修改、删除这些私有属性所储存的数据是安全的,不受外部其他程序的影响。
享元类
顾名思义,“享”、“元”。就是共享通用的方法和属性,将差异比较大的类中相同功能的方法集中到一个类中声明,这样需要这些方法的类就可以直接从享元类中进行扩展,这样使得这样通用的方法只需要声明一遍就行了。这个从代码的大小、质量来说还是有一定的效益的。
函数绑定
函数绑定就是为了纠正函数的执行上下文,特别是函数中带有this关键字的时候,这点尤其显得重要,稍微不小心,使得函数的执行上下文发生了跟预期的不同的改变,导致了代码执行上的错误(有时候也不会出现错误,这样调试起来,会很变态)。对于这个问题,bind函数是再熟悉不过的了,bind函数的功能就是提供一个可选的执行上下文传递给函数,并且在bind函数内部返回一个函数,来纠正在函数调用上出现的执行上下文发生的变化。最容易出现的错误就是回调函数和事件处理程序一起使用了,下面是摘自《Javascript高级程序设计第二版》的一个示例:
var handler = {
  message:"Event handler",
  handlerClick:function(e){
    alert(this.message);
  }
}
var btn = document.getElementById("my-btn");
//这句就造成了回调函数执行上下文的改变了
EventUtil.addHandler(btn,"click",handler.handlerClick);
//这样运行的很好
EventUtil.addHandler(btn,"click",function(e){
  handler.handlerClick(e);
});
var bind = function(fn,context){
  return function(){
    return fn.apply(context || this,arguments);
  }
}
EventUtil.addHandler(btn,"click",bind(handler.handlerClick));// So Good!
函数curry化
函数curry化的主要功能就是提供了强大的动态函数创建的功能。通过调用另一个函数并为它传入要curry的函数和必要的参数。说白点就是利用已有的函数,再创建一个动态的函数,该动态的函数内部还是通过该已有的函数来发生作用,只是传入更多的参数来简化函数的参数方面的调用。具体示例:
//curry function
function curry(fn){
  var args = [].slice.call(arguments,1); //这个就相当于一个存储器了。
  return function(){
    return fn.apply(null,args.concat([].slice.call(arguments,0)));
  }
}
//Usage:
function add(num1,num2){
  return num1+num2;
}
var newAdd = curry(add,5);
alert(newAdd(6));
高级定时器
提到定时器,无非就是利用setTimeout/setInterval了。但问题是定时器并不是相当于新开一个线程来执行js程序,也不会说是在指定的时间间隔内就会一定执行。指定的时间间隔表示何时将定时器的代码添加到浏览器的执行队列,而不是合适实际执行代码。对此,就有这样的一个问题了:如果代码执行时间超过了定时器指定的时间间隔,那么在指定的时间里代码还是加入的执行队列,但是并没有执行,这样就会造成了无意义的代码执行,这也是使用setInterval的弊端。为此,使用setTimeout才能更好的避免这个问题,在代码本身中执行完毕了,再通过setTimeout来重新设定定时器,把代码加入到执行队列。比如:
setTimeout(function(){
  //many code here...
  setTimeout(arguments.callee,100); //Key
},100);
保护上下文的构造函数
这个主要是避免构造函数在没有使用new来实例化的时候,内部的this指向错误问题。通常没有使用new的话,this一般执行window去了,因此造成了执行错误,给代码带来了灾难。使用下面的方式就可以避免这个问题:
function myClass(name,size){
  if(this instanceof myClass){ //Key,使用instanceof来检测当前实例是否是myClass的实例化对象
    this.name = name;
    this.size = size;
  }else{
    return new myClass(name,size);
  }
}
函数节流
oTrigger.onmouseover=function(e){
  //如果上一个定时器还没有执行,则先清除掉定时器
  oContainer.autoTimeoutId && clearTimeout(oContainer.autoTimeoutId);
  e = e || window.event;
  var target = e.target || e.srcElement;
  if((/li$/i).test(target.nodeName)){
    oContainer.timeoutId = setTimeout(function(){
      addTweenForContainer(oContainer,oTrigger,target);
    },300);
  }
}
自定义事件
学会重构代码的技术,有计划性的重构下自己之前所编写过的一些代码,加强自己掌控代码的能力。
Code review。这里说的review,并不是指个人,而是对团队来说的,一个人编写的代码的想象空间有限,如果在自己编写代码完成之后,邀请其他团队内的伙伴来查看你的代码,及时发现问题以及提出更好的解决方案,这也不失为一种即时重构的方式,提高代码的质量。
编写具体的功能代码之前,首先设计代码、规划代码、组织代码的模式。
在代码的质量、性能、大小之间能作出合理的权衡。
编写阅读性良好、一目了然、扩展性、可维护性良好、重复利用的代码,也是一门艺术。
关注web前端的性能优化,包括Javascript、HTML、CSS、客户端、服务端、前端、后端等整体性的优化。
最后一点或许也是最重要的:善于总结。这点比上面的任何一点都来的重要,因为上面的每一点都是出自这点的积累。
| 欢迎光临 中科因仑“3+1”工程特种兵精英论坛 (http://bbs.enlern.com/) | Powered by Discuz! X3.4 |