JS繼承 -> ES6的class和decorator

繼承6種套餐

參照紅皮書,JS繼承一共6種

1.原型鏈繼承

核心思想:子類的原型指向父類的一個實例

Son.prototype=new Father();

2.構造函數繼承

核心思想:借用apply和call方法在子對象中調用父對象

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.原型式繼承

核心思想:返回一個臨時類型的一個新實例,現提出了規范的原型式繼承,使用Object.create()方法。

var person={name:"xiaoming",age:16}

var anotherperson=Object.create(person,{name:"xiaowang"})

5.寄生式繼承

核心思想:創建一個僅用于封裝繼承過程的函數,該函數在內部使用某種方式增強對象

function createAnother(original){

var clone=object(original);

clone.name="ahaha";

return clone;

}

6.寄生組合繼承

核心思想:3+5

function inheritPropertype(son,father){

var prototype=object(father.prototype);//創建

prototype.constructor=son;//增強

son.prototype=prototype;//指定

}

在阮一峰老師的解說下,他將繼承分成了兩種,構造函數的繼承非構造函數的繼承

構造函數的繼承:

1.apply或call

2.prototype,即子類原型屬性指向父類實例

3.直接的prototype,子類原型=父類原型

4.利用空對象作為中介,這種方法類似寄生繼承,但是會變成子類->中介->父類這樣的繼承關系。好處是當子類對原型進行變動時,對父類沒有影響。

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;//為子對象設一個uber屬性,這個屬性直接指向父對象的prototype屬性。只是為

??????????????????????????????????????????????????????? //了實現繼承的完備性,純屬備用性質。

  }

5.拷貝繼承,將父對象的prototype對象中的屬性,一一拷貝給Child對象的prototype對象。

  function extend2(Child, Parent) {

    var p = Parent.prototype;

    var c = Child.prototype;

    for (var i in p) {

      c[i] = p[i];

      }

    c.uber = p;

  }

非構造函數的繼承:

1.原型式繼承。

2.淺拷貝

  function extendCopy(p) {

    var c = {};

    for (var i in p) {

      c[i] = p[i];

    }

    c.uber = p;

    return c;

  }

子對象獲得的只是一個內存地址,而不是真正拷貝,因此存在父對象被篡改的可能。

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語法糖

不知道為什么標題都是跟吃的有關

