第9章 Classes

Classes

我們知道javascript是沒有類的概念的细卧, ES6新添加了 class 關(guān)鍵詞茴扁,為書寫類提供了很大的便利,下面我們通過ES5和ES6對比來看一下ES6這種新的語法糖石洗。

  1. class 與 function ;
  2. class 與 對象字面量 ;
  3. 靜態(tài)方法 "static" 關(guān)鍵詞 ;
  4. 繼承 "extends", "super" ;
  5. 抽象類 new.target

一. class和函數(shù)的關(guān)系

1.ES5類的表達方式 和 ES6 class

ES5通過函數(shù)構(gòu)造器和原型的方法來模擬類的概念

function Person(name, age) {
    this.name = name;
    this.age = age;
}
// 將方法寫在原型鏈上的原因是:函數(shù)是對象
// 如果每次實例化一個對象都創(chuàng)建一個相同的函數(shù)缀雳,這樣會占用很多內(nèi)存
Person.prototype.sayName = function() {
    console.log(this.name);
}
// 實例化一個對象
var person = new Person("james", 26);
person.sayName(); // "james"
console.log(person instanceof Person); // true 

ES6 通過 class關(guān)鍵詞羞延, 還有constructor()方法渣淳, 將實例屬性寫在構(gòu)造器函數(shù)中, 原型上的屬性使用簡寫語法伴箩,上面的例子相當(dāng)于:

class Person {
    // 構(gòu)造器
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    // 簡寫語法入愧,原型鏈上的屬性
    sayName() {
        console.log(this.name);
    }
}
// 實例化 new操作符
var person = new Person("kobe", 37);
person.sayName(); // "kobe"
// 注意下面Person的實質(zhì)類型
console.log(typeof Person); // "function"
console.log(typeof Person.prototype.sayName); // "function"

2.class語法和自定義函數(shù)表示類的區(qū)別

  1. class declarations 不提升(hoisting),就像用let聲明一樣,函數(shù)聲明會自動提升;
  2. 在class declarations中嗤谚,所有的代碼自動在嚴格模式下;
  3. class中所有的方法都是不可枚舉的(nonenumerable).函數(shù)聲明要想方法不可枚舉棺蛛,需要使用Object.defineProperty方法;
  4. class中的方法內(nèi)部都不存在[[Construct]]屬性,因此不能使用new;
  5. 調(diào)用class構(gòu)造器必須使用new,否則拋出異常;
  6. 嘗試用class中的方法改寫 class name會拋出錯誤巩步,在外部改寫鞠值,則像使用let聲明之后重新賦值一樣,不會拋出錯誤;

class語法相當(dāng)于下面代碼:

// 使用let聲明渗钉,避免變量提升
let PersonType = (function() {
    "use strict";
        
    // 使用const, 內(nèi)部不能改變class 名稱
     const PersonType = function(name) {
         // 確保構(gòu)造器使用new
        if (typeof new.target === "undefined") {
            throw new Error("Constructor must be called with new");
        }
        this.name = name;
    }
    // 方法不可枚舉
    Object.defineProperty(PersonType.prototype, "sayName", {
        value: function() { 
            // 確保方法不使用new操作符
            if (typeof new.target !== "undefined") {
                console.log(this.name)
            }     
        },
        enumerable: false,
        writable: true,
        configurable: true
    });

    return PersonType;    
}());

3.Class Expressions

class 和 function 的相似之處都有兩種形式: 聲明式 和 表達式彤恶, class表達式一般用作變量聲明或者當(dāng)作參數(shù)傳入函數(shù)中。
表達式形式:

let Person = class {
    constructor(name) {
        this.name = name;
    }
    sayName() {
        console.log(this.name);
    }
};

功能上與class聲明形式一樣鳄橘,就是"name"屬性不一樣(因為class本質(zhì)上是function声离,ES6函數(shù)新添加了name 屬性)。上面的匿名class表達式 Person.name 為空字符串""

4.Named Class expressions

這個和Named Function expressions一樣瘫怜,就是在class后面添加一個標(biāo)識符术徊, 該標(biāo)識符只能在class定義內(nèi)部使用

