從新認識面向對象

一、首先來看下javascript組成:ECMAScirpt(核心)+BOM+DOMjavascript

二、如何理解javascript的面向對象?html

    面向對象有三個基本特性:封裝、繼承、多態。其中,對象系統中的繼承特性有三種實現方案:基於類的(class-based)、基於原型(prototype-based)的、基於元類(metaclass-based)的。Javascript 是面嚮對象語言,比 Java 還要完全的面向對象。在 Javascript沒有使用常見的類繼承體系,而是使用「構造器」來實現對象的類型化。構造器就是函數,慣例是首字母大寫函數名來表明這是一個構造器,用 new構造器名字 來建立實例對象,在構造器(及其原型屬性對象)內部,this 指代實例對象。Javascript採用「原型繼承」而不是「類型繼承」,經過對象的原型鏈進行屬性(方法)的查找。java

三、從對象類型開始理解: git

 3.一、JavaScript 是一門完全的面向對象的語言,所以有必要從面向對象的概念着手 , 探討一下面向對象中的幾個概念:github

      a、一切事物皆對象web

      b、對象具備封裝和繼承特性chrome

      c、對象與對象之間使用消息通訊,各自存在信息如何隱藏 ,經過一種叫作 原型(prototype)的方式來實現面向對象編程的。編程

來看下對象類型系統數組

 

   obj-system.png

     上圖能夠看出:javascript對象系統圖解瀏覽器

      a、javascript數據類型可分兩大類:「值類型」和「引用類型」 。這兩中類型可使用typeof方法加以區分

      b、引用類型 即整個對象類型系統。大體分爲原生對象+宿主對象+引擎擴展對象。其中原生對象包括內置對象和arguments,

      c、對象類型:全文下來討論的都是這兩類對象----普通實例對象(object)和函數對象(function) (區分這兩類對象 即:typeof 返回object /function)

 注:本文主要討論兩種引用類型建立以及它們之間關係:對象(object/function)的建立以及的聯繫。

  3.二、其中,ECMAScirpt定義數據六種基本類型:undefined boolean string number object function引用類型),

  3.三、理解兩種引用類型之間關係 最重要的兩點是:函數就是對象;對象是經過函數(即構造函數)建立的。 

四、如何建立對象

     上面說到兩種引用類型之間的關係時談到,函數就是對象,對象又是經過構造函數建立的。怎麼感受那麼繞呢,這個問題像是先有雞仍是先有蛋的問題。這種問題能解釋清楚嗎?固然,但得一步步來吧,javascript中一切皆是對象,那就先從對象是如何建立的提及吧

     建立對象的方法有兩種,一種是使用new 操做符後跟Object 類型的構造函數 ,另外一種則是對象字面量方法(建立對象的一種快捷方式:簡化包含大量屬性的對象的建立過程,即語法糖)。

// 構造函數方法--建立
var  obj = new Object();//< == > obj = {}
obj.name = 'foo';
obj.method = function(){};

//對象字面量方法--建立
var obj = {
    name:'foo',
    method:function{console.log(this.name);}
}

   就這麼簡單,沒那麼簡單!!實際這中間發生了什麼? 

      對象是如何建立的, 即實例化一個對象的過程,都是經過 new 構造器來實現的。來看下下面這段代碼

 
var name = 'tom'
var Person = function(name,age){ this.name = name; this.age = age; } Person.prototype.say = function(){
  console.log(this.name);
} var p = new Person('jerry','10');
console.log(p);//
p.say();//jerry
console.log('一、全局window做用域下的變量name:'+name);//tom ---new Person 並無修改全局變量。

var p1 = Person('carry','12'); //若是把構造函數看成普通函數調用狀況下,this 指向當前運行環境window。
console.log(p1);//undefined
//p1.say();
console.log('二、全局window做用域下的變量name:'+name);//carry ---全局對象被修改
建立p對象大體能夠分爲四個階段:

 a、var p ={};

 b、p.__proto__ = Person.prototype;//原型鏈

 c、Person.call(p,arg1,arg2);//初始化p對象

d、返回p對象

這種狀況下,若是把構造函數看成普通函數調用狀況下,有什麼不一樣呢?

a、this 指向window(便可能修改全局變量)

b、不建立對象也不返回對象。//返回默認undefined

 

    本文接下來將重點討論幾種經過建立對象模式,這樣咱們就可以更深入理解對象與函數之間的關係的,同時對比這幾種模式的優缺點。

思考:基於Object構造函數,建立了一個對象,該對象包含兩個屬性,其中一個爲方法。若是須要不少相似obj的實例,那就會有許多重複的代碼。

所以建立對象引入新的模式,其中包括了幾種經典的模式;工廠模式,構造函數模式,原型模式,混合構造函數/原型模式,動態原型模式,寄生構造函數模式等。

4.1 工廠模式

/*工廠模*/
function person(name,age){
  var  obj = new Object();//經過Object構造器建立實例對象。
  obj.name = name;
  obj.age = age; 
  obj.say = function(){
    console.log(this.name);
  };
  return obj;
}

var person1 =   person('zhangsan','18');

var person2 =   person('lisi','3');
console.log(instanceOf person1)//object
console.log(instanceof person2)//object

 缺:a、每次調用person函數,都會經過該函數內部的對象obj建立新的對象,而後返回,除此以外,這個爲了建立新對象而存在的內部對象obj沒有其餘的用途。

         b、另外,沒法判斷工廠模式建立的對象的類型(即 沒法經過instanceOf等判斷區分,都爲Object 對象的一個實例 )

4.二、構造函數模式

var Car = function (model, year, miles) {
  this.model = model;
  this.year = year;
  this.miles = miles;
  this.run =function(){
    console.log(this.miles);
  }
};
var baoma = new Car("Tom", 2009, 20000);
var benchi = new Car("Dudu", 2010, 5000);
console.log(baoma.constructor == Car);//constructor位於構造函數原型中,並指向構造函數,結果爲true
console.log(baoma instanceof Car);//經過instanceof操做符,判斷baoma是否爲構造函數car的實例
console.log(baoma.run == benchi.run);//false 

      優: 對比工廠模式,能夠發現,這裏並不須要建立中間對象(obj),也沒有返回值。另外,能夠將構造函數的實例標識爲一種特定的類型(instanceOf Car),這就解決了工廠模式建立對象識別的問題(經過檢查實例的constructor屬性,或利用instanceof操做符檢查該實例是否經過某個構造函數建立)。 

      缺:不過經過構造函數建立仍然會有本身的問題,實際上,run方法在每一個實例上都會被從新建立一次,須要注意的是,經過new 構造函數,實例化建立的方法且並不相等,便可以經過比較baoma.run == benchi.run 獲得 false 就能判斷這兩個方法並不相等。

      next: 所以,雖然咱們能夠考慮將方法移到構造器外部(變爲全局函數)來解決這個問題。可是 在全局下建立的全局函數實際上只能被經由Person建立的實例調用,這就有點名存實亡了,控制使用權達不到僅限於當前對象的效果

4.3  原型模式 

    首先來理解下函數:ECMAScript 中,咱們聲明的每一個函數都是Function構造器(後面統一:實際爲構造函數)的一個實例,便可以理解爲,咱們聲明(不管是全局仍是局部)的每個函數都是new Function()建立出來的一個函數對象。

    這裏稍微跑下題:函數對象的有如下兩種屬性:

        內部屬性(便可以理解爲函數執行過程當中能訪問的局部變量) arguments this, arguments類數組包含屬性(callee);

        自身屬性:length(函數但願接收的參數個數)和prototype ,javascript中的每個函數(function)都包含一個指向prototype屬性的指針。

   (大部分瀏覽器中實例對象能夠經過__proto__訪問內部屬性),prototype屬性實際上也是一個對象,其中包含了由某種引用類型(Object/Function/自定義構造器)建立的全部實例共享的屬性和方法。如toString() ,valueOf();

     這裏強調下:大部分對象是能夠經過for in 來訪問其屬性的,不過,雖然prototype 是一個對象,但它是不可枚舉的,即不能夠經過for in 來遍歷訪問

     此外還有兩個非繼承而來的方法:call()、apply():做用是在指定的做用域執行函數,即從新設置函數內部屬性 上下文執行環境this,固然也可使用ECMAScript 5提供的bind 方法從新設置函數執行的上下文環境。

     好迴歸正題^^

4.3.1   定義prototype屬性

