一些優秀的代碼分析與學習【一】


所有的代碼實例都不是直接copy的源碼,都結合了本人的理解添加了原理描述與實例(可以直接運行),通俗易懂。

 

1.jQuery初始化代碼段

  技術亮點:jQuery無new化構建、每次jQuery構建的作用域隔離、jQuery拓展插件。

  實現源碼:

var jQuery = function(args){
  return new jQuery.fn.init(args); } jQuery.fn = jQuery.prototype = { init: function(args){ return this; }, otherFn: function(){} } jQuery.fn.init.prototype = jQuery.fn;

  分析:

  每次jQuery函數內部new的作用是隔離作用域。每次構建的都是一個新對象,新對象obj如果重寫obj.__proto__下的屬性不影響其他jQuery對象__proto__下的屬性。

var m = jQuery(),n= jQuery();//m = {__proto__:{init:fn(),otherFn:fn()}};n = {__proto__:{init:fn(),otherFn:fn()}}
m.otherFn;//fn(){}
m.otherFn = "test";//m =  {otherFn:"test",__proto__:{init:fn(),otherFn:fn()}};
n.otherFn;//fn(){}; n的結構n = {__proto__:{init:fn(),otherFn:fn()}}

  然后jQuery.fn提供jQuery對象的拓展,只要使用jQuery.fn.xx定義的屬性在通過var m = jQuery();方式獲取的對象m都可以通過m.xx獲取到。如果直接使用jQuery.prototype來提供拓展給人感覺不太友好。

  上面的代碼換成下面這種寫法也是可以的

var jQuery = function(args){
  return new jq(args); } function jq(args){ return this; } jQuery.fn = jq.prototype = { otherFn: function(){}; }

  多出了一個函數變量jq,jQuery的這種寫法只是減少了變量而已。

  關於new的詳細點擊這里

 

2.Sizzle引擎詞法緩存機制

  技術亮點:一個函數實現存、取、緩存數據。

  實現源碼:

function createCache() {
  var cache, keys = [];   return (cache = function( key, value ) {     // 使用 (key + " ")避免命名沖突,最大緩存createCache.cacheLength:50     if ( keys.push( key += " " ) > createCache.cacheLength ) {       // 去掉最老的記錄       delete cache[ keys.shift() ];     }     return (cache[ key ] = value);   }); } createCache.cacheLength = 50; 
//例子
var tokenCache = createCache(); //設置緩存 tokenCache("name", "chua"); //tokenize函數中獲取緩存 cached = tokenCache[ "name" + " " ];//"chua"

  分析:

  局部變量cache使用函數表達式方式賦值,cache函數中給cache這個函數對象添加要緩存的屬性。可以說是把函數對象運用到了極致。

 

3.jQuery判斷js數據的具體類型

  技術亮點:call的妙用

  實現源碼:

var class2type = {} ,
core_toString = class2type.toString;  jQuery.type = function( obj ) { if ( obj == null ) { return String( obj ); } return typeof obj === "object" || typeof obj === "function" ? class2type[ core_toString.call(obj) ] || "object" : typeof obj; } jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); });

   分析:

  首先我們知道toString()函數用於將當前對象以字符串的形式返回。該方法屬於Object對象,由於所有的對象都"繼承"了Object的對象實例,因此幾乎所有的實例對象都可以使用該方法。而Object調用toString方法返回"[object ObjectName]",其中ObjectName是對象類型的名稱。然后jQuery利用這一特性將准備檢測的數據代入object的toString方法替換掉了其上下文環境為要檢測的數據。

 

4.Dean Edwards的base2格式化函數

  技術亮點:arguments的靈活運用

  實現源碼:

function format(string) {
  var args = arguments; var pattern = new RegExp('%([1-' + arguments.length + '])', 'g'); return String(string).replace(pattern, function(match, index,position,all) { console.log(match + ' ' + index + ' ' + position + ' ' + all); return args[index]; }); }; //實例 var name = "chua", age = 20, expr = "%1的年齡是%2"; format(expr,name,age);//"chua的年齡是20" //控制台打印 //%1 1 0 %1的年齡是%2 //%2 2 6 %1的年齡是%2

  分析:這個比較簡單就不分析了。主要就是arguments.length的運用。

   

5.jQuery節點排序

  技術亮點:docElem.compareDocumentPosition的靈活運用

  實現源碼:

function sortby( a, b ) {
    var compare;

    if ( a === b ) {return 0;}

    if ( (compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b )) ) {
    //如果b在a后面,那么compare的比特位至少是0?0100,?表示可能是0,也可能是1
    return compare & 4 ? -1 : 1;
    }
  //最后的容錯處理,如果節點a不包含compareDocumentPosition方法,我們認為是非法節點,直接放在數組最后。
    return a.compareDocumentPosition ? -1 : 1;
}

  分析:

  這里的sorby函數是arrayObject.sort(sortby)的比較函數sortby。具體的arrayObject.sort與docElem.compareDocumentPosition的詳解點擊這里查找

  里面主要就是將compareDocumentPosition方法用到恰到好處,可能有很多童鞋不了解這個方法的可以學習一下,這個函數特別強大。而且該函數返回比特位的方式在實際運用中我們也可以借鑒。

 

6.jQuery延時對象的promise方法

  技術亮點:提供對外方法同時屏蔽私有接口

  實現源碼:

function Deferred(){
  var state = true,
  promise = {
    state: function() {
      return state;
    },
    promise: function( obj ) {
      return obj != null ? jQuery.extend( obj, promise ) : promise;
    }
  },
  deferred = {};    

  deferred.resolve = "resolve function";
  promise.promise( deferred );
  
  return deferred;
}

//實例
var Deff = Deferred();
//Deff = {resolve:"resolve function", state:function(), promise:function()}
var test = {tt:"test"};
Deff.promise(test);
//test = {tt:"test", state:function(), promise:function()}
//test不能訪問resolve方法了

  分析:

  通過promise給相應的對象組裝Deferred對外提供的屬性,屏蔽私有屬性。見實例可知,Deff.promise(test);執行后test可以訪問Deferred對外提供的方法state,但是不能訪問Deferred的私有方法resolve了。

  另外延時對象的實現思想還是挺有意思,也很有學習意義,可以按序學習

  jQuery-1.9.1源碼分析系列(五) 回調對象

  jQuery-1.9.1源碼分析系列(六) 延時對象

  jQuery-1.9.1源碼分析系列(六) 延時對象續——輔助函數jQuery.when

  

7.jQuery處理兼容的鈎子(hook)機制

  技術亮點:所有可能出現的分支情況全部使用對象屬性來存取。

  實現源碼:

//屬性鈎子對象(所有的屬性鈎子都放在里面)
var attrHooks = {
  //屬性為type的鈎子
  type: {
    //操作為set的鈎子
    set: function( elem, value ) {
      if ( value === "radio" && elem.nodeName.toLowerCase() === "input" ) {
        var val = elem.value;
        elem.setAttribute( "type", value );
        if ( val ) { elem.value = val; }
        return value;
      }
    }
  },
  other:{}
}

//使用例子
var attr = "type";
var hooks =  attrHooks[attr];
var value = 1;
var elem = document.querySelector("input[type='radio']");
if(hooks && "set" in hooks && (ret = hooks.set( elem, value)) !== undefined ){
    alert(ret);
}

  例子中添加上相應html代碼即可用。

  分析:

  以往我們判斷分支的時候使用一大堆if/else或Switch/case語句,使得執行代碼很長。更省力的方法是將這些分支添加到對象obj上,直接通過obj[branch]方式直接調用對應的分支處理即可。

 

8.Dean Edwards的跨瀏覽器AddEvent()設計

  技術亮點:實現同一事件綁定多個處理函數

  代碼實現:

//事件添加方法
function addEvent(element, type, handler) {
  //保證每個不同的事件響應函數只有唯一一個id
    if (!handler.$$guid) handler.$$guid = addEvent.guid++;
 
  // 給element維護一個events屬性,初始化為一個空對象。  
    // element.events的結構類似於 { "click": {...}, "dbclick": {...}, "change": {...} }  
    if (!element.events) element.events = {};
 
  // 試圖取出element.events中當前事件類型type對應的對象(這個對象更像數組),賦值給handlers
  //如果element.events中沒有當前事件類型type對應的對象則初始化
    var handlers = element.events[type];
  if (!handlers) {
     handlers = element.events[type] = {};
 
     // 如果這個element已經有了一個對應的事件的響應方法,例如已經有了onclick方法
        // 就把element的onclick方法賦值給handlers的0元素,此時handlers的結構就是:
        // { 0: function(e){...} },這也是為什么addEvent.guid初始化為1的原因,預留看為0的空間;
        // 此時element.events的結構就是: { "click": { 0: function(e){...} },  /*省略其他事件類型*/ } 
        if (element["on" + type]) {
           handlers[0] = element["on" + type];
        }
    }
    
  // 把當前的事件handler存放到handlers中,handler.$$guid = addEvent.guid++; addEvent.guid = 1; 肯定是從1開始累加的    
  //因此,這是handlers的結構可能就是 { 0: function(e){...}, 1: function(){}, 2: function(){} 等等... }
    handlers[handler.$$guid] = handler;
 
  //下文定義了一個handleEvent(event)函數,將這個函數,綁定到element的type事件上作為事件入口。
  //說明:在element進行click時,將會觸發handleEvent函數,handleEvent函數將會查找element.events,並調用相應的函數。可以把handleEvent稱為“主監聽函數”
    element["on" + type] = handleEvent;
};
 
//計數器
addEvent.guid = 1;
 
function removeEvent(element, type, handler) {
    // delete the event handler from the hash table
    if (element.events && element.events[type]) {
        delete element.events[type][handler.$$guid];
    }
};
 
function handleEvent(event) {
  //兼容ie
  event = event || window.event;
  //this是響應事件的節點,這個接點上有events屬性(在addEvent中添加的)
  //獲取節點對應事件響應函數列表
    var handlers = this.events[event.type];
    // 循環響應函數列表執行
  for (var i in handlers) {
         //保持正確的作用域,即this關鍵字
     this.$$handleEvent = handlers[i];
        this.$$handleEvent(event);
    }
};
View Code

  分析:

  詳細的分析可以點擊jQuery-1.9.1源碼分析系列(十) 事件系統——事件體系結構查看。我們這里分析的是他的實現思路:將添加的事件放在一個單獨的屬性

events中,而直接綁定到事件上的處理函數為我們定義的函數handleEvent,handleEvent中遍歷events中對應的處理函數並執行。中間多了一個處理handleEvent來實現多個函數的綁定這種思想影響了后來的jQuery,這種添加中間控制層的思想影響了一大批js框架開發者,比如說react的虛擬DOM。

 

  待續。。。

 

  如果覺得本文不錯,請點擊右下方【推薦】!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM