寫給Android/Java開發者的JavaScript精解(2)
Java和JavaScript最不一樣的地方是什么?我覺得是函數!在Java中,函數(也稱為方法)是對象的一部分,一般是通過對象調用函數。在JavaScript中,函數已經和對象平起平坐,函數是獨立于對象的,甚至可以用來創建對象。因此,從Java轉學JavaScript時,應時刻注意不要將Java中對象和函數的概念生搬硬套到JavaScript中,這是學好JavaScript的關鍵。
五、在JavaScript中,對象能繼承嗎?
在Java中,繼承通常指的是類之間、接口之間的一種關系,所謂A類繼承B類,是指A類中可以直接使用B類中的非private的字段和方法,就像A類中本來就有這些字段和方法一樣。
在JavaScript中,沒有對應于Java的類、接口的概念,但是卻有繼承的概念。只是這里的繼承指的是對象之間的一種關系。對象A繼承對象B,指的是對象A可以直接使用對象B中的屬性,就想對象A本身就有這些屬性一樣。
1、對象繼承的實現原理。
由于JavaScript中的對象只有鍵值對,所以實現對象之間的繼承很自然地也要使用這一特性。
在任意一個JavaScript對象a中,都有一個屬性叫做"__proto__"(注意:這里proto兩側各是兩條短下劃線),這個屬性的值是另一個對象b,它被稱為對象A的原型(即proto一詞的含義)。此時,對象a可以將對象b中的屬性當作自己的來使用。舉例如下:
var b ={ prop_of_b : "property of b" }; var a = {__proto__ : b } ;
上面代碼中,我們將對象a的原型設置為對象b,此時我們可以這樣使用:
a.prop_of_b //值為 property of b
對象a的原型為對象b,也可以稱為對象a繼承對象b,只是這種繼承不再是Java中的繼承了,它叫做基于原型的繼承(我們把它簡稱為原型繼承)。
2、原型繼承中的屬性屏蔽
好的,現在問題來了,假如我們在a中定義一個與對象b中同名的屬性會怎樣呢?如下所示:
a.prop_of_b = ' own property of a' ;
此時,如果在程序中再訪問a.prop_of_b,它的值是什么呢?答案是:own property of a。
這里,要引入一個新的概念,叫做對象的自有屬性(own property),簡單講,它指的是那些對象自己擁有的,不是繼承自原型的屬性。
當我們使用圓點語法(object.prop)去獲取一個對象的屬性值時,JavaScript引擎首先在對象的自有屬性中查找,一旦找到,就直接返回該自有屬性的值,只有當自有屬性中沒有要訪問的屬性時,才會去對象的原型中查找它的自有屬性。
回到上面的例子,a已經有了一個自有屬性prop_of_b,所以訪問a.prop_of_b時就直接返回了該自有屬性的值 ' own property of a',而沒有去它的原型b中查找屬性prop_of_b。
所以,當對象的自有屬性與原型對象中的屬性同名時,會屏蔽原型對象中的同名屬性。這種特性很像Java中的覆寫(override)。
3、原型繼承中的原型鏈
進一步思考,我們可能會問,假如我們訪問的對象屬性既不是對象的自有屬性,也不是原型對象的自有屬性,JavaScript會怎么處理我們的訪問請求呢?比如我這樣寫:
a.notExistProperty 。
這里,又要引入一個新的概念:原型鏈!
很簡單,對象a的原型是b,對象b也會有一個原型c,對象c還會有一個原型d,對象d還會有......
這很自然地形成了一個原型鏈,鏈的盡頭是什么?答案是一個很特殊很重要的對象:Object.prototype。
原型鏈盡頭的對象,就是Object的屬性prototype所指向的對象。我們在上篇文章開頭已經用到了Object對象,我們用它來創建了一個新的對象(new Object(),記得吧?),現在,我們對它有了新的認識,它的prototype屬性所指向的對象是所有對象原型鏈的最后一站。
如果你非要問對象Object.prototype難道沒有__proto__屬性?如果有,這個屬性值是什么?
答案是:有!但它的值是null。好了,你好奇心是不是得到了極大滿足?
明白了原型鏈,我們再來看JavaScript引擎如何處理
a.notExistProperty
首先,引擎查找a的自有屬性,沒找到notExistProperty,
然后查找其原型b的自有屬性,也沒有找到notExistProperty,
然后查找b的原型的自有屬性......
就這樣一級一級往上找,
最后找到了Object.prototype,發現它也沒有屬性notExistProperty,于是JavaScript返回值 undefined,表示這個屬性未定義。
4、使用原型繼承的一些便利方法
對象Object和對象Object.prototype中有一些方法,為大家使用原型帶來了很大便利。如Object.prototype有一個方法hasOwnProperty可以用來判斷一個屬性是不是對象的自有屬性。上例中,我們給b增加一個新的屬性:
b.prop1 = 'prop1' ;
雖然,我們可以通過a.prop1來獲取'prop1',但我們可以判斷出來prop1并不是a的自有屬性,方法如下:
a.hasOwnProperty('prop1') ; //值為 false
注:使用這個方法時,屬性字符串必須加上單引號或雙引號
這里有一個問題,為什么我們可以直接在對象a上調用方法hasOwnProperty呢?這個方法不是Object.prototype所有的嗎?
原因是這樣的:a的原型是對象b,b的原型是Object.prototype,a繼承了這條原型鏈上游所有對象的所有屬性和方法,自然也包括hasOwnProperty方法。
可是,我們并沒有指定對象b的原型為Object.prototype啊?
答案是:當我們用大括號語法({})創建對象時,如果沒有明確指定該對象的原型,那么JavaScript會自動把它的原型設為Object.prototype。
Object為我們使用原型提供了什么好的方法?
-
Object.create( ):方便我們創建對象的同時指定它的原型
如 var b = { propB : 'propB' } ;
var a = Object.create(b);
以上,對象a的原型將是對象b。 -
Object.getPrototypeOf():方便我們獲取一個對象的原型對象。
Object.getPrototypeOf(a) //值為對象b -
Object.setPrototypeOf():方便我們為一個對象設置原型,使用與上類似。
實際上,便利方法還有很多,ES6中甚至引入了class、extends、super、static、constructor等關鍵字,將原型繼承封裝得很像面向對像語言中的類繼承。這個內容在下一個問題:創建對象的方法有多少中會詳細講,敬請期待。
來自:http://www.jianshu.com/p/1a0ae94fd592