溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JS原形與原型鏈的用法

發(fā)布時(shí)間:2020-07-28 12:06:09 來(lái)源:億速云 閱讀:153 作者:小豬 欄目:web開(kāi)發(fā)

這篇文章主要講解了JS原形與原型鏈的用法,內(nèi)容清晰明了,對(duì)此有興趣的小伙伴可以學(xué)習(xí)一下,相信大家閱讀完之后會(huì)有幫助。

前言

在JS中,我們經(jīng)常會(huì)遇到原型。字面上的意思會(huì)讓我們認(rèn)為,是某個(gè)對(duì)象的原型,可用來(lái)繼承。但是其實(shí)這樣的理解是片面的,下面通過(guò)本文來(lái)了解原型與原型鏈的細(xì)節(jié),再順便談?wù)劺^承的幾種方式。

原型

在講到原型之前,我們先來(lái)回顧一下JS中的對(duì)象。在JS中,萬(wàn)物皆對(duì)象,就像字符串、數(shù)值、布爾、數(shù)組等。ECMA-262把對(duì)象定義為:無(wú)序?qū)傩缘募?,其屬性可包含基本值、?duì)象或函數(shù)。對(duì)象是擁有屬性和方法的數(shù)據(jù),為了描述這些事物,便有了原型的概念。

無(wú)論何時(shí),只要?jiǎng)?chuàng)建了一個(gè)新函數(shù),就會(huì)根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個(gè)prototype屬性,這個(gè)屬性指向該函數(shù)的原型對(duì)象。所有原型對(duì)象都會(huì)獲得一個(gè)constructor屬性,這個(gè)屬性包含一個(gè)指向prototype屬性所在函數(shù)的指針。

這段話摘自《JS高級(jí)程序設(shè)計(jì)》,很好理解,以創(chuàng)建實(shí)例的代碼為例。

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayName = function() {
    alert(this.name);
  };
}

const person1 = new Person("gali", 18);
const person2 = new Person("pig", 20);

JS原形與原型鏈的用法

上面例子中的person1跟person2都是構(gòu)造函數(shù)Person()的實(shí)例,Person.prototype指向了Person函數(shù)的原型對(duì)象,而Person.prototype.constructor又指向Person。Person的每一個(gè)實(shí)例,都含有一個(gè)內(nèi)部屬性__proto__,指向Person.prototype,就像上圖所示,因此就有下面的關(guān)系。

console.log(Person.prototype.constructor === Person); // true
console.log(person1.__proto__ === Person.prototype); // true
console.log(person2.__proto__ === Person.prototype); // true

繼承

JS是基于原型的語(yǔ)言,跟基于類的面向?qū)ο笳Z(yǔ)言有所不同,JS中并沒(méi)有類這個(gè)概念,有的是原型對(duì)象這個(gè)概念,原型對(duì)象作為一個(gè)模板,新對(duì)象可從原型對(duì)象中獲得屬性。那么JS具體是怎樣繼承的呢?

在講到繼承這個(gè)話題之前,我們先來(lái)理解原型鏈這個(gè)概念。

原型鏈

構(gòu)造函數(shù),原型和實(shí)例的關(guān)系已經(jīng)很清楚了。每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象,原型對(duì)象都包含一個(gè)指向構(gòu)造函數(shù)的指針,而實(shí)例對(duì)象都包含一個(gè)指向與原型對(duì)象的指針。這樣的關(guān)系非常好理解,但是如果我們想讓原型對(duì)象等于另一個(gè)類型的實(shí)例對(duì)象呢?那么就會(huì)衍生出相同的關(guān)系,此時(shí)的原型對(duì)象就會(huì)含有一個(gè)指向另一個(gè)原型對(duì)象的指針,而另一個(gè)原型對(duì)象會(huì)含有一個(gè)指向另一個(gè)構(gòu)造函數(shù)的指針。如果另一個(gè)原型對(duì)象又是另一個(gè)類型的實(shí)例對(duì)象呢?這樣就構(gòu)成了原型鏈。文字可能有點(diǎn)難理解,下面用代碼舉例。

function SuperType() {
  this.name = "張三";
}
SuperType.prototype.getSuperName = function() {
  return this.name;
};

function SubType() {
  this.subname = "李四";
}
SubType.prototype = new SuperType();
SubType.prototype.getSubName = function() {
  return this.subname;
};

const instance = new SubType();
console.log(instance.getSuperName()); // 張三

上述例子中,SubType的原型對(duì)象作為SuperType構(gòu)造函數(shù)的實(shí)例對(duì)象,此時(shí),SubType的原型對(duì)象就會(huì)有一個(gè)__proto__屬性指向SuperType的原型對(duì)象,instance作為SubType的實(shí)例對(duì)象,必然能共享SubType的原型對(duì)象的屬性,又因?yàn)?code>SubType的原型對(duì)象又指向SuperType原型對(duì)象的屬性,因此可得,instance繼承了SuperType原型的所有屬性。

我們都知道,所有函數(shù)的默認(rèn)原型都是Object的實(shí)例,所以也能得出,SuperType的默認(rèn)原型必然有一個(gè)__proto__指向Object.prototype。

圖中由__proto__屬性組成的鏈子,就是原型鏈,原型鏈的終點(diǎn)就是null。

JS原形與原型鏈的用法

上圖可很清晰的看出原型鏈的結(jié)構(gòu),這不禁讓我想到JS的一個(gè)運(yùn)算符instanceof,instanceof可用來(lái)判斷一個(gè)實(shí)例對(duì)象是否屬于一個(gè)構(gòu)造函數(shù)。

