小編給大家分享一下ES6中類的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
東城ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!ES5近似結(jié)構(gòu)
在ES5中沒有類的概念,最相近的思路是創(chuàng)建一個自定義類型:首先創(chuàng)建一個構(gòu)造函數(shù),然后定義另一個方法并賦值給構(gòu)造函數(shù)的原型
function PersonType(name) { this.name = name; } PersonType.prototype.sayName = function() { console.log(this.name); }; let person = new PersonType("huochai"); person.sayName(); // 輸出 "huochai" console.log(person instanceof PersonType); // true console.log(person instanceof Object); // true
這段代碼中的personType是一個構(gòu)造函數(shù),其執(zhí)行后創(chuàng)建一個名為name的屬性給personType的原型添加一個sayName()方法,所以PersonType對象的所有實例都將共享這個方法。然后使用new操作符創(chuàng)建一個personType的實例person,并最終證實了person對象確實是personType的實例,且由于存在原型繼承的特性,因而它也是object的實例
許多模擬類的JS庫都是基于這個模式進(jìn)行開發(fā),而且ES6中的類也借鑒了類似的方法
類的聲明
ES6有一種與其他語言中類似的類特性:類聲明。同時,它也是ES6中最簡單的類形式
【基本的類聲明語法】
要聲明一個類,首先編寫class關(guān)鍵字,緊跟著的是類的名字,其他部分的語法類似于對象字面量方法的簡寫形式,但不需要在類的各元素之間使用逗號分隔
class PersonClass { // 等價于 PersonType 構(gòu)造器 constructor(name) { this.name = name; } // 等價于 PersonType.prototype.sayName sayName() { console.log(this.name); } } let person = new PersonClass("huochai"); person.sayName(); // 輸出 "huochai" console.log(person instanceof PersonClass); // true console.log(person instanceof Object); // true console.log(typeof PersonClass); // "function" console.log(typeof PersonClass.prototype.sayName); // "function"
通過類聲明語法定義PersonClass的行為與之前創(chuàng)建PersonType構(gòu)造函數(shù)的過程相似,只是這里直接在類中通過特殊的constructor方法名來定義構(gòu)造函數(shù),且由于這種類使用簡潔語法來定義方法,因而不需要添加function關(guān)鍵字。除constructor外沒有其他保留的方法名,所以可以盡情添加方法
私有屬性是實例中的屬性,不會出現(xiàn)在原型上,且只能在類的構(gòu)造函數(shù)或方法中創(chuàng)建,此例中的name就是一個私有屬性。建議在構(gòu)造函數(shù)中創(chuàng)建所有私有屬性,從而只通過一處就可以控制類中的所有私有屬性
類聲明僅僅是基于已有自定義類型聲明的語法糖。typeofPersonClass最終返回的結(jié)果是"function",所以PersonClass聲明實際上創(chuàng)建了一個具有構(gòu)造函數(shù)方法行為的函數(shù)。此示例中的sayName()方法實際上是PersonClass.prototype上的一個方法;與之類似的是,在之前的示例中,sayName()也是personType.prototype上的一個方法。通過語法糖包裝以后,類就可以代替自定義類型的功能,不必?fù)?dān)心使用的是哪種方法,只需關(guān)注如何定義正確的類
[注意]與函數(shù)不同的是,類屬性不可被賦予新值,在之前的示例中,PersonClass.prototype就是這樣一個只可讀的類屬性
【為何使用類語法】
盡管類與自定義類型之間有諸多相似之處,但是它們之間仍然有一些差異
1、函數(shù)聲明可以被提升,而類聲明與let聲明類似,不能被提升真正執(zhí)行聲明語句之前,它們會一直存在于臨時死區(qū)中
2、類聲明中的所有代碼將自動運(yùn)行在嚴(yán)格模式下,而且無法強(qiáng)行讓代碼脫離嚴(yán)格模式執(zhí)行
3、在自定義類型中,需要通過Object.defineProperty()方法手工指定某個方法為不可枚舉;而在類中,所有方法都是不可枚舉的
4、每個類都有一個名為[[Construct]]的內(nèi)部方法,通過關(guān)鍵字new調(diào)用那些不含[[Construct]]的方法會導(dǎo)致程序拋出錯誤
5、使用除關(guān)鍵字new以外的方式調(diào)用類的構(gòu)造函數(shù)會導(dǎo)致程序拋出錯誤
6、在類中修改類名會導(dǎo)致程序報錯
了解了這些差異之后,可以用除了類之外的語法為之前示例中的PersonClass聲明編寫等價代碼
// 直接等價于 PersonClass let PersonType2 = (function() { "use strict"; const PersonType2 = function(name) { // 確認(rèn)函數(shù)被調(diào)用時使用了 new if (typeof new.target === "undefined") { throw new Error("Constructor must be called with new."); } this.name = name; } Object.defineProperty(PersonType2.prototype, "sayName", { value: function() { // 確認(rèn)函數(shù)被調(diào)用時沒有使用 new if (typeof new.target !== "undefined") { throw new Error("Method cannot be called with new."); } console.log(this.name); }, enumerable: false, writable: true, configurable: true }); return PersonType2; }());
這段代碼中有兩處personType2聲明:一處是外部作用域中的let聲明,一處是立即執(zhí)行函數(shù)表達(dá)式(IIFE)中的const聲明,這也從側(cè)面說明了為什么可以在外部修改類名而內(nèi)部卻不可修改。在構(gòu)造函數(shù)中,先檢查new.target是否通過new調(diào)用,如果不是則拋出錯誤;緊接著,將sayName()方法定義為不可枚舉,并再次檢查new.target是否通過new調(diào)用,如果是則拋出錯誤;最后,返回這個構(gòu)造函數(shù)
盡管可以在不使用new語法的前提下實現(xiàn)類的所有功能,但如此一來,代碼變得極為復(fù)雜
【常量類名】
類的名稱只在類中為常童,所以盡管不能在類的方法中修改類名,但可以在外部修改
class Foo { constructor() { Foo = "bar"; // 執(zhí)行時拋出錯誤 } } // 但在類聲明之后沒問題 Foo = "baz";
以上代碼中,類的外部有一個Foo聲明,而類構(gòu)造函數(shù)里的Foo則是一個獨立存在的綁定。內(nèi)部的Foo就像是通過const聲明的,修改它的值會導(dǎo)致程序拋出錯誤;而外部的Foo就像是通過let聲明的,可以隨時修改這個綁定值
類表達(dá)式
類和函數(shù)都有兩種存在形式:聲明形式和表達(dá)式形式。聲明形式的函數(shù)和類都由相應(yīng)的關(guān)鍵字(分別為function和class)進(jìn)行定義,隨后緊跟一個標(biāo)識符;表達(dá)式形式的函數(shù)和類與之類似,只是不需要在關(guān)鍵字后添加標(biāo)識符
類表達(dá)式的設(shè)計初衷是為了聲明相應(yīng)變量或傳入函數(shù)作為參數(shù)
【基本的類表達(dá)式語法】
下面這段代碼等價于之前PersonClass示例的類表達(dá)式
let PersonClass = class { // 等價于 PersonType 構(gòu)造器 constructor(name) { this.name = name; } // 等價于 PersonType.prototype.sayName sayName() { console.log(this.name); } }; let person = new PersonClass("huochai"); person.sayName(); // 輸出 "huochai" console.log(person instanceof PersonClass); // true console.log(person instanceof Object); // true console.log(typeof PersonClass); // "function" console.log(typeof PersonClass.prototype.sayName); // "function"
類聲明和類表達(dá)式僅在代碼編寫方式略有差異,二者均不會像函數(shù)聲明和函數(shù)表達(dá)式一樣被提升,所以在運(yùn)行時狀態(tài)下無論選擇哪一種方式,代碼最終的執(zhí)行結(jié)果都沒有太大差別
二者最重要的區(qū)別是name屬性不同,匿名類表達(dá)式的name屬性值是一個空字符串,而類聲明的name屬性值為類名,例如,通過聲明方式定義一個類PersonClass,則PersonClass.name的值為"PersonClass"
【命名類表達(dá)式】
類與函數(shù)一樣,都可以定義為命名表達(dá)式。聲明時,在關(guān)鍵字class后添加一個標(biāo)識符即可
let PersonClass = class PersonClass2 { // 等價于 PersonType 構(gòu)造器 constructor(name) { this.name = name; } // 等價于 PersonType.prototype.sayName sayName() { console.log(this.name); } }; console.log(typeof PersonClass); // "function" console.log(typeof PersonClass2); // "undefined"
上面的示例中,類表達(dá)式被命名為PersonClass2,由于標(biāo)識符PersonClass2只存在于類定義中,因此它可被用在像sayName()這樣的方法中。而在類的外部,由于不存在一個名為PersonClass2的綁定,因而typeof PersonClass2的值為"undefined"
// 直接等價于 PersonClass 具名的類表達(dá)式 let PersonClass = (function() { "use strict"; const PersonClass2 = function(name) { // 確認(rèn)函數(shù)被調(diào)用時使用了 new if (typeof new.target === "undefined") { throw new Error("Constructor must be called with new."); } this.name = name; } Object.defineProperty(PersonClass2.prototype, "sayName", { value: function() { // 確認(rèn)函數(shù)被調(diào)用時沒有使用 new if (typeof new.target !== "undefined") { throw new Error("Method cannot be called with new."); } console.log(this.name); }, enumerable: false, writable: true, configurable: true }); return PersonClass2; }());
在JS引擎中,類表達(dá)式的實現(xiàn)與類聲明稍有不同。對于類聲明來說,通過let定義的外部綁定與通過const定義的內(nèi)部綁定具有相同名稱;而命名類表達(dá)式通過const定義名稱,從而PersonClass2只能在類的內(nèi)部使用
盡管命名類表達(dá)式與命名函數(shù)表達(dá)式有不同的表現(xiàn),但二者間仍有許多相似之處,都可以在多個場景中作為值使用
一等公民
在程序中,一等公民是指一個可以傳入函數(shù),可以從函數(shù)返回,并且可以賦值給變量的值。JS函數(shù)是一等公民(也被稱作頭等函數(shù)),這也正是JS中的一個獨特之處
ES6延續(xù)了這個傳統(tǒng),將類也設(shè)計為一等公民,允許通過多種方式使用類的特性。例如,可以將類作為參數(shù)傳入函數(shù)中
function createObject(classDef) { return new classDef(); } let obj = createObject(class { sayHi() { console.log("Hi!"); } }); obj.sayHi(); // "Hi!"
在這個示例中,調(diào)用createObject()函數(shù)時傳入一個匿名類表達(dá)式作為參數(shù),然后通過關(guān)鍵字new實例化這個類并返回實例,將其儲存在變量obj中
類表達(dá)式還有另一種使用方式,通過立即調(diào)用類構(gòu)造函數(shù)可以創(chuàng)建單例。用new調(diào)用類表達(dá)式,緊接著通過一對小括號調(diào)用這個表達(dá)式
let person = new class { constructor(name) { this.name = name; } sayName() { console.log(this.name); } }("huochai"); person.sayName(); // "huochai"
這里先創(chuàng)建一個匿名類表達(dá)式,然后立即執(zhí)行。依照這種模式可以使用類語法創(chuàng)建單例,并且不會在作用域中暴露類的引用,其后的小括號表明正在調(diào)用一個函數(shù),而且可以傳參數(shù)給這個函數(shù)
我們可以通過類似對象字面量的語法在類中創(chuàng)建訪問器屬性
訪問器屬性
盡管應(yīng)該在類構(gòu)造函數(shù)中創(chuàng)建自己的屬性,但是類也支持訪問器屬性。創(chuàng)建getter時,需要在關(guān)鍵字get后緊跟一個空格和相應(yīng)的標(biāo)識符;創(chuàng)建setter時,只需把關(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類是一個針對現(xiàn)有DOM元素的包裝器,并通過getter和setter方法將這個元素的innerHTML方法委托給html屬性,這個訪問器屬性是在CustomHTMLElement.prototype上創(chuàng)建的。與其他方法一樣,創(chuàng)建時聲明該屬性不可枚舉。下面這段代碼是非類形式的等價實現(xiàn)
// 直接等價于上個范例 let CustomHTMLElement = (function() { "use strict"; const CustomHTMLElement = function(element) { // 確認(rèn)函數(shù)被調(diào)用時使用了 new 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; }());
由上可見,比起非類等效實現(xiàn),類語法可以節(jié)省很多代碼。在非類等效實現(xiàn)中,僅html訪問器屬性定義的代碼量就與類聲明一樣多
可計算成員名稱
類和對象字面量還有更多相似之處,類方法和訪問器屬性也支持使用可計算名稱。就像在對象字面量中一樣,用方括號包裹一個表達(dá)式即可使用可計算名稱
let methodName = "sayName"; class PersonClass { constructor(name) { this.name = name; } [methodName]() { console.log(this.name); } } let me = new PersonClass("huochai"); me.sayName(); // "huochai"
這個版本的PersonClass通過變量來給類定義中的方法命名,字符串"sayName"被賦值給methodName變量,然后methodName又被用于聲明隨后可直接訪問的sayName()方法
通過相同的方式可以在訪問器屬性中應(yīng)用可計算名稱
let propertyName = "html"; class CustomHTMLElement { constructor(element) { this.element = element; } get [propertyName]() { return this.element.innerHTML; } set [propertyName](value) { this.element.innerHTML = value; } }
在這里通過propertyName變量并使用getter和setter方法為類添加html屬性,并且可以像往常一樣通過.html訪問該屬性
在類和對象字面量諸多的共同點中,除了方法、訪問器屬性及可計算名稱上的共同點外,還需要了解另一個相似之處,也就是生成器方法
生成器方法
在對象字面量中,可以通過在方法名前附加一個星號(*)的方式來定義生成器,在類中亦是如此,可以將任何方法定義成生成器
class MyClass { *createIterator() { yield 1; yield 2; yield 3; } } let instance = new MyClass(); let iterator = instance.createIterator();
這段代碼創(chuàng)建了一個名為MyClass的類,它有一個生成器方法createIterator(),其返回值為一個硬編碼在生成器中的迭代器。如果用對象來表示集合,又希望通過簡單的方法迭代集合中的值,那么生成器方法就派上用場了。數(shù)組、Set集合及Map集合為開發(fā)者們提供了多個生成器方法來與集合中的元素交互
盡管生成器方法很實用,但如果類是用來表示值的集合的,那么為它定義一個默認(rèn)迭代器會更有用。通過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) { // 1 // 2 // 3 console.log(x); }
這個示例用可計算名稱創(chuàng)建了一個代理this.items數(shù)組values()迭代器的生成器方法。任何管理一系列值的類都應(yīng)該引入默認(rèn)迭代器,因為一些與特定集合有關(guān)的操作需要所操作的集合含有一個迭代器?,F(xiàn)在可以將collection的實例直接用于for-of循環(huán)中或用展開運(yùn)算符操作它
如果不介意在對象的實例中出現(xiàn)添加的方法和訪問器屬性,則可以將它們添加到類的原型中;如果希望它們只出現(xiàn)在類中,那么需要使用靜態(tài)成員
靜態(tài)成員
在ES5中,直接將方法添加到構(gòu)造函數(shù)中來模擬靜態(tài)成員是一種常見的模式
function PersonType(name) { this.name = name; } // 靜態(tài)方法 PersonType.create = function(name) { return new PersonType(name); }; // 實例方法 PersonType.prototype.sayName = function() { console.log(this.name); }; var person = PersonType.create("huochai");
在其他編程語言中,由于工廠方法PersonType.create()使用的數(shù)據(jù)不依賴personType的實例,因而其會被認(rèn)為是一個靜態(tài)方法。ES6的類語法簡化了創(chuàng)建靜態(tài)成員的過程,在方法或訪問器屬性名前使用正式的靜態(tài)注釋即可
class PersonClass { // 等價于 PersonType 構(gòu)造器 constructor(name) { this.name = name; } // 等價于 PersonType.prototype.sayName sayName() { console.log(this.name); } // 等價于 PersonType.create static create(name) { return new PersonClass(name); } } let person = PersonClass.create("huochai");
PersonClass定義只有一個靜態(tài)方法create(),它的語法與sayName()的區(qū)別只在于是否使用static關(guān)鍵字。類中的所有方法和訪問器屬性都可以用static關(guān)鍵字來定義,唯一的限制是不能將static用于定義構(gòu)造函數(shù)方法
[注意]不可在實例中訪問靜態(tài)成員,必須要直接在類中訪問靜態(tài)成員
繼承與派生類
在ES6之前,實現(xiàn)繼承與自定義類型是一個不小的工作。嚴(yán)格意義上的繼承需要多個步驟實現(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,為了這樣做,必須用一個創(chuàng)建自Rectangle.prototype的新對象重寫Square.prototype并調(diào)用Rectangle.call()方法。JS新手經(jīng)常對這些步驟感到困惑,即使是經(jīng)驗豐富的開發(fā)者也常在這里出錯
類的出現(xiàn)讓我們可以更輕松地實現(xiàn)繼承功能,使用熟悉的extends關(guān)鍵字可以指定類繼承的函數(shù)。原型會自動調(diào)整,通過調(diào)用super()方法即可訪問基類的構(gòu)造函數(shù)
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 console.log(square instanceof Rectangle); // true
這一次,square類通過extends關(guān)鍵字繼承Rectangle類,在square構(gòu)造函數(shù)中通過super()調(diào)用Rectangle構(gòu)造函數(shù)并傳入相應(yīng)參數(shù)。請注意,與ES5版本代碼不同的是,標(biāo)識符Rectangle只用于類聲明(extends之后)
繼承自其他類的類被稱作派生類,如果在派生類中指定了構(gòu)造函數(shù)則必須要調(diào)用super(),如果不這樣做程序就會報錯。如果選擇不使用構(gòu)造函數(shù),則當(dāng)創(chuàng)建新的類實例時會自動調(diào)用super()并傳入所有參數(shù)
class Square extends Rectangle { // 沒有構(gòu)造器 } // 等價于: class Square extends Rectangle { constructor(...args) { super(...args); } }
示例中的第二個類是所有派生類的等效默認(rèn)構(gòu)造函數(shù),所有參數(shù)按順序被傳遞給基類的構(gòu)造函數(shù)。這里展示的功能不太正確,因為square的構(gòu)造函數(shù)只需要一個參數(shù),所以最好手動定義構(gòu)造函數(shù)
注意事項
使用super()時有以下幾個關(guān)鍵點
1、只可在派生類的構(gòu)造函數(shù)中使用super(),如果嘗試在非派生類(不是用extends聲明的類)或函數(shù)中使用則會導(dǎo)致程序拋出錯誤
2、在構(gòu)造函數(shù)中訪問this之前一定要調(diào)用super(),它負(fù)責(zé)初始化this,如果在調(diào)用super()之前嘗試訪問this會導(dǎo)致程序出錯
3、如果不想調(diào)用super(),則唯一的方法是讓類的構(gòu)造函數(shù)返回一個對象
【類方法遮蔽】
派生類中的方法總會覆蓋基類中的同名方法。比如給square添加getArea()方法來重新定義這個方法的功能
class Square extends Rectangle { constructor(length) { super(length, length); } // 重寫并屏蔽 Rectangle.prototype.getArea() getArea() { return this.length * this.length; } }
由于為square定義了getArea()方法,便不能在square的實例中調(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(); } }
以這種方法使用Super,this值會被自動正確設(shè)置,然后就可以進(jìn)行簡單的方法調(diào)用了
【靜態(tài)成員繼承】
如果基類有靜態(tài)成員,那么這些靜態(tài)成員在派生類中也可用。JS中的繼承與其他語言中的繼承一樣,只是在這里繼承還是一個新概念
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
在這段代碼中,新的靜態(tài)方法create()被添加到Rectangle類中,繼承后的Square.create()與Rectangle.create()的行為很像
【派生自表達(dá)式的類】
ES6最強(qiáng)大的一面或許是從表達(dá)式導(dǎo)出類的功能了。只要表達(dá)式可以被解析為一個函數(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是一個ES5風(fēng)格的構(gòu)造函數(shù),Square是一個類,由于Rectangle具有[[Construct]]屬性和原型,因此Square類可以直接繼承它
extends強(qiáng)大的功能使類可以繼承自任意類型的表達(dá)式,從而創(chuà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 Square 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)用后返回Rectang?e,此示例實現(xiàn)的功能與之前的示例等價。由于可以動態(tài)確定使用哪個基類,因而可以創(chuàng)建不同的繼承方法
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函數(shù)代替?zhèn)鹘y(tǒng)的繼承方法,它可以接受任意數(shù)量的mixin對象作為參數(shù)。首先創(chuàng)建一個函數(shù)base,再將每一個mixin對象的屬性值賦值給base的原型,最后minxin函數(shù)返回這個base函數(shù),所以Square類就可以基于這個返回的函數(shù)用extends進(jìn)行擴(kuò)展。由于使用了extends,因此在構(gòu)造函數(shù)中需要調(diào)用super()
Square的實例擁有來自AreaMixin對象的getArea()方法和來自SerializableMixin對象的serialize方法,這都是通過原型繼承實現(xiàn)的,mixin()函數(shù)會用所有mixin對象的自有屬性動態(tài)填充新函數(shù)的原型。如果多個mixin對象具有相同屬性,那么只有最后一個被添加的屬性被保留
[注意]在extends后可以使用任意表達(dá)式,但不是所有表達(dá)式最終都能生成合法的類。如果使用null或生成器函數(shù)會導(dǎo)致錯誤發(fā)生,類在這些情況下沒有[[Consturct]]屬性,嘗試為其創(chuàng)建新的實例會導(dǎo)致程序無法調(diào)用[[Construct]]而報錯
【內(nèi)建對象的繼承】
自JS數(shù)組誕生以來,一直都希望通過繼承的方式創(chuàng)建屬于自己的特殊數(shù)組。在ES5中這幾乎是不可能的,用傳統(tǒng)的繼承方式無法實現(xiàn)這樣的功能
// 內(nèi)置數(shù)組的行為 var colors = []; colors[0] = "red"; console.log(colors.length); // 1 colors.length = 0; console.log(colors[0]); // undefined // 在 ES5 中嘗試?yán)^承數(shù)組 function MyArray() { Array.apply(this, arguments); } MyArray.prototype = Object.create(Array.prototype, { constructor: { value: MyArray, writable: true, configurable: true, enumerable: true } }); var colors = new MyArray(); colors[0] = "red"; console.log(colors.length); // 0 colors.length = 0; console.log(colors[0]); // "red"
這段代碼最后console.log()的輸出結(jié)果與預(yù)期不符,MyArray實例的length和數(shù)值型屬性的行為與內(nèi)建數(shù)組中的不一致,這是因為通過傳統(tǒng)JS繼承形式實現(xiàn)的數(shù)組繼承沒有從Array.apply()或原型賦值中繼承相關(guān)功能
ES6類語法的一個目標(biāo)是支持內(nèi)建對象繼承,因而ES6中的類繼承模型與ES5稍有不同,主要體現(xiàn)在兩個方面
在ES5的傳統(tǒng)繼承方式中,先由派生類型(如MyArray)創(chuàng)建this的值,然后調(diào)用基類型的構(gòu)造函數(shù)(如Array.apply()方法)。這也意味著,this的值開始指向MyArray的實例,但是隨后會被來自Array的其他屬性修飾
ES6中的類繼承則與之相反,先由基類(Array)創(chuàng)建this的值,然后派生類的構(gòu)造函數(shù)(MyArray)再修改這個值。所以一開始可以通過this訪問基類的所有內(nèi)建功能,然后再正確地接收所有與之相關(guān)的功能
class MyArray extends Array { // 空代碼塊 } var colors = new MyArray(); colors[0] = "red"; console.log(colors.length); // 1 colors.length = 0; console.log(colors[0]); // undefined
MyArray直接繼承自Array,其行為與Array也很相似,操作數(shù)值型屬性會更新length屬性,操作length屬性也會更新數(shù)值型屬性。于是,可以正確地繼承Array對象來創(chuàng)建自己的派生數(shù)組類型,當(dāng)然也可以繼承其他的內(nèi)建對象
【Symbol.species屬性】
內(nèi)建對象繼承的一個實用之處是,原本在內(nèi)建對象中返回實例自身的方法將自動返回派生類的實例。所以,如果有一個繼承自Array的派生類MyArray,那么像slice()這樣的方法也會返回一個MyArray的實例
class MyArray extends Array { // 空代碼塊 } let items = new MyArray(1, 2, 3, 4), subitems = items.slice(1, 3); console.log(items instanceof MyArray); // true console.log(subitems instanceof MyArray); // true
正常情況下,繼承自Array的slice()方法應(yīng)該返回Array的實例,但是在這段代碼中,slice()方法返回的是MyArray的實例。在瀏覽器引擎背后是通過Symbol.species屬性實現(xiàn)這一行為
Symbol.species是諸多內(nèi)部Symbol中的一個,它被用于定義返回函數(shù)的靜態(tài)訪問器屬性。被返回的函數(shù)是一個構(gòu)造函數(shù),每當(dāng)要在實例的方法中(不是在構(gòu)造函數(shù)中)創(chuàng)建類的實例時必須使用這個構(gòu)造函數(shù)。以下這些內(nèi)建類型均己定義Symbol.species屬性
Array ArrayBuffer Map Promise RegExp Set Typed arrays
列表中的每個類型都有一個默認(rèn)的symbol.species屬性,該屬性的返回值為this,這也意味著該屬性總會返回構(gòu)造函數(shù)
// 幾個內(nèi)置類型使用 species 的方式類似于此 class MyClass { static get [Symbol.species]() { return this; } constructor(value) { this.value = value; } clone() { return new this.constructor[Symbol.species](this.value); } }
在這個示例中,Symbol.species被用來給MyClass賦值靜態(tài)訪問器屬性。這里只有一個getter方法卻沒有setter方法,這是因為在這里不可以改變類的種類。調(diào)用this.constructor[Symbol.species]會返回MyClass,clone()方法通過這個定義可以返回新的實例,從而允許派生類覆蓋這個值
class MyClass { static get [Symbol.species]() { return this; } constructor(value) { this.value = value; } clone() { return new this.constructor[Symbol.species](this.value); } } class MyDerivedClass1 extends MyClass { // 空代碼塊 } class MyDerivedClass2 extends MyClass { static get [Symbol.species]() { return MyClass; } } let instance1 = new MyDerivedClass1("foo"), clone1 = instance1.clone(), instance2 = new MyDerivedClass2("bar"), clone2 = instance2.clone(); console.log(clone1 instanceof MyClass); // true console.log(clone1 instanceof MyDerivedClass1); // true console.log(clone2 instanceof MyClass); // true console.log(clone2 instanceof MyDerivedClass2); // false
在這里,MyDerivedClass1繼承MyClass時未改變Symbol.species屬性,由于this.constructor[Symbol.species]的返回值是MyDerivedClass1,因此調(diào)用clone()返回的是MyDerivedClass1的實例;MyDerivedClass2繼承MyClass時重寫了Symbol.species讓其返回MyClass,調(diào)用MyDerivedClass2實例的clone()方法時,返回值是一個MyClass的實例。通過Symbol.species可以定義當(dāng)派生類的方法返回實例時,應(yīng)該返回的值的類型
數(shù)組通過Symbol.species來指定那些返回數(shù)組的方法應(yīng)當(dāng)從哪個類中獲取。在一個派生自數(shù)組的類中,可以決定繼承的方法返回何種類型的對象
class MyArray extends Array { static get [Symbol.species]() { return Array; } } let items = new MyArray(1, 2, 3, 4), subitems = items.slice(1, 3); console.log(items instanceof MyArray); // true console.log(subitems instanceof Array); // true console.log(subitems instanceof MyArray); // false
這段代碼重寫了MyArray繼承自Array的Symbol.species屬性,所有返回數(shù)組的繼承方法現(xiàn)在將使用Array的實例,而不使用MyArray的實例
一般來說,只要想在類方法中調(diào)用this.constructor,就應(yīng)該使用Symbol.species屬性,從而讓派生類重寫返回類型。而且如果正從一個已定義Symbol.species屬性的類創(chuàng)建派生類,那么要確保使用那個值而不是使用構(gòu)造函數(shù)
【在類的構(gòu)造函數(shù)中使用new.target】
new.target及它的值根據(jù)函數(shù)被調(diào)用的方式而改變。在類的構(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)時等價于Rectangle的new.target。類構(gòu)造函數(shù)必須通過new關(guān)鍵字調(diào)用,所以總是在類的構(gòu)造函數(shù)中定義new.target屬性,但是其值有時會不同
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ā)生時new.target等于Square。這一點非常重要,因為每個構(gòu)造函數(shù)都可以根據(jù)自身被調(diào)用的方式改變自己的行為
// 靜態(tài)的基類 class Shape { constructor() { if (new.target === Shape) { throw new Error("This class cannot be instantiated directly.") } } } 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
在這個示例中,每當(dāng)new.target是Shape時構(gòu)造函數(shù)總會拋出錯誤,這相當(dāng)于調(diào)用new Shape()時總會出錯。但是,仍可用Shape作為基類派生其他類,示例中的Rectangle便是這樣。super()調(diào)用執(zhí)行了Shape的構(gòu)造函數(shù),new.target與Rectangle等價,所以構(gòu)造函數(shù)繼續(xù)執(zhí)行不會拋出錯誤
[注意]因為類必須通過new關(guān)鍵字才能調(diào)用,所以在類的構(gòu)造函數(shù)中,new.target屬性永遠(yuǎn)不會是undefined
以上是“ES6中類的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)網(wǎng)站建設(shè)公司行業(yè)資訊頻道!
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)建站m.rwnh.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。
當(dāng)前文章:ES6中類的示例分析-創(chuàng)新互聯(lián)
轉(zhuǎn)載來源:http://m.rwnh.cn/article26/ddoejg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供響應(yīng)式網(wǎng)站、網(wǎng)頁設(shè)計公司、定制開發(fā)、網(wǎng)站內(nèi)鏈、域名注冊、面包屑導(dǎo)航
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容