前面的话

  函数对任何一门语言来说都是一个核心的概念,在javascript中更是如此。前面曾以深入理解函数系列的形式介绍了函数的相关内容,本文将再深入一步,介绍函数的3个高级技巧

技巧一:作用域安全的构造函数

  构造函数其实就是一个使用new操作符调用的函数

  1. function Person(name,age,job){
  2. this.name=name;
  3. this.age=age;
  4. this.job=job;
  5. }
  6. var person=new Person('match',28,'Software Engineer');
  7. console.log(person.name);//match

  如果没有使用new操作符,原本针对Person对象的三个属性被添加到window对象

  1. function Person(name,age,job){
  2. this.name=name;
  3. this.age=age;
  4. this.job=job;
  5. }
  6. var person=Person('match',28,'Software Engineer');
  7. console.log(person);//undefined
  8. console.log(window.name);//match

  window的name属性是用来标识链接目标和框架的,这里对该属性的偶然覆盖可能会导致页面上的其它错误,这个问题的解决方法就是创建一个作用域安全的构造函数

  1. function Person(name,age,job){
  2. if(this instanceof Person){
  3. this.name=name;
  4. this.age=age;
  5. this.job=job;
  6. }else{
  7. return new Person(name,age,job);
  8. }
  9. }
  10. var person=Person('match',28,'Software Engineer');
  11. console.log(window.name); // ""
  12. console.log(person.name); //'match'
  13. var person= new Person('match',28,'Software Engineer');
  14. console.log(window.name); // ""
  15. console.log(person.name); //'match'

  但是,对构造函数窃取模式的继承,会带来副作用。这是因为,下列代码中,this对象并非Polygon对象实例,所以构造函数Polygon()会创建并返回一个新的实例

  1. function Polygon(sides){
  2. if(this instanceof Polygon){
  3. this.sides=sides;
  4. this.getArea=function(){
  5. return 0;
  6. }
  7. }else{
  8. return new Polygon(sides);
  9. }
  10. }
  11. function Rectangle(wifth,height){
  12. Polygon.call(this,2);
  13. this.width=this.width;
  14. this.height=height;
  15. this.getArea=function(){
  16. return this.width * this.height;
  17. };
  18. }
  19. var rect= new Rectangle(5,10);
  20. console.log(rect.sides); //undefined

  如果要使用作用域安全的构造函数窃取模式的话,需要结合原型链继承,重写Rectangle的prototype属性,使它的实例也变成Polygon的实例

  1. function Polygon(sides){
  2. if(this instanceof Polygon){
  3. this.sides=sides;
  4. this.getArea=function(){
  5. return 0;
  6. }
  7. }else{
  8. return new Polygon(sides);
  9. }
  10. }
  11. function Rectangle(wifth,height){
  12. Polygon.call(this,2);
  13. this.width=this.width;
  14. this.height=height;
  15. this.getArea=function(){
  16. return this.width * this.height;
  17. };
  18. }
  19. Rectangle.prototype= new Polygon();
  20. var rect= new Rectangle(5,10);
  21. console.log(rect.sides);

技巧二:惰性载入函数

  因为各浏览器之间的行为的差异,我们经常会在函数中包含了大量的if语句,以检查浏览器特性,解决不同浏览器的兼容问题。比如,我们最常见的为dom节点添加事件的函数

  1. function addEvent(type, element, fun) {
  2. if (element.addEventListener) {
  3. element.addEventListener(type, fun, false);
  4. }
  5. else if(element.attachEvent){
  6. element.attachEvent('on' + type, fun);
  7. }
  8. else{
  9. element['on' + type] = fun;
  10. }
  11. }

  每次调用addEvent函数的时候,它都要对浏览器所支持的能力进行检查,首先检查是否支持addEventListener方法,如果不支持,再检查是否支持attachEvent方法,如果还不支持,就用dom0级的方法添加事件。这个过程,在addEvent函数每次调用的时候都要走一遍,其实,如果浏览器支持其中的一种方法,那么他就会一直支持了,就没有必要再进行其他分支的检测了。也就是说,if语句不必每次都执行,代码可以运行的更快一些。

  解决方案就是惰性载入。所谓惰性载入,指函数执行的分支只会发生一次

  有两种实现惰性载入的方式

