javascript-理解原型、原型鏈

  首先,先認識下什麼是原型?javascript

      原型是一個對象,其餘對象能夠經過它實現屬性繼承,並且任何一個對象均可以成爲原型。這是爲何呢?請繼續看。html

      首先,要弄明白什麼是對象,在JavaScript中能夠說一切都是對象,除了(undefined, number, string, boolean)這四種值類型,咱們能夠經過 typeof() 這個函數和instanceof進行檢測,如何判斷一個變量是否是對象,值類型的類型判斷用typeof,引用類型的類型判斷用instanceof。以下:java

 1 function show(x) {
 2 
 3             console.log(typeof(x));    // undefined
 4             console.log(typeof(10));   // number
 5             console.log(typeof('abc')); // string
 6             console.log(typeof(true));  // boolean
 7 
 8             console.log(typeof(function () { }));  //function
 9             console.log((function () { }) instanceof Object);  // true
10 
11             console.log(typeof([1, 'a', true]));  //object
12             console.log(typeof ({ a: 10, b: 20 }));  //object
13             console.log(typeof (null));  //object
14             console.log(typeof (new Number(10)));  //object
15         }
16         show();

  其中上面的四種(undefined, number, string, boolean)屬於簡單的值類型,不是對象。剩下的幾種狀況——函數、數組、對象、null、new Number(10)都是對象。對象是屬性的集合。git

      弄明白什麼是對象後,咱們再來理解下函數與對象的關係。對象都是經過函數建立的,爲何這麼說呢,請看代碼:  github

 1         var obj = { a: 10, b: 20 };
 2         var arr = [5, 'x', true];
 3         //上面看起來不像是經過函數建立出一個對象,其實其本質以下
 4 
 5         var obj = new Object();
 6         obj.a = 10;
 7         obj.b = 20;
 8 
 9         var arr = new Array();
10         arr[0] = 5;
11         arr[1] = 'x';
12         arr[2] = true;
13 
14         console.log(typeof (Object));  // function
15         console.log(typeof (Array));  // function

  經過上述代碼理解仍是很是的迷惑吧。對象是函數建立的,而函數卻又是一種對象,函數和對象究竟是什麼關係啊?咱們就經過prototyoe原型來加以理解。chrome

      每一個函數都有一個屬性叫作prototype(原型)。這個prototype的屬性值是一個對象(屬性的集合),默認的只有一個叫作constructor的屬性,指向這個函數自己。如圖(SuperType是一個函數,右側的方框就是它的原型)json

 

  固然,原型做爲一個對象,不可能只含有consrtuctor這一個屬性而已,咱們能夠在其中添加一些咱們自定義的屬性,例如:數組

1         function Fn() { };
2         Fn.prototype.name = 'hello';
3         Fn.prototype.getYear = function () {
4             return 2015;
5         };

 這樣原型屬性就增長了:
  問題是,這樣有什麼用呢?咱們用代碼來演示下:
1         function Fn() { };
2         Fn.prototype.name = 'hello';
3         Fn.prototype.getYear = function () {
4             return 2015;
5         };
6 
7         var fn = new Fn();
8         console.log(fn.name);         //hello
9         console.log(fn.getYear());    //2015

  Fn是一個函數,fn對象是從Fn函數new出來的,這樣fn對象就能夠調用Fn.prototype中的屬性。由於每一個對象都有一個隱藏的屬性 ----「__proto__」,這個屬性引用了建立這個對象的函數的prototype。即:fn.__proto__ === Fn.prototype.這裏的"__proto__"成爲「隱式原型」瀏覽器

 prototype與__proto__的區別app

   二者都是對象類型的屬性,並不是全部的對象類型都有prototype屬性,通常只有function對象纔有prototype屬性(除非主動賦值),它指向的是一個對象,未來會被多個該function的實例所繼承(或者說該對象處於多個實例的原型鏈上);__proto__纔是真正的原型鏈的實際指針,然而許多瀏覽器並不對外公開這個屬性,Firefox暴露出這一屬性,僅供開發人員理解,但不推薦開發中使用,目前chrome也能夠支持了。

  小插曲:instanceof表示的就是一種繼承關係,或者原型鏈的結構

  typeof在判斷到引用類型的時候,返回值只有object/function,你不知道它究竟是一個object對象,仍是數組,仍是new Number等等。這個時候就須要用到instanceof.

