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

Is there an environment-agnostic way to detect Javascript Host Objects?

本文关键字:方法 检测 Javascript 对象 主机 环境 一种 是否      更新时间:2023-09-26

我正在写一个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来实现。

查看主机对象的定义- "由主机环境提供的对象来完成ECMAScript的执行环境。" -很明显,没有简单的方法来确定一个对象是主机还是本机。

与本机对象不同,宿主对象以特定于实现的方式定义内部属性(如[[Prototype]], [[Class]]等)。这是因为规范允许他们这样做。然而,宿主对象以特定于实现的方式实现内部行为并没有"必须"的要求;这是一种"可能"类型的需求。所以我们不能依赖这个。这些物体可能会也可能不会表现得"奇怪"。没有办法知道。

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

在某些环境中,宿主对象不继承Object.prototype(使其易于检查),或者具有抛出错误的某些属性(例如:"prototype"(在IE中某些"interface"对象上),甚至在访问时抛出错误。

看起来好像你只需要检查一个对象是否属于规范中定义的对象之一,如果不是,就认为它是宿主。但这并没有真正的帮助;它只会给你非内置的对象。这些非标准对象中的一些仍然可以是本机的(这意味着它们将实现规范中描述的常规语义)。

你最好的办法是测试你的应用程序/脚本的特定行为,宿主对象可能敏感。这永远是最安全的方法。您是否计划访问某个对象的某些内容?从对象中删除一些东西?添加一些东西到对象?测试一下。看看它是否有效。

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

几乎解决了

差点就成功了。

解决方案的不足之处在于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'); 

这里是一个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');

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

正如您所注意到的,jQuery项目也试图检测主机对象并遇到类似的问题。那个bug页面上的讨论很能说明问题。

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

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