A instanceof B; // true

實(shí)現(xiàn)原理其實(shí)就是在A的原型鏈上尋找是否有原型等于B.prototype,如果一直找到A原型鏈的頂端null,仍然找不到原型等于B.prototype,那么就可返回false。下面手寫一個(gè)instanceof,這個(gè)也是很多大廠常用的手寫面試題。

function Instance(left, right) {
  left = left.__proto__;
  right = right.prototype;
  while (true) {
    if (left === null) return false;
    if (left === right) return true;
    // 繼續(xù)在left的原型鏈向上找
    left = left.__propo__;
  }
}
原型鏈繼承

上面例子中,instance繼承了SuperType原型的屬性,其繼承的原理其實(shí)就是通過(guò)原型鏈實(shí)現(xiàn)的。原型鏈很強(qiáng)大,可用來(lái)實(shí)現(xiàn)繼承??墒菃渭兊脑玩溊^承也是有問(wèn)題存在的。

  • 實(shí)例屬性變成原型屬性,影響其他實(shí)例
  • 創(chuàng)建子類型的實(shí)例時(shí),不能向超類型的構(gòu)造函數(shù)傳遞參數(shù)
function SuperType() {
  this.colorArr = ["red", "blue", "green"];
}
function SubType() {}
SubType.prototype = new SuperType();

const instance1 = new SubType();
instance1.colorArr.push("black");
console.log(instance1.colorArr); // ["red", "blue", "green", "black"]

const instance2 = new SubType();
console.log(instance2.colorArr); // ["red", "blue", "green", "black"]

當(dāng)SubType的原型作為SuperType的實(shí)例時(shí),此時(shí)SubType的實(shí)例對(duì)象通過(guò)原型鏈繼承到colorArr屬性,當(dāng)修改了其中一個(gè)實(shí)例對(duì)象從原型鏈中繼承到的原型屬性時(shí),便會(huì)影響到其他實(shí)例。對(duì)instance1.colorArr的修改,在instance2.colorArr便能體現(xiàn)出來(lái)。

組合繼承

組合繼承指的是組合原型鏈和構(gòu)造函數(shù)的技術(shù),通過(guò)原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,而通過(guò)借用構(gòu)造函數(shù)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承。

function SuperType(name) {
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
  console.log(this.name);
};

function SubType(name, age) {
  // 繼承屬性,借用構(gòu)造函數(shù)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承
  SuperType.call(this, name);
  this.age = age;
}

// 繼承原型屬性及方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
  console.log(this.age);
};

const instance1 = new SubType("gali", 18);
instance1.colors.push("black");
console.log(instance1.colors); // ["red", "blue", "green", "black"]
instance1.sayName(); // gali
instance1.sayAge(); // 18

const instance2 = new SubType("pig", 20);
console.log(instance2.colors); // ["red", "blue", "green"]
instance2.sayName(); // pig
instance2.sayAge(); // 20

上述例子中,借用構(gòu)造函數(shù)繼承實(shí)例屬性,通過(guò)原型繼承原型屬性與方法。這樣就可讓不同的實(shí)例分別擁有自己的屬性,又可共享相同的方法。而不會(huì)像原型繼承那樣,對(duì)實(shí)例屬性的修改影響到了其他實(shí)例。組合繼承是JS最常用的繼承方式。

寄生組合式繼承

雖然說(shuō)組合繼承是最常用的繼承方式,但是有沒(méi)有發(fā)現(xiàn),就上面的例子中,組合繼承中調(diào)用了2次SuperType函數(shù)?;貞浺幌?,在第一次調(diào)用SubType時(shí)。

SubType.prototype = new SuperType();

這里調(diào)用完之后,SubType.prototype會(huì)從SuperType繼承到2個(gè)屬性:name和colors。這2個(gè)屬性存在SubType的原型中。而在第二次調(diào)用時(shí),就是在創(chuàng)造實(shí)例對(duì)象時(shí),調(diào)用了SubType構(gòu)造函數(shù),也就會(huì)再調(diào)用一次SuperType構(gòu)造函數(shù)。

SuperType.call(this, name);

第二次調(diào)用之后,便會(huì)在新的實(shí)例對(duì)象上創(chuàng)建了實(shí)例屬性:name和colors。也就是說(shuō),這個(gè)時(shí)候,實(shí)例對(duì)象跟原型對(duì)象擁有2個(gè)同名屬性。這樣實(shí)在是浪費(fèi),效率又低。

為了解決這個(gè)問(wèn)題,引入了寄生組合繼承方式。重點(diǎn)就在于,不需要為了定義SubType的原型而去調(diào)用SuperType構(gòu)造函數(shù),此時(shí)只需要SuperType原型的一個(gè)副本,并將其賦值給SubType的原型即可。

function InheritPrototype(subType, superType) {
  // 創(chuàng)建超類型原型的一個(gè)副本
  const prototype = Object(superType.prototype);
  // 添加constructor屬性,因?yàn)橹貙懺蜁?huì)失去constructor屬性
  prototype.constructor = subType;
  subType.prototype = prototype;
}

將組合繼承中的:

SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;

替換成:

InheritPrototype(SubType, SuperType);

寄生組合繼承的優(yōu)點(diǎn)在于,只需要調(diào)用一次SuperType構(gòu)造函數(shù)。避免了在SubType的原型上創(chuàng)建多余的不必要的屬性。

看完上述內(nèi)容,是不是對(duì)JS原形與原型鏈的用法有進(jìn)一步的了解,如果還想學(xué)習(xí)更多內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

js
AI