jQuery結構簡析

本文簡單實現jQuery框架,深刻理解javascript對象。
本文的對照版本是jQuery-1.2.6.jsjavascript

本文注重jquery結構設計思路,並不側重具體功能的實現以及兼容性和安全性的部分。css

首先創建基本框架以下:html

(function(window){
  "use strict";
  var jQuery = window.jQuery = window.$ = function(selector){
    //定義$函數,並把$和jQuery暴露到外面
  };

  jQuery.fn = jQuery.prototype = {
    //jQuery原型
  };

  jQuery.extend = jQuery.fn.extend = function(){
    //添加擴展方法,jQuery.extend添加靜態方法,也能夠實現繼承,jQuery.fn.extend添加動態方法
  }
})(window);

進一步,實現jQuery的初始化java

//上述框架中的部分代碼
//因爲$('#selector')獲得的是一個jQuery對象,嘗試直接返回jQuery對象
var jQuery = window.jQuery = window.$ = function(selector){
  return new jQuery();   //這裏會致使一個死循環,因此不能這樣直接構建jQuery對象
};

修正上述代碼中的死循環,咱們能夠試圖返回this,可是this明顯是window,不是咱們須要的jQuery,利用原型中的this返回構造函數實例化對象的特色(不理解的能夠參看javascript中this詳解),咱們做如下修改:jquery

//上述框架中的部分代碼
//因爲$('#selector')獲得的是一個jQuery對象,嘗試直接返回jQuery對象
var jQuery = window.jQuery = window.$ = function(){
    return jQuery.fn.init();   //執行初始化
};
jQuery.fn = jQuery.prototype = {
  init: function(){
    return this;
  },
  jQuery: "1.0.0",   //jQuery版本信息
  length: 0,    //模擬數組,即這裏構成一個類數組對象
  size: function(){
    return this.length;
  }
}

到此$()能夠返回一個jQuery對象了。可是在舊瀏覽器中有一個bug。若是用戶以下這樣使用代碼,那麼什麼都得不到:segmentfault

var ele = $.fn.init();

這裏直接調用了init(), 這樣會獲得init創造的對象,因爲$是個函數,$.fn是函數的原型,函數原型是個空函數,因此這裏獲得了一個以空函數爲構造函數創造的對象。爲了解決這問題,採用new的方式:數組

//上述框架中的部分代碼
var jQuery = window.jQuery = window.$ = function(selector){
    return new jQuery.fn.init(selector);   //執行初始化
};
jQuery.fn = jQuery.prototype = {
  constructor: jQuery,
  init: function(selector){
    var elements = document.querySelectorAll(selector);   //順便簡單的實現了選擇器
    //注意querySelectorAll在老的瀏覽器中是不支持的,這裏專一在jQuery的結構上。
    Array.prototype.push.apply(this, elements);   //構成類數組對象,引入length,並使其自增
    return this;
  },
  jQuery: "1.0.0",   //jQuery版本信息
  length: 0,    //模擬數組,即這裏構成一個類數組對象
  size: function(){
    return this.length;
  }
}
jQuery.fn.init.prototype = jQuery.fn;

因爲這裏把jQuery.fn.init()做爲構造函數調用,獲得一個jQuery對象,因此咱們把jQuery.fn做爲jQuery.fn.init()的原型。瀏覽器

下一步實現extend方法安全

//上述框架中的部分代碼
jQuery.extend = jQuery.fn.extend = function(obj, srcObj){
    var target, len = arguments.length;
    if(len === 1){   //傳入一個參數時實現繼承
      deep(obj, this);
      return this;
    } else {
      for(var i = 1; i < len; i++){
        target = arguments[i];
        deep(target, obj);
      }
      return obj;
    }

    function deep(oldOne, newOne){   //實現深拷貝
      for(var prop in oldOne){
        if(typeof oldOne[prop] === "object" && oldOne[prop] !== null){
            newOne[prop] = oldOne[prop].constructor === Array ? [] : {};
            deep(oldOne[prop], newOne[prop]);
        }
        else{
            newOne[prop] = oldOne[prop];
        }
      }
    }
  };

寫了extend,咱們定義幾個簡單的方法(2靜態方法,3個動態方法)能夠用來測試。app

//添加靜態方法
jQuery.extend({
  trim: function(text){
    return (text || "").replace(/^\s+|\s+$/g, "");
  },
  makeArray: function(obj){
    return Array.prototype.slice.call(obj);
  }
});