可能是因為到了半夜吧(虛

在學ES6之前,我們苦苦背下JS繼承的典型方法

學習ES6后,發現官方雞賊地給我們一個語法糖——class。它可以看作是構造函數穿上了統一的制服,所以class的本質依然是函數,一個構造函數。

class是es6新定義的變量聲明方法(復習:es5的變量聲明有var function和隱式聲明 es6則新增let const class import),它的內部是嚴格模式。class不存在變量提升。

例:

//定義類

classPoint{

??? constructor(x,y){

??? ??? this.x=x;

???? ?? this.y=y;

??? }

??? toString(){

??? ??? return'('+this.x+', '+this.y+')';

??? }

}

constructor就是構造函數,不多說,跟c++學的時候差不多吧,this對象指向實例。

類的所有方法都定義在類的prototype屬性上面,在類的內部定義方法不用加function關鍵字。在類的外部添加方法,請指向原型,即實例的__proto__或者類的prototype。

Object.assign方法可以很方便地一次向類添加多個方法。

Object.assign(Point.prototype,{toString(){},toValue(){}});

私有的,靜態的,實例的

私有方法,私有屬性

類的特性是封裝,在其他語言的世界里,有private、public和protected來區分,而js就沒有

js在es5的時代,嘗試了一些委婉的方法,比如對象屬性的典型的set和get方法,在我之前說的JS的數據屬性和訪問器屬性

現在es6規定,可以在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,老辦法,假裝私有。私有的東西,命名前加個下劃線,當然了這只是前端程序員的自我暗示,實際上在外部應該還是可以訪問得到私有方法。

2,乾坤大挪移。把目標私有方法挪出class外,class的一個公有方法內部調用這個外部的“私有”方法。

class Widget {

foo (baz) { bar.call(this, baz); } // ...

}

function bar(baz) { return this.snaf = baz; }

3,ES6順風車,SYMBOL。利用Symbol值的唯一性,將私有方法的名字命名為一個Symbol值。Symbol是第三方無法獲取的,所以外部也就無法偷看私有方法啦。

const bar = Symbol('bar');

const snaf = Symbol('snaf');

export default class myClass{

// 公有方法

foo(baz) { this[bar](baz); }

// 私有方法

[bar](baz) { return this[snaf] = baz; }

// ... };

那屬性怎么私有化呢?現在還不支持,但ES6有一個提案,私有屬性應在命名前加#號。

靜態方法,靜態屬性

類相當于實例的原型,所有在類中定義的方法,都會被實例繼承。如果在一個方法前,加上static關鍵字,就表示該方法不會被實例繼承,而是直接通過類來調用,這就稱為“靜態方法”。如果靜態方法包含this關鍵字,這個this指的是類,而不是實例。父類的靜態方法,可以被子類繼承。

?ES6 明確規定,Class 內部只有靜態方法,沒有靜態屬性。

聲明一個靜態屬性,目前只支持以下寫法,定義在外部:

class Foo {

}

Foo.prop = 1;

Foo.prop // 1

ES6當然也有提案,靜態屬性的聲明采用static關鍵字,不過也是只提案。

實例屬性

直接寫。

class MyClass {

myProp = 42;

constructor() {

console.log(this.myProp); // 42

}

}

我有特殊的繼承技巧

既然已經把class明擺出來,當然就可以擺脫“私生子”的身份,光明正大繼承了。

Class 可以通過extends關鍵字實現繼承:

class ColorPoint extends Point {

constructor(x, y, color) {

super(x, y); // 調用父類的constructor(x, y)

this.color = color;

}

toString() {

return this.color + ' ' + super.toString(); // 調用父類的toString()

}

}

在這里Point是父類,ColorPoint是子類,在子類中,super關鍵字代表父類,而在子類的構造函數中必須調用super方法,通過super方法新建一個父類的this對象(子類自身沒有this對象),子類是依賴于父類的?;谶@個設計思想,我們在子類中需要注意:子類實例實際上依賴于父類的實例,是先有爹后有子,所以構造函數先super后用this;父類的靜態方法是會被子類所繼承的。

Class繼承的原理

class A { }

class B { }

// B 的實例繼承 A 的實例

Object.setPrototypeOf(B.prototype, A.prototype);//B.prototype.__proto__=A.prototype

// B 的實例繼承 A 的靜態屬性

Object.setPrototypeOf(B, A);//B.__proto__=A

const b = new B();

在這里我們重新擦亮雙眼,大喊三遍:class的本質是構造函數class的本質是構造函數class的本質是構造函數

在之前的原型學習筆記里面,我學習到了prototype是函數才有的屬性,而__proto__是每個對象都有的屬性。


我的學習圖,沒有備注的箭頭表示__proto__的指向

在上述的class實質繼承操作中,利用了Object.setPrototypeOf(),這個方法把參數1的原型設為參數2。

所以實際上我們是令B.prototype.__proto__=A.prototype,轉化為圖像就是上圖所示,Father.prototype(更正圖上的Father)截胡,變為了Son.prototype走向Object.prototype的中間站。

那為什么還有第二步B.__proto__=A呢?在class出來以前,我們的繼承操作僅到上一步為止。

但是既然希望使用class來取代野路子繼承,必須考慮到方法面面,譬如父類靜態屬性的繼承。

在沒有這一步之前,我們看看原本原型鏈的意義:Son.__proto__==Function.prototype,意味著Son是Function 的一個實例。因為我們可以通過類比,一個類的實例的__proto__的確指向了類的原型對象(prototype)。

所以B.__proto__=A意味著B是A的一個實例嗎?可以說有這樣的意味在里面,所以假使將B看作是A的一個實例,A是一個類似于原型對象的存在,而A的靜態屬性在這里失去了相對性,可看作是一個實例屬性,同時B還是A的子類,那么A的靜態屬性就是可繼承給B的,并且繼承后,B對繼承來的靜態對象如何操作都影響不到A,AB的靜態對象是互相獨立的。

當然,上述只是我一個弱雞的理解,讓我們看看在阮一峰大神的教程里是怎么解讀的:

大多數瀏覽器的 ES5 實現之中,每一個對象都有__proto__屬性,指向對應的構造函數的prototype屬性。Class 作為構造函數的語法糖,同時有prototype屬性和__proto__屬性,因此同時存在兩條繼承鏈。

(1)子類的__proto__屬性,表示構造函數的繼承,總是指向父類。

(2)子類prototype屬性的__proto__屬性,表示方法的繼承,總是指向父類的prototype屬性。

經過上述的我個人推測和大神的準確解說,解除了我心中一個顧慮:一個類的原型畢竟指向函數的原型對象,如果我們把子類的原型指向父類,是否會對它函數的本質有一定的影響?

事實上我們可以把這個操作視為“子類降級”,子類不再直接地指向函數原型對象,它所具備的函數的一些方法特性等,會順著原型鏈指向函數原型對象,當我們希望對某個子類實行一些函數特有的操作等,編譯器自然會通過原型鏈尋求目標。這就是原型鏈的精妙之處。

阮一峰老師的ES6教程的“extends的繼承目標”一節中,講解了三種特殊的繼承,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

剛才有說到構造函數里面有super(x,y),方法里面有super.toString(),也就是說super有兩種意義

1,父類的構造函數

然而這個super方法是在子類構造函數里面使用的,所以它應當返回一個子類的實例,所以super里面的this應該指向子類。super()在這里相當于A.prototype.constructor.call(this)。

super()只能用在子類的構造函數之中,用在其他地方會報錯。

2,與父類相關的對象

super作為對象時,在普通方法中,指向父類的原型對象;在靜態方法中,指向父類。

Decorator-修飾器

修飾器是一個對類進行處理的函數。修飾器函數的第一個參數,就是所要修飾的目標類。

例:

@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);

修飾器函數一共可以接受三個參數。第一個是類的原型對象,第二個是要修飾的參數,第三個是修飾參數的數據屬性對象

太累了,不想細說了,先寫到這

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容

  • class的基本用法 概述 JavaScript語言的傳統方法是通過構造函數,定義并生成新對象。下面是一個例子: ...
    呼呼哥閱讀 4,118評論 3 11
  • 面向對象的語言都有一個類的概念,通過類可以創建多個具有相同方法和屬性的對象,ES6之前并沒有類的概念,在ES6中引...
    Erric_Zhang閱讀 1,122評論 1 4
  • 本文先對es6發布之前javascript各種繼承實現方式進行深入的分析比較,然后再介紹es6中對類繼承的支持以及...
    lazydu閱讀 16,711評論 7 44
  • 細雨明月不兼得 涼意秋思亦可獲 酒過腸 遙思遠方 憶往昔 且向前方 前方會有故鄉 前方也亦有明月 ...
    彭澤西哦閱讀 169評論 0 1
  • 每一個失眠的夜晚,我都在回憶與你有關的那段青春歲月,然后告訴自己,縱然不再聯系,可是沒有失去聯系,就是最好的結局!...
    fly飛魚閱讀 687評論 0 2