JavaScript instanceof 運算子深入剖析

developerworks發表於2013-06-19

  隨著 web 的發展,越來越多的產品功能都放在前端進行實現,增強使用者體驗。而前端開發的主要語言則是 JavaScript。學好 JavaScript 對開發前端應用已經越來越重要。在開發複雜產品中,需要使用物件導向的機制時,往往會用到複雜的 JavaScript 繼承,而 instanceof 運算子是 JavaScript 語言中原生的用來判斷例項繼承關係的操作符。深入理解 instanceof 運算子的用法,對寫好複雜的 JavaScript 程式,會有很大幫助。

  instanceof 運算子簡介

  在 JavaScript 中,判斷一個變數的型別嚐嚐會用 typeof 運算子,在使用 typeof 運算子時採用引用型別儲存值會出現一個問題,無論引用的是什麼型別的物件,它都返回 "object"。ECMAScript 引入了另一個 Java 運算子 instanceof 來解決這個問題。instanceof 運算子與 typeof 運算子相似,用於識別正在處理的物件的型別。與 typeof 方法不同的是,instanceof 方法要求開發者明確地確認物件為某特定型別。例如:

  清單 1. instanceof 示例

var oStringObject = new String("hello world"); 
console.log(oStringObject instanceof String); 	 // 輸出 "true"

  這段程式碼問的是“變數 oStringObject 是否為 String 物件的例項?”oStringObject 的確是 String 物件的例項,因此結果是"true"。儘管不像 typeof 方法那樣靈活,但是在 typeof 方法返回 "object" 的情況下,instanceof 方法還是很有用的。

  instanceof 運算子的常規用法

  通常來講,使用 instanceof 就是判斷一個例項是否屬於某種型別。例如:

  清單 2. instanceof 常規用法

 // 判斷 foo 是否是 Foo 類的例項
 function Foo(){} 
 var foo = new Foo(); 
 console.log(foo instanceof Foo)//true

  另外,更重的一點是 instanceof 可以在繼承關係中用來判斷一個例項是否屬於它的父型別。例如:

  清單 3. instanceof 在繼承中關係中的用法

 // 判斷 foo 是否是 Foo 類的例項 , 並且是否是其父型別的例項
 function Aoo(){} 
 function Foo(){} 
 Foo.prototype = new Aoo();//JavaScript 原型繼承

 var foo = new Foo(); 
 console.log(foo instanceof Foo)//true 
 console.log(foo instanceof Aoo)//true

  上面的程式碼中是判斷了一層繼承關係中的父類,在多層繼承關係中,instanceof 運算子同樣適用。

  你真的瞭解 instanceof 操作符嗎?

  看了上面的程式碼示例,是不是覺得 instanceof 操作符很簡單,下面來看點複雜的用法。

  清單 4. instanceof 複雜用法

 console.log(Object instanceof Object);//true 
 console.log(Function instanceof Function);//true 
 console.log(Number instanceof Number);//false 
 console.log(String instanceof String);//false 

 console.log(Function instanceof Object);//true 

 console.log(Foo instanceof Function);//true 
 console.log(Foo instanceof Foo);//false

  看了上面的程式碼是不是又暈頭轉向了?為什麼 Object 和 Function instanceof 自己等於 true,而其他類 instanceof 自己卻又不等於 true 呢?如何解釋?要想從根本上了解 instanceof 的奧祕,需要從兩個方面著手:1,語言規範中是如何定義這個運算子的。2,JavaScript 原型繼承機制。

  詳細剖析 ECMAScript-262 edition 3 中 instanceof 運算子的定義

  語言規範對中 instanceof 運算子的定義如下:

  清單 5. 規範中 instanceof 運算子定義

 11.8.6 The instanceof operator 
 The production RelationalExpression: 
	 RelationalExpression instanceof ShiftExpression is evaluated as follows: 

 1. Evaluate RelationalExpression. 
 2. Call GetValue(Result(1)).// 呼叫 GetValue 方法得到 Result(1) 的值,設為 Result(2) 
 3. Evaluate ShiftExpression. 
 4. Call GetValue(Result(3)).// 同理,這裡設為 Result(4) 
 5. If Result(4) is not an object, throw a TypeError exception.// 如果 Result(4) 不是 object,
                                                                //丟擲異常
 /* 如果 Result(4) 沒有 [[HasInstance]] 方法,丟擲異常。規範中的所有 [[...]] 方法或者屬性都是內部的,
在 JavaScript 中不能直接使用。並且規範中說明,只有 Function 物件實現了 [[HasInstance]] 方法。
所以這裡可以簡單的理解為:如果 Result(4) 不是 Function 物件,丟擲異常 */ 
 6. If Result(4) does not have a [[HasInstance]] method, 
   throw a TypeError exception. 
 // 相當於這樣呼叫:Result(4).[[HasInstance]](Result(2)) 
 7. Call the [[HasInstance]] method of Result(4) with parameter Result(2). 
 8. Return Result(7). 

 // 相關的 HasInstance 方法定義
 15.3.5.3 [[HasInstance]] (V) 
 Assume F is a Function object.// 這裡 F 就是上面的 Result(4),V 是 Result(2) 
 When the [[HasInstance]] method of F is called with value V, 
	 the following steps are taken: 
 1. If V is not an object, return false.// 如果 V 不是 object,直接返回 false 
 2. Call the [[Get]] method of F with property name "prototype".// 用 [[Get]] 方法取 
                                                                // F 的 prototype 屬性
 3. Let O be Result(2).//O = F.[[Get]]("prototype") 
 4. If O is not an object, throw a TypeError exception. 
 5. Let V be the value of the [[Prototype]] property of V.//V = V.[[Prototype]] 
 6. If V is null, return false. 
 // 這裡是關鍵,如果 O 和 V 引用的是同一個物件,則返回 true;否則,到 Step 8 返回 Step 5 繼續迴圈
 7. If O and V refer to the same object or if they refer to objects 
   joined to each other (section 13.1.2), return true. 
 8. Go to step 5. 

  上面的規範定義很晦澀,而且看起來比較複雜,涉及到很多概念,但把這段規範翻譯成 JavaScript 程式碼卻很簡單,如下:

  清單 6. JavaScript instanceof 運算子程式碼

 function instance_of(L, R) {//L 表示左表示式,R 表示右表示式
  var O = R.prototype;// 取 R 的顯示原型
  L = L.__proto__;// 取 L 的隱式原型
  while (true) { 
    if (L === null) 
      return false; 
    if (O === L)// 這裡重點:當 O 嚴格等於 L 時,返回 true 
      return true; 
    L = L.__proto__; 
  } 
 } 

  JavaScript 原型繼承機制

  由於本文主要集中在剖析 JavaScript instanceof 運算子,所以對於 JavaScript 的原型繼承機制不再做詳細的講解,下面參考來自 http://www.mollypages.org/misc/js.mp 的一張圖片,此圖片詳細的描述了 JavaScript 各種物件的顯示和隱式原型鏈結構。

  由其本文涉及顯示原型和隱式原型,所以下面對這兩個概念作一下簡單說明。在 JavaScript 原型繼承結構裡面,規範中用 [[Prototype]] 表示物件隱式的原型,在 JavaScript 中用 __proto__ 表示,並且在 Firefox 和 Chrome 瀏覽器中是可以訪問得到這個屬性的,但是 IE 下不行。所有 JavaScript 物件都有 __proto__ 屬性,但只有 Object.prototype.__proto__ 為 null,前提是沒有在 Firefox 或者 Chrome 下修改過這個屬性。這個屬性指向它的原型物件。 至於顯示的原型,在 JavaScript 裡用 prototype 屬性表示,這個是 JavaScript 原型繼承的基礎知識,在這裡就不在敘述了。

