如何理解JavaScript原型

來源:互聯網
上載者:User

Javascript的原型總會給人產生一些困惑,無論是經驗豐富的專家,還是作者自己也時常表現出對這個概念某些有限的理解,我認為這樣的困惑在我們一開始接觸原型時就已經產生了,它們常常和new、constructor相關,特別是函數(function)的原型(prototype)屬性(property)。事實上,原型是一種非常簡單的概念。為了更好的理解它,我們應該首先記住這個原則,那就是忘記我們已經學到的關於構造原型(construtor prototypes)的認識。
 
什麼是原型?
 
原型是一個對象,其他對象可以通過它實現屬性繼承。
 
任何一個對象都可以成為原型嗎?
 

 
哪些對象有原型
 
所有的對象在預設的情況下都有一個原型,因為原型本身也是對象,所以每個原型自身又有一個原型(只有一種例外,預設的對象原型在原型鏈的頂端。更多關於原型鏈的將在後面介紹)
 
好吧,再繞回來,那什麼又是對象呢?
 
在javascript中,一個對象就是任何無序索引值對的集合,如果它不是一個主要資料類型(undefined,null,boolean,number,or string),那它就是一個對象
 
你說每個對象都有一個原型,可是我當我寫成({}).prototype 我得到了一個null,你說的不對吧?
 
忘記你已經學到的關於原型屬性的一切,它可能就是你對原型困惑的根源所在。一個對象的真正原型是被對象內部的[[Prototype]]屬性(property)所持有。ECMA引入了標準對象原型訪問器Object.getPrototype(object),到目前為止只有Firefox和chrome實現了此訪問器。除了IE,其他的瀏覽器支援非標準的訪問器__proto__,如果這兩者都不起作用的,我們需要從對象的建構函式中找到的它原型屬性。下面的代碼展示了擷取對象原型的方法
var a = {};
 
  //Firefox 3.6 and Chrome 5
 
  Object.getPrototypeOf(a); //[object Object] 
 
  //Firefox 3.6, Chrome 5 and Safari 4
 
a.__proto__; //[object Object] 
 
  //all browsers
 
  a.constructor.prototype; //[object Object]
ok,一切都進行的很好,但是false明明是一個主要資料類型,可是false.__proto__卻返回了一個值
 
當你試圖擷取一個主要資料類型的原型時,它被強制轉化成了一個對象
 
//(works in IE too, but only by accident)
 
false .__proto__ === Boolean( false ).__proto__; //true
我想在繼承中使用原型,那我該怎麼做?
 
如果僅僅只是因為一個執行個體而使用原型是沒有多大意義的,這和直接添加屬性到這個執行個體是一樣的,假如我們已經建立了一個執行個體對象 ,我們想要繼承一個已經存在的對象的功能比如說Array,我們可以像下面這樣做( 在支援__proto__ 的瀏覽器中)
//unusual case and does not work in IE
var a = {};
a.__proto__ = Array.prototype;
a.length; //0
———————————————————————————————————–
 譯者注:上面這個例子中,首先建立了一個對象a,然後通過a的原型來達到繼承Array 這個已經存在的對象的功能
———————————————————————————————————–
 
原型真正魅力體現在多個執行個體共用一個通用原型的時候。原型對象(注:也就是某個對象的原型所引用的對象)的屬性一旦定義,就可以被多個引用它的執行個體所繼承(注:即這些執行個體對象的原型所指向的就是這個原型對象),這種操作在效能和維護方面其意義是不言自明的
 
這也是建構函式的存在的原因麼?
 
是的。建構函式提供了一種方便的跨瀏覽器機制,這種機制允許在建立執行個體時為執行個體提供一個通用的原型
 
在你能夠提供一個例子之前,我需要知道constructor.prototype 屬性究竟是什嗎?
 
首先,javascript並沒有在建構函式(constructor)和其他函數之間做區分,所以說每個函數都有一個原型屬性。反過來,如果不是函數,將不會有這樣一個屬性。請看下面的代碼
 
//function will never be a constructor but it has a prototype property anyway
 
Math.max.prototype; //[object Object]
 
//function intended to be a constructor has a prototype too
 
var A = function (name) {
 
      this .name = name;
 
}
 
  A.prototype; //[object Object]  
 
  //Math is not a function so no prototype property
 
  Math.prototype; //null
現在我們可以下個定義了:函數A的原型屬性(prototype property )是一個對象,當這個函數被用作建構函式來建立執行個體時,該函數的原型屬性將被作為原型賦值給所有對象執行個體(注:即所有執行個體的原型引用的是函數的原型屬性)
———————————————————————————————————-
譯者注:以下的代碼更詳細的說明這一切
 
//建立一個函數b
var b = function(){ var one; }
//使用b建立一個對象執行個體c
var c = new b();
//查看b 和c的建構函式
b.constructor;   // function Function() { [native code]}
b.constructor==Function.constructor;  //true
c.constructor;  //執行個體c的建構函式 即b function(){ var one; }
c.constructor==b  //true
//b是一個函數,查看b的原型如下
b.constructor.prototype // function (){}
b.__proto__  //function (){}
 
//b是一個函數,由於javascript沒有在建構函式constructor和函數function之間做區分,所以函數像constructor一樣,
//有一個原型屬性,這和函數的原型(b.__proto__ 或者b.construtor.prototype)是不一樣的
b.prototype //[object Object]   函數b的原型屬性
 
b.prototype==b.constructor.prototype //fasle
b.prototype==b.__proto__  //false
b.__proto__==b.constructor.prototype //true
 
//c是一個由b建立的對象執行個體,查看c的原型如下
c.constructor.prototype //[object Object] 這是對象的原型
c.__proto__ //[object Object] 這是對象的原型
 
c.constructor.prototype==b.constructor.prototype;   //false  c的原型和b的原型比較
c.constructor.prototype==b.prototype;   //true c的原型和b的原型屬性比較
 
//為函數b的原型屬性添加一個屬性max
b.prototype.max = 3
//執行個體c也有了一個屬性max
c.max   //3
上面的例子中,對象執行個體c的原型和函數的b的原型屬性是一樣的,如果改變b的原型屬性,則對象執行個體c
的原型也會改變
———————————————————————————————————-
理解一個函數的原型屬性(function’s prototype property )其實和實際的原型(prototype)沒有關係對我們來說至關重要
//(example fails in IE)
 
  var A = function (name) {
 
   this .name = name;
 
}
 
A.prototype == A.__proto__; //false
 
  A.__proto__ == Function.prototype; //true - A's prototype is set to its constructor's prototype property
給個例子撒
 
你可能曾經上百次的像這樣使用javascript,現在當你再次看到這樣的代碼的時候,你或許會有不同的理解。
 
 
 
//Constructor. <em>this</em> is returned as new object and its internal [[prototype]] property will be set to the constructor's default prototype property
var Circle = function (radius) {
     this .radius = radius;
     //next line is implicit, added for illustration only
     //this.__proto__ = Circle.prototype;
  }   
//augment Circle's default prototype property thereby augmenting the prototype of each generated instance
Circle.prototype.area = function () {
     return Math.PI* this .radius* this .radius;
  }  
  //create two instances of a circle and make each leverage the common prototype
var a = new Circle(3), b = new Circle(4);
  a.area().toFixed(2); //28.27
  b.area().toFixed(2); //50.27
棒極了。如果我更改了建構函式的原型,是否意味著已經存在的該建構函式的執行個體將獲得建構函式的最新版本?
 
不一定。如果修改的是原型屬性,那麼這樣的改變將會發生。因為在a實際被建立之後,a.__proto__是一個對A.prototype 的一個引用,。
 
var A = function (name) {
     this .name = name;
  }  
