高程3总结#第22章高级技巧

高级技巧

高级函数

安全的类型检测

  • typeof操做符在检测数据类型时,可能会获得不靠谱的结果
  • instanceof操做符在存在多个全局做用域,也就是页面包含多个iframe的状况下,也会出现问题
  • 在任何值上调用Object原生的toString()方法,都会返回一个[object NativeConstructorName]格式的字符串
  • 原生数组的构造函数名与全局做用域无关,所以使用toString()就能保证返回一致的值javascript

    function isArray(value){
      return Object.prototype.toString.call(value)=="[object Array]";
    }
  • 基于这一思路来测试某个值是否是原生函数或正则表达式java

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

做用域安全的构造函数

  • 做用域安全的构造函数在进行任何更改以前,首先确认this对象是正确类型的实例,若是不是,那么会建立新的实例并返回正则表达式

    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 person1 = Person("Nicholas", 29, "Software Engineer");
    alert(window.name); //""
    alert(person1.name); //"Nicholas"
    var person2 = new Person("Shelby", 34, "Ergonomist");
    alert(person2.name); //"Shelby
  • 使用构造函数窃取模式的继承且不适用原型链,这个继承可能被破坏数组

    function Polygon(sides){
      if (this instanceof Polygon) {
        this.sides = sides;
        this.getArea = function(){
          return 0;
        };
      } else {
        return new Polygon(sides);
      }
    }
    function Rectangle(width, height){
      Polygon.call(this, 2);
      this.width = width;
      this.height = height;
      this.getArea = function(){
        return this.width * this.height;
      };
    }
    var rect = new Rectangle(5, 10);
    alert(rect.sides); //undefined
  • 上面的代码中,Polygon构造函数是做用域安全的,然而Rectangle构造函数则不是。若是构造函数窃取结合使用原型链或者寄生组合则能够解决这个问题安全

    function Polygon(sides){
      if (this instanceof Polygon) {
        this.sides = sides;
        this.getArea = function(){
          return 0;
        };
      } else {
        return new Polygon(sides);
      }
    }
    function Rectangle(width, height){
      Polygon.call(this, 2);
      this.width = width;
      this.height = height;
      this.getArea = function(){
        return this.width * this.height;
      };
    }
    Rectangle.prototype = new Polygon();
    var rect = new Rectangle(5, 10);
    alert(rect.sides); //2

惰性载入函数

  • 惰性载入表示函数执行的分支仅会发生一次。闭包

    • 第一种实现惰性载入的方法,在函数被调用时再处理函数。在第一次调用的过程当中,该函数会覆盖为另外一个按什么时候方式执行的函数,这样任何对原函数的调用都不用再通过执行的分支了app

      function createXHR(){
        if (typeof XMLHttpRequest != "undefined"){
          createXHR = function(){
            return new XMLHttpRequest();
          };
        } else if (typeof ActiveXObject != "undefined"){
          createXHR = function(){
            if (typeof arguments.callee.activeXString != "string"){
              var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
                              "MSXML2.XMLHttp"],
                  i, len;
              for (i=0,len=versions.length; i < len; i++){
                try {
                  new ActiveXObject(versions[i]);
                  arguments.callee.activeXString = versions[i];
                  break;
                } catch (ex){
                  //skip
                }
              }
            }
            return new ActiveXObject(arguments.callee.activeXString);
          };
        } else {
          createXHR = function(){
            throw new Error("No XHR object available.");
          };
        }
        return createXHR();
      }
    • 第二种实现惰性载入的方式是在声明函数时就指定适当的函数,这样,第一次调用函数时就不会丧失性能了,而在代码首次加载的时候回损失一点性能ide

      var createXHR = (function(){
        if (typeof XMLHttpRequest != "undefined"){
          return function(){
            return new XMLHttpRequest();
          };
        } else if (typeof ActiveXObject != "undefined"){
          return function(){
            if (typeof arguments.callee.activeXString != "string"){
              var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
                              "MSXML2.XMLHttp"],
                  i, len;
              for (i=0,len=versions.length; i < len; i++){
                try {
                  new ActiveXObject(versions[i]);
                  arguments.callee.activeXString = versions[i];
                  break;
                } catch (ex){
                  //skip
                }
              }
            }
            return new ActiveXObject(arguments.callee.activeXString);
          };
        } else {
          return function(){
            throw new Error("No XHR object available.");
          };
        }
      })();

