高級程序設計(第3版)第二十二章高級技巧/筆記

高級技巧

  • 高級函數
    • 安全的類型檢測
function isArray(value){     
  return Object.prototype.toString.call(value) == "[object Array]";
}  
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); 
  }
} 
//本來,像這種沒有使用new就直接調用person的,this會被解析成window 對象
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);    //返回一個新的Polygon對象,裏面的屬性方法不會成爲Rectangle的屬性和方法
  this.width = width; 
  this.height = height;
    this.getArea = function(){
    return this.width * this.height; 
  };
} 
----------------------------------------------------

var rect = new Rectangle(5, 10);
alert(rect.sides); //undefined 

----------------------------------------------------

Rectangle.prototype = new Polygon(); //使得,一個Rectangle實例也同時是一個Polygon實例
var rect = new Rectangle(5, 10); 
alert(rect.sides);        //2

 

 

    • 惰性載入函數
      • 惰性載入表示函數執行的分支僅會發生一次,避免了大量的if語句,避免了沒必要要的代碼
//法一:
//在第一次調用的過程當中,該函數會被覆蓋爲另一個按合適方式執行的函數,
//這樣任何對原函數的調用都不用再通過執行的分支了
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(); 
} 

 

//第二種實現惰性載入的方式是在聲明函數時就指定適當的函數。
//這樣,第一次調用函數時就不會損 失性能了,而在代碼首次加載時會損失一點性能
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.");   
    };    
  }
})(); 

 

 

    • 函數綁定
      • 函數綁定要建立一個函數,能夠在特定的 this 環境中 以指定參數調用另外一個函數。
      • 該技巧常和回調函數與事件處理程序一塊兒使用,以便在將函數做爲變量傳遞的同時保留代碼執行環境
      • 被綁 定函數與普通函數相比有更多的開銷,它們須要更多內存,同時也由於多重函數調用稍微慢一點,所 以好只在必要時使用。
      • 一個簡單的 bind()函數接受一個函數和一個環境,並返回一個在給定環境中調用給定函數的函數, 而且將全部參數原封不動傳遞過去
function bind(fn, context){ 
  return function(){ 
    return fn.apply(context, arguments); 
  };
} 
//注意這裏使用的 arguments 對象是內部函 數的,而非 bind()的。
//當調用返回的函數時,它會在給定環境中執行被傳入的函數並給出全部參數

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

//原生的 bind()方法
//不用 再本身定義 bind()函數了,而是能夠直接在函數上調用這個方法
var btn = document.getElementById("my-btn"); 
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));

 

 

    • 函數柯里化
      • 函數柯里化的基本方法和函數綁定是同樣的:使用一個閉包返回一個函數。
      • 二者的區別在於,當函數被調用時,返回的函數還須要設置一些傳入的參數。
      • 柯里化函數一般由如下步驟動態建立:調用另外一個函數併爲它傳入要柯里化的函數和必要參數

 

  • 防篡改對象
    • 不可擴展對象
      • 默認狀況下,全部對象都是能夠擴展的。也就是說,任什麼時候候均可以向對象中添加屬性和方法
      • Object.preventExtensions()方法能夠改變這個行爲,讓你不能再給對象添加屬性和方法。
      • 雖然不能給對象添加新成員,但已有的成員則絲絕不受影響。你仍然還能夠修改和刪除已有的成員
      • 另外,使用 Object.istExtensible()方法還能夠肯定對象是否能夠擴展。  

 

var person = {
  name: "Nicholas"
}; 
Object.preventExtensions(person); 
person.age = 29; 
alert(person.age); //undefined 

//------------------------------------------------

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

 

    • 密封的對象
      • 密封對象不可擴展,而 且已有成員的[[Configurable]]特性將被設置爲 false。這就意味着不能刪除屬性和方法,由於不能 使用 Object.defineProperty()把數據屬性修改成訪問器屬性,或者相反。
      • 屬性值是能夠修改的。
      • 使用 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]]函數,訪問器屬性仍然是可寫的。
      • Object.freeze()方法能夠用來凍結對象。
      • Object.isFrozen()方法用於檢測凍結對象
      • 由於凍結對象既是密封的又是不可 擴展的,因此用 Object.isExtensible()和 Object.isSealed()檢測凍結對象將分別返回 false 和 true。  

 

  • 高級定時器
    1. 瀏覽器負責進行排序,指派某段代碼在某個時間點運行 的優先級
    2. 在 JavaScript中沒有任何代碼是馬上執行的,但一旦進程空閒則儘快執行
    3. 定時器對隊列的工做方式是,當特定時間過去後將代碼插入。
    4. 設定一個 150ms後執行的定時器不表明到了 150ms代碼就馬上 執行,它表示代碼會在 150ms後被加入到隊列中
    • 重複的定時器
      • 定時器代碼可能在代碼再次被添加到隊列以前尚未完成執行,結果致使定時器代碼連續運行好幾回, 而之間沒有任何停頓。
      • 當使用 setInterval()時,僅 當沒有該定時器的任何其餘代碼實例時,纔將定時器代碼添加到隊列中。
      • 這種重複定時器的規則有兩個問題:
        • 某些間隔會被跳過
        • 多個定時器的代碼執行之間的間隔 可能會比預期的小。
      • 解決:使用鏈式setTimeout() 調用
setTimeout(function(){ 
 
    //處理中 
 
    setTimeout(arguments.callee, interval); 
 
}, interval); 

//這個模式鏈式調用了 setTimeout(),每次函數執行的時候都會建立一個新的定時器。
//第二個 setTimeout()調用使用了 arguments.callee 來獲取對當前執行的函數的引用,併爲其設置另一個定時器。
//這樣作的好處是,在前一個定時器代碼執行完以前,不會向隊列插入新的定時器代碼,確保 不會有任何缺失的間隔。
//並且,它能夠保證在下一次定時器代碼執行以前,至少要等待指定的間隔,避免了連續的運行。

 

    • Yielding Processes 二刷預約
      • 腳本長時間運行的問題一般有兩個緣由
        • 過長的、過深嵌套的函數調用或者是進行大 量處理的循環
      • 當你發現某個循環佔用了大量時間,同時對於「該處理是否必須同步完成?」和「數據是否必須按順序完成?」兩個問題,你的回答都是「否」時,你就能夠 使用定時器分割這個循環。這是一種叫作數組分塊(array chunking)的技術,小塊小塊地處理數組,通 常每次一小塊。基本的思路是爲要處理的項目建立一個隊列,而後使用定時器取出下一個要處理的項目 進行處理,接着再設置另外一個定時器。
//基本模式
setTimeout(function(){  
  //取出下一個條目並處理
  var item = array.shift();
  process(item); 
 
  
  //若還有條目,再設置另外一個定時器 
  if(array.length > 0){  
    setTimeout(arguments.callee, 100); 
  } 
}, 100)
 

function chunk(array, process, context){
  setTimeout(function(){  
    var item = array.shift();   
    process.call(context, item); 
 
        if (array.length > 0){  
          setTimeout(arguments.callee, 100);    
        }   
  }, 100);
} 

 

    • 函數節流
      • 函數節流背後的基本思想是指,某些代碼不能夠在沒有間斷的狀況連續重複執行。
//基本模式
var processor = {    
  timeoutId: null, 
 
    //實際進行處理的方法 
  performProcessing: function(){
    //實際執行的代碼  
  }, 
 
    //初始處理調用的方法 
  process: function(){ 
    clearTimeout(this.timeoutId); 
    var that = this;    
    this.timeoutId = setTimeout(function(){    
      that.performProcessing();   
    }, 100);   
  } 
}; 
 
//嘗試開始執行  processor.process(); 

 

      • throttle()函數
        • 能夠自動進行定時器的設置和清除
        • 接受兩個參數:要執行的函數以及在哪一個做用域中執行
      • 節流在 resize 事件中是經常使用的
      • 只要代碼是週期性執行的,都應該使用節流,可是你不能控制請求執行的速率。
//簡化版(使用 throttle()函數)
function throttle(method, context) {  
  clearTimeout(method.tId);    
  method.tId= setTimeout(function(){    
    method.call(context);    
  }, 100); 
} 

 

  • 自定義事件
function EventTarget(){   
  this.handlers = {}; //用於儲存事件處理程序
} 
 
EventTarget.prototype = {
  constructor: EventTarget,   
  addHandler: function(type, handler){  //法接受兩個參數:事件類型和用於處理該事件的函數
    if (typeof this.handlers[type] == "undefined"){ //handlers 屬性中是否已經存在一個針對該事件類型的數組
      this.handlers[type] = [];   //;若是沒有,則建立一個新的數組
    } 
 
   this.handlers[type].push(handler); //使用 push()將該處理程序添加到數組的末尾。 
  }, 
 
    fire: function(event){    //接受一個單獨的參數,是一個至少包含 type 屬性的對象。
      if (!event.target){   //給 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);
      }  
    }
}; 

 

  • 拖放

拖放的基本概念很簡單:建立一個絕對定位的元素,使其能夠用鼠標移動javascript

    • 修繕拖動功能
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); 
        } 
   } 
}();     
View Code

 

    • 添加自定義事件二刷預約

//這一章看得我眼珠子都要掉了,難頂,緩一緩再二刷叭~java

相關文章
相關標籤/搜索