function Person() {}

    Person.name ='tom';

    Person.prototype.friends = ['jerry','miqi','carry'];

    Person.prototype.logName = function() {
      console.log(this.name);
    }
}

var person1 = new Person();
person1.logName();//'tom'   
for(i in person1) {console.log(i);}

  

以上代碼作了這幾件事情:

1.定義了一個構造器Person,Person函數自動得到一個prototype屬性,該屬性默認只包含一個指向Person的constructor屬性(固然大部分瀏覽器還會增長一個__proto__屬性)。

來看看瀏覽器中運行是什麼狀況,滷煮隨手在控制檯打印一些代碼並截圖。從下圖能夠看到函數的prototype屬性上包含constructor、__proto__這兩個屬性:constructor展開又有這兩個屬性,__proto__展開有一堆其它屬性+constructor:一層層指向,可以往下一直展開無窮盡也。頭大沒有,反正滷煮是暈了。後面再詳細討論這些屬性與對象和函數直接的關係和由來。

2.經過Person.prototype添加三個屬性,其中一個做爲方法;

3.建立一個Person的實例,隨後在實例上調用了logName()方法。!!!這裏須要注意的是logName()方法的調用過程:

    a.在person1實例上查找logName()方法,發現沒有這個方法,因而追溯到person1的原型prototype

    b.在person1的原型上查找logame()方法,有這個方法,因而調用該方法 。所以基於這樣一個查找過程,咱們也能夠經過在實例上定義原型中的同名屬性,來阻止該實例訪問原型上的同名屬性,須要注意的是,這樣作並不會刪除原型上的同名屬性,僅僅是阻止實例訪問

    c、接下來,利用for-in循環枚舉出實例能夠訪問到的全部屬性(不論該屬性存在於實例或是原型中),注意:這與上面提到的prototype 不可遍歷訪問並不衝突。至於爲何滷煮也不清楚^^。

好了,原型模式建立對象過程大概理解了,

    提問:那怎麼區分判斷某個屬性到底存在於實例上,仍是存在於原型中?

         咱們能夠看到__proto__列舉出了對象Object構造器的原型上繼承過來的方法:hasOwnProperty(),isPrototypeOf(),toString(),valueOf()等

       答1:  這裏咱們能夠利用hasOwnProperty()方法只有當屬性存在於實例中,纔會返回true:

   console.log(person1.hasOwnProperty('name'));//true

       答2:另外判斷屬性是否存在與某個實例對象,也能夠經過同時使用in操做符和hasOwnProperty()方法來判斷某個屬性存在於實例中仍是存在於原型中:

       console.log(('friends' in person1) && !person1.hasOwnProperty('friends'));

        //註釋:先判斷person1是否能夠訪問到friends屬性,若是能夠,再判斷這個屬性是否存在於實例當中(注意前面的!),若是不存在於實例中,就說明這個屬性存在於原型中。 

注意:hasOwnProperty來自Object的原型,是javascript中惟一一個在處理屬性時不查找原型鏈的方法

 

4.3.2 字面量方式(語法糖)重寫prototype屬性

    前面提到,原型屬性prototype也是對象,因此咱們能夠用對象字面量表示法書寫原型,以前爲原型添加代碼的寫法能夠修改成:

function Person(){//使用對象字面量 重寫Person 的原型 prototype屬性
}
Person.prototype = {
      name: 'tom',
      friends: ['jerry','miqi','carry'],
      logName: function() { 
           console.log(this.name); 
      }
}
var person1 = new Person();

//person1.logName();//'tom' 
//對象字面量重寫原型以後
console.log(person1.constructor);//function Object(){[native code]}構造函數
console.log(person1.constructor == Object);//true
console.log(person1.constructor == Person);//false
console.log(person1 instanceof Person);//true
console.log(person1 instanceof Object);//true

     因爲對象字面量語法重寫了整個prototype原型,原先經過建立構造函數(new Object())時默認取得的constructor屬性會指向Object構造函數,

      不過,instanceof操做符仍會返回但願的結果:console.log(person1 instanceof Person);//true

     所以,必要時能夠在原型中手動設置constructor的值來解決這個問題。

Person.prototype = {

    constructor: Person,
    ......

}
從上面代碼也能夠看出全部對象都是Object的一個實例

      經過原型模式建立也一樣存在一些問題:若是在建立對象實例以後修改原型對象prototype某個屬性或者重寫整個原型對象,那麼經過原型模式建立出來的對象在訪問原型上方法時會出現什麼樣的結果呢?