instanceof的判斷隊則是:沿着A的__proto__這條線來找,同時沿着B的prototype這條線來找,若是兩條線能找到同一個引用,即同一個對象,那麼就返回true。若是找到終點還未重合,則返回false。以下圖:

    

 

     全部構造器/函數的__proto__都指向Function.prototype,它是一個空函數(Empty function),JavaScript中有內置(build-in)構造器/對象共計12個(ES5中新加了JSON),這裏列舉了可訪問的8個構造器。剩下如Global不能直接訪問,Arguments僅在函數調用時由JS引擎建立,MathJSON是以對象形式存在的,無需new。它們的__proto__是Object.prototype。以下:

 1     Number.__proto__ === Function.prototype  // true
 2     Boolean.__proto__ === Function.prototype // true
 3     String.__proto__ === Function.prototype  // true
 4     Object.__proto__ === Function.prototype  // true
 5     Function.__proto__ === Function.prototype // true
 6     Array.__proto__ === Function.prototype   // true
 7     RegExp.__proto__ === Function.prototype  // true
 8     Error.__proto__ === Function.prototype   // true
 9     Date.__proto__ === Function.prototype    // true
10 
11     Math.__proto__ === Object.prototype  // true
12     JSON.__proto__ === Object.prototype  // true

  注:上述代碼中__proto__目前在IE6/7/8/9中都不支持,IE9中可使用Object.getPrototypeOf(ES5)獲取對象的內部原型。除了IE(IE11開始支持),其餘的瀏覽器支持非標準的訪問器__proto__。那麼那些不支持__proto__屬性的能夠經過constructor間接獲得,constructor屬性不是對象本身的屬性,而是順着原型鏈向上從原型對象中獲取的。這個屬性指向的是這個原型對象所對應的構造函數。而構造函數的prototype屬性指向了原型對象, 因此這樣咱們就能夠間接獲得了,例:

     function Foo(){};
     var foo = new Foo();
     alert(foo.constructor.prototype == Foo.prototype); // true

  每一個對象都有一個__proto__屬性,原型鏈上的對象正是依靠這個__proto__屬性連結在一塊兒的,__proto__是否指向實例對象的原型prototype對象的引用。什麼是原型鏈呢?簡單來講,訪問一個對象的屬性時,先在基本屬性中查找,若是沒有,再沿着__proto__這條鏈向上找,這就是原型鏈。全部對象都繼承於Object,原型鏈的頂端就是Object.prototype,Object.prototype.__proto__ = null;看圖理解下:

  

  對象的原型鏈是沿着__proto__這條線走的,所以在查找f1.hasOwnProperty屬性時,就會順着原型鏈一直查找到Object.prototype。因爲全部的對象的原型鏈都會找到Object.prototype,所以全部的對象都會有Object.prototype的方法。這也是所謂的「繼承」。 說一個函數的例子吧,咱們都知道每一個函數都有call,apply方法,都有length,arguments,caller等屬性。爲何每一個函數都有?這確定是「繼承」的。函數由Function函數建立,所以繼承的Function.prototype中的方法。

  理解了原型與原型鏈後,那麼用原型有什麼好處呢?

  1.對象屬性能夠隨時改動,對象或者函數,剛開始new出來以後,可能啥屬性都沒有。可是你能夠根據你的須要繼續添加,很是靈活。

  2.若是繼承的方法不合適,能夠作出修改。例如在json2.js源碼中,爲Date、String、Number、Boolean方法添加一個toJSON的屬性。

  

  3.能夠繼續建立新的方法,不過若是你要添加內置方法的原型屬性,最好作一步判斷,若是該屬性不存在,則添加。若是原本就存在,就不必再添加了。

 

  記:博文參考了網上不少關於原型的介紹,概括得不足之處,請指正。

相關文章
相關標籤/搜索