# Class 类
# 生成实例对象的传统方法 是使用构造函数
// 生成实例对象的传统方法 是使用构造函数
function Foo(x, y, name = 'hello') {
this.x = x;
this.y = y;
this.name = name;
this.getInfo = () => {
return this.name;
}
}
// 箭头函数 this 指向 window
// name = 'Ben';
Foo.prototype.getName = () => {
return `name: ${this.name}`;
}
Foo.prototype.toString = function() {
return `x: ${this.x}; y: ${this.y}`;
}
const foo = new Foo(1, 2, 'Moto');
console.log(foo.x); // 1
console.log(foo.y); // 2
// function 普通函数 this 指向该实例
console.log(foo.toString()); // x: 1; y: 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# class
关键字定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
// 不需要使用 function 定义方法
toString() {
return `x: ${this.x}; y: ${this.y}`;
}
}
// ES6 类是另一种构造函数的写法
// 类的基本数据类型是函数
console.log(typeof Point); // 'function'
// 类本身指向构造函数
console.log(Point === Point.prototype.constructor); // true
// 使用 class 时用 new 关键字 跟构造函数一样
const point = new Point(100, 200);
const str = point.toString();
console.log(str); // x: 100; y: 200
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 构造函数的
prototype
在 ES6 上继续存在事实上,类的所有方法都是定义在类的
prototype
属性上
class Point {
constructor {}
toString() {}
}
// 等于
Point.prototype = {
constructor() {}
toString() {}
}
console.log(Point.prototype); // {constructor: ƒ, toString: ƒ}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 在类的实例上调用方法,实际上就是在调用类的原型上的方法
class Bar {
}
const b = new Bar();
b.constructor === Bar.prototype.constructor // true
1
2
3
4
5
2
3
4
5
- 类内部定义的所有方法, 都是不可枚举的, 跟 ES5 不一致
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
// 不需要使用 function 定义方法
toString() {
return `x: ${this.x}; y: ${this.y}`;
}
}
const keys = Object.keys(Point.prototype);
console.log(keys); // []
console.log(Object.getOwnPropertyNames(Point.prototype)); // ["constructor", "toString"]
console.log(Object.keys(Foo.prototype)); // ["getName", "toString"]
console.log(Object.getOwnPropertyNames(Foo.prototype)); // ["constructor", "getName", "toString"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 类必须使用
new
调用,否则会报错,普通构造函数不使用new
也可以,不会报错。
# constructor 方法
- 是类的默认方法,通过
new
命令生成对象实例时,默认调用该方法。一个类必须有constructor
方法,
如果没有显示定义,会被默认添加 - 默认返回实例对象 (this),也可以指定返回一个对象
constructor() {
return Object.create(null);
}
1
2
3
2
3
# 类的实例
- 与 ES5 一样,实例的属性除非显示定义在其本身(this),否则都是定义在原型上(class)
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
// 不需要使用 function 定义方法
toString() {
return `x: ${this.x}; y: ${this.y}`;
}
}
const point = new Point(100, 200);
console.log(point.hasOwnProperty('x')); // true
console.log(point.hasOwnProperty('y')); // true
console.log(point.hasOwnProperty('toString')); // false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 取值函数(getter)和存值函数(setter)
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
// 不需要使用 function 定义方法
toString() {
return `x: ${this.x}; y: ${this.y}`;
}
get prop() {
console.log('getter');
return 'prop';
}
set prop(value) {
console.log('setter', value);
// this.prop = value;
}
}
// 存取值
console.log(point.prop);
// 'getter'
// 'prop'
point.prop = 'hello'; // 'setter hello'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- 存值函数和取值函数都是设置在属性的描述符(Descriptor)上
const descriptor = Object.getOwnPropertyDescriptor(Point.prototype, 'prop');
console.log('get' in descriptor); // true
console.log('set' in descriptor); // true
1
2
3
2
3
# 属性表达式
const methodName = 'getName';
class Point {
//...
[methodName]() {
// ...
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# Class 表达式
const someClass = class Me {
getClassName() {
return Me.name;
}
}
1
2
3
4
5
2
3
4
5
- 注意, 这个类的名字是
Me
,但只能在 class 内部使用, 外部只能使用 someClass 引用
# 注意点
默认是严格模式
不存在提升
name 属性,总是返回紧跟 class 后面的类名
class Hello {}
Hello.name // Hello
1
2
2
Generator 方法 TODO
this 指向
类的方法内部如果含有 this ,默认指向类的实例,但是使用时需特别小心
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
// 绑定
this.getString = this.getString.bind(this);
}
// 不需要使用 function 定义方法
toString() {
return `x: ${this.x}; y: ${this.y}`;
}
getString() {
return this.toString();
}
}
const point = new Point(100, 200);
const { getString } = point;
getString(); // Uncaught TypeError: Cannot read property 'toString' of undefined
// getString 中的 this 默认指向 Point ,但是如果将这个方法提出来单独使用,this 会指向该方法运行时环境
// 因为 class 内部是严格模式,所以 this 实际指向是 undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
解决方法:
- 在构造方法中绑定 this (this.getString = this.getString.bind(this);)
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
// 绑定
this.getString = this.getString.bind(this);
}
// 不需要使用 function 定义方法
toString() {
return `x: ${this.x}; y: ${this.y}`;
}
getString() {
return this.toString();
}
}
const point = new Point(100, 200);
const { getString } = point;
console.log(getString()); // x: 100; y: 200
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 箭头函数
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
// 箭头函数
this.getThis = () => this;
}
// 不需要使用 function 定义方法
toString() {
return `x: ${this.x}; y: ${this.y}`;
}
getString() {
return this.toString();
}
}
const point = new Point(100, 200);
const { getThis, getString } = point;
console.log(getThis().getString()); // x: 100; y: 200
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 静态方法
- 类相当于实例的原型,所有定义在类上的方法都会被实例继承。如果在某个方法前面加上
static
,则表示该方法不会被实例继承,
而是直接通过类调用。
class Bar {
static getName() {
// ...
}
}
const bar = new Bar();
bar.getName() // Error
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 如果静态方法包含
this
,则this
指的是类,而不是实例。
class Bar {
static getName() {
this.queryName();
}
// 类调用的方法
static queryName() {
console.log('hello');
}
// 实例调用的方法
queryName() {
console.log('world');
}
}
Bar.getName() // 'hello'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 父类的静态方法,可以被子类继承
class Bar {
static getName() {
// ...
}
}
class Foo extends Bar {
}
Foo.getName();
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 实例属性的新写法
- 实例属性除了可以定义在 constructor 的 this 上,也可以定义在类的最顶层。
class Foo {
bar = 'hello';
baz = 'world';
constructor() {
// ...
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 静态属性
- 是指 Class 本身的属性,而不是定义在 this 上的
class Boo {
static num = 1;
}
Boo.count = 12
console.log(Boo.num); // 1
console.log(Boo.count); // 12
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 私有属性和私有方法
- 只能在类内部使用
class Boo {
#age = 0;
up () {
this.#age++;
console.log(this.#age);
}
get age() {
return this.#age;
}
set age(value) {
this.#age = value;
}
}
const boo = new Boo();
boo.up(); // 1
boo.up(); // 2
// boo.#age; // Error
console.log(boo.age); // 2
boo.age = 10;
console.log(boo.age); // 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Map →