Javascript原型
prototype: 用來定義實例共享的屬性或方法
__proto__: 用於實現對象的繼承
對象原型關係 圖
// ======以下爲原文:大紅色部分爲我的標註======
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__,如果這兩者都不起作用的,我們需要從對象的構造函數中找到的它原型屬性。下面的代碼展示了獲取對象原型的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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__卻返回了一個值
1
2
3
|
//(works
in IE too, but only by accident) false .__proto__
=== Boolean( false ).__proto__; //true |
我想在繼承中使用原型,那我該怎麼做?
如果僅僅只是因爲一個實例而使用原型是沒有多大意義的,這和直接添加屬性到這個實例是一樣的,假如我們已經創建了一個實例對象 ,我們想要繼承一個已經存在的對象的功能比如說Array,我們可以像下面這樣做( 在支持__proto__ 的瀏覽器中)
1
2
3
4
|
//unusual
case and does not work in IE var a
= {}; a.__proto__
= Array.prototype;// 這裏更好的辦法是使用一個空函數 a.length; //0 |
———————————————————————————————————–
譯者注:上面這個例子中,首先創建了一個對象a,然後通過a的原型來達到繼承Array 這個已經存在的對象的功能
———————————————————————————————————–
原型真正魅力體現在多個實例共用一個通用原型的時候。原型對象(注:也就是某個對象的原型所引用的對象)的屬性一旦定義,就可以被多個引用它的實例所繼承(注:即這些實例對象的原型所指向的就是這個原型對象),這種操作在性能和維護方面其意義是不言自明的
這也是構造函數的存在的原因麼?
是的。構造函數提供了一種方便的跨瀏覽器機制,這種機制允許在創建實例時爲實例提供一個通用的原型
在你能夠提供一個例子之前,我需要知道constructor.prototype 屬性究竟是什麼?
首先,javascript並沒有在構造函數(constructor)和其他函數之間做區分,所以說每個函數都有一個原型屬性(注意:這個函數本身也有自己的原型,用來實現繼承,所以函數的原型不等於函數的原型對象。但是:對由該函數構造出來的實例來講函數的原型屬性就是該實例對象的原型。)。反過來,如果不是函數,將不會有這樣一個屬性。請看下面的代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
//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 |
//創建一個函數b現在我們可以下個定義了:函數A的原型屬性(prototype property )是一個對象,當這個函數被用作構造函數來創建實例時,該函數的原型屬性將被作爲原型賦值給所有對象實例(注:即所有實例的原型引用的是函數的原型屬性)
———————————————————————————————————-
譯者注:以下的代碼更詳細的說明這一切
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)沒有關係對我們來說至關重要
1
2
3
4
5
6
7
8
9
10
11
|
//(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,現在當你再次看到這樣的代碼的時候,你或許會有不同的理解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//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 的一個引用,。
1
2
3
4
5
6
7
|
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的原型屬性
1
2
3
4
5
6
7
|
var A
= function (name)
{ this .name
= name; }
var a
= new A( 'alpha' ); a.name; //'alpha'
A.prototype
= {x:23}; a.x; //null |
——————————————————————————————————————
譯者注:即如果在實例被創建之後,改變了函數的原型屬性所指向的對象,也就是改變了創建實例時實例原型所指向的對象
但是這並不會影響已經創建的實例的原型。
——————————————————————————————————————-
一個默認的原型是什麼樣子的?
1
2
3
4
|
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 耍個詭計讓它不在起作用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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這個方法,對字符串本身進行指定數目的複製
1
2
3
4
5
6
7
|
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..." |
告訴我繼承是怎樣通過原型來工作的。什麼是原型鏈?
因爲每個對象和原型都有一個原型(注:原型也是一個對象),對象的原型指向對象的父,而父的原型又指向父的父,我們把這種通過原型層層連接起來的關係撐爲原型鏈。這條鏈的末端一般總是默認的對象原型。
1
2
3
4
5
6
7
|
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。爲了把一個屬性添加到原型中,你需要直接指定該原型。