javascript笔记:临摹jQuery(二)

在我前一篇博客里有位童鞋问了xQuery.fn.init.prototype = xQuery.fn;写法的目的,这个问题在我临摹jQuery时候也碰到过,最开始我是没有加入这个方法,在firebug里面,方法console.log(xQuery('').xquery);打印出的结果是:undefined,这就表明xquery属性根本就没有定义,我构建的xQuery对象只是保留了在xQuery.fn.init(selector,context);里面存在的属性,如是我做了下面的测试:
xQuery.fn = xQuery.prototype = { init:function(){ return this; }, tempquery:'1.0.1', length:23, size:function(){ return this.length; } }; console.log(xQuery().tempquery);//undefined console.log(xQuery().length);//undefined console.log(xQuery().size());//error:xQuery().size is not a function
那么xQuery.fn.init.prototype = xQuery.fn;作用到底是怎样的?我做了下面一系列的测试:
测试一:对于代码:
var xQuery = function(selector,context){ return new xQuery.fn.init(selector,context); }
这是使用了javascript里面的工厂模式构建对象,只不过这个工厂比较特别,让人第一眼觉得是个迭代的调用的工厂模式,因此我写了下面的测试代码:
var xQuery = function(){ return new tempQuery(); }; var tempQuery = function(){}; tempQuery.prototype = { tempquery:'1.0.1', length:4, size:function(){ return this.length; } }; console.log(xQuery().tempquery);//1.0.1 console.log(xQuery().length);//4 console.log(xQuery().size());//4 console.log(xQuery() instanceof xQuery);//false
这里我另起了一个对象tempQuery ,初始化了tempQuery 的原型链的属性,那么每次调用xQuery()就是在使用作为xQuery构造函数返回值的tempQuery 对象。如果这么做,就存在了javascript里工厂模式的缺点了:1.会产生重复的相似对象,造成系统资源的浪费;2.xQuery对象识别的问题(例如:console.log(xQuery() instanceof xQuery);//false)。
要解决这些问题,那么xQuery的构造函数应该是构造自己本身,而不是每次构造都是一个新的对象,这样的思路就能避免javascript工厂模式的缺陷,因此我又做了下面测试。
测试二:
var xQuery = function(){ new xQuery();//too much recursion new xQuery();//too much recursion,无限递归调用 }; xQuery.prototype = { tempquery:'1.0.1', length:8, size:function(){ return this.length; } }; console.log(xQuery().tempquery); console.log(xQuery().length); console.log(xQuery().size()); console.log(xQuery() instanceof xQuery);
最终的结果是会在new xQuery();代码处报出too much recursion new xQuery();//too much recursion,无限递归调用 的错误,也就是所谓的内存溢出了。这种写法真白痴,如果不是为了验证结果,我不会去写这么明显的递归调用的代码的。
那么有什么更好的写法了,如是我再看看jQuery源码,发现它是使用到了prototype原型链来构造xQuery对象。因此就有下面测试代码:
测试三:
var xQuery = function(){ return new xQuery.prototype.init(); }; xQuery.prototype = { init:function(){ return this; }, tempquery:'1.0.1', length:8, size:function(){ return this.length; } }; console.log(xQuery().tempquery);//undefined console.log(xQuery().length);//undefined console.log(xQuery() instanceof xQuery);//false console.log(xQuery().size());//xQuery().size is not a function
这种写法就没有报内存溢出的错误了,不过也没有达到我们预期的效果xQuery().tempquery和xQuery.length都是undefined,xQuery() 的类型也不是xQuery,size()还没有定义(这个我和文章前面没写xQuery.fn.init.prototype = xQuery.fn;的情况一样),为什么会有这样的结果了?其实从javascript对象的理论来理解这种结果就很简单了。原因分析如下:
我们知道javascript里面除了string,boolean等基本类型外其他都是object(对象),而对象的名称只是该对象的在栈内存中的地址,换种说法就是该对象的引用。其实我们程序中的return new xQuery.prototype.init();这个只是调用了xQuery.prototype.init();这个引用的所对应的对象,其他的对象里的属性都没管,也就是说代码里的:
size:function(){ return this.length; }
还没有被调用过,所以我们看到报了size is not a function的错误。呵呵,是不是被我说糊涂了啊,其实这里我们还要了解一个javascript里面的一个知识,javascript是一个脚本语言,他和C、java在编译,运行上有很大不同。javascript里面如果我们定义一个function,例如:
var ftn = function(){ alert('Hello World'); }
页面加载时候,浏览器里面的javascript解释器只是生命了ftn变量,这个过程叫做javasript的预编译,这时候的ftn = undefined,当我们new ftn();时候,也就是我们运行js代码时候,javascript解释器是先编译再执行,而return new xQuery.prototype.init();只是运行了init()对象,而size还没运行,所以运行结果就是size is not a function的错误。 那么要解决这个问题,我们只要在return new xQuery.prototype.init();时候同时让xQuery.prototype其他代码也运行起来,那么上面的问题不是就解决了吗?
如是我做了第四个测试。
测试四:
var xQuery = function(){ return new xQuery.prototype.init(); }; xQuery.prototype = { init:function(){ return this; }, tempquery:'1.0.1', length:8, size:function(){ return this.length; } }; xQuery.prototype.init.prototype = xQuery.prototype; console.log(xQuery().tempquery);//1.0.1 console.log(xQuery().length);//8 console.log(xQuery() instanceof xQuery);//true console.log(xQuery().size());//8
哈哈,这不就是jQuery的效果啊。我把xQuery.prototype.init引用对应的对象的prototype原型链指向了xQuery.prototype,那么构建 new xQuery.prototype.init(); 时候xQuery.prototype 也同样被构造了,那么我在init里面修改和init平级的属性,效果如何了?
测试五:
var xQuery = function(){ return new xQuery.prototype.init(); }; xQuery.prototype = { init:function(){ this.length = 10001; this.tempquery = '1.6.1'; return this; }, tempquery:'1.0.1', length:8, size:function(){ return this.length; } }; xQuery.prototype.init.prototype = xQuery.prototype;
结果:
console.log(xQuery().tempquery);//1.6.1 console.log(xQuery().length);//10001 console.log(xQuery() instanceof xQuery);//true console.log(xQuery().size());//10001
这就说明this指针指向的对象是同一个xQuery对象,表明xQuery.prototype也被构建了。不过上面的写法似乎和jQuery的写法还是有所不同,不过效果一样了哈。
jQuery里面的jQuery.fn真是没啥好说的,只是jQuery的开发者觉得jQuery.prototype太长了为了节省代码而将jQuery.fn作为jQuery.prototype的别名,下面我把代码修改成jQuery的样式,最终代码如下:
var xQuery = function(){ return new xQuery.prototype.init(); }; xQuery.fn = xQuery.prototype = { init:function(){ this.length = 10001; this.tempquery = '1.6.1'; return this; }, tempquery:'1.0.1', length:8, size:function(){ return this.length; } }; xQuery.fn.init.prototype = xQuery.fn; console.log(xQuery().tempquery);//1.6.1 console.log(xQuery().length);//10001 console.log(xQuery() instanceof xQuery);//true console.log(xQuery().size());//10001
总结下吧:本来我想说这就是jQuery架构的核心,写完后发现其实不然,所以这里的总结只是说jQuery源码里的代码技巧性很高,我下篇博客打算暂停下分析jQuery源码,而将一些基础知识系统的讲解下,javascript是一个常常让人蒙掉的语言,就是它有很多非常规诡异的语法,或许这些大伙都清楚,但是时不时的温故而知新还是相当有好处的。
Tags: 

延伸阅读

最新评论

发表评论