我大学时候在图书馆翻过一本很破旧的书,讲生物理论的,主要内容就是探讨生命的产生是偶然还是必然。里面很多亚里士多德都看不懂的公式计算什么的,还有模拟原始地球环境出现了有机物的实验什么的 。总之,书论述的观点是:"在当时的地球环境下,生命的产生是必然的!" 无数次机会的偶然条件、无数次化合物的相遇反应等必定会产生有机物,再有 N 多偶然,有机物必然形成了有机体……
这种理论类似于,你是个过马路非常小心的人,且你万寿无疆,除了怕被汽车撞。给你 100 万年的寿命,你最后必然还是被车撞死。
如果以这种理论来看 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";
- };
好像又看见长长的重复的东西,
, 为什么每次开门都要从包里找到钥匙、对准插口插进去、还要左扭扭右扭扭呢?一次还好,天天经常老是这样怎受得了。设想,要是有个芝麻开门的暗号就好了,"open 开",声音识别,门自动开了,多省心。
- xxx.style.display = "none"
这里每次隐藏都要
, 比每天拿钥匙开门还要烦,我希望有一个快捷的方式,例如,"hide 隐",语句识别,元素自动隐藏,多省心。
- xxx.style.display = "none"
就是要变成下面的效果:
- $("button").onclick = function() {
- $("image1").hide();
- $("image2").hide();
- };
3. 如何识别 "芝麻开门" 的暗号
本质是个 DOM 元素,
- $("image1")
也就是在 DOM 元素上扩展一个
- $("image1").hide()
方法,调用即隐藏。
- hide
哦,扩展,立马想到了 JS 中的
原型。//zxx: 老板,现在满大街什么菜最便宜。老板:原型啊,都泛滥了!
- prototype
- HTMLElement.prototype.hide = function() {
- this.style.display = "none";
- };
上面代码的应该不会被人看到吧……
虽然在身体上钻了个窟窿插进入了一个方法,毕竟浏览器有有效果啊,切肤之痛就不算什么了。但是,我们是在泱泱天朝,很多 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
不就完事 OK 啦!——
- DOM元素
=
- this.style.hide
=
- new F().style.hide
"!
- DOM.style.hide
上面的引用来自。什么意思呢?说白了就是,
如果没有返回值 (
- new F()
类型),或返回值是 5 种基本型(
- Undefined
类型、
- 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
因此,
直接返回 DOM 是不可取的,但我们可以借助
- new F()
间接调用。比方说:
- this
- var F = function(id) {
- this.element = document.getElementById(id);
- };
- F.prototype.hide = function() {
- this.element.style.display = "none";
- };
- new F("image").hide(); // 看你还不隐藏
上面代码的应该不会被人看到吧……
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()
直接返回 DOM 是不可取的 ",怎么现在又有
- new F()
呢?大家务必擦亮眼睛,
- return
的返回值是
- F.prototype.getElementById
,也就是
- this
的返回值是
- new F()
. 形象点就是
- this
出了一拳,又反弹到自己脸上了。
- new F("image")
上面代码的应该不会被人看到吧……
6. 我不喜欢 new, 我喜欢 $
这种写法我好不喜欢,我喜欢
- new F("image")
, 我就是喜欢
- $
, 我要换掉。
- $
好吧,把
什么什么藏在
- new
方法中把~
- $
- var $ = function(id) {
- return new F(id);
- };
于是,上面的图片隐藏的直接执行代码就是:
- $("image").hide();
上面代码的应该不会被人看到吧……
IE6 浏览器也是支持的哦!是不是已经有些 jQuery 的样子啦!
在 IE8 + 浏览器中,我们有选择器 API,
与
- document.querySelector
,前者返回唯一
- document.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 就使用了一个比较强大的选择器框架 -
. 知道就好,重在演示原理,因此,下面还是使用原生的选择器 API 示意,故 demo 效果需要 IE8 + 浏览器下查看。
- Sizzle
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(); // 页面所有图片都隐藏啦!
上面代码的应该不会被人看到吧……
单纯一个
方法还可以应付,再来个
- 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(); // 页面所有图片都隐藏啦!
上面代码的应该不会被人看到吧……
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]
上面代码的应该不会被人看到吧……
10. 我是完美主义者,我特不喜欢 F 名称,可以换掉吗?
这个名称从头到尾出现,我好不喜欢的来,我要换成
- F
, 我就是要换成
- $
符号……
- $
这个……
已经用了啊,再用冲突的吧。再说,你又不是狐后,耍无赖也没用啊……
- $
好吧,想想其他办法吧。一步一步来,那我把所有的
换成
- F
.
- $.fn
就有:
上图代码的应该不会被人看到吧……
显然,运行是 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.init
原型现在就是个光杆司令啊,哟,正好,
- prototype
对应的原型方法,除了 init 没用外,其他
- $.fn
,
- 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;
- };
- // ...
上图代码的应该不会被人看到吧……
实际上,如果你不是非得一个
行便天下的话,到了上面进阶第 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
. over!
- this[0] = this_node
以下 13~? 都是完善啊,补充啊,兼容性处理啊什么的,没有价值,到此为止!
网上也有其他一些介绍 jQuery 原理或机制的文章,可能当事人自己理解,而阅读者本来就不懂,说来说去,越说越绕,可能更不懂了。
jQuery 是很优秀,好比身为灵长类的人类。但是,其诞生显然是从简单开始的。因此,要了解人类,可以通过追溯其起源。如果你是上帝,要让你造一个人,你会怎么造,是一口气出来?女娲造人还要捏泥人呢!不妨从单细胞生物开始,随着自然进化,淘汰,自然而然,就会出现人类,上帝他就是这么干的。
jQuery 的诞生也大致如此,要想了解 jQuery,可以试试踏着本文 jQuery 的成长足迹,一点一点逐步深入,您就会了解为何 jQuery 要这么设计,它是如何设计的等。
虽然,内容由浅及深,但是,其中涉及的原型以及
构造函数的一些特性,对于新人而言,还是有一些理解门槛的,希望我的描述与解释可以让你有一丝豁然开朗,那就再好不过了。
- new
感谢您的阅读至此,欢迎指出文章可能书写不准确的地方,再次感谢!
来源: http://www.bubuko.com/infodetail-1957167.html