4.3.3 修改原型對象某個屬性

       結論是:對原型的修改會當即在全部對象實例中反映出來:

var person1 = new Person();

Person.prototype.name = 'tidy';//修改原型name屬性

console.log(person1.name);//'tidy'

      實例和原型之間的鏈接僅僅是一個指針,而不是一個原型的拷貝。因此其實是在實例和原型上一次搜索過程,對原型對象的所作的任何修改都會在全部對象實例中反映出來,就算在建立實例以後修改原型,也是如此。

 

4.3.4   若是在建立對象實例以後重寫原型對象,狀況又會如何?

function Person() {};

var person1 = new Person1();//建立的實例引用的是最初的原型

//重寫了原型
Person.prototype = {
    friends: ['小花','二狗子','二愣子']
}

var person2 = new Person();//這個實例引用新的原型

console.log(person2.friends);

console.log(person1.friends);  

        以上代碼在執行到最後一行時會出現未定義錯誤,若是咱們用for-in循環枚舉person1中的可訪問屬性時,會發現,裏頭空無一物,可是person2卻能夠訪問到原型上的friends屬性。 !重寫原型切斷了現有原型與以前建立的全部對象實例的聯繫,以前建立的對象實例的原型還在,只不過是舊的

//建立person1時,原型對象還未被重寫,所以,原型對象中的constructor仍是默認的Person()
console.log(person1.constructor);//Person()

//可是person2的constructor指向Object()
console.log(person2.constructor);//Object()  

      須要注意的是,原型模式忽略了爲構造函數傳遞參數的過程,全部的實例都取得相同的屬性值。同時,原型模式還存在着一個很大的問題,就是原型對象中的引用類型值會被全部實例共享,對引用類型值的修改,也會反映到全部對象實例當中。

function Person() {};
Person.prototype = {
    friends:['小花','二狗子','二愣子']
} 

var person1 = new Person();
var person2 = new Person(); person1.friends.push('傻妞'); console.log(person2.friends);//['小花','二狗子','二愣子','傻妞']

  

   從上述代碼能夠看出:修改person1的引用類型值friends,意味着person2中的friends也會發生變化,實際上,原型中保存的friends實際上只是一個指向堆中friends值的指針(這個指針的長度是固定的,保存在棧中),實例經過原型訪問引用類型值時,也是按指針訪問,而不是訪問各自實例上的副本(這樣的副本並不存在)。

4.4.組合使用構造函數和原型模式建立對象 

      上面討論了構造函數和原型模式建立對象優點和缺點,發現能夠將構造函數和原型模式的優勢結合起來,彌補各自的不足,利用構造函數傳遞初始化參數,在其中定義實例屬性,利用原型定義公用方法和公共屬性,該模式應用最爲普遍。

function Person(name,age) {
    this.name = name;
    this.age = age;
    this.friends = ['ajiao','jianjian','pangzi'];
}

Person.prototype = 
    constructor: Person,
    logName: function() {
        console.log(this.name);
    }
}

var person1 = new Person('evansdiy','22');
var person2 = new Person('amy','21');
person1.logName();//'evansdiy'
person1.friends.push('haixao');
console.log(person2.friends.length);//3

 4.5.原型動態模式 

       原型動態模式將須要的全部信息都封裝到構造函數中,經過if語句判斷原型中的某個屬性是否存在,若不存在(在第一次調用這個構造函數的時候),執行if語句內部的原型初始化代碼。

function Person(name,age) {
    this.name = name;
    this.age = age;
    if(typeof this.logName != 'function') {
        Person.prototype.logName = function() {
            console.log(this.name);
        };
        Person.prototype.logAge = function() {
            console.log(this.age);
        };
    };

}

var person1 = new Person('evansdiy','22');//初次調用構造函數,此時修改了原型

var person2 = new Person('amy','21');//此時logName()方法已經存在,不會再修改原型

須要注意的是,該模式不能使用對象字面量語法書寫原型對象(這樣會重寫原型對象)。若重寫原型,那麼經過構造函數建立的第一實例能夠訪問的原型對象不會包含if語句中的原型對象屬性。

