摘要:眾所周知,是一門面向對象的語言,如果說針對面向對象來發問的話,我會想到兩個問題,在中,類與實例對象是如何創建的,類與實例對象又是如何實現繼承的。但是在中是指向的,因為每一個構造函數其實都是這個對象構造的,中子類的指向父類可以實現屬性的繼承。
眾所周知,Javascript是一門面向對象的語言,如果說針對面向對象來發問的話,我會想到兩個問題,在js中,類與實例對象是如何創建的,類與實例對象又是如何實現繼承的。
面向對象 如何聲明一個類ES5中,還沒有類的概念,而是通過函數來聲明;到了ES6,有了class關鍵字,則通過class來聲明
// 類的聲明 var Animal = function () { this.name = "Animal"; }; // es6中class的聲明 class Animal2 { constructor () { this.name = "Animal2"; }如何創建對象
1.字面量對象
2.顯示的構造函數
3.Object.create
// 第一種方式:字面量 var o1 = {name: "o1"}; var o2 = new Object({name: "o2"}); // 第二種方式:構造函數 var M = function (name) { this.name = name; }; var o3 = new M("o3"); // 第三種方式:Object.create var p = {name: "p"}; var o4 = Object.create(p);類與繼承
如何實現繼承?
繼承的本質就是原型鏈
/** * 借助構造函數實現繼承 */ function Parent1 () { this.name = "parent1"; } Parent1.prototype.say = function () { }; function Child1 () { Parent1.call(this); // 或Parent1.apply(this,arguments) this.type = "child1"; } console.log(new Child1(), new Child1().say());
重點是這句:Parent1.call(this); 在子類的構造函數里執行父類的構造函數,通過call/apply改變this指向,從而導致父類構造函數執行時的這些屬性都會掛載到子類實例上去。
問題: 只能繼承父類構造函數中聲明的實例屬性,并沒有繼承父類原型的屬性和方法
/** * 借助原型鏈實現繼承 */ function Parent2 () { this.name = "parent2"; this.play = [1, 2, 3]; } function Child2 () { this.type = "child2"; } Child2.prototype = new Parent2(); var s1 = new Child2(); var s2 = new Child2(); console.log(s1.play, s2.play); s1.play.push(4);
重點就是這句: Child2.prototype = new Parent2(); 就是說 new 一個父類的實例,然后賦給子類的原型 也就是說 new Child2().__proto__ === Child2.prototype === new Parent2()當我們在new Child2()中找不到屬性/方法,順著原型鏈就能找到new Parent2(),這樣就實現了繼承。
問題: 原型鏈中的原型對象是共用的,子類無法通過父類創建私有屬性
比如當你new兩個子類s1、s2的時候,改s1的屬性,s2的屬性也跟著改變
/** * 組合方式 */ function Parent3 () { this.name = "parent3"; this.play = [1, 2, 3]; } function Child3 () { Parent3.call(this); // 父類構造函數執行了 this.type = "child3"; } Child3.prototype = new Parent3(); // 父類構造函數執行了 var s3 = new Child3(); var s4 = new Child3(); s3.play.push(4); console.log(s3.play, s4.play);
組合式就是原型鏈+構造函數繼承,解決了前兩種方法的問題,但也有不足:子類實例化時,父類構造函數執行了兩次,所以有了下面的組合繼承的優化1
組合繼承的優化1/** * 組合繼承的優化1 * @type {String} */ function Parent4 () { this.name = "parent4"; this.play = [1, 2, 3]; } function Child4 () { Parent4.call(this); this.type = "child4"; } Child4.prototype = Parent4.prototype; var s5 = new Child4(); var s6 = new Child4(); console.log(s5, s6); console.log(s5 instanceof Child4, s5 instanceof Parent4); console.log(s5.constructor);
其實就是把原型鏈繼承的那句 Child4.prototype = new Parent4(); 改為 Child4.prototype = Parent4.prototype; 這樣雖然父類構造函數只執行了一次了,但又有了新的問題: 無法判斷s5是Child4的實例還是Parent4的實例 因為Child4.prototype.constructor指向了Parent4的實例;如果直接加一句 Child4.prototype.constructor = Child4 也不行,這樣Parent4.prototype.constructor也指向Child4,就無法區分父類實例了。
若要判斷a是A的實例 用constructor組合繼承的優化2(推薦)
a.__proto__.constructor === A
用instanceof則不準確, instanceof 判斷 實例對象的__proto__ 是不是和 構造函數的prototype 是同一個引用。若A 繼承 B, B 繼承 C 在該原型鏈上的對象 用instanceof判斷都返回ture
/** * 組合繼承的優化2 */ function Parent5 () { this.name = "parent5"; this.play = [1, 2, 3]; } function Child5 () { Parent5.call(this); this.type = "child5"; } //注意此處,用到了Object.creat(obj)方法,該方法會對傳入的obj對象進行淺拷貝 //這個方法作為一個橋梁,達到父類和子類的一個隔離 Child5.prototype = Object.create(Parent5.prototype); //修改構造函數指向 Child5.prototype.constructor = Child5
構造函數屬性繼承和建立子類和父類原型的鏈接
ES6實現繼承引入了class、extends、super關鍵字,在子類構造函數里調用super()方法來調用父類的構造函數。
在子類的構造函數中,只有調用super之后,才可以使用this關鍵字,否則會報錯。這是因為子類實例的構建,是基于對父類實例加工,只有super方法才能返回父類實例。
class Child6 extends Parent6 { constructor(x, y, color) { super(x, y); // 調用父類的constructor(x, y) this.color = color; } toString() { return this.color + " " + super.toString(); // super代表父類原型,調用父類的toString() } }class實現原理
Class充當了ES5中構造函數在繼承實現過程中的作用
有prototype屬性,有__proto__屬性,這個屬性在ES6中的指向有一些主動的修改。
同時存在兩條繼承鏈:一條實現屬性繼承,一條實現方法繼承。
class A extends B {} A.__proto__ === B; //繼承屬性 A.prototype.__proto__ === B.prototype; //繼承方法
ES6的子類的__proto__是父類,子類的原型的__proto__是父類的原型。
但是在ES5中 A.__proto__是指向Function.prototype的,因為每一個構造函數其實都是Function這個對象構造的,ES6中子類的__proto__指向父類可以實現屬性的繼承。
只有函數有prototype屬性,只有對象有__proto__屬性 ;但函數也有__proto__屬性,因為函數也是一個對象,函數的__proto__等于 Function.prototype。extends實現原理
//原型連接 Man.prototype = Object.create(Person.prototype); // B繼承A的靜態屬性 Object.setPrototypeOf(Man, Person); //綁定this Person.call(this);
前兩句實現了原型鏈上的繼承,最后一句實現構造函數上的繼承。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93997.html
摘要:很多情況下,通常一個人類,即創建了一個具體的對象。對象就是數據,對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍圖或原型。在中,對象通過對類的實體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:很多情況下,通常一個人類,即創建了一個具體的對象。對象就是數據,對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍圖或原型。在中,對象通過對類的實體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:很多情況下,通常一個人類,即創建了一個具體的對象。對象就是數據,對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍圖或原型。在中,對象通過對類的實體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:是完全的面向對象語言,它們通過類的形式組織函數和變量,使之不能脫離對象存在。而在基于原型的面向對象方式中,對象則是依靠構造器利用原型構造出來的。 JavaScript 函數式腳本語言特性以及其看似隨意的編寫風格,導致長期以來人們對這一門語言的誤解,即認為 JavaScript 不是一門面向對象的語言,或者只是部分具備一些面向對象的特征。本文將回歸面向對象本意,從對語言感悟的角度闡述為什...
摘要:之前,本質上不能算是一門面向對象的編程語言,因為它對于封裝繼承多態這些面向對象語言的特點并沒有在語言層面上提供原生的支持。所以在中出現了等關鍵字,解決了面向對象中出現了問題。 ES6之前,javascript本質上不能算是一門面向對象的編程語言,因為它對于封裝、繼承、多態這些面向對象語言的特點并沒有在語言層面上提供原生的支持。但是,它引入了原型(prototype)的概念,可以讓我們以...
摘要:除了以上介紹的幾種對象創建方式,此外還有寄生構造函數模式穩妥構造函數模式。 showImg(https://segmentfault.com/img/remote/1460000018196128); 面向對象 是以 對象 為中心的編程思想,它的思維方式是構造。 面向對象 編程的三大特點:封裝、繼承、多態: 封裝:屬性方法的抽象 繼承:一個類繼承(復制)另一個類的屬性/方法 多態:方...
閱讀 2337·2021-11-16 11:52
閱讀 2323·2021-11-11 16:55
閱讀 750·2021-09-02 15:41
閱讀 2981·2019-08-30 15:54
閱讀 3142·2019-08-30 15:54
閱讀 2252·2019-08-29 15:39
閱讀 1507·2019-08-29 15:18
閱讀 968·2019-08-29 13:00