JS雖然不是物件導向類型的語言,但這不並不代表JS就不能夠實現OOP的特性。 我相信大家在使用JS的時候,一定有用過Object的原型方法,像是call,apply,hasOwnProperty等等方法,可是這些方法是從哪裡來的呢?如果JS無法實作繼承的話,這些方法的使用就無從談起了。這裡我們就來談談在JS實現繼承的方法,原型鏈。
首先我們要了解什麼是普通對象,什麼是函數物件。
普通物件
#var a = {}
#var a = new Object();
var a = new f1();//與上一個建立物件的方式相同
函數物件
var a = function(){};
var a = new Function() {};
f1()
#_proto_是每個普通物件都擁有的屬性,用來指向建構函數的prototype,也就是建構函式的原型物件。而建構子的原型物件一般來說也是一個普通物件(在建構子是Function的時候,它就變成了一個函數物件),所以它也有_proto_屬性。而它的_proto_則指向它的建構子的原型對象,也就是Object.prototype。最後的Object.prototype._proto_指向null,到了原型鏈的頂端。
prototype是函數物件都擁有的屬性,它在物件建立的時候被指定給新的物件實例。當然也可以動態修改。
function Person(){}; var p = new Person();//创建一个普通对象 //创建过程实际为 var p={}; p._proto_=Person.prototype; Person.apply(p,arguments);//或者是call... //执行构造函数,并返回创建的对象。
對上面程式碼的補充說明
正常來講建構函式中是不用寫return語句的,因為它會預設回傳新建立的物件。但是,如果在建構函式中寫了return語句,如果return的是一個對象,那麼函數就會覆寫新建立的對象,而傳回此物件;如果return的是基本型別如字串、數字、布林值等,那麼函數會忽略掉return語句,還是傳回新建立的物件。
而建構函數的原型物件的預設值為:
Person.prototype={ constructor://指向构造函数本身 _proto_://指向构造函数Person的原型对象的构造函数的原型对象,这里是指Object.prototype } //这里有一个特殊情况——当构造函数为Function的时候 Function.prototype._proto_===Object.prototype //我们知道Function.prototype是一个函数对象,它的_proto_应该指向它的构造函数的原型,也就是Function.prototype。 //可是这样下去就没完没了了,毕竟一条链总是有顶端的。这里约定Function.prototype._proto_===Object.prototype; //这时,Object.prototype._proto_===null;完美结束原型链。
我們可以不斷修改建構子的原型物件的指向,這樣最終就可以形成一條鏈。而上面提到的一條鏈就是JS中的預設原型鏈。
下面我們來看看程式碼:
function Parent(name){ this.name=name||"parent"; } function Son(name){ this.name=name||"son"; this.property="initial Son name"; } function Grandson(name){ this.name=name||"grandson"; this.ggs="initial Grandson name"; } Son.prototype = new Parent("原型中的Parent"); Grandson.prototype = new Son("原型中的Son"); let grandson = new Grandson("孙子"); console.log(grandson instanceof Son);//true console.log(grandson instanceof Grandson);//true console.log(grandson instanceof Parent);//true
很顯然,最後都會輸出true。但是我們改動一點程式碼:
Grandson.prototype = new Son("原型中的Son"); Son.prototype = new Parent("原型中的Parent");//其实上一步已经实例化了一个Son的对象给Grandson.prototype //这个时候Son的实例的_proto_已经确定指向那个时候的构造函数.prototype了(默认原型对象) let grandson = new Grandson("孙子"); console.log(grandson instanceof Son);//false console.log(grandson instanceof Grandson);//true console.log(grandson instanceof Parent);//false
為什麼結果會改變呢?原因也很簡單。我們之前有提到物件的創建的創建過程:物件在實例化的時候就已經給物件的_proto_賦了建構子的prototype了。也就是說上面程式碼中第一行已經確定了Grandson.prototype._proto_的值了,即使在第二行修改了Son.prototype也是無法修改Grandson.prototype._proto_的值。
Conclusion:JS中原型鏈的關係是由_proto_維持的,而不是prototype。
var animal = function(){}; var dog = function(){}; animal.price = 2000; dog.prototype = animal; var tidy = new dog(); console.log(dog.price) console.log(tidy.price)
答案是輸出什麼呢?是undefined和2000,我們分析一下:
首先我們清楚animal和dog都是函數對象,在第四行修改了dog的原型對象為animal。那我們接著往下看,console.log(dog.price)
這句話首先會尋找dog的price,沒有。然後到原型鏈上尋找。怎麼找的呢?我們之前提到是透過_proto_去到它建構函數的原型物件上,這裡因為dog是函數對象,那麼它的建構函式的原型物件就是Function.prototype,這是一個empty function。於是回傳undefined,沒有找到price這個屬性。
那麼console.log(tidy.price)
呢?
tidy是一個普通對象,首先也是尋找它本身的屬性price,也沒有。透過_proto_去到它建構函式的原型物件上,也就是dog.prototype。因為tidy實例化在dog.prototype = animal;
之後,所以tidy._proto_的指向已經指向了修改後的dog.prototype。也就是指向了animal,也就是能夠找到price這個屬性了,所以輸出2000。
原型物件上的所有屬性和方法都可以看成是Java中父類別的public(protected)屬性和方法,在這些方法內部使用this即可存取建構函式中的屬性和方法。至於為什麼,這又得提到JS中this的綁定問題了….總而言之,誰調用的函數,this就指向誰。箭頭函數除外…
以上是JS中的原型鏈詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!