根據Long型別時間戳獲取當前年份並得到當前年份最後一天的時間戳
js繼承的幾種方式
1原型鏈繼承
原型鏈繼承是比較常見的繼承方式之一,其中涉及的建構函式、原型和例項,三者之間存在著一定的關係,即每一個建構函式都有一個原型物件,原型物件又包含一個指向建構函式的指標,而例項則包含一個原型物件的指標。
下面我們結合程式碼來了解一下。
function Parent1() {
this.name = 'parent1';
this.play = [1, 2, 3]
}
function Child1() {
this.type = 'child2';
}
Child1. prototype = new Parent1();
console.log(new Child1())
上面的程式碼看似沒有問題,雖然父類的方法和屬性都能夠訪問,但其實有一個潛在的問題,我再舉個例子來說明這個問題。
var s1 = new Child2();
var s2 = new Child2();
s1.play.push(4);
console.log(s1.play, s2.play);//[1,2,3,4][1,2,3,4]
明明我只改變了 s1 的 play 屬性,為什麼 s2 也跟著變了呢?原因很簡單,因為兩個例項使用的是同一個原型物件。它們的記憶體空間是共享的,當一個發生變化的時候,另外一個也隨之進行了變化,這就是使用原型鏈繼承方式的一個缺點。
第二種:建構函式繼承(藉助 call)
function Parent1(){
this.name = 'parent1';
}
Parent1.prototype.getName = function () {
return this.name;
}
function Child1(){
Parent1.call(this);
this.type = 'child1'
}
let child = new Child1();
console.log(child); // 沒問題
console.log(child.getName()); // 會報錯
執行上面的這段程式碼,可以得到這樣的結果。
可以看到最後列印的 child 在控制檯顯示,除了 Child1 的屬性 type 之外,也繼承了 Parent1 的屬性 name。這樣寫的時候子類雖然能夠拿到父類的屬性值,解決了第一種繼承方式的弊端,但問題是,父類原型物件中一旦存在父類之前自己定義的方法,那麼子類將無法繼承這些方法。
因此,從上面的結果就可以看到建構函式實現繼承的優缺點,它使父類的引用屬性不會被共享,優化了第一種繼承方式的弊端;但是隨之而來的缺點也比較明顯——只能繼承父類的例項屬性和方法,不能繼承原型屬性或者方法。
上面的兩種繼承方式各有優缺點,那麼結合二者的優點,於是就產生了下面這種組合的繼承方式。
第三種:組合繼承(前兩種組合)
這種方式結合了前兩種繼承方式的優缺點,結合起來的繼承,程式碼如下。
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
Parent3.prototype.getName = function () {
return this.name;
}
function Child3() {
// 第二次呼叫 Parent3()
Parent3.call(this);
this.type = 'child3';
}
// 第一次呼叫 Parent3()
Child3.prototype = new Parent3();
// 手動掛上構造器,指向自己的建構函式
Child3.prototype.constructor = Child3;
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play); // 不互相影響
console.log(s3.getName()); // 正常輸出'parent3'
console.log(s4.getName()); // 正常輸出'parent3'
但是這裡又增加了一個新問題:通過註釋我們可以看到 Parent3 執行了兩次,第一次是改變Child3 的 prototype 的時候,第二次是通過 call 方法呼叫 Parent3 的時候,那麼 Parent3 多構造一次就多進行了一次效能開銷,這是我們不願看到的。
那麼是否有更好的辦法解決這個問題呢?請你再往下學習,下面的第四種繼承方式可以更好地解決這裡的問題。
上面介紹的更多是圍繞著建構函式的方式,那麼對於 JavaScript 的普通物件,怎麼實現繼承呢?
第四種:原型式繼承
這裡不得不提到的就是 ES5 裡面的 Object.create 方法,這個方法接收兩個引數:一是用作新物件原型的物件、二是為新物件定義額外屬性的物件(可選引數)。
我們通過一段程式碼,看看普通物件是怎麼實現的繼承。
let parent4 = {
name: "parent4",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
let person4 = Object.create(parent4);
person4.name = "tom";
person4.friends.push("jerry");
let person5 = Object.create(parent4);
person5.friends.push("lucy");
console.log(person4.name); //tom
console.log(person4.name === person4.getName()); //true
console.log(person5.name); //parent4
console.log(person4.friends); //["p1","p2","p3","jerry","lucy"]
console.log(person5.friends); //["p1","p2","p3","jerry","lucy"]
從上面的程式碼中可以看到,通過 Object.create 這個方法可以實現普通物件的繼承,不僅僅能繼承屬性,同樣也可以繼承 getName 的方法:
- 第一個結果“tom”,比較容易理解,person4 繼承了 parent4 的 name 屬性,但是在這個基礎上又進行了自定義。
- 第二個是繼承過來的 getName 方法檢查自己的 name 是否和屬性裡面的值一樣,答案是 true。
- 第三個結果“parent4”也比較容易理解,person5 繼承了 parent4 的 name屬性,沒有進行覆蓋,因此輸出父物件的屬性。
- 最後兩個輸出結果是一樣的,講到這裡你應該可以聯想到 02 講中淺拷貝的知識點,關於引用資料型別“共享”的問題,其實
Object.create 方法是可以為一些物件實現淺拷貝的。
那麼關於這種繼承方式的缺點也很明顯,多個例項的引用型別屬性指向相同的記憶體,存在篡改的可能,接下來我們看一下在這個繼承基礎上進行優化之後的另一種繼承方式——寄生式繼承。
第五種:寄生式繼承
使用原型式繼承可以獲得一份目標物件的淺拷貝,然後利用這個淺拷貝的能力再進行增強,新增一些方法,這樣的繼承方式就叫作寄生式繼承。
雖然其優缺點和原型式繼承一樣,但是對於普通物件的繼承方式來說,寄生式繼承相比於原型式繼承,還是在父類基礎上添加了更多的方法。那麼我們看一下程式碼是怎麼實現。
let parent5 = {
name: "parent5",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
function clone(original) {
let clone = Object.create(original);
clone.getFriends = function() {
return this.friends;
};
return clone;
}
let person5 = clone(parent5);
console.log(person5.getName()); //parent5
console.log(person5.getFriends()); //["p1","p2","p3"]
通過上面這段程式碼,我們可以看到 person5 是通過寄生式繼承生成的例項,它不僅僅有 getName 的方法,而且可以看到它最後也擁有了 getFriends 的方法.
從最後的輸出結果中可以看到,person5 通過 clone 的方法,增加了 getFriends 的方法,從而使 person5 這個普通物件在繼承過程中又增加了一個方法,這樣的繼承方式就是寄生式繼承。
我在上面第三種組合繼承方式中提到了一些弊端,即兩次呼叫父類的建構函式造成浪費,下面要介紹的寄生組合繼承就可以解決這個問題。
第六種:寄生組合式繼承
結合第四種中提及的繼承方式,解決普通物件的繼承問題的 Object.create 方法,我們在前面這幾種繼承方式的優缺點基礎上進行改造,得出了寄生組合式的繼承方式,這也是所有繼承方式裡面相對最優的繼承方式,程式碼如下。
function clone (parent, child) {
// 這裡改用 Object.create 就可以減少組合繼承中多進行一次構造的過程
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
}
function Parent6() {
this.name = 'parent6';
this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {
return this.name;
}
function Child6() {
Parent6.call(this);
this.friends = 'child5';
}
clone(Parent6, Child6);
Child6.prototype.getFriends = function () {
return this.friends;
}
let person6 = new Child6();
console.log(person6);
console.log(person6.getName());
console.log(person6.getFriends());
通過這段程式碼可以看出來,這種寄生組合式繼承方式,基本可以解決前幾種繼承方式的缺點,較好地實現了繼承想要的結果,同時也減少了構造次數,減少了效能的開銷.
整體看下來,這六種繼承方式中,寄生組合式繼承是這六種裡面最優的繼承方式。