zoukankan      html  css  js  c++  java
  • jQuery源码中的“new jQuery.fn.init()”什么意思?

    引子

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

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

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

    自执行的匿名函数

    jQuery源码最外层的结构如下:

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

    任何库的引入都得做到不污染全局变量,得有自己的命名空间。上面的自执行匿名函数就可以做到这点,把所有库私有的变量和方法,都包到一个私有的空间内,允许外界访问的属性或方法可以挂载到window上。

    例如下面这段代码:

    (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,了解了这个自执行匿名函数的作用,这里还有两个问题。

    第一,为什么要传入window?

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

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

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

    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,就是每次用构造函数实例化了一个新对象,彼此都是不同的。

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

    转自:http://www.cnblogs.com/SheilaSun/p/4779895.html

  • 相关阅读:
    语言特性-上下文对象
    语言特性-闭包
    语言特性-变量作用域
    语言特性-函数重载与类型检查
    面向对象的JS代码
    单例模式
    wait操作接口
    进程的创建模型
    模拟密码登陆过程
    目录操作的一些函数理解
  • 原文地址:https://www.cnblogs.com/BluceLee/p/7028187.html
Copyright © 2011-2022 走看看