jQuery诞生记-原理与机制

这篇文章发布于 2013年07月17日,星期三,22:57,归类于 jQuery相关。 阅读 195660 次, 今日 6 次 96 条评论

 

一、看似偶然的东西实际是必然会发生的

我大学时候在图书馆翻过一本很破旧的书,讲生物理论的,主要内容就是探讨生命的产生是偶然还是必然。里面很多亚里士多德都看不懂的公式计算什么的,还有模拟原始地球环境出现了有机物的实验什么的 。总之,书论述的观点是:“在当时的地球环境下,生命的产生是必然的!” 无数次机会的偶然条件、无数次化合物的相遇反应等必定会产生有机物,再有N多偶然,有机物必然形成了有机体……

这种理论类似于,你是个过马路非常小心的人,且你万寿无疆,除了怕被汽车撞。给你100万年的寿命,你最后必然还是被车撞死。

如果以这种理论来看jQuery的出现,结论也应该是必然的

二、需求、动力、发展、事物产生与jQuery的诞生

刀削面机器人

一个成熟的东西显然不是一口气就出来的,所谓“一铲子挖不了一口井”,我想jQuery的原作者再天才,也是循序渐进过来的,如何个循序渐进法,我想,很有可能就是需求驱动而产生的,好比上图刀削面机器人,据说现在已经第八代了!

1. gelElementById太长了
页面上有个按钮,还有个图片,我想点击按钮图片隐藏,如下HTML:

<button id="button">点击我</button>
<img id="image" src="xxx.jpg">

于是,我的脚本可能就这样:

var button = document.getElementById("button")
    , image = document.getElementById("image")

button.onclick = function() {
    image.style.display = "none";
};

有何问题?人几乎都是天生的“懒惰者”,document.getElementById名称长且重复出现,好像到了公司发现卡没带又回家重新拿卡的感觉,我希望越简单越好。恩, 我很喜欢钱,$这个符号我很喜欢,我打算改造一番,简化我的工作:

var $ = function(id) {
    return document.getElementById(id);
};

$("button").onclick = function() {
    $("image").style.display = "none";
};

这里的$()就是最简单的包装器,只是返回的是原生的DOM对象。

2. 我需要一个简洁的暗号,就像“芝麻开门”
后来页面复杂了,点击一个按钮,有2张图片要隐藏。

$("button").onclick = function() {
    $("image1").style.display = "none";
    $("image2").style.display = "none";
};

好像又看见长长的重复的东西,xxx.style.display = "none", 为什么每次开门都要从包里找到钥匙、对准插口插进去、还要左扭扭右扭扭呢?一次还好,天天经常老是这样怎受得了。设想,要是有个芝麻开门的暗号就好了,“open开”,声音识别,门自动开了,多省心。

这里每次隐藏都要xxx.style.display = "none", 比每天拿钥匙开门还要烦,我希望有一个快捷的方式,例如,“hide隐”,语句识别,元素自动隐藏,多省心。

就是要变成下面的效果:

$("button").onclick = function() {
    $("image1").hide();
    $("image2").hide();
};

3. 如何识别“芝麻开门”的暗号
$("image1")本质是个DOM元素,$("image1").hide()也就是在DOM元素上扩展一个hide方法,调用即隐藏。

哦,扩展,立马想到了JS中的prototype原型。//zxx: 老板,现在满大街什么菜最便宜。老板:原型啊,都泛滥了!

HTMLElement.prototype.hide = function() {
    this.style.display = "none";
};

上面代码的demo地址应该不会被人看到吧……

虽然在身体上钻了个窟窿插进入了一个方法,毕竟浏览器有有效果啊,切肤之痛就不算什么了。但是,我们是在泱泱天朝,很多IE6~IE8老顽固,这些老东西不认识HTMLElement,对于HTMLElement自残扩展之法根本理解不了,而这些老家伙掌管了半壁江山。唉,面对现实,元素直接扩展是行不通了。

因此,由于兼容性,我们需要想其他扩展方法。

4. 条条大路通罗马,此处不留爷,自有留爷处
虽IE6~IE8不识HTMLElement原型扩展,但是,Function的原型扩展其认识啊。管不管用,暂时不得而知,先随便搞个简单的试试呗~

var F = function() {};
F.prototype.hide = function() {
    this?.style.display = "none";
};

new F().hide();    // 这个实现隐藏?

本文至少还有一半的内容,但是,全文的最难点就在这里的,对new F()的认识和理解。