function Person(name,age) {
    this.name = name;
    this.age = age;

    if(typeof this.logName != 'function') {
        Person.prototype = {
            logName: function() {
                console.log(this.name);
            },
            logAge: function() {
                console.log(this.Age);
            }
        }
    };

}

var person1 = new Person('evansdiy','22');
var person2 = new Person('amy','21');

person2.logName();//'amy'

person1.logName();//logName()方法不存在

     須要說明的是,各模式都有本身的應用場景,無所謂優劣。 

 

五、如何區分這些對象?

     上面討論 對象如何建立的問題,接下來討論的是如何區分這些對象(無論是object,仍是function),這些對象有什麼本質性區別。(注:本文討論僅限於原生對象)

思考一: 有幾類對象?

      思路:JavaScript規範裏寫着有三種對象: 原生對象, 內置對象(屬於原生對象); 宿主對象(本文不具體討論,這是運行環境(瀏覽器)有關)

     上文提到:在 Javascript沒有使用常見的類繼承體系,而是使用「構造器」來實現對象的類型化。從對象類型系統能夠看出,原生對象中包含13內置對象,9個構造器

     所以能夠簡單理解爲:兩類對象

           一、函數function:普通函數/構造函數(構造器)——由Function構造器建立的實例函數對象。

           2、普通實例對象object:---由自定義構造函數或內置構造器建立

 

思考二:有幾類構造函數?

    思路:根據對象分類都是由構造函數,大體可分爲:自定義構造器,內置構造器(包括Function 和Object、)

 

思考三:這些對象都有什麼屬性,對象與構造器之間有什麼聯繫,屬性是如何訪問/繼承(原型鏈概念)過來的?

    思路:先來理解三個概念:原型、內部初始化屬性、構造屬性

      一、prototype,每個函數(構造器)都有一個顯式的prototype屬性,它表明了對象的原型(Function.prototype是個例外,沒有prototype屬性)。

      二、 __proto__: __ptoto__屬性(IE瀏覽器不支持)是實例對象指向原型對象的一個指針,它的做用就是指向構造函數的原型屬性prototype,經過這兩個屬性,就能夠訪問原型裏的屬性和方法了。

Javascript中的對象實例本質上是由一系列的屬性組成的,在這些屬性中,每一個實例對象都有一個的內部隱藏屬性[[prototype]](瀏覽器經過__proto__把這個屬性暴露出來了),指向於它所對應構造器的的原型對象(chrome、firefox中名稱爲__proto__,而且能夠被訪問到)。原型鏈正是基於__proto__才得以造成。

      三、constructor: 返回建立此對象的構造函數的引用,即指向對象的構造函數

思考四:__proto__ 是什麼?

      每一個實例對象都會在其內部初始化一個屬性[[prototype]],大部分瀏覽器器就是__proto__來實現,當咱們訪問一個對象的屬性時,若是這個對象內部不存在這個屬性,那麼他就會去__proto__裏找這個屬性,這個__proto__又會有本身的__proto__,因而就這樣 一直找下去,也就是咱們平時所說的原型鏈的概念。不過值得注意的是:按照標準,__proto__是不對外公開的,也就是說是個私有屬性。不過,大部分瀏覽器都對外暴露了這個屬性。

    思考結果:

            a、「一切(引用類型)皆爲對象」

            b、 「每一個函數(function)都有一個prototype」

            c、 「每一個對象(object)都有一個__proto__」

       

5.一、函數/構造函數 —— 函數也是一個對象。

先區分下普通函數與構造函數:與普通函數相比,構造函數有如下特色(不成文默認使用規範)

      a.用new關鍵字調用

      b.函數內部可使用this關鍵字

      c.默認不用return返回值

      d.函數命名建議首字母大寫,與普通函數區分開。

固然,構造函數也能夠當作普通函數來使用,不過內部使用this 就跟當前運行上下文綁定到一塊兒了

 

5.1.1  全部自定義構造器/函數實際上也是一個對象,這裏比較特殊的是它的__proto__都指向Function.prototype   

      JavaScript中有內置(build-in)構造器/對象共計13個(ES5中新加了JSON,不包括null),這裏列舉了可訪問的9個構造器(可以使用new 建立實例對象)。剩下如Global不能直接訪問,Arguments僅在函數調用時由JS引擎建立,Math,JSON是以對象形式存在的,無需new。它們的__proto__是Object.prototype。以下

  

