本文由segementfalt上的一道instanceof題引出: var str = new String("hello world"); console.log(str instanceof String);//true console.log(String instanceof Functio ...
本文由segementfalt上的一道instanceof題引出:
var str = new String("hello world");
console.log(str instanceof String);//true
console.log(String instanceof Function);//true
console.log(str instanceof Function);//false
先拋開這道題,我們都知道在JS中typeof可以檢測變數的基本數據類型。
let s = "abcd";
let b = true;
let i = 22;
let u;
let n = null;
let o = new Object();
alert(typeof s); //string
alert(typeof b); //boolean
alert(typeof i); //number
alert(typeof u); //undefined
alert(typeof n); //object
alert(typeof o); //object
從上面的信息可以看到,如果我們想知道某個值是什麼類型的對象時,typeof就無能為力了!ECMAScript引入了instanceof來解決這個問題。instanceof用來判斷某個構造函數的prototype是否在要檢測對象的原型鏈上。
function Fn(){};
var fn = new Fn();
console.log(fn instanceof Fn) //true
//判斷fn是否為Fn的實例,並且是否為其父元素的實例
function Aoo();
function Foo();
Foo.prototype = new Aoo();
let foo = new Foo();
console.log(foo instanceof Foo); //true
console.log(foo instanceof Aoo); //true
//instanceof 的複雜用法
console.log(Object instanceof Object) //true
console.log(Function instanceof Function) //true
console.log(Number instanceof Number) //false
console.log(Function instaceof Function) //true
console.log(Foo instanceof Foo) //false
看到上面的代碼,你大概會有很多疑問吧。有人將ECMAScript-262 edition 3中對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__;
}
}
我們知道每個對象都有proto([[prototype]])屬性,在js代碼中用**__proto__**來表示,它是對象的隱式屬性,在實例化的時候,會指向prototype所指的對象;對象是沒有prototype屬性的,prototype則是屬於構造函數的屬性。通過proto屬性的串聯構建了一個對象的原型訪問鏈,起點為一個具體的對象,終點在Object.prototype。
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
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
現在我們回到開始的題目。
對於第一條判斷:
console.log(str.__proto__ === String.prototype); //true
console.log(str instanceof String);//true
第二條:
console.log(String.__proto__ === Function.prototype) //true
console.log(String instanceof Function);//true
第三條:
console.log(str__proto__ === String.prototype)//true
console.log(str__proto__.__proto__. === Function.prototype) //true
console.log(str__proto__.__proto__.__proto__ === Object.prototype) //true
console.log(str__proto__.__proto__.__proto__.__proto__ === null) //true
console.log(str instanceof Function);//false
總結以上:
str的原型鏈:
str ---> String.prototype ---> Object.prototype
String原型鏈:
String ---> Function.prototype ---> Object.prototype
由上面的討論我們可以得知對於基本類型的判斷我們可以使用typeof,而對於引用類型判斷要用到instanceof,instanceof無法對原始類型進行判斷。當然用constructor也可以來判斷數據類型,但是要註意的是其值是可以被人為修改的,我們用另外一道面試題來談論constuctor:
var A = function() {};
A.prototype = {};
var B = {};
console.log(A.constructor);//Function
console.log(B.constructor);//Object
var a = new A();
A.prototype = {};
var b = new A();
b.constructor = A.constructor;
console.log(a.constructor == A);//false
console.log(a.constructor == b.constructor);//false
console.log(a instanceof A);//false
console.log(b instanceof A);//true
在JS高程設計裡面,作者是這樣寫的:無論什麼時候,只要創建一個新函數,就會為該函數創建一個prototype屬性,這個屬性指向函數原型對象。在預設情況下,所有的原型都會自動獲得一個constructor屬性,這個屬性指向prototype所在函數的指針。
我們先來看下麵這個例子:
function Foo() {
// ...
}
Foo.prototype.constructor === Foo; // true
var a = new Foo();
a.constructor === Foo; // true
a本身並沒有constructor屬性,雖然a.constructor確實指向Foo,但是這個屬性並不是a由Foo“構造”的。實際上,.constructor 引用同樣被委托給了Foo.prototype,而Foo.prototype.constructor 預設指向Foo。思考以下代碼便知:
function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; // 創建一個新原型對象
var a1 = new Foo();
a1.constructor === Foo; // false!
a1.constructor === Object; // true!
我們回到拋出的第二道面試題:
var A = function() {};
A.prototype = {}; //此時對構造函數對象A的prototype屬性重新複製,constructor屬性不見了
var B = {};
console.log(A.constructor);//Function 函數的構造函數為 function Function()
console.log(B.constructor);//Object 普通object的構造函數為 function Object()
var a = new A(); new一個新對象
A.prototype = {};
var b = new A();
b.constructor = A.constructor;
console.log(a.constructor == A);//false a.constructor 為 Object
console.log(a.constructor == b.constructor);//false b.constructor 為 Function
console.log(a instanceof A);//false
console.log(b instanceof A);//true
上面的三種方式都有弊端,接下來引出我們的Object.prototype.toString.call()
Object.prototype.toString()可以通用的來判斷原始數據類型和引用數據類型。
console.log(Object.prototype.toString.call(123)) //[object Number]
console.log(Object.prototype.toString.call('123')) //[object String]
console.log(Object.prototype.toString.call(undefined)) //[object Undefined]
console.log(Object.prototype.toString.call(true)) //[object Boolean]
console.log(Object.prototype.toString.call({})) //[object Object]
console.log(Object.prototype.toString.call([])) //[object Array]
console.log(Object.prototype.toString.call(function(){})) //[object Function]
參考: