javascript中的class
start
- 最近在學習:cocos ,準備自己制作小游戲。過程中遇到不少疑問,我計劃將這些疑問寫成一個系列博客,用以記錄。
- 這篇文章來了解
class
1. 前言
1. 前言
- 本文對應版本
Cocos Creator 3.8
。 Cocos Creator3.x
編寫腳本的語言是TypeScript
,在了解TypeScript
中的語法之前,我們先掌握javascript
中的class
。- 后面為了方便描述,
javascript
我簡稱為js
,TypeScript
簡稱為ts
ts
可以理解為是具有類型語法的js
,用大白話說,ts
是基于js
,擴充了類型語法。
- 本文僅對
class
主要內容進行說明,更詳細說明可參考 阮一峰-ECMAScript 6 入門-class基礎語法
2. class 基礎介紹
2.1 如何定義class?
// 直接使用 class 關鍵詞定義即可
class Point {}
注意事項:
class
小寫;Point
也就是類名,按規范推薦首字母大寫;- 和定義函數不同,定義類,類名后不需要增加小括號;
2.2 如何使用class?
定義一個 class
,結合 new
關鍵詞我們可以創建一個對象(創建出來的對象,我們叫它:實例對象 )
比如:
class Point {}var p = new Point()
// p 就是一個實例對象
2.3 class 可以看做 es5 中構造函數寫法的語法糖
js
中創建實例對象,是有兩種方式:
- 在早期的代碼中,往往會通過構造函數的方式去實現。
- 在 es6 中,引入了
class
關鍵詞,通過class
實現。
class
的絕大部分功能,ES5 都可以做到。因此為了加深印象,在學習 class
關鍵詞的時候,相同代碼,我會列出 ES5 中如何實現的。
es6中的“類”
class Point {constructor(x, y) {this.x = x;this.y = y;}toString() {return '(' + this.x + ', ' + this.y + ')';}
}var p = new Point(1, 2)
es5中的“類”
function Point(x, y) {this.x = xthis.y = y
}Point.prototype.toString = function () {return '(' + this.x + ', ' + this.y + ')'
}var p = new Point(1, 2)// 構造函數的形式
注意事項:
- 直接對類使用
new
命令,跟構造函數的用法完全一致;- 類的所有方法都定義在類的
prototype
屬性上面;
3. class 中的 constructor() 方法
3.1 基礎說明
constructor()
方法是類的默認方法,通過new
命令生成對象實例時,自動調用該方法。
-
一個類必須有
constructor()
方法,如果沒有顯式定義,一個空的constructor()
方法會被默認添加。 -
constructor()
方法默認返回實例對象(即this
)。除了默認對象,也可以指定返回另外一個對象。
3.2 個人理解
簡單來說,constructor
對標 ES5 中的構造函數。我們可以在 constructor
,定義 new
輸出的實例對象。class
中constructor
必須要有,不寫會默認添加。
如下圖:
4. class 中定義的屬性和方法
4.1 實例屬性
由上文得知,我們定義實例對象上的屬性,需要在 constructor
定義。ES2022為類的實例屬性,又規定了一種新寫法。
如下:
寫法一
// 原來的寫法
class Demo {constructor() {this._count = 0;}add() {this._count++;}
}var d = new Demo()
console.log(d) // { _count: 0 }
寫法二
// 新的寫法
class Demo {_count = 0 // _count會綁定在實例對象上add() {this._count++}
}var d = new Demo()
console.log(d) // { _count: 0 }
這樣寫,好處非常明顯,定義實例對象的屬性的時候,可以更加簡潔明了。
ps: Cocos Creator3.x 中定義實例屬性,就是使用的
寫法二
,更加簡潔明了。
注意事項
- 實例屬性顧名思義,定義的屬性是實例對象自身的屬性,而不是定義在實例對象的原型上面。(參考上方的示例代碼。實例屬性對應:
d
上的屬性,而不是Demo.prototype
上的屬性)
4.2 class中定義的方法
和實例屬性不同,class
中直接定義的函數,是定義在實例對象的原型對象上,如下圖示例。
為什么屬性和方法有這樣的不同?為什么要這么做?
-
js
中當我們試圖訪問一個對象的屬性時,如果該對象本身沒有這個屬性,那么js
會在對象的原型對象上查找這個屬性,依次類推,直到找到屬性或者達到原型鏈的頂端。這樣就保證了我們的函數定義在實例對象的原型上,實例對象是可以訪問,調用的。
-
所有的實例都可以共享同一個方法,而不是每個實例都存儲一份方法的副本。這種做法可以節省內存。
-
將方法放在原型上,我們還可以實現方法的繼承和重寫。子類可以通過在其原型上添加或重寫父類的方法來實現繼承或重寫。
注意事項
class
中直接定義的函數,實際上是定義在實例對象的原型對象上
5. 取值函數(getter)和存值函數(setter)
“類”的內部可以使用get
和set
關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行為。
5.1 如何定義?
class MyClass {constructor() {// ...}get prop() {return 'getter';}set prop(value) {console.log('setter: '+value);}
}let inst = new MyClass();inst.prop = 123;
// setter: 123inst.prop
// 'getter'// prop是一個屬性,通過 `get`和`set`關鍵字,攔截 prop 的存取。
// 能攔截屬性的存取,就可以根據我們自身的需求去增加邏輯
7. 靜態方法和靜態屬性 static
函數其實本身也是一個對象,而class定義的類,其實也是一個對象。這個對象本身,是可以存儲屬性的。這些屬性我們就叫它靜態方法和靜態屬性
。
在 class
中,為了方便定義一個靜態屬性,我們可以在屬性前,增加關鍵詞 static
用以表示。
ES5 中定義靜態方法
function Point(x, y) {}Point.like = 'lazyTomato'Point.say = function () {console.log('我是say方法')
}
ES6 中 class 舊版的定義靜態方法
class Point {}
Point.like = 'lazyTomato'
Point.say = function () {console.log('我是say方法')
}
ES6 中 class 使用static關鍵詞定義靜態方法
class Point {static like = 'lazyTomato'static say() {console.log('我是say方法')}
}
注意事項:
static
定義的就是靜態屬性和靜態方法;- 因為靜態屬性和靜態方法,直接定義在
class
上的屬性和方法,所以可以不用實例化直接調用。
8. 私有屬性和私有方法
有時候,我們定義在類上的屬性或者方法,僅供類內部使用,不希望被實例對象調用。
這個時候就出現了希望能私有這些屬性和方法的方式。
私有,可以理解為就是僅供內部使用。
8.1 早期的實現方式:
class Point {_conut:1_say() {console.log('不希望被實例對象調用的方法')}
}// 通過給屬性或者方法增加 `_` (下劃線),表示這個屬性或者方法是私有的。
// 但是這個方式并不是百分百保險的,外部還是可以調用。
8.2 利用 Symbol 值的唯一性:
const bar = Symbol('bar');
const snaf = Symbol('snaf');export default class myClass{// 公有方法foo(baz) {this[bar](baz);}// 私有方法[bar](baz) {return this[snaf] = baz;}// ...
};
但是使用 Reflect.ownKeys()
依然可獲取到這些屬性。
const inst = new myClass();Reflect.ownKeys(myClass.prototype)
// [ 'constructor', 'foo', Symbol(bar) ]
8.3 使用 ES6中的
class Foo {#a;#b;constructor(a, b) {this.#a = a;this.#b = b;}#sum() {return this.#a + this.#b;}printSum() {console.log(this.#sum());}
}
9.總結
9.1 總結一下 class
中的一些屬性:
名稱 | 定義在哪里 | 示例 |
---|---|---|
實例屬性 | 實例對象 | ![]() |
class中定義的函數 | 定義在實例對象的原型對象上 | ![]() |
get和set函數 | 實例對象 | ![]() |
靜態方法和靜態屬性 | 類自身 | ![]() |
私有屬性和私有方法 | 類內部 | ![]() |
9.2 不同的屬性在谷歌瀏覽器中的展示效果
1.實例屬性,紅色高亮
2.class中定義的函數:紅色偏灰
3.get和set方法:紅色更加灰
4.靜態屬性和靜態方法
5.私有屬性和私有方法
9.3 為什么 class 中有些屬性可以直接通過 this
調用
示例代碼一
class Point {num = 1say() {console.log('我是say方法')}test() {console.log(this.num) // 問題1console.log(this.say()) // 問題2}
}
把上面的代碼換一種寫法
示例代碼二
class Point {constructor() {this.num = 1}say() {console.log('我是say方法')}test() {console.log(this.num) // 問題1console.log(this.say()) // 問題2}
}
var p = new Point()
p.test()
問題1: 為什么可以調用 this.num ?
因為誰調用函數,函數this就指向誰,執行
p.test()
時this
指向p
,p
本身有一個num
屬性,所以可以正常調用。
問題2: 為什么可以調用 this.say()?
執行
p.test()
時this
指向p
,p
本身沒有say
方法,但是它原型鏈上存在,所以可以正常調用。
end
- 目前就class的基礎內容就介紹到這里了。
- 后續再補充 子類,繼承 等內容。