圖 1. JavaScript 原型鏈

JavaScript 原型鏈

  講解 instanceof 複雜用法

  有了上面 instanceof 運算子的 JavaScript 程式碼和原型繼承圖,再來理解 instanceof 運算子將易如反掌。下面將詳細講解 Object instanceof Object,Function instanceof Function 和 Foo instanceof Foo 三個示例,其它示例讀者可自行推演。

  清單 7. Object instanceof Object

 // 為了方便表述,首先區分左側表示式和右側表示式
 ObjectL = Object, ObjectR = Object; 
 // 下面根據規範逐步推演
 O = ObjectR.prototype = Object.prototype 
 L = ObjectL.__proto__ = Function.prototype 
 // 第一次判斷
 O != L 
 // 迴圈查詢 L 是否還有 __proto__ 
 L = Function.prototype.__proto__ = Object.prototype 
 // 第二次判斷
 O == L 
 // 返回 true

  清單 8. Function instanceof Function

 // 為了方便表述,首先區分左側表示式和右側表示式
 FunctionL = Function, FunctionR = Function; 
 // 下面根據規範逐步推演
 O = FunctionR.prototype = Function.prototype 
 L = FunctionL.__proto__ = Function.prototype 
 // 第一次判斷
 O == L 
 // 返回 true

  清單 9. Foo instanceof Foo

 // 為了方便表述,首先區分左側表示式和右側表示式
 FooL = Foo, FooR = Foo; 
 // 下面根據規範逐步推演
 O = FooR.prototype = Foo.prototype 
 L = FooL.__proto__ = Function.prototype 
 // 第一次判斷
 O != L 
 // 迴圈再次查詢 L 是否還有 __proto__ 
 L = Function.prototype.__proto__ = Object.prototype 
 // 第二次判斷
 O != L 
 // 再次迴圈查詢 L 是否還有 __proto__ 
 L = Object.prototype.__proto__ = null 
 // 第三次判斷
 L == null 
 // 返回 false

  簡析 instanceof 在 Dojo 繼承機制中的應用

  在 JavaScript 中,是沒有多重繼承這個概念的,就像 Java 一樣。但在 Dojo 中使用 declare 宣告類時,是允許繼承自多個類的。下面以 Dojo 1.6.1 為例。

  清單 10. Dojo 中多重繼承

 dojo.declare("Aoo",null,{}); 
 dojo.declare("Boo",null,{}); 
 dojo.declare("Foo",[Aoo,Boo],{}); 

 var foo = new Foo(); 
 console.log(foo instanceof Aoo);//true 
 console.log(foo instanceof Boo);//false 

 console.log(foo.isInstanceOf(Aoo));//true 
 console.log(foo.isInstanceOf(Boo));//true 

  上面的示例中,Foo 同時繼承自 Aoo 和 Boo,但當使用 instanceof 運算子來檢查 foo 是否是 Boo 的例項時,返回的是 false。實際上,在 Dojo 的內部,Foo 仍然只繼承自 Aoo,而通過 mixin 機制把 Boo 類中的方法和屬性拷貝到 Foo 中,所以當用 instanceof 運算子來檢查是否是 Boo 的例項時,會返回 false。所以 Dojo 為每個類的例項新增了一個新的方法叫 isInstanceOf,用這個方法來檢查多重繼承。

  結束語

  本文詳細介紹了 JavaScript 語言中 instanceof 運算子,並且結合語言規範深入剖析了此操作符的演算法。對讀者使用 JavaScript 編寫複雜的物件導向程式會有很大的幫助。本文所有程式碼在 Firefox 15 下通過測試。

相關文章