【1】第一种是在函数被调用时,再处理函数。函数在第一次调用时,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了

  我们可以用下面的方式使用惰性载入重写addEvent()

  1. function addEvent(type, element, fun) {
  2. if (element.addEventListener) {
  3. addEvent = function (type, element, fun) {
  4. element.addEventListener(type, fun, false);
  5. }
  6. }
  7. else if(element.attachEvent){
  8. addEvent = function (type, element, fun) {
  9. element.attachEvent('on' + type, fun);
  10. }
  11. }
  12. else{
  13. addEvent = function (type, element, fun) {
  14. element['on' + type] = fun;
  15. }
  16. }
  17. return addEvent(type, element, fun);
  18. }

  在这个惰性载入的addEvent()中,if语句的每个分支都会为addEvent变量赋值,有效覆盖了原函数。最后一步便是调用了新赋函数。下一次调用addEvent()时,便会直接调用新赋值的函数,这样就不用再执行if语句了

  但是,这种方法有个缺点,如果函数名称有所改变,修改起来比较麻烦

【2】第二种是声明函数时就指定适当的函数。 这样在第一次调用函数时就不会损失性能了,只在代码加载时会损失一点性能

  以下就是按照这一思路重写的addEvent()。以下代码创建了一个匿名的自执行函数,通过不同的分支以确定应该使用哪个函数实现

  1. var addEvent = (function () {
  2. if (document.addEventListener) {
  3. return function (type, element, fun) {
  4. element.addEventListener(type, fun, false);
  5. }
  6. }
  7. else if (document.attachEvent) {
  8. return function (type, element, fun) {
  9. element.attachEvent('on' + type, fun);
  10. }
  11. }
  12. else {
  13. return function (type, element, fun) {
  14. element['on' + type] = fun;
  15. }
  16. }
  17. })();

技巧三:函数绑定

  在javascript与DOM交互中经常需要使用函数绑定,定义一个函数然后将其绑定到特定DOM元素或集合的某个事件触发程序上,绑定函数经常和回调函数及事件处理程序一起使用,以便把函数作为变量传递的同时保留代码执行环境

  1. <button id="btn">按钮</button>
  2. <script>
  3. var handler={
  4. message:"Event handled.",
  5. handlerFun:function(){
  6. alert(this.message);
  7. }
  8. };
  9. btn.onclick = handler.handlerFun;
  10. </script>

  上面的代码创建了一个叫做handler的对象。handler.handlerFun()方法被分配为一个DOM按钮的事件处理程序。当按下该按钮时,就调用该函数,显示一个警告框。虽然貌似警告框应该显示Event handled,然而实际上显示的是undefiend。这个问题在于没有保存handler.handleClick()的环境,所以this对象最后是指向了DOM按钮而非handler

  可以使用闭包来修正这个问题

  1. <button id="btn">按钮</button>
  2. <script>
  3. var handler={
  4. message:"Event handled.",
  5. handlerFun:function(){
  6. alert(this.message);
  7. }
  8. };
  9. btn.onclick = function(){
  10. handler.handlerFun();
  11. }
  12. </script>

  当然这是特定于此场景的解决方案,创建多个闭包可能会令代码难以理解和调试。更好的办法是使用函数绑定

  一个简单的绑定函数bind()接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去

  1. function bind(fn,context){
  2. return function(){
  3. return fn.apply(context,arguments);
  4. }
  5. }

  这个函数似乎简单,但其功能是非常强大的。在bind()中创建了一个闭包,闭包使用apply()调用传入的函数,并给apply()传递context对象和参数。当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数

  1. <button id="btn">按钮</button>
  2. <script>
  3. function bind(fn,context){
  4. return function(){
  5. return fn.apply(context,arguments);
  6. }
  7. }
  8. var handler={
  9. message:"Event handled.",
  10. handlerFun:function(){
  11. alert(this.message);
  12. }
  13. };
  14. btn.onclick = bind(handler.handlerFun,handler);
  15. </script>

  ECMAScript5为所有函数定义了一个原生的bind()方法,进一步简化了操作

  只要是将某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就突显出来了。它们主要用于事件处理程序以及setTimeout()和setInterval()。然而,被绑定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用

// 0){
return;
}
if(select[i].getBoundingClientRect().top 0){
change(oCon.children[i+2])
}
}else{
change(oCon.children[select.length+1])
}
}

}
document.body.onmousewheel = wheel;
document.body.addEventListener('DOMMouseScroll',wheel,false);

