jQuery源码中的“new jQuery.fn.init()”什么意思?

全部文章搬运自个人我的主页:sheilasun.mecss

引子

最近打算试试看看jQuery的源码,刚开个头就卡住了。不管如何都理解不了jQuery源码入口部分中的html

return new jQuery.fn.init( selector, context )jquery

看了好多帖子都没看懂,以为本身很蠢,内心很苦,吃宵夜都不香了。昨晚去游泳,游完8*100后靠在池壁上喘气,有人从我旁边出发,水花溅起的瞬间,我忽然,想通了!这大概就是回光返照 (划掉)福至心灵吧!
下面一点点地说下我对jQuery入口源码的理解。chrome

自执行的匿名函数

jQuery源码最外层的结构以下:框架

(function(window,undefined){
    ...
})(window);

任何库的引入都得作到不污染全局变量,得有本身的命名空间。上面的自执行匿名函数就能够作到这点,把全部库私有的变量和方法,都包到一个私有的空间内,容许外界访问的属性或方法能够挂载到window上。ide

例以下面这段代码:函数

(function(){
  var count=0;
  var addOne=function(){
    alert(count++);
  };
  window.outerAddOne=addOne; //挂到window上外界方可访问
})();

outerAddOne();//alert "0"
console.log(count);//error
console.log(addOne);//error

内部定义的count变量以及addOne方法,外部环境下是没法访问到的,可是在window上挂载一个方法outerAddOne,指向addOne,外界就能够访问到了。工具

OK,了解了这个自执行匿名函数的做用,这里还有两个问题。this

第一,为何要传入window?

看了上面的outerAddOne这个例子,就会发现,不传入window也没什么嘛,照样能够把方法挂到window身上啊。
两个缘由:prototype

首先,从代码压缩混淆的角度考虑。

咱们用线上工具来压缩混淆下面这段示例代码:

function say(){
  var name="naima";
  window.description="hi "+name;
}

压完混完后瘦了一点:

function say(){var a="naima";window.description="hi "+a}

看到没有,用a代替了name,可是window既不是声明的局部变量也不是参数,是不会被压缩混淆的,因此将window做为参数传入可解决这个问题。

其次,传入window参数,就能够不用沿着做用域链一层层向上查找直到顶层做用域去获取window对象了,访问更快了。

第二,为何要传入undefined?

undefined并非JS中的关键字,在IE8及如下中是能够对其从新赋值的。

var undefined="new value";
alert(undefined);//alert “new value"

在参数列表中给出undefined参数,可是不传入值,那么这个参数值就是undefined值了。

jQuery对象的构建

先看jQuery源码中如何对jQuery赋值的:

jQuery = function( selector, context ) {
        // The jQuery object is actually just the init constructor 'enhanced'
        return new jQuery.fn.init( selector, context, rootjQuery );
    }

