为什么jQuery的构造方法要放在jQuery.fn.init里?
发布于 10 年前 作者 yu521088 13678 次浏览 最后一次编辑是 8 年前

如题,为什么要这么做呢?为了配合上面的语句,还得加上下面这句很绕的语句:

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

为什么不能这么干呢?

(function(){
var jQuery = function(){
	return new F();
};
var F = function(){
	return this;
};
F.fn = F.prototype = {
	jquery : 1.0,
	test : function(){
		console.log('test');
		return this;
	}
};
jQuery.fn = jQuery.prototype = F.prototype;
window.jQuery = window.$ = jQuery;
})();

var  a = $('body').test('li');
console.log('');
$.fn.extend = function(){console.log('extended');}
a.extend();

这样层次清晰,而且兼顾到插件扩展,求大神赐教。

43 回复

把你的这个定义里面的F换成jQuery.fn.init,不就等价了吗?

@bnuhero 我的代码结构可以实现jQuery的要求了啊,为什么要把F换成jQuery.fn.init呢?这样多不直观啊

两者的效果区别只在于,jquery多做了把F挂到$.fn上。所以你的代码加一句jQuery.fn.init = F;如楼上说,就等价了。这样的好处是在访问不到F的地方,可以调用jQuery.fn.init。

我也一直有这疑问 同求

@nighca 但是没有必要访问F啊,要扩展有两种,意识jQuery.extend;二是jQuery.fn.extend。现在两者都可以被扩展啊。

@nighca 当然,如果要扩展jQuery,必须加上jQuery.extend = F.extend = F.fn.extend = function extend(){};如果是为了避免还有可能出现的这种情况,那么可以解释为什么要把F挂到jQuery.fn上

  • 其实楼主的F和jQuery.fn.init是相等的; 实现功能是和jq一样的, 只是jq的把构造函数放进原型;
  • 如果非要说原因, 个人理解jq这样写整体结构清晰, 先是入口构造函数,紧跟着是原型部分(原型里面init是初始化), 但是不好理解;
  • 乍一看确实挺绕, 我也是看了好久才明白怎么回事

jQuery = function( selector, context ) { return new jQuery.fn.init( ); } jQuery.fn=JQuery.prototype = { init: function() {}, … }

  • 最后重置原型, 和楼主的 jQuery.prototype = F.prototype;一个意思 jQuery.fn.init.prototype = jQuery.fn;

@jeffZhong 其实我也明白我的代码和jQuery的代码其实是相同的,只是把F替换成了jQuery.fn.init,但是疑问的是为什么要这么干,因为这样看起来很绕。 我的理解是为了extend方法的代码层次清晰,因为现在没办法在外面访问F,所以,要达到扩展jQuery本身的目的,需要添加这么一句:

jQuery.extend = F.extend = F.fn.extend;

但是这样做破坏了原本代码的层次,把jQuery和F混合了,所以直接吧F挂到jQuery.fn.init上

@yu521088 将F的引用挂在fn上,也就是间接地挂在$这个对外暴露的变量上。那么在外部就可以访问到并使用F,比如F.apply(obj),或者obj instance of F,总之,这个函数作为jQuery中重要的一个函数(所有jQuery对象的constructor),大量重要逻辑在其中,将其暴露出来是可能发挥出一些作用的。以上为个人理解。

你后面的话我没看懂…

@yu521088 jq要扩展方法直接这样也不麻烦:

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

jQuery.extend是扩展到jquery上, jQuery.fn.extend扩展到jquery实例上,也就是原型上面的 而你的也跟jq一样:

jQuery.extend = jQuery.fn.extend; 不需要这样: jQuery.extend = F.extend = F.fn.extend;

这样就可以了

@nighca 其实jQuery.extend = F.extend;这样就可以对外暴露F这个变量了吧。你要对F做的扩展都可以通过jQuery.extend实现啊。

我后面那句话是想说,如果有大量的需要在内部扩展F的语句,比如:

F.extend({
    one: ...
});
F.extend({
    two: ...
});
F.extend({
    three: ...
});

那为了把这些方法暴露出来,需要同时扩展jQuery和F:

var one = {
    one: ...
},
two = {
    two: ...
},
three = {
    three: ...
};
jQuery.extend(one);
F.extend(one);
jQuery.extend(two);
F.extend(two);
jQuery.extend(three);
F.extend(three);

@nighca 其实就是怎么暴露F的问题,jQuery是把F通过jQuery.fn.init暴露了,依我现在的实现,每一次扩展F,必须同时扩展jQuery。其实也可以这么做吧:

F.fn.extend = function(){
    // 现在jQuery的extend方法
}
jQuery.extend = function(){
    F.fn.extend.apply(F, arguments.slice);
}

