JavaScript: проблема с ActiveX-объектом и функцией apply () - - PullRequest
3 голосов
/ 16 сентября 2009

У меня есть объект ActiveX (Master), и я хотел бы динамически вызывать на нем функции. Для этого я использую функцию 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);
}

Ответы [ 5 ]

3 голосов
/ 17 сентября 2009

Проблема с некоторыми из хост-объектов (т. Е. Любых не собственных объектов) в IE (и не только IE) заключается в том, что они не наследуются от Function.prototype (и часто ни от верхнего уровня Object.prototype). Некоторые хост-объекты, которые могут выглядеть как функции , на самом деле не имеют ничего общего с функциями, за исключением того, что они могут быть вызваны. Тот факт, что эти объекты не наследуются от Function.prototype, означает, что они не могут быть идентифицированы как функции с оператором instanceof; что их конструктор не ссылается Function; и что им не хватает всех Function.prototype.* методов, таких как call или apply. Даже их внутреннее свойство [[Class]] может не совпадать со свойством «Function», как и с любым собственным объектом (обратите внимание, что [[Class]] может быть выведен из результата Object.prototype.toString value).

Это действительно ожидается, поскольку хост-объекты не требуются для реализации многих вещей, которые делают нативные объекты (согласно ECMA-262, 3-е изд.). Допускается, чтобы хост-объект допустил ошибку при вызове метода (например, hostObject.hostMethod()); или при передаче его в качестве операнда стандартным операторам, таким как delete (например, delete hostObject.hostMethod). Как вы можете видеть, вызываемые объекты хоста также НЕ ДОЛЖНЫ наследоваться от нативного 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"]);
2 голосов
/ 16 февраля 2011

У меня была такая же проблема, и я решил ее, скомпилировав функцию 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);
};
1 голос
/ 17 сентября 2009

Спасибо 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);
}

Опять же, спасибо за ваше время!

1 голос
/ 16 сентября 2009

Очевидно, JS-движок IE не видит функции ActiveX как объекты функций JavaScript, для которых вы можете вызвать apply(). Как насчет просто сделать eval() - хотя уродливо, кажется, это ваш единственный вариант.

function invoke(objectName, fnName, args) {
    return eval(objectName + "." + fnName + "(" + args + ")");
}
0 голосов
/ 01 марта 2010

Просто подумал, что упомяну, что если вы используете метод eval, как сказал Атес Горал, вам нужно быть осторожным со строковыми аргументами в вашем массиве, так как они будут считаться именами переменных, например,

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
}

так что invoke становится

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]);
...