通过前面两篇博文的积累,
使用闭包构造模块(基础篇)——Object-Oriented Javascript之三
使用闭包构造模块(优化篇)——Object-Oriented Javascript之四
我们现在已经具备了足够的知识,去完成一个比较有挑战性的任务——构造一个简化版的jQuery库——myQuery。
我选择去构造myQuery的动机是:
1 jQuery足够优秀,是模块封装的典范;
2 如果我们能够实现一个良好的jQuery模块,那么就意味着我们能够应付绝大多数的模块封装问题。
如果读者读过jQuery源码,本文依然能够为你带来一些价值,因为在本文中封装myQuery所使用的方法是与jQuery完全不同的,而且还实现了jQuery所没有做到的底层数据的私有性。
jQuery最大好处在于免除了开发人员学习那糟糕的DOM API。另外从编码的角度去看,jQuery的选择器和链式调用等,大大简化了代码。
为了实现以上的优点,jQuery将DOM元素和相应DOM操作封装成一个jQuery对象,使得我们能够舍弃糟糕的DOM API,而去拥抱简单易用的jQuery API
jQuery还有一个重要的特点,它是可以被动态扩展的,我们可以简单地添加自己的jQuery API/插件。为实现这一点,jQuery的数据和方法是“分开”定义的,方法的集合对象暴漏出去,让开发人员有机会往里面加自定义的内容。分开的数据和方法,最后以某种方式结合起来,统一为一个jQuery对象。方法可以不和数据耦合在一起,“分开”定义,听起来很不可思议,但是javascript利用this延迟绑定就能够做到这一点。我们可以在不同的地方分别定义数据和方法,然后可以利用 apply/call/bind/原型链 简单地将二者结合起来。这给人的感觉就像,可以简单地把任意的数据和方法拼成一个模块。
我们的myQuery将着眼于如何封装实现上面提及到jQuery的重要特点,需求如下:
1. selector支持#id和tagName
2. 支持text(),text(str),each(function(index,dom)), size()方法
3. 支持链式调用
4. 支持方法的扩展
尽管需求很少,但是在这些需求实现的背后,往往是我们封装模块的时候最可能遇到的问题,而且在这个简化版jQuery的基础上,完全可以扩展为一个完整的jQuery。
目录:
私有模式实现myQuery
委托模式实现myQuery
私有模式实现myQuery
myQuery架构思想:
1. 底层数据:使用一个[],存放选择器选择出来的dom元素;
2. 对外方法:使用一个{},存放所有公用方法。
3. 关联数据与方法:调用$("xxx")时,dom元素数组和公用方法object关联起来构成一个myQuery对象。
对看过jQuery源码的同学说的话:jQuery底层数据其实是一个由dom元素构成的“类数组”,所有的方法都是操作这个类数组。但是jQuery的类数组不是私有的,可以被直接修改,例如$("div")[0] = document.getElementById("xxx")。我们可以使用前两章学过的闭包克服这个缺点,将dom数组隐藏起来,使外界完全不可能直接修改dom数组,仅仅能修改dom数组的成员,就是说类数组[div1,div2]中的div1、div2的属性可以改变,但是[div1,div2]不能变成[div1,div3]。
实现如下
<!DOCTYPE HTML> <HTML> <HEAD> <TITLE>闭包实现myQuery</TITLE> </HEAD> <BODY> <div id="lazy2009">hello,lazy2009!</div> <div id="lazy_">hello,lazy_!</div> <script> if (!Function.prototype.bind) { Function.prototype.bind = function(context) { var args = Array.prototype.slice.call(arguments, 1); var that = this; return function() { return that.apply(context, args.concat(Array.prototype.slice.call(arguments))); } } } (function(win) { var slice = Array.prototype.slice; //使用闭包实现的类似jQuery的封装 //selector只支持#id和tagName两种选择器,例如$("#id"),$("div") var myQuery = function(selector) { //私有成员,不让外界直接修改 var arrDom = []; //调用document.getElementById或者document.getElementsByTagName获取元素集合 if (selector.charAt(0) === '#') { arrDom[0] = document.getElementById(selector.substring(1)); } else { var elements = document.getElementsByTagName(selector); for ( var i = 0; i < elements.length; i++) { arrDom[i] = elements[i]; } } var myQueryObj = {}; //导入myQuery对象公用方法 var methods = myQuery.methods; for ( var methodName in methods) { myQueryObj[methodName] = methods[methodName].bind(myQueryObj, arrDom); } return myQueryObj; }; //myQuery导出到window全局作用域 win.$ = win.myQuery = myQuery; //myQuery对象公用方法 myQuery.methods = { version : function() { return "1.0"; }, text : function(arrDom, s) { if (!s) { return arrDom[0].innerText; } else { for ( var i = 0; i < arrDom.length; i++) { arrDom[i].innerText = s; } } return this; }, each : function(arrDom, fn) { for ( var i = 0; i < arrDom.length; i++) { if (false === fn.call(arrDom[i], i, arrDom[i])) { break; } } return this; }, get : function(arrDom, index) { if (!index) { return slice.call(arrDom); } else { return arrDom[i]; } }, size : function(arrDom) { return arrDom.length; } }; })(window); </script> <script> //封装DOM API alert($("#lazy2009").text()); alert($("#lazy_").text()); //链式代码 $("div").text("hello, world").each(function(i, o) { alert(o.innerText + "==" + this.innerText); }); alert($("div").size()); //扩展方法 if (!myQuery.methods.toArray) { myQuery.methods.toArray = function(arrDom) { return arrDom.slice(0); } } alert($("div").toArray()); </script> </BODY> </HTML>
下面重点解析代码的几个地方
var arrDom = []; //调用document.getElementById或者document.getElementsByTagName获取元素集合 if (selector.charAt(0) === '#') { arrDom[0] = document.getElementById(selector.substring(1)); } else { var elements = document.getElementsByTagName(selector); for ( var i = 0; i < elements.length; i++) { arrDom[i] = elements[i]; } } var myQueryObj = {};
var myQueryObj = {}; //导入myQuery对象公用方法 var methods = myQuery.methods; for ( var methodName in methods) { myQueryObj[methodName] = methods[methodName].bind(myQueryObj,arrDom); }
-
第1步 使用bind"改造"公用方法,返回一个绑定了具体数据的新方法(this确定为myQueryObj,第一个参数确定为arrDom)。
-
第2步 将"改造"后的方法,设置到最终要导出的对象中
//导入myQuery对象公用方法 var methods = myQuery.methods; for ( var methodName in methods) { myQueryObj[methodName] = (function() { var _methodName = methodName;//这一步的意义是什么?循环中访问闭包的变量要注意 return function() { return methods[_methodName].apply(myQueryObj, [ arrDom ].concat(slice.call(arguments)));//闭包访问arrDom } })(); }
<!DOCTYPE HTML> <HTML> <HEAD> <TITLE>"私有"模式实现Counter</TITLE> </HEAD> <BODY> <script> if (!Function.prototype.bind) { Function.prototype.bind = function(context) { var args = Array.prototype.slice.call(arguments, 1); var that = this; return function() { return that.apply(context, args.concat(Array.prototype.slice .call(arguments))); } } } (function() { //定义方法 var methods = { add : function(data) { data.i++; return this; }, sub : function(data) { data.i--; return this; }, get : function(data) { return data.i; } }; function createCounter(num) { //定义返回对象 var ret = {}; //定义私有数据 var data = { i : num||0 }; //关联数据与方法,构成一个模块 for ( var methodName in methods) { ret[methodName] = methods[methodName].bind(ret, data); } //返回对象 return ret; } window.createCounter = createCounter; })(); var Counter = createCounter(1); alert(Counter.add().add().sub().get());//2 </script> </BODY> </HTML>
委托模式实现myQuery
有一种封装的方法,既能够做到底层数据是私有的,又能保证生成对象的高效,具体代码如下:
<!DOCTYPE HTML> <HTML> <HEAD> <TITLE>闭包实现myQuery</TITLE> </HEAD> <BODY> <div id="lazy2009">hello,lazy2009!</div> <div id="lazy_">hello,lazy_!</div> <script> (function(win) { var slice = Array.prototype.slice; //使用闭包实现的类似jQuery的封装 //selector只支持#id和tagName两种选择器,例如$("#id"),$("div") var myQuery = function(selector) { //私有成员,不让外界直接修改 var arrDom = []; //调用document.getElementById或者document.getElementsByTagName获取元素集合 if (selector.charAt(0) === '#') { arrDom[0] = document.getElementById(selector.substring(1)); } else { var elements = document.getElementsByTagName(selector); for ( var i = 0; i < elements.length; i++) { arrDom[i] = elements[i]; } } var myQueryObj = {}; //导入myQuery对象公用方法 var methods = myQuery.methods; //myQuery对象执行exec方法,会根据methodName委托到真正要执行的函数 myQueryObj.exec = function(methodName) { return methods[methodName].apply(myQueryObj, [ arrDom ] .concat(slice.call(arguments, 1))); } return myQueryObj; }; //myQuery导出到window全局作用域 win.$ = win.myQuery = myQuery; //myQuery对象公用方法 myQuery.methods = { version : function() { return "1.0"; }, text : function(arrDom, s) { if (!s) { return arrDom[0].innerText; } else { for ( var i = 0; i < arrDom.length; i++) { arrDom[i].innerText = s; } } return this; }, each : function(arrDom, fn) { for ( var i = 0; i < arrDom.length; i++) { if (false === fn.call(arrDom[i], i, arrDom[i])) { break; } } return this; }, get : function(arrDom, index) { if (!index) { return slice.call(arrDom); } else { return arrDom[i]; } }, size : function(arrDom) { return arrDom.length; } }; })(window); </script> <script> //封装DOM API alert($("#lazy2009").exec("text")); alert($("#lazy_").exec("text")); //链式代码 $("div").exec("text", "hello, world").exec("each", function(i, o) { alert(o.innerText + "==" + this.innerText); }); alert($("div").exec("size")); //扩展方法 if (!myQuery.methods.toArray) { myQuery.methods.toArray = function(arrDom) { return arrDom.slice(0); } } alert($("div").exec("toArray")); </script> </BODY> </HTML>这个myQuery实现与私有模式的唯一区别仅仅在于
//myQuery对象执行exec方法,会根据methodName委托到真正要执行的函数 myQueryObj.exec = function(methodName) { return methods[methodName].apply(myQueryObj, [ arrDom ] .concat(slice.call(arguments, 1))); }
myQuery对象只有一个方法exec,这个方法的第一个参数是实际上要执行方法的名字,当执行exec时候,exec委托了第一个参数指定的方法去执行。execMethod接下来的其他参数是依次传递到要执行方法的参数。
这个实现版本确实满足了生成对象的高性能以及底层数据的私有性,但是缺点是调用语法不符合常规。jQuery easy ui采取了类似这样的模式来封装api,不过目的不是为了私有性,而是为了每一次方法调用都返回一个jQuery对象。例如$("div").datagrid('addRow',{xxx:xxx}).datagrid('deleteRow',3)。尽管直觉上$("div").getDatagrid().addRow({XXX:XXX}).deleteRow(3)更可读,但是中途getDatagrid()就不是返回一个jQuery对象了。委托模式除了调用语法有些别扭之外,其余在性能、扩展性、适应各种变态要求上等等方面是非常好的,大家可以将之作为一种候选的封装模式。
相关推荐
python 闭包和装饰器(csdn)————程序
JavaScript闭包 JavaScript闭包 JavaScript闭包 JavaScript闭包
3.JavaScript 闭包 4.JavaScript 事件 5.javascript 跨域 6.javascript 命名空间 Oject-Oriented 1.JavaScript Expressive 2. Interfaces 3.Introduction 4. Inheritance 5.AOP Jquery [jQuery][9] [jQuery...
本书是jQuery经典技术教程的最新升级版,涵盖jQuery 1.10.x和jQuery 2.0.x。本书前6章以通俗易懂 的方式讲解了jQuery的核心组件,包括jQuery的选择符、事件、动画、DOM操作、Ajax支持等。第7章 和第8章介绍了jQuery ...
原书名: Object-Oriented JavaScript: Create scalable, reusable high-quality JavaScript applications and libraries. JavaScript作为一门浏览器语言的核心思想; 面向对象编程的基础知识及其在JavaScript中...
javascript Javascript 代码 其中很多都是伪代码的写法,便有回顾和总结。参考资料 包含 javascript 的基础语法 面向对象的实现 设计模式实现 模块化开 javascript 常见的疑问 jQuery NodeJs html5 Javascript ...
JavaScript代码+注释(初学者的入门到提高宝典),入门到提高目录 '1-JavaScript变量与函数.html', '2-JavaScript变量作用域的各种啃.html', '3-JavaScript对象详解.html', '4-JavaScript数组详解.html', '5-...
3.JavaScript 闭包 4.JavaScript 事件 5.javascript 跨域 6.javascript 命名空间 Oject-Oriented 1.JavaScript Expressive 2. Interfaces 3.Introduction 4. Inheritance 5.AOP Jquery [jQuery][9] [jQuery...
闭包的几个例子:变量的捕获发生在创建闭包的时候,isOdd不会察觉isEven。作为私有变量的o被可能意外修改,为了避免,需要把捕获变量私有:在这个例子里,PR
本文结合 ECMA 262 规范详解了闭包的内部工作机制,让 JavaScript 编程人员对闭包的理解从“嵌套的函数”深入到“标识符解析、执行环境和作用域链”等等 JavaScript 对象背后的运行机制当中,真正领会到闭包的实质。
python 03、PYTHon 模块包异常处理 4-1_闭包、装饰器_Day04_AM.mp4
深化解析Javascript闭包的功能及实现方法_.docx
本文主要介绍了JavaScript利用闭包实现模块化的方法。具有一定的参考价值,下面跟着小编一起来看下吧
1)根据表格中的数据,用Matlab编程进行...2)根据标准化处理后的数据,用Matlab编程,建立模糊相似矩阵,并编程求出其传递闭包矩阵; 3)根据模糊等价矩阵,编程绘制动态聚类图; 4)根据原始数据,编程确定最佳分类结果。
代码可以实现基于传递闭包法的模糊聚类分析。是我之前调试好的,代码结构合理,可以供研究参考。
python闭包深入(csdn)————程序
深入理解javascript原型和闭包(01)——一切都是对象 深入理解javascript原型和闭包(02)——函数和对象的关系
Javascript中有几个非常重要的语言特性——对象、原型继承、闭包。其中闭包 对于那些使用传统静态语言C/C++的程序员来说是一个新的语言特性。本文将以例子入手来介绍Javascript闭包的语言特性,并结合一点 ...
三角网是由一系列连续三角形构成的网状的平面控制图形,是三角测量中布设连续三角形的两种主要扩展形式,同时向各方向扩展而构成网状.适用于地势起伏大,通视条件比较好的场地。
闭包是ECMAScript (JavaScript)最强大的特性之一,但用好闭包的前提是必须理解闭包。闭包的创建相对容易,人们甚至会在不经意间创建闭包,但这些无意创建的闭包却存在潜在的危害,尤其是在比较常见的浏览器环境下...