摘要:原型會(huì)自動(dòng)調(diào)整,通過調(diào)用方法即可訪問基類的構(gòu)造函數(shù)。在簡單情況下,等于類的構(gòu)造函數(shù)的值是輸出這段代碼展示了當(dāng)調(diào)用時(shí)等于。
大多數(shù)面向?qū)ο缶幊陶Z言都支持類和類繼承的特性,而JavaScript只能通過各種特定方式模仿并關(guān)聯(lián)多個(gè)相似的對(duì)象。這個(gè)情況一直持續(xù)到ES5。由于類似的庫層出不窮,最終ES6引入了類特性,統(tǒng)一了類和類繼承的標(biāo)準(zhǔn)。
ES5模仿類先看一段ES5中模仿類的代碼:
function PersonType(name) { this.name = name; } PersonType.prototype.sayName = function(){ console.log(this.name); }; var person = new PersonType("Nicholas"); person.sayName(); console.log(person instanceof PersonType); // true console.log(person instanceof Object); // true
這段代碼中的PersonType是一個(gè)構(gòu)造函數(shù),執(zhí)行后創(chuàng)建一個(gè)名為name的屬性;給PersonType的原型添加一個(gè)sayName()方法,所以PersonType對(duì)象的所有實(shí)例共享這個(gè)方法。然后使用new操作符創(chuàng)建一個(gè)PersonType的實(shí)例person,并最終證實(shí)了person對(duì)象確實(shí)是PersonType的實(shí)例。
ES6的類ES6有一種與其他語言中類似的類特性:類聲明。
類聲明語法class PersonType { // 等價(jià)于PersonType構(gòu)造函數(shù) constructor(name) { this.name = name; } // 等價(jià)于PersonType.prototype.sayName sayName() { console.log(this.name); } } let person = new PersonType("Nicholas"); person.sayName(); console.log(person instanceof PersonType); // true console.log(person instanceof Object); // true console.log(typeof PersonType); // "function" console.log(typeof PersonType.prototype.sayName); // "function"
通過類聲明語法定義PersonType的行為與之前創(chuàng)建PersonType構(gòu)造函數(shù)的過程相似,只是這里直接通過特殊的constructor方法名來定義構(gòu)造函數(shù)。
訪問器屬性盡管應(yīng)該在類構(gòu)造函數(shù)中創(chuàng)建自己的屬性,但是類也支持直接在原型上定義訪問器屬性。創(chuàng)建getter時(shí),需要在關(guān)鍵字get后緊跟一個(gè)空格和相應(yīng)的標(biāo)識(shí)符;創(chuàng)建setter時(shí),只需把關(guān)鍵字get替換為set即可:
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
這段代碼中的CustomHTMLElement類是一個(gè)針對(duì)現(xiàn)有DOM元素的包裝器,并通過getter和setter方法將這個(gè)元素的innerHTML方法委托給html屬性,這個(gè)訪問器屬性是在CustomHTMLElement.prototype上創(chuàng)建的。
可計(jì)算成員名稱類和對(duì)象字面量還有更多相似之處,類方法和訪問器屬性也支持使用可計(jì)算名稱:
let methodName = "sayName"; class PersonType { constructor(name) { this.name = name; } [methodName]() { console.log(this.name); } } let me = new PersonType("Nicholas"); me.sayName();
通過相同的方式可以在訪問器屬性中應(yīng)用可計(jì)算名稱:
let propertyName = "html"; class CustomHTMLElement { constructor(element) { this.element = element; } get [propertyName]() { return this.element.innerHTML; } set [propertyName](value) { this.element.innerHTML = value; } }生成器方法
關(guān)于生成器和迭代器的知識(shí)點(diǎn),可以參考ES6系列---生成器和迭代器。
在對(duì)象字面量中,可以通過在方法名前附加一個(gè)星號(hào)(*)的方式來定義生成器,在類中亦是如此:
class MyClass { *createIterator() { yield 1; yield 2; yield 3; } } let instance = new MyClass(); let iterator = instance.createIterator();
如果用對(duì)象來表示集合,又希望通過簡單的方法迭代集合中的值,那么生成器方法就派上用場了。數(shù)組、Set集合及Map集合為開發(fā)者們提供了多個(gè)生成器方法來與集合中的元素交互。
盡管生成器方法很實(shí)用,但如果你的類是用來表示值的集合的,那么定義一個(gè)默認(rèn)迭代器會(huì)更有用。通過Symbol.iterator定義生成器方法即可為類定義默認(rèn)迭代器:
class Collection { constructor() { this.items = []; } *[Symbol.iterator]() { yield *this.items.values(); } } var collection = new Collection(); collection.items.push(1); collection.items.push(2); collection.items.push(3); for (let x of collection) { console.log(x); } // 輸出: // 1 // 2 // 3靜態(tài)成員
在ES5及其早期版本中,直接將方法添加到構(gòu)造函數(shù)中類模擬靜態(tài)成員是一種常見模式:
function PersonType(name) { this.name = name; } // 靜態(tài)方法 PersonType.create = function(name) { return new PersonType(name); }; // 實(shí)例方法 PersonType.prototype.sayName = function() { console.log(this.name); }; var person = PersonType.create("Nicholas");
ES6簡化了創(chuàng)建靜態(tài)成員的過程,在方法或訪問器屬性名前使用正式的靜態(tài)注釋即可:
class PersonType { // 等價(jià)于PersonType構(gòu)造函數(shù) constructor(name) { this.name = name; } // 等價(jià)于PersonType.prototype.sayName sayName() { console.log(this.name); } // 等價(jià)于PersonType.create static create(name) { return new PersonType(name); } } let person = PersonType.create("Nicholas");
靜態(tài)成員或方法,不可在實(shí)例中訪問,必須要直接在類上訪問。
繼承與派生類在ES6之前,實(shí)現(xiàn)繼承與自定義類型是個(gè)不小的工作:
ES5中實(shí)現(xiàn)繼承function Rectangle(length, width) { this.length = length; this.width = width; } Rectangle.prototype.getArea = function() { return this.length * this.width; }; function Square(length) { Rectangle.call(this, length, length); } Square.prototype = Object.create(Rectangle.prototype, { constructor: { value: Square, enumerable: true, writable: true, configurable: true } }); var square = new Square(3); console.log(square.getArea()); // 9 console.log(square instanceof Square); // true console.log(square instanceof Rectangle); // true
Square繼承自Rectangle,為了這樣做,必須用一個(gè)創(chuàng)建自Rectangle.prototype的新對(duì)象重寫Square.prototype并調(diào)用Rectangle.call()方法。
ES6中實(shí)現(xiàn)繼承類的出現(xiàn)讓我們可以輕松地實(shí)現(xiàn)繼承功能,使用熟悉的extends關(guān)鍵字。原型會(huì)自動(dòng)調(diào)整,通過調(diào)用super()方法即可訪問基類的構(gòu)造函數(shù)。下面是之前示例的ES6等價(jià)版:
class Rectangle { constructor(length, width) { this.length = length; this.width = width; } getArea() { return this.length * this.width; } } class Square extends Rectangle { constructor(length) { // 等價(jià)于Rectangle.call(this, length, length) super(length, length); } } var square = new Square(3); console.log(square.getArea()); // 9 console.log(square instanceof Square); // true
這一次,Square類通過extends關(guān)鍵字繼承Rectangle類,在Square構(gòu)造函數(shù)中通過super()調(diào)用Rectangle構(gòu)造函數(shù)并傳入相應(yīng)參數(shù)。
類方法重寫派生類中的方法總會(huì)覆蓋基類中的同名方法:
class Square extends Rectangle { constructor(length) { super(length, length); } // 重寫Rectangle.prototype.getArea()方法 getArea() { return this.length * this.length; } }
由于為Square定義了getArea()方法,便不能在Square實(shí)例中調(diào)用Rectangle.prototype.getArea()方法。當(dāng)然,如果你想調(diào)用基類中的方法,則可以調(diào)用super.getArea()方法,就像這樣:
class Square extends Rectangle { constructor(length) { super(length, length); } // 重寫后調(diào)用Rectangle.prototype.getArea() getArea() { return super.getArea(); } }靜態(tài)成員繼承
如果基類有靜態(tài)成員,那么這些靜態(tài)成員在派生類中也可用:
class Rectangle { constructor(length, width) { this.length = length; this.width = width; } getArea() { return this.length * this.width; } static create(length, width) { return new Rectangle(length, width); } } class Square extends Rectangle { constructor(length) { // 等價(jià)于Rectangle.call(this, length, length) super(length, length); } } var rect = Square.create(3, 4); console.log(rect instanceof Rectangle); // true console.log(rect.getArea()); // 12 console.log(rect instanceof Square); // false
在這段代碼中,新的靜態(tài)方法create()被添加到Rectangle類中,繼承后的Square.create()與Rectangle.create()行為一致。
派生自表達(dá)式的類ES6最強(qiáng)大的一面或許是從表達(dá)式導(dǎo)出類的功能了。只要表達(dá)式可以被解析為一個(gè)函數(shù)并且具有[[Construct]]屬性和原型,那么就可以用extends進(jìn)行派生:
function Rectangle(length, width) { this.length = length; this.width = width; } Rectangle.prototype.getArea = function() { return this.length * this.width; }; class Square extends Rectangle { constructor(length) { super(length, length); } } var x = new Square(3); console.log(x.getArea()); // 9 console.log(x instanceof Rectangle); // true
Rectangle是一個(gè)ES5風(fēng)格的構(gòu)造函數(shù),Square是一個(gè)類,由于Rectangle具有[[Construct]]屬性和原型,因此Square類可以直接繼承它。
extends強(qiáng)大的功能使得類可以繼承自任意類型的表達(dá)式,從而創(chuàng)造更多可能性,例如動(dòng)態(tài)地確定類的繼承目標(biāo):
function Rectangle(length, width) { this.length = length; this.width = width; } Rectangle.prototype.getArea = function() { return this.length * this.width; }; function getBase() { return Rectangle; } class Squre extends getBase() { constructor(length) { super(length, length); } } var x = new Square(3); console.log(x.getArea()); // 9 console.log(x instanceof Rectangle); // true
getBase()函數(shù)是類聲明的一部分,直接調(diào)用后返回Rectangle,此示例實(shí)現(xiàn)的功能與之前的示例等價(jià)。由于可以動(dòng)態(tài)確定使用哪個(gè)基類,因而可以創(chuàng)建不同的繼承方法。例如,可以這樣創(chuàng)建mixin:
let SerializableMixin = { serialize() { return JSON.stringify(this); } }; let AreaMixin = { getArea() { return this.length * this.width; } }; function mixin(...mixins) { var base = function() {}; Object.assign(base.prototype, ...mixins); return base; } class Square extends mixin(AreaMixin, SerializableMixin) { constructor(length) { super(); this.length = length; this.width = length; } } var x = new Square(3); console.log(x.getArea()); // 9 console.log(x.serialize()); // "{"length":3, "width":3}"
這個(gè)示例使用了mixin函數(shù)代替?zhèn)鹘y(tǒng)的繼承方法,它可以接受任意數(shù)量的mixin對(duì)象作為參數(shù)。首先創(chuàng)建一個(gè)函數(shù)base,再將每一個(gè)mixin對(duì)象的屬性值賦值給base的原型,最后mixin函數(shù)返回這個(gè)base函數(shù),所以Square類就可以基于這個(gè)返回的函數(shù)用extends進(jìn)行擴(kuò)展。
Square的實(shí)例擁有來自AreaMixin對(duì)象的getArea()方法和來自SerializableMixin對(duì)象的serialize方法,這都是通過原型繼承實(shí)現(xiàn)的,mixin()函數(shù)會(huì)用所有mixin對(duì)象的自有屬性動(dòng)態(tài)填充新函數(shù)的原型。
在類的構(gòu)造函數(shù)中也可以通過new.target來確定類是如何被調(diào)用。在簡單情況下,new.target等于類的構(gòu)造函數(shù):
class Rectangle { constructor(length, width) { console.log(new.target === Rectangle); this.length = length; this.width = width; } } // new.target的值是Rectangle var obj = new Rectangle(3, 4); // 輸出true
這段代碼展示了當(dāng)調(diào)用new Rectangle(3, 4)時(shí)new.target等于Rectangle。
繼承情況下,有所不同:
class Rectangle { constructor(length, width) { console.log(new.target === Rectangle); this.length = length; this.width = width; } } class Square extends Rectangle { constructor(length) { super(length, length); } } // new.target的值是Square var obj = new Square(3); // 輸出false
Square調(diào)用Rectangle的構(gòu)造函數(shù),所以當(dāng)調(diào)用發(fā)生時(shí)new.target等于Square。據(jù)此,我們可以創(chuàng)建一個(gè)抽象基類(不能被實(shí)例化的類),就像這樣:
// 抽象基類 class Shape { constructor() { if (new.target === Shape) { throw new Error("這個(gè)類不能被直接實(shí)例化。"); } } } class Rectangle extends Shape { constructor(length, width) { super(); this.length = length; this.width = width; } } var x = new Shape(); // 拋出錯(cuò)誤 var y = new Rectangle(3, 4); // 沒有錯(cuò)誤 console.log(y instanceof Shape); // true
在這個(gè)示例中,每當(dāng)new.target是Shape時(shí)構(gòu)造函數(shù)總會(huì)拋出錯(cuò)誤,這相當(dāng)于調(diào)用new Shape()時(shí)總會(huì)出錯(cuò)。但是,仍可用Shape作為基類派生其他類。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/91873.html
摘要:前言在了解是如何編譯前,我們先看看的和的構(gòu)造函數(shù)是如何對(duì)應(yīng)的。這是它跟普通構(gòu)造函數(shù)的一個(gè)主要區(qū)別,后者不用也可以執(zhí)行。該函數(shù)的作用就是將函數(shù)數(shù)組中的方法添加到構(gòu)造函數(shù)或者構(gòu)造函數(shù)的原型中,最后返回這個(gè)構(gòu)造函數(shù)。 前言 在了解 Babel 是如何編譯 class 前,我們先看看 ES6 的 class 和 ES5 的構(gòu)造函數(shù)是如何對(duì)應(yīng)的。畢竟,ES6 的 class 可以看作一個(gè)語法糖,...
摘要:其實(shí)的面向?qū)ο蠛芏嘣砗蜋C(jī)制還是的,只不過把語法改成類似和老牌后端語言中的面向?qū)ο笳Z法一用封裝一個(gè)基本的類是不是很向和中的類其實(shí)本質(zhì)還是原型鏈,我們往下看就知道了首先說下語法規(guī)則中的就是類名,可以自定義就是構(gòu)造函數(shù),這個(gè)是關(guān)鍵字,當(dāng)實(shí)例化對(duì) 其實(shí)es6的面向?qū)ο蠛芏嘣砗蜋C(jī)制還是ES5的,只不過把語法改成類似php和java老牌后端語言中的面向?qū)ο笳Z法. 一、用es6封裝一個(gè)基本的類 ...
摘要:并且用驗(yàn)證了中一系列的實(shí)質(zhì)就是魔法糖的本質(zhì)。抽絲剝繭我們首先看的編譯結(jié)果這是一個(gè)自執(zhí)行函數(shù),它接受一個(gè)參數(shù)就是他要繼承的父類,返回一個(gè)構(gòu)造函數(shù)。 如果你已經(jīng)看過第一篇揭秘babel的魔法之class魔法處理,這篇將會(huì)是一個(gè)延伸;如果你還沒看過,并且也不想現(xiàn)在就去讀一下,單獨(dú)看這篇也沒有關(guān)系,并不存在理解上的障礙。 上一篇針對(duì)Babel對(duì)ES6里面基礎(chǔ)class的編譯進(jìn)行了分析。這一篇將...
摘要:前言在閱讀入門的時(shí)候,零散的看到有私有變量的實(shí)現(xiàn),所以在此總結(jié)一篇。構(gòu)造函數(shù)應(yīng)該只做對(duì)象初始化的事情,現(xiàn)在為了實(shí)現(xiàn)私有變量,必須包含部分方法的實(shí)現(xiàn),代碼組織上略不清晰。 前言 在閱讀 《ECMAScript 6 入門》的時(shí)候,零散的看到有私有變量的實(shí)現(xiàn),所以在此總結(jié)一篇。 1. 約定 實(shí)現(xiàn) class Example { constructor() { this...
閱讀 2570·2021-09-30 10:00
閱讀 3505·2021-09-22 10:54
閱讀 6274·2021-09-07 10:28
閱讀 2957·2019-08-29 13:53
閱讀 753·2019-08-29 12:42
閱讀 968·2019-08-26 13:51
閱讀 1266·2019-08-26 13:32
閱讀 3029·2019-08-26 10:39