函数绑定

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

    function bind(fn, context){
      return function(){
        return fn.apply(context, arguments);
      };
    }
  • 当调用返回的函数时,它会在给定环境中执行被传入的函数并给出全部参数性能

    var handler = {
      message: "Event handled",
      handleClick: function(event){
        alert(this.message);
      }
    };
    var btn = document.getElementById("my-btn");
    EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler))
  • ECMAScript5为全部函数定义了一个原生的bind()方法,进一步简单了操做,不用再本身定义bind()函数了,而是能够直接在函数上调用这个方法

    var handler = {
      message: "Event handled",
      handleClick: function(event){
        alert(this.message + ":" + event.type);
      }
    };
    var btn = document.getElementById("my-btn");
    EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));

函数的柯里化

  • 用于建立已经设置好了一个或者多个参数的函数。函数柯里化的基本方法和函数绑定是同样的。使用一个闭包返回一个函数,二者的区别在于,当函数被调用时,返回的函数还须要设置一些传入的参数
  • 柯里化函数一般由一下步骤动态建立:调用另外一个函数并为它传入要柯里化的函数和必要参数

    function curry(fn){
      var args = Array.prototype.slice.call(arguments, 1);
      return function(){
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return fn.apply(null, finalArgs);
      };
    }
  • ECMAScript5的bind()方法也实现函数柯里化,只要在this的值以后再传入另外一个参数便可

    var handler = {
      message: "Event handled",
      handleClick: function(name, event){
        alert(this.message + ":" + name + ":" + event.type);
      }
    };
    var btn = document.getElementById("my-btn");
    EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn"));

防篡改对象

不可扩展对象

  • 默认状况下,全部对象都是能够扩展的,也就是说,任什么时候候均可以向对象中添加属性和方法
  • Object.preventExtensions()方法能够改变这个行为,不能再给对象添加属性和方法

    var person={name:"Nicholas"};
    Object.preventExtensions(person);
    person.age=29;
    alert(person.age);//undefined
  • Object.isExtensible()方法还能够肯定对象是否能够扩展

    var person={name:"Nicholas"};
    alert(Object.isExtensible(person));//true
    Object.preventExtensions(person);
    alert(Object.isExtensible(person));//true

密封的对象

  • 封闭对象不可扩展,并且已有成员[Configurable]特性将被设置为false,这就意味着不能删除属性和方法,由于不能使用Object.defineProperty()把数据属性修改成访问器属性

    var person = { name: "Nicholas" };
    Object.seal(person);
    person.age = 29;
    alert(person.age); //undefined
    delete person.name;
    alert(person.name); //"Nicholas"
  • Object.isSealed()方法能够肯定对象是否被密封了,由于被密封的对象不可扩展,因此用Object.isExtensible()检测密封的对象也会返回false

    var person = { name: "Nicholas" };
    alert(Object.isExtensible(person)); //true
    alert(Object.isSealed(person)); //false
    Object.seal(person);
    alert(Object.isExtensible(person)); //false
    alert(Object.isSealed(person)); //true

冻结的对象

  • 冻结的对象既不可扩展又是密封的,并且对象数据属性的[Writable]特性会被设置为false,若是定义[Set]函数,访问器属性仍然是可写的。ECMAScript5丁意思的Object.freeze()方法能够用来冻结对象

    var person = { name: "Nicholas" };
    Object.freeze(person);
    person.age = 29;
    alert(person.age); //undefined
    delete person.name;
    alert(person.name); //"Nicholas"
    person.name = "Greg";
    alert(person.name); //"Nicholas"
  • Object.isFrozen()方法用于检测冻结对象,由于冻结对象既是密封的又是不可扩展的,因此Object.isExtensible()和Object.isSealed()检测冻结对象将分别返回false和true

    var person = { name: "Nicholas" };
    alert(Object.isExtensible(person)); //true
    alert(Object.isSealed(person)); //false
    alert(Object.isFrozen(person));  //false
    Object.freeze(person);
    alert(Object.isExtensible(person)); //false
    alert(Object.isSealed(person)); //true
    alert(Object.isFrozen(person)); //true

高级定时器

