JavaScript中原型和原型鏈詳解

來源:互聯網
上載者:User

JavaScript中原型和原型鏈詳解

 這篇文章主要介紹了JavaScript中原型和原型鏈詳解,本文講解了私人變數和函數、靜態變數和函數、執行個體變數和函數、原型和原型鏈的基本概念,需要的朋友可以參考下

 

 

javascript中的每個對象都有一個內建的屬性prototype,Javascript中對象的prototype屬性的解釋是:返回物件類型原型的引用。意思是是prototype屬性儲存著對另一個JavaScript對象的引用,這個對象作為當前對象的父物件。

複製代碼 代碼如下:


A.prototype = new B();


理解prototype不應把它和繼承混淆。A的prototype為B的一個執行個體,可以理解A將B中的方法和屬性全部複製了一遍。A能使用B的方法和屬性。這裡強調的是複製而不是繼承。可以出現這種情況:A的prototype是B的執行個體,同時B的prototype也是A的執行個體。

 

繼續看下面的分析:

私人變數和函數

在函數內部定義的變數和函數,如果不對外提供介面,外部是無法訪問到的,也就是該函數的私人的變數和函數。

複製代碼 代碼如下:


<script type="text/javascript">
function Box(){
var color = "blue";//私人變數
var fn = function() //私人函數
{

 

}
}
</script>


這樣在函數對象Box外部無法訪問變數color和fn,他們就變成私人的了:

複製代碼 代碼如下:


var obj = new Box();
alert(obj.color);//彈出 undefined
alert(obj.fn);//同上

 

靜態變數和函數

當定義一個函數後通過點號 “.”為其添加的屬性和函數,通過對象本身仍然可以訪問得到,但是其執行個體卻訪問不到,這樣的變數和函數分別被稱為靜態變數和靜態函數。

 

複製代碼 代碼如下:


<script type="text/javascript">
function Obj(){};

 

Obj.num = 72;//靜態變數
Obj.fn = function() //靜態函數
{

}

alert(Obj.num);//72
alert(typeof Obj.fn)//function

var t = new Obj();
alert(t.name);//undefined
alert(typeof t.fn);//undefined
</script>

 

執行個體變數和函數

在物件導向編程中除了一些庫函數我們還是希望在對象定義的時候同時定義一些屬性和方法,執行個體化後可以訪問,js也能做到這樣

複製代碼 代碼如下:


<script type="text/javascript">
function Box(){
this.a=[]; //執行個體變數
this.fn=function(){ //執行個體方法

 

}
}

console.log(typeof Box.a); //undefined
console.log(typeof Box.fn); //undefined

var box=new Box();
console.log(typeof box.a); //object
console.log(typeof box.fn); //function
</script>

 

為執行個體變數和方法添加新的方法和屬性

複製代碼 代碼如下:


<script type="text/javascript">
function Box(){
this.a=[]; //執行個體變數
this.fn=function(){ //執行個體方法

 

}
}

var box1=new Box();
box1.a.push(1);
box1.fn={};
console.log(box1.a); //[1]
console.log(typeof box1.fn); //object

var box2=new Box();
console.log(box2.a); //[]
console.log(typeof box2.fn); //function
</script>

 

在box1中修改了a和fn,而在box2中沒有改變,由於數組和函數都是對象,是參考型別,這就說明box1中的屬性和方法與box2中的屬性與方法雖然同名但卻不是一個引用,而是對Box對象定義的屬性和方法的一個複製。

這個對屬性來說沒有什麼問題,但是對於方法來說問題就很大了,因為方法都是在做完全一樣的功能,但是卻又兩份複製,如果一個函數對象有上千和執行個體方法,那麼它的每個執行個體都要保持一份上千個方法的複製,這顯然是不科學的,這可腫麼辦呢,prototype應運而生。

基本概念

我們建立的每個函數都有一個prototype屬性,這個屬性是一個指標,指向一個對象,這個對象的用途是包含可以由特定類型的所有執行個體共用的屬性和方法。那麼,prototype就是通過調用建構函式而建立的那個對象執行個體的原型對象。

使用原型的好處是可以讓對象執行個體共用它所包含的屬性和方法。也就是說,不必在建構函式中添加定義對象資訊,而是可以直接將這些資訊添加到原型中。使用建構函式的主要問題就是每個方法都要在每個執行個體中建立一遍。

在JavaScript中,一共有兩種類型的值,原始值和對象值。每個對象都有一個內部屬性 prototype ,我們通常稱之為原型。原型的值可以是一個對象,也可以是null。如果它的值是一個對象,則這個對象也一定有自己的原型。這樣就形成了一條線性鏈,我們稱之為原型鏈。

含義

函數可以用來作為建構函式來使用。另外只有函數才有prototype屬性並且可以訪問到,但是對象執行個體不具有該屬性,只有一個內部的不可訪問的__proto__屬性。__proto__是對象中一個指向相關原型的神秘連結。按照標準,__proto__是不對外公開的,也就是說是個私人屬性,但是Firefox的引擎將他暴露了出來成為了一個共有的屬性,我們可以對外訪問和設定。

複製代碼 代碼如下:


<script type="text/javascript">
var Browser = function(){};
Browser.prototype.run = function(){
alert("I'm Gecko,a kernel of firefox");
}

 

var Bro = new Browser();
Bro.run();
</script>

 

當我們調用Bro.run()方法時,由於Bro中沒有這個方法,所以,他就會去他的__proto__中去找,也就是Browser.prototype,所以最終執行了該run()方法。(在這裡,函數首字母大寫的都代表建構函式,以用來區分普通函數)

當調用建構函式建立一個執行個體的時候,執行個體內部將包含一個內部指標(__proto__)指向建構函式的prototype,這個串連存在於執行個體和建構函式的prototype之間,而不是執行個體與建構函式之間。

複製代碼 代碼如下:


<script type="text/javascript">
function Person(name){ //建構函式
this.name=name;
}

 

Person.prototype.printName=function() //原型對象
{
alert(this.name);
}

var person1=new Person('Byron');//執行個體化對象
console.log(person1.__proto__);//Person
console.log(person1.constructor);//自己試試看會是什麼吧
console.log(Person.prototype);//指向原型對象Person
var person2=new Person('Frank');
</script>


Person的執行個體person1中包含了name屬性,同時自動產生一個__proto__屬性,該屬性指向Person的prototype,可以訪問到prototype內定義的printName方法,大概就是這個樣子的:

 

每個JavaScript函數都有prototype屬性,這個屬性引用了一個對象,這個對象就是原型對象。原型對象初始化的時候是空的,我們可以在裡面自訂任何屬性和方法,這些方法和屬性都將被該建構函式所建立的對象繼承。

那麼,現在問題來了。建構函式、執行個體和原型對象三者之間有什麼關係呢?

建構函式、執行個體和原型對象的區別

執行個體就是通過建構函式建立的。執行個體一創造出來就具有constructor屬性(指向建構函式)和__proto__屬性(指向原型對象),

建構函式中有一個prototype屬性,這個屬性是一個指標,指向它的原型對象。

原型對象內部也有一個指標(constructor屬性)指向建構函式:Person.prototype.constructor = Person;

執行個體可以訪問原型對象上定義的屬性和方法。

在這裡person1和person2就是執行個體,prototype是他們的原型對象。

再舉個栗子:

複製代碼 代碼如下:


<script type="text/javascript">
function Animal(name) //積累建構函式
{
this.name = name;//設定對象屬性
}

 

Animal.prototype.behavior = function() //給基類建構函式的prototype添加behavior方法
{
alert("this is a "+this.name);
}

var Dog = new Animal("dog");//建立Dog對象
var Cat = new Animal("cat");//建立Cat對象

Dog.behavior();//通過Dog對象直接調用behavior方法
Cat.behavior();//output "this is a cat"

alert(Dog.behavior==Cat.behavior);//output true;
</script>

 

可以從程式運行結果看出,建構函式的prototype上定義的方法確實可以通過對象直接調用到,而且代碼是共用的。(可以試一下將Animal.prototype.behavior 中的prototype屬性去掉,看看還能不能運行。)在這裡,prototype屬性指向Animal對象。

數組對象執行個體

再看個數組對象的執行個體。當我們建立出array1這個對象的時候,array1實際在Javascript引擎中的物件模型如下:

複製代碼 代碼如下:


var array1 = [1,2,3];

 

array1對象具有一個length屬性值為3,但是我們可以通過如下的方法來為array1增加元素:

複製代碼 代碼如下:


array1.push(4);


push這個方法來自於array1的__proto__成員指向對象的一個方法(Array.prototye.push())。正是因為所有的數組對象(通過[]來建立的)都包含有一個指向同一個具有push,reverse等方法對象(Array.prototype)的__proto__成員,才使得這些數組對象可以使用push,reverse等方法。

 

函數對象執行個體

複製代碼 代碼如下:


function Base() {
this.id = "base"
}

 

 

複製代碼 代碼如下:


var obj = new Base();


這樣代碼的結果是什麼,我們在Javascript引擎中看到的物件模型是:

 

new操作符具體幹了什麼呢?其實很簡單,就幹了三件事情。

複製代碼 代碼如下:


var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);

 

原型鏈

原型鏈:當從一個對象那裡調取屬性或方法時,如果該對象自身不存在這樣的屬性或方法,就會去自己關聯的prototype對象那裡尋找,如果prototype沒有,就會去prototype關聯的前輩prototype那裡尋找,如果再沒有則繼續尋找Prototype.Prototype引用的對象,依次類推,直到Prototype.….Prototype為undefined(Object的Prototype就是undefined)從而形成了所謂的“原型鏈”。

 

複製代碼 代碼如下:


<script type="text/javascript">
function Shape(){
this.name = "shape";
this.toString = function(){
return this.name;
}
}
function TwoShape(){
this.name = "2 shape";
}
function Triangle(side,height){
this.name = "Triangle";
this.side = side;
this.height = height;
this.getArea = function(){
return this.side*this.height/2;
}
}

 

TwoShape.prototype = new Shape();
Triangle.prototype = new TwoShape();
</script>

 

這裡,用構造器Shape()建立了一個實體,然後用它去覆蓋該對象的原型。

複製代碼 代碼如下:


<script type="text/javascript">
function Shape(){
this.name = "shape";
this.toString = function(){
return this.name;
}
}
function TwoShape(){
this.name = "2 shape";
}
function Triangle(side,height){
this.name = "Triangle";
this.side = side;
this.height = height;
this.getArea = function(){
return this.side*this.height/2;
}
}

 

TwoShape.prototype = new Shape();
Triangle.prototype = new TwoShape();

TwoShape.prototype.constructor = TwoShape;
Triangle.prototype.constructor = Triangle;

var my = new Triangle(5,10);
my.getArea();
my.toString();//Triangle
my.constructor;//Triangle(side,height)
</script>

 

原型繼承

原型繼承:在原型鏈的末端,就是Object建構函式prototype屬性指向的那個原型對象。這個原型對象是所有對象的祖先,這個老祖宗實現了諸如toString等所有對象天生就該具有的方法。其他內建建構函式,如Function,Boolean,String,Date和RegExp等的prototype都是從這個老祖宗傳承下來的,但他們各自又定義了自身的屬性和方法,從而他們的子孫就表現出各自宗族的那些特徵。

ECMAScript中,實現繼承的方法就是依靠原型鏈實現的。

複製代碼 代碼如下:


