繼承6種套餐
參照紅皮書,JS繼承一共6種
1.原型鏈繼承
核心思想:子類的原型指向父類的一個(gè)實(shí)例
Son.prototype=new Father();
2.構(gòu)造函數(shù)繼承
核心思想:借用apply和call方法在子對(duì)象中調(diào)用父對(duì)象
function Son(){Father.call(this);}
3.組合繼承(1+2)(常用)
核心思想:1+2,但記得修正constructor
function Son(){Father.call(this);}
Son.prototype=new Father();
Son.prototype.constructor = Son;
4.原型式繼承
核心思想:返回一個(gè)臨時(shí)類型的一個(gè)新實(shí)例,現(xiàn)提出了規(guī)范的原型式繼承,使用Object.create()方法。
var person={name:"xiaoming",age:16}
var anotherperson=Object.create(person,{name:"xiaowang"})
5.寄生式繼承
核心思想:創(chuàng)建一個(gè)僅用于封裝繼承過(guò)程的函數(shù),該函數(shù)在內(nèi)部使用某種方式增強(qiáng)對(duì)象
function createAnother(original){
var clone=object(original);
clone.name="ahaha";
return clone;
}
6.寄生組合繼承
核心思想:3+5
function inheritPropertype(son,father){
var prototype=object(father.prototype);//創(chuàng)建
prototype.constructor=son;//增強(qiáng)
son.prototype=prototype;//指定
}
在阮一峰老師的解說(shuō)下,他將繼承分成了兩種,構(gòu)造函數(shù)的繼承和非構(gòu)造函數(shù)的繼承
構(gòu)造函數(shù)的繼承:
1.apply或call
2.prototype,即子類原型屬性指向父類實(shí)例
3.直接的prototype,子類原型=父類原型
4.利用空對(duì)象作為中介,這種方法類似寄生繼承,但是會(huì)變成子類->中介->父類這樣的繼承關(guān)系。好處是當(dāng)子類對(duì)原型進(jìn)行變動(dòng)時(shí),對(duì)父類沒有影響。
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;//繼承方法2
Child.prototype = new F();//繼承方法1
Child.prototype.constructor = Child;//修正
Child.uber = Parent.prototype;//為子對(duì)象設(shè)一個(gè)uber屬性,這個(gè)屬性直接指向父對(duì)象的prototype屬性。只是為
??????????????????????????????????????????????????????? //了實(shí)現(xiàn)繼承的完備性,純屬備用性質(zhì)。
}
5.拷貝繼承,將父對(duì)象的prototype對(duì)象中的屬性,一一拷貝給Child對(duì)象的prototype對(duì)象。
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
非構(gòu)造函數(shù)的繼承:
1.原型式繼承。
2.淺拷貝
function extendCopy(p) {
var c = {};
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
return c;
}
子對(duì)象獲得的只是一個(gè)內(nèi)存地址,而不是真正拷貝,因此存在父對(duì)象被篡改的可能。
3.深拷貝
function deepCopy(p, c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
ES6的class語(yǔ)法糖
不知道為什么標(biāo)題都是跟吃的有關(guān)
可能是因?yàn)榈搅税胍拱桑ㄌ?/p>
在學(xué)ES6之前,我們苦苦背下JS繼承的典型方法
學(xué)習(xí)ES6后,發(fā)現(xiàn)官方雞賊地給我們一個(gè)語(yǔ)法糖——class。它可以看作是構(gòu)造函數(shù)穿上了統(tǒng)一的制服,所以class的本質(zhì)依然是函數(shù),一個(gè)構(gòu)造函數(shù)。
class是es6新定義的變量聲明方法(復(fù)習(xí):es5的變量聲明有var function和隱式聲明 es6則新增let const class import),它的內(nèi)部是嚴(yán)格模式。class不存在變量提升。
例:
//定義類
classPoint{
??? constructor(x,y){
??? ??? this.x=x;
???? ?? this.y=y;
??? }
??? toString(){
??? ??? return'('+this.x+', '+this.y+')';
??? }
}
constructor就是構(gòu)造函數(shù),不多說(shuō),跟c++學(xué)的時(shí)候差不多吧,this對(duì)象指向?qū)嵗?/p>
類的所有方法都定義在類的prototype屬性上面,在類的內(nèi)部定義方法不用加function關(guān)鍵字。在類的外部添加方法,請(qǐng)指向原型,即實(shí)例的__proto__或者類的prototype。
Object.assign方法可以很方便地一次向類添加多個(gè)方法。
Object.assign(Point.prototype,{toString(){},toValue(){}});
私有的,靜態(tài)的,實(shí)例的
私有方法,私有屬性
類的特性是封裝,在其他語(yǔ)言的世界里,有private、public和protected來(lái)區(qū)分,而js就沒有
js在es5的時(shí)代,嘗試了一些委婉的方法,比如對(duì)象屬性的典型的set和get方法,在我之前說(shuō)的JS的數(shù)據(jù)屬性和訪問(wèn)器屬性
現(xiàn)在es6規(guī)定,可以在class里面也使用setter和getter:
class MyClass {
constructor() { // ... }
get prop() { return 'getter'; }
set prop(value) { console.log('setter: '+value); }
}
let inst = new MyClass();
inst.prop = 123; // setter: 123
inst.prop // 'getter'
那么在這次es6的class里面,如何正式地去表示私有呢?
方法有叁:
1,老辦法,假裝私有。私有的東西,命名前加個(gè)下劃線,當(dāng)然了這只是前端程序員的自我暗示,實(shí)際上在外部應(yīng)該還是可以訪問(wèn)得到私有方法。
2,乾坤大挪移。把目標(biāo)私有方法挪出class外,class的一個(gè)公有方法內(nèi)部調(diào)用這個(gè)外部的“私有”方法。
class Widget {
foo (baz) { bar.call(this, baz); } // ...
}
function bar(baz) { return this.snaf = baz; }
3,ES6順風(fēng)車,SYMBOL。利用Symbol值的唯一性,將私有方法的名字命名為一個(gè)Symbol值。Symbol是第三方無(wú)法獲取的,所以外部也就無(wú)法偷看私有方法啦。
const bar = Symbol('bar');
const snaf = Symbol('snaf');
export default class myClass{
// 公有方法
foo(baz) { this[bar](baz); }
// 私有方法
[bar](baz) { return this[snaf] = baz; }
// ... };
那屬性怎么私有化呢?現(xiàn)在還不支持,但ES6有一個(gè)提案,私有屬性應(yīng)在命名前加#號(hào)。
靜態(tài)方法,靜態(tài)屬性
類相當(dāng)于實(shí)例的原型,所有在類中定義的方法,都會(huì)被實(shí)例繼承。如果在一個(gè)方法前,加上static關(guān)鍵字,就表示該方法不會(huì)被實(shí)例繼承,而是直接通過(guò)類來(lái)調(diào)用,這就稱為“靜態(tài)方法”。如果靜態(tài)方法包含this關(guān)鍵字,這個(gè)this指的是類,而不是實(shí)例。父類的靜態(tài)方法,可以被子類繼承。
?ES6 明確規(guī)定,Class 內(nèi)部只有靜態(tài)方法,沒有靜態(tài)屬性。
聲明一個(gè)靜態(tài)屬性,目前只支持以下寫法,定義在外部:
class Foo {
}
Foo.prop = 1;
Foo.prop // 1
ES6當(dāng)然也有提案,靜態(tài)屬性的聲明采用static關(guān)鍵字,不過(guò)也是只提案。
實(shí)例屬性
直接寫。
class MyClass {
myProp = 42;
constructor() {
console.log(this.myProp); // 42
}
}
我有特殊的繼承技巧
既然已經(jīng)把class明擺出來(lái),當(dāng)然就可以擺脫“私生子”的身份,光明正大繼承了。
Class 可以通過(guò)extends關(guān)鍵字實(shí)現(xiàn)繼承:
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 調(diào)用父類的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 調(diào)用父類的toString()
}
}
在這里Point是父類,ColorPoint是子類,在子類中,super關(guān)鍵字代表父類,而在子類的構(gòu)造函數(shù)中必須調(diào)用super方法,通過(guò)super方法新建一個(gè)父類的this對(duì)象(子類自身沒有this對(duì)象),子類是依賴于父類的?;谶@個(gè)設(shè)計(jì)思想,我們?cè)谧宇愔行枰⒁猓鹤宇悓?shí)例實(shí)際上依賴于父類的實(shí)例,是先有爹后有子,所以構(gòu)造函數(shù)先super后用this;父類的靜態(tài)方法是會(huì)被子類所繼承的。
Class繼承的原理:
class A { }
class B { }
// B 的實(shí)例繼承 A 的實(shí)例
Object.setPrototypeOf(B.prototype, A.prototype);//B.prototype.__proto__=A.prototype
// B 的實(shí)例繼承 A 的靜態(tài)屬性
Object.setPrototypeOf(B, A);//B.__proto__=A
const b = new B();
在這里我們重新擦亮雙眼,大喊三遍:class的本質(zhì)是構(gòu)造函數(shù)class的本質(zhì)是構(gòu)造函數(shù)class的本質(zhì)是構(gòu)造函數(shù)
在之前的原型學(xué)習(xí)筆記里面,我學(xué)習(xí)到了prototype是函數(shù)才有的屬性,而__proto__是每個(gè)對(duì)象都有的屬性。