Number.__proto__ === Function.prototype //true 
Boolean.__proto__ === Function.prototype //true
String.__proto__ === Function.prototype //true
Object.__proto__ === Function.prototype//true
Function.__proto__ === Function.prototype //true
Array.__proto__ === Function.prototype // true
RegExp.__proto__ === Function.prototype // true
Error.__proto__ === Function.prototype // true
Date.__proto__ === Function.prototype // true
//普通函數對象+自定義構造函數
var func1 = function(){console.log()} 
function Person(name){
    this.name = name;
   this.logName = function(){};
}
console.log(func1.__proto__ === Function.prototype);//true 
console.log(func2.__proto__ === Function.prototype);//true
//結論全部自定義構造器/函數和內置構造器實際上也是一個實例對象(構造函數Function 的實例對象)
//它內部初始化屬性__proto__都指向Function.prototype  
(function(){console.log(arguments.__proto__ == Object.prototype)})() //true
Math.__proto__ === Object.prototype  // true
JSON.__proto__ === Object.prototype  // true

  

    從上面例子能夠看出什麼?全部的函數/構造函數(構造器)都來自於Function.prototype,甚至包括根構造器Object及Function自身(通俗的講,函數/構造函數都是構造器Function的一個實例)。所以,全部構造器都繼承了Function.prototype的屬性及方法。如length、call、apply、bind(ES5)等屬性或方法。

   那咱們可能會產生疑問,全部函數/構造器都來自於 Function.prototype ,prototype 自己也是一個不可枚舉對象(全部對象都有一個隱藏內部屬性,指向其對應構造函數的原型),它又是由那個構造器建立的?

 console.log(Function.prototype.__proto__ === Object.prototype) // true

  一目瞭然,原來是來自於Object.prototype, 好吧,追根朔源,Object.prototype的__proto__。

Object.prototype.__proto__ === null  // true  

      好吧到頂了,無法再繼續了》》

 5.1.2 上面討論了JavaScript引擎內置構造器和自定義構造的__proto__,全部對象的__proto__都指向其構造器的prototype;下面再來看下經過這些構造器建立實例對象的__proto__

var date = new Date();
var error = new Error();
var person = new Person();
console.log(date.__proto__ === Date.prototype);//true
console.log(error.__proto__ === Error.prototype);//true
console.log(person.__proto__ === Person.prototype);//true 

5.2 全部對象(包括函數/構造函數(function)和普通實例對象(object))。

注意:每一個實例對象都又有一個constructor屬性,能夠獲取它的構造器。即我以看下下面這段代碼

//函數對象/構造函數--
Number.constructor === Function // true
Boolean.constructor === Function// true
String.constructor === Function// true
Object.constructor === Function// true
Function.constructor === Function// true
Array.constructor === Function// true
RegExp.constructor === Function// true
Error.constructor === Function// true
Date.constructor === Function// true
Function.constructor=== Function //true 即: Function.constructor === Function
 
//實例對象
var num = new Number(2);
var date = new Date();
var str = new String();
var obj = new Object();
var func1 = new Function();
var error = new Error();
var arr = new Array();
var rep = new RegExp();
var person = new Person();
num.constructor. __proto__ === Function.prototype // true
date.constructor. __proto__ === Function.prototype // true
str.constructor. __proto__ === Function.prototype // true
obj.constructor. __proto__ === Function.prototype // true
error.constructor. __proto__ === Function.prototype // true
func1.constructor. __proto__ === Function.prototype // true
arr.constructor. __proto__ === Function.prototype // true 
rep.constructor. __proto__ === Function.prototype // true
person.constructor. __proto__ === Function.prototype; 

  

從而能夠得出結論:

     person.__proto__ === Perosion.protoype === person.constructor.prototype  即 指向同一個對象

上面提到過:用過字面量方式重寫原型對象prototype的值會修改prototype的執行爲Object

function Person(name) {
    this.name = name
}
// 重寫原型
Person.prototype = {
    getName: function() {}
}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false   
//等價於  對象直接量{getName: function(){}}與根構造器 Object.protype 的空對象 {}比較

console.log(Object === p.constructor.prototype) // true  是由於 重寫原型以後修改了constructor屬性
//  建議重寫原型對象默認增長constructor屬性,修正原型指向
Person.prototype = {
  constructor:Person,
    getName: function() {}
}

