JS
系列暫定 27 篇,從基礎,到原型,到異步,到設計模式,到架構模式等,javascript
本篇是JS
系列中第 4 篇,文章主講 JS instanceof
,包括 instanceof
做用、內部實現機制,以及 instanceof
與 typeof
、Symbol.hasInstance
、isPrototype
、Object.prototype.toString
、[[Class]]
等的對比使用 ,深刻了解 JS instanceof
。html
在 JS 中,判斷一個變量的類型,經常會用到 typeof
運算符,但當用 typeof
來判斷引用類型變量時,不管是什麼類型的變量,它都會返回 Object
。前端
// 基本類型
console.log(typeof 100); // number
console.log(typeof 'bottle'); // string
console.log(typeof true); // boolean
// 引用類型
console.log(typeof {}); // object
console.log(typeof [1, 2, 3]); // object
複製代碼
爲此,引入了instanceof
。java
instanceof
操做符用於檢測對象是否屬於某個 class,同時,檢測過程當中也會將繼承關係考慮在內。git
// 類
class Bottle {}
// bottle 是 Bottle 類的實例對象
let bottle = new Bottle();
console.log(bottle instanceof Bottle); // true
// 也能夠是構造函數,而非 class
function AnGe() {}
let an = new AnGe();
console.log(an instanceof AnGe); // true
複製代碼
instanceof
與 typeof
相比,instanceof
方法要求開發者明確的確認對象爲某特定類型。即 instanceof
用於判斷引用類型屬於哪一個構造函數的方法。github
var arr = []
arr instanceof Array // true
typeof arr // "object"
// typeof 是沒法判斷類型是否爲數組的
複製代碼
另外,更重的一點是 instanceof
能夠在繼承關係中用來判斷一個實例是否屬於它的父類型。web
// 判斷 f 是不是 Foo 類的實例 , 而且是不是其父類型的實例
function Aoo(){}
function Foo(){}
//JavaScript 原型繼承
Foo.prototype = new Aoo();
var foo = new Foo();
console.log(foo instanceof Foo) // true
console.log(foo instanceof Aoo) // true
複製代碼
f instanceof Foo
的判斷邏輯是:windows
__proto__
一層一層往上,是否對應到 Foo.prototype
Aoo.prototype
f instanceof Object
即 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(Array instanceof Array) // false
console.log(Function instanceof Object) //true
console.log(Foo instanceof Function) //true
console.log(Foo instanceof Foo) //false
複製代碼
在這組數據中,Object、Function instanceof 本身均爲 true
, 其餘的 instanceof 本身都爲 false
,這就要從 instanceof
的內部實現機制以及 JS 原型繼承機制講起。
instanceof 的內部實現機制是:經過判斷對象的原型鏈上是否能找到對象的 prototype
,來肯定 instanceof
返回值
// instanceof 的內部實現
function instance_of(L, R) {//L 表左表達式,R 表示右表達式,即L爲變量,R爲類型
// 取 R 的顯示原型
var prototype = R.prototype
// 取 L 的隱式原型
L = L.__proto__
// 判斷對象(L)的類型是否嚴格等於類型(R)的顯式原型
while (true) {
if (L === null) {
return false
}
// 這裏重點:當 prototype 嚴格等於 L 時,返回 true
if (prototype === L) {
return true
}
L = L.__proto__
}
}
複製代碼
instanceof
運算符用來檢測 constructor.prototype
是否存在於參數 object
的原型鏈上。
看下面一個例子,instanceof
爲何會返回 true
?很顯然,an
並非經過 Bottle()
建立的。
function An() {}
function Bottle() {}
An.prototype = Bottle.prototype = {};
let an = new An();
console.log(an instanceof Bottle); // true
複製代碼
這是由於 instanceof
關心的並非構造函數,而是原型鏈。
an.__proto__ === An.prototype; // true
An.prototype === Bottle.prototype; // true
// 即
an.__proto__ === Bottle.prototype; // true
複製代碼
即有 an.__proto__ === Bottle.prototype
成立,因此 an instanceof Bottle
返回了 true
。
因此,按照 instanceof
的邏輯,真正決定類型的是 prototype
,而不是構造函數。
圖片來自於 JS原型鏈
由其本文涉及顯示原型 prototype
和隱式原型 __proto__
,因此下面對這兩個概念做一下簡單說明。
在 JavaScript 原型繼承結構裏面,規範中用 [Prototype]]
表示對象隱式的原型,在 JavaScript 中用 __proto__
表示,而且在 Firefox 和 Chrome 瀏覽器中是能夠訪問獲得這個屬性的,可是 IE 下不行。全部 JavaScript 對象都有 __proto__
屬性,但只有 Object.prototype.__proto__
爲 null,前提是沒有在 Firefox 或者 Chrome 下修改過這個屬性。這個屬性指向它的原型對象。 至於顯示的原型,在 JavaScript 裏用 prototype
屬性表示,這個是 JavaScript 原型繼承的基礎知識,若是想進一步瞭解,請參考 JS 基礎之: 深刻 constructor、prototype、__proto__
、[[Prototype]] 及 原型鏈
下面介紹幾個例子(及其推演過程),加深你的理解:
// 爲了方便表述,首先區分左側表達式和右側表達式
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
複製代碼
// 爲了方便表述,首先區分左側表達式和右側表達式
FunctionL = Function, FunctionR = Function;
// 下面根據規範逐步推演
O = FunctionR.prototype = Function.prototype
L = FunctionL.__proto__ = Function.prototype
// 第一次判斷
O === L
// 返回 true
複製代碼
// 爲了方便表述,首先區分左側表達式和右側表達式
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
複製代碼
Symbol.hasInstance 用於判斷某對象是否爲某構造器的實例。所以你能夠用它自定義 instanceof
操做符在某個類上的行爲。
你可實現一個自定義的instanceof
行爲,例如:
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof MyArray); // true
複製代碼
isPrototypeOf
也是用來判斷一個對象是否存在與另外一個對象的原型鏈上。
// 判斷 f 是不是 Foo 類的實例 ,
// 而且是不是其父類型的實例
function Aoo(){}
function Foo(){}
// JavaScript 原型繼承
Foo.prototype = new Aoo();
var foo = new Foo();
console.log(Foo.prototype.isPrototypeOf(foo)) //true
console.log(Aoo.prototype.isPrototypeOf(foo)) //true
複製代碼
須要注意的是:
instanceof
:foo
的原型鏈是針對 Foo.prototype
進行檢查的isPrototypeOf
:foo
的原型鏈是針對 Foo
自己instanceof 在多個全局做用域下,判斷會有問題,例如:
// parent.html
<iframe src="child.html" onload="test()">
</iframe>
<script>
function test(){
var value = window.frames[0].v;
console.log(value instanceof Array); // false
}
</script>
複製代碼
// child.html
<script>
window.name = 'child';
var v = [];
</script>
複製代碼
嚴格上來講 value 就是數組,但 parent 頁面中打印輸出: false ;
這是由於 Array.prototype !== window.frames[0].Array.prototype ,而且數組從前者繼承。
出現問題主要是在瀏覽器中,當咱們的腳本開始開始處理多個 frame 或 windows 或在多個窗口之間進行交互。多個窗口意味着多個全局環境,不一樣的全局環境擁有不一樣的全局對象,從而擁有不一樣的內置類型構造函數。
能夠經過使用
Array.isArray(myObj)
或者
Object.prototype.toString.call(myObj) === "[object Array]"
來安全的檢測傳過來的對象是不是一個數組
默認狀況下(不覆蓋 toString
方法前提下),任何一個對象調用 Object
原生的 toString
方法都會返回 "[object type]"
,其中 type
是對象的類型;
let obj = {};
console.log(obj); // {}
console.log(obj.toString()); // "[object Object]"
複製代碼
每一個實例都有一個 [[Class]]
屬性,這個屬性中就指定了上述字符串中的 type
(構造函數名)。 [[Class]]
不能直接地被訪問,但一般能夠間接地經過在這個值上借用默認的 Object.prototype.toString.call(..)
方法調用來展現。
Object.prototype.toString.call("abc"); // "[object String]"
Object.prototype.toString.call(100); // "[object Number]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call([1,2,3]); // "[object Array]"
Object.prototype.toString.call(/\w/); // "[object RegExp]"
複製代碼
Object.prototype.toString.call(..)
檢測對象類型能夠經過 Object.prototype.toString.call(..)
來獲取每一個對象的類型。
function isFunction(value) {
return Object.prototype.toString.call(value) === "[object Function]"
}
function isDate(value) {
return Object.prototype.toString.call(value) === "[object Date]"
}
function isRegExp(value) {
return Object.prototype.toString.call(value) === "[object RegExp]"
}
isDate(new Date()); // true
isRegExp(/\w/); // true
isFunction(function(){}); //true
複製代碼
或者可寫爲:
function generator(type){
return function(value){
return Object.prototype.toString.call(value) === "[object "+ type +"]"
}
}
let isFunction = generator('Function')
let isArray = generator('Array');
let isDate = generator('Date');
let isRegExp = generator('RegExp');
isArray([])); // true
isDate(new Date()); // true
isRegExp(/\w/); // true
isFunction(function(){}); //true
複製代碼
Object.prototype.toString
方法可使用 Symbol.toStringTag
這個特殊的對象屬性進行自定義輸出。
舉例說明:
let bottle = {
[Symbol.toStringTag]: "Bottle"
};
console.log(Object.prototype.toString.call(bottle)); // [object Bottle]
複製代碼
大部分和環境相關的對象也有這個屬性。如下輸出可能因瀏覽器不一樣而異:
// 環境相關對象和類的 toStringTag:
console.log(window[Symbol.toStringTag]); // Window
console.log(XMLHttpRequest.prototype[Symbol.toStringTag]); // XMLHttpRequest
console.log(Object.prototype.toString.call(window)); // [object Window]
console.log(Object.prototype.toString.call(new XMLHttpRequest())); // [object XMLHttpRequest]
複製代碼
輸出結果和 Symbol.toStringTag
(前提是這個屬性存在)同樣,只不過被包裹進了 [object ...]
裏。
因此,若是但願以字符串的形式獲取內置對象類型信息,而不只僅只是檢測類型的話,能夠用這個方法來替代 instanceof
。
適用於 | 返回 | |
---|---|---|
typeof | 基本數據類型 | string |
instanceof | 任意對象 | true/false |
Object.prototype.toString |
基本數據類型、內置對象以及包含 Symbol.toStringTag 屬性的對象 |
string |
Object.prototype.toString
基本上就是一加強版 typeof
。
instanceof
在涉及多層類結構的場合中比較實用,這種狀況下須要將類的繼承關係考慮在內。
想看更過系列文章,點擊前往 github 博客主頁
1. ❤️玩得開心,不斷學習,並始終保持編碼。👨💻
2. 若有任何問題或更獨特的看法,歡迎評論或直接聯繫瓶子君(公衆號回覆 123 便可)!👀👇
3. 👇歡迎關注:前端瓶子君,每日更新!👇