读Ext之三(原型扩展)
续上篇,
1 | Ext.ns = Ext.namespace; |
有了一个简写的namespace。整个匿名函数执行完了。接下来
1 2 | Ext.ns( "Ext.util" , "Ext.lib" , "Ext.data" ); Ext.elCache = {}; |
分别为Ext添加了util,lib,data,elCache属性,默认都是空的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Ext.apply(Function.prototype, { createInterceptor : function (){ // ... }, createCallback : function (){ // ... }, createDelegate : function (){ // ... }, defer : function (){ // ... } }); |
Ext.apply在第一篇已经讲到,这里用来扩展Function,为其增加了四个方法createInterceptor、createCallback、createDelegate、defer。
众多流行的JS库都不同程度的“污染”了原生JS,最典型的如Prototype ,Mootools 。JQuery则完全例外,一个匿名函数执行后便诞生了集所有API为一身的强大 $ .
虽然如此,JQuery的事件对象则不是DOM中事件对象,它完全重写了一个兼容IE,DOM3的事件对象。这里的污染加了双引号,不赞同的就理解成扩展吧。
createInterceptor 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | createInterceptor : function (fcn, scope){ var method = this ; return !Ext.isFunction(fcn) ? this : function () { var me = this , args = arguments; fcn.target = me; fcn.method = method; return (fcn.apply(scope || me || window, args) !== false ) ? method.apply(me || window, args) : null ; }; }, |
Interceptor顾名思义,拦截器。它却不是Strust2中的拦截器,但还是有部分相似之处。这里是利用所传函数fcn拦截,如果fcn返回false,将被拦截,true才执行。
var method = this,因为是给Function.prototype扩展,因此其prototype上挂的所有的函数,函数内的this都是Function,即函数自身。示例可能更容易理解
1 2 3 4 5 | Function.prototype.test = function (){ alert( this ); }; function fn(){ return 'test' ;} fn.test(); |
给Function.prototype添加了一个test方法,定义函数fn,fn会自动继承test方法,fn.test()时弹出的this可以看到就是fn自身。
!Ext.isFunction(fcn),这个条件表明如果所传参数fcn不是一个function,那么将直接返回this,this即函数自身。或者说这时没有进行任何拦截,原样返回了该函数自身。
当fcn为一个function时,将执行 “:”后的分支,此时给fcn添加了两个属性target,method。target是me,me是this。
此时的this是什么呢?多数情况下是window,但整个function如果作为对象属性存在时,this则是该对象。谁调用了createInterceptor,method就是谁。如:
1 2 3 4 5 6 7 8 | function oldFn(){ alert( 'test' ); } function ret(){ return false ; } var newFn = oldFn.createInterceptor(ret); newFn(); |
oldFn继承了createInterceptor方法,且调用了它,参数是ret。这时createInterceptor内部的method, fcn.method就是oldFn;me,fcn.target则为window对象。改成如下,me,fcn.target则为obj了。
1 2 3 4 5 6 7 8 9 | function oldFn(){ alert( 'test' ); } function ret(){ return false ; } var obj = {name: 'jack' }; obj.method = oldFn.createInterceptor(ret); obj.method(); |
再往下看
(fcn.apply(scope || me || window, args) !== false),所传参数fcn被执行,执行上下文优先是scope,其次是me,最后是window。返回结果不等于false才执行method。method执行上下文先为me,me不存在则是window。
整个createInterceptor方法就看完了,下面是一个具体例子:
1 2 3 4 5 6 7 8 9 | function sayName(name){ alert( 'hello, ' + name); } function rule(name){ return name == 'snandy' ; } var sayName2 = sayName.createInterceptor(rule); sayName2( 'zhangsan' ); // -> no alert sayName2( 'snandy' ); // -> "hello, snandy" |
createCallback 方法
1 2 3 4 5 6 7 8 | createCallback : function ( /*args...*/ ){ // make args available, in function below var args = arguments, method = this ; return function () { return method.apply(window, args); }; }, |
这个方法非常有用,实现简单。返回一个新函数,新函数内执行method(method是谁就不用再提了吧),会把外面的参数给传进来。
初学者经常 纠结于给事件handler传参数 。createCallback 解决了给DOM事件handler(监听器)传参问题。
createCallback 不仅用在DOM事件handler上你完全可以自定义事件,设计一套属于自己的 观察者模式 API。即每个类有自己的事件,如 LTMaps ,除了拥有属性,方法还有许多事件,如移动地图(move),缩放地图(zoom)。Ext的众多UI组件也都是这种模式,Ext.Panel 具有afterlayout,close等事件。
在给这些事件添加hanlder,又想传参或许也会用到 createCallback。
再看createDelegate方法,创建一个代理函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | createDelegate : function (obj, args, appendArgs){ var method = this ; return function () { var callArgs = args || arguments; if (appendArgs === true ){ callArgs = Array.prototype.slice.call(arguments, 0); callArgs = callArgs.concat(args); } else if (Ext.isNumber(appendArgs)){ callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first var applyArgs = [appendArgs, 0].concat(args); // create method call params Array.prototype.splice.apply(callArgs, applyArgs); // splice them in } return method.apply(obj || window, callArgs); }; }, |
createDelegate 比 createCallback 更强大,除了能解决回调函数传参问题。还能控制:
1, 自定义参数是否覆盖默认参数(如 DOM事件对象 作为handler第一个参数)
2, 自定义参数的位置
内部实现:取自身(var method=this),返回一个新function,该function调用自身(method.apply),同时指定上下文(obj||window)及参数(callArgs)。就这么简单,细节之处在于对参数的控制。
1,只传obj和args时将覆盖回调函数默认参数(DOM事件对象)
1 2 3 4 5 6 7 8 9 | <a href= "#" id= "aa" >SINA</a> <script type= "text/javascript" > var aa = document.getElementById( 'aa' ); function sayName(name){ alert( 'hello, ' + name); } var sayName2 = sayName.createDelegate(aa,[ 'jack' ]); aa.onclick = sayName2; </script> |
2,三个参数都传,appendArgs为true时,默认参数(DOM事件对象)位置不变(第一个),自定义参数args在最后
1 2 3 4 5 6 7 8 9 10 11 | < a href="#" id="aa">SINA</ a > < script type="text/javascript"> var aa = document.getElementById('aa'); function sayName(){ alert('实际参数长度:' + arguments.length); alert('hello, ' + arguments[0]); alert('hi, ' + arguments[1]); } var sayName2 = sayName.createDelegate(aa,['jack'],true); aa.onclick = sayName2; </ script > |
3, 三个参数都传,appendArgs为数字时将指定自定义参数的位置
1 2 3 4 5 6 7 8 9 10 11 12 | < a href="#" id="aa">SINA</ a > < script type="text/javascript"> var aa = document.getElementById('aa'); function sayName(name){ alert('实际参数长度:' + arguments.length); alert('hello, ' + arguments[0]); alert('hi, '+ arguments[1]); alert('hi, '+ arguments[2]); } var sayName2 = sayName.createDelegate(aa,['jack','lily'],0); aa.onclick = sayName2; </ script > |
此外,method的执行上下文应该增加this,这样实用一些了。
1 | return method.apply(obj || this || window, callArgs); |
接着看defer,指定函数在多少毫秒后执行
1 2 3 4 5 6 7 8 | defer : function (millis, obj, args, appendArgs){ var fn = this .createDelegate(obj, args, appendArgs); if (millis > 0){ return setTimeout(fn, millis); } fn(); return 0; } |
内部实用了刚刚提到的createDelegate方法,指定函数的执行上下文及参数设定。如果millis是正数则延迟执行返回setTimeout的返回值(一个数字),有必要可用clearTimeout终止。否则立即执行,返回0。
好了,以上是对Function.prototype的扩展。接下来是对String,Array的扩展。使用Ext.applyIf,第一篇 已提到Ext.applyIf不会覆盖已有的方法。
1 2 3 4 5 6 7 8 | Ext.applyIf(String, { format : function (format){ var args = Ext.toArray(arguments, 1); return format.replace(/\{(\d+)\}/g, function (m, i){ return args[i]; }); } }); |
给String添加静态方法(类方法)format,可以把字符串中特殊写法({0},{1})用指定的变量替换。如
1 2 3 | var href = 'http://www.sina.com.cn' , text = '新浪' ; var s = String.format( '<a href="{0}">{1}</a>' , href, text); alert(s); // --> <a href="http://www.sina.com.cn">新浪</a> |
有点类似于JSP的EL表达式。
对Array扩展,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | Ext.applyIf(Array.prototype, { indexOf : function (o, from){ var len = this .length; from = from || 0; from += (from < 0) ? len : 0; for (; from < len; ++from){ if ( this [from] === o){ return from; } } return -1; }, remove : function (o){ var index = this .indexOf(o); if (index != -1){ this .splice(index, 1); } return this ; } }); |
indexOf 再熟悉不过了吧,String早有了。用来查找元素是否在数组中,如果有则返回该元素在数组中的索引,否则返回-1。该方法在 ECMAScript 5 中已经引入,各浏览器的新版本都实现了。
reomve 删除指定元素,如果存在该元素则删除,返回数组自身。
好了,以上3篇是整个Ext-core的Ext.js。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
2011-04-25 结果分类-Ajax之三
2011-04-25 html5中可通过document.head获取head元素
2011-04-25 改造请求参数-Ajax之二
2011-04-25 基本的封装-Ajax之一
2010-04-25 获取事件对象的全家