javascript中的prototype和__proto__的理解

慕斯不想說話發表於2019-04-06

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

1、prototype

   每個函式都有一個prototype屬性,該屬性是一個指標,指向一個物件。 而這個物件的用途是包含由特定型別的所有例項共享的屬性和方法。使用這個物件的好處就是可以讓所有例項物件共享它所擁有的屬性和方法

2、 __proto__

   每個例項物件都有一個__proto__屬性,用於指向建構函式的原型物件。__proto__屬性是在呼叫建構函式建立例項物件時產生的。
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
複製程式碼
輸出結果如下:

javascript中的prototype和__proto__的理解

  總結:

1、呼叫建構函式建立的例項物件的prototype屬性為"undefined",建構函式的prototype是一個物件。
2、__proto__屬性是在呼叫建構函式建立例項物件時產生的。
3、呼叫建構函式建立的例項物件的__proto__屬性指向建構函式的prototype,本質上就是繼承建構函式的原型屬性
4、在預設情況下,所有原型物件都會自動獲得一個constructor(建構函式)屬性,這個屬性包含一個指向prototype屬性所在函式的指標。

下圖展示了使用Person建構函式建立例項後各個物件之間的關係

javascript中的prototype和__proto__的理解
  上圖展示了 Person 建構函式、 Person 的原型屬性以及 Person現有的兩個例項之間的關係。

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

isPrototypeOf(): 雖然在所有實現中都無法訪問到__proto__,但可以通過 isPrototypeOf()方法來確定物件之間是否存在這種關係。

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

Object.getPrototypeOf(): 在所有支援的實現中,這個方法返回__proto__的值。例如:

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

  注意:雖然可以通過物件例項訪問儲存在原型中的值,但卻不能通過物件例項重寫原型中的值。如果我們在例項中新增了一個屬性,而該屬性與例項原型中的一個屬性同名,那我們就在例項中建立該屬性,該屬性將會遮蔽原型中的那個屬性。請看下面的例子:

    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"—— 來自原型
複製程式碼

4、 判斷屬性是存在例項物件中,還是存在原型物件中,有以下方法

hasOwnProperty():可以檢測一個屬性是存在於例項中,還是存在於原型中。返回值為true表示該屬性存在例項物件中,其他情況都為false。

in 操作符:無論該屬性存在於例項中還是原型中。只要存在物件中,都會返回true。但是可以同時使用 hasOwnProperty()方法和 in 操作符,就可以確定該屬性到底是存在於物件中,還是存在於原型中。

    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
複製程式碼

5、 獲取或遍歷物件中屬性的幾種方法

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。如下圖所示

javascript中的prototype和__proto__的理解

6、 原型鏈

  基本思想是利用原型讓一個引用型別繼承另一個引用型別的屬性和方法。最直觀的表現就是讓原型物件等於另一個型別的例項。
    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,最後一步才會找到該方法。

相關文章