在上述的class實(shí)質(zhì)繼承操作中,利用了Object.setPrototypeOf(),這個(gè)方法把參數(shù)1的原型設(shè)為參數(shù)2。
所以實(shí)際上我們是令B.prototype.__proto__=A.prototype,轉(zhuǎn)化為圖像就是上圖所示,F(xiàn)ather.prototype(更正圖上的Father)截胡,變?yōu)榱薙on.prototype走向Object.prototype的中間站。
那為什么還有第二步B.__proto__=A呢?在class出來(lái)以前,我們的繼承操作僅到上一步為止。
但是既然希望使用class來(lái)取代野路子繼承,必須考慮到方法面面,譬如父類靜態(tài)屬性的繼承。
在沒有這一步之前,我們看看原本原型鏈的意義:Son.__proto__==Function.prototype,意味著Son是Function 的一個(gè)實(shí)例。因?yàn)槲覀兛梢酝ㄟ^(guò)類比,一個(gè)類的實(shí)例的__proto__的確指向了類的原型對(duì)象(prototype)。
所以B.__proto__=A意味著B是A的一個(gè)實(shí)例嗎?可以說(shuō)有這樣的意味在里面,所以假使將B看作是A的一個(gè)實(shí)例,A是一個(gè)類似于原型對(duì)象的存在,而A的靜態(tài)屬性在這里失去了相對(duì)性,可看作是一個(gè)實(shí)例屬性,同時(shí)B還是A的子類,那么A的靜態(tài)屬性就是可繼承給B的,并且繼承后,B對(duì)繼承來(lái)的靜態(tài)對(duì)象如何操作都影響不到A,AB的靜態(tài)對(duì)象是互相獨(dú)立的。
當(dāng)然,上述只是我一個(gè)弱雞的理解,讓我們看看在阮一峰大神的教程里是怎么解讀的:
大多數(shù)瀏覽器的 ES5 實(shí)現(xiàn)之中,每一個(gè)對(duì)象都有__proto__屬性,指向?qū)?yīng)的構(gòu)造函數(shù)的prototype屬性。Class 作為構(gòu)造函數(shù)的語(yǔ)法糖,同時(shí)有prototype屬性和__proto__屬性,因此同時(shí)存在兩條繼承鏈。
(1)子類的__proto__屬性,表示構(gòu)造函數(shù)的繼承,總是指向父類。
(2)子類prototype屬性的__proto__屬性,表示方法的繼承,總是指向父類的prototype屬性。
經(jīng)過(guò)上述的我個(gè)人推測(cè)和大神的準(zhǔn)確解說(shuō),解除了我心中一個(gè)顧慮:一個(gè)類的原型畢竟指向函數(shù)的原型對(duì)象,如果我們把子類的原型指向父類,是否會(huì)對(duì)它函數(shù)的本質(zhì)有一定的影響?
事實(shí)上我們可以把這個(gè)操作視為“子類降級(jí)”,子類不再直接地指向函數(shù)原型對(duì)象,它所具備的函數(shù)的一些方法特性等,會(huì)順著原型鏈指向函數(shù)原型對(duì)象,當(dāng)我們希望對(duì)某個(gè)子類實(shí)行一些函數(shù)特有的操作等,編譯器自然會(huì)通過(guò)原型鏈尋求目標(biāo)。這就是原型鏈的精妙之處。
在阮一峰老師的ES6教程的“extends的繼承目標(biāo)”一節(jié)中,講解了三種特殊的繼承,Object,不繼承,null。從這里也可以看見Function.prototype和子類的原型指向在原型鏈的角色。
class A{
constructor(){}
}
console.log(A.prototype,A.__proto__,A.prototype.__proto__)
//A.prototype==A {}//A.__proto__==[Function]
//A.prototype.__proto__=={}
super
剛才有說(shuō)到構(gòu)造函數(shù)里面有super(x,y),方法里面有super.toString(),也就是說(shuō)super有兩種意義
1,父類的構(gòu)造函數(shù)
然而這個(gè)super方法是在子類構(gòu)造函數(shù)里面使用的,所以它應(yīng)當(dāng)返回一個(gè)子類的實(shí)例,所以super里面的this應(yīng)該指向子類。super()在這里相當(dāng)于A.prototype.constructor.call(this)。
super()只能用在子類的構(gòu)造函數(shù)之中,用在其他地方會(huì)報(bào)錯(cuò)。
2,與父類相關(guān)的對(duì)象
super作為對(duì)象時(shí),在普通方法中,指向父類的原型對(duì)象;在靜態(tài)方法中,指向父類。
Decorator-修飾器
修飾器是一個(gè)對(duì)類進(jìn)行處理的函數(shù)。修飾器函數(shù)的第一個(gè)參數(shù),就是所要修飾的目標(biāo)類。
例:
@testable class MyTestableClass {
// ...
}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
另外修飾器也可以修飾方法
class Math {
@log
add(a, b) { return a + b; }
}
function log(target, name, descriptor) {
var oldValue = descriptor.value; descriptor.value = function() {
console.log(`Calling ${name} with`, arguments);
return oldValue.apply(null, arguments);
};
return descriptor;
}
const math = new Math(); // passed parameters should get logged now
math.add(2, 4);
修飾器函數(shù)一共可以接受三個(gè)參數(shù)。第一個(gè)是類的原型對(duì)象,第二個(gè)是要修飾的參數(shù),第三個(gè)是修飾參數(shù)的數(shù)據(jù)屬性對(duì)象
太累了,不想細(xì)說(shuō)了,先寫到這