jQuery源码初探(3)

来源:互联网 发布:长春知合动画 编辑:程序博客网 时间:2024/06/02 05:58

今天我们先来 聊聊 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 );   // 20console.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
原创粉丝点击