在 ES5 中,有的人可能對原型,原型對象,及其原型鏈不是很清楚,今天我就說說對這些的深入認識下。(如果有什么不懂得歡迎留言探討,當然如果有什么寫的不恰當的也希望大家留言備注。)
首先,再說原型與原型對象之前,當然有必要清楚構造函數,實例,原型與原型對象之間的關系。其實他們的關系也很簡單。
構造函數,實例,原型與原型對象之間的關系:
構造函數有它自己的屬性及其方法,其中包括自己定義的屬性和方法外,還有兩個特殊屬性(prototype、constructor);而每個他的實例都會擁有它的所有屬性和方法(包括prototype、constructor)constructor則是指向每個實例的構造函數,而prototype 原型 則是一個地址指向原型對象,這個原型對象創建了實例后,只會取得constructor屬性,其他的都是從Object繼承而來;在Firefox 、 chrome在對象上都支持一個屬性"_proto_";這個原型對象的屬性和方法是所有該類實例共享的任何該類實例夠可以訪問該原型對象的屬性和方法(后面會介紹訪問原型對象屬性和方法的三個方式)
如上圖,p1 ,p2的的實例都有Person的屬性和方法,並且prototype都指向原型對象,p1\p2共享prototype原型對象的屬性和方法,各自的constructor都指向Peson,這便是構造函數、實例、原型(對象)三者的關系。
現在我來說一說訪問原型對象屬性和方法的三個方式:
1.通過Person.prototype 屬性
console.log(Person.prototype.name);//輸出----->person
2.通過 屬性屏蔽 delete (屏蔽構造函數屬性或者方法)
p1.sayName(); //輸出----->構造函數對象
delete p1.name;
console.log(p1.name); //輸出----->原型屬性
delete p1.sayName;
p1.sayName(); //輸出 --->原型對象方法
3.通過Object.getPrototypeOf(p1)
console.log(Object.getPrototypeOf(p1).name);//輸出----->原型屬性
上面我們需要注意就是當實例調用屬性或者方法時,有一個”屬性搜索機制“,所謂”屬性搜索機制“就是當實例訪問屬性或者方法時首先會現在自身的實例中搜索,看是否有對應屬性,有,則返回;如果沒有那么它會通過prototype 到原型對象中尋找對應的屬性和方法;
原型鏈:
其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。我們知道,每個構造函數都有一個原型對象,每個原型對象都有一個指向構造函數的指針,而實例又包涵一個指向原型對象的內部指針。
如果我們讓原型對象(A.prototype) = 另一個類型的實例(new B()),那么,該原型對象(A.prototype)就有一個指向另一個原型對象(B.prototype)的指針,相應的,另一個原型對象(B.prototype)也包含指向另一個構造函數(B)的指針。 ------------->如果另一個的原型(B.prototype)又是另一個類型(C)的實例,上訴關系依然成立,就構成了實例與原型的鏈條,這就是原型鏈。
實現原型鏈的基本方法:
function Person () { this.name = "person"; } Person.prototype.getPersonName = function () { return this.name; }; function Student () { this.studentname = "student"; } // 繼承了Person Student.prototype = new Person(); Student.prototype.getStudentName = function () { return this.name; }; var stu = new Student(); console.log(stu.getPersonName()); //person
如上就是通過將 Student()的prototype = new Person() 即子類的原型對象等於父類的實例,從而Student.prototype有了Person的所有屬性和方法,實現了繼承。通過實現原型鏈,再結合 “屬性搜索機制“,則
stu.getPersonName()
會經過三個階段:1.搜索實例 2.搜索Student.prototype 3.搜索Person.prototype;如果再沒就會到Object 對象的prototype對象上尋找。因為所有的function和對象等引用類型都繼承Object;這也就說明了為什么不是通過Object直接實例的對象(自定義類型)會有valueof(),toString()等方法。
需要注意的是,子類有時需要重寫父類的方法或者新增心得方法,這些都要 替換了原型之后(也就是實現繼承之后)。-----------stu 指向Student的原型,Student的原型又指向了Person的原型。從而實現了stu 繼承Student Student繼承Person
function Person () { this.name = "person"; } Person.prototype.getPersonName = function () { return this.name; }; function Student () { this.studentname = "student"; } // 繼承了Person Student.prototype = new Person(); // 新增方法 Student.prototype.getStudentName = function () { return this.name; }; // 重寫父類方法 Student.prototype.getPersonName = function () { return false; }; var stu = new Student(); console.log(stu.getPersonName()); //false
還有一點需要注意的是:在通過原型鏈實現繼承時,不能用對象字面量創建原型方法,因為這樣會重寫原型鏈。 剛剛把Person的實例賦值給原型,緊接着使用字面量導致出錯。------->因為現在的原型包含的是一個Object的實例,不是Person的實例,原型鏈被切斷。
function Person () { this.name = "person"; } Person.prototype.getPersonName = function () { return this.name; }; function Student () { this.studentname = "student"; } // 繼承了Person Student.prototype = new Person(); // 使用字面量添加新方法,會導致Student.prototype = new Person(); 無效 Student.prototype = { getStudentName :function () { return this.name; }, otherMethod :function () { return false; } }; var stu = new Student(); console.log(stu.getPersonName()); //stu.getPersonName is not a function
會報錯 stu.getPersonName is not a function 因為此時Student和Person已經沒有關系了。
所以理想的繼承方式是”寄生組合式繼承“,所謂寄生組合式繼承通過借用構造函數來繼承屬性(父類構造函數里的屬性+方法),通過原型鏈的形式繼承方法(父類原型里的方法)。
// 寄生組合式繼承(理想的繼承方式) function inherPrototype (Subobject,Superobject) { var prototype = Superobject.prototype; prototype.constructor = Subobject; Subobject.prototype = prototype; } function Person (name) { this.name = name, this.hand = ["right-hand","left-hand"], this.say = function () { alert("hi"); } } Person.prototype.sayName = function () { alert(this.name); } function Student (name,age) { Person.call(this,name); this.age = age; } // 實現繼承 inherPrototype(Student,Person); Student.prototype.sayAge = function () { alert(this.age); } var stu1 = new Student("jack",20); // 繼承了屬性(構造函數里的屬性+方法) console.log(stu1.hand[0]); //----------->輸出right-hand stu1.say(); //輸出 hi // 繼承了原型里的方法 stu1.sayName(); //輸出 jack
要實現繼承無非就是擁有父類對象的方法和屬性,即擁有一個父類的”模板副本“,inherPrototype()方法做了如下三件事:1.創建父類的原型(prototype)副本,賦值給prototype 2.為創建的prototype副本添加constructor屬性 ,彌補因為原型賦值而導致失去默認的constructor屬性3.把這個副本賦值給子類原型。這樣便實現了繼承。