JS繼承 -> ES6的class和decorator

繼承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ì)象都有的屬性。


我的學(xué)習(xí)圖,沒有備注的箭頭表示__proto__的指向

在上述的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ō)了,先寫到這

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • class的基本用法 概述 JavaScript語(yǔ)言的傳統(tǒng)方法是通過(guò)構(gòu)造函數(shù),定義并生成新對(duì)象。下面是一個(gè)例子: ...
    呼呼哥閱讀 4,210評(píng)論 3 11
  • 面向?qū)ο蟮恼Z(yǔ)言都有一個(gè)類的概念,通過(guò)類可以創(chuàng)建多個(gè)具有相同方法和屬性的對(duì)象,ES6之前并沒有類的概念,在ES6中引...
    Erric_Zhang閱讀 1,217評(píng)論 1 4
  • 本文先對(duì)es6發(fā)布之前javascript各種繼承實(shí)現(xiàn)方式進(jìn)行深入的分析比較,然后再介紹es6中對(duì)類繼承的支持以及...
    lazydu閱讀 16,834評(píng)論 7 44
  • 細(xì)雨明月不兼得 涼意秋思亦可獲 酒過(guò)腸 遙思遠(yuǎn)方 憶往昔 且向前方 前方會(huì)有故鄉(xiāng) 前方也亦有明月 ...
    彭澤西哦閱讀 191評(píng)論 0 1
  • 每一個(gè)失眠的夜晚,我都在回憶與你有關(guān)的那段青春歲月,然后告訴自己,縱然不再聯(lián)系,可是沒有失去聯(lián)系,就是最好的結(jié)局!...
    fly飛魚閱讀 816評(píng)論 0 2

友情鏈接更多精彩內(nèi)容