JavaScript原型鏈和instanceof運(yùn)算符的曖昧關(guān)系
時(shí)間回到兩個(gè)月前,簡(jiǎn)單地理了理原型鏈、prototype以及__proto__之間的亂七八糟的關(guān)系,同時(shí)也簡(jiǎn)單了解了下typeof和instanceof兩個(gè)運(yùn)算符,但是,anyway,試試以下兩題:
console.log(Function instanceof Function);
console.log(String instanceof String);
如果無(wú)法得出準(zhǔn)確答案,跟著樓主一起溫故而知新吧。
溫故
我們經(jīng)常用typeof運(yùn)算符來(lái)判斷一個(gè)變量的類型,也確實(shí)挺好用,能判斷出number、boolean、string,但是對(duì)于 object的判斷能力一般,比如Array和null的判斷結(jié)果都是object,而且對(duì)于new的一些對(duì)象,比如new String(),new Number()等的結(jié)果都是object,這樣下來(lái)就有個(gè)問(wèn)題,無(wú)法更細(xì)致地判斷對(duì)象實(shí)例,這時(shí)就需要instanceof出馬了。顧名思 義,instanceof是判斷一個(gè)對(duì)象是否為某一構(gòu)造函數(shù)實(shí)例的運(yùn)算符,這樣下來(lái),對(duì)于new出來(lái)的變量,到底是number、string還是別的什 么玩意,就有了進(jìn)一步的判斷認(rèn)識(shí)了。看起來(lái)很簡(jiǎn)單,其實(shí)不然,上面的兩題就是小試牛刀了,先放著,再來(lái)回顧下原型鏈。
JavaScript中一切皆為對(duì)象,所有對(duì)象都有個(gè)__proto__屬性值(即原型),可能某些坑爹瀏覽器下不支持,暫且不管,某對(duì)象的該 屬性的取值是該對(duì)象的構(gòu)造函數(shù)的prototype值,作用就是對(duì)于某一個(gè)類型的對(duì)象,不用重復(fù)定義某種方法,譬如說(shuō)對(duì)于對(duì)象[1,2,3],顯然這是個(gè) 數(shù)組對(duì)象,它具有pop、push等方法,但是并不是說(shuō)它本身就具有這些方法,而是它的構(gòu)造函數(shù)Array函數(shù)所具有,而該對(duì)象的__proto__取值 就是Array函數(shù)的prototype值,so如果本身并不具有pop方法,就會(huì)從它的__proto__中尋找,即所謂的原型鏈;而該條鏈的末端就是 Object,因?yàn)橐磺袑?duì)象都是由Object構(gòu)造而成,而Object.prototype.__proto__規(guī)定指向null。
文字的描述永遠(yuǎn)是蒼白無(wú)力的,舉個(gè)簡(jiǎn)單的例子:
- var fun = function(){
- this.a = 1;
- };
- fun.prototype.b = 2;
- var obj = new fun();
- obj.a; //1
- obj.b; //2
網(wǎng)上盜的例子和圖,仔細(xì)看就會(huì)發(fā)現(xiàn)說(shuō)的很清楚。
知新
接下來(lái)看看instanceof運(yùn)算符。
instanceof的常規(guī)用法是判斷a是否是b類型:
- console.log(true instanceof Boolean); // false
- console.log(new Number(1) instanceof Number); // true
instanceof還能判斷父類型:
- function Father() {} function Child() {} Child.prototype = new Father(); var a = new Child(); console.log(a instanceof Child); // true
- console.log(a instanceof Father); // true
Child構(gòu)造函數(shù)繼承自Father,實(shí)例a是Child構(gòu)造的無(wú)疑,但是為何也是Father的實(shí)例呢?其實(shí)instanceof運(yùn)算符的內(nèi)核可以簡(jiǎn)單地用以下代碼描述:
- function check(a, b) { while(a.__proto__) { if(a.__proto__ === b.prototype) return true; a = a.__proto__; } return false; } function Foo() {} console.log(Object instanceof Object === check(Object, Object)); // true
- console.log(Function instanceof Function === check(Function, Function)); // true
- console.log(Number instanceof Number === check(Number, Number)); // true
- console.log(String instanceof String === check(String, String)); // true
- console.log(Function instanceof Object === check(Function, Object)); // true
- console.log(Foo instanceof Function === check(Foo, Function)); // true
- console.log(Foo instanceof Foo === check(Foo, Foo)); // true
簡(jiǎn)單地說(shuō),a如果是b的實(shí)例,那么a肯定能使用b的prototype中定義的方法和屬性,那么用代碼表示就是a的原型鏈中有b.prototype取值相同的對(duì)象,于是順著a的原型鏈一層層找就行了。
另外值得注意的是,String Number Boolean 以及Function等都是函數(shù),而函數(shù)則是統(tǒng)一由Function構(gòu)造而來(lái)的,so它們和任何單純的函數(shù)一樣,能用Function上的原型屬性:
- Function.prototype.a = 10; console.log(String.a); // 10
***來(lái)簡(jiǎn)單講講最開(kāi)始的兩道題吧。
- // 為了方便表述,首先區(qū)分左側(cè)表達(dá)式和右側(cè)表達(dá)式
- FunctionL = Function, FunctionR = Function; // 下面根據(jù)規(guī)范逐步推演
- O = FunctionR.prototype = Function.prototype L = FunctionL.__proto__ = Function.prototype // ***次判斷
- O == L // 返回 true
- // 為了方便表述,首先區(qū)分左側(cè)表達(dá)式和右側(cè)表達(dá)式
- StringL = String, StringR = String; // 下面根據(jù)規(guī)范逐步推演
- O = StringR.prototype = String.prototype L = StringL.__proto__ = Function.prototype // ***次判斷
- O != L // 循環(huán)再次查找 L 是否還有 __proto__
- L = String.prototype.__proto__ = Object.prototype // 第二次判斷
- O != L // 再次循環(huán)查找 L 是否還有 __proto__
- L = String.prototype.__proto__ = null
- // 第三次判斷
- L == null
- // 返回 false