什麼是原型?javascript
原型是一個對象,其餘對象能夠經過它實現屬性繼承。java
任何一個對象均可以成爲原型麼?chrome
是瀏覽器
哪些對象有原型ide
全部的對象在默認的狀況下都有一個原型,由於原型自己也是對象,因此每一個原型自身又有一個原型(只有一種例外,默認的對象原型在原型鏈的頂端。更多關於原型鏈的將在後面介紹)函數
好吧,再繞回來,那什麼又是對象呢?性能
在javascript中,一個對象就是任何無序鍵值對的集合,若是它不是一個主數據類型(undefined,null,boolean,number,or string),那它就是一個對象this
你說每一個對象都有一個原型,但是我當我寫成({}).prototype 我獲得了一個null,你說的不對吧?spa
忘記你已經學到的關於原型屬性的一切,它可能就是你對原型困惑的根源所在。一個對象的真正原型是被對象內部的[[Prototype]]屬性(property)所持有。ECMA引入了標準對象原型訪問器Object.getPrototype(object),到目前爲止只有Firefox和chrome實現了此訪問器。除了IE,其餘的瀏覽器支持非標準的訪問器__proto__,若是這二者都不起做用的,咱們須要從對象的構造函數中找到的它原型屬性。下面的代碼展現了獲取對象原型的方法prototype
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
|
如今咱們能夠下個定義了:函數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)沒有關係對咱們來講相當重要
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。爲了把一個屬性添加到原型中,你須要直接指定該原型。