上面的代码,new F()您可以看做是this?.style这里的this. 您可能会跳起来抢答道:“那new F()return值 = DOM元素不就完事OK啦!—— this.style.hide = new F().style.hide = DOM.style.hide”!

很傻很天真

只要new表达式之后的constructor返回(return)一个引用对象(数组,对象,函数等),都将覆盖new创建的匿名对象,如果返回(return)一个原始类型(无return时其实为return原始类型undefined),那么就返回new创建的匿名对象。

上面的引用来自这里。什么意思呢?说白了就是,new F()如果没有返回值(Undefined类型),或返回值是5种基本型(Undefined类型、Null类型、Boolean类型、Number类型、String类型)之一,则new F()我们可以看成是原型扩展方法中的this; 如果返回是是数组啊、对象啊什么的,则返回值就是这些对象本身,此时new F()this

举例说明:

var F = function(id) {
    return document.getElementById(id);
};

new F("image1") == document.getElementById("image1");    // true 说明看上去返回DOM对象,实际确实就是DOM对象
var F = function(id) {
    return id;
};

new F("image1") == "image1";    // false 说明看上去返回字符串值,实际并不是字符串

回到上面天真的想法。要想使用prototype.hide方法中的this, 偶们就不能让F函数有乱七八糟的返回值。

因此,new F()直接返回DOM是不可取的,但我们可以借助this间接调用。比方说:

var F = function(id) {
    this.element = document.getElementById(id);
};
F.prototype.hide = function() {
    this.element.style.display = "none";
};

new F("image").hide();    // 看你还不隐藏

上面代码的demo地址应该不会被人看到吧……

5. 暴露与重用元素获取方法
上面的方法,元素的获取直接在F方法中,但是,实际情况,考虑到兼容性实现,元素获取可能会相当复杂,同时方法私有,不能重利用。因此,可以把元素获取方法放在原型上,便于管理和重用。代码如下:

var F = function(id) {
    return this.getElementById(id);
};
F.prototype.getElementById = function(id) {
    this.element = document.getElementById(id);
    return this;
};
F.prototype.hide = function() {
    this.element.style.display = "none";
};

new F("image").hide();    // 看你还不隐藏

元素获取方法放在prototype上,通过F()执行。你可能会奇怪了,你刚明明说“new F()直接返回DOM是不可取的”,怎么现在又有return呢?大家务必擦亮眼睛,F.prototype.getElementById的返回值是this,也就是new F()的返回值是this. 形象点就是new F("image")出了一拳,又反弹到自己脸上了。

上面代码的demo地址应该不会被人看到吧……

6. 我不喜欢new, 我喜欢$
new F("image")这种写法我好不喜欢,我喜欢$, 我就是喜欢$, 我要换掉。

好吧,把new什么什么藏在$方法中把~

var $ = function(id) {
    return new F(id);
};

于是,上面的图片隐藏的直接执行代码就是:

$("image").hide();

上面代码的demo地址应该不会被人看到吧……

IE6浏览器也是支持的哦!是不是已经有些jQuery的样子啦!

7. 你怎么就一种姿势啊,人家都腻了诶
循序渐进到现在,都是拿id来举例的,实际应用,我们可能要使用类名啊,标签名啊什么的,现在,为了接下来的继续,有必要支持多个“姿势”。

在IE8+浏览器中,我们有选择器API,document.querySelectordocument.querySelectorAll,前者返回唯一Node,后者为NodeList集合。大统一起见,我们使用后者。于是,就有:

var F = function(selector, context) {
    return this.getNodeList(selector, context);
};
F.prototype.getNodeList = function(selector, context) {
    context = context || document;
    this.element = context.querySelectorAll(selector);
    return this;
};

var $ = function(selector, context) {
    return new F(selector, context);
};

此时,我们就可以使用各种选择器了,例如,$("body #image"), this.element就是选择的元素们。

8. IE6/IE7肿么办?
IE6/IE7不认识querySelectorAll,咋办?
怎么办?

jQuery就使用了一个比较强大的选择器框架-Sizzle. 知道就好,重在演示原理,因此,下面还是使用原生的选择器API示意,故demo效果需要IE8+浏览器下查看。

8. 遍历是个麻烦事
this.element此时类型是NodeList, 因此,直接this.element.style.xxx的做法一定是报错,看来有必要循环下:

F.prototype.hide = function() {
    var i=0, length = this.element.length;
    for (; i<length; i+=1) {
        this.element[i].style.display = "none";
    }    
};

