JavaScript-原型&原型鏈&原型繼承
JavaScript-原型&原型鏈&原型繼承
JavaScript的原型是一個重要的知識點,很多擴充套件應用都是從原型出發的。要說原型,我們先簡單說一下函式建立過程。上一篇文章用閉包實現類和繼承中用的是原型繼承,今天就講一講原型繼承。更多繼承在後面的文章中更新。
函式建立過程
function Xzavier() {};
1.建立一個物件(有constructor屬性及[[Prototype]]屬性),其中[[Prototype]]屬性不可訪問、不可列舉。
2.建立一個函式(有name、prototype屬性),再通過prototype屬性 引用第1步建立的物件。
3.建立變數Xzavier,同時把函式的引用賦值給變數Xzavier。
建構函式是用來新建同時初始化一個新物件的函式,所以,任何一個函式都可以是建構函式。只是我們在寫程式碼的時候一般首字母大寫以便區分使用。
原型
每個函式在建立的時候js都自動添加了prototype屬性,這就是函式的原型,原型就是函式的一個屬性,類似一個指標。原型在函式的建立過程中由js編譯器自動新增。
function Xzavier() { this.name = 'xzavier'; this.sex = 'boy'; this.job = "jser"; } //給A新增屬性 Xzavier.age = 23; //給A的原型物件新增屬性 Xzavier.prototype.sports = function() {console.log('basketball')} Xzavier.prototype = { hobbit1: function() {console.log('basketball');}, hobbit2: function() {console.log('running');} };
原型鏈
在JavaScript中,每個物件都有一個[[Prototype]]屬性,其儲存著的地址就構成了物件的原型鏈
。
[[Prototype]]屬性是js編譯器在物件被建立時自動新增的,其取值由new運算子的右側引數決定。字面量的方式可轉化為new Obejct();
var x = new Xzavier(); vae o = {}; //var o = new Obejct();
通過物件的[[Prototype]]儲存對另一個物件的引用,通過這個引用往上進行屬性的查詢,這就是原型鏈查詢機制
。
物件在查詢某個屬性的時候,會首先遍歷自身的屬性,如果沒有則會繼續查詢[[Prototype]]
[[Prototype]].[[Prototype]]
引用的物件,依次類推,直到[[Prototype]].….[[Prototype]]
為undefined
var str = new String('xzavier'); str
Object.prototype的[[Prototype]]就是undefined function Xzavier() { this.name = 'xzavier'; } var x = new Xzavier(); x.age = 23; console.log(x.job); // 獲取x的job屬性 undefined
1、遍歷x物件本身,結果x物件本身沒有job屬性
2、找到x的[[Prototype]],也就是其對應的物件Xzavier.prototype,同時進行遍歷。 Xzavier.prototype也沒有job屬性
3、找到Xzavier.prototype物件的[[Prototype]],指向其對應的物件Object.prototype。Object.prototype也沒有job屬性
4、尋找Object.prototype的[[Prototype]]屬性,返回undefined。
函式的變數和內部函式
說了函式的原型鏈,這裡需要說一下的變數和內部函式。
私有變數和內部函式
私有成員,即定義函式內部的變數或函式,外部無法訪問。
function Xzavier(){ var name = "xzavier"; //私有變數 var sports = function() {console.log('basketball')}; //私有函式 } var x = new Xzavier(); x.name; //undefined 如果要訪問,需要對外提供介面。 function Xzavier(){ var name = "xzavier"; //私有變數 var sports = function() {console.log('basketball')}; //私有函式 return{ name: name, sports: sports } } var x = new Xzavier(); x.name; //"xzavier"
靜態變數和內部函式
用點操作符定義的靜態變數和內部函式就是例項不能訪問到的變數和內部函式。只能通過自身去訪問。
function Xzavier(){ Xzavier.name = "xzavier"; //靜態變數 Xzavier.sports = function() {console.log('basketball')}; //靜態函式 } Xzavier.name; //"xzavier" var x = new Xzavier(); x.name; //undefined
例項變數和內部函式
通過this定義給例項使用的屬性和方法。
function Xzavier(){ this.name = "xzavier"; //例項變數 this.sports = function() {console.log('basketball');}; //例項函式 } Xzavier.name; //undefined var x = new Xzavier(); x.name; //"xzavier"
原型鏈繼承
有了原型鏈,就可以藉助原型鏈實現繼承。
function Xzavier() { this.name = 'xzavier'; this.sex = 'boy'; this.job = "jser"; } function X() {};
X的原型X.prototype原型本身就是一個Object物件。F12開啟控制檯輸入函式,再列印X.prototype
:
Object { constructor: X() __proto__: Object }
prototype本身是一個Object物件的例項,所以其原型鏈指向的是Object的原型。
X.prototype = Xzavier.prototype
X.prototype = Xzavier.prototype;
這樣相當於把X的prototype指向了Xzavier的prototype;
這樣只是繼承了Xzavier的prototype方法,Xzavier中的自定義方法則不繼承。
X.prototype.love = "dog";
這樣也會改變Xzavier的prototype,所以這樣基礎就不好。
X.prototype = new Xzavier()
X.prototype = new Xzavier();
這樣產生一個Xzavier的例項,同時賦值給X的原型,也即X.prototype相當於物件:
{ name: "xzavier", sex: "boy", job: "jser", [[Prototype]] : Xzavier.prototype }
這樣就把Xzavier的原型通過X.prototype.[[Prototype]]這個物件屬性儲存起來,構成了原型的連結。
不過,這樣X產生的物件的建構函式發生了改變,因為在X中沒有constructor屬性,只能從原型鏈找到Xzavier.prototype,讀出constructor:Xzavier。
var x = new X; console.log(x.constructor); 輸出: Xzavier() { this.name = 'xzavier'; this.sex = 'boy'; this.job = "jser"; }
手動改正:
X.prototype.constructor = X;
這是X的原型就多了個屬性constructor,指向X。這樣就OK。
function Xzavier() { this.name = 'xzavier'; this.sex = 'boy'; this.job = "jser"; } function X(){} X.prototype = new Xzavier(); var x = new X() x.name // "xzavier"
[[Prototype]],__proto__,prototype
關於我們經常遇到的[[Prototype]],__proto__,prototype
:
我們在控制檯列印 var str = new String('xzavier')
,展開檢視屬性時,只會看到__proto__
,所以起作用的是__proto__
,__proto__
是物件的內建屬性,是每個物件都有的屬性,但是這個屬性使用不標準,所以不建議直接使用。但是,我們的原型鏈就是基於 __proto__
的。通過建構函式得到的例項的 __proto__
屬性,指向其對應的原型物件 String.prototype
,這正如文中我們列印 var str = new String('xzavier')
中看到的一樣。
[[Prototype]]
是一個隱藏屬性,指向的是這個物件的原型。幾乎每個物件有一個[[prototype]]
屬性。
而prototype
是每個函式物件都具有的屬性,指向原型物件,如果原型物件被新增屬性和方法,那麼由應的建構函式建立的例項會繼承prototype
上的屬性和方法,這也是我們在程式碼中經常遇到的。建構函式產生例項時,例項通過其對應原型物件的 constructor 訪問對應的建構函式物件。所以,我們繼承出來的例項往往沒有constructor,只是通過原型鏈查詢,會讓我們產生錯覺,可參見本系列原型鏈文章。
hasOwnProperty
hasOwnProperty是Object.prototype的一個方法,判斷一個物件是否包含自定義屬性而不是原型鏈上的屬性。
hasOwnProperty 是JavaScript中唯一一個處理屬性但是不查詢原型鏈的函式。
function Xzavier() { this.name = 'xzavier'; this.sex = 'boy'; this.job = "jser"; } //給A的原型物件新增屬性 Xzavier.prototype.sports = function() {console.log('basketball');}; var x = new Xzavier(); x.name; // 'xzavier' 'sex' in x; // true x.hasOwnProperty('job'); // true x.hasOwnProperty('sports'); // false
當檢查物件上某個屬性是否存在時,hasOwnProperty 是非常推薦的方法。
繼承在js中使用頻繁。ES6也設計了專門的CLASS語法糖供開發者使用。
更多繼承方法在新的文章中更新...
難得週末,應該運動O(∩_∩)O~ 打打籃球,運動運動,有程式碼,有籃球,有生活。。。
長時間不動肩膀(其實身體各地方都是),還真疼啊。希望程式猿們都健健康康的!!
參考資料:https://segmentfault.com/a/1190000006876041#articleHeader0
作者:xzavier