重复的定时器

  • 使用setInterval()建立的定时器确保了定时器代码规则地插入队列中
  • 使用setInterval()时,仅当没有该定时器的任何其余代码实现时,才将定时器代码添加到队列中,这确保了定时器加入到队列中的最小时间间隔为指定间隔
  • 重复定时器有两个问题:某些间隔会被跳过;多个定时器的代码执行之间的间隔可能会比预期小
  • 为了不setInterval()的重复定时器的两个问题,使用链式setTimeout()调用

    setTimeout(function(){
      //处理中
      setTimeout(arguments.callee, interval);
    }, interval);
  • 这个模式主要用于重复定时器

    setTimeout(function(){
      var div = document.getElementById("myDiv");
      left = parseInt(div.style.left) + 5;
      div.style.left = left + "px";
      if (left < 200){
        setTimeout(arguments.callee, 50);
      }
    }, 50)

Yielding Processes

  • 脚本长时间运行的问题一般是由两个缘由之一形成的:过长的、过深嵌套的函数调用或者是进行大量处理的循环
  • 两个重要问题:该处理是否必须同步完成;数据是否必须按顺序完成
  • 若是两个问题回答都是否,可使用定时器分隔这个循环,即数组分块技术
  • 小块小块地处理数组,一般每次一小块,基本的思路是为要处理的项目建立一个队列,而后使用定时器取出下一个要处理的项目进行处理,接着再设置另外一个定时器

    setTimeout(function(){
      //取出下一个条目并处理
      var item = array.shift();
      process(item);
      //若还有条目,再设置另外一个定时器
      if(array.length > 0){
        setTimeout(arguments.callee, 100);
      }
    }, 100);
  • 数组分块模式中,array变量本质上就是一个列表,包含了要处理的项目。使用shift()方法获取队列中要处理的项目,而后将其传递给某个函数。若是在队列中还有其余项目,则设置另外一个定时器,并经过arguments.callee调用同一个匿名函数,要实现数组分块很是简单,可使用下面的函数

    function chunk(array, process, context){
      setTimeout(function(){
        var item = array.shift();
        process.call(context, item);
        if (array.length > 0){
          setTimeout(arguments.callee, 100);
        }
      }, 100);
    }
  • chunk()方法接收三个参数:要处理的项目的数组,用于处理项目的函数,以及可选的运行该函数的环境

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

函数节流

  • 函数节流背后的基本思想是指,某些代码不能够在没有间断的状况连续重复执行。第一次调用函数,建立一个定时器,在指定的时间间隔以后运行代码。当第二次调用该函数时,它会清除前一次的定时器并设置另外一个。若是前一个定时器已经执行过了,这个操做就没有任何意义。然而,若是前一个定时器还没有执行,其实就是将其替换为一个新的定时器。目的是只有在执行函数的请求中止了一段时间以后才执行。

    var processor = {
      timeoutId: null,
      //实际进行处理的方法
      performProcessing: function(){
        //实际执行的代码
      },
      //初始处理调用的方法
      process: function(){
        clearTimeout(this.timeoutId);
        var that = this;
        this.timeoutId = setTimeout(function(){
          that.performProcessing();
        }, 100);
      }
    };
    //尝试开始执行
    processor.process();

自定义事件

  • 观察者模式由两类对象组成:主体和观察者。主体负责发布事件,同时观察者经过订阅这些事件来观察该主体
  • 自定义事件背后的概念是建立一个管理事件的对象,让其余对象监听事件

    function EventTarget(){
      this.handlers = {};
    }
    EventTarget.prototype = {
      constructor: EventTarget,
      addHandler: function(type, handler){
        if (typeof this.handlers[type] == "undefined"){
          this.handlers[type] = [];
        }
        this.handlers[type].push(handler);
      },
      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: 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);
        }
      }
    };
  • EventTarget类型有一个单独的属性handlers ,用于储存事件处理程序。
  • 还有三个方法:

    • addHandler() ,用于注册给定类型事件的事件处理程序

      • addHandler() 方法接受两个参数:事件类型和用于处理该事件的函数。当调用该方法时,会进行一次检查,看看 handlers 属性中是否已经存在一个针对该事件类型的数组;若是没有,则建立一个新的。而后使用 push() 将该处理程序添加到数组的末尾。
    • fire() ,用于触发一个事件

      • 若是要触发一个事件,要调用 fire() 函数。该方法接受一个单独的参数,是一个至少包含 type属性的对象。 fire() 方法先给 event 对象设置一个 target 属性,若是它还没有被指定的话。而后它就查找对应该事件类型的一组处理程序,调用各个函数,并给出 event 对象。
    • removeHandler() ,用于注销某个事件类型的事件处理程序。

      • removeHandler() 方法是 addHandler() 的辅助,它们接受的参数同样:事件的类型和事件处理程序。这个方法搜索事件处理程序的数组找到要删除的处理程序的位置。若是找到了,则使用 break操做符退出 for 循环。而后使用 splice() 方法将该项目从数组中删除。
