2011-05-03 52 views
15

更新:我改寫了這個問題,因爲重要的一點,在我看來,識別物體的文字:如何區分其他Javascript對象的對象字面值?

我如何才能知道一個物體之間的差異文字和任何其他JavaScript對象(例如一個DOM節點,日期對象等)?我怎麼能寫這樣的功能:

function f(x) { 
    if (typeof x === 'object literal') 
     console.log('Object literal!'); 
    else 
     console.log('Something else!'); 
} 

因此,它只能打印Object literal!如下第一次調用的結果:

f({name: 'Tom'}); 
f(function() {}); 
f(new String('howdy')); 
f('hello'); 
f(document); 

原始的問題

我編寫一個旨在接受對象字面量,字符串或DOM節點作爲其參數的Javascript函數。它需要稍微不同地處理每個參數,但目前我無法弄清楚如何區分DOM節點和普通的舊對象文字。

這裏是我的功能大大簡化版本,爲每一種說法我需要處理測試沿:

function f(x) { 
    if (typeof x == 'string') 
     console.log('Got a string!'); 
    else if (typeof x == 'object') 
     console.log('Got an object literal!'); 
    else 
     console.log('Got a DOM node!'); 
} 

f('hello'); 
f({name: 'Tom'}); 
f(document); 

此代碼將記錄相同的消息後兩個電話。我無法弄清楚在else if條款中包含什麼。我嘗試了其他變體,如x instanceof Object,它們具有相同的效果。

我知道這可能是我的糟糕的API /代碼設計。即使是這樣,我仍然想知道如何做到這一點。

+0

http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object – Ben 2011-05-03 22:19:49

+0

@Ben,現在我已經更新了這個問題,我認爲這不是那個問題的重複。但如果它是另一個問題的重複,我會很樂意關閉它! – 2011-05-03 23:00:40

+0

這幾乎是重複的,但有沒有解決方案在給定用戶定義的對象時不會導致誤報? http://stackoverflow.com/questions/4320767/check-that-value-is-object-literal – 2011-05-03 23:07:11

回答

32

我怎樣才能告訴對象字面量和任何其他Javascript對象(例如,DOM節點,Date對象等)之間的區別?

簡短的回答是你不能。

對象字面是一樣的東西:

var objLiteral = {foo: 'foo', bar: 'bar'}; 

而同一對象使用對象構造函數創建可能是:

var obj = new Object(); 
obj.foo = 'foo'; 
obj.bar = 'bar'; 

我不認爲有任何可靠如何區分這兩個對象是如何創建的。

爲什麼它很重要?

一般功能測試策略是測試傳遞給函數的對象的屬性,以確定它們是否支持要調用的方法。這樣你就不會在乎如何創建一個對象。

你可以使用「鴨子打字」,但只能在有限的範圍內。你不能保證僅僅因爲一個對象有一個getFullYear()方法,它是一個Date對象。同樣,僅僅因爲它具有nodeType屬性並不意味着它是一個DOM對象。

例如,jQuery的isPlainObject函數認爲,如果一個對象都有一個nodeType屬性,它是一個DOM節點,如果它有一個setInterval財產這是一個Window對象。這種鴨子打字非常簡單,在某些情況下會失敗。

您可能還會注意到,jQuery依賴於以特定順序返回的屬性 - 另一個不被任何標準支持的危險假設(儘管一些支持者試圖改變標準以適應他們的假定行爲)。

編輯22-APR-2014:在版本1.10的jQuery包括基於測試單個屬性(顯然這是IE9支持),看看是否繼承屬性列舉第一個或最後一個support.ownLast屬性。這繼續忽略這樣一個事實,即一個對象的屬性可以返回任何的順序,無論它們是繼承的還是自己的,並且可能是混亂的。

可能是「普通」對象最簡單的測試:

function isPlainObj(o) { 
    return typeof o == 'object' && o.constructor == Object; 
} 

這將永遠成爲使用對象文本或Object構造函數創建的對象真實的,但很可能給出虛假的結果爲對象創建其他方式和可能(可能會)跨幀失敗。您也可以添加instanceof測試,但是我看不到它執行的任何構造函數測試都沒有。

