jQuery结构简析

本文简单实现jQuery框架,深入理解javascript对象。
本文的对照版本是jQuery-1.2.6.js

本文注重jquery结构设计思路,并不侧重具体功能的实现以及兼容性和安全性的部分。

首先建立基本框架如下:

(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的初始化

//上述框架中的部分代码
//由于$('#selector')得到的是一个jQuery对象,尝试直接返回jQuery对象
var jQuery = window.jQuery = window.$ = function(selector){
  return new jQuery();   //这里会导致一个死循环,所以不能这样直接构建jQuery对象
};

修正上述代码中的死循环,我们可以试图返回this,但是this明显是window,不是我们需要的jQuery,利用原型中的this返回构造函数实例化对象的特点(不理解的可以参看javascript中this详解,我们作以下修改:

//上述框架中的部分代码
//由于$('#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。如果用户如下这样使用代码,那么什么都得不到:

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个动态方法)可以用来测试。

//添加静态方法
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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容

  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,706评论 2 17
  • 在线阅读 http://interview.poetries.top[http://interview.poetr...
    程序员poetry阅读 114,192评论 24 450
  • 请参看我github中的wiki,不定期更新。https://github.com/ivonzhang/Front...
    zhangivon阅读 7,085评论 2 19
  • @转自GitHub 介绍js的基本数据类型。Undefined、Null、Boolean、Number、Strin...
    YT_Zou阅读 1,134评论 0 0
  • 1.“Moon, for what do you wait?” “To salute the sun for wh...
    洛梵华阅读 188评论 0 0