深入理解Javascript中構造函數和原型對象的區別(轉存)


 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()(設置),進行原型對象的讀寫操作。

 

 

  來做一個小小的總結,上面對一些屬性和方法的介紹都可以歸結為一句話:

  構造函數本身的屬性無法被對象實例共享,而原型對象上的屬性和方法可以被所用對象實例所共享。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM