Pregunta

Tengo un objeto ActiveX (maestro) y me gustaría invocar funciones dinámicamente en él. Para hacer esto, uso la función apply (). Pero lamentablemente, InternetExplorer me dice algo como: "Este objeto no es compatible con este método". ¿Alguien puede darme una pista de lo que puedo hacer?

(Para probar esto, también podría usar un pequeño objeto flash como Maestro y llamar a "doSomething" en lugar de mi "Inicializar" específico).

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 comparación, esta es la función apply () en acción:

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);
}
¿Fue útil?

Solución 4

¡Gracias kangax por tu tiempo y tu extensa explicación! Lamentablemente, no pude hacerlo funcionar de esta manera (funciona para el cuadro de alerta) Pero me llevó a la idea de usar una clase proxy. No es la forma más elegante porque tengo que proporcionar todas las funciones de mi objeto que quiero usar, pero funciona ¡Y no involucra 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);
}

Nuevamente, ¡gracias por tu tiempo!

Otros consejos

El problema con algunos de los objetos de host (es decir, cualquier objeto no nativo) en IE (y no solo IE) es que no heredan de Function.prototype (y a menudo tampoco de nivel superior Object.prototype ). Algunos objetos host que pueden parecer funciones en realidad no tienen nada que ver con las funciones, excepto que se pueden invocar. El hecho de que estos objetos no hereden de Function.prototype significa que no pueden identificarse como funciones con el operador instanceof ; que su constructor no hace referencia a Function ; y que carecen de todos los métodos Function.prototype. * , como call o apply . Incluso su propiedad interna [[Class]] podría no ser la de " Function " ;, como lo es con cualquier objeto nativo (tenga en cuenta que [[Class]] puede inferirse del resultado de Object.prototype.toString value).

Esto es realmente esperado, ya que no se requieren objetos host para implementar muchas cosas que hacen los objetos nativos (según ECMA-262, 3ª ed.). Está perfectamente permitido que un objeto host, por ejemplo, arroje un error en la invocación del método (por ejemplo, hostObject.hostMethod () ); o al pasarlo como un operando a operadores estándar como delete (por ejemplo, delete hostObject.hostMethod ). Como puede ver, también está bien que los objetos host invocables NO hereden de Function.prototype nativo.

Este comportamiento impredecible (pero perfectamente compatible) es en realidad una de las razones principales por las que se recomienda el aumento de objetos host en contra .

Pero volviendo a su problema de call :)

Lo que pasa con estos " complicado " Los objetos host de IE es que a menudo implementan el método interno [[Call]], y es posible invocar call y apply en ellos, aunque no directamente .

Aquí hay un patrón para emular la invocación apply en un objeto que no lo tiene:

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

null se puede reemplazar con cualquier objeto de contexto que deba llamarse, por supuesto.

Y un ejemplo de invocación apply en el objeto host que no tiene call :

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

Aplicarlo a su código

// Calls Master.initialize borrowing from Function.prototype
Function.prototype.apply.call(Master.initialize, Master, [1,"VC2"]);

Tuve este mismo problema y lo resolví compilando una función thunk en tiempo de ejecución para desenrollar el número correcto de argumentos (similar a la solución anterior, pero sin la restricción de que el identificador de objeto ActiveX tiene que estar en un global variable).

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, el motor JS de IE no ve las funciones ActiveX como objetos de función JavaScript en los que puede llamar apply () . ¿Qué tal si solo hacemos un eval () ? Aunque feo, parece ser tu única opción.

function invoke(objectName, fnName, args) {
    return eval(objectName + "." + fnName + "(" + args + ")");
}

Solo pensé en mencionar que si usa el método eval como Ates Goral dijo que debe tener cuidado con los argumentos de cadena en su matriz, ya que se considerarán nombres de variables, por ejemplo,

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

la eval pasará la línea

Master.Initialize(1,VC1)

que arrojará un error si VC1 no es una variable definida. Podría ser mejor "desenrollar" el nombre de la matriz en lugar de pasar literales:

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 que invocar se convierta

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

el eval se pasará

Master.Initialize(args[0],args[1]);
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top