javascript中的prototype和__proto__的理解

  在工做中有時候會看到prototype和__proto__這兩個屬性,對這兩個屬性我一直比較蒙圈,可是我經過查閱相關資料,決定作一下總結加深本身的理解,寫得不對的地方還請各位大神指出。函數

一、prototype

   每一個函數都有一個prototype屬性,該屬性是一個指針,指向一個對象。 而這個對象的用途是包含由特定類型的全部實例共享的屬性和方法。使用這個對象的好處就是可讓全部實例對象共享它所擁有的屬性和方法。這個屬性只用js中的類(或者說可以做爲構造函數的對象)纔會有this

二、 __proto__

   每一個實例對象都有一個__proto__屬性,用於指向構造函數的原型對象。__proto__屬性是在調用構造函數建立實例對象時產生的。該屬性存在於實例和構造函數的原型對象之間,而不是存在於實例於構造函數之間。spa

function Person(name, age, job){    
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        console.log(this.name);
    }; // 與聲明函數在邏輯上是等價的
}
var person1=new Person("Nicholas",29,"Software Engineer");
console.log(person1);
console.log(Person);
console.log(person1.prototype);//undefined
console.log(person1.__proto__);
console.log(Person.prototype);
console.log(person1.__proto__===Person.prototype);//true
複製代碼
輸出結果以下:

  總結:prototype

一、調用構造函數建立的實例對象的prototype屬性爲"undefined",構造函數的prototype是一個對象。
二、__proto__屬性是在調用構造函數建立實例對象時產生的。
三、調用構造函數建立的實例對象的__proto__屬性指向構造函數的prototype,本質上就是繼承構造函數的原型屬性
四、在默認狀況下,全部原型對象都會自動得到一個constructor(構造函數)屬性,這個屬性包含一個指向prototype屬性所在函數的指針。3d

下圖展現了使用Person構造函數建立實例後各個對象之間的關係

  上圖展現了 Person 構造函數、 Person 的原型屬性以及 Person現有的兩個實例之間的關係。

三、 跟__proto__屬性相關的兩個方法

isPrototypeOf(): 雖然在全部實現中都沒法訪問到__proto__,但能夠經過 isPrototypeOf()方法來肯定對象之間是否存在這種關係。指針

alert(Person.prototype.isPrototypeOf(person1)); //true
    alert(Person.prototype.isPrototypeOf(person2)); //true
複製代碼

Object.getPrototypeOf(): 在全部支持的實現中,這個方法返回__proto__的值。例如:code

alert(Object.getPrototypeOf(person1) == Person.prototype); //true
    alert(Object.getPrototypeOf(person1).name); //"Nicholas"  person1
    是下面代碼中的實例
複製代碼

  注意:雖然能夠經過對象實例訪問保存在原型中的值,但卻不能經過對象實例重寫原型中的值。若是咱們在實例中添加了一個屬性,而該屬性與實例原型中的一個屬性同名,那咱們就在實例中建立該屬性,該屬性將會屏蔽原型中的那個屬性。請看下面的例子:cdn

function Person(){
    }
    Person.prototype.name = "Nicholas";
    Person.prototype.age = 29;
    Person.prototype.job = "Software Engineer";
    Person.prototype.sayName = function(){
        alert(this.name);
    };
    var person1 = new Person();
    var person2 = new Person();
    person1.name = "Greg";
    alert(person1.name); //"Greg"—— 來自實例
    alert(person2.name); //"Nicholas"—— 來自原型
複製代碼

四、 判斷屬性是存在實例對象中,仍是存在原型對象中,有如下方法

hasOwnProperty():能夠檢測一個屬性是存在於實例中,仍是存在於原型中。返回值爲true表示該屬性存在實例對象中,其餘狀況都爲false。對象

in 操做符:不管該屬性存在於實例中仍是原型中。只要存在對象中,都會返回true。可是能夠同時使用 hasOwnProperty()方法和 in 操做符,就能夠肯定該屬性究竟是存在於對象中,仍是存在於原型中。blog

var person1 = new Person();
    var person2 = new Person();
    alert(person1.hasOwnProperty("name")); //false
    alert("name" in person1); //true
    person1.name = "Greg";
    alert(person1.name); //"Greg" —— 來自實例
    alert(person1.hasOwnProperty("name")); //true
    alert("name" in person1); //true
    alert(person2.name); //"Nicholas" —— 來自原型
    alert(person2.hasOwnProperty("name")); //false
    alert("name" in person2); //true
    delete person1.name;
    alert(person1.name); //"Nicholas" —— 來自原型
    alert(person1.hasOwnProperty("name")); //false
    alert("name" in person1); //true