<script type="text/javascript">
function Box(){ //被繼承的函數叫做超類型(父類,基類)
this.name = "Jack";
}

 

function Tree(){ //繼承的函數叫做子類型(子類,衍生類別)
this.age = 300;
}
//通過原型鏈繼承,賦值給子類型的原型屬性
//new Box()會將box構造裡的資訊和原型裡的資訊都交給Tree
Tree.prototype = new Box();//Tree繼承了Box,通過原型,形成鏈條

var tree = new Tree();
alert(tree.name);//彈出 Jack
</script>

 

原型鏈的問題:原型鏈雖然很強大,可以用它來實現繼承,但它也存在一些問題。其中最主要的問題來自包含參考型別的值原型。包含參考型別的原型屬性會被所有執行個體共用;而這也正是為什麼要在建構函式中,而不是在原型對象中定義屬性的原因。在通過原型來實現繼承時,原型實際上回變成另一個類型的執行個體。於是,原先的執行個體屬性也就變成了原型的屬性。

在建立子類型的執行個體時,不能向超類型的建構函式中傳遞參數。實際上,應該說是沒有辦法在不影響所有對象執行個體的情況下,給超類型的建構函式傳遞參數。再加上剛剛討論的由於原型中包含參考型別值所帶來的問題,實踐中很少會單獨使用原型鏈。

再舉個栗子:

 

複製代碼 代碼如下:


<script type="text/javascript">
function Person(name)
{
this.name = name;//設定對象屬性
};

 

Person.prototype.company = "Microsoft";//設定原型的屬性
Person.prototype.SayHello = function() //原型的方法
{
alert("Hello,I'm "+ this.name+ " of " + this.company);
};

var BillGates = new Person("BillGates");//建立person對象
BillGates.SayHello();//繼承了原型的內容,輸出"Hello,I'm BillGates of Microsoft"

var Jobs = new Person("Jobs");
Jobs.company = "Apple";//設定自己的company屬性,掩蓋了原型的company屬性
Jobs.SayHello = function()
{
alert("Hi,"+this.name + " like " + this.company);
};
Jobs.SayHello();//自己覆蓋的屬性和方法,輸出"Hi,Jobs like Apple"
BillGates.SayHello();//Jobs的覆蓋沒有影響原型,BillGates還是照樣輸出
</script>

 

看下面一個原型鏈例子:

 

複製代碼 代碼如下:


<script type="text/javascript">
function Year(){
this.value = 21;
}
Year.prototype = {
method:function(){

 

}
};

function Hi(){

};
//設定Hi的prototype屬性為Year的執行個體對象
Hi.prototype = new Year();
Hi.prototype.year = 'Hello World';

Hi.prototype.constructor = Hi;

var test = new Hi();//建立一個Hi的新執行個體

//原型鏈
test [Hi的執行個體]
Hi.prototype [Year的執行個體]
{year:'Hello World'}
Year.prototype
{method:……};
object.prototype
{toString:...};

</script>

 

從上面例子中,test對象從Hi.prototype和Year.prototype中繼承下來;因此他能訪問Year的原型方法method,同時他能訪問執行個體屬性value

__ptoto__屬性

__ptoto__屬性(IE瀏覽器不支援)是執行個體指向原型對象的一個指標,它的作用就是指向建構函式的原型屬性constructor,通過這兩個屬性,就可以訪問原型裡的屬性和方法了。

Javascript中的對象執行個體本質上是由一系列的屬性群組成的,在這些屬性中,有一個內部的不可見的特殊屬性——__proto__,該屬性的值指向該對象執行個體的原型,一個對象執行個體只擁有一個唯一的原型。

 

複製代碼 代碼如下:


<script type="text/javascript">
function Box(){ //大寫,代表建構函式
Box.prototype.name = "trigkit4";//原型屬性
Box.prototype.age = "21";
Box.prototype.run = function()//原型方法
{
return this.name + this.age + 'studying';
}
}

 

