您好,登錄后才能下訂單哦!
這篇文章主要講解了JS原形與原型鏈的用法,內容清晰明了,對此有興趣的小伙伴可以學習一下,相信大家閱讀完之后會有幫助。
前言
在JS中,我們經常會遇到原型。字面上的意思會讓我們認為,是某個對象的原型,可用來繼承。但是其實這樣的理解是片面的,下面通過本文來了解原型與原型鏈的細節,再順便談談繼承的幾種方式。
在講到原型之前,我們先來回顧一下JS中的對象。在JS中,萬物皆對象,就像字符串、數值、布爾、數組
等。ECMA-262把對象定義為:無序屬性的集合,其屬性可包含基本值、對象或函數。對象是擁有屬性和方法的數據,為了描述這些事物,便有了原型的概念。
無論何時,只要創建了一個新函數,就會根據一組特定的規則為該函數創建一個prototype屬性,這個屬性指向該函數的原型對象。所有原型對象都會獲得一個constructor屬性,這個屬性包含一個指向prototype屬性所在函數的指針。
這段話摘自《JS高級程序設計》,很好理解,以創建實例的代碼為例。
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);
上面例子中的person1跟person2都是構造函數Person()
的實例,Person.prototype指向了Person函數的原型對象,而Person.prototype.constructor又指向Person。Person的每一個實例,都含有一個內部屬性__proto__
,指向Person.prototype,就像上圖所示,因此就有下面的關系。
console.log(Person.prototype.constructor === Person); // true console.log(person1.__proto__ === Person.prototype); // true console.log(person2.__proto__ === Person.prototype); // true
JS是基于原型的語言,跟基于類的面向對象語言有所不同,JS中并沒有類這個概念,有的是原型對象這個概念,原型對象作為一個模板,新對象可從原型對象中獲得屬性。那么JS具體是怎樣繼承的呢?
在講到繼承這個話題之前,我們先來理解原型鏈這個概念。
原型鏈
構造函數,原型和實例的關系已經很清楚了。每個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例對象都包含一個指向與原型對象的指針。這樣的關系非常好理解,但是如果我們想讓原型對象等于另一個類型的實例對象呢?那么就會衍生出相同的關系,此時的原型對象就會含有一個指向另一個原型對象的指針,而另一個原型對象會含有一個指向另一個構造函數的指針。如果另一個原型對象又是另一個類型的實例對象呢?這樣就構成了原型鏈。文字可能有點難理解,下面用代碼舉例。
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的原型對象作為SuperType構造函數
的實例對象,此時,SubType的原型對象
就會有一個__proto__
屬性指向SuperType的原型對象
,instance作為SubType的實例對象,必然能共享SubType的原型對象的屬性,又因為SubType的原型對象
又指向SuperType原型對象
的屬性,因此可得,instance繼承了SuperType原型的所有屬性。
我們都知道,所有函數的默認原型都是Object的實例,所以也能得出,SuperType的默認原型必然有一個__proto__
指向Object.prototype。
圖中由__proto__
屬性組成的鏈子,就是原型鏈,原型鏈的終點就是null。
上圖可很清晰的看出原型鏈的結構,這不禁讓我想到JS的一個運算符instanceof,instanceof可用來判斷一個實例對象是否屬于一個構造函數。
A instanceof B; // true
實現原理其實就是在A的原型鏈上尋找是否有原型等于B.prototype,如果一直找到A原型鏈的頂端null,仍然找不到原型等于B.prototype,那么就可返回false。下面手寫一個instanceof
,這個也是很多大廠常用的手寫面試題。
function Instance(left, right) { left = left.__proto__; right = right.prototype; while (true) { if (left === null) return false; if (left === right) return true; // 繼續在left的原型鏈向上找 left = left.__propo__; } }
原型鏈繼承
上面例子中,instance繼承了SuperType原型的屬性,其繼承的原理其實就是通過原型鏈實現的。原型鏈很強大,可用來實現繼承。可是單純的原型鏈繼承也是有問題存在的。
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"]
當SubType的原型作為SuperType的實例時,此時SubType的實例對象通過原型鏈繼承到colorArr屬性,當修改了其中一個實例對象從原型鏈中繼承到的原型屬性時,便會影響到其他實例。對instance1.colorArr的修改,在instance2.colorArr便能體現出來。
組合繼承
組合繼承指的是組合原型鏈和構造函數的技術
,通過原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數實現對實例屬性的繼承。
function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { console.log(this.name); }; function SubType(name, age) { // 繼承屬性,借用構造函數實現對實例屬性的繼承 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
上述例子中,借用構造函數繼承實例屬性,通過原型繼承原型屬性與方法。這樣就可讓不同的實例分別擁有自己的屬性,又可共享相同的方法。而不會像原型繼承那樣,對實例屬性的修改影響到了其他實例。組合繼承是JS最常用的繼承方式。
寄生組合式繼承
雖然說組合繼承是最常用的繼承方式,但是有沒有發現,就上面的例子中,組合繼承中調用了2次SuperType函數。回憶一下,在第一次調用SubType時。
SubType.prototype = new SuperType();
這里調用完之后,SubType.prototype會從SuperType繼承到2個屬性:name和colors。這2個屬性存在SubType的原型中。而在第二次調用時,就是在創造實例對象時,調用了SubType構造函數,也就會再調用一次SuperType構造函數。
SuperType.call(this, name);
第二次調用之后,便會在新的實例對象上創建了實例屬性:name和colors。也就是說,這個時候,實例對象跟原型對象擁有2個同名屬性。這樣實在是浪費,效率又低。
為了解決這個問題,引入了寄生組合繼承方式。重點就在于,不需要為了定義SubType的原型而去調用SuperType構造函數,此時只需要SuperType原型的一個副本,并將其賦值給SubType的原型即可。
function InheritPrototype(subType, superType) { // 創建超類型原型的一個副本 const prototype = Object(superType.prototype); // 添加constructor屬性,因為重寫原型會失去constructor屬性 prototype.constructor = subType; subType.prototype = prototype; }
將組合繼承中的:
SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType;
替換成:
InheritPrototype(SubType, SuperType);
寄生組合繼承的優點在于,只需要調用一次SuperType構造函數。避免了在SubType的原型上創建多余的不必要的屬性。
看完上述內容,是不是對JS原形與原型鏈的用法有進一步的了解,如果還想學習更多內容,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。