1. 程式人生 > >JS6-Class

JS6-Class

方便 c++ ons ref 表達式 fin 繼承 當前 父類

====Class(類)

Js中,生成實例對象的傳統方法是通過構造函數

function Point(x, y) {

this.x = x;

this.y = y;

}

var p = new Point(1, 2);

上面這種寫法跟傳統的面向對象語言(比如 C++ 和 Java)差異很大

ES6 提供了更接近傳統語言的寫法,引入了 Class(類)這個概念,作為對象的模板。通過class關鍵字,可以定義類。

//定義類

class Point {

constructor(x, y) {

this.x = x;

this.y = y;

}

toString() {

return ‘(‘ + this.x + ‘, ‘ + this.y + ‘)‘;

}

}

//創建對象

var p=new Point(1,2)

console.log(p.x)

上面代碼定義了一個“類”,可以看到裏面有一個constructor方法,這就是構造方法,而this關鍵字則代表實例對象。也就是說,ES5 的構造函數Point,對應 ES6 的Point類的構造方法。

Point類除了構造方法,還定義了一個toString方法。註意,定義“類”的方法的時候,前面不需要加上function這個關鍵字,直接把函數定義放進去了就可以了。另外,方法之間不需要逗號分隔,加了會報錯。

ES6 的類,完全可以看作構造函數的另一種寫法。

class Point {

// ...

}

typeof Point // "function"

Point === Point.prototype.constructor // true

上面代碼表明,類的數據類型就是函數,類本身就指向構造函數。

使用的時候,也是直接對類使用new命令,跟構造函數的用法完全一致。

class Bar {

doStuff() {

console.log(‘stuff‘);

}

}

var b = new Bar();

b.doStuff() // "stuff"

構造函數的prototype屬性,在 ES6 的“類”上面繼續存在。事實上,類的所有方法都定義在類的prototype屬性上面。

class Point {

constructor() {

// ...

}

toString() {

// ...

}

toValue() {

// ...

}

}

// 等同於

Point.prototype = {

constructor() {},

toString() {},

toValue() {},

};

在類的實例上面調用方法,其實就是調用原型上的方法。

class B {}

let b = new B();

b.constructor === B.prototype.constructor // true

上面代碼中,b是B類的實例,它的constructor方法就是B類原型的constructor方法。

由於類的方法都定義在prototype對象上面,所以類的新方法可以添加在prototype對象上面。Object.assign方法可以很方便地一次向類添加多個方法。

class Point {

constructor(){

// ...

}

}

Object.assign(Point.prototype, {

toString(){},

toValue(){}

});

類必須使用new調用,否則會報錯。這是它跟普通構造函數的一個主要區別,ES5不用new也可以執行。

class Foo {

constructor() {

return 666;

}

}

Foo()// TypeError: Class constructor Foo cannot be invoked without ‘new‘

類的實例對象

生成類的實例對象的寫法,與 ES5 完全一樣,也是使用new命令。如果忘記加上new,像函數那樣調用Class,將會報錯。

class Point {

// ...

}

// 報錯

var point = Point(2, 3);

// 正確

var point = new Point(2, 3);

與 ES5 一樣,類的所有實例共享一個原型對象。

var p1 = new Point(2,3);

var p2 = new Point(3,2);

p1.__proto__ === p2.__proto__//true

上面代碼中,p1和p2都是Point的實例,它們的原型都是Point.prototype,所以__proto__屬性是相等的。

這也意味著,可以通過實例的__proto__屬性為“類”添加方法。

__proto__ 並不是語言本身的特性,這是各大廠商具體實現時添加的私有屬性,雖然目前很多現代瀏覽器的JS引擎中都提供了這個屬性,但不建議在生產中使用該屬性:

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"

上面代碼在p1的原型上添加了一個printName方法,由於p1的原型就是p2的原型,因此p2也可以調用這個方法。而且,此後新建的實例p3也可以調用這個方法。這意味著,使用實例的__proto__屬性改寫原型,必須相當謹慎,不推薦使用,因為這會改變“類”的原始定義,影響到所有實例。

Class 表達式

與函數一樣,類也可以使用表達式的形式定義。

const MyClass = class Me {

getClassName() {

return Me.name;

}

};

上面代碼使用表達式定義了一個類。需要註意的是,這個類的名字是MyClass而不是Me,Me只在 Class 的內部代碼可用,指代當前類。

let inst = new MyClass();

Me.name // ReferenceError: Me is not defined

上面代碼表示,Me只在 Class 內部有定義。

類也可以寫成下面的表達式形式。

const MyClass = class { };

采用 Class 表達式,可以寫出立即執行的 Class。

let person = new class {

constructor(name) {

this.name = name;

}

sayName() {

console.log(this.name);

}

}(‘張三‘);

person.sayName(); // "張三"

上面代碼中,person是一個立即執行的類的實例。

不存在變量提升

類不存在變量提升,與 ES5 完全不同。

new Foo(); // ReferenceError

class Foo {}

上面代碼中,Foo類使用在前,定義在後,這樣會報錯,因為 ES6 不會把類的聲明提升到代碼頭部。這種規定的原因與繼承有關,必須保證子類在父類之後定義。

