JavaScript 原型 與 原型鏈

原型

什麼是JS原型?javascript

若是對JS原型解釋的話,會涉及到兩個概念:構造函數,原型對象java

  • 構造函數

通俗的說,就是你在script標籤裏面聲明的那個函數git

<script>


function Test(){
	// 我就是構造函數
}


</script>
複製代碼
  • 原型對象

在聲明瞭一個函數以後,瀏覽器會自動按照必定的規則建立一個對象,這個對象就叫作原型對象。這個原型對象實際上是儲存在了內存當中數組

在聲明瞭一個函數後,這個構造函數(聲明瞭的函數)中會有一個屬性prototype,這個屬性指向的就是這個構造函數(聲明瞭的函數)對應的原型對象;原型對象中有一個屬性constructor,這個屬性指向的是這個構造函數(聲明瞭的函數)瀏覽器

以下圖:bash

圖片加載失敗!

使用構造函數建立對象函數

咱們的構造函數使用new來建立對象的時候,就像下面這樣:ui

<script>

 	function students(){
 		// 我就是構造函數
 	}

 	let stu = new students();

</script>
複製代碼

此時,stu就是那個構造函數students建立出來的對象,這個stu對象中是沒有prototype屬性的,prototype屬性只有在構造函數students中有,看圖!this

圖片加載失敗!

圖片加載失敗!

能夠看出,構造函數students中有prototype屬性,指向的是students對應的原型對象;而stu是構造函數students建立出來的對象,他不存在prototype屬性,因此在調用prototype的時候的結構是undefined,但stu有一個__proto__屬性,stu調用這個屬性能夠直接訪問到構造函數students的原型對象(也就是說,stu的__proto__屬性指向的是構造函數的原型對象),看圖:spa

圖片加載失敗!

說明:

  • 從上面的代碼中能夠看到,建立stu對象雖然使用的是students構造函數,可是對象建立出來以後,這個stu對象其實已經與students構造函數沒有任何關係了,stu對象的__proto__屬性指向的是students構造函數的原型對象
  • 若是使用new students()建立多個對象stu一、stu二、stu3,則多個對象都會同時指向students構造函數的原型對象
  • 咱們能夠手動給這個原型對象添加屬性和方法,那麼stu1,stu2,stu3…這些對象就會共享這些在原型中添加的屬性和方法
  • 若是咱們訪問stu中的一個屬性name,若是在stu對象中找到,則直接返回。若是stu對象中沒有找到,則直接去stu對象的__proto__屬性指向的原型對象中查找,若是查找到則返回。(若是原型中也沒有找到,則繼續向上找原型的原型—原型鏈)
  • 若是經過stu對象添加了一個屬性name,則stu對象來講就屏蔽了原型中的屬性name。 換句話說:在stu中就沒有辦法訪問到原型的屬性name了
  • 經過stu對象只能讀取原型中的屬性name的值,而不能修改原型中的屬性name的值。 stu.name = 「李四」; 並非修改了原型中的值,而是在stu對象中給添加了一個屬性name

開始擼代碼:

<script type="text/javascript">
	function students () {        
	}
	// 可使用students.prototype 直接訪問到原型對象
	//給students函數的原型對象中添加一個屬性 name而且值是 "張三"
	students.prototype.name = "張三";
	students.prototype.age = 20;

	var stu = new students();
	/*
		訪問stu對象的屬性name,雖然在stu對象中咱們並無明確的添加屬性name,可是
		stu的__proto__屬性指向的原型中有name屬性,因此這個地方能夠訪問到屬性name
		就值。
		注意:這個時候不能經過stu對象刪除name屬性,由於只能刪除在stu中刪除的對象。
	*/
	alert(stu.name);  // 張三

	var stu1 = new students();
	alert(stu1.name);  // 張三  都是從原型中找到的,因此同樣。

	alert(stu.name === stu1.name);  // true

	// 因爲不能修改原型中的值,則這種方法就直接在stu中添加了一個新的屬性name,而後在stu中沒法再訪問到
	//原型中的屬性。
	stu.name = "李四";
	alert(stu.name); //李四
	// 因爲stu1中沒有name屬性,則對stu1來講仍然是訪問的原型中的屬性。    
	alert(stu1.name);  // 張三  
</script>
複製代碼

與原型有關的幾個方法:

  • prototype屬性

prototype 存在於構造函數中 (其實任意函數中都有,只是否是構造函數的時候prototype咱們不關注而已) ,他指向了這個構造函數的原型對象

  • constructor屬性

constructor屬性存在於原型對象中,他指向了構造函數

以下代碼:

<script type="text/javascript">
    function students () {
    }
    alert(students.prototype.constructor === students); // true
