衆所周知,Javascript是一門面向對象的語言,若是說針對面向對象來發問的話,我會想到兩個問題,在js中,類與實例對象是如何建立的,類與實例對象又是如何實現繼承的。es6
ES5中,尚未類的概念,而是經過函數來聲明;到了ES6,有了class關鍵字,則經過class來聲明app
// 類的聲明 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指向,從而致使父類構造函數執行時的這些屬性都會掛載到子類實例上去。
問題: 只能繼承父類構造函數中聲明的實例屬性,並無繼承父類原型的屬性和方法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兩個子類s一、s2的時候,改s1的屬性,s2的屬性也跟着改變prototype
/** * 組合方式 */ 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);
組合式就是原型鏈+構造函數繼承,解決了前兩種方法的問題,但也有不足:子類實例化時,父類構造函數執行了兩次,因此有了下面的組合繼承的優化1code
/** * 組合繼承的優化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
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
構造函數屬性繼承和創建子類和父類原型的連接繼承
引入了class、extends、super關鍵字,在子類構造函數裏調用super()方法來調用父類的構造函數。
在子類的構造函數中,只有調用super以後,纔可使用this關鍵字,不然會報錯。這是由於子類實例的構建,是基於對父類實例加工,只有super方法才能返回父類實例。ip
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充當了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。
//原型鏈接 Man.prototype = Object.create(Person.prototype); // B繼承A的靜態屬性 Object.setPrototypeOf(Man, Person); //綁定this Person.call(this);
前兩句實現了原型鏈上的繼承,最後一句實現構造函數上的繼承。