本週面試題一覽:
本週是繼承專題,在開始以前,須要先了解構造函數、原型和原型鏈的相關知識。javascript
更多優質文章可戳: https://github.com/YvetteLau/...前端
構造函數和普通函數的區別僅在於調用它們的方式不一樣,任何函數,只要經過 new
操做符來調用,那它就能夠做爲構造函數;任何函數,若是不經過 new
操做符來調用,那麼它就是一個普通函數。java
實例擁有 constructor(構造函數)
屬性,該屬性返回建立實例對象的構造函數。git
function Person(name, age) { this.name = name; this.age = age; } var Yvette = new Person('劉小夕', 20); console.log(Yvette.constructor === Person); //true
有一點須要說明的是,除了基本數據類型的 constructor
外( null
和 undefined
無 constructor
屬性),constructor
屬性是能夠被重寫的。所以檢測對象類型時,instanceof
操做符比 contsrutor
更可靠一些。github
function Person(name) { this.name = name; } function SuperType() { } var Yvette = new Person('劉小夕'); console.log(Yvette.constructor); //[Function: Person] Yvette.constructor = SuperType; console.log(Yvette.constructor); //[Function: SuperType]
咱們建立的每一個函數都有 prototype
屬性,這個屬性指向函數的原型對象。原型對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。面試
在默認狀況下,全部原型對象都會自動得到一個 constructor
屬性,這個屬性包含一個指向 prototype
屬性所在函數的指針。segmentfault
當調用構造函數建立一個新實例後,該實例的內部將包含一個指針,指向構造函數的原型對象(能夠經過實例的 __proto__
來訪問構造函數的原型對象)。數組
function Person(name) { this.name = name; } Person.prototype.sayName = function() { console.log(this.name); } var person1 = new Person('劉小夕'); var person2 = new Person('前端小姐姐'); //構造函數原型對象上的方法和屬性被實例共享 person1.sayName(); person1.sayName();
實例.__proto__ === 構造函數.prototype
函數
簡單回顧一下構造函數、原型和實例的關係:this
每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個能夠執行原型對象的內部指針(能夠經過 __proto
訪問)。
假如咱們讓原型對象等於另外一個類型的實例,那麼此時原型對象包含一個指向另外一個原型的指針,相應地,另外一個原型中也包含着一個指向另外一個構造函數的指針。加入另外一個原型又是另外一個類型的實例,那麼上述關係仍然成立,如此層層遞進,就構成了實例與原型的鏈條,這就是原型鏈的基本概念。
function SuperType() { this.type = 'animal'; } SuperType.prototype.getType = function() { console.log(this.type); } function SubType() { } SubType.prototype = new SuperType(); SubType.prototype.sayHello = function() { console.log('hello'); } function SimType(name) { this.name = name; } SimType.prototype = new SubType(); SimType.prototype.sayHi = function() { console.log('hi'); } var instance = new SimType('劉小夕'); instance.getType();
一圖勝萬言:
調用 instance.getType()
會調用如下的搜索步驟:
instance
實例SimType.prototype
SubType.prototype
SuperType.prototype
,找到了 getType
方法在找不到屬性或方法的狀況下,搜索過程老是要一環一環地前行到原型鏈的末端纔會停下來。
全部引用類型都繼承了 Object
,這個繼承也是經過原型鏈實現的。若是在 SuperType.prototype
尚未找到 getType
,就會到 Object.prototype
中找(圖中少畫了一環)。
原型鏈繼承的基本思想是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。
如 SubType.prototype = new SuperType()
;
function SuperType() { this.name = 'Yvette'; this.colors = ['pink', 'blue', 'green']; } SuperType.prototype.getName = function () { return this.name; } function SubType() { this.age = 22; } SubType.prototype = new SuperType(); SubType.prototype.getAge = function() { return this.age; } SubType.prototype.constructor = SubType; let instance1 = new SubType(); instance1.colors.push('yellow'); console.log(instance1.getName()); //'Yvette' console.log(instance1.colors);//[ 'pink', 'blue', 'green', 'yellow' ] let instance2 = new SubType(); console.log(instance2.colors);//[ 'pink', 'blue', 'green', 'yellow' ]
能夠看出 colors
屬性會被全部的實例共享(instance一、instance二、...)。
缺點:
借用構造函數的技術,其基本思想爲:
在子類型的構造函數中調用超類型構造函數。
function SuperType(name) { this.name = name; this.colors = ['pink', 'blue', 'green']; } function SubType(name) { SuperType.call(this, name); } let instance1 = new SubType('Yvette'); instance1.colors.push('yellow'); console.log(instance1.colors);//['pink', 'blue', 'green', yellow] let instance2 = new SubType('Jack'); console.log(instance2.colors); //['pink', 'blue', 'green']
優勢:
缺點:
組合繼承指的是將原型鏈和借用構造函數技術組合到一塊,從而發揮兩者之長的一種繼承模式。基本思路:
使用原型鏈實現對原型屬性和方法的繼承,經過借用構造函數來實現對實例屬性的繼承,既經過在原型上定義方法來實現了函數複用,又保證了每一個實例都有本身的屬性。
function SuperType(name) { this.name = name; this.colors = ['pink', 'blue', 'green']; } SuperType.prototype.sayName = function () { console.log(this.name); } function SuberType(name, age) { SuperType.call(this, name); this.age = age; } SuberType.prototype = new SuperType(); SuberType.prototype.constructor = SuberType; SuberType.prototype.sayAge = function () { console.log(this.age); } let instance1 = new SuberType('Yvette', 20); instance1.colors.push('yellow'); console.log(instance1.colors); //[ 'pink', 'blue', 'green', 'yellow' ] instance1.sayName(); //Yvette let instance2 = new SuberType('Jack', 22); console.log(instance2.colors); //[ 'pink', 'blue', 'green' ] instance2.sayName();//Jack
缺點:
優勢:
原型繼承的基本思想:
藉助原型能夠基於已有的對象建立新對象,同時還沒必要所以建立自定義類型。
function object(o) { function F() { } F.prototype = o; return new F(); }
在 object()
函數內部,先穿甲一個臨時性的構造函數,而後將傳入的對象做爲這個構造函數的原型,最後返回了這個臨時類型的一個新實例,從本質上講,object()
對傳入的對象執行了一次淺拷貝。
ECMAScript5經過新增 Object.create()
方法規範了原型式繼承。這個方法接收兩個參數:一個用做新對象原型的對象和(可選的)一個爲新對象定義額外屬性的對象(能夠覆蓋原型對象上的同名屬性),在傳入一個參數的狀況下,Object.create()
和 object()
方法的行爲相同。
var person = { name: 'Yvette', hobbies: ['reading', 'photography'] } var person1 = Object.create(person); person1.name = 'Jack'; person1.hobbies.push('coding'); var person2 = Object.create(person); person2.name = 'Echo'; person2.hobbies.push('running'); console.log(person.hobbies);//[ 'reading', 'photography', 'coding', 'running' ] console.log(person1.hobbies);//[ 'reading', 'photography', 'coding', 'running' ]
在沒有必要建立構造函數,僅讓一個對象與另外一個對象保持類似的狀況下,原型式繼承是能夠勝任的。
缺點:
同原型鏈實現繼承同樣,包含引用類型值的屬性會被全部實例共享。
寄生式繼承是與原型式繼承緊密相關的一種思路。寄生式繼承的思路與寄生構造函數和工廠模式相似,即建立一個僅用於封裝繼承過程的函數,該函數在內部已某種方式來加強對象,最後再像真地是它作了全部工做同樣返回對象。
function createAnother(original) { var clone = object(original);//經過調用函數建立一個新對象 clone.sayHi = function () {//以某種方式加強這個對象 console.log('hi'); }; return clone;//返回這個對象 } var person = { name: 'Yvette', hobbies: ['reading', 'photography'] }; var person2 = createAnother(person); person2.sayHi(); //hi
基於 person
返回了一個新對象 -—— person2
,新對象不只具備 person
的全部屬性和方法,並且還有本身的 sayHi()
方法。在考慮對象而不是自定義類型和構造函數的狀況下,寄生式繼承也是一種有用的模式。
缺點:
所謂寄生組合式繼承,即經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法,基本思路:
沒必要爲了指定子類型的原型而調用超類型的構造函數,咱們須要的僅是超類型原型的一個副本,本質上就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型。寄生組合式繼承的基本模式以下所示:
function inheritPrototype(subType, superType) { var prototype = object(superType.prototype); //建立對象 prototype.constructor = subType;//加強對象 subType.prototype = prototype;//指定對象 }
constructor
屬性至此,咱們就能夠經過調用 inheritPrototype
來替換爲子類型原型賦值的語句:
function SuperType(name) { this.name = name; this.colors = ['pink', 'blue', 'green']; } //...code function SuberType(name, age) { SuperType.call(this, name); this.age = age; } inheritPrototype(SuberType, SuperType); //...code
優勢:
只調用了一次超類構造函數,效率更高。避免在SuberType.prototype
上面建立沒必要要的、多餘的屬性,與其同時,原型鏈還能保持不變。
所以寄生組合繼承是引用類型最理性的繼承範式。
Class
能夠經過extends關鍵字實現繼承,如:
class SuperType { constructor(age) { this.age = age; } getAge() { console.log(this.age); } } class SubType extends SuperType { constructor(age, name) { super(age); // 調用父類的constructor(x, y) this.name = name; } getName() { console.log(this.name); } } let instance = new SubType(22, '劉小夕'); instance.getAge(); //22
對於ES6的 class
須要作如下幾點說明:
console.log(typeof SuperType);//function console.log(SuperType === SuperType.prototype.constructor); //true
Object.keys(SuperType.prototype);
constructor
方法是類的默認方法,經過 new
命令生成對象實例時,自動調用該方法。一個類必須有constructor
方法,若是沒有顯式定義,一個空的 constructor
方法會被默認添加。Class
不能像構造函數那樣直接調用,會拋出錯誤。使用 extends
關鍵字實現繼承,有一點須要特別說明:
constructor
中調用 super
方法,不然新建實例時會報錯。若是沒有子類沒有定義 constructor
方法,那麼這個方法會被默認添加。在子類的構造函數中,只有調用 super
以後,才能使用 this
關鍵字,不然報錯。這是由於子類實例的構建,基於父類實例,只有super方法才能調用父類實例。class SubType extends SuperType { constructor(...args) { super(...args); } }
[1] CSS-清除浮動
[2] 詳解JS函數柯里化
[3] JavaScript數組去重
謝謝各位小夥伴願意花費寶貴的時間閱讀本文,若是本文給了您一點幫助或者是啓發,請不要吝嗇你的贊和Star,您的確定是我前進的最大動力。 https://github.com/YvetteLau/...
推薦關注本人公衆號: