跳到主要內容
黯羽輕揚每天積累一點點

JS 學習筆記 11_高級技巧

免費2015-04-13#JS#js高级技巧

本文介紹類型檢測,作用域安全的建構函式,惰性載入,函式綁定,函式柯里化(函式套用),防篡改物件,函式節流,觀察者模式等高級技巧

##1. 類型檢測

typeof 有時返回值不合理,比如 RegExp 物件返回 object,測試程式碼:

var regex = /^what$/i;
regex = new RegExp('^what$');
alert(typeof regex);

instanceof 在頁面有多個 frame 時用不了,來自不同 frame 的物件 instanceof 返回 false

可以用 Object.prototype.toString.call(value) === '[object Array/Function...]' 來做類型檢查,也可以用來區分原生物件和自定義物件,例如:

[object JSON]//原生 JSON 物件
[object Object]//自定義 JSON 物件

注意:IE 中以 COM 物件形式實現的函式物件 toString 不會返回 [object Function]

##2. 作用域安全的建構函式

用 new 運算子呼叫建構函式會給新建立的物件新增屬性,而直接呼叫建構函式會給全域物件 window 新增屬性

為了避免污染全域作用域,可以用如下建構函式:

/* 可能會污染全域作用域
function Student(name){
  this.name = name;
}
*/
//作用域安全的建構函式
function Student(name){
  if(this instanceof Student){
    this.name = name;
  }
  else{
    return new Student(name);
  }
}

上面的建構函式能夠避免直接呼叫建構函式給全域物件意外新增屬性,但用這種方式實現繼承可能會出現問題,類型檢查可能導致繼承失敗

##3. 惰性載入(避免重複分支檢測)

  1. 在第一次執行分支檢測時,覆蓋原有函式,例如:

    function detect(){
      if(...){
        detect = function(){
          //
        }
      }
      else if(...){
        detect = function(){
          //
        }
      }
      else...
    }
    
  2. 可以用匿名函式立即執行並返回匿名函式來實現惰性載入,例如:

    var detect = (function(){
      if(...){
        return function(){
          //
        }
      }
      else if(...){
        return function(){
          //
        }
      }
      else...
    })();
    

第一種方式第一次呼叫時損失性能,以後呼叫不會損失性能;第二種方式在第一次呼叫時也不會損失性能,因為把時耗放到了第一次載入程式碼時

##4. 函式綁定(指定執行環境)

可以用下面的函式給函式指定執行環境並創造新函式:

function bind(fun, context){
  return function(){
    return fun.apply(context, arguments);
  }
}

可以方便地根據已有函式生成新函式,[IE9+] 有原生的 bind 方法,例如 var newFun = fun.bind(obj);

注意:函式綁定存在記憶體消耗多,執行慢的缺點

##5. 函式柯里化(也叫函式套用,允許指定一些參數)

建立柯里化函式的通用方式:

function curry(fun){
  var args = Array.prototype.slice.call(arguments, 1);//去掉第一個參數 fun,得到給定的參數值
  return function(){
    var innerArgs = Array.prototype.slice.call(arguments);//把內部 arguments 物件轉換為陣列(為了用 concat 方法)
    var finalArgs = args.concat(innerArgs);//拼接參數列表
    return fun.apply(null, finalArgs);//把拼接的參數列表傳給 fun
  }
}

或者增強 bind 方法實現柯里化:

function bind(fun, context){
  var args = Array.prototype.slice.call(arguments, 2);//去掉前 2 個參數
  return function(){
    var innerArgs = Array.prototype.slice.call(arguments);//同 curry
    var finalArgs = args.concat(innerArgs);//同 curry
    return fun.apply(context, finalArgs);//指定執���環境和參數
  }
}

注意:柯里化和 bind 都存在額外開銷,不要濫用

##6. 防篡改物件

  1. 不可擴充套件物件(不能新增新屬性)

    var obj = {a : 1, b : 2};
    alert(Object.isExtensible(obj));//true
    Object.preventExtensions(obj);//把 obj 設定為不可擴充套件
    alert(Object.isExtensible(obj));//false
    obj.c = 3;
    alert(obj.c);//undefined
    

注意:設定不可擴充套件操作無法撤銷(改不回來),設定之後無法新增新屬性,但可以修改/刪除原有屬性

  1. 密封物件(只能修改現有屬性,無法刪除或新增)

    var obj = {a : 1, b : 2};
    Object.seal(obj);//設定密封物件
    alert(Object.isSealed(obj));//true
    obj.c = 3;
    alert(obj.c);//undefined
    delete obj.a;//嚴格模式下報錯
    alert(obj.a);//1
    
  2. 凍結物件(只讀,訪問器屬性可寫)

    var obj = {a : 1, b : 2};
    Object.freeze(obj);//設定密封物件
    alert(Object.isFrozen(obj));//true
    obj.a = 3;
    alert(obj.a);//1
    

上面的實現都是ES5 新增的部分,瀏覽器支援性未知,本機測試*[IE8-] 不支援*,Chrome 和 FF 支援

##7. 函式節流

把耗時的大任務分割成小塊,用 setTimeout 控制執行

優點:提高了頁面響應速度

缺點:邏輯連貫性沒了,實現難度增大,而且不易實現事務控制,因為完整事務被拆開了

##8. 觀察者模式

用自定義事件可以實現觀察者模式:

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);
      }
  }
};

用法如下:

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!"});

評論

暫無評論,快來發表你的看法吧

提交評論