jQuery源码初探(3)

今天我们先来 聊聊 jQuery 中的 无 new 构造
写过 js 面向对象的同学知道,一般我们是这么来写的

//构造函数
function myjQuery () {
    this.age = 20;
}
//方法挂载到原型上
myjQuery.prototype.say = function () {
    console.log( 'my age is ' + this.age );
}

var myjQuery = new myjQuery();   // 实例化

console.log( myjQuery.age );   // 20
console.log( myjQuery.say() );  // my age is 20

这是一种比较常见的写法,然而 jQuery 并不是这么做的,回想一下,我们平时在用 jQuery 获取元素的时候,经常都是 $('#myId') , $('.myClass')这种方式去调用的,难道说 jQuery 不用 new 去创建一个实例?
其实不然,jQuery 内部也是用 new 创建一个 jQuery 对象 , 只不过用的十分巧妙, 接下来我们来看看jQuery是怎么做到的!

在 jQuery 源码中有这么一段

    jQuery = function( selector, context ) {

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

这个就是 jQuery 的入口方法 , 它返回的 是一个 new jQuery.fn.init( selector, context ); 我们知道, 用 new 一个对象 ,肯定是返回一个对象的实例,那么也就是返回的是jQuery.fn.init( selector, context )的实例,那么这个 jQuery.fn 又是什么? 我们查看源码中有这么一段:

    jQuery.fn = jQuery.prototype = {
        constructor: jQuery,
        /***code***/
    }

也就是说,jQuery.fn 就是 jQuery 构造函数的原型。
接着我们看看jQuery.fn.init()这个方法,源码中可以找到是这么写的:

    init = jQuery.fn.init = function( selector, context, root ) {
            /****code****/
            return this;
    }

有的同学可能认为到这里就结束了,是吗?ok,我们来测试一下,我们把上面我们的代码改造跟 jQuery 一样结构,

    var myjQuery = function () {
        return new myjQuery.fn.init(); 
    }

    myjQuery.fn = myjQuery.prototype= {
        init : function(){       //初始化
            return this;
        },
        age : 20,
        say : function(){
            return this.age;
        }
    }

    myjQuery().age; //'undefiend'
    myjQuery().say(); 
    // 'Uncaught TypeError: myjQuery(...).say is not a function'

什么?出错了,没错,得到的结果确实 令人意外 , 不过也是意料之中 , 我们稍加分析一下就知道了。

上面代码我们使用了return new myjQuery.fn.init();
返回的是 myjQuery 原型上的 init() 方法的实例 , 我们在发现在 init() 方法中返回了this ; 此时的 this 指向的是 myjQuery.fn.init的实例 ,
调用myjQuery() 得到的结果是 myjQuery.fn.init 的实例
而在myjQuery.prototype.init内部中根本就没有 age 属性 和 say 方法, 所以此时通过这个结果访问不到 myjQuery.prototype 的属性 和方法的 , 因此上面得到的结果也就 不意外了。

可是我们用 jQuery 为什么没有出现这样的情况呢?原来我们还少了一句关键的代码, jQuery 中是这么写的:

init.prototype = jQuery.fn;

我们在上面就看到了 init = jQuery.fn.init,所以说这句代码相当于

jQuery.fn.init.prototype = jQuery.fn;

这句话是关键,现在我们可以发现:

jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype;

通过原型传递来解决上面的问题,把jQuery的原型传递给
jQuery.prototype.init.prototype
也就是说 jQuery 的原型对象 覆盖了内部 init 构造器的原型对象。
这样new init 的出来的实例对象也就等价于new jQuery的实例对象,所以也就可以访问jQuery的原型方法了。
我们把这句话加上去,把上面的函数改写成:

    var myjQuery = function () {
        return new myjQuery.fn.init(); 
    }

    myjQuery.fn = myjQuery.prototype= {
        init : function(){       //初始化
            return this;
        },
        age : 20,
        say : function(){
            return this.age ;
        }
    }

    myjQuery.prototype.init.prototype = myjQuery.prototype;

    myjQuery().age;  //20
    myjQuery().say() //20

世界清静多了,不是么?

ok,这就是 jQuery 实现无 new 操作的 原理 ~

上面还有一点点东西,我们补充一下:
1. 我们看到 jQuery 对象的是通过原型中的 init 方法 return 回来的,如下:

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

也就是说,我们每次调用 jQuery 的时候去 new 一个新的对象 , 因此,我们在写代码的时候,如果频繁使用一个 jQuery 对象的话,我们不妨把它用一个变量存起来,可以避免每次去创建对象~
2. 上面 jQuery 原型上,我单独拿出来一句:
constructor: jQuery
jQuery 这么做是为了保证 constructor 的指向,防止构造函数指向错误,引起调用的问题。那么为什么会产生这样的问题呢?

    //方式一
    fn.prototype.say = function(){}

    //方式二
    fn.prototype = {
        say : function() {}
    }

jQuery 采用的是 第二种 方式,这种方式与第一种方式差别很大,第一种是给原型添加一个方法,这种方式是不会影响fn原型内部 constructor 的指向问题,而第二种方式是 重写原型 , 相当于原型覆盖,原来的原型已不复存在,所以需要重新定义 constructor 的指向,小伙伴们以后写代码的时候多注意一点~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值