Dav*_*nco 6 javascript recursion hook language-features
我开始研究JS的动态分析工具,我想不引人注目地描述整个环境.我基本上遍历各种上下文,深入挖掘对象,每次我点击一个函数,我都会陷入其中.现在,这种方法相对较好,除了在处理jQuery/prototype等库时它会中断.
到目前为止,这是我的代码(尽我所能评论):
var __PROFILER_global_props = new Array(); // visited properties
/**
* Hook into a function
* @name the name of the function
* @fn the reference to the function
* @parent the parent object
*/
function __PROFILER_hook(name, fn, parent) {
//console.log('hooking ' + name + ' ' + fn + ' ' + parent);
if (typeof parent == 'undefined')
parent = window;
for (var i in parent) {
// find the right function
if (parent[i] === fn) {
// hook into it
console.log('--> hooking ' + name);
parent[i] = function() {
console.log('called ' + name);
return fn.apply(parent, arguments);
}
//parent[i] = fn; // <-- this works (obviously)
break;
}
}
}
/**
* Traverse object recursively, looking for functions or objects
* @obj the object we're going into
* @parent the parent (used for keeping a breadcrumb)
*/
function __PROFILER_traverse(obj, parent) {
for (i in obj) {
// get the toString object type
var oo = Object.prototype.toString.call(obj[i]);
// if we're NOT an object Object or an object Function
if (oo != '[object Object]' && oo != '[object Function]') {
console.log("...skipping " + i);
// skip
// ... the reason we do this is because Functions can have sub-functions and sub-objects (just like Objects)
continue;
}
if (__PROFILER_global_props.indexOf(i) == -1 // first we want to make sure we haven't already visited this property
&& (i != '__PROFILER_global_props' // we want to make sure we're not descending infinitely
&& i != '__PROFILER_traverse' // or recusrively hooking into our own hooking functions
&& i != '__PROFILER_hook' // ...
&& i != 'Event' // Event tends to be called a lot, so just skip it
&& i != 'log' // using FireBug for debugging, so again, avoid hooking into the logging functions
&& i != 'notifyFirebug')) { // another firebug quirk, skip this as well
// log the element we're looking at
console.log(parent+'.'+i);
// push it.. it's going to end up looking like '__PROFILER_BASE_.something.somethingElse.foo'
__PROFILER_global_props.push(parent+'.'+i);
try {
// traverse the property recursively
__PROFILER_traverse(obj[i], parent+'.'+i);
// hook into it (this function does nothing if obj[i] is not a function)
__PROFILER_hook(i, obj[i], obj);
} catch (err) {
// most likely a security exception. we don't care about this.
}
} else {
// some debugging
console.log(i + ' already visited');
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是配置文件,这就是我调用它的方式:
// traverse the window
__PROFILER_traverse(window, '__PROFILER_BASE_');
// testing this on jQuery.com
$("p.neat").addClass("ohmy").show("slow");
Run Code Online (Sandbox Code Playgroud)
遍历工作正常并且挂钩工作正常,只要函数简单且非匿名(我认为挂钩到匿名函数是不可能的,所以我不太担心它).
这是预处理阶段的一些修剪输出.
notifyFirebug already visited
...skipping firebug
...skipping userObjects
__PROFILER_BASE_.loadFirebugConsole
--> hooking loadFirebugConsole
...skipping location
__PROFILER_BASE_.$
__PROFILER_BASE_.$.fn
__PROFILER_BASE_.$.fn.init
--> hooking init
...skipping selector
...skipping jquery
...skipping length
__PROFILER_BASE_.$.fn.size
--> hooking size
__PROFILER_BASE_.$.fn.toArray
--> hooking toArray
__PROFILER_BASE_.$.fn.get
--> hooking get
__PROFILER_BASE_.$.fn.pushStack
--> hooking pushStack
__PROFILER_BASE_.$.fn.each
--> hooking each
__PROFILER_BASE_.$.fn.ready
--> hooking ready
__PROFILER_BASE_.$.fn.eq
--> hooking eq
__PROFILER_BASE_.$.fn.first
--> hooking first
__PROFILER_BASE_.$.fn.last
--> hooking last
__PROFILER_BASE_.$.fn.slice
--> hooking slice
__PROFILER_BASE_.$.fn.map
--> hooking map
__PROFILER_BASE_.$.fn.end
--> hooking end
__PROFILER_BASE_.$.fn.push
--> hooking push
__PROFILER_BASE_.$.fn.sort
--> hooking sort
__PROFILER_BASE_.$.fn.splice
--> hooking splice
__PROFILER_BASE_.$.fn.extend
--> hooking extend
__PROFILER_BASE_.$.fn.data
--> hooking data
__PROFILER_BASE_.$.fn.removeData
--> hooking removeData
__PROFILER_BASE_.$.fn.queue
Run Code Online (Sandbox Code Playgroud)
当我$("p.neat").addClass("ohmy").show("slow");在jQuery.com上执行时(通过Firebug),我得到一个合适的调用堆栈,但是我似乎在某个地方丢失了我的上下文,因为没有任何反应,我e is undefined从jQuery 得到一个错误(显然,挂钩搞砸了一些东西).
called init
called init
called find
called find
called pushStack
called pushStack
called init
called init
called isArray
called isArray
called merge
called merge
called addClass
called addClass
called isFunction
called isFunction
called show
called show
called each
called each
called isFunction
called isFunction
called animate
called animate
called speed
called speed
called isFunction
called isFunction
called isEmptyObject
called isEmptyObject
called queue
called queue
called each
called each
called each
called each
called isFunction
called isFunction
Run Code Online (Sandbox Code Playgroud)
问题是我认为我this在调用时失去了上下文
return fn.apply(parent, arguments);
Run Code Online (Sandbox Code Playgroud)
这是另一个有趣的怪癖.如果我在遍历之前挂钩,即:
// hook into it (this function does nothing if obj[i] is not a function)
__PROFILER_hook(i, obj[i], obj);
// traverse the property recursively
__PROFILER_traverse(obj[i], parent+'.'+i);
Run Code Online (Sandbox Code Playgroud)
..应用程序运行绝对正常,但调用堆栈被更改(我似乎没有得到特定于jQuery的函数)由于某种原因:
called $
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called setInterval
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called clearInterval
Run Code Online (Sandbox Code Playgroud)
..代替animation,show,merge,等现在,所有的钩不被说called functionName,但最终我想要做的堆栈跟踪和时间功能(通过Java小程序).
这个问题最终变得庞大而且我道歉,但任何帮助都表示赞赏!
注意:如果您不小心,上面的代码可能会使您的浏览器崩溃.公平警告:P
我认为你走在正确的道路上。this当你使用 时,的价值就会被破坏apply。jQuery 中定义的函数可能是通过apply内部调用的,并且取决于this.
的第一个参数apply是将用于 的值this。您确定您应该用于parent此目的吗?
我能够通过以下方式重复该问题:
var obj = {
fn : function() {
if(this == "monkeys") {
console.log("Monkeys are funny!");
}
else {
console.log("There are no monkeys :(");
}
}
};
obj.fn.apply("monkeys");
var ref = obj.fn;
//assuming parent here is obj
obj.fn = function() {
console.log("hooking to obj.fn");
return ref.apply(obj);
};
obj.fn.apply("monkeys");
Run Code Online (Sandbox Code Playgroud)
这里,该函数取决于 的值this来打印文本Monkeys are funny!。正如您所看到的,使用您的hook算法,此上下文会丢失。萤火虫显示:
猴子真有趣! 挂接到 obj.fn 没有猴子:(
我做了一个小小的改变,并this在 apply 中使用而不是obj(父级):
obj.fn = function() {
console.log("hooking to obj.fn");
return ref.apply(this);
};
Run Code Online (Sandbox Code Playgroud)
这次 Firebug 说:
猴子真有趣! 挂接到 obj.fn 猴子真有趣!
恕我直言,问题的根源是您正在为this(即parent引用父对象)设置一个显式值。因此,您的挂钩函数最终会覆盖 的值this,该值可能已由调用原始函数的代码显式设置。当然,该代码不知道您用自己的钩子函数包装了原始函数。this所以你的钩子函数应该保留调用原始函数时的值:
return fn.apply(this, arguments);
Run Code Online (Sandbox Code Playgroud)
因此,如果您在应用程序中使用this而不是,您的问题可能会得到解决。parent
如果我没有正确理解您的问题,我深表歉意。有不对的地方请指正。
更新
jQuery 中有两种函数。附加到jQuery对象本身的方法(有点像静态方法),然后还有对结果进行操作的方法jQuery(selector)(有点像实例方法)。您需要关心的是后者。在这里,this很重要,因为这就是实现链接的方式。
我能够让以下示例发挥作用。请注意,我正在处理对象的实例,而不是对象本身。所以在你的例子中,我将致力于jQuery("#someId")而不只是jQuery:
var obj = function(element) {
this.element = element;
this.fn0 = function(arg) {
console.log(arg, element);
return this;
}
this.fn1 = function(arg) {
console.log(arg, arg, element);
return this;
}
if(this instanceof obj) {
return this.obj;
}
else {
return new obj(element);
}
};
var objInst = obj("monkeys");
var ref0 = objInst.fn0;
objInst.fn0 = function(arg) {
console.log("calling f0");
return ref0.apply(this, [arg]);
};
var ref1 = objInst.fn1;
objInst.fn1 = function(arg) {
console.log("calling f1");
return ref1.apply(this, [arg]);
};
objInst.fn0("hello").fn1("bye");
Run Code Online (Sandbox Code Playgroud)
我不知道这是否能解决你的问题。也许研究 jQuery 源代码会给你一些更多的见解:)。我认为您面临的困难是区分通过调用的函数apply和通过链接调用的函数(或直接调用)。
| 归档时间: |
|
| 查看次数: |
712 次 |
| 最近记录: |