Object是構造函數,而Object.prototype是構造函數的原型對象。構造函數自身的屬性和方法無法被共享,而原型對象的屬性和方法可以被所有實例對象所共享。
首先,我們知道,構造函數是生成對象的模板,一個構造函數可以生成多個對象,每個對象都有相同的結構。構造函數的缺點就是,每當你實例化兩個對象時,需要調用兩次構造函數的某一個方法,這帶來的壞處就是占用內存,而且沒必要。
其次,為了解決構造函數的屬性和方法無法被對象實例所共享的問題,我們可以把需要共享的屬性和方法放在原型(prototype)對象上。原型對象上的所有屬性和方法,都會被對象實例所共享。對於構造函數來說,prototype是作為構造函數的屬性;對於對象實例來說,prototype是對象實例的原型對象。所以prototype即是屬性,又是對象。
然后,除了undefined和null之外,每一個數據類型都可以看成一個對象,每一個對象都有它的原型。所有一切對象的原型頂端,都是Object.prototype,即Object構造函數的prototype屬性指向的那個對象。當然,Object.prototype對象也有自己的原型對象,那就是沒有任何屬性和方法的null對象,而null對象沒有自己的原型。
原型鏈的特點有:
a:讀取對象的某個屬性時,JavaScript引擎先尋找對象本身的屬性,如果找不到,就到它的原型去找,如果還是找不到,就到原型的原型去找。如果直到最頂層的Object.prototype
還是找不到,則返回undefined
。
b:如果對象自身和它的原型,都定義了一個同名屬性,那么優先讀取對象自身的屬性,這叫做“覆蓋”(overiding)。
c:一級級向上在原型鏈尋找某個屬性,對性能是有影響的。所尋找的屬性在越上層的原型對象,對性能的影響越大。如果尋找某個不存在的屬性,將會遍歷整個原型鏈。
再次,constructor屬性是原型對象上的一個屬性,可以被所有實例對象所共享。要注意的是,prototype是構造函數的屬性,而constructor則是構造函數的prototype屬性所指向的那個對象,也就是原型對象的屬性。由於constructor屬性是一種原型對象和構造函數的關系,所以在修改原型對象的時候,一定要注意constructor的指向問題。
最后,instanceof運算符返回一個布爾值,用於判斷對象是否為某個構造函數的實例。
在接下來的分享中,會談談Object的部分方法和Object.prototoype的部分方法。雖然都是概念性問題,但是如果理解了這些概念,對於MVVM框架和各種js框架的理解都有相當大的幫助。
以下的分享會分為如下內容:
1.Object和Object.prototype的區別
2.Object.getPrototypeOf()
3.Object.setPrototypeOf()
4.Object.create()
5.Object.prototype.isPrototypeOf()
6.Object.prototype.__proto__
1.Object和Object.prototype的區別
個人認為,要學好javascript的其中一個方法就是,必須理解每一個" . "所代表的意思是什么,是調用自身的屬性和方法呢,還是繼承原型的對象的屬性和方法。來看看Object構造函數和構造函數的原型Object.prototype都有哪些屬性和方法。
Object是構造函數,而Object.prototype是構造函數的原型對象。構造函數自身的屬性和方法無法被共享,而原型對象的屬性和方法可以被所有實例對象所共享。
Object的屬性和方法:
Object.prototype的屬性和方法:
上面例子中,Object擁有自己的方法prototype,getPrototypeOf(),setPrototypeOf()等,這些方法無法被實例所共享。而Object.prototypeOf()的hasOwnProperty,isPrototypeOf(),constructor等屬性和方法是可以被實例對象所共享的。舉一個最簡單的例子。
1 function Keith() {} 2 var a = new Keith(); 3 console.log(a.prototype); //undefined 4 console.log(a.constructor); //Keith()
上面代碼中,構造函數Keith是沒有任何屬性和方法的。當訪問prototype屬性時返回undefined,是因為prototype屬性沒有辦法從構造函數中繼承,只能由構造函數本身訪問。而constructor返回了Keith(),因為constructor屬性本身就是Object.prototype中的屬性,可以被所有實例對象所共享。
那么問題來了,如何知道實例對象的原型呢?可以通過Object.isPrototypeOf方法和繼承原型對象的isPrototypeOf方法實現。
1 console.log(Keith.prototype.isPrototypeOf(a)); //true 2 console.log(Object.getPrototypeOf(a) === Keith.prototype) //true
上面代碼中,實例對象a的原型就是Keith.prototype。這兩個屬性會稍后介紹。
2.Object.getPrototypeOf()
Object.getPrototypeOf
方法返回一個對象的原型。這是獲取原型對象的標准方法。
1 // 空對象的原型是Object.prototype 2 console.log(Object.getPrototypeOf({}) === Object.prototype) // true 3 4 // 函數的原型Function.prototype 5 function keith() {} 6 console.log(Object.getPrototypeOf(keith) === Function.prototype) //true 7 8 // 數組的原型Array.prototype 9 var arr = [1,2,3]; 10 console.log(Object.getPrototypeOf(arr) === Array.prototype) ///true
3.Object.setPrototypeOf()
Object.setPrototypeOf方法可以為現有對象設置原型,然后返回一個新對象。這個可以接收兩個參數,第一個是現有對象,第二個是原型對象。
1 var keith = { 2 height: 180 3 }; 4 var rascal = Object.setPrototypeOf({}, keith); 5 console.log(rascal.height); //180 6 7 //上下兩個代碼片段相同。 8 var keith = { 9 height: 180 10 }; 11 var rascal ={ 12 __proto__: keith 13 }; 14 console.log(rascal.height); //180
上面代碼中,rascal對象是Object.setPrototypeOf
方法返回的一個新對象。該對象本身為空、原型為keith對象,所以rascal對象可以拿到keith對象的所有屬性和方法。rascal對象本身並沒有height屬性,但是JavaScript引擎找到它的原型對象keith,然后讀取keith的height屬性。
4.Object.create()
Object.create方法用於從原型對象生成新的對象實例,可以代替new命令。它接受一個參數,這個參數為所要繼承的原型對象,然后返回一個實例對象。
1 var Keith = { 2 hobby : function() { 3 return 'Watching Movies'; 4 } 5 }; 6 7 var rascal = Object.create(Keith); 8 console.log(rascal.hobby()) //'Watching Movies'
上面代碼中,Object.create方法將Keith對象作為rascal的原型對象,此時rascal就繼承了Keith對象中的所有屬性和方法。rascal就成為了Keith對象的實例對象。用下面這段代碼比較好理解。
1 function Keith() {}; 2 Keith.prototype.hobby = function() { 3 return 'Watching Movies'; 4 } 5 6 var rascal = Object.create(Keith); 7 console.log(rascal.hobby()) //'Watching Movies';
new操作符和Object.create方法都是返回一個對象實例,但是兩者有一些區別。
1 function Keith() {} 2 var a = new Keith(); 3 var b = Object.create(Keith.prototype); 4 5 console.log(a instanceof Keith); //true 6 console.log(b instanceof Keith); //true
上面代碼中,可以使用new操作符來調用構造函數,返回對象實例;而Object.create傳入的參數必須是構造函數Keith的原型。
實際上,如果有老式瀏覽器不支持Object.create方法,可以用下面這段代碼來構造一個Object.create方法。
1 if (typeof Object.create !=='function') { 2 Object.create = function(x) { 3 function F() {}; 4 F.prototype = x; 5 return new F(); 6 }; 7 }
下面這三種方式生成的實例對象都是等價的。
1 var o1 = Object.create({}); 2 var o2 = Object.create(Object.prototype); 3 var o2 = new Object();
在使用Object.create方法時,要注意的是必須傳入原型對象,否則會報錯。
1 var o1 = Object.create(); 2 console.log(o1);//TypeError: Object.create requires more than 0 arguments
Object.create方法生成的對象實例,動態繼承了原型對象。也就是說,修改原型對象的屬性和方法會反應在對象實例上。
1 var keith = { 2 height:180 3 }; 4 5 var rascal = Object.create(keith); 6 keith.height=153; 7 console.log(rascal.height) //153
上面代碼中,修改原型對象,會影響生成的對象實例。
Object.create方法生成的對象,繼承了它的原型對象的構造函數。
1 function Keith() {}; 2 var boy = new Keith(); 3 var girl = Object.create(boy); 4 console.log(Object.getPrototypeOf(girl) === boy); //true 5 console.log(girl.constructor === Keith); //true 6 console.log(girl instanceof Keith); //true
上面代碼中,girl對象的原型是boy對象,girl對象的constructor屬性指向了原型對象boy的構造函數Keith。
5.Object.prototype.isPrototypeOf()
對象實例的isPrototypeOf方法,用於判斷一個對象對象是否是另外一個對象的原型。
1 var o1 = {}; 2 var o2 = Object.create(o1); 3 var o3 = Object.create(o2); 4 5 console.log(o1.isPrototypeOf(o2)); //true 6 console.log(o2.isPrototypeOf(o3)); //true 7 console.log(o1.isPrototypeOf(o3)); //true
上面代碼中,可以看出,只要某個對象處於原型鏈上,isPrototypeOf都返回true。
1 function Keith() {}; 2 3 console.log(Function.prototype.isPrototypeOf(Keith)); //true 4 console.log(Object.prototype.isPrototypeOf(Function)); //true 5 console.log(Object.getPrototypeOf(Object.prototype) === null); //true
上面代碼中,構造函數Keith的原型指向了Function.prototype,而構造函數Function的原型指向了Object.prototype。Object的原型指向了沒有任何屬性和方法的null對象。
6.Object.prototype.__proto__
__proto__屬性(前后兩條下划線)可以改寫某個對象的原型對象。這個屬於實例方法。
1 var keith = {}; 2 var rascal = {}; 3 rascal.__proto__ = keith; 4 console.log(keith.isPrototypeOf(rascal)); //true
上面代碼中,通過rascal對象的__proto__屬性,將rascal的原型指向了keith對象。
__proto__
屬性只有瀏覽器才需要部署,其他環境可以沒有這個屬性,而且前后的兩根下划線,表示它本質是一個內部屬性,不應該對使用者暴露。因此,應該盡量少用這個屬性,而是用Object.getPrototypeof()
(讀取)和Object.setPrototypeOf()
(設置),進行原型對象的讀寫操作。
來做一個小小的總結,上面對一些屬性和方法的介紹都可以歸結為一句話:
構造函數本身的屬性無法被對象實例共享,而原型對象上的屬性和方法可以被所用對象實例所共享。