Javascript笔记:jQuery源码分析以及从jQuery对象创建的角度理解extend方法的原理

1.1     创建属于jQuery对象的插件

前面我看到jQuery插件的方式:通过$.extend方式可以定义属于jQuery本身的全局性的插件,为此我做了下面的测试,大家先看下面这段js代码:

;(function($){
    // 创建jQuery全局作用域的插件
    $.extend({
        'wholeftn':function(){
            console.log('你要用jQuery.wholeftn()方式调用,如果jQuery(XX).wholeftn()就会报错');    
        },
        'wholeattr':'全局jQuery属性'
    });
    // 创建jQuery对象的插件
    $.fn.extend({
        'partfrn':function(){
            console.log('你要用jQuery(XX).wholeftn()方式调用,如果jQuery.wholeftn()就会报错');    
        },
        'partattr':'局部jQuery作用域'
    });
})(jQuery)

测试代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>jQuery.extend和jQuery.fn.extend区别</title>
</head>
<script type="text/javascript" src="js/jquery-1.7.1.js"></script>
<script type="text/javascript" src="js/jquery.extenddiff.js"></script>
<body>
</body>
</html>
<script type="text/javascript">
$(document).ready(function(){

    $.wholeftn();// 你要用jQuery.wholeftn()方式调用,如果jQuery(XX).wholeftn()就会报错
    console.log($.wholeattr);// 全局jQuery属性
    
    $('body').partfrn();//你要用jQuery(XX).wholeftn()方式调用,如果jQuery.wholeftn()就会报错
    console.log($('body').partattr);// 局部jQuery作用域
    
    // 错误测试
    console.log($('body').wholeattr);// undefined
    console.log($.partattr);// undefined
    //$('body').wholeftn();// $("body").wholeftn is not a function
    $.partftn();// $.partftn is not a function
});
</script>

  我们发现$.extend是创建jQuery对象全局的方法和属性,这很像java里的静态方法和静态变量,而用$.fn.extend创建的是jQuery(XX)对象的方法,二者是有区别的:区别在于一个是属于全局的一个是属于对象的。我们平时经常使用的$.ajax就是全局方法而$(‘div’).html()就是属于jQuery对象的方法,我们仔细瞧瞧jQuery手册里,jQuery几乎所有的属性和方法其实都可以按照属于jQuery全局和属于jQuery对象进行分类,因此我有个看法了:

  想理解jQuery框架的原理,读懂它的源代码,插件技术是一个很好的切入点,整个jQuery框架大致就是分为三大部分:第一部分就是如何构建jQuery对象,第二部分是如何创建属于jQuery全局的属性和方法,第三部分就是如何创建属于jQuery对象的属性和方法了,而第一部分是jQuery框架的根本,后面两部分就是jQuery框架的延伸了,只要理解了第一部分,包括如何构建jQuery全局插件和jQuery对象插件我们就可以真正理解jQuery框架设计原理了

  下面我就会好好分析下jQuery框架是如何构建jQuery对象的。

1.2     jQuery框架里如何定义jQuery对象

jQuery对象的定义使用的是javascript最基础的技术:对象的创建和对象的继承技术,具体点就是创建一个javascript对象以及使用javascript里的prototype技术,大家先看下面的代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>javascript 面向对象</title>
</head>

<body>
</body>
</html>
<script type="text/javascript">
// 定义一个Person类,也可以说是对象,javascript里面对象和类是混合在一起的
// 类或者说对象的定义又是构造函数和类的定义混合在一起的
function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        console.log(this.name);    
    }
}

Person.prototype.commonType = 'Human';

Person.prototype.commonFtn  = function(){
    console.log('Name:' + this.name + ';Age:' + this.age + ';Job:' + this.job);    
}

Person.staticType = 'Animal';

Person.staticFtn = function(){
    console.log('All are Mammal');    
}

var per1 = new Person('张三',30,'man');
var per2 = new Person('Lucy',25,'woman');

per1.sayName();// 张三
per2.sayName();// Lucy

per1.commonFtn();// Name:张三;Age:30;Job:man
per2.commonFtn();// Name:Lucy;Age:25;Job:woman

console.log(per1.commonType);// Human
console.log(per2.commonType);// Human

console.log(Person.staticType);// Animal
Person.staticFtn();//All are Mammal

console.log(per1.staticType);// undefined
console.log(Person.commonType);// undefined
//per1.staticFtn();// per1.staticFtn is not a function
//Person.commonFtn();// Person.commonFtn is not a function

(new Person('李四',40,'man')).commonFtn();// Name:李四;Age:40;Job:man

</script>

代码里的Person换成jQuery是不是就和我们平时使用jQuery.ajax,jQuery(‘div’).html()很类似了,当然还是有点区别的,比如jQuery对象的定义都是如下模式:

Var Person = {}

