檢驗物件


通常很少直接確認物件的型態,因為JavaScript是動態語言,對於物件的操作,僅要求是否具備所需特性,而不在意所謂的類型,物件的特性偵測絕大多數情況下就足夠了。例如:
if(obj.someProperty) {
    // 特性存在時作某些事
}

因為特性不存在的話,會傳回undefined,而在判斷式中會被作為false,若存在,則會傳回物件,在判斷式中會被作為true,這就是物件特性偵測的基本原理。

如果真得確認物件的型態,有許多方式,但基本上要不就所提供的資訊有限,要不就不可信任。

例如typeof運算子,傳回值是字串,對於基本資料型態,數值會傳回'number'、字串會傳回'string'、布林會傳回 'boolean'、對於Function實例會傳回'function'、對於undefined會傳回'undefined'、對於其它物件一律傳回'object',包括null也是傳回'object',所以,只要是非函式實例的物件,基本上無從辨別真正型態。

你可以從物件的constructor特性來確認物件的建構式為何,因為如 函式 prototype 特性 中有談過,每個函式的實例,其prototype會有個constructor特性,參考至函式本身,這是確認物件型態的方式之一,只不過,constructor是個可修改的特性,雖然沒什麼人會去修改constructor特性,但是如果是在原型鏈的情況下:
js> function Person() {}
js> function Car() {}
js> Car.prototype.wheels = 4;
4
js> function SportsCar() {}
js> SportsCar.prototype = new Car();
[object Object]
js> SportsCar.prototype.doors = 2;
2
js> var sportsCar = new SportsCar();
js> sportsCar.doors;
2
js> sportsCar.wheels;
4
js> sportsCar.constructor;
function Car() {
}

js>

上 面這個例子,是利用原型鏈查找機制,達到所謂繼承的效果。由於SportsCar.prototype設定為Car的實例,所以在查找wheels特性 時,sportsCar所參考物件本身沒有,就到原型物件上找,也就是SportsCar.prototype所參考的物件上找,這個物件是Car的實 例,本身也沒有wheels特性,所以就到Car實例的原型尋找,也就是Car.prototype所參考的物件,此時就找到了。

然而,在查找constructor時,依同樣的機制,所找到的其實是Car.prototype.constructor特性,上例中應該再加一行才會比較正確:
SportsCar.prototype.constructor = SportsCar;

如果忘了作這個動作,所得到的就會是不正確的結果。

關於new建立物件,函式 prototype 特性中談過,使用new關鍵字時,JavaScript會先建立一個空物件,接著設定物件的原型為函式的prototype特性所參考的物件,然後呼叫建構式並將所建立的空物件設為this。

注意,物件的原型是在建立物件之後就確立下來的,原型鏈查找特性時,是根據物件上的原型,而不是函式上的prototype。例如,你可以看看以下為何無法取得特性:

js> function Car() {
  >     Car.prototype.wheels = 4;
  > }
js> function SportsCar() {
  >     SportsCar.prototype = new Car();
  >     SportsCar.prototype.doors = 2;
  > }

js> var sportsCar = new SportsCar();
js> print(sportsCar.doors);
undefined
js> print(sportsCar.wheels);
undefined
js>


這是初學者常犯的錯誤。注意,物件的原型是在建立物件之後就確立下來的,所以在這行:
var sportsCar = new SportsCar();

sportsCar就被指定了原型物件,也就是當時的SportsCar.prototype所參考的物件,預設就是具有一個constructor特性 的Object實例,之後你在SportsCar()函式中將SportsCar.prototype指定為Car的實例,對sportsCar的原型物 件根本沒有影響,sportsCar的原型物件仍是Object實例,而不是Car實例,自然就找不到doors特性,更別說是wheels特性了。

再來用實際的程式示範會更清楚,這次用非標準的__proto__來驗證:

js> function Car() {
  >     Car.prototype.wheels = 4;
  > }
js> function SportsCar() {
  >     SportsCar.prototype = new Car();
  >     SportsCar.prototype.doors = 2;
  > }
js> var p = SportsCar.prototype;
js> var sportsCar = new SportsCar();
js> p ==
sportsCar.__proto__;
true
js>
sportsCar.__proto__ == SportsCar.prototype;
false
js>


從上例中可以看到,建立物件時即設定原型,而物件上的原型最後跟SportsCar.prototype根本就不是同一個物件了。所以new建立物件時,例如:
var some = new Some();

可以說作了這些事:
var some = {};
some.__proto__ = Some.prototype;
Some.call(some);

事實上,instanceof也是根據物件的原型物件來判斷true或false的。例如:

js> function Car() {}
js> function SportsCar() {}
js> SportsCar.prototype = new Car();
[object Object]
js> var sportsCar = new SportsCar();
js> sportsCar instanceof SportsCar;
true
js> sportsCar instanceof Car;
true
js> sportsCar instanceof Object;
true
js>


簡單地說,instanceof是根據原型鏈來查找。明白這個機制,以下用非標準__proto__特性來欺騙instanceof:
js> var o = {};
js> o instanceof Array;
false
js> o.__proto__ = Array.prototype;
js> o instanceof Array;
true
js>


上例中建立的絕不是Array的實例,不過最後欺騙了instanceof使之傳回true。

或許檢驗物件的原型也是個方式,但__proto__是個非標準特性,如果你想要檢驗物件原型,可以使用isPrototypeOf()方法。例如 函式 prototype 特性 中就這麼作過:
js> var arr = [];
js> Array.prototype.isPrototypeOf(arr);
true
js> Function.prototype.isPrototypeOf(Array);
true
js> Object.prototype.isPrototypeOf(Array.prototype);
true
js>


其實isPrototypeOf()的作用與instanceof類似,都是透過原型鏈來確認:
js> var arr = [];
js> Array.prototype.isPrototypeOf(arr);
true
js> Object.prototype.isPrototypeOf(arr);
true
js>


在取得一個物件的特性時,會尋找原型鏈,如果想確認特性是物件本身所擁有,或是其原型上的特性,則可透過物件都具有的hasOwnProperty()方法(當然,這是Object.prototype上的一個特性)。例如:
js> var o = { x : 10 };
js> o.hasOwnPrototype('x');
js> o.hasOwnProperty('x');
true
js> o.hasOwnProperty('toString');
false
js> o.hasOwnProperty('xyz');
false
js>


如果特性不是物件本身擁有,而是原型鏈上可取得,則會傳回false,尋找不到特性也是傳回false。

物件本身所新建的特性是可以用for in列舉的,有些內建特性無法列舉,想要知道特性是不是可用for in列舉,則可以使用propertyIsEnumerable()方法。例如:
js> var o = { x : 10 };
js> o.propertyIsEnumerable('x');
true
js> o.propertyIsEnumerable('toString');
false
js> o.propertyIsEnumerable('xyz');
false
js>


當然,特性不存在時就無法列舉,所以會傳回false。

另外,ECMAScript規格要求Object預設的toString()要傳回以下的字串:
[object class]

JavaScript的內建型態基本上都會遵守這樣的規定,例如Object實例會傳回[object Object]、陣列會傳回[object Array]、函式會傳回[object Function]等,這也可作為判斷型態的依據,基於對標準的支持,現在一些程式庫多使用這個來作判斷。

注意!在Internet Explorer中,alert ()、confirm()等內建函式,或是某些物件上的方法,typeof不會正確地回報為'function',使用instanceof看看是否為 Function實例,結果也是false,toString()傳回的也不一定是[object Function]。