javascript中函数的3个高级技巧

AimGriffin 8年前
   <h2><strong>前面的话</strong></h2>    <p>函数对任何一门语言来说都是一个核心的概念,在javascript中更是如此。前面曾以深入理解函数系列的形式介绍了函数的相关内容,本文将再深入一步,介绍函数的3个高级技巧</p>    <h3><strong>技巧一:作用域安全的构造函数</strong></h3>    <p>构造函数其实就是一个使用new操作符调用的函数</p>    <pre>  <code class="language-javascript">function Person(name,age,job){      this.name=name;      this.age=age;      this.job=job;  }  var person=new Person('match',28,'Software Engineer');  console.log(person.name);//match  </code></pre>    <p>如果没有使用new操作符,原本针对Person对象的三个属性被添加到window对象</p>    <pre>  <code class="language-javascript">function Person(name,age,job){      this.name=name;      this.age=age;      this.job=job;  }            var person=Person('match',28,'Software Engineer');  console.log(person);//undefined  console.log(window.name);//match  </code></pre>    <p>window的name属性是用来标识链接目标和框架的,这里对该属性的偶然覆盖可能会导致页面上的其它错误,这个问题的解决方法就是创建一个作用域安全的构造函数</p>    <pre>  <code class="language-javascript">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);      }  }  var person=Person('match',28,'Software Engineer');  console.log(window.name); // ""  console.log(person.name); //'match'  var person= new Person('match',28,'Software Engineer');  console.log(window.name); // ""  console.log(person.name); //'match'  </code></pre>    <p>但是,对构造函数窃取模式的继承,会带来副作用。这是因为,下列代码中,this对象并非Polygon对象实例,所以构造函数Polygon()会创建并返回一个新的实例</p>    <pre>  <code class="language-javascript">function Polygon(sides){      if(this instanceof Polygon){          this.sides=sides;          this.getArea=function(){              return 0;          }      }else{          return new Polygon(sides);      }  }  function  Rectangle(wifth,height){      Polygon.call(this,2);      this.width=this.width;      this.height=height;      this.getArea=function(){          return this.width * this.height;      };  }  var rect= new Rectangle(5,10);  console.log(rect.sides); //undefined  </code></pre>    <p>如果要使用作用域安全的构造函数窃取模式的话,需要结合原型链继承,重写Rectangle的prototype属性,使它的实例也变成Polygon的实例</p>    <pre>  <code class="language-javascript">function Polygon(sides){      if(this instanceof Polygon){          this.sides=sides;          this.getArea=function(){              return 0;          }      }else{          return new Polygon(sides);      }  }  function  Rectangle(wifth,height){      Polygon.call(this,2);      this.width=this.width;      this.height=height;      this.getArea=function(){          return this.width * this.height;      };  }  Rectangle.prototype= new Polygon();  var rect= new Rectangle(5,10);  console.log(rect.sides); //2  </code></pre>    <h3><strong>技巧二:惰性载入函数</strong></h3>    <p>因为各浏览器之间的行为的差异,我们经常会在函数中包含了大量的if语句,以检查浏览器特性,解决不同浏览器的兼容问题。比如,我们最常见的为dom节点添加事件的函数</p>    <pre>  <code class="language-javascript">function addEvent(type, element, fun) {      if (element.addEventListener) {          element.addEventListener(type, fun, false);      }      else if(element.attachEvent){          element.attachEvent('on' + type, fun);      }      else{          element['on' + type] = fun;      }  }  </code></pre>    <p>每次调用addEvent函数的时候,它都要对浏览器所支持的能力进行检查,首先检查是否支持addEventListener方法,如果不支持,再检查是否支持attachEvent方法,如果还不支持,就用dom0级的方法添加事件。这个过程,在addEvent函数每次调用的时候都要走一遍,其实,如果浏览器支持其中的一种方法,那么他就会一直支持了,就没有必要再进行其他分支的检测了。也就是说,if语句不必每次都执行,代码可以运行的更快一些。</p>    <p>解决方案就是惰性载入。所谓惰性载入,指函数执行的分支只会发生一次</p>    <p>有两种实现惰性载入的方式</p>    <p>【1】第一种是在函数被调用时,再处理函数。函数在第一次调用时,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了</p>    <p>我们可以用下面的方式使用惰性载入重写addEvent()</p>    <pre>  <code class="language-javascript">function addEvent(type, element, fun) {      if (element.addEventListener) {          addEvent = function (type, element, fun) {              element.addEventListener(type, fun, false);          }      }      else if(element.attachEvent){          addEvent = function (type, element, fun) {              element.attachEvent('on' + type, fun);          }      }      else{          addEvent = function (type, element, fun) {              element['on' + type] = fun;          }      }      return addEvent(type, element, fun);  }  </code></pre>    <p>在这个惰性载入的addEvent()中,if语句的每个分支都会为addEvent变量赋值,有效覆盖了原函数。最后一步便是调用了新赋函数。下一次调用addEvent()时,便会直接调用新赋值的函数,这样就不用再执行if语句了</p>    <p>但是,这种方法有个缺点,如果函数名称有所改变,修改起来比较麻烦</p>    <p>【2】第二种是声明函数时就指定适当的函数。 这样在第一次调用函数时就不会损失性能了,只在代码加载时会损失一点性能</p>    <p>以下就是按照这一思路重写的addEvent()。以下代码创建了一个匿名的自执行函数,通过不同的分支以确定应该使用哪个函数实现</p>    <pre>  <code class="language-javascript">var addEvent = (function () {      if (document.addEventListener) {          return function (type, element, fun) {              element.addEventListener(type, fun, false);          }      }      else if (document.attachEvent) {          return function (type, element, fun) {              element.attachEvent('on' + type, fun);          }      }      else {          return function (type, element, fun) {              element['on' + type] = fun;          }      }  })();  </code></pre>    <h3><strong>技巧三:函数绑定</strong></h3>    <p>在javascript与DOM交互中经常需要使用函数绑定,定义一个函数然后将其绑定到特定DOM元素或集合的某个事件触发程序上,绑定函数经常和回调函数及事件处理程序一起使用,以便把函数作为变量传递的同时保留代码执行环境</p>    <pre>  <code class="language-javascript"><button id="btn">按钮</button>  <script>                  var handler={          message:"Event handled.",          handlerFun:function(){              alert(this.message);          }      };  btn.onclick = handler.handlerFun;  </script>  </code></pre>    <p>上面的代码创建了一个叫做handler的对象。handler.handlerFun()方法被分配为一个DOM按钮的事件处理程序。当按下该按钮时,就调用该函数,显示一个警告框。虽然貌似警告框应该显示Event handled,然而实际上显示的是undefiend。这个问题在于没有保存handler.handleClick()的环境,所以this对象最后是指向了DOM按钮而非handler</p>    <p>可以使用闭包来修正这个问题</p>    <pre>  <code class="language-javascript"><button id="btn">按钮</button>  <script>              var handler={      message:"Event handled.",      handlerFun:function(){          alert(this.message);      }  };  btn.onclick = function(){      handler.handlerFun();      }  </script>  </code></pre>    <p>当然这是特定于此场景的解决方案,创建多个闭包可能会令代码难以理解和调试。更好的办法是使用函数绑定</p>    <p>一个简单的绑定函数bind()接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去</p>    <pre>  <code class="language-javascript">function bind(fn,context){      return function(){          return fn.apply(context,arguments);      }  }  </code></pre>    <p>这个函数似乎简单,但其功能是非常强大的。在bind()中创建了一个闭包,闭包使用apply()调用传入的函数,并给apply()传递context对象和参数。当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数</p>    <pre>  <code class="language-javascript"><button id="btn">按钮</button>  <script>    function bind(fn,context){      return function(){          return fn.apply(context,arguments);      }  }            var handler={      message:"Event handled.",      handlerFun:function(){          alert(this.message);      }  };  btn.onclick = bind(handler.handlerFun,handler);  </script>  </code></pre>    <p>ECMAScript5为所有函数定义了一个原生的bind()方法,进一步简化了操作</p>    <p>只要是将某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就突显出来了。它们主要用于事件处理程序以及setTimeout()和setInterval()。然而,被绑定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用</p>    <p> </p>    <p>来自:http://www.cnblogs.com/xiaohuochai/p/5893898.html</p>    <p> </p>