{

let Foo = class {};

class Bar extends Foo {

}

}

上面的代碼不會報錯,因為Bar繼承Foo的時候,Foo已經有定義了。但是,如果存在class的提升,上面代碼就會報錯,因為class會被提升到代碼頭部,而let命令是不提升的,所以導致Bar繼承Foo的時候,Foo還沒有定義。

this 的指向

類的方法內部如果含有this,它默認指向類的實例。但是,必須非常小心,一旦單獨使用該方法,很可能報錯。

class Logger {

printName(name = ‘there‘) {

this.print(`Hello ${name}`);

}

print(text) {

console.log(text);

}

}

const logger = new Logger();

const { printName } = logger;

printName(); // TypeError: Cannot read property ‘print‘ of undefined

上面代碼中,printName方法中的this,默認指向Logger類的實例。但是,如果將這個方法提取出來單獨使用,this會指向該方法運行時所在的環境,因為找不到print方法而導致報錯。

解決方法:寫在構造函數constructor裏。

class Logger {

constructor() {

this.printName = (name = ‘there‘) => {

this.print(`Hello ${name}`);

};

}

}

Class 的取值函數(getter)和存值函數(setter)

在“類”的內部可以使用get和set關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行為。

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‘

上面代碼中,prop屬性有對應的存值函數和取值函數,因此賦值和讀取行為都被自定義了。

Class 的靜態方法

類相當於實例的原型,所有在類中定義的方法,都會被實例繼承。如果在一個方法前,加上static關鍵字,就表示該方法不會被實例繼承,而是直接通過類來調用,這就稱為“靜態方法”。

class Foo {

static classMethod() {

return ‘hello‘;

}

}

Foo.classMethod() // ‘hello‘

var foo = new Foo();

foo.classMethod()// TypeError: foo.classMethod is not a function

上面代碼中,Foo類的classMethod方法前有static關鍵字,表明該方法是一個靜態方法,可以直接在Foo類上調用(Foo.classMethod()),而不是在Foo類的實例上調用。如果在實例上調用靜態方法,會拋出一個錯誤,表示不存在該方法。

父類的靜態方法,可以被子類繼承。

class Foo {

static classMethod() {

return ‘hello‘;

}

}

class Bar extends Foo {

}

Bar.classMethod() // ‘hello‘

上面代碼中,父類Foo有一個靜態方法,子類Bar可以調用這個方法。

Class 的靜態屬性和實例屬性

靜態屬性指的是 Class 本身的屬性,即Class.propName,而不是定義在實例對象(this)上的屬性。

class Foo {

}

Foo.prop = 1;

Foo.prop // 1

上面的寫法為Foo類定義了一個靜態屬性prop。

目前,只有這種寫法可行,因為 ES6 明確規定,Class 內部只有靜態方法,沒有靜態屬性。

// 以下兩種寫法都無效

class Foo {

// 寫法一

prop: 2

// 寫法二

static prop: 2

}

Foo.prop // undefined

目前有一個靜態屬性的提案,對實例屬性和靜態屬性都規定了新的寫法:

類的實例屬性

類的實例屬性可以用等式,寫入類的定義之中。

以前,我們定義實例屬性,只能寫在類的constructor方法裏面。有了新的寫法以後,可以不在constructor方法裏面定義。

class MyClass {

myProp = 42;

constructor() {

console.log(this.myProp); // 42

}

}

上面代碼中,myProp就是MyClass的實例屬性。在MyClass的實例上,可以讀取這個屬性。

這種寫法比以前更清晰:

class Person{

it= {

count: 3

};

}

為了可讀性的目的,對於那些在constructor裏面已經定義的實例屬性,新寫法允許直接列出:

class Person{

state;

constructor() {

this.state = {

count: 0

};

}

}

類的靜態屬性

類的靜態屬性只要在上面的實例屬性寫法前面,加上static關鍵字就可以了。這個新寫法大大方便了靜態屬性的表達。

// 老寫法

class Foo {

// ...

}

Foo.prop = 1;

// 新寫法

class Foo {

static prop = 1;

}

上面代碼中,老寫法的靜態屬性定義在類的外部。整個類生成以後,再生成靜態屬性。這樣讓人很容易忽略這個靜態屬性,也不符合相關代碼應該放在一起的代碼組織原則。

new.target屬性

new是從構造函數生成實例的命令。ES6 為new命令引入了一個new.target屬性,該屬性一般用在在構造函數之中,返回new命令作用於的那個構造函數。如果構造函數不是通過new命令調用的,new.target會返回undefined,因此這個屬性可以用來確定構造函數是怎麽調用的。

function Person(name) {

if (new.target !== undefined) {

this.name = name;

} else {

throw new Error(‘必須使用new生成實例‘);

}

}

// 另一種寫法

function Person(name) {

if (new.target === Person) {

this.name = name;

} else {

throw new Error(‘必須使用 new 生成實例‘);

}

}

var person = new Person(‘張三‘); // 正確

var notAPerson = Person(‘張三‘); // 報錯

上面代碼確保構造函數只能通過new命令調用。

JS6-Class