Class constructor() 實例對象 表達式 提升 私有方法和私有屬性 this name屬性 取值函數和存值函數 Generator方法 靜態方法 靜態屬性和實例屬性 new.target屬性 1.constructor是構造方法。 2.this關鍵字則代表實例對象。 3.定義“類”的 ...
Class
- constructor()
- 實例對象
- 表達式
- 提升
- 私有方法和私有屬性
- this
- name屬性
- 取值函數和存值函數
- Generator方法
- 靜態方法
- 靜態屬性和實例屬性
- 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方法來獲取實例對象的原型。
表達式
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 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完全一致。
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 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 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會報錯。