Существует ли независимый от среды способ обнаружения объектов хоста Javascript? - PullRequest
8 голосов
/ 27 декабря 2011

Я пишу библиотеку трассировки стека Javascript. Библиотека должна определять, какой объект или функция были созданы программистом или были частью среды (включая встроенные объекты). Хост-объекты становятся немного проблематичными из-за их непредсказуемого поведения, поэтому я использую независимый от среды способ определить, является ли конкретный объект в Javascript хост-объектом (см. ECMAScript 3 - 4.3.8) , Тем не менее, различение хост-объектов от собственных объектов и значений примитивов полезно для программистов в других проектах, особенно в средах без браузера, поэтому я хотел бы сосредоточиться на этом, а не на проблемах, которые хост-объекты вызывают в моей библиотеке или на отличительном программисте объекты.

До сих пор мне удавалось только найти решения, которые зависят от среды, в которой работает код JavaScript. Например:

// IE Only: does not implement valueOf() in Host Objects
var isHost = (typeof obj === 'object' && typeof obj.valueOf === 'undefined');

// Firefox Only: Host objects have own constructor
var isHost = (obj.constructor && obj.hasOwnProperty('constructor'));

Я заметил, что собственный метод jQuery isPlainObject () также зависит от среды, и что логика довольно запутанная.

Возможно, это потому, что такова природа зверя с хост-объектами (поскольку их поведение определяется средой), но я хотел бы еще немного покопаться, чтобы увидеть, возможно ли это, и хотел бы знать, сталкивался ли кто-нибудь с ним. эта конкретная проблема ранее и имеет готовое решение.

Итак. Кто-нибудь знает простое независимое от платформы решение для тестирования на Host Objects? И если он работает в среде без браузера, такой как Node или Rhino, тем лучше для него.

Возможные подходы (которые могут не работать):

  • Тестирование характеристик хост-объектов кажется безнадежным делом, учитывая, что нет спецификации для их поведения, однако проверка того, является ли объект частью спецификации ES3, может быть возможной.
  • Я пытался использовать Object.prototype.toString(), учитывая, что он определен довольно конкретно, но результаты неубедительны, так как некоторые среды (а именно IE) решили возвращать одно и то же значение для нативных и хост-объектов.
  • Это можно сделать, проверив, действительно ли конечный constructor объекта в цепочке прототипов instanceof Function.

Ответы [ 5 ]

5 голосов
/ 28 декабря 2011

Когда вы посмотрите на определение хост-объекта - "объект, предоставленный хост-средой для завершения среды исполнения ECMAScript." - становится довольно ясно, что не существует простогоспособ определить, является ли объект хостом или собственным.

В отличие от собственных объектов, хост-объекты определяют внутренние свойства (такие как [[Prototype]], [[Class]] и т. д.) специфичным для реализации способом.Это потому, что спецификация позволяет им делать это .Тем не менее, для хост-объектов нет требования «ОБЯЗАННО» реализовывать внутреннее поведение в зависимости от реализации;это тип требования "МОЖЕТ".Поэтому мы не можем полагаться на это.Эти объекты могут или не могут действовать "странно".Нет никакого способа узнать.

В прошлом было несколько попыток обнаружить хост-объекты, но все они, очевидно, полагаются на наблюдения определенных сред (одним из них является MSHTML DOM) - помните, что хост-объекты нене имеет какого-либо уникального паттерна / черты, по которой можно идентифицировать.Питер Мишо задокументировал большинство выводов здесь (см. Раздел «Проверка возможностей хост-объекта»).Печально известный typeof ... == "unknown" происходит от MSHTML DOM и его основанных на ActiveX хост-объектов.Обратите внимание, что Питер говорит о хост-объектах в основном в контексте сценариев браузера, и он сужает проверки до «это метод хоста?», «Это хост collection object» и т. Д.

В некоторых средах хост-объекты не наследуются от Object.prototype (что облегчает проверку) или имеют определенные свойства, которые выдают ошибки (например, «прототип» в некоторых «интерфейсных» объектах в IE) или даже выбрасываютсами ошибки при доступе.

Может показаться, что вы можете просто проверить, является ли объект одним из объектов, определенных в спецификации, и, если нет, считать его хостом.Но это не поможет.это даст вам только объекты, которые не являются встроенными .Некоторые из этих нестандартных объектов могут все еще быть нативными (это означает, что они будут реализовывать обычную семантику, как описано в спецификации).

Лучше всего было бы проверить конкретное поведение вашего приложения./ script, к которому могут относиться чувствительные объекты.Это всегда самый безопасный способ.Планируете ли вы получить доступ к чему-то от объекта?Удалить что-то из объекта?Добавить что-то к объекту?Проверьте это.Посмотри, работает ли это.Если это не так - вы, вероятно, имеете дело с хост-объектом.

4 голосов
/ 15 февраля 2012

Вот более новая версия isNative, которая отклоняет все объекты с нативной реализацией для toString, которая решает проблему для библиотеки трассировки стека, но не отвечает удовлетворительно на вопрос, размещенный здесь.Там, где этот подход не работает для последнего, он отфильтровывает все встроенные определения типов, такие как Object, Date, String, Math и т. Д., Которые сами не являются объектами хоста.Кроме того, это решение зависит от того, как среда выводит определения встроенных / встроенных функций (для работы функции необходимо включить «[собственный код]»).Поскольку поведение функции отличается, она была переименована в isUserObject.

// USER OBJECT DETECTION

function isUserObject(obj) {

    // Should be an instance of an Object
    if (!(obj instanceof Object)) return false;

    // Should have a constructor that is an instance of Function
    if (typeof obj.constructor === 'undefined') return false;
    if (!(obj.constructor instanceof Function)) return false;

    // Avoid built-in functions and type definitions
    if (obj instanceof Function && 
      Function.prototype.toString.call(obj).indexOf('[native code]') > -1) 
          return false;

    return true;
}

// CHECK IF AN OBJECT IS USER-CREATED OR NOT

if (typeof myObject === 'object' || typeof myObject === 'function')
   alert(isUserObject(myObject) ? 'User Object' : 'Non-user Object'); 

. Здесь приведен список тестов JsFiddle , которые можно использовать для проверки этого в различных браузерах.

// ASSERT HELPER FUNCTION

var n = 0;
function assert(condition, message) {
    n++;
    if (condition !== true) {
       document.write(n + '. FAILS: ' + (message || '(no message)') + '<br/>');
    } else {
       document.write(n + '. PASS: ' + (message || '(no message)') + '<br/>');
    }
}

// USER CREATED OBJECTS

assert(isUserObject({}), '{} -- Plain object');
assert(isUserObject(function() {}), 'function() {} -- Plain function');
assert(isUserObject([]), '[] -- Plain array');

assert(isUserObject(/regex/), '/regex/ - Native regex');
assert(isUserObject(new Date()), 'new Date() - Native date object through instantiation');

assert(isUserObject(new String('string')), 'new String("string") - Native string object through instantiation');
assert(isUserObject(new Number(1)), 'new Number(1) - Native number object through instantiation');
assert(isUserObject(new Boolean(true)), 'new Boolean(true) - Native boolean object through instantiation');
assert(isUserObject(new Array()), 'new Array() - Native array object through instantiation');
assert(isUserObject(new Object()), '{} -- new Object() - Native object through instantiation');
assert(isUserObject(new Function('alert(1)')), '{} -- Native function through instantiation');

// USER OBJECT INSTANTIATION AND INHERITANCE

var Animal = function() {};
var animal = new Animal();

var Dog = function() {};
Dog.prototype = animal;
var dog = new Dog();

assert(isUserObject(Animal), 'Animal -- User defined type');
assert(isUserObject(animal), 'animal -- Instance of User defined type');

assert(isUserObject(Dog), 'Dog -- User defined inherited type');
assert(isUserObject(dog), 'dog -- Instance of User defined inherited type');

// BUILT IN OBJECTS

assert(!isUserObject(Object), 'Object -- Built in');
assert(!isUserObject(Array), 'Array -- Built in');
assert(!isUserObject(Date), 'Date -- Built in');
assert(!isUserObject(Boolean), 'Boolean -- Built in');
assert(!isUserObject(String), 'String -- Built in');
assert(!isUserObject(Function), 'Function -- Built in');

// PRIMITIVE TYPES 

assert(!isUserObject('string'), '"string" - Primitive string');
assert(!isUserObject(1), '1 - Primitive number');
assert(!isUserObject(true), 'true - Primitive boolean');
assert(!isUserObject(null), 'null - Primitive null');
assert(!isUserObject(NaN), 'NaN - Primitive number NotANumber');
assert(!isUserObject(Infinity), 'Infinity - Primitive number Infinity');
assert(!isUserObject(undefined), 'undefined - Primitive value undefined');

// HOST OBJECTS

assert(!isUserObject(window), 'window -- Host object');
assert(!isUserObject(alert), 'alert -- Host function');
assert(!isUserObject(document), 'document -- Host object');
assert(!isUserObject(location), 'location -- Host object');
assert(!isUserObject(navigator), 'navigator -- Host object');
assert(!isUserObject(parent), 'parent -- Host object');
assert(!isUserObject(frames), 'frames -- Host object');​
2 голосов
/ 04 января 2012

ПОЧТИ РЕШЕНО

Почти удалось заставить это работать.

Решение не дало того, что объекты Host иногда неотличимы от объектов Native. Приведенный ниже код дает сбой при тестировании isNative(window.alert) в Chrome, поскольку движок webkit определяет функцию alert, которая (пока) выглядит идентичной нативной.

Он использует простой javascript согласно ES3 и основан на тестировании того, что объект является нативным (в отличие от объекта Host). Однако в соответствии с определением ES3 хост-объектов: 'Любой объект, который не является нативным, является хост-объектом.' эта функция может использоваться для обнаружения хост-объектов.

// ISNATIVE OBJECT DETECTION

function isNative(obj) {

    switch(typeof obj) {
        case 'number': case 'string': case 'boolean':
            // Primitive types are not native objects
            return false;
    }  

    // Should be an instance of an Object
    if (!(obj instanceof Object)) return false;

    // Should have a constructor that is an instance of Function
    if (typeof obj.constructor === 'undefined') return false;
    if (!(obj.constructor instanceof Function)) return false;

    return true;
}

// CHECK IF AN OBJECT IS HOST OR NATIVE

if (typeof myObject === 'object' || typeof myObject === 'function')
   alert(isNative(myObject) ? 'Native Object' : 'Host Object'); 

Вот список тестов JsFiddle , который можно использовать для проверки этого в IE / Firefox / Chrome.

Я не тестировал небраузерные среды, так как это немного сложнее, но поскольку код настолько прост, я не думаю, что у него возникнут какие-либо проблемы.

// ASSERT HELPER FUNCTION

var n = 0;
function assert(condition, message) {
    n++;
    if (condition !== true) {
       document.write(n + '. FAILS: ' + (message || '(no message)') + '<br/>');
    } else {
       document.write(n + '. PASS: ' + (message || '(no message)') + '<br/>');
    }
}

// USER CREATED OBJECTS

assert(isNative({}), '{} -- Plain object');
assert(isNative(function() {}), 'function() {} -- Plain function');
assert(isNative([]), '[] -- Plain array');

assert(isNative(/regex/), '/regex/ - Native regex');
assert(isNative(new Date()), 'new Date() - Native date object through instantiation');

assert(isNative(new String('string')), 'new String("string") - Native string object through instantiation');
assert(isNative(new Number(1)), 'new Number(1) - Native number object through instantiation');
assert(isNative(new Boolean(true)), 'new Boolean(true) - Native boolean object through instantiation');
assert(isNative(new Array()), 'new Array() - Native array object through instantiation');
assert(isNative(new Object()), '{} -- new Object() - Native object through instantiation');
assert(isNative(new Function('alert(1)')), '{} -- Native function through instantiation');

// USER OBJECT INSTANTIATION AND INHERITANCE

var Animal = function() {};
var animal = new Animal();

var Dog = function() {};
Dog.prototype = animal;
var dog = new Dog();

assert(isNative(Animal), 'Animal -- User defined type');
assert(isNative(animal), 'animal -- Instance of User defined type');

assert(isNative(Dog), 'Dog -- User defined inherited type');
assert(isNative(dog), 'dog -- Instance of User defined inherited type');

// BUILT IN OBJECTS

assert(isNative(Object), 'Object -- Built in');
assert(isNative(Array), 'Array -- Built in');
assert(isNative(Date), 'Date -- Built in');
assert(isNative(Boolean), 'Boolean -- Built in');
assert(isNative(String), 'String -- Built in');
assert(isNative(Function), 'Function -- Built in');

// PRIMITIVE TYPES 

assert(!isNative('string'), '"string" - Primitive string');
assert(!isNative(1), '1 - Primitive number');
assert(!isNative(true), 'true - Primitive boolean');
assert(!isNative(null), 'null - Primitive null');
assert(!isNative(NaN), 'NaN - Primitive number NotANumber');
assert(!isNative(Infinity), 'Infinity - Primitive number Infinity');
assert(!isNative(undefined), 'undefined - Primitive value undefined');

// HOST OBJECTS

assert(!isNative(window), 'window -- Host object');
assert(!isNative(alert), 'alert -- Host function'); // fails on chrome
assert(!isNative(document), 'document -- Host object');
assert(!isNative(location), 'location -- Host object');
assert(!isNative(navigator), 'navigator -- Host object');
assert(!isNative(parent), 'parent -- Host object');
assert(!isNative(frames), 'frames -- Host object');
0 голосов
/ 27 декабря 2011

У меня есть идея, которая может быть неприменима во всех контекстах.

Убедитесь, что ваш скрипт является первым , который нужно выполнить, и оберните его в замыкание , во многом как в JS Framework.
Затем перебирает все объекты в вашей глобальной области видимости (Если вы используете что-то, что не является браузером, window будет неопределенным; следовательно, в начале скрипта сделайте window = this), и перебирает своих потомков и так далее . Все объекты, кроме вашего, будут хост-объектами! затем вы можете добавить это в локальную базу данных или даже сохранить и связать с работающей средой для будущего использования.

0 голосов
/ 27 декабря 2011

Я считаю, что сама природа хост-объектов означает, что не существует простого, независимого от окружающей среды способа их обнаружения.См. это обсуждение SO , если вам интересно.

Как вы заметили, проект jQuery также попытался обнаружить хост-объекты и столкнулся саналогичные проблемы.Обсуждение на этой странице ошибок очень показательно.

...