@jeffZhong 我觉得你说的方法也不对,因为这样的话你调用$.extend() 其实扩展的是jQuery,而不是F,我觉得更好的方法是:

F.fn.extend = function(){
    // 现在jQuery的extend方法
}
jQuery.extend = function(){
    F.fn.extend.apply(F, arguments.slice);
}

这样一来,所有对jQuery的扩展,实质上是对F的扩展

@yu521088

其实jQuery.extend = F.extend;这样就可以对外暴露F这个变量了吧。你要对F做的扩展都可以通过jQuery.extend实现啊。

做不到吧,比如我在外部(jQuery使用者角度)这样用:

var a = jQuery.fn.init.apply({});

如果没挂在jQuery.fn上,怎么拿得到这个函数啊。

另外,我还是没理解你的意思,jQuery里边现在有三个东西:jQuery(即$,返回值为一个Constructor的实例的函数), Constructor(jQuery对象的构造函数,记为Constructor,即F,亦即jQuery.fn.init),jQuery.fn(是Constructor的prototype)。

jQuery的主要扩展就两个地方:

  1. 往jQuery上挂

     jQuery.extend({
         xxx: function(){}
     })
    

    使用方式:$.xxx()或jQuery.xxx()

  2. 往jQuery.fn上挂

     jQuery.fn.extend({
         xxx: function(){}
     })
    

    使用方式:$(‘div’).xxx()

并没有需要扩展F(Constructor)的场景啊。

不知道prototype除了写这么大的东西,还在其他地方用的到么?

@nighca

其实jQuery.extend = F.extend;这样就可以对外暴露F这个变量了吧。你要对F做的扩展都可以通过jQuery.extend实现啊。

这里是我想错了。

但是,从框架提供者的角度,更应该避免用户以下面这种方式调用的情况吧:

var a = jQuery.fn.init.apply({});

jQuery 需要扩展两个地方我理解,现在jQuery的代码是通过jQuery.extend和jQuery.fn.extend方式实现。 那么,要达到这个目的,我可以在我的代码里这么写:

jQuery.extend = F.fn.extend = function(){
    // current jquery 'extend' method
}

这样,在外部,我既可以通过$.extend 来扩展jQuery,也可以通过$.fn.extend来扩展jQuery对象。

至于我说的要扩展F的情况,我的想法是这样的,可能有点洁癖的感觉: 我最开始定义的jQuery变量只是暴露给外部的一个接口,而我真正的jQuery对象是通过new F() 生成的,而且所有的jQuery对象的方法都是定义在F.prototype里面。所以像需要对外暴露的jQuery方法,比如:$.ajax, $.data 这些方法,我希望他们是定义在内部的F下,而不是定义在对外暴露的jQuery变量下,因此我希望调用$.extend()的时候,他是对F进行扩展,而不是对jQuery进行扩展。不知道这样的解释你能明白吗?

@yu521088 你平时用prototype么?

@yu521088 要明白jQuery和jQuery.fn的区别 你上面jQuery.extend = function(){} 扩展的当然是jQuery, 你要扩展jqeury的实例(也就是F)就必须这样

jQuery.fn.extend=function(){…}; // 认真看,中间多了个fn

用你的代码: 同时扩展到jquery和jquery实例(即F)上面

$.extend=$.fn.extend = function(){ console.log(this); console.log(‘extended’); } $(‘body’).extend();//这个方法是jqeury的实例,是F.fn上面的 this的结果是: F $.extend();//这个方法是jquery本身 this的结果是: jQuery