var box1 = new Box();
var box2 = new Box();
alert(box1.constructor);//構造屬性,可以擷取建構函式本身,
//作用是被原型指標定位,然後得到建構函式本身
</script>

 

__proto__屬性和prototype屬性的區別

prototype是function對象中專有的屬性。
__proto__是普通對象的隱式屬性,在new的時候,會指向prototype所指的對象;
__ptoto__實際上是某個實體物件的屬性,而prototype則是屬於建構函式的屬性。__ptoto__只能在學習或調試的環境下使用。

原型模式的執行流程

1.先尋找建構函式執行個體裡的屬性或方法,如果有,就立即返回。
2.如果建構函式的執行個體沒有,就去它的原型對象裡找,如果有,就立即返回

原型對象的

複製代碼 代碼如下:


<script type="text/javascript">
function Box(){ //大寫,代表建構函式
Box.prototype.name = "trigkit4";//原型屬性
Box.prototype.age = "21";
Box.prototype.run = function()//原型方法
{
return this.name + this.age + 'studying';
}
}

 

var box1 = new Box();
alert(box1.name);//trigkit4,原型裡的值
box1.name = "Lee";
alert(box1.name);//Lee,就進原則

var box2 = new Box();
alert(box2.name);//trigkit4,原型的值,沒有被box1修改
</script>

 

建構函式的

複製代碼 代碼如下:


<script type="text/javascript">
function Box(){
this.name = "Bill";
}

 

Box.prototype.name = "trigkit4";//原型屬性
Box.prototype.age = "21";
Box.prototype.run = function()//原型方法
{
return this.name + this.age + 'studying';
}

var box1 = new Box();
alert(box1.name);//Bill,原型裡的值
box1.name = "Lee";
alert(box1.name);//Lee,就進原則
</script>

 

綜上,整理一下:

複製代碼 代碼如下:


<script type="text/javascript">
function Person(){};

 

Person.prototype.name = "trigkit4";
Person.prototype.say = function(){
alert("Hi");
}

var p1 = new Person();//prototype是p1和p2的原型對象
var p2 = new Person();//p2為執行個體化對象,其內部有一個__proto__屬性,指向Person的prototype

console.log(p1.prototype);//undefined,這個屬性是一個對象,訪問不到
console.log(Person.prototype);//Person
console.log(Person.prototype.constructor);//原型對象內部也有一個指標(constructor屬性)指向建構函式
console.log(p1.__proto__);//這個屬性是一個指標指向prototype原型對象
p1.say();//執行個體可以訪問到在原型對象上定義的屬性和方法

</script>

 

原廠模式

複製代碼 代碼如下:


function createObject(name,age){
var obj = new Object();
obj.name = name;
obj.age = age;
return obj;
}

 

原廠模式解決了執行個體化對象大量重複的問題,但還有一個問題,那就是根本無法搞清楚他們到底是哪個對象的執行個體。
使用建構函式的方法,既解決了重複執行個體化的問題,又解決了對象識別的問題。

使用建構函式的方法和原廠模式的不同之處在於:

1.建構函式方法沒有顯示的建立對象(new Object());
2.直接將屬性和方法賦值給this對象
3.沒有return 語句

當使用了建構函式,並且new 建構函式(),那麼就在後台執行了new Object();
函數體內的this代表了new Object()出來的對象

1.判斷屬性是在建構函式的執行個體裡,還是在原型裡,可以使用`hasOwnProperty()`函數
2.字面量建立的方式使用constructor屬性不會指向執行個體,而會指向Object,建構函式建立的方式則相反
為什麼指向Object?因為Box.prototype = {};這種寫法其實就是建立了一個新對象。
而每建立一個函數,就會同時建立它的prototype,這個對象也會自動擷取constructor屬性
3.如果是執行個體方法,不同的執行個體化,他們的方法地址是不一樣的,是唯一的
4.如果是原型方法,那麼他們的地址的共用的

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.