JS 原型、原型鏈、繼承

原型

簡(jiǎn)單創(chuàng)建一個(gè)構(gòu)造函數(shù)與實(shí)例:

function Person() {

}
Person.prototype.name = 'Zhar';
Person.prototype.age = 30;
Person.prototype.say = function(){
  console.log(this.name);
}
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Zhar
console.log(person2.name) // Zhar

Person 構(gòu)造函數(shù)

person 是 Person 的一個(gè)實(shí)例對(duì)象

instanceof

instanceof 用于判斷一個(gè)對(duì)象是否是另一個(gè)對(duì)象的實(shí)例(該方法在原型鏈中依然有效)

console.log(person1 instanceof Person);//true
console.log(person2 instanceof Person);//true
var arr = new Array();
console.log(arr instanceof Array);//true

prototype

每個(gè)函數(shù)都有一個(gè) prototype屬性(只有函數(shù))

prototype 指向?

函數(shù)的 prototype屬性指向了一個(gè)對(duì)象,這個(gè)對(duì)象就是調(diào)用該構(gòu)造函數(shù)而創(chuàng)建的實(shí)例的原型,也就是上面代碼中的 person1 person2的原型對(duì)象。使用原型對(duì)象的好處就是可以讓把有對(duì)象的實(shí)例共享它所包含的屬性和方法。換句話說,不必在構(gòu)造函數(shù)中定義對(duì)象實(shí)例的信息,而是可以將這些信息直接添加到原型對(duì)象中去。

constructor

默認(rèn)情況下,所有原型對(duì)象都會(huì)自動(dòng)獲得一個(gè) constructor(構(gòu)造器)屬性,這個(gè)屬性指向所在的函數(shù)。

Person.prototype.constructor 指向 Person

任何情況下,只要?jiǎng)?chuàng)建了一個(gè)新函數(shù),就會(huì)為該函數(shù)創(chuàng)建一個(gè) prototype 屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象。

"原型"是什么?

每一個(gè)對(duì)象都有一個(gè)與之關(guān)聯(lián)的另一個(gè)'原型'對(duì)象,每一個(gè)對(duì)象都會(huì)從原型繼承屬性

如何訪問對(duì)象的原型?

在 ECMA中,并沒有規(guī)定直接訪問對(duì)象原型的方法,但 Firefox/Chrome/safari 在每一個(gè)對(duì)象都支持一個(gè)__proto__用來訪問對(duì)象的原型。

__proto__

console.log(Person.prototype);
console.log(person);
console.log(person.__proto__);

__proto__存在于實(shí)例與構(gòu)造函數(shù)的原型對(duì)象之間,而不是存在于實(shí)例與構(gòu)造函數(shù)之間

isPrototypeOf

可通過object1.isPrototypeOf(object2)來判斷一個(gè)對(duì)象是否存在于另一個(gè)對(duì)象的原型鏈中,即 object1 是否存在于 object2的原型鏈中。換句話講:判斷 object2是否有一個(gè)指針指向 object1

Person.prototype.isPrototypeOf(person1);//true
Person.prototype.isPrototypeOf(person2);//true

getPrototypeOf(ES5)

返回一個(gè)對(duì)象的原型

Object.getPrototypeOf(person1);
Object.getPrototypeOf(person2);

一些判斷

person.__proto__ === Person.prototype;//true
person.__proto__.constructor === Person.prototype.constructor;//true
person1.__proto__ === person2.__proto__;//true
person1.say === person2.say;//true
Object.getPrototypeOf(person1) === Object.getPrototypeOf(person2);//true
Object.getPrototypeOf(person1) === Person.prototype;//true

hasOwnProperty

觀察下面的代碼:

console.log(pserson1.name);//Zhar   來自于原型
console.log(person1.address);//undefined
person1.name = "new Name";
console.log(person1.name);//new Name   來自于實(shí)例
console.log(person2.name);//Zhar   來自于原型

尋找規(guī)則?

當(dāng)代碼讀取某個(gè)對(duì)象的某個(gè)屬性時(shí),首先會(huì)從實(shí)例本身開始,如果在實(shí)例中找到了該屬性,則返回該屬性的值;如果沒有找到,則搜索指針指向的原型對(duì)象,如果找到,則返回值,如果沒有則繼續(xù)向上尋找,直到找到 Obejct