如果你傳遞的是ActiveX對象,最好把它包裝在try..catch中,因爲它們可以返回各種奇怪的結果,甚至會拋出錯誤。

編輯13 - 10月 - 2015年

當然也有一些陷阱:

isPlainObject({constructor: 'foo'}); // false, should be true 

// In global scope 
var constructor = Object; 
isPlainObject(this);  // true, should be false 

與constructor屬性搞亂會導致一些問題。還有其他陷阱,例如由Object以外的構造函數創建的對象。

由於ES5現在幾乎無處不在,因此有Object.getPrototypeOf來檢查對象的[[Prototype]]。如果它是插入Object.prototype,那麼該對象是一個普通的對象。但是,一些開發人員希望創建沒有繼承屬性的真正「空」對象。這是可以做到用:

var emptyObj = Object.create(null); 

在這種情況下,[[Prototype]]屬性爲。因此只需檢查內部原型是否爲Object.prototype是不夠的。

也有相當廣泛用於:被指定爲返回基於內部[[Class]]屬性,這對於對象是[對象的對象]將字符串

Object.prototype.toString.call(valueToTest) 

。但是,在ECMAScript 2015中,這已經發生了變化,因此測試是針對其他類型的對象執行的,默認值是[object Object],因此該對象可能不是「普通對象」,而只是一個不被識別爲其他類型的對象。因此,該說明書中指出:

「不提供測試 可靠類型的機構對於其他種內置或程序定義的對象的[使用的toString測試]」

http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.prototype.tostring

所以更新的功能,允許進行預ES5主機,與空和其他對象類型的[[Prototype]]沒有getPrototypeOf(如對象,感謝Chris Nielsen ) 在下面。

注意,也沒有辦法來填充工具getPrototypeOf,因此,如果需要舊的瀏覽器支持可能沒有用(例如IE 8和下部,根據MDN)。

/* Function to test if an object is a plain object, i.e. is constructed 
 
** by the built-in Object constructor and inherits directly from Object.prototype 
 
** or null. Some built-in objects pass the test, e.g. Math which is a plain object 
 
** and some host or exotic objects may pass also. 
 
** 
 
** @param {} obj - value to test 
 
** @returns {Boolean} true if passes tests, false otherwise 
 
*/ 
 
function isPlainObject(obj) { 
 

 
    // Basic check for Type object that's not null 
 
    if (typeof obj == 'object' && obj !== null) { 
 

 
    // If Object.getPrototypeOf supported, use it 
 
    if (typeof Object.getPrototypeOf == 'function') { 
 
     var proto = Object.getPrototypeOf(obj); 
 
     return proto === Object.prototype || proto === null; 
 
    } 
 
    
 
    // Otherwise, use internal class 
 
    // This should be reliable as if getPrototypeOf not supported, is pre-ES5 
 
    return Object.prototype.toString.call(obj) == '[object Object]'; 
 
    } 
 
    
 
    // Not an object 
 
    return false; 
 
} 
 

 

 
// Tests 
 
var data = { 
 
    'Host object': document.createElement('div'), 
 
    'null'  : null, 
 
    'new Object' : {}, 
 
    'Object.create(null)' : Object.create(null), 
 
    'Instance of other object' : (function() {function Foo(){};return new Foo()}()), 
 
    'Number primitive ' : 5, 
 
    'String primitive ' : 'P', 
 
    'Number Object' : new Number(6), 
 
    'Built-in Math' : Math 
 
}; 
 

 
Object.keys(data).forEach(function(item) { 
 
    document.write(item + ': ' + isPlainObject(data[item]) + '<br>'); 
 
});

+0

感謝您的徹底答案(這主要證實了我的假設)。我認爲你的'isPlainObj'函數對於我的目的來說足夠準確。多謝! – 2011-05-04 05:05:34

+0

請注意,'typeof null ===「object」'和'Object.getPrototypeOf(null)'會引發錯誤。如果你希望'null'輸入返回false而不是拋出錯誤,你的'isPlainObject'函數應該包含'null'的檢查。 – 2015-10-12 17:15:49