function handleMessage(event){
  alert("Message received: " + event.message);
}
//建立一个新对象
var target = new EventTarget();
//添加一个事件处理程序
target.addHandler("message", handleMessage);
//触发事件
target.fire({ type: "message", message: "Hello world!"});
//删除事件处理程序
target.removeHandler("message", handleMessage);
//再次,应没有处理程序
target.fire({ type: "message", message: "Hello world!"});

拖放

  • 基本实现过程

    var DragDrop = function(){
      var dragging = null;
      function handleEvent(event){
        //获取事件和目标
        event = EventUtil.getEvent(event);
        var target = EventUtil.getTarget(event);
        //肯定事件类型
        switch(event.type){
          case "mousedown":
            if (target.className.indexOf("draggable") > -1){
              dragging = target;
            }
            break;
          case "mousemove":
            if (dragging !== null){
              //指定位置
              dragging.style.left = event.clientX + "px";
              dragging.style.top = event.clientY + "px";
            }
            break;
          case "mouseup":
            dragging = null;
            break;
        }
      };
      //公共接口
      return {
        enable: function(){
          EventUtil.addHandler(document, "mousedown", handleEvent);
          EventUtil.addHandler(document, "mousemove", handleEvent);
          EventUtil.addHandler(document, "mouseup", handleEvent);
        },
        disable: function(){
          EventUtil.removeHandler(document, "mousedown", handleEvent);
          EventUtil.removeHandler(document, "mousemove", handleEvent);
          EventUtil.removeHandler(document, "mouseup", handleEvent);
        }
      }
    }();

修缮拖动功能

图片描述

  • 为防止出现上图状况。计算元素左上角和指针位置之间的差值。这个差值应该在 mousedown 事件发生的时候肯定,而且一直保持,直到 mouseup 事件发生。经过将 event的 clientX 和 clientY 属性与该元素的 offsetLeft 和 offsetTop 属性进行比较,就能够算出水平方向和垂直方向上须要多少空间

    var DragDrop = function(){
      var dragging = null;
      diffX = 0;
      diffY = 0;
      function handleEvent(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", handleEvent);
          EventUtil.addHandler(document, "mousemove", handleEvent);
          EventUtil.addHandler(document, "mouseup", handleEvent);
        },
        disable: function(){
          EventUtil.removeHandler(document, "mousedown", handleEvent);
          EventUtil.removeHandler(document, "mousemove", handleEvent);
          EventUtil.removeHandler(document, "mouseup", handleEvent);
        }
      }
    }()

添加自定义事件

var DragDrop = function(){
var dragdrop = new EventTarget(),
    dragging = null,
    diffX = 0,
    diffY = 0;
function handleEvent(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;
        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", handleEvent);
  EventUtil.addHandler(document, "mousemove", handleEvent);
  EventUtil.addHandler(document, "mouseup", handleEvent);
};
dragdrop.disable = function(){
  EventUtil.removeHandler(document, "mousedown", handleEvent);
  EventUtil.removeHandler(document, "mousemove", handleEvent);
  EventUtil.removeHandler(document, "mouseup", handleEvent);
};
return dragdrop;
}();
  • 这段代码定义了三个事件: dragstart 、 drag 和 dragend 。它们都将被拖动的元素设置为了 target ,并给出了 x 和 y 属性来表示当前的位置。它们触发于 dragdrop 对象上,以后在返回对象前给对象增长 enable() 和 disable() 方法。这些模块模式中的细小更改令 DragDrop 对象支持了事件

    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/> Dropped " + event.target.id + " at (" + event.x +
        "," + event.y + ")";
    });
相关文章
相关标签/搜索