Hook Javascript Function
在公文升级方案中, 因为使用了新模板, 我需要在原来系统执行某几个函数之前, 做一些检查,如果成立则执行原有函数,否则执行我的新逻辑,然后再依情况决定是不是执行原函数。
我们知道,Javascript中函数有静态函数、成员函数和实例化对象的成员函数之分,这些函数的实现存在正常函数和匿名函数的区分。所以在我们Hook成员时,我们要对这些情况兼而顾之。
例如对与下列函数Hook调用,预想产生如下的输出:
//全局函数无参数
function Method1(){
alert('Method1');
}
Method1.hook(function()
{
alert('befor Method1');
return true;
},
function()
{
alert('after Method1');
}
);
Method1();
/* 输出
befor Method1
Method1
after Method1
*/
alert('-----------------');
//全局函数有参数
function Method2(name){
alert('Method2 with ' + name);
}
Method2.hook(function(name)
{
alert('befor Method2 with ' + name);
return true;
},
function()
{
alert('after Method2 ');
}
);
Method2('evlon');
/* 输出
befor Method2 with evlon
Method2 with evlon
after Method2
*/
alert('-----------------');
//Hook字符串的toString 函数
String.prototype.toString.hook(String.prototype, function()
{
alert('befor string.toString');
return true;
});
var s = "return s";
alert(s.toString());
/* 输出
befor string.toString
return s
*/
alert('-----------------');
//Hook 系统已有全局函数parseInt
parseInt.hook(function()
{
alert('befor parseInt');
return true;
});
alert(parseInt('5'));
/* 输出
befor parseInt
5
*/
alert('-----------------');
//Hook 所有数组对象的join 方法
Array.prototype.join.hook(Array.prototype, function(span)
{
alert('befor Array.prototype.join separator ' + span);
return true;
});
alert([3,5,6].join(','));
/* 输出
befor Array.prototype.join separator ,
3,5,6
*/
alert('-----------------');
var list = [3, 5, 6];
//Hook list 这个实例数组对象的join 方法
list.join.hook(list, function(span)
{
alert('befor list.join separator ' + span);
return true;
});
alert(list.join(','));
/* 输出
befor list.join separator ,
befor Array.prototype.join separator ,
3,5,6
*/
alert('-----------------');
var list2 = [3, 5, 6, 7];
// 此处调用不会产生befor list.join separator ,
alert(list2.join(','));
/* 输出
befor Array.prototype.join separator ,
3,5,6,7
*/
alert('-----------------');
//自定义类
function People() {
//成员函数带参数
this.getName = function(name) {
alert('in getName with ' + name);
return 'return evlon';
}
}
var p = new People();
var p2 = new People();
//这里我们只Hook实例p2 的函数调用
p2.getName.hook(p2, 'getName2',
function(name) {
alert('befor getName2 with ' + name);
return true;
},
function() {
alert('after getName2');
}
);
p2.getName.hook(p2,
function(name) {
alert('befor getName with ' + name);
return true;
},
function() {
alert('after getName');
}
);
//因为只Hook了P2, 对这个调用无影响
alert(p.getName('argName'));
/* 输出
in getName with argName
return evlon
*/
alert('-----------------');
alert(p2.getName('argName'));
/* 输出
befor getName with argName
in getName with argName
after getName
return evlon
*/
alert('-----------------');
alert(p2.getName2('argName2'));
/* 输出
befor getName2 with argName2
in getName with argName2
after getName2
return evlon
*/
要实现这样的东西,我们需要知道修改某一处的函数引用,对于这样的全局函数,我们知道它的所属对象是 window。如果对于类的成员函数,则应该是类.prototype.funName,对于实例的成员,我们可以通过在实例上添加函数来重写方法。
首先我们这个函数是所有函数的方法,所以它必须在 Function.prototyp 上添加一个新的函数Hook, 它的逻辑应该是首先查找函数的名称,如果得到了,则直接改写在指定对象上的函数实现。但如果这个函数是匿名函数,我们需要查找这个对象的所有属性,看哪个属性和这个函数相等,如果有一个,则取出第一个相等的进行Hook。当然,如果Hook时明确指定了属性,则找到这个属性进行Hook。
下面是实现的代码:
//这里我们改变输出方式
window.alert = function(msg)
{
tbVal.innerText += '\n' + msg;
}
Function.prototype.hook = function(/*[context,]*//*[methodName,*/fnBefor /*[,fnAfter]*/) {
//提出函数名称
function getFunName(fn) {
var method = fn.toString();
var rgx = /function\s+(\w+)\s*\(/;
var r = method.match(rgx);
if (r) {
return r[1];
}
return '';
}
//加载参数
var context = null;
var methodName = null;
var argIndex = 0;
if (typeof (arguments[argIndex]) != 'function' && typeof (arguments[argIndex]) != 'string')
context = arguments[argIndex++];
if (typeof (arguments[argIndex]) == 'string')
methodName = arguments[argIndex++];
fnBefor = arguments[argIndex++] || function() { }
var fnAfter = arguments[argIndex++] || function() { }
//处理默认参数
context = context || window;
var method = this;
var methodName = methodName || getFunName(method);
if (methodName == '') {
//找不到,可能是匿名函数,我们从对象中查找试试
var bFound = false;
var contextObject = context.constructor == Array.prototype.constructor ? Array.prototype : context;
for (var m in contextObject) {
// alert(m + ':' + context[m]);
if (context[m] == method) {
methodName = m;
bFound = true;
break;
}
}
if (!bFound) {
alert('请提供正确的函数所属对象使用有名函数');
return null;
}
}
//生成有参数名的新函数
eval('context[methodName] = function ' + methodName + '()\n' +
'{\n' +
' var args = Array.prototype.slice.call(arguments,0);\n' +
' var ctx = this;\n' +
' try\n' +
' {\n' +
' if(fnBefor.apply(ctx, args))\n' +
' return method.apply(ctx, args);\n' +
' }\n' +
' finally\n' +
' {\n' +
' fnAfter.apply(ctx, args);\n' +
' }\n' +
'};');
}
不过写完了,发现不能Unhook一个函数,下次改进吧,写了一天了这个东西,得赶赶进度了。