let Person = class PersonType {
    constructor(name) {
        this.name = name;
    }
    sayName() {
        console.log(this.name);
    }
}

console.log(typeof Person); // "function"
console.log(typeof PersonType); // undefined PersonType只能在class內(nèi)部使用

相當(dāng)于:

   let Person = (function() {
    "use strict";
        
    // 使用const, 內(nèi)部不能改變class 名稱 此處和 Person不一樣
     const PersonType = function(name) {
         // 確保構(gòu)造器使用new
        if (typeof new.target === "undefined") {
            throw new Error("Constructor must be called with new");
        }
        this.name = name;
    }
    // 方法不可枚舉
    Object.defineProperty(PersonType.prototype, "sayName", {
        value: function() { 
            // 確保方法不適用new操作符
            if (typeof new.target !== "undefined") {
                console.log(this.name)
            }     
        },
        enumerable: false,
        writable: true,
        configurable: true
    });

    return PersonType;    
}());

二.class和對象字面量關(guān)系

1.訪問器get,set屬性

class能夠在構(gòu)造器中定義實例屬性,也可以在原型上定義訪問器屬性(accessor properties), 定義方法和在對象字面量中的方式一致:

class CustomHTMLElement {
    constructor(element) {
        this.element = element;
    }
    get html() {
        return this.element.innerHTML;
    }
    set html(value) {
        this.element.innerHTML = value;
    }
}

var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, "html");
console.log("get" in descriptor); // true
console.log("set" in descriptor); // true
console.log(descriptor.enumerable); // false

熟悉jquery的應(yīng)該知道html()方法鲸湃,原理和這個差不多赠涮,相當(dāng)于:

let CustomHTMLElement = (function() {
    function CustomHTMLElement(element) {
        // 確保使用new調(diào)用構(gòu)造器
        if (typeof new.target === "undefined") {
            throw new Error("Constructor must be called with new");
        }
        this.element = element;
    }
    Object.defineProperty(CustomHTMLElement.prototype, "html", {
        enumerable: false,
        configurable: true,
        get: function() {
            return this.element.innerHTML;
        },
        set: function(value) {
            this.element.innerHTML = value;
        }
    });
    return CustomHTMLElement;
}());

2.Computed Member Names

對象字面量中有個計算變量屬性,ES6新添加的語法暗挑, 同樣class 方法和訪問器 也可以采用類似的計算命名

let methodName = "sayName";
class Person {
    constructor(name) {
        this.name = name;
    }
    [methodName]() {
        console.log(this.name);
    }
}

訪問器:

let propertyName = "html";
class CustomHTMLElement {
    constructor(element) {
        this.element = element;
    }
    get [propertyName]() {
        return this.element.innerHTML;
    }
    set [propertyName](value) {
        this.element.innerHTML = value;
    }    
}

3.產(chǎn)生器方法(generator method)

在方法名前添加 "*"笋除,可以將任意方法變?yōu)橐粋€產(chǎn)生器丁侄, Generator method對對象是集合類型值硅堆,進行迭代十分的方便议谷, 定義一個默認的迭代器
可以通過 Symbol.iterator 來定義一個默認產(chǎn)生器方法

class Collection {
    constructor() {
        // 屬性為集合類型
        this.items = [];
    }
    // 定義一個默認的產(chǎn)生器方法, 使用computed name
    *[Symbol.iterator]() {
        // 利用數(shù)組默認的迭代器values()
        yield *this.items.values();
    }
}

var c = new Collection();
c.items.push(1);
c.items.push(2);
c.items.push(3);
for (let n of c) {
    console.log(n);
}
// 1
// 2
// 3

// 使用spread操作符
var arr = [...c];
arr; // [1, 2, 3]

4.first-citizen

class的本質(zhì)是function,意味著可以當(dāng)作對象一樣當(dāng)作參數(shù)或者作為返回值裙盾。

1.當(dāng)作參數(shù)

function createObj(classDef) {
    return new classDef();
}
let obj = createObj(class {
    sayHi() {
        console.log("hi");
    }
});
obj.sayHi(); // "hi"

2.創(chuàng)建單利(singletons),利用IIEF