于是乎:

$("img").hide();  // 页面所有图片都隐藏啦!

上面代码的demo地址应该不会被人看到吧……

单纯一个hide方法还可以应付,再来个show方法,岂不是还要循环遍历一次,岂不是要烦死~

因此,急需一个遍历包装器元素的方法,姑且叫做each吧~

于是有:

F.prototype.each = function(fn) {
    var i=0, length = this.element.length;
    for (; i<length; i+=1) {
        fn.call(this.element[i], i, this.element[i]);
    }
    return this;
};
F.prototype.hide = function() {
    this.each(function() {
       this.style.display = "none";
    });
};

$("img").hide();  // 页面所有图片都隐藏啦!

上面代码的demo地址应该不会被人看到吧……

9. 我不喜欢this.element, 可以去掉吗?
现在包装器对象结构类似这样:

F.prototype = {
    element: [NodeList],
    each: function() {},
    hide: function() {}
}

element看上去好碍眼,就不能去掉吗?可以啊,宝贝,NodeList是个类数组结构,我们把它以数值索引形式分配到对象中就好啦!一来去除冗余element属性,二来让原型对象成为类数组结构,可以有一些特殊的功能。

于是,F.prototype.getNodeList需要换一个名字了,比方说初始化init, 于是有:

F.prototype.init = function(selector, context) {
    var nodeList = (context || document).querySelectorAll(selector);
    this.length = nodeList.length;
    for (var i=0; i<this.length; i+=1) {
        this[i] = nodeList[i];    
    }
    return this;
};

此时,each方法中,就没有烦人碍眼的this.element[i]出现了,而是直接的this[i].

F.prototype.each = function(fn) {
    var i=0, length = this.length;
    for (; i<length; i+=1) {
        fn.call(this[i], i, this[i]);
    }
    return this;
};

我们也可以直接使用索引访问包装器中的DOM元素。例如:$("img")[0]就是第一张图片啦!

上面代码的demo地址应该不会被人看到吧……

10. 我是完美主义者,我特不喜欢F名称,可以换掉吗?
F这个名称从头到尾出现,我好不喜欢的来,我要换成$, 我就是要换成$符号……

这个……$已经用了啊,再用冲突的吧。再说,你又不是狐后,耍无赖也没用啊……

狐后耍无赖

御姐发飙了

好吧,想想其他办法吧。一步一步来,那我把所有的F换成$.fn.

就有:
所有的F换成$.fn之后~

上图代码的demo地址应该不会被人看到吧……

显然,运行是OK的。似乎也非常有jQuery的模样了,但是,实际上,跟jQuery比还是有差别的,有个较大的差别。如果是上图代码所示的JS结构,则包装器对象要扩展新方法,每个都需要再写一个原型的。例如,扩展一个attr方法,则要写成:

$.fn.prototype.attr = function() {
    // ...
};

又看到prototype了,高级的东西应该要隐藏住,否则会给人难以上手的感觉。那该怎么办呢?御姐不是好惹的。

脑子动一下就知道了,把F.prototype换成$.fn不久好了。这样,扩展新方法的时候,直接就是

$.fn.attr = function() {
    // ...
};
至此,就使用上讲,与jQuery非常接近了。 但是,还有几个F怎么办呢,总不能就像下面这样放着吧:
var $ = function(selector, context) {
    return new F(selector, context);
};
var F = function(selector, context) {
    return this.init(selector, context);
};

$.fn = F.prototype;

$.fn.init = function(selector, context) {
    // ...
    return this;
};
$.fn.each = function(fn) {
   // ...
};
$.fn.hide = function() {
   // ...
};

数学中,我们都学过合并同类项。仔细观察上面的的代码:
$()返回的是new F(),而new F()又是返回的对象的引用。擦,这返回来返回去的,参数又是一样的,我们是不是可以一次性返回,然后再做些手脚,让$.fn.init返回的this依然能够正确指向。

于是,一番调整有:

var $ = function(selector, context) {
    return new $.fn.init(selector, context);
};
var F = function() { };

$.fn = F.prototype;
$.fn.init = function(selector, context) {
    // ...
    return this;
};

// ...

上面代码显然是有问题的,new的是$.fn.init, $.fn.init的返回值是this. 也就是$()的返回值是$.fn.init的原型对象,尼玛$.fn.initprototype原型现在就是个光杆司令啊,哟,正好,$.fn对应的原型方法,除了init没用外,其他hide(), each()就是我们需要的。因此,我们需要加上这么一行:

$.fn.init.prototype = $.fn

于是,$()的返回值从$.fn.init.prototype一下子变成$.fn,正好就是我们一开始的扩展方法。

于是乎,大功告成。慢着……
慢着……

上面明明还有残留的F呢!

哦,那个啊。F是任意函数,$本身就是函数,因此,直接使用$替换就可以了:

var $ = function(selector, context) {
    return new $.fn.init(selector, context);
};
var F = function() { };   // 这个直接删除
$.fn = $.prototype;
$.fn.init = function(selector, context) {
    // ...
    return this;
};

// ...

上图代码的demo地址应该不会被人看到吧……

实际上,如果你不是非得一个$行便天下的话,到了上面进阶第9步就足够了。jQuery在第10步的处理是为了彰显其$用得如此的出神入化,代码完美,令人惊叹!

至此,jQuery大核心已经一步一步走完了,可以看到,所有的这些进阶都是根据需求、实际开发需要来的,慢慢完善,慢慢扩充的!

11. 每个扩展方法都要$.fn.xxxx, 好闹心的来

$.fn.css = function() {}
$.fn.attr = function() {}
$.fn.data = function() {}
// ...

每个扩展前面都有个$.fn, 好讨厌的感觉,就不能合并吗?

于是,jQuery搞了个extend方法。

$.fn.extend({
    css: function() {},
    attr: function() {},
    data: function() {},
    // ...
});

12. $()不仅可以是选择器字符串,还可以是DOM
init方法中,判断第一个参数,如果是节点,直接this[0] = this_node. over!

以下13~?都是完善啊,补充啊,兼容性处理啊什么的,没有价值,到此为止!

三、排了很长队的结束语

网上也有其他一些介绍jQuery原理或机制的文章,可能当事人自己理解,而阅读者本来就不懂,说来说去,越说越绕,可能更不懂了。

jQuery是很优秀,好比身为灵长类的人类。但是,其诞生显然是从简单开始的。因此,要了解人类,可以通过追溯其起源。如果你是上帝,要让你造一个人,你会怎么造,是一口气出来?女娲造人还要捏泥人呢!不妨从单细胞生物开始,随着自然进化,淘汰,自然而然,就会出现人类,上帝他就是这么干的。

jQuery的诞生也大致如此,要想了解jQuery,可以试试踏着本文jQuery的成长足迹,一点一点逐步深入,您就会了解为何jQuery要这么设计,它是如何设计的等。

虽然,内容由浅及深,但是,其中涉及的原型以及new构造函数的一些特性,对于新人而言,还是有一些理解门槛的,希望我的描述与解释可以让你有一丝豁然开朗,那就再好不过了。

感谢您的阅读至此,欢迎指出文章可能书写不准确的地方,再次感谢!

月底在百姓网有个小分享,演示文档连个肉渣子还没准备呢。因此,未来一周休文。

(本篇完)

分享到:


发表评论(目前96 条评论)

  1. 热血灬马里奥说道:

    偶然与必然的关系 有机物的产生 其实和人择原理密切关系 为什么会产生有机物 这是产生以后 我们的思考 类似观察者偏差 如果不产生也就没有这个命题了 因为思考的主体已经没有了 绝对的客体是不存在的 一定要有主体参与 正如宇宙大爆炸理论一样

  2. 阿登说道:

    大神整体讲的很好,不过,关于
    var $ = function(selector, context) {
    return new $.fn.init(selector, context);
    };
    var F = function() { };

    $.fn = F.prototype;
    $.fn.init = function(selector, context) {
    // …
    return this;
    };
    这里,从js原型,F的原型就是{},这里用
    $.fn = {} //定义$的fn属性为对象,下面都是给该属性对象添加属性方法,如hide
    或者
    $.fn = $.prototype // 给$函数的原型对象起个别名
    这两种都好理解,但此处大神你使用一个无关的F函数的原型,我就纳闷不甚理解了,就让我纠结了好久

  3. sx说道:

    非常好的文章,我刚学的js,也能看懂。基本把jquery分析了透彻。

  4. 陈子云说道:

    我按照您写的这个为核心, 弄了一个超简易的jq版本,算注释加一起也才400多行,github地址如下https://github.com/everlose/jqfree。

  5. liuyc说道:

    (function(w){
    function ss(){
    return new ss.F();
    }
    ss.F = function(){
    this.length = 10;
    }

    ss.F.prototype.show = function(){
    alert(this.length);
    }

    ss.fn = ss.F.prototype;

    ss.fn.add = function(){
    this.length += 10;
    return this;
    }
    w.$s = w.ss = ss;
    }(window));

    var _s = $s();
    _s.add().show();//20
    console.log(_s instanceof $s); // false

    var $div = $(“#cc”);
    console.log($div instanceof jQuery); //true

    这应该就是$s和jquery定义的唯一区别吧,$()选择器获得的对象通过原型链继承,是jQuery的实例

  6. Perhaps说道:

    var F = function(id) {
    return this.getElementById(id);
    };
    F.prototype.getElementById = function(id) {
    this.element = document.getElementById(id);
    return this;
    };
    F.prototype.hide = function() {
    this.element.style.display = “none”;
    };

    new F(“image”).hide(); // 看你还不隐藏

    按照之前说的“new F()如果没有返回值(Undefined类型),或返回值是5种基本型(Undefined类型、Null类型、Boolean类型、Number类型、String类型)之一,则new F()我们可以看成是原型扩展方法中的this; ”
    那么此处getElementById里面的return this应该是可以不写的吧

  7. 沐沐说道:

    第八节: $(“img”).hide(); 有效,为什么 $(“image”).hide();无效,求大神解答

    • gxx说道:

      你这个是按元素标签查找,对于一个img元素来说,document.querySelectorAll方法支持的标签名是img而不是image。

  8. 74sharlock说道:

    不能再吊。看到第二遍的时候,自己测试了几个知识点,完全理解了。
    为何
    var $ = function(){}
    的时候,
    $.fn = $.prototype?
    然后我发现其实当一个变量指向一个引用数据类型或者一个方法时,都是可以直接挂载属性的,并非只有当
    $ = {}
    才行。
    var $ = ‘string’ 或者 var $ = 123或者其他三种基本数据类型,$.fn都是没有意义的。
    再次感谢作者让我注意到了平时忽略掉的知识点。

  9. sbaway说道:

    第五部分中的 this.element = document.getElementById(id);
    是不是应该改成this.__proto__.element = document.getElementById(id) 啊

    不然element还是暴漏在new F()中

    • tommy说道:

      这里的”暴露“指的是不暴露F类型实例属性创建的过程,即隐藏this.element的创建过程,因为创建过程可以看作一种“行为”,或者说没有function包着的函数吧。假如初始化this.element的过程很复杂,再把他们放在构造函数内部就不合理了,应放到原型中复用。还有就是this.element这个属性一定要“暴露”,因为它是实例属性,即每次通过$()获取的实例对象的element都不一样。放在this,_proto_中由于element成为共享属性,每调用一次$()方法就被覆盖一次。

  10. kiwi说道:

    楼主可以搜搜“醉汉回家理论”,讲的就是某些事物必然会发生

  11. clover说道:

    上面的引用来自这里。什么意思呢?说白了就是,new F()如果没有返回值(Undefined类型),或返回值是5种基本型(Undefined类型、Null类型、Boolean类型、Number类型、String类型)之一,则new F()我们可以看成是原型扩展方法中的this; 如果返回是是数组啊、对象啊什么的,则返回值就是这些对象本身,此时new F() ≠ this。

    怎么感觉这句话说反了呢,或者是我理解错了?

  12. mxfc说道:

    那么关键的地方放个那么暴露的。我都不敢多做停留,一看到那就赶紧滑过去。。。

  13. 路人说道:

    第5点后面的解决暴露的问题的代码
    var F = function(id) {
    return this.getElementById(id);
    };
    F.prototype.getElementById = function(id) {
    this.element = document.getElementById(id);
    return this;
    };
    F.prototype.hide = function() {
    this.element.style.display = “none”;
    };

    new F(“image”).hide(); // 看你还不隐藏

    但是这个通过new F(“image”).hasOwnProperty(“element”)还是返回true
    这样岂不是还是暴露着

  14. 潇意说道:

    大神有研究过jquery的事件的处理机制么?如果要对jquery事件进行分析,大神会从哪些点入手呢?

  15. 潇意说道:

    非常感谢,之前看这块的时候一直纠结于$.fn = $.prototype,一直都无法理解jquery作者为啥这样子写,看完大神的讲解,理会到了,多谢了,每次看你的文章,你都能把知识点这样深入浅出的讲解出来。大神可以传授一下秘诀么?

  16. 小新说道:

    把 var $ = function(selector, context) {
    return new $.fn.init(selector, context);
    };的new去掉 ,再去掉这一行 $.fn.init.prototype = $.fn;
    我发现最后一个demo也能跑的通嘛
    new到底有什么好处呢 js这个函数又能new又能直接执行怎么这么蛋疼··

  17. Potter说道:

    —-“11. 每个扩展方法都要$.fn.xxxx, 好闹心的来”—– 这个最后运行是会有问题的。

    @@. 此段目的很明确,是将公共函数通过$.fn(原型对象)上extend功能添加到 对象中 。
    missing @1. 但是这个extend函数还没有被实现,无法处理传入的包含各种方法的json对象。

    == 最后,只能模仿JQuery源码将extend函数的函数体实现

  18. nowucme说道:

    第5部分有点疑惑,一开始为了证明element暴露在new F(“image”)上,用
    new F(“image”).hasOwnProperty(“element”);
    返回为true说明了下,
    但后面的改进方法,调用上面的还是会返回true啊?还是没隐藏吧

  19. Neilgle说道:

    旭哥,提个建议
    第5部分的

    建议解释完 new F(“”)返回值是DOM后无法使用this,

    这样说明一下,如果返回的是DOM,那么DOM就和原来的new F()完全没关系了,它也无法使用F的原型F.prototype.hide,所以要用到this.element

  20. 青你个葱说道:

    旭哥,IE7下this.length的值是0 有啥解决办法不

  21. Mayon说道:

    文章真的很赞哦。如今大部分前端的文章,要么是翻译国外的,要么就是转来转去改俩字换各上下文那种,感觉楼主的每篇文章都非常用心。学习的榜样啦!~

    • Mayon说道:

      PS:剖析的真的很深生动。我上周无意中看到这篇文章,收藏下来。今天一口气看完了,说实话,放在一年前,我肯定看不太懂,估计中途就放弃了。哈哈。太赞了,非深入研究JS源码和jquery源码的人,不可能写出这么深入浅出的文啊。

  22. qaq说道:

    很不错,还幽默。
    教书的写不出这样的东东,写这样东东的人又不教书去,可惜了…

  23. 欧耶说道:

    请问如何能像jquery一样直接 $(“#id”).value 而不需要[0]呢?( $(“#id”)[0].value)。

  24. wenyejie说道:

    亲!你的文章写的很棒,从需求出发,从无到有,一步一个脚印。

  25. blwoosky说道:

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

    这句没懂,$.fn不就是$的原型吗?为什么$.fn.init前面要加new?????

    • LeoYuan说道:

      这句要结合$.fn.init.prototype = $.fn来看,因为从外界来看,$是构造函数,而实际上$.fn.init才是构造函数,而从代码写法简单的角度来讲,别人扩展$的功能习惯在$的原型里边写,即$.fn,而$.fn = $.fn.init.prototype结合起来就刚好完成了内部写法简单和外部功能完善。

  26. abel533说道:

    写的真不错。。最近看过一些jquery代码分析的文章,都没有你写的好,关键是在你讲解的方法不是从jquery代码开始。而是逐步去实现他。这个角度不错。

    我前段时间也在写理解jquery源码方面的文章,我主要在讲js的一些基本尝试,如prototype,oo,立即执行函数等…

    总之,楼主的文章让我受益匪浅!

  27. 凹死卡影帝说道:

    搞笑的配图+幽默风趣的解释,看完豁然开朗,您的很多文章 对我们先手来说,真的很棒,少走了一些弯路,希望以后能多有一些这样的文章

  28. 看了很多遍,终于把它看懂了,智商不行啊。。。

  29. 刘郁说道:

    看下源码就知道jQuery的实例化机制再简单不过了,就利用很普通的原型继承,完全没有博主说的那么绕,而且这篇文章讲得有些曲解了且表述不到位的地方。

    • 张 鑫旭说道:

      @刘郁 不是所有人都像兄弟你这么牛逼的!

    • LeoYuan说道:

      @刘郁
      能否讲述一下为啥jQuery不直接使用$当构造函数,而要使用$.fn.init。
      只是为了不使用new来获取对象么?貌似不用new来构造对象有很多其他方法,John采用的这种方式的优点是啥?

      • 东门吹风说道:

        我觉得用new来调用也是可以的,只不过没啥作用,还是返回new $.fin.init
        另外下面这种实现方式,有什么不妥之处吗?
        var $ = function(selector, context) {
        if (this instanceof $) {
        return this.init(selector, context);
        } else {
        return new $(selector, context);
        }
        }; 这种方法有什么问题吗?求指教!

  30. misadancer说道:

    fn.call(this[i], i, this[i]);

    第一个this[i]是定义this的指向的,后面的i和this[i]是干啥的?
    试验后,去掉后面的参数,改成fn.call(this[i]) 也没问题~

    • 张 鑫旭说道:

      @misadancer i是索引值,this[i]指遍历元素。约定俗成写法,参考ES5 foreach方法。jQuery中类似遍历“索引”为第一个参数。

  31. 游客说道:

    image-hide9.html 的代码看着头晕
    我改成这样就很好看懂了。
    请教下这样有哪些不妥。

    var $ = function(selector, context) {
    return new $.fn(selector, context);
    };

    $.fn = function(selector, context) {
    var nodeList = (context || document).querySelectorAll(selector);
    this.length = nodeList.length;
    for (var i=0; i<this.length; i+=1) {
    this[i] = nodeList[i];
    }
    return this;
    };
    $.fn.prototype = $.fn;
    $.fn.each = function(fn) {
    var i=0, length = this.length;
    for (; i<length; i+=1) {
    fn.call(this[i], i, this[i]);
    }
    return this;
    };

    $.fn.hide = function() {
    this.each(function() {
    this.style.display = "none";
    });
    };

    • 东门吹风说道:

      你的方法中$.fn是作为构造函数使用的,随后的这条语句 $.fn.prototype = $.fn; 设置该构造函数的原型对象为构造函数本身。这样就导致一个问题:生成的jQuery对象的原型对象是一个函数对象,会导致沿着__proto__链查找属性的时候出现混乱。

  32. KEInwang说道:

    旭哥是怎么安排工作和兴趣的啊,工作之外的内容自己立项练习么?

  33. 3322说道:

    jQuery是如何实现 在控制台中输入$(“button”)输出一个nodeList数组而不是$.fn.init …(略)

    另外链式操作是怎么实现的呢?

    • 张 鑫旭说道:

      @3322 对象中的方法执行后返回对象自身即可以实现链式操作。

      • 3322说道:

        那前一个问题呢

        • zbs1216说道:

          9. 我不喜欢this.element, 可以去掉吗?

          …………

          F.prototype.init = function(selector, context) {
          var nodeList = (context || document).querySelectorAll(selector);
          this.length = nodeList.length;
          for (var i=0; i<this.length; i+=1) {
          this[i] = nodeList[i];
          }
          return this;

          ………..分割线………
          以上代码,运行 $(“button”) 时,把 nodeList 数组的每一个项的引用都复制到 $(“button”) 返回的对象,成为该对象的属性(属性名为:“1”、“2”、“3”、…、(nodeList.length – 1).toString)。
          因此, $(“button”) 返回的对象,就包含了 nodeList 数组的每一个数组项。

          不知道对不对?

  34. cc说道:

    “上面代码显然是有问题的,new的是$.fn.init, $.fn.init的返回值是this. 也就是$()的返回值是$.fn.init的原型对象,尼玛$.fn.init的prototype原型现在就是个光杆司令啊”这句怎么理解啊?
    为什么new的是$.fn.init, $.fn.init的返回值是this. $()的返回值就是$.fn.init的原型对象?看不懂。

    • 张 鑫旭说道:

      @cc 类似于第5部分的解释,因为原型中获取元素的方法返回的是this, 因此,new F("image")可以看成是F.prototype; 同样new $.fn.init()返回值可以看成是$.fn.init.prototype

      • 说道:

        我也有上面的疑问,于是我在chrome浏览器中调试了下面的代码:

        var $ = function(selector, context) {
        return new $.fn.init(selector, context);
        };
        var F = function() { };

        $.fn = F.prototype;
        $.fn.init = function(selector, context) {
        // …
        return this;
        };

        $.fn.init.prototype===$();

        >>false

        $.fn.init.prototype===$().__proto__;
        >>true

        (注: >>为chrome浏览器控制台输出内容)

        从上面的chrome浏览器控制台输出结果看来,是不是这样的:$()的返回值与$.fn.init.prototype不是同一个对象,所以应该不能等同吧?

        • 求讨论说道:

          “上面代码显然是有问题的,new的是$.fn.init, $.fn.init的返回值是this. 也就是$()的返回值是$.fn.init的原型对象,尼玛$.fn.init的prototype原型现在就是个光杆司令啊”
          这句话表述错误吧,$()返回对象的__proto__属性是$.fn.init的原型对象,$()都返回jQuery对象了怎么可能是谁谁的原型对象呢。。。

  35. 靖鸣君说道:

    不知何时,张含韵已经不再出现在旭哥的博文里了。。。换口味了么?

  36. 47说道:

    1. gelElementById太长了 错别字~

  37. steven说道:

    fn.call(this[i], i, this[i]);

    ———————-

    这一步的作用是??

    • 张 鑫旭说道:

      @steven 改变fn的上下文,这样,each方法中的this就是我们遍历的DOM元素了。

      • 可惜不是你说道:

        为什么是fn.call(this[i], i, this[i]); 呢?后面两个值i,this[i]是fn的参数,可是你写的each的参数fn函数没有传递参数呢?

        F.prototype.hide = function() {
        this.each(function() {
        this.style.display = “none”;
        });
        };

        • 可惜不是你说道:

          为什么是fn.call(this[i], i, this[i]); 呢?后面两个值i,this[i]是fn的参数,可是你写的each的参数fn函数没有传递参数呢?

          F.prototype.hide = function() {
          this.each(function() {
          this.style.display = “none”;
          });
          };

  38. CJ说道:

    好几处字符被吞掉了

    如:
    var F = function() {};
    F.prototype.hide = function() {
    this?.style.display = “none”;
    };
    拼错了
    gelElementById太长了

  39. anonymous说道:

    F.prototype = {
    element: [NodeList],
    each: function() {},
    hide: function() {}
    }
    element不是原型属性吧。

  40. sss说道:

    赞同博主的说法,很多复杂东西都是重简单开始然后慢慢完善的,要了解其发展历史才知道其遇到的问题和解决的思路,这一点感觉很多时候很多书或者文章感觉都不是很好啊

  41. artwl说道:

    循序渐进,通俗易懂,谢谢分享

  42. ahmu说道:

    不得不说此文分析的非常透彻 层层递进 顶!!!

  43. 702017364说道:

    看被的文章看的把自己都绕进去了,今天看见楼主的贴,有种豁然开朗的感觉。。。感谢楼主

  44. skip说道:

    原来jquery是这么回事呀!

  45. 我太笨了说道:

    第5条的必要性也没看懂。运行修改后的例子发现new F(“image”)还是有element这个属性啊,那多转了个小圈子是图什么呢?

    • 张 鑫旭说道:

      @我太笨了 第5条的element不是在包装器对象本身上,而是在原型上。

      • 萨娇说道:

        第五条的必要性?按照那样子改了new F(“main”).hasOwnProperty(“element”)的值还是true啊?

      • alwaysonlinetxm说道:

        旭哥你好!我仔细研究了下第五条,发现element实际上还是存在于new F(“image”)这个新建的对象上,并不在原型上。虽然是在F.prototype.getElementById里位element复制,但还是通过this.element来实现的。尽管不在构造函数中而是在F.prototype的方法中,但此时的this仍旧是指向通过new新建的对象,所以element并不是添加在原型上的。而且原型是共享的,如果真的是添加在原型上那岂不是每次new F()都会更改所有之前F的实例的element了,那就不合逻辑了。不知道我的看法是否正确,期待旭哥回复!

      • haha~说道:

        个人感觉第5条的element还是在实例对象上,没有在原型上,请解释一下?

  46. 我太笨了说道:

    “要想使用prototype.hide方法中的this, 偶们就不能让F函数有乱七八糟的返回值。
    因此,new F()直接返回DOM是不可取的,但我们可以借助this间接调用。”

    ———————————————-

    第四条的这一步推理我没看懂。为什么不能让F返回乱七八糟的值所以返回DOM是不可取的?如果只返回DOM而不返回那5种基本类型,那为什么不可取呢?

  47. 一堵墙说道:

    学习了,我们在使用一些东西的时候还是有必要知道它的原理。

  48. sweetehs说道:

    好文章。收藏了。

  49. cssmagic说道:

    这篇文章分析得真透彻啊,佩服佩服,感谢分享!

  50. 玉面小飞鱼说道:

    第4条里面有一行代码: this?.style.display = “none”;

    这里的?是啥子意思?

    看了一下订餐小秘书的代码里面没有用jQuery库,用的是MooTools,张大哥是怎么考究的?

    • 张 鑫旭说道:

      ?表示不一定是this.style.display, 也有可能是this.xxx.style.display. MooTools是前辈留下的,我只是沿袭传承并发扬光大,(*^__^*) 嘻嘻……