9

我正在编写一个 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()方法也依赖于环境,而且逻辑相当复杂。

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

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

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

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

5 回答 5

5

当您查看宿主对象的定义时—— “宿主环境提供的完成 ECMAScript 执行环境的对象”。— 很明显,没有简单的方法可以确定一个对象是宿主对象还是本地对象。

与原生对象不同,宿主对象以特定于实现的方式定义内部属性(例如 [[Prototype]]、[[Class]] 等)。那是因为规范允许他们这样做。但是,没有“必须”要求宿主对象以特定于实现的方式实现内部行为;这是“可能”类型的要求。所以我们不能依赖这个。这些对象可能会或可能不会表现得“奇怪”。没有办法说。

过去很少有人尝试检测宿主对象,但所有这些显然都依赖于对某些环境的观察(MSHTML DOM 就是其中之一)——请记住,宿主对象没有任何独特的模式/特征来识别经过。Peter Michaux在这里记录了大部分推论(请查看“功能测试主机对象”部分)。臭名昭著typeof ... == "unknown"的来自 MSHTML DOM 及其基于 ActiveX 的宿主对象。请注意,Peter 主要在浏览器脚本的上下文中讨论主机对象,他将检查范围缩小到“这是一个主机方法吗?”、“这是一个主机集合对象”等。

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

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

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

于 2011-12-28T05:58:37.257 回答
4

这是一个较新的版本,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');​
于 2012-02-15T02:27:40.460 回答
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'); 

这是可用于在 IE / Firefox / Chrome 中进行测试的 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(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');
于 2012-01-04T11:33:17.383 回答
0

我相信宿主对象的本质意味着没有一种简单的、与环境无关的方法来检测它们。如果您好奇,请参阅关于 SO 的更多讨论。

正如您所注意到的,jQuery 项目也尝试检测宿主对象并遇到类似的问题。该错误页面上的讨论非常有说服力。

于 2011-12-27T15:11:36.707 回答
0

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

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

于 2011-12-27T15:36:46.233 回答