3

我有一个 ActiveX 对象(主)并希望在其上动态调用函数。为此,我使用 apply() 函数。但遗憾的是 InternetExplorer 告诉我一些类似的东西:“这个对象不支持这个方法”。有人可以给我一个提示我能做什么吗?

(要对此进行测试,您还可以使用一个小的 flash 对象作为 Master 并调用“doSomething”而不是我的特定“Initialize”。)

function invoke(object, fnName, args)
{
  return object[fnName].apply(object, args);
}

function test_it()
{
  try{
    Master = window.document["Master"];
  }
  catch(e){alert(e);}
  var param = [1,"VC2"]; 
  var ret = invoke(Master, "Initialize", param);
  alert("got: "+ret);
}

相比之下,这是 apply() 函数的作用:

function Obj()
{
  this.msg = function(a, b, c)
  {
      alert("msg: \n a: "+a+"\n b: "+b+"\n c: "+c);
      return "hi";
  }
    return this;
}


function invoke(object, fnName, args)
{
  return object[fnName].apply(object, args);
}

function test_it()
{
  var obj = new Obj();
  var ret = invoke(obj, "msg", [1, 2, 3]);
  alert("got: "+ret);
}
4

5 回答 5

3

IE(不仅是 IE)中的一些宿主对象(即任何非本地对象)的问题在于它们不继承自Function.prototype(通常也不继承自顶级Object.prototype)。一些看起来像函数的宿主对象实际上与函数无关,只是它们可以被调用。这些对象不继承自的事实Function.prototype意味着它们无法被识别为带有instanceof操作符的函数;他们的构造函数没有引用Function;并且他们缺乏所有的Function.prototype.*方法,例如callor apply。甚至它们的内部 [[Class]] 属性也可能不是“函数”的属性,因为它与任何本机对象一样(注意 [[Class]] 可以从Object.prototype.toStringvalue 的结果中推断出来)。

这实际上是意料之中的,因为不需要宿主对象来实现本机对象所做的许多事情(根据 ECMA-262,第 3 版)。完全允许宿主对象在方法调用时抛出错误(例如hostObject.hostMethod());或将其作为操作数传递给标准运算符delete(例如delete hostObject.hostMethod)时。如您所见,可调用的主机对象也可以不从 native 继承Function.prototype

这种不可预测(但完全符合)的行为实际上是不推荐使用主机对象增强的主要原因之一。

但回到你的call问题:)

这些“棘手”的 IE 宿主对象的问题在于它们通常实现内部 [[Call]] 方法,并且可以在它们上调用call和调用apply,尽管不是直接的。

这是一种模拟apply对没有它的对象的调用的模式:

function f(){ return arguments };
Function.prototype.apply.call(f, null, [1,2,3]); // [1,2,3] 

null当然,可以用应该调用的任何上下文对象替换。

还有一个apply在没有的主机对象上调用的示例call

// should work in IE6, even though `alert` has no `call` there
Function.prototype.call.call(alert, window, 'test');

将其应用于您的代码

// Calls Master.initialize borrowing from Function.prototype
Function.prototype.apply.call(Master.initialize, Master, [1,"VC2"]);
于 2009-09-16T21:25:34.303 回答
2

我遇到了同样的问题,我通过在运行时编译一个 thunk 函数来展开正确数量的参数来解决它(类似于以前的解决方案,但没有 ActiveX 对象句柄必须位于全局变量中的限制)。

varArgsThunkFunctionsCache = [];

function getVarArgsThunkFunction(arrayLength) {
  var fn = varArgsThunkFunctionsCache[arrayLength];
  if (!fn) {
    var functionCode = 'return o[m](';
    for (var i = 0; i < arrayLength; ++i) {
      if (i != 0) {
        functionCode += ','
      }
      functionCode += 'a[' + i + ']';
    }
    functionCode += ')';
    fn = new Function('o', 'm', 'a', functionCode);
    varArgsThunkFunctionsCache[arrayLength] = fn;
  }
  return fn;
};


function invoke(object, methodName, args) {
  var fn = getVarArgsThunkFunction(args.length);
  return fn(object, methodName, args);
};
于 2011-02-16T01:20:55.567 回答
1

显然 IE 的 JS 引擎没有将 ActiveX 函数视为您可以调用的 JavaScript 函数对象apply()。不如只做一个eval()——虽然丑陋,但它似乎是你唯一的选择。

function invoke(objectName, fnName, args) {
    return eval(objectName + "." + fnName + "(" + args + ")");
}
于 2009-09-16T16:17:25.203 回答
1

感谢 kangax 的宝贵时间和广泛的解释!遗憾的是,我无法让它以这种方式工作(虽然它适用于警报框)但它让我想到了使用代理类的想法。这不是最优雅的方式,因为我必须从我想要使用的对象中提供每个函数,但它可以工作并且它不涉及 eval()!

function proxy(obj)
{
    this.obj = obj;

    this.Initialize = function(a, b)
    {
        return obj.Initialize(a, b);
    }   
}

function test_it()
{
    var myMaster = new proxy(window.document["Master"]);    
    var ret = myMaster["Initialize"].apply(myMaster, [1, "VC2"]);
    alert(ret);
}

再次感谢您的宝贵时间!

于 2009-09-17T11:30:27.797 回答
0

只是想我会提到,如果您使用evalAtes Goral 所说的方法,您需要注意数组中的字符串参数,因为它们将被视为变量名,例如

function invoke(objectName, fnName, args) {
    return eval(objectName + "." + fnName + "(" + args + ")");
}
invoke("Master", "Initialize", [1, "VC1"]);

eval通过线

Master.Initialize(1,VC1)

如果 VC1 不是定义的变量,这将引发错误。最好“展开”数组名称而不是传递文字:

function UnrollArray(arrayname, length) {
    var s = "";
    for(var i = 0; i < length; i++) {
        s += arrayname + "[" + i + "],";
    }
    return s.substring(0, s.length - 1); //remove the trailing comma
}

所以调用变成

function invoke(objectName, fnName, args) {
    var unrolledarray = UnrollArray("args", args.length);
    return eval(objectName + "." + fnName + "(" + unrolledarray + ");");
}
invoke("Master", "Initialize", [1, "VC1"]);

然后将eval通过

Master.Initialize(args[0],args[1]);
于 2010-03-01T09:22:53.087 回答