其实上面就对应了我们jquery中不同类型的api 比如: $.ajax() 和 $(’#id’)的区别, 前者对应的是jquery.extend, 后者对应jquery.fn.extend

@jeffZhong ,首先,我明白$和$.fn的区别,也明白$.extend和$.fn.extend的区别。你的这段代码:

jQuery.fn.extend=function(){…}; // 认真看,中间多了个fn 等同于我写的: F.fn.extend = function(){ // 现在jQuery的extend方法 } 因为在jQuery.fn = F.fn。

而我要同时满足$.extend和$.fn.extend,我可以这么实现:

jQuery.extend = F.fn.extend = function(){}; 这样,在外部,我既可以$.extend({‘ajax’: function(){}})来扩展jQuery实现$.ajax,也可以$.fn.extend({‘some’:function(){}})来扩展jQuery.fn实现$(’#id’).some();

但是这么做,当我们调用$.extend的时候,实际上是扩展的内部的jQuery这个变量,而我觉得更合理的代码实现应该做到这样:在外部调用$.extend的时候,不应该是对内部jQuery变量的扩展,而是对F的扩展。所以才有了这段代码:

jQuery.extend = function(){ F.fn.extend.apply(F, arguments.slice); }

还有,下面这段代码中的this分别是F和jQuery是正确的逻辑啊:

$.extend=$.fn.extend = function(){
            console.log(this);
            console.log('extended');
        }
    $('body').extend();//这个方法是jqeury的实例,是F.fn上面的
    this的结果是:  F
    $.extend();//这个方法是jquery本身
    this的结果是:  jQuery

@kingapple prototype用来实现继承啊

@yu521088 额,平时用到多么?

@kingapple 多啊,你要写一个类,把这个类的非公有属性放在自己的属性中,把公有的方法和属性放在prototype对象中啊

@yu521088 你看看jquery的源码就知道了,不太了解你要达到什么效果

@yu521088 你说的公有,是子类可继承的意思吧?

@jeffZhong 其实我只是单纯的不明白为什么jQuery要把用这么绕的方式实现: new jQuery.fn.init();

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

@kingapple 子类可继承是一方面。另一方面是,定义一个Man类,每个Man实例都会有自己的名字,年龄,性别,这些属性对每个Man实例都是不同的,不能公用。但是像run(), eat()这些方法,可以是每个Man实例都共享的。

@yu521088 理解为你是要禁用扩展jquery本身的功能, 如果别人真要扩展在jquery上面, 不用你的

jQuery.extend({aa:function(){...}})

这个方法,直接向下面这样扩展呢

$.aa=function(){...}

@yu521088 prototype里面可以放公共变量,类似于静态变量?

@yu521088 其实你的方式实现了和jquery差不多的功能, 同时扩展插件方面也是和jq一样的效果的. , 只是解决方式不一样而已

@yu521088 还有本来人家jq中 $.extend就是扩展jquery本身, $.fn.extend就是扩展jquery实例, 而我只是不理解你干嘛要禁用掉扩展jq本身, 两个都留着多好, 想扩展哪个就扩哪个 :)

@jeffZhong 恩,好像这个解释比较说的通,如果用这样的方法扩展$: `$.extend({‘a’:function(){}}); 而用

$.b = function(){
   //这里访问不到this.a
}

在b里就访问不到this.a 了,因为这里的this是jQuery,而用extend扩展的是F。

但是我也可以这么干啊:

jQuery.extend = F.fn.extend = function(){}

这样,扩展jQuery就可以了,可以用$.extend 也可以用$.newMethod。

@yu521088 但是你要知道fn是jquery下面的属性,在外部是可以访问得到的, 而你的F是内部的, 在外面是没法访问到的, 如果我外部扩展类库的方法是可以的

$.fn.extend({a:function(){...}})

而你的

F.fn.extend(a:function(){...})

你外面根本不能这样调,会报错的,因为F是内部的变量 你的类库总不能每次人家要扩展插件了,都要在你内部扩展吧

@kingapple 对的,比如计数器

@yu521088 js的实现好蛋疼,还是Java的直观:)

@jeffZhong 内部有一句话 jQuery.fn = jQuery.prototype = F.prototype; 所以在外面我可以通过$.fn.extend(a:function(){…})来扩展F.fn

@jeffZhong

还有本来人家jq中 $.extend就是扩展jquery本身, $.fn.extend就是扩展jquery实例, 而我只是不理解你干嘛要禁用掉扩展jq本身, 两个都留着多好, 想扩展哪个就扩哪个 :)

我的想法是内部的jQuery这个变量是暴露给外面的桥梁,也就是说这个jQuery变量可以随便取什么名字,比如cQuery,而jQuery对象其实是F的实例,所有实例的方法代码其实都在F.fn下,所以我这里的F其实应该就是John大神代码里的jQuery变量,所以我想让所有对jQuery本身的扩展放在F下面 :) 感觉有点代码洁癖的样子,恩,是这样的 LOL

@kingapple 对的,这就是js神(dan)奇(teng)的地方。

@yu521088 js没有接口的概念吧?要实现复杂模式,就变成天书了:(

@yu521088 也就是说本来就已经实现了的功能, 像jq那样

$.fn.extend({a:function(){...}}); 扩展实例
$.extend({a:function(){...}}); 扩展本身

那你还什么不满足,你这是要闹那样呢?

@jeffZhong 我只是想知道jQuery为什么不像我这么干,要把F放到jQuery.fn.init下 %>_<%

@kingapple 尼玛又见到你了…真水啊…

最近我也在读jquery源码,也思考了这个问题。这个实现方式感觉很晦涩。 我也百度了不少文章没有找到讲的特别清楚的。 从这个问题还可以引申出 为什么不用 jQuery.init = function(){//构造函数 },应该可以实现相同的功能。并且同样可以暴露到全局作用域。 大家应该知道js中的prototype 主要是为了实现继承。那么jQuery.fn.init可以被所有的jQuery实例对象调用。 另外 我在这个地址找到了正确答案:http://stackoverflow.com/questions/18782973/why-is-the-init-function-in-jquery-prototype-and-not-in-jquerys-closure

回到顶部