//添加動態方法
jQuery.fn.extend({
  //get方法
  get: function(num){
    return num == null ?
    jQuery.makeArray(this):
    num < 0 ? this[ num + this.length ] : this[ num ];   //索引小於零表示倒數
  },

  //each 遍歷執行函數
  each: function(fun){
    for(var i = 0, len = this.length; i < len; ++i){
      if(fun(i, this[i]) === false)
        break;
    }
    return this;  //用於鏈式調用
  },

  //修改css屬性
  css: function(key, value){
    var len = arguments.length;
    if(len === 1){     //傳入1個參數返回對應值
      return this[0].style[key];
    } else if(len === 2){    //傳入2個參數設置對應值
      this.each(function(index, ele){
        ele.style[key] = value;
      });
    }
    return this;  //用於鏈式調用
  }
});

到這裏,jQuery的基本結構就造成了,還有一個問題須要解決,就是處理變量衝突。
當環境中以及有jQuery$時能夠選擇釋放$或時釋放jQuery$

(function(window){
  "use strict";
  //在框架一開始先保留外部可能存在的$或jQuery變量,以便在後來恢復
  var _$ = window.$;
  var _jQuery = window.jQuery;

  var jQuery = window.jQuery = window.$ = function(selector){};
  jQuery.fn = jQuery.prototype = {};
  jQuery.extend = jQuery.fn.extend = function(){};
})(window);

而後寫noConflict函數

jQuery.extend({
  noConflict: function(deep){  //傳入true時同時釋放$和jQuery,不然只是釋放$
    window.$ = _$;
    if(deep) window.jQuery = _jQuery;
    return jQuery;
  }
});

到此爲止,jQuery的框架已經造成。下面是完整代碼部分:

(function(window){
  "use strict";
  var _$ = window.$;
  var _jQuery = window.jQuery;

  //定義全局接口
  var jQuery = window.jQuery = window.$ = function(selector){
    return new jQuery.fn.init(selector);
  };

  var HTMLRegex = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;  //用於匹配html標籤

  var rootjQuery;   //默認根節點的jQuery對象

  //原型
  jQuery.fn = jQuery.prototype = {
    constructor: jQuery,
    // init: function(selector){
    //   var elements = document.getElementsByTagName(selector);
    //   Array.prototype.push.apply(this, elements);
    //   return this;
    // },
    init: function(selector){
      if(!selector){
        return this;
      }
      var elements = document.getElementsByTagName(selector);
      Array.prototype.push.apply(this, elements);
      return this;
    },
    jQuery: "1.0.0",
    length: 0,
    size: function(){
      return this.length;
    }
  };
  jQuery.fn.init.prototype = jQuery.fn;

  //繼承
  jQuery.extend = jQuery.fn.extend = function(obj){
    var target, len = arguments.length;
    if(len === 1){   //傳入一個參數時實現繼承
      deep(obj, this);
      return this;
    } else {
      for(var i = 1; i < len; i++){
        target = arguments[i];
        deep(target, obj);
      }
      return obj;
    }

    function deep(oldOne, newOne){
      for(var prop in oldOne){
        if(typeof oldOne[prop] === "object" && oldOne[prop] !== null){
            newOne[prop] = oldOne[prop].constructor === Array ? [] : {};
            deep(oldOne[prop], newOne[prop]);
        }
        else{
            newOne[prop] = oldOne[prop];
        }
      }
    }
  };

  //靜態函數
  jQuery.extend({
    trim: function(text){
      return (text || "").replace(/^\s+|\s+$/g, "");
    },
    noConflict: function(deep){
      window.$ = _$;
      if(deep) window.jQuery = _jQuery;
      return jQuery;
    },
    makeArray: function(obj){
      return Array.prototype.slice.call(obj);
    }
  });

  //對象方法
  jQuery.fn.extend({
    get: function(num){
      return num == null ?
      jQuery.makeArray(this):
      num < 0 ? this[ num + this.length ] : this[ num ];   //索引小於零表示倒數第n個
    },
    each: function(fun){
      for(var i = 0, len = this.length; i < len; ++i){
        if(fun(i, this[i]) === false)
          break;
      }
      return this;  //用於鏈式調用
    },
    css: function(key, value){
      var len = arguments.length;
      if(len === 1){
        return this[0].style[key];
      } else if(len === 2){
        this.each(function(index, ele){
          ele.style[key] = value;
        });
      }
      return this;  //用於鏈式調用
    }
  });
}(window));

上述代碼源碼:Download

相關文章
相關標籤/搜索