这个方式和我们上面写的function Person(){}是一样的,为了和jQuery保持一致我们可以代码修改成这样:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>javascript 面向对象</title>
</head>

<body>
</body>
</html>
<script type="text/javascript">
// 定义一个Person类,也可以说是对象,javascript里面对象和类是混合在一起的
// 类或者说对象的定义又是构造函数和类的定义混合在一起的
/*function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        console.log(this.name);    
    }
}*/

var Person = function(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        console.log(this.name);    
    }
}

Person.prototype.commonType = 'Human';

Person.prototype.commonFtn  = function(){
    console.log('Name:' + this.name + ';Age:' + this.age + ';Job:' + this.job);    
}

Person.staticType = 'Animal';

Person.staticFtn = function(){
    console.log('All are Mammal');    
}

var per1 = new Person('张三',30,'man');
var per2 = new Person('Lucy',25,'woman');

per1.sayName();// 张三
per2.sayName();// Lucy

per1.commonFtn();// Name:张三;Age:30;Job:man
per2.commonFtn();// Name:Lucy;Age:25;Job:woman

console.log(per1.commonType);// Human
console.log(per2.commonType);// Human

console.log(Person.staticType);// Animal
Person.staticFtn();//All are Mammal

console.log(per1.staticType);// undefined
console.log(Person.commonType);// undefined
//per1.staticFtn();// per1.staticFtn is not a function
//Person.commonFtn();// Person.commonFtn is not a function

(new Person('李四',40,'man')).commonFtn();// Name:李四;Age:40;Job:man

</script>

这样就更加接近jQuery的写法,但是对于jQuery对象的调用还是有些区别的,至少我们使用jQuery对象时候没有在前面new一下,而是直接jQuery(XXX)的,消除new这个关键字的方式其实很简单就是使用工厂模式,大家看下面的代码:

function createPerson(name,age,job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        console.log(this.name);    
    }
    return o;
}

createPerson('Lily','16','woman').sayName();// Lily

大家可以看到,工厂模式消除了new的关键字。

我简单阅读过jQuery1.0版本的源码,构建jQuery对象的方式和这种简单的工厂模式很类似,但是之后的jQuery版本创建对象的方式就更加的有技巧了。下面我给出一个根据改进后的jQuery对象定义的方式定义的xQuery对象:

Xquery1.0.0.js代码如下:

(function(window, undefined) {
    // 将经常使用的全局对象重新定义在自己写好的封闭作用域内是提高你代码速度以及质量的一致方式
    // 因为在javascript这种语言里局部变量的效率永远都是高于全局变量的
    var document = window.document,navigator = window.navigator,location = window.location;
    
    var xQuery = (function(){
        
        // 我们在定义xQuery里面再定义一个xQuery对象,这个技巧也是把构建jQuery的代码封装到
        // 一个独立的封闭作用域里,这个作用域和插件技术里的xQuery区别开来,从而提升了在定义
        // xQuery时候的代码效率
        var xQuery = function(selector,context){
            return new xQuery.fn.init();
        };
        
        // xQuery.fn是xQuery.prototype的别名
        xQuery.fn = xQuery.prototype = {
            init:function(){
                this.length = 0;
                this.test = function(){
                    return this.length;    
                }
                return this;
            },
            xquery:'1.0.0',
            length:1,
            size:function(){
                return this.length;    
            }
        };
        
        xQuery.fn.init.prototype = xQuery.fn; // 使用xQuery的原型对象覆盖init的原型对象
        
        return xQuery;
        
    })();
    
    window.xQuery = window.$ = xQuery;
})(window);

测试页面的代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>xQuery 测试</title>
</head>
<script type="text/javascript" src="js/xquery-1.0.0.js"></script>
<body>
</body>
</html>
<script type="text/javascript">
console.log($().xquery);// 1.0.0
console.log($().test());// 0
console.log($().size());// 0
</script>

呵呵,上面的代码是不是有点jQuery的味道啊,关于代码的解释我在注释里面写的比较详细了,大家又不理解的地方可以好好读下我写的注释。

这段代码就是构建jQuery对象的核心代码,不过理解这段代码还是很不容易的,讲也不太好讲,但是我还是要尝试把它讲明白。

1.3     jQuery框架里定义jQuery对象的原理

我先看这段代码new xQuery.fn.init();,这就是使用了工厂模式返回jQuery对象,因此我们使用jQuery对象之前从不会new一下。但是jQuery里的new的对象很奇怪,他不是直接new xQuery(),直接new xQuery结果会如何了?这个不用写测试代码了,我们仔细看看这个就不行,因为这种写法会产生重复调用,结果会变成一个死循环直到报出内存溢出的错误。

大家要注意,使用new xQuery.fn.init()代码同时我们一定要加一句话xQuery.fn.init.prototype = xQuery.fn;,如果没有xQuery.fn.init.prototype = xQuery.fn;这段代码,代码也是有问题,我们注释掉代码,再执行下代码结果如下:

<script type="text/javascript">
console.log($().xquery);// undefined
console.log($().test());// 0
console.log($().size());// 没有结果
</script>

我们会发现,xquery和size()都没有被定义,产生上面的结果的原因就是this指针,前面我的博客里讲到过javascript里面this指针的运用,如果有对javascript里this指针的使用不太明白的可以参见我的这篇博文,博文的地址是:

javascript笔记:深入分析javascript里对象的创建(中)

javascript里指针用法的核心是:this在对象的方法中,this总是指向调用该方法的对象

New xQuery.fn.init()里的this指针是指向init方法内部的元素而非是xQuery.fn下的元素,因此外部调用xquery和size()时候会报出没有定义的错误(这个地方大家要细细体会,这个是关键所在),为了让this指针指向xQuery.fn里的元素我们就得把xQuery.fn.init.prototype指向xQuery.fn,这样init对象的指针就能指向,xQuery.fn里的属性和方法了,jQuery里面用这样的技巧巧妙的改变了this指针的指向。最后我还是要总结下:

jQuery这样构造对象的原因有以下个理由:

  1.  jQuery对象对外应该是可以直接被使用,而不需要使用new关键字来构建,因此jQuery对象的定义使用的是工厂模式;
  2. 但是普通的工厂模式又有自己的局限,我们希望return是构造对象本身,但是直接这么返回会导致重复调用的错误,因此jQuery只得重新定义一个对象作为返回对象避免这种重复调用的错误;
  3. 但是重新定义的返回对象又要包含jQuery本身所具有的属性和方法,换句话说我们要模拟return new jQuery(),但是又不能让代码产生重复调用的错误,因此最后使用原型技术改变了this指针的指向

1.4     再看看$.extend和$.fn.extend的原理

jQuery源码里是这样的形式定义$.extend和$.fn.extend方法的:

jQuery.extend = jQuery.fn.extend = function() {
        …..
});

如果我们只传一个对象参数到$.extend和$.fn.extend方法里,那么这个对象最后会拷贝到this指针里,$.extend方法里的this指针指向的是jQuery对象本身,而$.fn.extend里的this指针是指向jQuery.fn也就是jQuery.prototype(jQuery的原型对象),所以$.extend可以扩展jQuery的属性和方法,而$.fn.extend可以扩展jQuery对象的属性和方法。

其实换种写法构建jQuery插件和使用$.extend和$.fn.extend方法等价的。

下面我改改我前面写的插件代码,代码如下:

;(function($){
    // 创建jQuery全局作用域的插件
    $.wholeftn = function(){
        console.log('你要用jQuery.wholeftn()方式调用,如果jQuery(XX).wholeftn()就会报错');    
    };
    $.wholeattr = '全局jQuery属性';
    // 创建jQuery对象的插件
    $.fn.partfrn = function(){
        console.log('你要用jQuery(XX).wholeftn()方式调用,如果jQuery.wholeftn()就会报错');    
    },
    $.fn.partattr = '局部jQuery作用域';
})(jQuery)

测试代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>jQuery插件的另一种写法</title>
</head>
<script type="text/javascript" src="js/jquery-1.7.1.js"></script>
<script type="text/javascript" src="js/jquery.newplug.js"></script>
<body>
</body>
</html>
<script type="text/javascript">
$(document).ready(function(){

    $.wholeftn();// 你要用jQuery.wholeftn()方式调用,如果jQuery(XX).wholeftn()就会报错
    console.log($.wholeattr);// 全局jQuery属性
    
    $('body').partfrn();//你要用jQuery(XX).wholeftn()方式调用,如果jQuery.wholeftn()就会报错
    console.log($('body').partattr);// 局部jQuery作用域
    
    // 错误测试
    console.log($('body').wholeattr);// undefined
    console.log($.partattr);// undefined
    //$('body').wholeftn();// $("body").wholeftn is not a function
    $.partftn();// $.partfrn is not a function
});

</script>

结果和使用$.extend和$.fn.extend一样的,但是这样的代码实在是丑陋,没有使用extend方法来的优雅。

好了,关于jQuery插件技术的分析到此结束,后面我还会写一篇文章,想聊聊编写插件的一些规范和技巧,这篇文章应该更加实用点。

只有当你真正理解某个技术的原理时候,你才能使用好这门技术,这就是我现在对我使用jQuery技术的要求,我会一直朝这个方向努力下去的。

我将我最近的研究做成了pdf,大家可以点击这个地址下载:

 https://files.cnblogs.com/sharpxiajun/jQuery%E6%8F%92%E4%BB%B6%E6%8A%80%E6%9C%AF%E7%A0%94%E7%A9%B6_cnblogs.pdf

源代码的地址是:

 https://files.cnblogs.com/sharpxiajun/jqextendstudy.zip

 

 

原文地址:https://www.cnblogs.com/sharpxiajun/p/2487208.html