複製代碼

五、 獲取或遍歷對象中屬性的幾種方法

for-in: 經過for-in循環的返回的是可以被訪問的、可枚舉的屬性,無論該屬性是在實例中,仍是存在原型中。

function Person(name, age, job) {
		this.name = name;
		this.age = age;
		this.job = job;	
	}
	Person.prototype={
		sayName:function(){
			return this.name;
		}
	}
	var p=new Person("李明",30,"詩人");
	for(var prop in p){
		console.log(prop);//name、age、job、sayName
	}
    console.log(Object.keys(p));//["name", "age", "job"]
    console.log(Object.keys(Person.prototype));//["sayName"]
    console.log(Object.getOwnPropertyNames(Person.prototype))
    // ["constructor", "sayName"] 
複製代碼

Object.keys(): 取得實例對象上全部可枚舉的屬性Object.getOwnPropertyNames(): 獲取實例對象全部屬性,不管它是否可枚舉。

  注意:使用對象字面量來重寫整個原型對象時,本質上徹底重寫了默認的 prototype 對象,所以 constructor 屬性也就變成了新對象的 constructor 屬性(指向 Object 構造函數),再也不指向 Person。可是能夠經過在重寫原型對象時指定constructor屬性,使之仍是指向原來的constructor。此時,儘管 instanceof 操做符還能返回正確的結果,但經過 constructor 已經沒法肯定對象的類型了。

object instanceof constructor: 檢測 constructor.prototype 是否存在於參數 object 的原型鏈上。

function Person() {}
    var friend2 = new Person();
    Person.prototype = {
    	//constructor : Person,
    	name: "Nicholas",
    	age: 29,
    	job: "Software Engineer",
    	sayName: function() {
    		alert(this.name);
    	}
    };
    var friend = new Person();
    console.log(friend2 instanceof Object); //true
    console.log(friend2 instanceof Person); //false,
    console.log(friend2.constructor == Person); //true
    console.log(friend2.constructor == Object); //false
    
    console.log(friend instanceof Object); //true
    console.log(friend instanceof Person); //true
    console.log(friend.constructor == Person); //false
    console.log(friend.constructor == Object); //true
複製代碼

  因爲原型的動態性,調用構造函數時會爲實例添加一個指向最初原型的Prototype指針,而把原型修改成另一個對象就等於切斷了構造函數與最初原型之間的聯繫。看下面的例子

function Person(){
    }
    var friend = new Person();
    Person.prototype = {
        constructor: Person,
        name : "Nicholas",
        age : 29,
        job : "Software Engineer",
        sayName : function () {
            alert(this.name);
        }
    };
    var friend2=new Person();
    friend.sayName(); //Uncaught TypeError: friend.sayName is not a function 
    friend2.sayName();//Nicholas
    console.log(friend instanceof Person);//false
    console.log(friend instanceof Object);//true
    console.log(friend2 instanceof Person);//true
複製代碼

  結果分析:這是由於friend1的prototype指向的是沒重寫Person.prototype以前的Person.prototype,也就是構造函數最初的原型對象。而friend2的prototype指向的是重寫Person.prototype後的Person.prototype。以下圖所示

六、 原型鏈

  基本思想是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。最直觀的表現就是讓原型對象等於另外一個類型的實例。
function SuperType(){
        this.property = true;
    }
    SuperType.prototype.getSuperValue = function(){
        return this.property;
    };
    function SubType(){
        this.subproperty = false;
    }
    //繼承了 SuperType
    SubType.prototype = new SuperType();
    SubType.prototype.getSubValue = function (){
        return this.subproperty;
    };
    var instance = new SubType();
    alert(instance.getSuperValue()); //true
複製代碼

  SubType.prototype = new SuperType();這句代碼使得原來存在於 SuperType 的實例中的全部屬性和方法,如今也存在於 SubType.prototype 中。使得instance的constructor指向了SuperType。

console.log(instance.constructor===SuperType);//true
複製代碼

  總結: 訪問一個實例屬性時,首先會在實例中搜索該屬性。若是沒有找到該屬性,則會繼續搜索實例的原型。在經過原型鏈實現繼承的狀況下,搜索過程就得以沿着原型鏈繼續向上。在找不到屬性或方法的狀況下,搜索過程老是要一環一環地前行到原型鏈末端纔會停下來。

就拿上面的例子來講,調用 instance.getSuperValue()會經歷4個搜索步驟:

  1. 搜索instance實例;
  2. 搜索 SubType.prototype;
  3. 搜索SuperType的實例;
  4. 搜索 SuperType.prototype,最後一步纔會找到該方法。
相關文章
相關標籤/搜索