</script>
複製代碼
  • _ _ proto_ _ 屬性(注意:左右各是2個下劃線)

用構造方法建立一個新的對象以後,這個對象中默認會有一個屬性__proto__, 這個屬性就指向了構造方法的原型對象


原型鏈

說完原型後,趁熱打鐵,說一下原型鏈

在js中,萬物皆對象,對象能夠說是重中之重了。每個對象都擁有本身的屬性。可是在這個世界中有不少東西都是類似的,能夠歸爲一類,他們有共同的方法和屬性。不可能讓每個對象都定義一個屬性吧。那樣太消耗內存了。因此,在js中怎麼才能讓多個對象共享一個或多個方法呢?原型的出現就是爲了解決這個問題。

  • prototype和__proto__的區別

一切皆對象,函數是特殊的對象。
全部的引用類型(函數、數組和對象)都擁有__proto__屬性(隱式原型)
全部函數擁有prototype屬性(顯示原型)
原型對象:擁有prototype屬性的對象,在定義函數時就被建立

  • 構造函數

大多數狀況下,__proto__能夠理解爲「構造器的原型」(__proto__ === constructor.prototype)

function Word(words){
		this.words = words
	}
	Word.prototype = {
			alert(){
				alert(this.words)
			}
	}
 
	var w = new Word("Hello Word");
	w.alert();
	console.log(w.__proto__ === Word.prototype) // true
複製代碼

實例w的隱式原型指向它構造函數的顯示原型。(指向即恆等於)

w.__proto__ === Word.prototype // true
複製代碼

當調用某種方法或查找某種屬性時,首先會在自身調用和查找,若是自身沒有該屬性或方法,則會去它的__proto__屬性中調用查找,也就是構造方法的prototype屬性中查找。實例經過這樣的方法繼承構造函數的方法和屬性。

實例經過__proto__屬性指向構造方法的prototype的屬性實現方法和屬性的繼承。


  • 原型鏈

因爲__proto__屬性是任何對象都有的屬性,在js中一切皆對象,因此會造成一條__proto__鏈接起來的鏈條,遞歸訪問__proto__必須最終到頭,而且值爲null。

圖片加載失敗!

Function.prototype.a = 'a';
	Object.prototype.b = 'b';
	function Person(){}
	console.log(Person);
	let p = new Person();
	console.log(p); // Person {} 對象
	console.log(p.a); // null
	console.log(p.b);  // b

複製代碼

說明: p是Person()的實例,是一個Person對象,它擁有一個屬性值__proto__,而且__proto__是一個對象,包含兩個屬性值constructor和__proto__,

圖片加載失敗!

會發現p.__proto__===Person.prototype,   p.__proto__.constructor ===Person // true,即p.__proto__.constructor指向了構造函數自己。  

Person.prototype的__proto__屬性,指向了Object 的prototype屬性。即p.b的打印結果爲b。則實例w經過__proto__屬性繼承了Object 的屬性b。經過__proto__屬性一直向上查找,一直到null爲止。

那麼構造函數Person的__proto__指向了Function.prototype。

圖片加載失敗!

  1. 查找屬性,若是自己沒有,則會去__proto__中查找,也就是構造函數的顯式原型中查找,若是構造函數中也沒有該屬性,由於構造函數也是對象,也有__proto__,那麼會去它的顯式原型中查找,一直到null,若是沒有則返回undefined
  2. p.__proto__.constructor == function Person(){}
  3. p.__proto__proto__== Object.prototype
  4. p.__proto____proto__.__proto__== Object.prototype.__proto__== null
  5. 經過__proto__造成原型鏈而非protrotype

圖片加載失敗!

事實上,js裏徹底依靠"原型鏈"模式來實現繼承。

再簡單說一下__proto__、prototype、constructor

  • _ _proto _ _:事實上就是原型鏈指針
  • prototype:這個是指向原型對象的
  • constructor:每個原型對象都包含一個指向構造函數的指針,就是constructor

繼承的實現方式:

爲了實現繼承,_ _proto _ _會指向上一層的原型對象,而上一層的結構依然相似,那麼就利用proto一直指向Object的原型對象上!Object.prototype. _ _ proto _ _ = null;表示到達最頂端。如此造成了原型鏈繼承。

下面有個圖很是經典:

圖片加載失敗!

你們能夠本身動手畫一下,加深本身的理解

大體總結一下:

1.Object是做爲衆多new出來的實例的基類 function Object(){ [ native code ] }

2.Function是做爲衆多function出來的函數的基類 function Function(){ [ native code ] }

3.構造函數的__proto__(包括Function.prototype和Object.prototype)都指向Function.prototype

4.原型對象的__proto__都指向Object.prototype

5.Object.prototype._ _ proto _ _指向null


^_<

相關文章
相關標籤/搜索