注意:__proto__目前在IE6/7/8/9中都不支持。  

 

六、內存圖解prototype、__proto__、constructor與Object、Function

      這樣將幫助咱們更加容易理解原型繼承原理,及原型鏈查找屬性方法的過程。很少說,上圖上代碼:

6.1 關於上面提到的函數對象,咱們來看如下例子,來講明:

        var o1 = {};
        var o2 =new Object();
        
        function f1(){}
        var f2 = function(){}
        var f3 = new Function('str','console.log(str)');   
        f3('aabb');   // aabb
        typeof Object //function
        typeof Function //function
        typeof o1 //object
        typeof o2 //object
        typeof f1//function
        typeof f2//function
        typeof f3 //function

 

  • a、一般咱們認爲o一、o2是對象,即普通對象(由Object或自定義構造函數建立);f一、f二、f3爲函數。
  • b、可是其實函數也是對象,是由Function構造的,
  • c、f3這種寫法就跟對象的建立的寫法同樣。f一、f2最終也都像f3同樣是有Function這個函數構造出來的
  • d、f一、f二、f3爲函數對象,Function跟Object自己也是函數對象。
        Js中每一個對象(null除外)都和另外一個對象相關聯, 經過如下例子跟內存效果圖來分析Function、Object、Prototype、__proto__對象 間的關係。
function Animal(){       
    }
    var  anim = new Animal();
    
    console.log('***********Animal anim proto*****************');
    typeof Animal.prototype  //object 
    anim.__proto__===Animal.prototype  //true
    Animal.__proto__===Function.prototype));  //true
    Animal.prototype.__proto__===Object.prototype));  //true
    
    console.log('***********Function proto*****************');
    typeof Function.prototype  //function
    typeof Function.__proto__  //function
    typeof Function.prototype.prototype   //undefined
    typeof Function.prototype.__proto__   //object
    Function.prototype===Function.__proto__ //true

    console.log('***********Object proto*****************');
   typeof Object.prototype)   //object
   typeof Object.__proto__  //function
   Object.prototype.prototype  //undefied
    Object.prototype.__proto__===null;  //null

    console.log('***********Function Object  proto關係*****************');
   Function.prototype===Object.__proto__;   //true
   Function.__proto__===Object.__proto__;   //true
   Function.prototype.__proto__===Object.prototype;   //true
Function、Object、Prototype、__proto__內存關係圖


        上面的內存圖跟堆棧結構能夠參照文章 Javascript_01_理解內存分配
        堆區圖說明:
 
         Function.prototype函數對象圖內部表示prototype屬性的 紅色虛框 ,只是爲了說明這個屬性不存在。

          經過上圖Function、Object、Prototype關係圖中,能夠得出一下幾點:
  1. 全部對象全部對象,包括函數對象的原型鏈最終都指向了Object.prototype,而Object.prototype.__proto__===null,原型鏈至此結束。
  2. Animal.prototype是一個普通對象。
  3. Object是一個函數對象,也是Function構造的,Object.prototype是一個普通對象
  4. Object.prototype.__type__指向null。
  5. Function.prototype是一個函數對象,前面說函數對象都有一個顯示的prototype屬性,可是Function.prototype卻沒有prototype屬性,即Function.prototype.prototype===undefined,全部Function.prototype函數對象是一個特例,沒有prototype屬性。
  6. Object雖是Function構造的一個函數對象,可是Object.prototype沒有指向Function.prototype,即Object.prototype!==Function.prototype。

6.2   Prototype跟Constructor關係
介紹
         在 JavaScript 中,每一個函數對象都有名爲「prototype」的屬性 (上面提到過Function.prototype函數對象是個例外,沒有prototype屬性) ,用於引用原型對象。此原型對象又有名爲「constructor」的屬性,它反過來引用函數自己。這是一種循環引用(i.e.  Animal.prototype.constructor===Animal)。
 
         經過如下例子跟內存效果圖來分析Prototype、constructor 間的關係。
    console.log('**************constructor****************'); 
    console.log('anim.constructor===Animal:'+(anim.constructor===Animal))    ;    //true
    console.log('Animal===Animal.prototype.constructor:'+(Animal===Animal.prototype.constructor))    ;    //true
    console.log('Animal.constructor===Function.prototype.constructor:'+(Animal.constructor===Function.prototype.constructor));   //true
    console.log('Function.prototype.constructor===Function:'+(Function.prototype.constructor===Function));    //true
    console.log('Function.constructor===Function.prototype.constructor:'+(Function.constructor===Function.prototype.constructor));    //true

    console.log('Object.prototype.constructor===Object:'+(Object.prototype.constructor===Object));    //true
    console.log('Object.constructor====Function:'+(Object.constructor===Function));    //true

  


 prototype、constructor內存關係圖(在Function、Object、Prototype關係圖上加入constructor元素):


        上圖中,紅色箭頭表示函數對象的原型的constructor所指向的對象。
  1. 注意Object.constructor===Function;自己Object就是Function函數構造出來的        
  2. 如何查找一個對象的constructor,就是在該對象的原型鏈上尋找碰到的第一個constructor屬性所指向的對象。

 

 

 


七、函數/構造器從Function 繼承什麼屬性, 普通對象從Object獲取到了什麼屬性。

也許上面這些還不夠味,對象建立都能構造器原型繼承隱藏屬性的,這些隱藏屬性是什麼?

下面經過兩張圖來看下具體從這兩個引用類型得到什麼屬性:

7.1 這張圖展現__proto__(object類型--構造器爲Object)從Object構造器繼承一系列屬性方法

 

   滷煮在控制檯定義了一個普通函數,並打印出來這個函數原型prototype,發現prototype也是個對象,這個對象包含兩個屬性:瀏覽器對外暴露的對象內部私有化屬性__proto__和構造器constructor;其中constructor是一個函數對象,__proto__是一個普通對象(構造器爲Object,所以繼承了Object的許多方法)。

 

 

7.2 這張圖展現constructor (function類型--構造器爲Function)從Function構造器繼承一系列屬性方法

 

 

 

      從上圖能夠看出 constructor 是一個函數對象,實際上 (function(){}).constructor.__proto__ = Function.prototype / (function(){}).__proto__  = Function.prototype,即任何一個函數的__proto__都指向Function.prototype, 任何一個對象的constructor(實際也爲函數)的__proto__都指向Function.prototype(圖中也很直觀顯示constructor 的構造器爲 Function)

    爲何要說上面這一段呢?由於 在控制檯 打印  console.log((function(){}).__proto__) 並不能直觀看到Function的原型是什麼--->因此只能間接經過constructor 查看Function.prototype屬性:

        apply:

        call:

        bind:

        length:

        name:

        arguments: 

 

總結:

一、全文下來:都是在說兩個引用類型(javascript 一切皆是對象):函數(function)和對象(object) ——函數對象,普通實例對象。簡單的說能夠用typeof()來區分:function/object

二、全部對象都包含一個內部私有化屬性___proto__ 和constructor

三、函數都包含一個prototype屬性,同時做爲一個對象,他也有__proto__ 和constructor屬性

四、幾個特殊的對象:

    a、prototype:做爲函數對象的屬性,本質上也是一個實例對象,因此也有__proto__ 和constructor屬性,特別的Function.prototype 是函數,但沒有prototype屬性;

    b、__proto__ :做爲全部對象都有的內部初始化屬性(瀏覽器對外暴露的屬性),指向對象的構造函數的原型prototype。

            大體分兩種狀況:普通實例對象__proto__ 指向其構造器的prototype,函數對象__proto__ 均爲:Function.prototype

    c、constructor:做爲全部對象一個屬性,本質上是一個函數,指向對象的構造器(三種):object function 自定義構造器;特別的 prototype 做爲一個對象,其constructor又反過來引用函數自己

         即   func.prototype.constructor = func  所以能夠理解爲 原型對象prototype的 constructor是有對應函數構造的 

五、做爲普通實例對象從構造器Object得到什麼屬性或方法:
    defineProperty:定義屬性
    hasProperty:判斷是否擁有屬性
    isPrototypeOf():
    propertyIsEnnumberable():
      另外還有:toString(),toLocaleString(),valueOf() 

六、做爲函數從構造器Function得到哪些屬性方法:

    apply,call,bind,length, name,arguments,this。

七、經過 __proto__ 造成原型鏈。

相關文章
相關標籤/搜索