高程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 + ")";
    });
相關文章
相關標籤/搜索