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