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一个函数,下次改进吧,写了一天了这个东西,得赶赶进度了。

原文地址:https://www.cnblogs.com/evlon/p/1624860.html