W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎勵
在面向?qū)ο蟮木幊讨校琧lass 是用于創(chuàng)建對象的可擴(kuò)展的程序代碼模版,它為對象提供了狀態(tài)(成員變量)的初始值和行為(成員函數(shù)或方法)的實(shí)現(xiàn)。
在日常開發(fā)中,我們經(jīng)常需要創(chuàng)建許多相同類型的對象,例如用戶(users)、商品(goods)或者任何其他東西。
正如我們在 構(gòu)造器和操作符 "new" 一章中已經(jīng)學(xué)到的,new function
可以幫助我們實(shí)現(xiàn)這種需求。
但在現(xiàn)代 JavaScript 中,還有一個更高級的“類(class)”構(gòu)造方式,它引入許多非常棒的新功能,這些功能對于面向?qū)ο缶幊毯苡杏谩?/p>
基本語法是:
class MyClass {
// class 方法
constructor() { ... }
method1() { ... }
method2() { ... }
method3() { ... }
...
}
然后使用 new MyClass()
來創(chuàng)建具有上述列出的所有方法的新對象。
new
會自動調(diào)用 constructor()
方法,因此我們可以在 constructor()
中初始化對象。
例如:
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
// 用法:
let user = new User("John");
user.sayHi();
當(dāng) new User("John")
被調(diào)用:
constructor
? 使用給定的參數(shù)運(yùn)行,并將其賦值給 ?this.name
?。……然后我們就可以調(diào)用對象方法了,例如 user.sayHi
。
類的方法之間沒有逗號
對于新手開發(fā)人員來說,常見的陷阱是在類的方法之間放置逗號,這會導(dǎo)致語法錯誤。
不要把這里的符號與對象字面量相混淆。在類中,不需要逗號。
所以,class
到底是什么?正如人們可能認(rèn)為的那樣,這不是一個全新的語言級實(shí)體。
讓我們揭開其神秘面紗,看看類究竟是什么。這將有助于我們理解許多復(fù)雜的方面。
在 JavaScript 中,類是一種函數(shù)。
看看下面這段代碼:
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// 佐證:User 是一個函數(shù)
alert(typeof User); // function
class User {...}
構(gòu)造實(shí)際上做了如下的事兒:
User
? 的函數(shù),該函數(shù)成為類聲明的結(jié)果。該函數(shù)的代碼來自于 ?constructor
? 方法(如果我們不編寫這種方法,那么它就被假定為空)。User.prototype
? 中的 ?sayHi
?。當(dāng) new User
對象被創(chuàng)建后,當(dāng)我們調(diào)用其方法時,它會從原型中獲取對應(yīng)的方法,正如我們在 F.prototype 一章中所講的那樣。因此,對象 new User
可以訪問類中的方法。
我們可以將 class User
聲明的結(jié)果解釋為:
下面這些代碼很好地解釋了它們:
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// class 是一個函數(shù)
alert(typeof User); // function
// ...或者,更確切地說,是 constructor 方法
alert(User === User.prototype.constructor); // true
// 方法在 User.prototype 中,例如:
alert(User.prototype.sayHi); // sayHi 方法的代碼
// 在原型中實(shí)際上有兩個方法
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
人們常說 class
是一個語法糖(旨在使內(nèi)容更易閱讀,但不引入任何新內(nèi)容的語法),因?yàn)槲覀儗?shí)際上可以在不使用 class
的情況下聲明相同的內(nèi)容:
// 用純函數(shù)重寫 class User
// 1. 創(chuàng)建構(gòu)造器函數(shù)
function User(name) {
this.name = name;
}
// 函數(shù)的原型(prototype)默認(rèn)具有 "constructor" 屬性,
// 所以,我們不需要創(chuàng)建它
// 2. 將方法添加到原型
User.prototype.sayHi = function() {
alert(this.name);
};
// 用法:
let user = new User("John");
user.sayHi();
這個定義的結(jié)果與使用類得到的結(jié)果基本相同。因此,這確實(shí)是將 class
視為一種定義構(gòu)造器及其原型方法的語法糖的理由。
盡管,它們之間存在著重大差異:
class
創(chuàng)建的函數(shù)具有特殊的內(nèi)部屬性標(biāo)記 [[IsClassConstructor]]: true
。因此,它與手動創(chuàng)建并不完全相同。編程語言會在許多地方檢查該屬性。例如,與普通函數(shù)不同,必須使用 new
來調(diào)用它:
class User {
constructor() {}
}
alert(typeof User); // function
User(); // Error: Class constructor User cannot be invoked without 'new'
此外,大多數(shù) JavaScript 引擎中的類構(gòu)造器的字符串表示形式都以 “class…” 開頭
class User {
constructor() {}
}
alert(User); // class User { ... }
還有其他的不同之處,我們很快就會看到。
"prototype"
中的所有方法的 enumerable
標(biāo)志設(shè)置為 false
。這很好,因?yàn)槿绻覀儗σ粋€對象調(diào)用 for..in
方法,我們通常不希望 class 方法出現(xiàn)。
use strict
?。 在類構(gòu)造中的所有代碼都將自動進(jìn)入嚴(yán)格模式。此外,class
語法還帶來了許多其他功能,我們稍后將會探索它們。
就像函數(shù)一樣,類可以在另外一個表達(dá)式中被定義,被傳遞,被返回,被賦值等。
這是一個類表達(dá)式的例子:
let User = class {
sayHi() {
alert("Hello");
}
};
類似于命名函數(shù)表達(dá)式(Named Function Expressions),類表達(dá)式可能也應(yīng)該有一個名字。
如果類表達(dá)式有名字,那么該名字僅在類內(nèi)部可見:
// “命名類表達(dá)式(Named Class Expression)”
// (規(guī)范中沒有這樣的術(shù)語,但是它和命名函數(shù)表達(dá)式類似)
let User = class MyClass {
sayHi() {
alert(MyClass); // MyClass 這個名字僅在類內(nèi)部可見
}
};
new User().sayHi(); // 正常運(yùn)行,顯示 MyClass 中定義的內(nèi)容
alert(MyClass); // error,MyClass 在外部不可見
我們甚至可以動態(tài)地“按需”創(chuàng)建類,就像這樣:
function makeClass(phrase) {
// 聲明一個類并返回它
return class {
sayHi() {
alert(phrase);
}
};
}
// 創(chuàng)建一個新的類
let User = makeClass("Hello");
new User().sayHi(); // Hello
就像對象字面量,類可能包括 getters/setters,計算屬性(computed properties)等。
這是一個使用 get/set
實(shí)現(xiàn) user.name
的示例:
class User {
constructor(name) {
// 調(diào)用 setter
this.name = name;
}
get name() {
return this._name;
}
set name(value) {
if (value.length < 4) {
alert("Name is too short.");
return;
}
this._name = value;
}
}
let user = new User("John");
alert(user.name); // John
user = new User(""); // Name is too short.
從技術(shù)上來講,這樣的類聲明可以通過在 User.prototype
中創(chuàng)建 getters 和 setters 來實(shí)現(xiàn)。
這里有一個使用中括號 ?[...]
? 的計算方法名稱示例:
class User {
['say' + 'Hi']() {
alert("Hello");
}
}
new User().sayHi();
這種特性很容易記住,因?yàn)樗鼈兒蛯ο笞置媪款愃啤?
舊的瀏覽器可能需要 polyfill
類字段(field)是最近才添加到語言中的。
之前,我們的類僅具有方法。
“類字段”是一種允許添加任何屬性的語法。
例如,讓我們在 class User
中添加一個 name
屬性:
class User {
name = "John";
sayHi() {
alert(`Hello, ${this.name}!`);
}
}
new User().sayHi(); // Hello, John!
所以,我們就只需在表達(dá)式中寫 "
類字段重要的不同之處在于,它們會在每個獨(dú)立對象中被設(shè)好,而不是設(shè)在 User.prototype
:
class User {
name = "John";
}
let user = new User();
alert(user.name); // John
alert(User.prototype.name); // undefined
我們也可以在賦值時使用更復(fù)雜的表達(dá)式和函數(shù)調(diào)用:
class User {
name = prompt("Name, please?", "John");
}
let user = new User();
alert(user.name); // John
正如 函數(shù)綁定 一章中所講的,JavaScript 中的函數(shù)具有動態(tài)的 this
。它取決于調(diào)用上下文。
因此,如果一個對象方法被傳遞到某處,或者在另一個上下文中被調(diào)用,則 this
將不再是對其對象的引用。
例如,此代碼將顯示 undefined
:
class Button {
constructor(value) {
this.value = value;
}
click() {
alert(this.value);
}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // undefined
這個問題被稱為“丟失 this
”。
我們在 函數(shù)綁定 一章中講過,有兩種可以修復(fù)它的方式:
setTimeout(() => button.click(), 1000)
?。類字段提供了另一種非常優(yōu)雅的語法:
class Button {
constructor(value) {
this.value = value;
}
click = () => {
alert(this.value);
}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // hello
類字段 click = () => {...}
是基于每一個對象被創(chuàng)建的,在這里對于每一個 Button
對象都有一個獨(dú)立的方法,在內(nèi)部都有一個指向此對象的 this
。我們可以把 button.click
傳遞到任何地方,而且 this
的值總是正確的。
在瀏覽器環(huán)境中,它對于進(jìn)行事件監(jiān)聽尤為有用。
基本的類語法看起來像這樣:
class MyClass {
prop = value; // 屬性
constructor(...) { // 構(gòu)造器
// ...
}
method(...) {} // method
get something(...) {} // getter 方法
set something(...) {} // setter 方法
[Symbol.iterator]() {} // 有計算名稱(computed name)的方法(此處為 symbol)
// ...
}
技術(shù)上來說,MyClass
是一個函數(shù)(我們提供作為 constructor
的那個),而 methods、getters 和 setters 都被寫入了 MyClass.prototype
。
在下一章,我們將會進(jìn)一步學(xué)習(xí)類的相關(guān)知識,包括繼承和其他功能。
?Clock
? 類是以函數(shù)式編寫的。請以 “class” 語法重寫它。
P.S. 時鐘在控制臺(console)中滴答,打開控制臺即可查看。
function Clock({ template }) {
let timer;
function render() {
let date = new Date();
let hours = date.getHours();
if (hours < 10) hours = '0' + hours;
let mins = date.getMinutes();
if (mins < 10) mins = '0' + mins;
let secs = date.getSeconds();
if (secs < 10) secs = '0' + secs;
let output = template
.replace('h', hours)
.replace('m', mins)
.replace('s', secs);
console.log(output);
}
this.stop = function() {
clearInterval(timer);
};
this.start = function() {
render();
timer = setInterval(render, 1000);
};
}
let clock = new Clock({template: 'h:m:s'});
clock.start();
class Clock {
constructor({ template }) {
this.template = template;
}
render() {
let date = new Date();
let hours = date.getHours();
if (hours < 10) hours = '0' + hours;
let mins = date.getMinutes();
if (mins < 10) mins = '0' + mins;
let secs = date.getSeconds();
if (secs < 10) secs = '0' + secs;
let output = this.template
.replace('h', hours)
.replace('m', mins)
.replace('s', secs);
console.log(output);
}
stop() {
clearInterval(this.timer);
}
start() {
this.render();
this.timer = setInterval(() => this.render(), 1000);
}
}
let clock = new Clock({template: 'h:m:s'});
clock.start();
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: