前言
关键字
静态成员、this 指向、单例模式
参考资料
notes
静态成员
什么是静态成员?
静态成员是指:附着在类上的成员(属于某个类的成员)
实例成员:对象成员,属于某个类的对象
静态成员:非实例成员,属于某个类
静态成员是通过类去调用的,而实例成员是通过实例去调用的。
静态成员并非是 ts 给我们整出来的一个东西,在 es 中就已经有了,只不过在 ts 中,对静态成员的一些操作添加了一些强约束,预防我们写出高隐患的代码。
static 关键字
使用 static 关键字修饰的成员为静态成员。
console.log(Number.MAX_VALUE);
// => 1.7976931348623157e+308
**Number.MAX_VALUE**
MAX_VALUE 就是类 Number 身上的一个静态成员属性。
静态成员只能通过类来访问。
Number 类身上除了含有 MAX_VALUE 这个静态成员之外,还有很多其它的静态成员。
在 vscode 编辑器中,我们只要输入 Number.
就能查看它身上所有的静态成员。
class User {
constructor(
public loginId: string,
public loginPwd: string,
public name: string,
public age: number
) {}
/**
* 登录方法
* @param loginId 账号
* @param loginPwd 密码
* @returns 成功返回用户对象,失败返回 undefined
*/
login(loginId: string, loginPwd: string): User | undefined {
// ...
return undefined;
}
}
const u = new User("admin", "123123", "dahuyou", 23);
// u.login();
login,是登录方法,它不应该作为一个实例方法,而应该作为一个静态成员方法。
分析
在注册时,需要新建一个用户对象,使用 User 类来实例化一个用户实例。(用户对象从没有到有)
在登录时,需要调用一个 login 方法,此时并不需要实例化新的用户对象。(检查该用户对象是否已存在)
每次登录都要先新建一个用户对象,这样的逻辑显然是不合理的,用户对象的创建,应该是在注册新用户的时候发生的事儿。
this 指向
class User {
static users: User[] = []; // 存放所有已注册的用户实例
constructor(
public loginId: string,
public loginPwd: string,
public name: string,
public age: number
) {
// this -> 当前用户实例 -> new User()
User.users.push(this);
}
static login(loginId: string, loginPwd: string): User | undefined {
// this -> 构造器 -> User
return this.users.find(
(u) => u.loginId === loginId && u.loginPwd === loginPwd
);
}
}
const u1 = new User("admin1", "123123", "dahuyou", 23);
const u2 = new User("admin2", "123123", "dahuyou", 23);
const u3 = new User("admin3", "123123", "dahuyou", 23);
// User.users.push(u1, u2, u3);
console.log(User.login("admin1", "123123")); // => User 实例
console.log(User.login("admin2", "123123")); // => User 实例
console.log(User.login("admin3", "123123")); // => User 实例
console.log(User.login("admin4", "123123")); // => undefined
🤔 users 更应该是一个实例成员还是静态成员?
答:静态成员。
users 属性存放的是所有已成功注册的用户。下面分析「如果将 users 定义为一个实例成员」为什么不合理:
**u1.users === u2.users === u3.users**
通过任一实例来访问 users,得到的将会是同一个 users。
这么设计显然是不合理的,因为每一个实例身上的东西通常都是独立的,对于这个相同的属性,应该将其定义为一个静态成员更加合理。
- 我们想要知道当前已经成功注册多少人
- 获取到 User 实例
2 不应该是 1 的前提条件,即便没有实例化 User 对象,我们应该也是可以获取到当前已经成功注册的成员的。
每新建一个用户实例,就将其丢到 users 中:
- 每次创建好实例后,手动 push:
User.users.push(u1, u2, u3)
「不推荐」 - 统一封装在构造器里面:
User.users.push(this)
「推荐」
此时 this 指向的就是当前创建的对象。
this 指向
在静态成员(属性、方法)中,this 指向当前类;
在实例方法、构造器中,this 指向当前对象;
在静态方法 static login
中,this.users
等效于 User.users
class User {
constructor(loginId, loginPwd, name, age) {
this.loginId = loginId;
this.loginPwd = loginPwd;
this.name = name;
this.age = age;
User.users.push(this);
}
static login(loginId, loginPwd) {
return this.users.find((u) => u.loginId === loginId && u.loginPwd === loginPwd);
}
}
User.users = [];
const u1 = new User("admin1", "123123", "dahuyou", 23);
const u2 = new User("admin2", "123123", "dahuyou", 23);
const u3 = new User("admin3", "123123", "dahuyou", 23);
console.log(User.login("admin1", "123123"));
console.log(User.login("admin2", "123123"));
console.log(User.login("admin3", "123123"));
console.log(User.login("admin4", "123123"));
static 关键字,在 es 中是存在的。
并且在 ts 和 js 中,它的用法是一致的。
class User {
// ...
static login(loginId: string, loginPwd: string): User | undefined {
return this.users.find(
(u) => u.loginId === loginId && u.loginPwd === loginPwd
);
}
}
User.login = function(loginId, loginPwd) {
return this.users.find(
(u) => u.loginId === loginId && u.loginPwd === loginPwd
);
}
表示的含义都是一样的,都是在直接扩展 User 类。
设计模式 - 单例模式
单例模式:某些类的对象,在系统中最多只能有一个,为了避免开发者犯错 —— 实例化多个该类的对象,可以使用单例模式进行强约束。
下面以中国象棋小游戏为例,有这么一个 Board 类,用于创建一个棋盘。
但是棋盘只需要一个即可,我们只需要创建一次。
class Board {
width: number = 500;
height: number = 700;
init() {
console.log("初始化棋盘");
}
}
const b1 = new Board();
const b2 = new Board(); // 重复了
b1 === b2; // => false
如果采用上面这种写法来实现棋盘类的初始化,那么可以创建多个不同的棋盘,而这并不是我们希望看到的。
我们可以采用单例模式来改写上述代码。
class Board {
width: number = 500;
height: number = 700;
// 1
private constructor() {}
// 2
private static _board?: Board;
// 3
static createBoard(): Board {
if (this._board) return this._board;
else this._board = new Board();
return this._board;
}
}
const b1 = Board.createBoard();
const b2 = Board.createBoard();
b1 === b2; // => true
步骤:
- 将构造函数私有化
- 准备一个私有变量 —— 系统中唯一的棋盘对象
- 准备一个静态成员方法 —— 创建唯一实例(棋盘对象),并保存到上一步创建的私有变量中
当我们将构造函数私有化之后,我们就没法通过 new 关键字来调用类,实例化对象了。
上面介绍的实现步骤并不是死的,也有其它实现写法。
class Board {
width: number = 500;
height: number = 700;
private constructor() {}
static readonly singleBoard = new Board();
}
const b1 = Board.singleBoard;
const b2 = Board.singleBoard;
b1 === b2 // => true
readonly
加上 readonly 的目的是为了防止给Board.singleBoard
重新赋值。
在这个例子中,即便不加 readonly 也无法给
Board.singleBoard
重新赋值 当我们在初始化singleBoard
时,通过类型推导,确定了它的类型是 Board 类型 如果想要给它重新赋值,那么我们必须得调用new Board()
因为我们已经将构造函数私有化,所以new Board()
没法在类 Board 之外调用
对比「写法1」和「写法2」
棋盘实例的创建
写法1:我们需要手动调用Board.createBoard()
去创建棋盘
写法2:自动就会创建好棋盘Board.singleBoard
如果我们在创建棋盘时,还有一些其它逻辑需要处理,那么使用「写法1」更加合理。