JavaScript: Problema com um ActiveX Object-ea aplicar () - Função
-
07-07-2019 - |
Pergunta
Eu tenho um objeto ActiveX (Master) e gostaria de invocar funções dinamicamente sobre ele. Para fazer isso eu uso a função de aplicar (). Mas infelizmente o InternetExplorer me diz algo ao longo das linhas de: "Este objeto não suporta este método". Alguém pode me dar uma dica que eu poderia fazer?
(Para testar isso, você também pode usar um pequeno objeto flash como mestre e chamada "doSomething" em vez do meu específico "Inicializar".)
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);
}
Para comparsion, esta é a função apply () em ação:
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);
}
Solução 4
kangax Obrigado pelo seu tempo e sua extensa explicação! Infelizmente eu não poderia fazê-lo funcionar dessa maneira (ele trabalha para o alertbox embora) Mas ele me levou à idéia de usar uma classe proxy. Não é a maneira mais elegante, porque eu tenho de fornecer todas as funções do meu objeto eu quero usar, mas ele funciona e não envolve 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);
}
Mais uma vez, obrigado pelo seu tempo!
Outras dicas
O problema com alguns dos objetos de host (ou seja, todos os objetos não-nativos) no IE (e não apenas IE) é que eles não herdam Function.prototype
(e muitas vezes nem do nível Object.prototype
superior). Alguns anfitrião objetos que pode parecer funções , na verdade, não tem nada a ver com as funções, exceto que eles podem ser chamados. O fato de que esses objetos não herdar de meios Function.prototype
que eles não conseguem ser identificados como funções com operador instanceof
; que seu construtor não faz referência Function
; e que eles não têm todos os métodos Function.prototype.*
, tais como call
ou apply
. Mesmo sua interno [[classe]] propriedade não pode ser a de "Função", como é com qualquer objeto nativo (note que [[classe]] pode ser inferida a partir do resultado do valor Object.prototype.toString
).
Este é realmente esperado, já que objetos de host não são obrigados para implementar muitas coisas que objetos nativos fazem (como por ECMA-262, 3ª ed.). É perfeitamente permitido para um objeto de host para, digamos, erro lance na invocação de método (por exemplo hostObject.hostMethod()
); ou quando passando-o como um operando a operadores padrão como delete
(por exemplo delete hostObject.hostMethod
). Como você pode ver, também é OK para objetos de host que podem ser chamadas não herdam Function.prototype
nativa.
Tal comportamento imprevisível (ainda perfeitamente compatível) é realmente uma das principais razões pelas quais anfitrião objetos aumento é recomendado contra .
Mas voltando ao seu problema call
:)
A coisa sobre estes anfitrião IE "complicado" objetos é que muitas vezes implementar [[Chamada]] método interno, e é possível invocar call
e apply
sobre eles, embora não diretamente .
Aqui está um padrão a invocação apply
emular em um objeto que não tem isso:
function f(){ return arguments };
Function.prototype.apply.call(f, null, [1,2,3]); // [1,2,3]
null
pode ser substituído com qualquer objeto de contexto deve ser chamado, é claro.
E um exemplo de invocação apply
no objeto de host que não tem call
:
// should work in IE6, even though `alert` has no `call` there
Function.prototype.call.call(alert, window, 'test');
Aplicar isso ao seu código
// Calls Master.initialize borrowing from Function.prototype
Function.prototype.apply.call(Master.initialize, Master, [1,"VC2"]);
Eu tive esse mesmo problema e eu resolvido através da compilação de uma função de conversão em tempo de execução para desenrolar o número certo de argumentos (semelhantes à solução anterior, mas sem a restrição de que a alça objeto ActiveX tem de estar em um mundial variável).
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);
};
Aparentemente motor JS do IE não vê funções ActiveX como objetos função JavaScript em que você pode chamar apply()
. Como apenas cerca de fazer um eval()
-. Embora feio, parece ser sua única opção
function invoke(objectName, fnName, args) {
return eval(objectName + "." + fnName + "(" + args + ")");
}
Apenas pensei em mencionar que se você usar o método eval
como disse Ates Goral você precisa ter cuidado com argumentos de seqüência em sua matriz como eles serão considerados nomes de variáveis, por exemplo
function invoke(objectName, fnName, args) {
return eval(objectName + "." + fnName + "(" + args + ")");
}
invoke("Master", "Initialize", [1, "VC1"]);
o eval
será passado a linha
Master.Initialize(1,VC1)
que irá lançar um erro se VC1 não é uma variável definida. Poderia ser melhor para "desenrolar" o nome da matriz em vez de passar literais:
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
}
para invocar torna-se
function invoke(objectName, fnName, args) {
var unrolledarray = UnrollArray("args", args.length);
return eval(objectName + "." + fnName + "(" + unrolledarray + ");");
}
invoke("Master", "Initialize", [1, "VC1"]);
o eval
será, então, passou
Master.Initialize(args[0],args[1]);