+0

@克里斯尼爾森 - 謝謝你讓我回到這裏。鑑於自ES5以來,可以使用Object.create(null)創建帶有* null *的對象作爲它們的[[Prototype]],現在上面的代碼已經過時了,並且只要我有時間。 – RobG 2015-10-12 22:54:33

1

將DOM節點的檢查移到對象字面上方。檢查DOM節點上存在的某個屬性以檢測節點。我正在使用nodeType。這不是非常萬無一失,因爲你可以通過一個對象{nodeType: 0 },這將打破這一點。

if (typeof x == 'string') { /* string */ } 
else if ('nodeType' in x) { /* dom node */ } 
else if (typeof x == 'object') { /* regular object */ } 

所有duck類型檢查像上面的和甚至instanceof檢查是註定要失敗。要真正確定給定對象是否實際上是一個DOM節點,您需要使用傳入對象本身以外的其他對象。

+0

對不起,我在回答之後更新了我的問題,因爲我意識到我需要強調一個事實,即如果可能,準確識別對象文字是我的實際目標。 – 2011-05-03 22:50:20

2

因爲所有的DOM節點從Node接口繼承,你可以嘗試以下方法:

if(typeof x === 'string') { 
    //string 
} else if(x instanceof Node) { 
    //DOM Node 
} else { 
    //everything else 
} 

但我不知道這工作在較舊版本的Internet Explorer

+2

你不知道。雖然使用原型繼承來實現DOM對象可能是有意義的,它可以模仿W3C DOM規範所建議的方案,但沒有理由期望**所有**瀏覽器都可以這樣做。事實上,瀏覽器不必爲主機對象實現任何類型的繼承。 – RobG 2011-05-04 01:35:02

+1

好吧,我已經在Chrome中測試過它(所以它可能也會在Safari中工作),在Firefox和IE9中,所以至少所有現代瀏覽器似乎都以這種方式工作。 – standardModel 2011-05-04 13:39:25

+0

@standardModel @RobG我剛剛遇到了[kangax的深度文章](http://perfectionkills.com/whats-wrong-with-extending-the-dom/#lack_of_specification),它警告'instanceof Node '。我不知道有任何可靠的替代方案。 – tomekwi 2015-04-13 22:52:14

1

也許這樣的事情?

var isPlainObject = function(value){ 
    if(value && value.toString && value.toString() === '[object Object]') 
     return true; 

    return false; 
}; 

還是這個另一種方法:

var isObject = function(value){ 
    var json; 

    try { 
     json = JSON.stringify(value); 
    } catch(e){ 

    } 

    if(!json || json.charAt(0) !== '{' || json.charAt(json.length - 1) !== '}') 
     return false; 

    return true; 
}; 
1

類似@RobG例如:

function isPlainObject(obj) { 
    return typeof obj === 'object' // separate from primitives 
     && obj !== null   // is obvious 
     && obj.constructor === Object // separate instances (Array, DOM, ...) 
     && Object.prototype.toString.call(obj) === '[object Object]'; // separate build-in like Math 
} 

TEST:

function isPlainObject(obj) { 
 
\t return \t typeof obj === 'object' 
 
\t \t && obj !== null 
 
\t \t && obj.constructor === Object 
 
\t \t && Object.prototype.toString.call(obj) === '[object Object]'; 
 
} 
 

 
var data = { 
 
    '{}': {}, 
 
    'DOM element': document.createElement('div'), 
 
    'null'  : null, 
 
    'Object.create(null)' : Object.create(null), 
 
    'Instance of other object' : new (function Foo(){})(), 
 
    'Number primitive ' : 5, 
 
    'String primitive ' : 'P', 
 
    'Number Object' : new Number(6), 
 
    'Built-in Math' : Math 
 
}; 
 

 
Object.keys(data).forEach(function(item) { 
 
    document.write(item + ':<strong>' + isPlainObject(data[item]) + '</strong><br>'); 
 
});

相關問題