详解jQuery是如何封装出来的

1256 / 2025-06-10 23:02:20 世界杯介绍

这篇文章是分析jQuery是如何封装的。这里把我自己模拟jQuery封装的一个类库拿出来分享。

一、首先做一点说明

1.这篇文章可以看做是我之前的一篇博文 浅析jQuery基本原理($实现原理)的续篇

2.个人认为jQuery 与其他库相比,它有3个最大的特点,其一是独有的jQuery对象,其二是隐式迭代,其三是链式编程。

3.所以我所封装的库,重点就在于描述jQuery的这3个特征是如何实现的,而不是真的要做一个完美的库,我们要说的是思想,是方法,而不是讲项目。

4.牛顿说他之所以看得远,是因为站在了巨人的肩膀上。而jQuery就是我们学习前端的一个巨人,我们应该拿它的源码来学习下。

二、直接上代码

代码中有一些必要的说明。不过不能说足够,主要是有点多,我懒得再做说明了。这里简单的提一点,jQuery.extend是一个核心模块,这个模块是用来拓展jQuery其他api的。换句话说,其他的api都不是直接就写到jQuery对象或者其原型对象身上的,而是通过jQuery.extend这个方法来扩展的。

(function(w){

//工厂

function jQuery(selector){

return new jQuery.fn.init(selector);

}

//1.jQuery原型替换,2.给原型提供一个简写方式

jQuery.fn = jQuery.prototype = {

constructor : jQuery,

version : '1.0.0',

toArray : function(){

return [].slice.call(this);

},

each : function(fun){

return jQuery.each(this, fun);

},

get : function(index){

if(index == undefined || index == null){

return this.toArray();

}

if(index >=0 && index < this.length){

return this[index];

}

if(index >= -(this.length - 1) && index < 0){

return this[this.length + index];

}else{

return undefined;

}

}

};

//给jQuery的构造函数和原型对象,都添加一个extend方法,该方法用来拓展对象功能

jQuery.extend = jQuery.fn.extend = function(obj){

for(var key in obj){

this[key] = obj[key];

}

};

//给jQuery添加静态方法

jQuery.extend({

//去除首尾空格

trim : function(str){

if(!str){

return str;

}else if(str.trim){

return str.trim();

}

return str.replace(/^\s+|\s+$/, '');

},

//判断是不是HTML标签片段

isHTML : function(str){

//null、undefined、0、NaN、false、''

if(!str){

return false;

}

//

//如果字符串的第一个字母是<,最后一个字母是>,并且length >= 3,就可以认为是html片段。

if(str.charAt(0) == '<' && str.charAt(str.length - 1) == '>' && str.length >= 3){

return true;

}

return false;

},

isString : function(str){

return typeof str === 'string';

},

isNumber : function(num){

// return typeof num === 'number' && isFinite(num); //isFinite(4/0) : false

return Object.prototype.toString.call(num) === '[object Number]';

},

isBool : function(arg){

// return typeof num === 'number' && isFinite(num); //isFinite(4/0) : false

return Object.prototype.toString.call(arg) === '[object Boolean]';

},

isObj : function(obj){

return Object.prototype.toString.call(obj) === '[object Object]';

},

isArray : function(arr){

return Object.prototype.toString.call(arr) === '[object Array]';

},

//判断是不是伪数组

isLikeArray : function(likeArr){

//1.具有length属性

//2.要么没成员 arr.length === 0,要么一定存在key为arr.length - 1 的成员(没办法对所有的key都做判断)

//3.对象的__proto__ != Array.prototype

return ('length' in likeArr) && ( likeArr.length === 0 || likeArr.length - 1 in likeArr ) && (likeArr.__proto__ != Array.prototype);

},

isFunction : function(fun){

return Object.prototype.toString.call(fun) === '[object Function]';

},

//each(index, element)迭代函数

each : function(obj, fun){

if(jQuery.isFunction(fun)){

if(jQuery.isArray(obj) || jQuery.isLikeArray(obj)){

for(var i = 0; i< obj.length; i++){

fun.call(obj[i], i, obj[i]);

}

}else{

for(var key in obj){

fun.call(obj[key], key, obj[key]);

}

}

}

return obj;

}

});

//AJAX模块

jQuery.extend({

ajax : function(jsonData){

var xhr = null;

if(window.XMLHttpRequest){//标准的浏览器

xhr = new XMLHttpRequest();

}else{

xhr = new ActiveXObject('Microsoft.XMLHTTP');

}

//配置参数

var type = jsonData.type == 'get'?'get':'post';

var url = '';

if(jsonData.url){

url = jsonData.url;

if(type == 'get'){

url += "?" + jsonData.data;

}

}

var flag = jsonData.asyn == 'true'?'true':'false';

xhr.open(type,url,flag); //指定回调函数

xhr.onreadystatechange = function(){

if(this.readyState == 4 && this.status == 200){//请求成功

if(typeof jsonData.success == 'function'){

var d = jsonData.dataType == 'xml' ? xhr.responseXML : xhr.responseText;

jsonData.success(d);

}

}else{//请求失败

if(typeof jsonData.failure == 'function'){

jsonData.failure();

}

}

};

//发送请求

if(type == 'get'){

xhr.setRequestHeader("If-Modified-Since","0");

xhr.send(null);

}else if(type == 'post'){

xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");

xhr.send(jsonData.data);

}

}

});

//获取样式

jQuery.extend({

getStyle : function(dom, style){

//判断浏览器是否支持主流浏览器获取样式的api

if(window.getComputedStyle){

return window.getComputedStyle(dom)[style];

}else{//IE8浏览器兼容处理

return dom.currentStyle[style];

}

}

});

//事件注册模块

jQuery.fn.extend({

on : function(type, fun){

if(jQuery.isString(type) && jQuery.isFunction(fun)){

this.each(function(i, e){

if(e.addEventListener){//主流浏览器支持的方式

e.addEventListener(type, fun);

}else{

e.attachEvent('on' + type, fun);//IE浏览器支持的方式

}

});

}

return this;

},

click : function(fun){

this.on('click', fun);

return this;

}

});

//属性操作模块

jQuery.fn.extend({

attr : function(attr, val){

/*

* 实现思路:

* 1、判断attr是不是字符串或者对象,不是直接return this。

* 2、如果是字符串,那么继续判断arguments的length

* 3、length为1,则获取第一个元素指定的属性节点值返回

* 4、length>=2,则遍历所有元素,分别给他们设置新的属性节点值( setAttribute )

* 5、如果不是字符串(是对象),那么遍历这个对象,得到所有的属性节点值,

* 然后遍历所有的元素,把所有的属性节点分别添加到这些元素中。

* 6、return this。

* */

// 不是字符串也不是对象,直接返回this

if( !jQuery.isString( attr ) && !jQuery.isObject( attr ) ) {

return this;

}

// 如果是字符串

if( jQuery.isString( attr ) ) {

// 如果length为1,则直接返回第一个元素的属性节点值

if( arguments.length === 1 ) {

return this.get( 0 ).getAttribute( attr );

}

// 如果length为多个(2和及2个以上)

// 则遍历所有的元素,分别设置属性节点值

else {

for( var i = 0, len = this.length; i < len; i++ ) {

this[ i ].setAttribute( attr, val );

}

}

}

// 如果是对象

// 遍历这个对象,和所有的元素,分别添加遍历到的属性节点值

else {

// 遍历得到所有的属性节点和属性节点值

for( var key in attr ) {

// 遍历得到所有的元素

for( var i = 0, len = this.length; i < len; i++ ) {

this[ i ].setAttribute( key, attr[ key ] );

}

}

}

// 链式编程

return this;

}

});

//样式操作模块css()

jQuery.fn.extend({

css : function(name, val){

if(arguments.length === 1){

//只有1个参数,并且参数是字符串,就是要读取元素的样式值

if(jQuery.isString(name)){

return jQuery.getStyle(this[0], name);

}

else if(jQuery.isObj(name)){//赋值的操作,比如传递的参数是{color: red, width:'400px'}

for(var key in name){

this.each(function(i, e){

//注意:这里一定不能写成:this[i]['style'][key] = name[key];

//因为在each中,已经把fun执行时的this替换成了this[i]

this['style'][key] = name[key];

});

}

}

}

else if(arguments.length >= 2){

this.each(function(i, e){

this['style'][name] = val;

});

}

//链式编程

return this;

}

});

//css类操作模块

jQuery.fn.extend({

addClass : function(clsName){

//思路:

//1.先判断dom元素是否有该class

//2.如果没有就添加,如果有就不能再添加了

this.each(function(){

if((' ' + this.className + ' ').indexOf(' ' + clsName + ' ') == -1){

this.className += ' ' + clsName;

}

});

return this;//链式编程

},

removeClass : function(clsName){

//1.如果没传参数,就把所有DOM对象的class属性清空

//2.如果传递参数了,就遍历所有DOM对象,把class属性值的对应字符串,用‘ ’替换

if(arguments.length == 0){

this.each(function(){

this.className = '' ;

});

}else{

this.each(function(){

this.className = jQuery.trim((' ' + this.className + ' ').replace(' ' + clsName + ' ', ' '));

});

}

return this;//链式编程

}

});

//init才是jQuery真正的构造函数

var init = jQuery.fn.init = function(selector){

if(!selector){

return this;

}

//如果是字符串

if(jQuery.isString(selector)){

selector = jQuery.trim(selector);

//如果是HTML标签片段,则创建对应的DOM,

//然后添加到实例(this)身上,这里的this是由工厂jQuery调用init构造函数new出来的对象

if(jQuery.isHTML(selector)){

/**

* 由于存在标签嵌套

的可能,所以不能简单的通过字符串切割,找到标签名,去createElement.

* 创建的思路:

* 1.先创建一个临时的容器div

* 2.设置这个div的innerHTML为这个selector字符串。这些标签就成为了div的子元素

* 3.然后遍历这个div容器的子元素,依次添加到this身上

* 4.最后追加length属性

*/

var tempDiv = document.createElement('div');

tempDiv.innerHTML = selector;

[].push.apply(this, tempDiv.childNodes);

// Array.prototype.push.apply(this, tempDiv.childNodes);

this.length = 1;

return this;

}else{ //selector是选择器

try{

var firtChar = selector.charAt(0);

var lastChars = selector.substr(1);

if(firtChar == '#'){//id选择器

var obj = document.getElementById(lastChars);

if(obj == null){

return this;

}

[].push.call(this, obj);

}else{

if(firtChar == '.'){//类选择器

var objs = document.getElementsByClassName(lastChars);

[].push.apply(this, objs);

}else{//标签选择器

var objs = document.getElementsByTagName(selector);

[].push.apply(this, objs);

}

}

return this;

}catch{

this.length = 0;

return this;

}

}

}

};

//把构造函数的原型,替换为jQuery工厂的原型

//这么做的目的是为了实现jQuery的插件机制,让外界可以通过jQuery方便的进行扩展

init.prototype = jQuery.fn;

w.jQuery = w.$ = jQuery;

})(window)