【讀書筆記】ES6 Class的基本語法
Class
- constructor方法
- 類的例項物件
- Class表示式
- 不存在變數提升
- 私有方法和私有屬性
- this的指向
- name屬性
- Class的取值函式和存值函式
- Class的Generator方法
- Class的靜態
- Class的靜態屬性和例項屬性
- new.target屬性
class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return'(' + this.x + ',' + this.y + ')'; } }
1.constructor是構造方法。
2.this關鍵字則代表例項物件。
3.定義“類”的方法的時候,前面不需要加上function關鍵字,直接把函式定義放進去了就可以了。
4.方法之間不需要逗號分隔。
class Point { } typeof Point // "function" Point === Point.prototype.constructor // true
5.類的資料型別就是函式,類本身就指向建構函式。
Point.protype = { constructor() {}, toString() {}, toValue() {} };
6.事實上,類的所有方法都定義在類的prototype屬性上面。
b.constructor === B.prototype.constructor // true
7.在類的例項上呼叫方法,其實就是呼叫原型上的方法。
Object.assign(Point.prototype, {
toString() {},
toValue() {}
});
8.Object.assign方法可以很方便地一次向類新增多個方法。
Point.prototype.constructor === Point //true
9.prototype物件的constructor屬性,直接指向“類”的本身。
Object.keys(Point.prototype); // [] Object.getOwnPropertyNames(Point.prototype); // ["constructor", "toString"]
10.類的內部所有定義的方法,都是不可列舉的。
let methodName = 'getArea';
class Square {
constructor(length) {
}
[methodName]() {
}
}
11.類的屬性名,可以採用表示式。
類和模組的內部,預設就是嚴格模式,所以不需要使用 use strict 指定執行模式。
只要你的程式碼寫在類或模組之中,就只有嚴格模式可用。
ES6實際上把整個語言升級到了嚴格模式。
constructor方法
1.constructor方法是類的預設方法,通過new命令生成物件例項時,自動呼叫該方法。
class Point { } // 等同於 class Point { constructor() {} }
2.一個類必須有constructor方法,如果沒有顯示定義,一個空的constructor方法會被預設新增。
class Foo { constructor() { return Object.create(null); } } new Foo() instanceof Foo // false
3.constructor方法預設返回例項物件,即this,完全可以指定返回另一個物件。
class Foo { constructor() { return Object.create(null); } } Foo(); // TypeError
4.類必須使用new呼叫,否則會報錯。
這是它跟普通建構函式的一個主要區別,後者不用new也可以執行。
類的例項物件
class Point { ... } var point = Point(2, 3); // 報錯 var point = new Point(2, 3); // 正確
1.生成類的例項物件的寫法,與ES5完全一樣,也是使用new命令。
class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' +this.x + ', ' + this.y + 'y'; } } var point = new Point(2, 3); point.toString(); // (2, 3) point.hasOwnProperty('x'); // true point.hasOwnProperty('y'); // true point.hasOwnProperty('toString'); // false point._proto_.hasOwnProperty('toString'); // true
2.與ES5一樣,例項的屬性除非顯式定義在其本身,即定義在this物件上,否則都是定義在原型上,即定義在class上。
hasOwnProperty用來檢測是不是例項的屬性。
var p1 = new Point(2, 3); var p2 = new Point(3, 2); p1._proto_ === p2._proto_ // ture
3.與ES5一樣,類的所有例項共享一個原型物件。
var p1 = new Point(2, 3); var p2 = new Point(3, 2); p1._proto_.printName = function() { return 'Oops' }; p1.printName() // "Oops" p2.printName() // "Oops" var p3 = new Point(4,2); p3.printName() // "Oops"
4.可以通過例項的_proto_屬性為“類”新增方法。
生產環境中,我們可以使用Object.getPrototypeOf方法來獲取例項物件的原型。
Class表示式
const MyClass = class Me { getClassName() { return Me.name; } };
let inst = new MyClass(); inst.getClassName(); // Me Me.name // ReferenceError
const MyClass = class {...};
1.與函式一樣,類也可以使用表示式的形式定義。
這個類的名字是MyClass而不是Me,Me只在Class的內部程式碼可用,指代當前類。
如果類的內部沒用到的話,可以省略Me。
let person = new class { constructor(name) { this.name = name; } sayName() { console.log(this.name); } }('張三'); person.sayName(); // "張三"
2.採用Class表示式,可以寫出立即執行的Class。
不存在變數提升
new Foo(); // ReferenceError class Foo{}
1.類不存在變數提升。
{ let Foo = class {}; class Bar extends Foo {} }
2.必須保證子類在父類之後定義。
私有方法和私有屬性
class Widget { // 公有方法 foo(baz) { this._bar(baz); } // 私有方法 _bar(baz) { return this.snaf = baz; } }
1.私有方法是常見需求,但ES6不提供,只能通過變通方法模擬實現。
一種做法是在命名上加以區別。
_bar方法前面的下劃線,表示這是一個只限於內部使用的私有方法。
class Widget { foo(baz) { bar.call(this, baz); } ... } function bar(baz) { return this.snaf = baz; }
2.另一種方法就是索性將私有方法移除模組,因為模組內部的所有方法都是對外可見的。
const bar = Symbol('bar'); const snaf = Symbol('snaf'); export default class myClass { // 公有方法 foo(baz) { this[bar](baz); } // 私有方法 [bar](baz) { return this[snaf] = baz; } ... }
3.還有一種方法就是利用Symbol值的唯一性,將私有方法的名字命名為一個Symbol值。
this的指向
class Logger { printName(name = 'there') { this.print(`Hello ${name}`); } print(text) { console.log(text); } } const logger = new Logger(); const { printName } = logger; printName(); // TypeError
1.類的方法內部如果含有this,它預設指向類的例項。
但是,必須非常小心,一點單獨使用該方法,很可能報錯。
如果將這個方法提取出來單獨使用,this會指向該方法執行時所作的環境,因為找不到print方法而導致報錯。
class Logger { constructor() { this.printName = this.printName.bind(this); } ... }
2.一個比較簡單的解決方法是,在構造方法中繫結this,這樣就不會找不到print方法了。
class Logger { constructor() { this.printName = (name = 'there') => { this.print(`Hellow ${name}`); }; } ... }
3.另一種解決方法是使用箭頭函式。
function selfish(target) { const cache = new WeakMap(); const handler = { get(target, key) { const value = Refkect.get(target, key); if (typeof value !== 'function') { return value; } if (!cache.has(value)) { cache.set(value, value.bind(target)); } return cache.get(value); } }; const proxy = new Proxy(target, handler); return proxy } const logger = selfish(new Logger());
4.還有一種解決方法是使用Proxy,獲取方法的時候,自動繫結this。
name屬性
class Point {} Point.name // "Point"
1.由於本質上,ES6的類只是ES5的建構函式的一層包裝,所以函式的許多特性都被Class繼承,包括name屬性。
name屬性總是返回緊跟在class關鍵字後面的類名。
Class的取值函式和存值函式
class MyClass { constructor() { ... } get prop() { return 'getter'; } set prop(value) { console.log('setter: ' + value); } } let inst = new MyClass(); inst.prop = 123; // setter: 123 inst.prop // 'getter'
1.與ES5一樣,在“類”的內部可以使用get和set關鍵字,對某個屬性設定存值函式和取值函式,攔截該屬性的存取行為。
上面程式碼中,prop屬性有對應的存值函式和取值函式,因此賦值和讀取行為都被自定義了。
class CustomHTMLElement { constructor(element) { this.element = element; } get html() { return this.element.innerHTML; } set html(value) { this.element.innerHTML = value; } } var decriptor = Object.getOwnPropertyDescroptor(CustomHTMLElement.prototype, "html"); "get" in decriptor // true "set" in decriptor // true
2.存值函式和取值函式是設定在屬性的Descriptor物件上的。
上面程式碼中,存值函式和取值函式是定義在html屬性的描述物件上面。這與ES5完全一致。
Class的 Generator 方法
class Foo { constructor(...args) { this.args = args; } * [Symbol.iterator]() { for (let arg of this.args) { yield arg; } } } for (let x of new Foo('hello', 'world')) { console.log(x); } // hello // world
1.如果某個方法之前加上星號(*),就表示該方法是一個 Generator 函式。
Foo類的Symbol.iterator方法前有一個星號,表示該方法是一個Generator函式。
Symbol.iterator方法返回一個Foo類的預設遍歷器,for...of 迴圈會自動呼叫這個遍歷器。
Class的靜態方法
class Foo { static classMethod() { return 'hello'; } } Foo.classMethod(); // "hello" var foo = new Foo(); foo.classMethod(); // TypeError
1.類相當於例項的原型,所有在類中定義的方法,都會被例項繼承。
如果在一個方法前,加上static關鍵字,就表示該方法不會被例項繼承,而是直接通過類來呼叫,這就稱為“靜態方法”。
class Foo { static bar() { this.baz(); } statoc baz() { console.log('hello'); } baz() { console.log('world'); } } Foo.bar(); // hello
2.如果靜態方法包含this關鍵字,這個this指的是類,而不是例項。
另外,從這個例子還可以看出,靜態方法可以與非靜態方法重名。
class Foo { static classMethod() { return 'hello'; } } class Bar extends Foo { } Bar.classMethod(); // 'hello'
3.父類的靜態方法,可以被子類繼承。
class Foo { static classMethod() { return 'hello'; } } class Bar extends Foo { static classMethod() { return super.classMethod() + ', too'; } } Bar.classMehtod(); // "hello, too"
4.靜態方法也是可以從super物件上呼叫的。
Class的靜態屬性和例項屬性
class Foo { } Foo.prop = 1; Foo.prop // 1
// 以下兩種寫法都無效 class Foo { // 寫法一 prop: 2 // 寫法二 static prop: 2 } Foo.prop // undefined
1.靜態屬性指的是Class本身的屬性,即Class.propName,而不是定義在例項物件(this)上的屬性。
目前,只有這種寫法可行,因為ES6明確規定,Class內部只有靜態方法,沒有靜態屬性。
class ReactCounter extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } }
2.以前,我們定義例項屬性,只能寫在類的constructor方法裡面。
new.target屬性
function Person(name) { if (new.target !== undefined) { this.name = name; } else { throw new Eooro('必須使用new命令生成例項'); } } // 另一種寫法 function Person(name) { if (new.target === Person) { this.name = name; } else { throw new Error(''); } } var person = new Person('張三'); // 正確 var notAPerson = Person.call(person, '張三'); // 報錯
1.new是從建構函式生成例項物件的命令。
ES6為new命令引入了一個new.target屬性,該屬性一般用在建構函式之中,返回new命令作用於的那個建構函式。
如果建構函式不是通過new命令呼叫的,new.target會返回undefined,因此這個屬性可以用來確定建構函式是怎麼呼叫的。
class Rectangle { constructor(lenght, width) { console.log(new.target === Rectangle); this.length = length; this.width = width; } } var obj = new Rectangle(3, 4); // true
class Rectangle { constructor(length, width) { console.log(new.target === Rectangle); ... } } class Square extends Rectangle { constructor(length) { super(legnth, length); } } var obj = new Square(3); // false
2.Class內部呼叫new.target,返回當前Class。
需要注意的是,子類繼承父類時,new.target會返回子類。
class Shape { constructor() { if (new.target === Shape) { throw new Error('本類不能例項化'); } } } class Rectangle extends Shape { constructor(length, width) { super(); ... } } var x = new Shape(); // 報錯 var y = new Rectangle(3, 4); // 正確
3.利用這個特點,可以寫出不能獨立使用、必須繼承後才能使用的類。
注意,在函式外部,使用new.target會報錯。