let person = new class {
    constructor(name) {
        this.name = name;
    }

    sayName() {
        console.log(this.name);
    }
}("Nicholas");

person.sayName(); // "Nicholas"

三.靜態(tài)方法 static

ES6之前可以通過直接添加方法到構(gòu)造器來模擬靜態(tài)方法(區(qū)別于實例方法)

function PersonType(name) {
    this.name = name;
}

// 模擬靜態(tài)方法 static method
PersonType.create = function(name) {
    // 調(diào)用構(gòu)造器    
    return new PersonType(name);
}

// instance method
PersonType.prototype.sayName = function() {
    console.log(this.name);
}

// 直接對類PersonType調(diào)用方法
var person = PersonType.create("Mike"); 

ES6 引入 static 關(guān)鍵詞

class PersonType {
    constructor(name) {
        this.name = name;
    }
    // instance method
    sayName() {
        console.log(this.name);
    }

    // static method
    static create(name) {
        return new PersonType(name);
    }
}

我們可以對任何方法添加 static 關(guān)鍵詞 使之變?yōu)殪o態(tài)方法往史,唯一不能對constructor構(gòu)造器添加static咨跌; 另外考廉,靜態(tài)成員只能直接通過class,而不能使用實例去訪問


四.繼承

ES6之前,實現(xiàn)繼承是比較繁瑣的步驟上忍,我們需要使用構(gòu)造器竊取惯殊,將子類指向一個父類的實例(或者使用Object.create()方法)酱吝,下面我們來看一下

1.ES6之前模擬類繼承

function Rectangle(width, height) {
    this.width = width;
    this.height = height;
}
Rectangle.prototype.getArea = function() {
    return this.width * this.height;
}

function Square(width) {
    // 構(gòu)造器竊取
    Rectangle.call(this, width, width);
}
// 實現(xiàn)繼承
Square.prototype = Object.create(Rectangle.prototype, {
    constructor: {
        // 將原型重新指向自身
        value: Square,
        enumerable: true,
        wriable: true,
        configurable: true
    }
});
var square = new Square(5);
square.getArea(); // 25
square instanceof Square; // true
square instanceof Rectangle; // true

2.ES6通過 super() 和 extends 實現(xiàn)繼承

class Rectangle {
    constructor(width, height) {
        this.width = width;
        this.height = height;
    }

    getArea() {
        return this.width * this.height;
    }
}

class Square extends Rectangle {
    constructor(width) {
        // 使用super()
        super(width, width);
    }
}

var square = new Square(5);
square.getArea(); // 25
square instanceof Square; // true
square instanceof Rectangle; // true

可以看出這種寫法十分的簡潔,同時也十分的清晰

使用super()注意事項:

  1. 只能在派生類(即子類)構(gòu)造器中使用super(),在非派生類中或者一個function中使用super,會拋出異常;
  2. 在訪問this之前必須先調(diào)用super(),因為super()是負責(zé)實例化this的;
  3. 唯一能避免調(diào)用super()的方式是從一個對象構(gòu)造器中返回一個對象

3.子類override父類方法

學(xué)過其他編程語言的肯定知道override土思,可以用來重寫父類中的屬性务热,ES6也提供了這樣的選擇毕源,可以直接重寫,也可以使用super.property方式

class Square extends Rectangle {
    constructor(width) {
        super(width, width);
    }

    // 重寫或者shadow父類原型上的方法getArea
    getArea() {
        return this.width * this.width;
    }
} 

或使用super

class Square extends Rectangle {
    constructor(width) {
        super(width, width);
    }
    // override or shadow 并調(diào)用父類原型上的方法
    getArea() {
        return super.getArea();
    }
}

4.繼承父類靜態(tài)屬性

父類上的靜態(tài)屬性同樣可以被繼承陕习,這是ES6新概念

 class Rectangle {
    constructor(width, height) {
        this.width = width;
        this.height = height;
    }

    getArea() {
        return this.width * this.height;
    }
    // 靜態(tài)方法
    static create(width, height) {
        return new Rectangle(width, height);
    } 
}

class Square extends Rectangle {
    constructor(width) {
        super(width, width);
    }
}

// 子類調(diào)用父類靜態(tài)方法
var rect = Square.create(3, 4);
console.log(rect instanceof Rectangle); // true
rect.getArea(); // 12
console.log(rect instanceof Square); // false 注意霎褐!

5.派生類繼承自表達式

1.當(dāng)一個表達式擁有[[Construct]]屬性 和 原型 就能夠被其他類繼承,這種方式給動態(tài)的繼承提供了很大的便利该镣,后面會談到
比如:

// 構(gòu)造器冻璃, 擁有[[Construct]]屬性
function Rectangel(width, height) {
    this.width = width;
    this.height = height;
}
// 擁有原型
Rectangle.prototype.getArea = function() {
    return this.widht * this,height;
}

// 實現(xiàn)繼承
class Square extends Rectangle {
    constructor(width) {
        super(width, width);
    }
}
var x = new Square(4);
x.getArea(); // 16
x instanceof Rectangle; // true

2.動態(tài)的決定繼承自誰

// 構(gòu)造器, 擁有[[Construct]]屬性
function Rectangel(width, height) {
    this.width = width;
    this.height = height;
}
// 擁有原型
Rectangle.prototype.getArea = function() {
    return this.widht * this,height;
}

// 利用這個函數(shù)损合,返回函數(shù)對象Rectangle    
function getBase() {
    return Rectangle;
}
// 動態(tài)實現(xiàn)繼承, 調(diào)用getBase()
class Square extends getBase() {
    constructor(width) {
        super(width, width);
    }
}
var x = new Square(4);
x.getArea(); // 16
x instanceof Rectangle; // true

3.上面的方法可以用來創(chuàng)建mixins, 將多個屬性添加到同一個類中省艳,然后子類繼承該mixins

let serializableMixin = {
    serialize() {
        return JSON.stringify(this);
    }
};

let AreaMixin = {
    getArea() {
        return this.width * this.height;
    }
};

// 創(chuàng)建mixins,利用Object.assign()
function Mixins(...mixins) {
    // 創(chuàng)建一個空的base函數(shù)
    var base = function() {};
    Object.assign(base.prototype, ...mixins);
    return base;
}
// 繼承Mixins
class Square extends Mixins(serializableMixin, AreaMixin) {
    constructor(width) {
        super(width, width);
        // 兩個實例屬性
        this.width = width;
        this.height = width;
    }
}    
var s = new Square(3);
s.getArea(); // 9
s.serilize(); // {"width": 3, "height: 3}

4.繼承內(nèi)置類(built-in)
ES6之前對繼承內(nèi)置類會出現(xiàn)問題嫁审,但是ES6允許繼承內(nèi)置類
比如繼承Array類跋炕,則擁有Array上的屬性:

class MyArray extends Array {
    // empty
}
var myArray = new MyArray();
myArray[0] = "red";
myArray.length; // 1

5.Symbol.species

Symbol.species: 一個靜態(tài)訪問器屬性,用于返回一個function,這個function是一個構(gòu)造器律适,當(dāng)實例必須使用內(nèi)部方法創(chuàng)建時辐烂,下列內(nèi)置類型擁有 Symbol.species 屬性:

  1. Array
  2. ArrayBuffer
  3. Map
  4. Promise
  5. RegExp
  6. Set
  7. TypeArray

eg:

class MyClass {
    static get [Symbol.species]() {
        return this;
    }

    constructor(value) {
        this.value = value;
    }

    // 使用內(nèi)置方法創(chuàng)建實例, 允許派生類改寫這個值
    clone() {
        return new this.constructor[Symbol.species](this.value);
    }
}

// 派生類
class DerivedClass1 extends MyClass {
    // empty
}
class DerivedClass2 extends MyClass {
    // 改寫
    static get [Symbol.species]() {
        return MyClass;
    }
}
let instance1 = new DerivedClass1("foo"),
    clone1 = instance1.clone();
let instance2 = new DerivedClass2("bar"),
    clone2 = instance2.clone();

instance1 intanceof MyClass; // true
clone1 instanceof DerivedClass1; // true
instance2 instanceof MyClass; // true
clone2 instanceof DerivedClass2; // false

比如上面的例子:

class MyArray extends Array {
      static get [Symbol.species]() {
        return Array;
      }
}
var arr = new MyArray(1,2,3,4)捂贿;
var sub = arr.slice(1, 3)纠修;
sub instanceof Array; // true
sub instanceof MyArray; // false

五.創(chuàng)建抽象類

抽象類的本質(zhì)就是當(dāng)父類不能實例化厂僧,當(dāng)父類使用new操作符時拋出異常扣草,這我們可以相當(dāng)使用new.target來控制

class Sharp {
    contructor(width, height) {
        // 如果new 的目標(biāo)是 類 本身 則拋出異常
        if (new.target === Sharp) {
            throw new Error("Abstract class cannot be instantiated");
        }

    }
}

class Rectangle extends Sharp {
    contructor(width, height) {
        super();
        this.width = width;
        this.height = height;
    }
}

var sharp = new Sharp(); // ERROR
var rec = new Rectangel(10, 5);
rec instanceof Sharp; // true

總結(jié)

class的出現(xiàn)為類的創(chuàng)建提供了極大的便利,這種語法糖其本質(zhì)還是function颜屠,擁有函數(shù)該有的特性辰妙,比如幾種表達方式,作為first-citizen甫窟,能夠作為參數(shù)或者返回值密浑;另外書寫方面和對象字面量又十分的相識,提供各種簡寫蕴坪。另外最重要的是對實現(xiàn)繼承是非常的友好肴掷,同時mixins,對擴展類的功能提供了很大方便敬锐,還有抽象類的出現(xiàn)背传,這使得javascript更加的OOP了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末台夺,一起剝皮案震驚了整個濱河市径玖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌颤介,老刑警劉巖梳星,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赞赖,死亡現(xiàn)場離奇詭異,居然都是意外死亡冤灾,警方通過查閱死者的電腦和手機前域,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來韵吨,“玉大人匿垄,你說我怎么就攤上這事」榉郏” “怎么了椿疗?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長糠悼。 經(jīng)常有香客問我届榄,道長,這世上最難降的妖魔是什么倔喂? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任铝条,我火速辦了婚禮,結(jié)果婚禮上席噩,老公的妹妹穿的比我還像新娘攻晒。我一直安慰自己,他們只是感情好班挖,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布鲁捏。 她就那樣靜靜地躺著,像睡著了一般萧芙。 火紅的嫁衣襯著肌膚如雪给梅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天双揪,我揣著相機與錄音动羽,去河邊找鬼。 笑死渔期,一個胖子當(dāng)著我的面吹牛运吓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疯趟,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼拘哨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了信峻?” 一聲冷哼從身側(cè)響起倦青,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎盹舞,沒想到半個月后产镐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體隘庄,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年癣亚,在試婚紗的時候發(fā)現(xiàn)自己被綠了丑掺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡述雾,死狀恐怖吼鱼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绰咽,我是刑警寧澤菇肃,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站取募,受9級特大地震影響琐谤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜玩敏,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一斗忌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧旺聚,春花似錦织阳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至碱璃,卻和暖如春弄痹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嵌器。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工肛真, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人爽航。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓蚓让,卻偏偏與公主長得像,于是被迫代替她去往敵國和親讥珍。 傳聞我的和親對象是個殘疾皇子历极,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)串述,斷路器执解,智...
    卡卡羅2017閱讀 134,665評論 18 139
  • class的基本用法 概述 JavaScript語言的傳統(tǒng)方法是通過構(gòu)造函數(shù),定義并生成新對象纲酗。下面是一個例子: ...
    呼呼哥閱讀 4,096評論 3 11
  • "Unterminated string literal.": "未終止的字符串文本衰腌。", "Identifier...
    兩個心閱讀 8,371評論 0 4
  • { "Unterminated string literal.": "未終止的字符串文本。", "Identifi...
    一粒沙隨風(fēng)飄搖閱讀 10,573評論 0 3
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持觅赊,譯者再次奉上一點點福利:阿里云產(chǎn)品券右蕊,享受所有官網(wǎng)優(yōu)惠,并抽取幸運大...
    HetfieldJoe閱讀 3,658評論 2 27