我就是被new jQuery.fn.init()这里弄晕了,先在这里暂停,回想一下日常我是如何使用jQuery的($即对应‘jQuery'):

$('body').css('background','red');
$.parseJSON('{}');

要实现这两种调用,$('body')应该是一个实例对象,css是每一个实例共享的方法,是原型上的方法。而$则是一个类,parseJSON则是类的静态方法。
接下来,咱们试着往这个结果上靠。

如何不用new关键字获得jQuery对象

回想一下日常我都是怎么构建实例对象的,一般我会这样写一个Prince类:

function Prince(name){
  this.name=name;
  this.body="human";
}

Prince.prototype.change=function(){
  this.body="frog";
};

而后我会这样去获取一个Prince实例对象:

var prince=new Prince("Harry");
prince.change();

若是我年纪大了忘记用new关键字了,程序就报错了:

var a=Prince('harry');
a.change();//error,"Cannot read property 'change' of undefined"

除了调用方法会出错以外,window还被挂载了两个变量上去,何其无辜。

可是获取jQuery对象(如下简称JQ对象)用new和不用new均可以,返回的是同样样的。

console.log($('*').length);//14
console.log(new $('*').length);//14

为了作到这点,咱们很容易想到须要在构造函数内部返回对象。引用下我在另外一篇博文JavaScript中的普通函数与构造函数里写的:

构造函数有return值怎么办?
构造函数里没有显式调用return时,默认是返回this对象,也就是新建立的实例对象。
当构造函数里调用return时,分两种状况:
1.return的是五种简单数据类型:String,Number,Boolean,Null,Undefined。
这种状况下,忽视return值,依然返回this对象。
2.return的是Object
这种状况下,再也不返回this对象,而是返回return语句的返回值。

因此咱们应该在jQuery构造函数内部去返回一个对象,这样就能够不用new的方式去建立JQ对象了,其实这时候,构造函数就至关于一个工厂函数了。
那么核心问题来了。

该返回什么样的对象?对于这个对象有何要求?

这个对象必须能够调用jQuery.prototype上的方法。

咱们使用或本身写jQuery插件的时候会常常遇到$.fn这个对象,不少插件都是经过扩展这个对象来实现的。
$.fn其实对应着jQuery.prototype,$和fn分别是jQuery和prototype的简写方式,只要咱们把方法扩展到这个原型对象身上,经过$()获取的JQ对象都是能够访问到方法的。
例如:

$.fn.greeting=function(){alert('hi')};
$('body').greeting();//alert 'hi'

因此,工厂函数内部返回的对象必定要能够调用jQuery.prototype上的方法。

是时候看John Resig究竟是怎么作的啦。

jQuery源码

jQuery = function( selector, context ) {
    return new jQuery.fn.init( selector, context, rootjQuery );
},
jQuery.fn = jQuery.prototype = { //fn即对应prototype
    constructor: jQuery,
    init: function( selector, context, rootjQuery ) {
        ...
        return this;
    }
    ...
}
jQuery.fn.init.prototype = jQuery.fn;

在chrome里调试时候添加JQ对象的watch,会看到相似以下的结果:

$('*'): n.fn.init[14]

看到上面这段源码,缘由就很明显了,其实咱们所说的JQ对象根本就是init函数的实例对象,而init则是jQuery原型上的一个对象,它自己是没有什么方法的,全靠从jQuery原型上拿。

"jQuery.fn.init.prototype = jQuery.fn"这句很重要,它将init的原型指向jQuery的原型,因此JQ对象才能够访问‘css'、'show'、'hide'这些写在jQuery.fn上的方法。

咱们可能会有疑问,为什么要从init这绕这么一大圈来访问jQuery的原型,而不是直接返回一个jQuery实例直接经过这个实例来访问自身原型?好比说代码能够写成这样:

jQuery = function( selector, context ) {
        return new jQuery();
}

问题很明显,这样作只会你们一块儿死,死在循环里。

好,那我接受init的存在,可是我这样写难道不能够吗?

jQuery = function( selector, context ) {
        return jQuery.fn.init();//不一样点在于去掉了new关键字
}

让咱们作点动做来证实加上new是有用的。

jQuery = function( selector, context ) {
    return jQuery.fn.init();
},
jQuery.fn = jQuery.prototype = {
    init: function() {
            this.name='sheila';
            return this;
    },
    anotherName:'sunwukong'
};
var jq=jQuery();
console.log(jq.anotherName);//"sunwukong"
console.log(jq.name);//"sheila"

上面这段代码是为了说明this的做用域问题,其不只能访问init函数内部,还能向上一层到fn对象。我听人家说,作框架的,做用域要独立才好呢。
给它加上new关键字:

...
return new jQuery.fn.init();
...

console.log(jq.anotherName);//undefined
console.log(jq.name);//"sheila"

这样this的做用域就独立出来了。

经博友评论提醒,加不加new还牵涉到一个更重要的问题:返回的对象到底是谁。不加new的状况下,'jQuery.fn.init()'至关于调用方法,this指向的以及最后返回的都是同一个jQuery.fn对象,$('body')和$('p')就没有区分了。显然,这是不合理的。而加了new,就是每次用构造函数实例化了一个新对象,彼此都是不一样的。

有任何不妥之处或错误欢迎各位指出,不胜感激~

题外话

常常看别人的博客,有些表述方式实在独特而有趣,往往读来都觉妙不可言,哑然失笑。不由心生羡慕,技术过硬,知识面广还写得一手好文章,赞! 想起在学校时每次咱们作presentation,上台第一句,“你们好,我今天讲的题目是……”,而后幻灯片一页页划过去,“历史背景”,“研究现状”,“我使用的方法”……导师都听得一脸崩溃,“nonono,不要,不要这样,大家这样讲,不会有人有耐心听下去的……咱们要像说故事同样娓娓道来,抓住听众的注意力,一点点引入……”因而之后我都尽可能按照“说故事”这个思路去讲,最后毕业答辩的时候,一个老师说,“为何我以为你像故宫导览哈哈哈哈”…… 果真仍是没有掌握表述的技巧啊。

相关文章
相关标签/搜索