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?
js
// 直接使用 class 关键词定义即可
class Point {}
注意事项:
class
小写;Point
也就是类名,按规范推荐首字母大写;- 和定义函数不同,定义类,类名后不需要增加小括号;
2.2 如何使用class?
定义一个 class
,结合 new
关键词我们可以创建一个对象(创建出来的对象,我们叫它:实例对象 )
比如:
js
class Point {}
var p = new Point()
// p 就是一个实例对象
2.3 class 可以看做 es5 中构造函数写法的语法糖
js
中创建实例对象,是有两种方式:
- 在早期的代码中,往往会通过构造函数的方式去实现。
- 在 es6 中,引入了
class
关键词,通过class
实现。
class
的绝大部分功能,ES5 都可以做到。因此为了加深印象,在学习 class
关键词的时候,相同代码,我会列出 ES5 中如何实现的。
es6中的"类"
js
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var p = new Point(1, 2)
es5中的"类"
js
function Point(x, y) {
this.x = x
this.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为类的实例属性,又规定了一种新写法。
如下:
写法一
javascript
// 原来的写法
class Demo {
constructor() {
this._count = 0;
}
add() {
this._count++;
}
}
var d = new Demo()
console.log(d) // { _count: 0 }
写法二
js
// 新的写法
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 如何定义?
js
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是一个属性,通过 `get`和`set`关键字,拦截 prop 的存取。
// 能拦截属性的存取,就可以根据我们自身的需求去增加逻辑
7. 静态方法和静态属性 static
函数其实本身也是一个对象,而class定义的类,其实也是一个对象。这个对象本身,是可以存储属性的。这些属性我们就叫它静态方法和静态属性
。
在 class
中,为了方便定义一个静态属性,我们可以在属性前,增加关键词 static
用以表示。
ES5 中定义静态方法
js
function Point(x, y) {}
Point.like = 'lazyTomato'
Point.say = function () {
console.log('我是say方法')
}
ES6 中 class 旧版的定义静态方法
js
class Point {}
Point.like = 'lazyTomato'
Point.say = function () {
console.log('我是say方法')
}
ES6 中 class 使用static关键词定义静态方法
js
class Point {
static like = 'lazyTomato'
static say() {
console.log('我是say方法')
}
}
注意事项:
static
定义的就是静态属性和静态方法;- 因为静态属性和静态方法,直接定义在
class
上的属性和方法,所以可以不用实例化直接调用。
8. 私有属性和私有方法
有时候,我们定义在类上的属性或者方法,仅供类内部使用,不希望被实例对象调用。
这个时候就出现了希望能私有这些属性和方法的方式。
私有,可以理解为就是仅供内部使用。
8.1 早期的实现方式:
js
class Point {
_conut:1
_say() {
console.log('不希望被实例对象调用的方法')
}
}
// 通过给属性或者方法增加 `_` (下划线),表示这个属性或者方法是私有的。
// 但是这个方式并不是百分百保险的,外部还是可以调用。
8.2 利用 Symbol 值的唯一性:
js
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()
依然可获取到这些属性。
js
const inst = new myClass();
Reflect.ownKeys(myClass.prototype)
// [ 'constructor', 'foo', Symbol(bar) ]
8.3 使用 ES6中的
js
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
调用
示例代码一
js
class Point {
num = 1
say() {
console.log('我是say方法')
}
test() {
console.log(this.num) // 问题1
console.log(this.say()) // 问题2
}
}
把上面的代码换一种写法
示例代码二
js
class Point {
constructor() {
this.num = 1
}
say() {
console.log('我是say方法')
}
test() {
console.log(this.num) // 问题1
console.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的基础内容就介绍到这里了。
- 后续再补充 子类,继承 等内容。