2018年4月6日 星期五

[ JavaScript Gossip ] JavaScript 核心 : 定義物件 (檢驗物件)

Source From Here 
前言 : 
通常很少直接確認物件的型態,因為 JavaScript 是動態語言,對於物件的操作,僅要求是否具備所需特性,而不在意所謂的類型,物件的特性偵測絕大多數情況下就足夠了. 例如 : 
  1. if(obj.someProperty) {   
  2.     // 特性存在時作某些事  
  3. }  
因為特性不存在的話,會傳回 undefined,而在判斷式中會被作為 false,若存在,則會傳回物件,在判斷式中會被作為 true,這就是物件特性偵測的基本原理. 

檢驗物件 : 
如果真得確認物件的型態,有許多方式,但基本上要不就所提供的資訊有限,要不就不可信任. 例如 typeof 運算子,傳回值是字串,對於基本資料型態,數值會傳回 'number'、字串會傳回 'string'、布林會傳回 'boolean'、對於 Function實例 會傳回'function'、對於 undefined 會傳回'undefined'、對於其它物件一律傳回 'object',包括 null 也是傳回'object',所以,只要是非函式實例的物件,基本上無從辨別真正型態. 

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

上面這個例子,是利用原型鏈查找機制,達到所謂繼承的效果. 由於 SportsCar.prototype 設定為 Car 的實例,所以在查找 wheels 特性 時,sportsCar 所參考物件本身沒有,就到原型物件上找,也就是 SportsCar.prototype 所參考的物件上找,這個物件是 Car 的實 例,本身也沒有 wheels 特性,所以就到 Car 實例的原型尋找,也就是 Car.prototype 所參考的物件,此時就找到了. 然而,在查找 constructor 時,依同樣的機制,所找到的其實是 Car.prototype.constructor 特性,上例中應該再加一行才會比較正確 : 
  1. SportsCar.prototype.constructor = SportsCar;  
如果忘了作這個動作,在判斷物件時所得到的就會是不正確的結果. 

關於 new 建立物件,函式 prototype 特性中談過,使用 new 關鍵字時,JavaScript 會先建立一個空物件,接著設定物件的原型為函式的 prototype 特性所參考的物件,然後呼叫建構式並將所建立的空物件設為 this. 注意,物件的原型是在建立物件之後就確立下來的,原型鏈查找特性時,是根據物件上的原型,而不是函式上的 prototype. 例如,你可以看看以下為何無法取得特性 : 
> function Car() {
> Car.prototype.wheels = 4;
> }

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

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

這是初學者常犯的錯誤. 注意!物件的原型是在建立物件之後就確立下來的,所以在這行 : 
  1. var sportsCar = new SportsCar();  
sportsCar 就被指定了原型物件,也就是當時的 SportsCar.prototype 所參考的物件,預設就是具有一個 constructor 特性 的 Object 實例,之後你在 SportsCar() 函式中將 SportsCar.prototype 指定為 Car 的實例,對 sportsCar 的原型物 件根本沒有影響,sportsCar 的原型物件仍是 Object 實例,而不是 Car 實例,自然就找不到 doors 特性,更別說是 wheels 特性了. 

再來用實際的程式示範會更清楚,這次用非標準的 __proto__ 來驗證 : 
> function Car() {
> Car.prototype.wheels = 4;
> }

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

> var p = SportsCar.prototype;
> var sportsCar = new SportsCar();
> p == sportsCar.__proto__;
true
> sportsCar.__proto__ == SportsCar.prototype;
false // 物件上的 prototype 與 函式上的 prototype 不相同!

從上例中可以看到,建立物件時即設定原型,而物件上的原型最後跟 SportsCar.prototype 根本就不是同一個物件了。所以 new 建立物件時,例如 : 
  1. var some = new Some();  
可以說作了這些事 : 
  1. var some = {};  
  2. some.__proto__ = Some.prototype;  
  3. Some.call(some);  
事實上,instanceof 也是根據物件的原型物件來判斷 true 或 false 的. 例如 : 
> function Car() {}
> function SportsCar() {}
> SportsCar.prototype = new Car();
[object Object]
> var sportsCar = new SportsCar();
> sportsCar instanceof SportsCar;
true
> sportsCar instanceof Car;
true
> sportsCar instanceof Object;
true

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

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

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

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

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

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

當然,特性不存在時就無法列舉,所以會傳回 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]

Supplement 
NodeJS Get Started

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...