雖然可以通過對(duì)象實(shí)例訪問原型中的值,但不能通過對(duì)象實(shí)例重寫原型中的值。

如果在實(shí)例中添加了一個(gè)屬性,而該屬性與實(shí)例原型中的一個(gè)屬性名相同,則會(huì)在實(shí)例中創(chuàng)建該屬性,該屬性將會(huì)屏蔽原型中的那個(gè)同名屬性(可以通過 delete 刪除實(shí)例屬性,恢復(fù)原型屬性的訪問)

delete person1.name;
console.log(person1.name);//Zhar 來自于原型

通過hasOwnProperty(propertyName)來檢測(cè)一個(gè)屬性是存在于實(shí)例還是原型中

console.log(person1.hasOwnProperty("name"));//false;
person1.name = "newName";
console.log(person1.hasOwnProperty("name"));//true

in

觀察以下代碼:

console.log("name" in person1);//true;
person1.name = "newName";
console.log("name" in person1);//true

通過in關(guān)鍵字可以判斷某個(gè)對(duì)象是否包含某個(gè)屬性,而不管這個(gè)屬性是屬于實(shí)例還是原型

通過 hasOwnProperty() 與 in 相結(jié)合,可精確定位一個(gè)屬性是屬于原型還是實(shí)例

keys(es5)

keys(object)方法可返回對(duì)象所有可枚舉屬性的字符串?dāng)?shù)組

console.log(Object.keys(person1));//[]
person1.name = "newName";
console.log(Object.keys(person1));//["name"]
console.log(Object.keys(Person.prototype));//[ 'name', 'age', 'say' ]

keys()返回的是該對(duì)象的屬性數(shù)組,并不包含其構(gòu)造函數(shù)上的屬性

通過字面量對(duì)象創(chuàng)建原型的問題?

繼承

原型鏈?zhǔn)嚼^承

讓一個(gè)函數(shù)的原型對(duì)象指向另一個(gè)原型的指針,而另一個(gè)原型中也包含指向另外一個(gè)構(gòu)造函數(shù)的指針,依此層層嵌套,形成原型鏈

function Animal() {
    this.topType = "脊椎動(dòng)物";
}
Animal.prototype.getTopType = function(){
    return this.topType;
}
function Person() {
    this.secondType = "人類";
}

Person.prototype = new Animal();
Person.prototype.getSecondType = function(){
    return this.secondType;
}
var person = new Person();
console.log(person.topType+"--"+person.secondType);//脊椎動(dòng)物--人類

在上面的例子中,Person.prototype=new Animal() 即是將 Animal 實(shí)例賦給了 Person.prototype

結(jié)合前面原型中的講解,new Animal 產(chǎn)生一個(gè) Animal 實(shí)例,該實(shí)例對(duì)象有一個(gè)__proto__指向其構(gòu)造函數(shù)的prototype,將該實(shí)例賦值給 Person 的原型,意味著Person的原型 既擁有了 Animal 的所有實(shí)例屬性或方法,還包含了一個(gè)指向 Animal的原型的指針。

思考:person.constructor 等于什么?

示意圖如下:

我們可以再擴(kuò)展代碼:

//省略....
function Student(){
    this.thirdType = "學(xué)生";
}
Student.prototype = new Person();
var student = new Student();
console.log(student.topType+"--"+student.secondType+"--"+student.thirdType);//'脊椎動(dòng)物--人類--學(xué)生'

在這里增加了第三個(gè)構(gòu)造函數(shù)Student,并且將 Person 實(shí)例賦給了 Student.prototype,這樣,層級(jí)關(guān)系將變?yōu)?Student 繼承了 Person,而 Person 繼承了 Animal;Student 擁有 Person 和 Animal 的全部共享屬性或方法,Person 擁有 Animal 的全部共享屬性或方法

擴(kuò)展后的示意圖:

搜索過程?

