十、Map數(shù)據(jù)結(jié)構(gòu)
ECMAScript 6 中的 map 類(lèi)型包含一組有序的鍵值對(duì),其中鍵和值可以是任何類(lèi)型。
鍵的比較結(jié)果由 Object.is() 來(lái)決定,所以你可以同時(shí)使用 5 和 "5" 做為鍵來(lái)存儲(chǔ),因?yàn)樗鼈兪遣煌念?lèi)型。
這和使用對(duì)象屬性做為值的方法大相徑庭,因?yàn)? **對(duì)象的屬性會(huì)被強(qiáng)制轉(zhuǎn)換為字符串類(lèi)型**。
10.1 創(chuàng)建Map對(duì)象和Map的基本的存取操作
- Map創(chuàng)建也是使用Map構(gòu)造函數(shù)
- 向Map存儲(chǔ)鍵值對(duì)使用set(key, value);方法
- 可以使用get(key),來(lái)獲取指定key對(duì)應(yīng)的value
<script type="text/javascript">
var map = new Map();
map.set("a", "lisi");
map.set("b", "zhangsan");
map.set("b", "zhangsan222"); // 第二次添加,新的value會(huì)替換掉舊的
console.log(map.get("a"));
console.log(map.get("b")); //zhangsan222
console.log(map.get("c")); //undefined.如果key不存在,則返回undefined
console.log(map.size); //2
</script>
10.2 Map與Set類(lèi)似的3個(gè)方法
- has(key) - 判斷給定的 key 是否在 map 中存在
- delete(key) - 移除 map 中的 key 及對(duì)應(yīng)的值
- clear() - 移除 map 中所有的鍵值對(duì)
10.3 初始化Map
創(chuàng)建Map的時(shí)候也可以像Set一樣傳入數(shù)組。但是傳入的數(shù)組中必須存儲(chǔ)的也是數(shù)組,而且每個(gè)數(shù)組中有兩個(gè)元素,分別是鍵和值
也就是傳入的實(shí)際是一個(gè)二維數(shù)組!
<script type="text/javascript">
//map接受一個(gè)二維數(shù)組
var map = new Map([
//每一個(gè)數(shù)組中,第一個(gè)是是map的可以,第二個(gè)是map的value。如果只有第一個(gè),則值是undefined
["name", "lisi"],
["age", 20],
["sex", "nan"]
]);
console.log(map.size);
console.log(map.get("name"))
</script>
10.4 Map的forEach方法
<script type="text/javascript">
var map = new Map([
["name", "李四"],
["age", 20],
["sex", "nan"]
]);
/*
回調(diào)函數(shù)有函數(shù):
參數(shù)1:鍵值對(duì)的value
參數(shù)2:鍵值對(duì)的key
參數(shù)3:map對(duì)象本身
*/
map.forEach(function (value, key, ownMap) {
console.log(`key=${key} ,vlue=${value}`);
console.log(this);
})
</script>
十一、迭代器(iterator)和for...of循環(huán)
11.1 循環(huán)問(wèn)題
var colors = ["red", "green", "blue"];
for (var i = 0, len = colors.length; i < len; i++) {
console.log(colors[i]);
}
上面的代碼寫(xiě)起來(lái)簡(jiǎn)單,但是實(shí)際使用的過(guò)程中,我們需要自己去控制變量,如果有嵌套的情況下,還要控制多個(gè)變量,很容易出錯(cuò)。
迭代器就是為了解決這個(gè)問(wèn)題的。
11.2 什么是迭代器
- 迭代器是一個(gè)對(duì)象
- 迭代器提供一個(gè)方法next() 這個(gè)方式總是能夠返回迭代到的對(duì)象。
- next返回的對(duì)象中,至少有兩個(gè)屬性:done 是一個(gè)boolean值(表示數(shù)據(jù)是否迭代完)。 value:具體的數(shù)據(jù)(迭代到的具體數(shù)據(jù))
迭代器只是帶有特殊接口(方法)的對(duì)象。所有迭代器對(duì)象都帶有 next() 方法并返回一個(gè)包含兩個(gè)屬性的結(jié)果對(duì)象。這些屬性分別是 value 和 done,前者代表下一個(gè)位置的值,后者在沒(méi)有更多值可供迭代的時(shí)候?yàn)?true 。迭代器帶有一個(gè)內(nèi)部指針,來(lái)指向集合中某個(gè)值的位置。當(dāng) next() 方法調(diào)用后,指針下一位置的值會(huì)被返回。
若你在末尾的值被返回之后繼續(xù)調(diào)用 next(),那么返回的 done 屬性值為 true,value 的值則由迭代器設(shè)定。該值并不屬于數(shù)據(jù)集,而是專(zhuān)門(mén)為數(shù)據(jù)關(guān)聯(lián)的附加信息,如若該信息并未指定則返回 undefined 。迭代器返回的值和函數(shù)返回值有些類(lèi)似,因?yàn)閮烧叨际欠祷亟o調(diào)用者信息的最終手段。
我們可以用ES5之前的知識(shí)手動(dòng)創(chuàng)建一個(gè)迭代器:
function createIterator(items) {
var i = 0;
return {
next: function() {
var done = (i >= items.length);
var value = !done ? items[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
//創(chuàng)建一個(gè)可以在指定數(shù)組上面迭代的迭代器對(duì)象。
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// for all further calls
console.log(iterator.next()); // "{ value: undefined, done: true }"
從以上的示例來(lái)看,根據(jù) ECMAScript 6 規(guī)范模擬實(shí)現(xiàn)的迭代器還是有些復(fù)雜。
幸運(yùn)的是,ECMAScript 6 還提供了生成器,使得迭代器對(duì)象的創(chuàng)建容易了許多。
11.3 生成器函數(shù)
生成器函數(shù)就是返回迭代器的函數(shù)!
生成器函數(shù)由 function 關(guān)鍵字和之后的星號(hào)(*)標(biāo)識(shí),同時(shí)還能使用新的 yield 關(guān)鍵字。
看下面代碼:
<script type="text/javascript">
//生成器函數(shù)。 注意中間的 * 不能丟
function * createIterator() {
//每個(gè)yield的后面的值表示我們迭代到的值。 yield也定義了我們迭代的順序。
yield 3;
yield 4;
yield 2;
}
var it = createIterator();
console.log(it.next().value); // 2
console.log(it.next().value); // 4
console.log(it.next().value); // 2
console.log(it.next().value); //undefined
</script>
迭代器函數(shù)也是函數(shù),所以他可以像正常的函數(shù)一樣調(diào)用,但是迭代器生成器函數(shù)會(huì)自動(dòng)返回一個(gè)迭代器對(duì)象。
每調(diào)用一次迭代器的next方法,如果碰到 yield 都會(huì)返回一個(gè)迭代到的一個(gè)對(duì)象,然后停止執(zhí)行,直到下次調(diào)用next方法,會(huì)從上次停止的地方繼續(xù)執(zhí)行。
//這個(gè)迭代器函數(shù)返回的迭代器可以迭代傳入的數(shù)組中的所有元素。
function *createIterator(items) {
for (let i = 0; i < items.length; i++) {
//每調(diào)用一次next,碰到y(tǒng)ild程序就會(huì)停止,并返回迭代到的對(duì)象 {value : items[i], done : true}
yield items[i];
}
}
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 進(jìn)一步調(diào)用
console.log(iterator.next()); // "{ value: undefined, done: true }"
注意:
- yield 關(guān)鍵字只能 直接用在生成器內(nèi)部 。在其它地方甚至是生成器內(nèi)部的函數(shù)中使用都會(huì)拋出語(yǔ)法錯(cuò)誤。
11.4 生成器函數(shù)表達(dá)式
你可以使用函數(shù)表達(dá)式來(lái)創(chuàng)建生成器,只需在 function 關(guān)鍵字和圓括號(hào)之間添加星號(hào)(*)。例如:
let createIterator = function *(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
};
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 進(jìn)一步調(diào)用
console.log(iterator.next()); // "{ value: undefined, done: true }"
注意:無(wú)法使用箭頭函數(shù)來(lái)創(chuàng)建生成器。
11.5 可迭代類(lèi)型和for-of迭代循環(huán)
迭代器的主要工作就是迭代數(shù)據(jù),但是不是所有的數(shù)據(jù)都是可以迭代的。
與迭代器緊密相關(guān)的是,可迭代類(lèi)型是指那些包含 Symbol.iterator 屬性(方法)的對(duì)象。
該 symbol 類(lèi)型定義了返回迭代器的函數(shù)。在 ECMAScript 6 中,所有的集合對(duì)象(數(shù)組,set 和 map)與字符串都是可迭代類(lèi)型,因此它們都有默認(rèn)的迭代器??傻?lèi)型是為了 ECMAScript6 新添加的 **for-of** 循環(huán)而設(shè)計(jì)的。
換句話(huà)說(shuō),默認(rèn)情況下只有 **數(shù)組、set、Map和字符串**才可以使用迭代器去迭代。 (也就可以使用for...of了)
for…of循環(huán)只迭代出來(lái)的元素,根本不管索引!不管索引!不管索引!重要的問(wèn)題重復(fù)三遍!
使用 for…of 迭代數(shù)組:
<script type="text/javascript">
var arr = ["a", "c", "b", "d"];
for(var item of arr){
console.log(item)
}
</script>
使用 for…of 迭代Set:
<script type="text/javascript">
var set = new Set(["a", "c", "b", "d"]);
for(var item of set){
console.log(item)
}
</script>
使用 for…of 迭代Map:
<script type="text/javascript">
var map = new Map([["name", "lisi"],["sex", "男"],["age", 20]]);
map.set("aaa", "bbb")
for(var item of map){
console.log(item); //注意:這里迭代到的是由key和value組成的數(shù)組。
}
</script>
使用for … of迭代字符串
<script type="text/javascript">
var s = "abcd";
for(let c of s){
console.log(c)
}
</script>
注意:for...of 只能迭代可以迭代的對(duì)象,對(duì)于非可迭代對(duì)象使用for...of會(huì)拋出異常
說(shuō)明:以數(shù)組為例。
for-of 循環(huán)首先會(huì)調(diào)用 values 數(shù)組的 Symbol.iterator 方法來(lái)獲取迭代器(Symbol.iterator 方法由幕后的 JavaScript 引擎調(diào)用)。之后再調(diào)用 iterator.next() 并將結(jié)果對(duì)象中的 value 屬性值,即 1,2,3,依次賦給 num 變量。當(dāng)檢測(cè)到結(jié)果對(duì)象中的 done 為 true,循環(huán)會(huì)退出,所以 num 不會(huì)被賦值為 undefined 。
如果你只想簡(jiǎn)單的迭代數(shù)組或集合中的元素,那么 for-of 循環(huán)比 for 要更好。for-of 一般不容易出錯(cuò),因?yàn)橐粉櫟臈l件更少。所以還是把 for 循環(huán)留給復(fù)雜控制條件的需求吧。
11.6 訪(fǎng)問(wèn)可迭代類(lèi)型的默認(rèn)迭代器
Symbol.iterator是可迭代類(lèi)型的一個(gè)方法,調(diào)用這個(gè)方法就可以獲取到他的默認(rèn)迭代器。
<script type="text/javascript">
let s = "abcd";
let it = s[Symbol.iterator](); //調(diào)用字符串的Symbol.iterator方法
console.log(it.next()); //返回迭代器迭代到的第一個(gè)對(duì)象
</script>
因?yàn)镾ymbol可以返回一個(gè)對(duì)象的默認(rèn)迭代器,所以我們可以使用它來(lái)判斷一個(gè)對(duì)象是否可迭代
<script type="text/javascript">
function isIterable(object) {
return typeof object[Symbol.iterator] === "function";
}
console.log(isIterable([1, 2, 3])); // true
console.log(isIterable("Hello")); // true
console.log(isIterable(new Map())); // true
console.log(isIterable(new Set())); // true
console.log(isIterable({"name":"李四"})); // false。普通對(duì)象不可迭代
</script>
11.7 自定義可迭代類(lèi)型
開(kāi)發(fā)者自定義的對(duì)象默認(rèn)是不可迭代類(lèi)型,但是你可以為它們創(chuàng)建 Symbol.iterator 屬性并指定一個(gè)生成器來(lái)使這個(gè)對(duì)象可迭代。例如:
let collection = {
items: [],
*[Symbol.iterator]() {
for (let item of this.items) {
yield item;
}
}
};
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
console.log(x);
}
十二、類(lèi)
和大多數(shù)面向?qū)ο蟮恼Z(yǔ)言(object-oriented programming language)不同,JavaScript 在誕生之初并不支持使用類(lèi)和傳統(tǒng)的類(lèi)繼承并作為主要的定義方式來(lái)創(chuàng)建相似或關(guān)聯(lián)的對(duì)象。
這很令開(kāi)發(fā)者困惑,而且在早于 ECMAScript 1 到 ECMAScript 5 這段時(shí)期,很多庫(kù)都創(chuàng)建了一些實(shí)用工具(utility)來(lái)讓 JavaScript 從表層上支持類(lèi)。
盡管一些 JavaScript 開(kāi)發(fā)者強(qiáng)烈主張?jiān)撜Z(yǔ)言不需要類(lèi),但由于大量的庫(kù)都對(duì)類(lèi)做了實(shí)現(xiàn),ECMAScript 6 也順勢(shì)將其引入。
12.1 ES5之前的模擬的類(lèi)
在 ECMAScript 5 或更早的版本中,JavaScript 沒(méi)有類(lèi)。和類(lèi)這個(gè)概念及行為最接近的是創(chuàng)建一個(gè)構(gòu)造函數(shù)并在構(gòu)造函數(shù)的原型上添加方法,這種實(shí)現(xiàn)也被稱(chēng)為自定義的類(lèi)型創(chuàng)建,例如:
function PersonType(name) {
this.name = name;
}
PersonType.prototype.sayName = function() {
console.log(this.name);
};
let person = new PersonType("Nicholas");
person.sayName(); // 輸出 "Nicholas"
console.log(person instanceof PersonType); // true
console.log(person instanceof Object); // true
說(shuō)明:
前面的PersonType我們以前一直叫做構(gòu)造函數(shù),其實(shí)他就是一個(gè)類(lèi)型,因?yàn)樗_實(shí)表示了一種類(lèi)型。
12.2 ES6中基本的類(lèi)聲明
在ES6直接借鑒其他語(yǔ)言,引入了類(lèi)的概念。所以再實(shí)現(xiàn)上面那種模擬 的類(lèi)就容易了很多。
//class關(guān)鍵字必須是小寫(xiě)。 后面就是跟的類(lèi)名
class PersonClass {
// 等效于 PersonType 構(gòu)造函數(shù)。
constructor(name) { //這個(gè)表示類(lèi)的構(gòu)造函數(shù)。constuctor也是關(guān)鍵字必須小寫(xiě)。
this.name = name; //創(chuàng)建屬性。 也叫當(dāng)前類(lèi)型的自有屬性。
}
// 等效于 PersonType.prototype.sayName. 這里的sayName使用了我們前面的簡(jiǎn)寫(xiě)的方式。
sayName() {
console.log(this.name);
}
}
let person = new PersonClass("Nicholas");
person.sayName(); // 輸出 "Nicholas"
console.log(person instanceof PersonClass); // true
console.log(person instanceof Object); // true
console.log(typeof PersonClass); // "function"
console.log(typeof PersonClass.prototype.sayName); // "function"
說(shuō)明:
- 自有屬性:屬性只出現(xiàn)在實(shí)例而不是原型上,而且只能由構(gòu)造函數(shù)和方法來(lái)創(chuàng)建。在本例中,name 就是自有屬性。我建議 盡可能的將所有自有屬性創(chuàng)建在構(gòu)造函數(shù)中,這樣當(dāng)查找屬性時(shí)可以做到一目了然。
- 類(lèi)聲明只是上例中自定義類(lèi)型的語(yǔ)法糖。PersonClass 聲明實(shí)際上創(chuàng)建了一個(gè)行為和 constructor 方法相同的構(gòu)造函數(shù),這也是 typeof PersonClass 返回 "function" 的原因。sayName() 在本例中作為 PersonClass.prototype 的方法,和上個(gè)示例中 sayName() 和 PersonType.prototype 關(guān)系一致。這些相似度允許你混合使用自定義類(lèi)型和類(lèi)而不需要糾結(jié)使用方式。
雖然類(lèi)和以前的使用構(gòu)造函數(shù)+原型的方式很像,但是還是有一些不太相同的地方,而且要牢記
- 類(lèi)聲明和函數(shù)定義不同,類(lèi)的聲明是不會(huì)被提升的。類(lèi)聲明的行為和 let 比較相似,所以當(dāng)執(zhí)行流作用到類(lèi)聲明之前類(lèi)會(huì)存在于暫存性死區(qū)(temporal dead zone)內(nèi)。
- 類(lèi)聲明中的代碼自動(dòng)運(yùn)行在嚴(yán)格模式下,同時(shí)沒(méi)有任何辦法可以手動(dòng)切換到非嚴(yán)格模式。
- 所有的方法都是不可枚舉的(non-enumerable),這和自定義類(lèi)型相比是個(gè)顯著的差異,因?yàn)楹笳咝枰褂?Object.defineProperty() 才能定義不可枚舉的方法。
- 所有的方法都不能使用 new 來(lái)調(diào)用,因?yàn)樗鼈儧](méi)有內(nèi)部方法 [[Construct]]。
- 不使用 new 來(lái)調(diào)用類(lèi)構(gòu)造函數(shù)會(huì)拋出錯(cuò)誤。也就是 必須使用new 類(lèi)() 的方式使用
- 試圖在類(lèi)的方法內(nèi)部重寫(xiě)類(lèi)名的行為會(huì)拋出錯(cuò)誤。(因?yàn)樵陬?lèi)的內(nèi)部,類(lèi)名是作為一個(gè)常量存在的)
12.2 匿名類(lèi)表達(dá)式
函數(shù)有函數(shù)表達(dá)式,類(lèi)也有類(lèi)表達(dá)式。
類(lèi)表達(dá)式的功能和前面的類(lèi)的聲明是一樣的。
let PersonClass = class {
// 等效于 PersonType 構(gòu)造函數(shù)
constructor(name) {
this.name = name;
}
// 等效于 PersonType.prototype.sayName
sayName() {
console.log(this.name);
}
};
let person = new PersonClass("Nicholas");
person.sayName(); // 輸出 "Nicholas"
console.log(person instanceof PersonClass); // true
console.log(person instanceof Object); // true
console.log(typeof PersonClass); // "function"
console.log(typeof PersonClass.prototype.sayName); // "function"
12.3 具名類(lèi)表達(dá)式
let PersonClass = class PersonClass2{
// 等效于 PersonType 構(gòu)造函數(shù)
constructor(name) {
this.name = name;
}
// 等效于 PersonType.prototype.sayName
sayName() {
console.log(this.name);
}
};
注意:具名類(lèi)表達(dá)式中PersonClass2這個(gè)類(lèi)名只能在類(lèi)的內(nèi)部訪(fǎng)問(wèn)到,在外面是訪(fǎng)問(wèn)不到的.
12.4 作為一等公民的類(lèi)型
在JavaScript中,函數(shù)是作為一等公民存在的。(也叫一等函數(shù))。
類(lèi)也是一等公民。
- 類(lèi)可以作為參數(shù)傳遞
function createObject(classDef) {
return new classDef();
}
let obj = createObject(class {
sayHi() {
console.log("Hi!");
}
});
obj.sayHi(); // "Hi!"
- 立即調(diào)用類(lèi)構(gòu)造函數(shù),創(chuàng)建單例
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}("Nicholas");
person.sayName(); // "Nicholas"
12.5 動(dòng)態(tài)計(jì)算類(lèi)成員的命名
類(lèi)的成員,也可以像我們前面的對(duì)象的屬性一樣可以動(dòng)態(tài)計(jì)算.( 使用[ ] 來(lái)計(jì)算)
let methodName = "sayName";
class PersonClass {
constructor(name) {
this.name = name;
}
[methodName]() {
console.log(this.name);
}
}
let me = new PersonClass("Nicholas");
me.sayName(); // "Nicholas"
12.6 靜態(tài)成員
在ES5中,我們可以直接給構(gòu)造函數(shù)添加屬性或方法來(lái)模擬靜態(tài)成員。
function PersonType(name) {
this.name = name;
}
// 靜態(tài)方法。 直接添加到構(gòu)造方法上。 (其實(shí)是把構(gòu)造函數(shù)當(dāng)做一個(gè)普通的對(duì)象來(lái)用。)
PersonType.create = function(name) {
return new PersonType(name);
};
// 實(shí)例方法
PersonType.prototype.sayName = function() {
console.log(this.name);
};
var person = PersonType.create("Nicholas");
在上面的create方法在其他語(yǔ)言中一般都是作為靜態(tài)方法來(lái)使用的。
下面高能,請(qǐng)注意:
ECMAScript 6 的類(lèi)通過(guò)在方法之前使用正式的 static 關(guān)鍵字簡(jiǎn)化了靜態(tài)方法的創(chuàng)建。例如,下例中的類(lèi)和上例相比是等效的:
class PersonClass {
// 等效于 PersonType 構(gòu)造函數(shù)
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("Nicholas");
注意:靜態(tài)成員通過(guò)實(shí)例對(duì)象不能訪(fǎng)問(wèn),只能通過(guò)類(lèi)名訪(fǎng)問(wèn)?。。?/p>
通過(guò)和ES5模擬靜態(tài)方法的例子你應(yīng)該知道為啥了吧
12.7 ES6中的繼承
在ES6之前要完成繼承,需要寫(xiě)很多的代碼。看下面的繼承的例子:
<script type="text/javascript">
function Father(name) {
this.name = name;
}
Father.prototype.sayName = function () {
console.log(this.name);
}
function Son(name,age) {
Father.call(this, name);
this.age = age;
}
Son.prototype = new Father();
Son.prototype.constructor = Son;
Son.prototype.sayAge = function () {
console.log(this.age);
}
var son1 = new Son("兒子", 20);
son1.sayAge(); //20
son1.sayName(); //兒子
</script>
12.7.1 繼承的基本寫(xiě)法
如果在ES6通過(guò)類(lèi)的方式完成繼承就簡(jiǎn)單了很多。
需要用到一個(gè)新的關(guān)鍵字:extends
<script type="text/javascript">
class Father{
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name);
}
}
class Son extends Father{ //extents后面跟表示要繼承的類(lèi)型
constructor(name, age){
super(name); //相當(dāng)于以前的:Father.call(this, name);
this.age = age;
}
//子類(lèi)獨(dú)有的方法
sayAge(){
console.log(this.age);
}
}
var son1 = new Son("李四", 30);
son1.sayAge();
son1.sayName();
console.log(son1 instanceof Son); // true
console.log(son1 instanceof Father); //true
</script>
這種繼承方法,和我們前面提到的構(gòu)造函數(shù)+原型的繼承方式本質(zhì)是一樣的。但是寫(xiě)起來(lái)更簡(jiǎn)單,可讀性也更好。
關(guān)于super的使用,有幾點(diǎn)需要注意:
- 你只能在派生類(lèi)中使用 super(),否則(沒(méi)有使用 extends 的類(lèi)或函數(shù)中使用)一個(gè)錯(cuò)誤會(huì)被拋出。
- 你必須在構(gòu)造函數(shù)的起始位置調(diào)用 super(),因?yàn)樗鼤?huì)初始化 this。任何在 super() 之前訪(fǎng)問(wèn) this 的行為都會(huì)造成錯(cuò)誤。也即是說(shuō)super()必須放在構(gòu)造函數(shù)的首行。
- 在類(lèi)構(gòu)造函數(shù)中,唯一能避免調(diào)用 super() 的辦法是返回一個(gè)對(duì)象。
12.7.2 在子類(lèi)中屏蔽父類(lèi)的方法
如果在子類(lèi)中聲明與父類(lèi)中的同名的方法,則會(huì)覆蓋父類(lèi)的方法。(這種情況在其他語(yǔ)言中稱(chēng)之為 方法的覆寫(xiě)、重寫(xiě) )
<script type="text/javascript">
class Father{
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name);
}
}
class Son extends Father{ //extents后面跟表示要繼承的類(lèi)型
constructor(name, age){
super(name); //相當(dāng)于以前的:Father.call(this, name);
this.age = age;
}
//子類(lèi)獨(dú)有的方法
sayAge(){
console.log(this.age);
}
//子類(lèi)中的方法會(huì)屏蔽到父類(lèi)中的同名方法。
sayName(){
super.syaName(); //調(diào)用被覆蓋的父類(lèi)中的方法。
console.log("我是子類(lèi)的方法,我屏蔽了父類(lèi):" + name);
}
}
var son1 = new Son("李四", 30);
son1.sayAge();
son1.sayName();
</script>
如果在子類(lèi)中又確實(shí)需要調(diào)用父類(lèi)中被覆蓋的方法,可以通過(guò)super.方法()來(lái)完成。
注意:
- 如果是調(diào)用構(gòu)造方法,則super不要加點(diǎn),而且必須是在子類(lèi)構(gòu)造方法的第一行調(diào)用父類(lèi)的構(gòu)造方法
- 普通方法調(diào)用需要使用super.父類(lèi)的方法() 來(lái)調(diào)用。
12.7.3 靜態(tài)方法也可以繼承
<script type="text/javascript">
class Father{
static foo(){
console.log("我是父類(lèi)的靜態(tài)方法");
}
}
class Son extends Father{
}
Son.foo(); //子類(lèi)也繼承了父類(lèi)的靜態(tài)方法。 這種方式調(diào)用和直接通過(guò)父類(lèi)名調(diào)用時(shí)一樣的。
</script>
十三、Babel
到目前2017年為止,也不是所有的瀏覽器都支持ES6的特性。
所以我們需要把ES6轉(zhuǎn)換成ES5的代碼,就要用到所謂的轉(zhuǎn)碼器。Babel就是目前使用最廣泛的把ES6代碼轉(zhuǎn)換成ES5及以前代碼的轉(zhuǎn)碼器。
有了babel我們就可以放心的使用ES6的最新的語(yǔ)法,而不用擔(dān)心瀏覽器不支持了?。?!
為了方便使用,我直接使用
webstorm的filewatcher功能
13.1 安裝Babel
首先要保證電腦上已經(jīng)安裝了npm
使用如下命令就可以安裝babel安裝(我們這里使用的是全局安裝):
npm install -g babel-cli
13.2 給你當(dāng)前項(xiàng)目配置filewatcher
[圖片上傳失敗...(image-f74234-1636424176194)]
13.3 安裝babel-present-env
npm install babel-preset-env --save-dev
13.4 自動(dòng)轉(zhuǎn)換為es5代碼
js文件中的es6代碼會(huì)自動(dòng)轉(zhuǎn)換為es5代碼
[圖片上傳失敗...(image-cd9db4-1636424176195)]
十四、Moudle
JavaScript 采用 “共享一切” 的代碼加載方式是該語(yǔ)言中最令人迷惑且容易出錯(cuò)的方面之一。
其它語(yǔ)言使用包(package)的概念來(lái)定義代碼的作用范圍,然而在 ECMAScript 6 之前,每個(gè) JavaScript 文件中定義的內(nèi)容都由全局作用域共享。
當(dāng) web 應(yīng)用變得復(fù)雜并需要書(shū)寫(xiě)更多的 JavaScript 代碼時(shí),上述加載方式會(huì)出現(xiàn)命名沖突或安全方面的問(wèn)題。
ECMAScript 6 的目標(biāo)之一就是解決作用域的問(wèn)題并將 JavaScript 應(yīng)用中的代碼整理得更有條理,于是模塊應(yīng)運(yùn)而生。
很不幸的是:目前,所有的瀏覽器都還不能支持ES6的模塊。只能通過(guò)第三方的工具轉(zhuǎn)成ES5的代碼
14.1 什么是模塊
模塊是指采取不同于現(xiàn)有加載方式的 JavaScript 文件(與 script 這種傳統(tǒng)的加載模式相對(duì))。這種方式很有必要,因?yàn)樗?script 使用不同的語(yǔ)義:
- 模塊中的代碼自動(dòng)運(yùn)行在嚴(yán)格模式下,并無(wú)任何辦法修改為非嚴(yán)格模式。
- 模塊中的頂級(jí)(top level)變量不會(huì)被添加到全局作用域中。它們只存在于各自的模塊中的頂級(jí)作用域。
- 模塊頂級(jí)作用域中的 this 為 undefined 。
- 模塊不允許存在 HTML 式的注釋?zhuān)↗avaScript 歷史悠久的遺留特性)。
- 模塊必須輸出可被模塊外部代碼使用的相關(guān)內(nèi)容。
- 一個(gè)模塊可以引入另外的模塊。
14.2 導(dǎo)出模塊
可以使用 export 關(guān)鍵字來(lái)對(duì)外暴露模塊中的部分代碼。
一般情況下,可以在任何變量,函數(shù)或類(lèi)聲明之前添加這個(gè)關(guān)鍵字來(lái)輸出它們,
看下面的代碼:
聲明一個(gè)文件:a.js 代碼如下
// 輸出變量
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;
// 輸出函數(shù)
export function sum(num1, num2) {
return num1 + num1;
}
// 輸出類(lèi)
export class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
}
// 該函數(shù)沒(méi)有使用export關(guān)鍵字 所以該函數(shù)是模塊私有的。也就是說(shuō)只能在當(dāng)前文件訪(fǎng)問(wèn),出了這個(gè)文件就訪(fǎng)問(wèn)不到
function subtract(num1, num2) {
return num1 - num2;
}
// 定義一個(gè)函數(shù)...
function multiply(num1, num2) {
return num1 * num2;
}
// 可以把這個(gè)函數(shù)的引用導(dǎo)出。 和導(dǎo)出函數(shù)是一樣的。
export { multiply };
注意:在上面的代碼中,除了exprot關(guān)鍵字,其他和我們以前的代碼沒(méi)有任何不同。
14.3 引入模塊
一旦有了導(dǎo)出內(nèi)容的模塊,則可以在另一個(gè)模塊中使用import關(guān)鍵字來(lái)獲取他們。
引入模塊的語(yǔ)法:
import { identifier1, identifier2 } from "./a.js";
import 之后的花括號(hào)表示從模塊中引入的綁定。from 關(guān)鍵字表示從哪個(gè)模塊引入這些綁定。模塊由一個(gè)包含模塊路徑的字符串表示(稱(chēng)為模塊指示符,module sepcifier)。瀏覽器中的 <script> 元素也使用了這個(gè)路徑形式,意味著它必須包含文件擴(kuò)展名。