在 JavaScript 中,判斷一個變量的類型嚐嚐會用 typeof 運算符,在使用 typeof 運算符時採用引用類型存儲值會出現一個問題,不管引用的是什麼類型的對象,它都返回 "object"。ECMAScript 引入了另外一個 Java 運算符 instanceof 來解決這個問題。instanceof 運算符與 typeof 運算符類似,用於識別正在處理的對象的類型。與 typeof 方法不一樣的是,instanceof 方法要求開發者明確地確認對象爲某特定類型。例如:瀏覽器
1
2
|
var oStringObject = new String("hello world");
console.log(oStringObject instanceof String); // 輸出 "true"
|
這段代碼問的是「變量 oStringObject 是否爲 String 對象的實例?」oStringObject 的確是 String 對象的實例,所以結果是"true"。儘管不像 typeof 方法那樣靈活,可是在 typeof 方法返回 "object" 的狀況下,instanceof 方法仍是頗有用的。lua
一般來說,使用 instanceof 就是判斷一個實例是否屬於某種類型。例如:prototype
1
2
3
4
|
// 判斷 foo 是不是 Foo 類的實例
function Foo(){}
var foo = new Foo();
console.log(foo instanceof Foo)//true
|
另外,更重的一點是 instanceof 能夠在繼承關係中用來判斷一個實例是否屬於它的父類型。例如:翻譯
1
2
3
4
5
6
7
8
|
// 判斷 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 運算符一樣適用。code
看了上面的代碼示例,是否是以爲 instanceof 操做符很簡單,下面來看點複雜的用法。cdn
1
2
3
4
5
6
7
8
9
|
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 原型繼承機制。對象
語言規範對中 instanceof 運算符的定義以下:blog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
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 代碼卻很簡單,以下:繼承
1
2
3
4
5
6
7
8
9
10
11
|
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 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 原型繼承的基礎知識,在這裏就不在敘述了。
有了上面 instanceof 運算符的 JavaScript 代碼和原型繼承圖,再來理解 instanceof 運算符將易如反掌。下面將詳細講解 Object instanceof Object,Function instanceof Function 和 Foo instanceof Foo 三個示例,其它示例讀者可自行推演。
1
2
3
4
5
6
7
8
9
10
11
12
|
// 爲了方便表述,首先區分左側表達式和右側表達式
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
|
1
2
3
4
5
6
7
8
|
// 爲了方便表述,首先區分左側表達式和右側表達式
FunctionL = Function, FunctionR = Function;
// 下面根據規範逐步推演
O = FunctionR.prototype = Function.prototype
L = FunctionL.__proto__ = Function.prototype
// 第一次判斷
O == L
// 返回 true
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 爲了方便表述,首先區分左側表達式和右側表達式
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
|
在 JavaScript 中,是沒有多重繼承這個概念的,就像 Java 同樣。但在 Dojo 中使用 declare 聲明類時,是容許繼承自多個類的。下面以 Dojo 1.6.1 爲例。
1
2
3
4
5
6
7
8
9
10
|
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,用這個方法來檢查多重繼承。