正如上面的例子,在調(diào)用 student.topType 時(shí),是如何得到"脊椎動(dòng)物"的,在前面的hasOwnProperty知識(shí)中,曾經(jīng)有過涉及,即:

  1. 搜索實(shí)例本身
  2. 搜索 Student.prototype
  3. 搜索 Person.prototype
  4. 搜索 Animal.prototype (查找到屬性,返回屬性值)

如下圖:

Object?

在上面的原型鏈圖中,可以看到在尋找某屬性時(shí),一直找到了 Object.prototype。所有函數(shù)的默認(rèn)原型都是 Object 的實(shí)例,因此默認(rèn)原型都會(huì)包含一個(gè)指針指向 Object.prototype。而這也是對(duì)象或函數(shù)能夠使用toString()方法的原因

更完善的原型鏈?zhǔn)疽鈭D:

原型鏈?zhǔn)嚼^承所帶來的問題?

  1. 引用數(shù)據(jù)類型問題

    JS面向?qū)ο?/strong>一節(jié)中,我們?cè)?jīng)嘗試將引用數(shù)據(jù)類型賦值給原型的屬性(參見<u>創(chuàng)建對(duì)象</u>一節(jié)的<u>原型模式</u>),結(jié)果出現(xiàn)實(shí)例間相互影響,在原型繼承時(shí),也會(huì)出現(xiàn)此問題,觀察下例:

    function Person(){
      this.desc = ["北京"];
    }
    function Student(){}
    Student.prototype = new Person();
    var s1 = new Student();
    var s2 = new Student();
    s1.desc.push("昌平");
    console.log(s1.desc);//[ '北京', '昌平' ]
    console.log(s2.desc);//[ '北京', '昌平' ]
    

    Person 函數(shù)含有一個(gè)desc 屬性,則 Person的實(shí)例會(huì)包含各自的一個(gè) desc 屬性。但此時(shí)的 Student 通過原型鏈繼承了 Person,Student 的 prototype 則變成了 Person 的一個(gè)實(shí)例,則 Student 就擁有了一個(gè) desc 屬性,相當(dāng)于使用 Student.prototype.desc=[…]為 Student 添加了這樣一個(gè)原型屬性,結(jié)果顯而易見。

  2. 傳參問題

    通過前面的代碼,會(huì)發(fā)現(xiàn),在使用原型鏈繼承時(shí),無法向父級(jí)構(gòu)造函數(shù)傳遞參數(shù)

借用構(gòu)造函數(shù)式繼承

通過使用 call 或 apply 來實(shí)現(xiàn)借用式繼承

call 或 apply :更改函數(shù)執(zhí)行的上下文環(huán)境(call 和 apply 的用法在后面的課程中會(huì)有講解)

function Person(name){
  this.name = name;
}
function Student(name){
  Person.call(this,name);
}
var student = new Student("zhar");
console.log(student.name);//zhar

上面的代碼中,使用了 call,在 Student 的實(shí)例環(huán)境中調(diào)用了 Person 構(gòu)造函數(shù),更改了 Person 的 this 指向?yàn)楫?dāng)前實(shí)例環(huán)境,最終 Student的實(shí)例就擁有了 name 屬性

優(yōu)勢(shì)

可以在調(diào)用構(gòu)造函數(shù)時(shí),向'父級(jí)'傳遞參數(shù),如上例中一樣,在調(diào)用 Person 時(shí)傳入了 name 值

缺點(diǎn)

無法繼承原型屬性或方法

function Person(name){
  this.name = name;
}
Person.prototype.say = function(){
  console.log(this.name);
}
function Student(name){
  Person.call(this,name);
}
var student = new Student("zhar");
console.log(student.name);//zhar
student.say();//報(bào)錯(cuò)  student.say is not a function

組合繼承

將原型鏈?zhǔn)嚼^承與借用構(gòu)造函數(shù)式繼承組合在一起,結(jié)合二者的優(yōu)勢(shì)。通過原型鏈實(shí)現(xiàn)對(duì)原型屬性或方法的繼承,通過借用構(gòu)造函數(shù)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承

function Person(name){
  this.name = name;
}
Person.prototype.say = function(){
  console.log(this.name);
}
function Student(name){
  Person.call(this,name);
}
Student.prototype = new Person();
var student = new Student("zhar");
console.log(student.name);//zhar
student.say();//zhar
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容