var oCon = document.getElementById("content");
var close = oCon.getElementsByTagName('span')[0];
close.onclick = function(){
if(this.innerHTML == '显示目录'){
this.innerHTML = '×';
this.style.background = '';
oCon.style.border = '2px solid #ccc';
oCon.style.width = '';
oCon.style.height = '';
oCon.style.overflow = '';
oCon.style.lineHeight = '30px';
}else{
this.innerHTML = '显示目录';
this.style.background = '#3399ff';
oCon.style.border = 'none';
oCon.style.width = '60px';
oCon.style.height = '30px';
oCon.style.overflow = 'hidden';
oCon.style.lineHeight = '';
}
}
for(var i = 2; i

javascript中函数的3个高级技巧的更多相关文章

  1. JavaScript中函数函数的定义与变量的声明<基础知识一>

    1.JavaScript中函数的三种构造方式 a.function createFun(){ } b.var createFun=function (){ } c.var createFun=new ...

  2. Javascript中函数的四种调用方式

    一.Javascript中函数的几个基本知识点: 1.函数的名字只是一个指向函数的指针,所以即使在不同的执行环境,即不同对象调用这个函数,这个函数指向的仍然是同一个函数. 2.函数中有两个特殊的内部属 ...

  3. JavaScript中函数的形参和实参的实现原理剖析

    我们都知道JS里面参数的传递是可以不一样的,比如我们有一个函数: <script type="text/javascript"> function one(a,b,c) ...

  4. JavaScript 中函数节流和函数去抖的讲解

    JavaScript 中函数节流和函数去抖的讲解 我们都知道频繁触发执行一段js逻辑代码对性能会有很大的影响,尤其是在做一些效果实现方面,或者逻辑中需要进行后端请求,更是会导致卡顿,效果失效等结果,所 ...

  5. JavaScript中函数是不能重载原因

    以前有一次写JS插件的时候,由于后台写习惯了,妄想在JS中写重载函数,可惜不能成功,原因花了一点时间记了下来 首先要理解重载的含义:函数返回值不同或者形式参数个数不同但函数名相同的函数 JavasSc ...

  6. JavaScript中函数的调用

    JavaScript中函数的调用 制作人:全心全意 在JavaScript中,函数定义后并不会自动执行,要执行一个函数需要在特定的位置调用该函数,调用函数需要创建调用语句,调用语句包含函数名称和参数. ...

  7. JavaScript中函数的定义

    JavaScript中函数的定义 制作人:全心全意 在JavaScript中,函数是由关键字function.函数名加一组参数以及置于大括号中需要执行的一段代码定义的.定义函数的基本语法格式如下: f ...

  8. JavaScript中函数作为另一个函数的参数的时候它存在于哪个作用域

    一直对函数作为参数被传递进另外一个函数理解的不是很清除.先看下这段代码吧: function test(fn){ var bar = 1; fn(); } var bar = 99; test(fun ...

  9. javascript中函数声明、变量声明以及变量赋值之间的关系与影响

    javascript中函数声明.变量声明以及变量赋值之间的关系与影响 函数声明.变量声明以及变量赋值之间有以下几点共识: 1.所有的全局变量都是window的属性 2.函数声明被提升到范围作用域的顶端 ...

随机推荐

  1. 在linux中设置静态ip地址

    在linux中设置静态ip地址1.在终端中输入:vi /etc/sysconfig/network-scripts/ifcfg-eth0 2.开始编辑,填写ip地址.子网掩码.网关.DNS等[root ...

  2. js库

    lanchpad用的js库 http://lesscss.org/ https://github.com/EightMedia/hammer.js/wiki/Getting-Started http: ...

  3. spark 入门整理

    1.第一个概念:RDD RDD(Resilient DistributedDatasets) ,弹性分布式数据集,是分布式内存的一个抽象概念,RDD提供了一种高度受限的共享内存模型,即RDD是只读的记 ...

  4. PHP的数组排序函数

    <?php class order{ /** * * 数组排序 * @param array $arr 例如: * array ( array ( 'deskId' => '460646' ...

  5. ThreadLocal类详解:原理、源码、用法

    以下是本文目录: 1.从数据库连接探究 ThreadLocal 2.剖析 ThreadLocal 源码 3. ThreadLocal 应用场景 4. 通过面试题理解 ThreadLocal 1.从数据 ...

  6. 验证mongodb主从复制过程~记录操作

    接 mongodb的安装:http://www.cnblogs.com/myrunning/p/4319367.html 1.1创建数据目录 在这里我们将不使用mongodb的配置文件启动mongod ...

  7. MaxTemperature程序Mapper ClassNotFoundException

    错误: 执行hadoop权威指南上MaxTemperature程序出现Mapper类ClassNotFoundException异常: 解决: 将书上的 JobConf job = new JobCo ...

  8. SQLServer存储过程事务用法

    更多资源:http://denghejun.github.io IF object_id('InsertAntennaProcedure') IS NOT NULL DROP PROCEDURE In ...

  9. SQL Server 2016五大优势挖掘企业用户数据价值

    SQL Server 2016五大优势挖掘企业用户数据价值 转载自:http://soft.zdnet.com.cn/software_zone/2016/0318/3074442.shtml 3月1 ...

  10. ASP.NET Core 数据保护(Data Protection)【中】

    前言 上篇主要是对 ASP.NET Core 的 Data Protection 做了一个简单的介绍,本篇主要是介绍一下API及使用方法. API 接口 ASP.NET Core Data Prote ...