JS中的prototype

JS中的phototype是JS中比較難理解的一個部分javascript

 

本文基於下面幾個知識點:html

 

1 原型法設計模式java

在.Net中可使用clone()來實現原型法json

原型法的主要思想是,如今有1個類A,我想要建立一個類B,這個類是以A爲原型的,而且能進行擴展。咱們稱B的原型爲A。設計模式

 

2 javascript的方法能夠分爲三類:瀏覽器

a 類方法服務器

b 對象方法app

c 原型方法函數

例子:學習

複製代碼
function People(name)
{
  this.name=name;
  //對象方法
  this.Introduce=function(){
    alert("My name is "+this.name);
  }
}
//類方法
People.Run=function(){
  alert("I can run");
}
//原型方法
People.prototype.IntroduceChinese=function(){
  alert("個人名字是"+this.name);
}

 

//測試

var p1=new People("Windking");

p1.Introduce();

People.Run();

p1.IntroduceChinese(); 
複製代碼

 

3 obj1.func.call(obj)方法

意思是將obj當作obj1,調用func方法

 

 

好了,下面一個一個問題解決:

 

prototype是什麼含義?

 

javascript中的每一個對象都有prototype屬性,Javascript中對象的prototype屬性的解釋是:返回對象類型原型的引用。

A.prototype = new B();

理解prototype不該把它和繼承混淆。A的prototype爲B的一個實例,能夠理解A將B中的方法和屬性所有克隆了一遍。A能使用B的方法和屬性。這裏強調的是克隆而不是繼承。能夠出現這種狀況:A的prototype是B的實例,同時B的prototype也是A的實例。

 

先看一個實驗的例子:


複製代碼
function baseClass()
{
  this.showMsg = function()
  {
     alert("baseClass::showMsg");   
  }
}

function extendClass()
{
}

extendClass.prototype = new baseClass();
var instance = new extendClass();
instance.showMsg(); // 顯示baseClass::showMsg
複製代碼

咱們首先定義了baseClass類,而後咱們要定義extentClass,可是咱們打算以baseClass的一個實例爲原型,來克隆的extendClass也同時包含showMsg這個對象方法。

extendClass.prototype = new baseClass()就能夠閱讀爲:extendClass是以baseClass的一個實例爲原型克隆建立的。

 

那麼就會有一個問題,若是extendClass中自己包含有一個與baseClass的方法同名的方法會怎麼樣?

下面是擴展實驗2:


複製代碼
function baseClass()
{
    this.showMsg = function()
    {
        alert("baseClass::showMsg");   
    }
}

function extendClass()
{
    this.showMsg =function ()
    {
        alert("extendClass::showMsg");
    }
}

extendClass.prototype = new baseClass();
var instance = new extendClass();

instance.showMsg();//顯示extendClass::showMsg
複製代碼

 

實驗證實:函數運行時會先去本體的函數中去找,若是找到則運行,找不到則去prototype中尋找函數。或者能夠理解爲prototype不會克隆同名函數。

 

那麼又會有一個新的問題:

若是我想使用extendClass的一個實例instance調用baseClass的對象方法showMsg怎麼辦?

 

答案是可使用call:


複製代碼
extendClass.prototype = new baseClass();
var instance = new extendClass();


var baseinstance = new baseClass();
baseinstance.showMsg.call(instance);//顯示baseClass::showMsg
複製代碼

 

這裏的baseinstance.showMsg.call(instance);閱讀爲「將instance當作baseinstance來調用,調用它的對象方法showMsg」

好了,這裏可能有人會問,爲何不用baseClass.showMsg.call(instance);

這就是對象方法和類方法的區別,咱們想調用的是baseClass的對象方法

 

最後,下面這個代碼若是理解清晰,那麼這篇文章說的就已經理解了:

 


複製代碼
<script type="text/javascript">

function baseClass()
{
    this.showMsg = function()
    {
        alert("baseClass::showMsg");   
    }
   
    this.baseShowMsg = function()
    {
        alert("baseClass::baseShowMsg");
    }
}
baseClass.showMsg = function()
{
    alert("baseClass::showMsg static");
}

function extendClass()
{
    this.showMsg =function ()
    {
        alert("extendClass::showMsg");
    }
}
extendClass.showMsg = function()
{
    alert("extendClass::showMsg static")
}

extendClass.prototype = new baseClass();
var instance = new extendClass();

instance.showMsg(); //顯示extendClass::showMsg
instance.baseShowMsg(); //顯示baseClass::baseShowMsg
instance.showMsg(); //顯示extendClass::showMsg

baseClass.showMsg.call(instance);//顯示baseClass::showMsg static

var baseinstance = new baseClass();
baseinstance.showMsg.call(instance);//顯示baseClass::showMsg

</script>
複製代碼

 

 

起由

最近在作一個項目,裏面大量地使用 javascript 做爲頁面的動態生成腳本, 使用 json 與服務器進行通訊. 在讀以前遺留的代碼時, 常常會弄不清楚, 做用域, this關鍵字在當前context下的指向等,因而便開始專門學習了 相關的知識,記錄下來與你們分享.

下面的內容中會有一些代碼,建議你們也去嘗試修改和理解,這樣更容易掌握. 點擊 這兒 下載所涉及到的源碼.

prototype

javascript 是一種 prototype based programming 的語言, 而與咱們一般的 class based programming 有很大 的區別,我列舉重要的幾點以下:

  1. 函數是first class object, 也就是說函數與對象具備相同的語言地位
  2. 沒有類,只有對象
  3. 函數也是一種對象,所謂的函數對象
  4. 對象是按 引用 來傳遞的