var a = new A( 'alpha' );
a.name; //'alpha'  
  A.prototype.x = 23;  
a.x; //23
——————————————————————————————————
譯者注:這個和上例中的一樣,執行個體對象a的原型(a.__proto__)是對函數A的原型屬性(A.prototype)的引用,所以如果修改的是A的原型屬性,
 
改變將影響由A建立的對象執行個體a 在下面的例子中,但是對函數A的原型進行了修改,但是並沒有反應到A所建立的執行個體a中
 
var A = function(name)
{
this.name = name;
}
var a = new A(‘alpha’);
a.name; //’alpha’
 
A.__proto__.max = 19880716;
 
a.max   //undefined
 
——————————————————————————————————
 
但是如果我現在替換A的原型屬性為一個新的對象,執行個體對象的原型a.__proto__卻仍然引用著原來它被建立時A的原型屬性
 
var A = function (name) {
     this .name = name;

  var a = new A( 'alpha' );
a.name; //'alpha'  
  A.prototype = {x:23};   
  a.x; //null
——————————————————————————————————————
譯者註:即如果在執行個體被建立之後,改變了函數的原型屬性所指向的對象,也就是改變了建立執行個體時執行個體原型所指向的對象
 
但是這並不會影響已經建立的執行個體的原型。
——————————————————————————————————————-
 
一個預設的原型是什麼樣子的?
 
var A = function () {};
  A.prototype.constructor == A; //true
  var a = new A();
  a.constructor == A; //true (a's constructor property inherited from it's prototype)
instance of 和原型有什麼關係
 
如果a的原型屬於A的原型鏈,運算式a instance of A 值為true。這意味著 我們可以對instance of 耍個詭計讓它不在起作用
var A = function () {}  
 
  var a = new A();
 
  a.__proto__ == A.prototype; //true - so instanceof A will return true
 
  a instanceof A; //true;  
 
//mess around with a's prototype
 
  a.__proto__ = Function.prototype;
 
  //a's prototype no longer in same prototype chain as A's prototype property
 
  a instanceof A; //false
還能使用原型做些什麼呢?
 
記住我曾經所提到過的每個建構函式都有一個原型屬性,它用來為每一個它所建立的執行個體提供原型。這同樣也適用原生態的建構函式Function,String等,擴充這個屬性,我們可以達到擴充指定建構函式的所有執行個體
我曾經在之前的很多文章中使用過這個技巧來示範函數的拓展。在tracer utility 這篇文章中所有的string執行個體都實現了times這個方法,對字串本身進行指定數目的複製
String.prototype.times = function (count) {
     return count < 1 ? '' : new Array(count + 1).join( this );
  }
 
"hello!" .times(3); //"hello!hello!hello!";
 
"please..." .times(6); //"please...please...please...please...please...please..."
告訴我繼承是怎樣通過原型來工作的。什麼是原型鏈?
 
因為每個對象和原型都有一個原型(注:原型也是一個對象),對象的原型指向對象的父,而父的原型又指向父的父,我們把這種通過原型層層串連起來的關係撐為原型鏈。這條鏈的末端一般總是預設的對象原型。
 
a.__proto__ = b;
 
  b.__proto__ = c;
 
  c.__proto__ = {}; //default object
 
{}.__proto__.__proto__; //null
原型的繼承機制是發生在內部且是隱式的.當想要獲得一個對象a的屬性foo的值,javascript會在原型鏈中尋找foo的存在,如果找到則返回foo的值,否則undefined被返回。
 
賦值呢?
 
原型的繼承is not a player 當屬性值被設定成a.foo=’bar’是直接給a的屬性foo設定了一個值bar。為了把一個屬性添加到原型中,你需要直接指定該原型。
 
以上就是這篇文章所要講述的。我個人感覺自己對原型概念的理解還是比較深刻的,但我的觀點並不能代表一切。如有紕漏和異議,敬請告知。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.