摘要 阮一峰《 ECMAScript 6 入門(mén) 》
1. Class
1.1 class的定義
// 定義一個(gè)Animal的類(lèi)(構(gòu)造函數(shù))
class Animal{
constructor(){
this.type = 'animal';
}
says(say){
console.log(this.type + ' says ' + say);
}
}
let animal = new Animal();
animal.says('hello'); // animal says hello
class Cat extends Animal{
constructor(){
super();
this.type = 'cat';
}
}
let cat = new Cat();
cat.says('hello'); // cat says hello
上面的代碼首先用class定義了一個(gè)"類(lèi)"(構(gòu)造函數(shù)),可以看到里面有個(gè)constructor方法,這就是構(gòu)造方法,而this則代表實(shí)例對(duì)象。簡(jiǎn)單的說(shuō),constructor里面的屬性和方法是實(shí)例對(duì)象自己的,而constructor外定義的方法和屬性則是所有實(shí)例對(duì)象所共享的。另外,方法之間不需要逗號(hào)分隔,加了會(huì)報(bào)錯(cuò)。
class之間是可以通過(guò)extends關(guān)鍵字實(shí)現(xiàn)繼承,這比ES5通過(guò)修改原型而清晰和方便很多。上面定義了一個(gè)Cat類(lèi),該類(lèi)通過(guò)extends關(guān)鍵字,繼承了Animal的所有方法和屬性。
super關(guān)鍵字,它代表父類(lèi)的實(shí)例,(即父類(lèi)的this對(duì)象)。子類(lèi)必須在constructor方法中調(diào)用super方法,否則新建的實(shí)例會(huì)報(bào)錯(cuò)。這是因?yàn)樽宇?lèi)沒(méi)有自己的實(shí)例對(duì)象,而是繼承父類(lèi)的實(shí)例對(duì)象,然后對(duì)其進(jìn)行加工。如果不調(diào)用super方法,子類(lèi)就得不到this對(duì)象。
ES6的繼承機(jī)制,實(shí)質(zhì)是先構(gòu)建父類(lèi)的實(shí)例對(duì)象this(所以必須要有super方法),然后再用子類(lèi)的構(gòu)造函數(shù)修改this。
ES6的類(lèi),完全可以看作構(gòu)造函數(shù)的另一種寫(xiě)法。
class Animal{
// ...
}
typeof Animal // "function"
Animale === Animal.prototype.constructor // true
上面的代碼表明,類(lèi)本身就是構(gòu)造函數(shù)。
與ES5一樣,實(shí)例的屬性除非顯示定義在本身(即定義在this上),否則都是定義在原型上。(即定義在class上)。
animal.hasOwnProperty('type'); // true
animal.hasOwnProperty('says'); // false
animal.__proto__.hasOwnProperty('says'); // true
1.2. class表達(dá)式
const MyClass = class Me{
getClassName(){
return Me.name;
}
上面代碼使用了表達(dá)式定義了一個(gè)類(lèi)。需要特別指出的是,這個(gè)類(lèi)的名字是MyClass而不是Me,Me只能在內(nèi)部使用,代指當(dāng)前類(lèi)。
let inst = new MyClass();
inst.getClassName(); // Me
Me.name; // ReferenceError: Me is not defined
1.3. super關(guān)鍵字的詳解(繼承)
super這個(gè)關(guān)鍵字,既可以當(dāng)做函數(shù)使用,也可以當(dāng)做對(duì)象使用。這兩種情況的使用完全不同。
第一種情況,super當(dāng)做函數(shù)調(diào)用時(shí),代表的是父類(lèi)的構(gòu)造函數(shù)。ES6要求,子類(lèi)的構(gòu)造函數(shù)必須執(zhí)行一次super()函數(shù)。
class A {}
class B extends A{
constructor(){
super();
}
}
上面代碼中,子類(lèi)B的構(gòu)造函數(shù)中的super(),代表調(diào)用父類(lèi)的構(gòu)造函數(shù)。這是必須的,否則報(bào)錯(cuò)。
注意:super雖然代表了父類(lèi)的構(gòu)造函數(shù)A,但是返回的是子類(lèi)的實(shí)例,即super內(nèi)部的this指向B,因此super()在這里就相當(dāng)于A.prototype.constructor.call(this)。
class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
new A() // A
new B() // B
上面代碼中,new.target指向當(dāng)前正在執(zhí)行的函數(shù)??梢钥吹?,在super()執(zhí)行時(shí),它指向的是子類(lèi)B的構(gòu)造函數(shù),而不是父類(lèi)A的構(gòu)造函數(shù)。也就是說(shuō),super()內(nèi)部的this指向B。
第二種情況,super作為對(duì)象時(shí),在普通的方法中,指向父類(lèi)的原型對(duì)象;在靜態(tài)方法中,指向父類(lèi)。
class A {
p(){
return 2;
}
}
class B extends A{
constructor(){
super();
console.log(super.p()); // 2
}
}
上面代碼中,子類(lèi)B中super.p(),就相當(dāng)于一個(gè)對(duì)象使用。這時(shí),super在普通方法中,指向A.prototype,super.p()就相當(dāng)于A.prototype.p()。
這里需要注意,由于super指向父類(lèi)的原型對(duì)象,所以定義在父類(lèi)實(shí)例上的方法或?qū)傩?,是無(wú)法通過(guò)super調(diào)用的。
class A {
constructor() {
this.p = 2;
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B();
b.m // undefined
上面代碼中,p是父類(lèi)A實(shí)例的屬性,super.p就引用不到它。
如果屬性定義在父類(lèi)的原型對(duì)象上,super就可以取到。
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
let b = new B();
上面代碼中,由于綁定子類(lèi)的this,所以如果通過(guò)super對(duì)某個(gè)屬性賦值,這時(shí)super就是this,賦值的屬性會(huì)變成子類(lèi)實(shí)例的屬性。super.x賦值為3,這時(shí)等同于對(duì)this.x賦值為3。而當(dāng)讀取super.x的時(shí)候,讀的是A.prototype.x,所以返回undefined。
如果super作為對(duì)象,用在靜態(tài)方法之中,這時(shí)super將指向父類(lèi),而不是父類(lèi)的原型對(duì)象。
class Parent {
static myMethod(msg) {
console.log('static', msg);
}
myMethod(msg) {
console.log('instance', msg);
}
}
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg);
}
myMethod(msg) {
super.myMethod(msg);
}
}
Child.myMethod(1); // static 1
var child = new Child();
child.myMethod(2); // instance 2
上面代碼中,super在靜態(tài)方法之中指向父類(lèi),在普通方法之中指向父類(lèi)的原型對(duì)象。
2. 字符串的擴(kuò)展
2.1 for of
for(let k of 'abc'){
console.log(k); // a b c
}
2.2 includes(),startsWith(),endsWith()
傳統(tǒng)上,JS之后indexof,可以用來(lái)確定一個(gè)字符串是否在另一個(gè)字符串中。ES6又提供了三種方法。
includes():返回布爾值,表示是否找到了參數(shù)字符串。
startsWith():返回布爾值,表示參數(shù)字符串是否在源字符串的開(kāi)頭。
endsWith():返回布爾值,表示參數(shù)字符串是否在源字符串的結(jié)尾。
這三個(gè)方法都支持第二個(gè)參數(shù),表示開(kāi)始搜索的位置。
var s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
上面代碼表示,使用第二個(gè)參數(shù)n時(shí),endsWith的行為與其他兩個(gè)方法有所不同。它針對(duì)前n個(gè)字符,而其他兩個(gè)方法針對(duì)從第n個(gè)位置直到字符串結(jié)束。