那麼這種 prototype based programming 的語言如何實現繼承呢(OO的一大基本要素), 這也即是 prototype 的由來.

看下面的代碼片段:

function foo(a, b, c)
{
return a*b*c;
}
alert(foo.length);
alert(typeof foo.constructor);
alert(typeof foo.call);
alert(typeof foo.apply);
alert(typeof foo.prototype);

對於上面的代碼,用瀏覽器運行後你會發現:

  1. length: 提供的是函數的參數個數
  2. prototype: 是一個object
  3. 其它三個都是function

而對於任何一個函數的聲明,它都將會具備上面所述的5個property(方法或者屬性).

下面咱們主要看下prototype.

// prototype
function Person(name, gender)
{
this.name = name;
this.gender = gender;
this.whoAreYou = function(){//這個也是所謂的closure, 內部函數能夠訪問外部函數的變量
var res = "I'm " + this.name + " and I'm a " + this.gender +".";
return res;
};
}
// 那麼在由Person建立的對象便具備了下面的幾個屬性
Person.prototype.age = 24;
Person.prototype.getAge = function(){
return this.age;
};
flag = true;
if (flag)
{
var fun = new Person("Tower", "male");
alert(fun.name);
alert(fun.gender);
alert(fun.whoAreYou());
alert(fun.getAge());
}
Person.prototype.salary = 10000;
Person.prototype.getSalary = function(){
return this.name + " can earn about " + this.salary + "RMB each month." ;
};
// 下面就是最神奇的地方, 咱們改變了Person的prototype,而這個改變是在建立fun以後
// 而這個改變使得fun也具備了相同的屬性和方法
// 繼承的意味即此
if (flag)
{
alert(fun.getSalary());
alert(fun.constructor.prototype.age);//而這個至關於你直接調用 Person.prototype.age
alert(Person.prototype.age);
}

從上面的示例中咱們能夠發現,對於prototype的方法或者屬性,咱們能夠 動態地 增長, 而由其建立的 對象自動會 繼承 相關的方法和屬性.

另外,每一個對象都有一個 constructor 屬性,用於指向建立其的函數對象,如上例中的 fun.constructor 指向的 就是 Person.

那麼一個疑問就天然產生了, 函數對象中自身聲明的方法和屬性與prototype聲明的對象有什麼差異?

有下面幾個差異:

  1. 自身聲明的方法和屬性是 靜態的, 也就是說你在聲明後,試圖再去增長新的方法或者修改已有的方法,並不會 對由其建立的對象產生影響, 也即 繼承 失敗
  2. 而prototype能夠動態地增長新的方法或者修改已有的方法, 從而是 動態的 ,一旦 父函數對象 聲明瞭相關 的prototype屬性,由其建立的對象會 自動繼承 這些prototype的屬性.

繼續上面的例子:

flag = true;
// 函數內部聲明的方法是靜態的,沒法傳遞的
Person.school = "ISCAS";
Person.whoAreYou = function(){
return "zhutao";
};//動態更改聲明期的方法,並不會影響由其建立的對象的方法, 即所謂的 靜態
if (flag)
{
alert(Person.school);
alert(fun.school);//輸出的是 "undefined"
alert(Person.whoAreYou()); //輸出 zhutao
alert(fun.whoAreYou()); // I'm Tower and I'm a male.
}
Person.prototype.getSalary = function(){
return "I can earn 1000000 USD";
};
if (flag)
{
alert(fun.getSalary());//已經繼承了改變, 即所謂的 動態
}

既然有函數對象自己的屬性, 也有prototype的屬性, 那麼是由其建立的對象是如何搜索相應的屬性的呢?

基本是按照下面的流程和順序來進行.

  1. 先去搜索函數對象自己的屬性,若是找到當即執行
  2. 若是1沒有找到,則會去搜索prototype屬性,有2種結果,找到則直接執行,不然繼續搜索 父對象 的 父對象 的prototype, 直至找到,或者到達 prototype chain 的結尾(結尾會是Object對象)

上面也回答若是函數對象自己的屬性與prototype屬性相同(重名)時的解決方式, 函數自己的對象 優先 .

再看一個多重prototype鏈的例子:

// 多重prototype鏈的例子
function Employee(name)
{
this.name = "";
this.dept = "general";
this.gender = "unknown";
}
function WorkerBee()
{
this.projects = [];
this.hasCar = false;
}
WorkerBee.prototype = new Employee; // 第一層prototype鏈
function Engineer()
{
this.dept = "engineer"; //覆蓋了 "父對象"
this.language = "javascript";
}
Engineer.prototype = new WorkerBee; // 第二層prototype鏈
var jay = new Engineer("Jay");
if (flag)
{
alert(jay.dept);    //engineer, 找到的是本身的屬性
alert(jay.hasCar);  // false, 搜索到的是本身上一層的屬性
alert(jay.gender);  // unknown, 搜索到的是本身上二層的屬性
}

上面這個示例的對象關係以下:

http://farm3.static.flickr.com/2585/3933273719_ccab4562d2.jpg

結論

javascript 的prototype給語言自己增長了很強的靈活性,但與 class based programming 相比整個思惟邏輯仍是有很大的不一樣,因此須要更多地思考和揣摩.

而 javascript是披着c語言外衣的函數式語言 的理解天然也須要更多地思考.

下一節我會繼續討論下 javascript 的另外一個重要並且比較容易弄錯的知識 closure.

相關文章
相關標籤/搜索