是否有一种与环境无关的方法来检测Javascript主机对象?

时间:2011-12-27 14:24:32

标签: javascript cross-platform javascript-objects ecma262

我正在编写一个Javascript stacktrace库。库需要检测程序员创建的特定对象或函数,还是作为环境的一部分(包括内置对象)。主机对象由于其不可预测的行为而变得有点问题,所以我采用环境无关的方式来确定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()方法也依赖于环境,而且逻辑相当复杂。

也许这是因为具有宿主对象的野兽的性质(因为它们的行为是由环境定义的),但是我想进一步深入研究这是否可能并且想知道是否有人遇到过之前的这个特殊问题,并准备好解决方案。

因此。有人知道一个简单的独立于平台的解决方案来测试主机对象吗?如果它在无浏览器的环境中运行,例如Node或Rhino,那就更好了。

可能的方法(可能不起作用):

  • 测试主机对象的特性似乎是一个失败的原因,因为它们的行为没有规范,但是测试该对象是否是ES3规范的一部分可能是可能的。
  • 我已尝试使用Object.prototype.toString(),因为它的定义非常具体,但结果是不确定的,因为某些环境(即IE)选择为本机和主机对象返回相同的值。
  • 可以通过检查原型链中对象的最终constructor是否真的是instanceof Function来实现此目的。

5 个答案:

答案 0 :(得分:5)

当您查看主机环境提供的definition of host object - “对象以完成ECMAScript的执行环境时。” - 很明显,没有简单的方法可以确定是否对象是主机或本机。

与本机对象不同,主机对象以特定于实现的方式定义内部属性(例如[[Prototype]],[[Class]]等)。那是因为specification allows them to do this。但是,主机对象没有“必须”要求以特定于实现的方式实现内部行为;这是一种“可能”类型的要求。所以我们不能依赖于此。这些对象可能会或可能不会“怪异”。没有办法说出来。

过去几乎没有尝试检测宿主对象,但所有这些都明显依赖于对某些环境的观察(MSHTML DOM就是其中之一) - 请记住宿主对象没有任何独特的模式/特征来识别。 Peter Michaux documented most of the inferences here(看一下“功能测试主机对象”部分)。臭名昭着的typeof ... == "unknown"来自MSHTML DOM及其基于ActiveX的主机对象。请注意,Peter主要在浏览器脚本的上下文中讨论主机对象,并将检查缩小为“这是一个主机方法吗?”,“这是一个主机集合对象”等。

在某些环境中,宿主对象不会从Object.prototype继承(使其易于检查),或者具有某些抛出错误的属性(例如,IE中某些“接口”对象上的“原型”),或者甚至在访问时自己抛出错误。

看起来您可能只是检查对象是否是规范中定义的对象之一,如果不是,则将其视为主机。但这并不会真正有用;它只会为你提供不是内置的对象。其中一些非标准对象仍然可以是原生的(这意味着它们将实现规范中描述的通常语义)。

您最好的选择是测试您的应用/脚本的特定行为,主机对象可能对此敏感。这始终是最安全的方式。你打算从对象上访问某些东西吗?从对象中删除内容?添加对象的东西?测试它。看看它是否有效。如果没有 - 你可能正在处理宿主对象。

答案 1 :(得分:4)

这是isNative的较新版本,它拒绝所有具有toString本机实现的对象,这解决了堆栈跟踪库的问题,但没有满意地回答此处发布的问题。对于后者,此方法失败的地方是过滤掉所有内置类型定义,例如ObjectDateStringMath等,这些定义不是主机对象本身。此外,此解决方案还取决于环境如何输出本机/内置函数定义(它必须包含“[native code]”才能使函数工作)。由于函数的行为不同,因此它已重命名为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'); 

以下是list of JsFiddle tests,可用于在各种浏览器中对此进行测试。

// 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 :(得分:2)

几乎已解决

几乎设法使这个工作。

解决方案不足之处在于Host对象有时与Native对象无法区分。在Chrome上测试isNative(window.alert)时,以下代码失败,因为webkit引擎定义了一个alert函数(到目前为止)看起来与本机函数相同。

它根据ES3使用普通javascript,并且基于测试对象是本机的(而不是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'); 

以下是list of JsFiddle tests,可用于在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');

答案 3 :(得分:0)

我相信主机对象的本质意味着没有一种简单的,与环境无关的方法来检测它们。如果您感到好奇,请参阅this discussion on SO了解更多信息。

正如您所指出的,jQuery项目也有tried to detect host objects并遇到类似的麻烦。关于该错误页面的讨论很有说服力。

答案 4 :(得分:0)

我的想法可能并不适用于所有情况。

确保您的脚本是要执行的第一个,并将其包装在闭包中,就像JS框架一样。
然后,遍历全局范围内的所有对象(如果您使用的不是浏览器,window将是未定义的;因此在脚本开始时执行{{1} }),遍历其子项,依此类推除了您之外的所有对象都是主机对象!然后您可以将其添加到本地数据库,甚至可以存储它并将其与运行环境相关联以备将来使用。