作者:Jiang, Jilin
JS作为一门脚本语言,十分容易上手。外加其灵活性,可以轻而易举地扩展功能。今天,我们就聊聊JS的方法代理。方法代理是脚本语言中常见的方法扩展形式。这种灵活的形式优点在于遇到复杂的JS代码需要扩展时,可以相对简单的抽取并修改。但是,其缺点也十分明显,会造成代码的碎片化,因而是一把双刃剑。
基本形式
var _func = obj.func; obj.func = function() { return _func(); }; |
此处,obj的func方法是原始方法。我们使用一个_func变量保存该方法,之后重写func方法,返回调用原始的方法。相当的好理解。
不定长参数
有时候,我们会遇到不定长参数的形式。那么我们可以做以下更改:
var _func = obj.func; obj.func = function() { return _func.apply(this, arguments); }; |
写到这里,JS方法代理的两种形式已经写完了。这时,你可能会疑惑这种方法代理有什么用?那么,就跟随我看看几个例子吧:
重写静态方法
出于某些原因,我们需要在console打印的同时,将打印数据传输到指定服务器上用户帮助统计数据。而在开发过程中,直接使用了原生态的console.log方法输出。那么,我们就可做如下修改。
首先,构建代理方法:
var _log = console.log; console.log = function() { return _log.apply(this, arguments); }; |
接着,插入ajax调用逻辑:
console.log = function() { var _args = Array.prototype.slice.call(arguments); $.post("srvAddr", {userID: userID, args: _args}); return _log.apply(this, arguments); }; |
可以很容易预见效果:
原型代理
有时候,我们需要直接接管一个类的方法。那么相应的,我们就要将其prototype中的方法做代理。现在我们假设要使得Array默认支持数字排序:
var _sort = Array.prototype.sort; Array.prototype.sort = function() { _sort.apply(this, arguments); }; |
我们首先检测数组中的元素是否是数字,如果是则按照数字排序,否则就按照默认方式排序:
var _sort = Array.prototype.sort; Array.prototype.sort = function() { var _isNum = true; for(var i = 0 ; i < this.length ; i += 1) { if(typeof this[i] !== "number") { _isNum = false; break; } } if(_isNum) { return _sort.call(this, function(a, b) { return a - b; }); } else { return _sort.apply(this, arguments); } }; |
原始排序方式(将数字转换成字符排序)
代理后排序方式
注意:此处仅作为原型方法代理的例子。在实际开发中,应该尽量避免对原生对象原型方法做代理。
重载
在jQuery中,toggle方法拥有多种重载,其中一个为toggle(display),通过接受boolean参数来决定元素显示还是隐藏。而fadeToggle方法则不接受该参数。让我们进行简单的代理,使其支持boolean参数:
var _fadeToggle = $.fn.fadeToggle; $.fn.fadeToggle = function(display) { if(arguments.length === 1 && typeof display === "boolean") { if(display) { return this.fadeIn(); } else { return this.fadeOut(); } } return _fadeToggle.apply(this, arguments); }; |
通过参数长度和接收参数类型类型判断是否调用fadeIn或fadeOut方法。如果不匹配则调用原来的fadeToggle方法。以此类推,slideToggle也可以同样适用。
*继承
继承中,我们有时候会需要调用父类方法来实现。这和java中的super有些类似。一个简单的例子。我们定义了一个Person,可以laugh。同时定义一个Robot继承,是其沿用Person的laugh但是稍作修改:
var Person = function() { this.name = ""; this.laugh = function() { return "Ha ha!"; }; return this; }; var Robot = function() { Person.apply(this); var _laugh = this.laugh; this.laugh = function() { return encodeURIComponent(_laugh()); }; return this; }; |
劫持
说到劫持,则和安全性开始挂钩了。如今大多数网站都习惯于使用开源的js库来开发,但是如果碰巧被插入了恶意代码,那么安全也就变得不安全了(例如chrome中大量未经验证的游览器插件)。以下,我们将举几个劫持的例子。
Ajax劫持
不少人以为,通过闭包的方式将重要用户参数存储在作用域内(例如动态生成的secureID),那么即便是被插入了恶意代码也无法获取到,从而无法伪造ajax请求来获取重要信息似乎就足够了。但是如果整个ajax请求被拦截了呢?
var _ajax = $.ajax; $.ajax = function() { var $p = _ajax.apply($, arguments); $p.done(function(data) { // Do something... }); return $p; }; |
jQuery中所有ajax请求(get,post,getJSON)最终都会调用$.ajax来实现。而通过方法代理直接劫持$.ajax方法并返回一个正常promise对象,会使得页面脚本运行毫无影响。但是实际上数据已经被劫持走了。
Array劫持
Array拥有不少原型方法,例如push,pop,shift,unshift等等。我们只需要简单的代理数组操作方法,便可以截获数据。
var _push = Array.prototype.push; Array.prototype.push = function() { // Do something... return _push.apply(this, arguments); }; |
好了,以上就是这次的方法代理介绍。由于方法代理会混乱代码逻辑使得代码结构变得不易理解。在日常开发过程中,我们应该尽量避免使用它。只有在代理重载,或者更改难以理解的遗留代码的部分逻辑时使用它。从而避免将双刃剑误伤了自己。