今天来分享下ts中的类,关于ts中的类的概念,面向对象的一种思想,以及类里面的一些属性成员,一些基础的用法,后面会有一个小练习。
类
基本概念
我的理解:类是编程语言中面向对象的一种思想,一个类是抽象事物的一个集合,比如人可以是一个类,类的实例对象具体到某一个人,所以这两者之间从抽象到具体。
面向对象思想
关于面向对象的一个思想的好处,可以让我们在开发的过程中,更易于维护我们的代码,以及我们的对象实例每个都是独立出来的,每次创建一个对象,都是NEW一个实例,每个实例之间互相不影响。面向对象用来编写模块化的代码也是非常的友好,而且公共类可以抽成接口用来继承,对于开发来说可以更加的清晰,逻辑更加的明确,同样对于灵活性和可扩展性也是有保障的。
- 代码重用性
- 代码可维护性
以前传统js类写法
es5
function User(userobj){
this.name = userobj.name;
this.age = userobj.age;
this.sex = userobj.sex;
this.phone = userobj.phone;
this.address = userobj.address;
}
const u = new User({
name : 'kakarote',
age : 18,
sex : "男",
phone : "1008611",
address : "广东深圳宝安区"
});
console.log(u)
es6
class User{
constructor(userobj){
this.name = userobj.name;
this.age = userobj.age;
this.sex = userobj.sex;
this.phone = userobj.phone;
this.address = userobj.address;
}
}
const u = new User({
name: 'kakarote',
age: 18,
sex: "男",
phone: "1008611",
address: "广东深圳宝安区"
});
console.log(u)
打印结果:
ts编写类
class User {
constructor(name: string, age: number,sex:string,phone:string,address:string) {
this.name = name;
this.age = age;
this.sex = sex;
this.phone = phone;
this.address = address;
}
}
在ts中如果按照上面这种写法是会报错的,ts觉得这种写法不太好,如果我们写一个类,我们应该是清楚这个类里面到底有哪些属性的,创建属性里面的工作不允许放在构造函数里面写。因为如果可以添加的话就可能会造成很多有隐患的代码,最后可能都不清楚这个对象到底是有哪些属性,ts认为属性是不能添加的,必须提前限制好。
ts让我们使用属性列表来描述类里面的属性
列表直接写到class里面
class User {
name: string
age: number
sex: string
phone: string
address: string
constructor(name: string, age: number, sex: string, phone: string, address: string) {
this.name = name;
this.age = age;
this.sex = sex;
this.phone = phone;
this.address = address;
}
}
编译结果里面不会带这个属性列表
不能随意添加属性
比如下图我想给这个u实例添加一个id,这个在ts中是不允许的
更加严格的属性初始化
但是可能会有下面这种情况,我没有构造函数,但是我直接创建user实例也不会报错,但是这样user对象的每个属性都是undefined,就和类型不符合了
class User {
name: string
age: number
sex: string
phone: string
address: string
}
const u = new User();
或者说,我写了构造函数,但是有些在构造函数里没赋值,希望ts能够提示出来,这样怎么做呢
这里可以在tsconfig.json里面加上一个配置,这个属性为strictPropertyInitialization
{
"compilerOptions": { //编译选项
"strictPropertyInitialization": true //这样ts会对类里面的属性初始化进行严格校验
},
}
strictPropertyInitialization
这个属性它会监测
- 构造函数中是否赋值
- 是否存在属性默认值
设置默认值
这里一般性别都是男或者女,这里我希望能有个默认值
enum Sex{
Male = "男",
Female = '女'
}
class User {
name: string
age: number
sex: Sex = Sex.Male //设置默认值
phone: string
address: string
//这样可以少传递一个参数
constructor(name: string, age: number phone: string, address: string) {
this.name = name;
this.age = age;
this.phone = phone;
this.address = address;
}
}
const u = new User(...具体参数);
u.sex = Sex.Female;
可选
类里面有些属性,我希望是可选的,那在ts中怎么写呢?
class User {
name: string
age: number
sex: Sex = Sex.Male //设置默认值
phone?: string //可以在:前面加个?这样就代表这个属性是可选的,可以传递也可以不传递
}
只读属性
在有些场景下,我们希望这个类的属性是只读属性,不能改的,比如说id属性,一般来说id都是自动生成的不可以改的,这里我们可以在属性前面加上readonly
关键字
class User {
readonly id:number
name: string
age: number
sex: Sex = Sex.Male //设置默认值
phone?: string //可以在:前面加个?这样就代表这个属性是可选的,可以传递也可以不传递
constructor(){
///...
//举例子
this.id = new Date().getTime();
}
}
使用访问修饰符
访问修饰符可以控制类中的某个成员的访问权限
- public:默认的访问修饰符,公开的,所有代码均可以访问
- private: 私有的,只有在类中可以访问
- protected
class User {
//...
//设置私有的两个属性
private _publishNumber: number = 3 //每天一共可以发布多少篇文章
private _curNumber: number = 0; //当前文章发布数量
publish(title: string) {
if (this._curNumber < this._publishNumber) {
console.log("发布了一篇文章" + title);
this._curNumber++;
} else {
console.log("你今日发布的文章数量已达到上限")
}
}
}
const u = new User("aa", 22);
u.publish('文章1')
u.publish('文章2')
u.publish('文章3')
u.publish('文章4')
Tips:开发的原则,尽量少的暴露公共的属性,暴露出来的成员容易被其他开发者直接修改,容易造成很多隐患,所以一般都是暴露出一些需要用的函数和属性,一些不需要用的属性尽量私有化。
属性简写
如果某个属性,通过构造函数的参数传递,并且不做任何处理的赋值给该属性。可以进行简写
class User {
sex: Sex = Sex.Male //设置默认值
phone?: string //可以在:前面加个?这样就代表这个属性是可选的,可以传递也可以不传递
constructor(public name: string, public age: number) {
}
}
const u = new User('kakarote',11);
console.log(u)
前提是这里必须加上修饰符,不加修饰符是无效的
访问器
有些属性我们不希望直接对他进行赋值,以及直接进行读取,这样可能会存在一些问题,这里就需要用到访问器的知识
class User{
//设置私有化_age变量
constructor(public name: string, private _age: number) {
}
setAge(value: number) {
if(value < 0){
this._age = 0;
}else if(value >= 200){
this._age = 200;
}else{
this._age = value;
}
}
getAge() {
return Math.floor(this._age);
}
}
const u = new User("aa", 22);
console.log(u.getAge())
u.setAge(11)
console.log(u.getAge())
我们可以使用这种函数的形式,这样就可以实现一个访问器的效果,但是js的类里面给我们提供了get和set的一个关键字,这个关键字可以达到一样的效果,而且还可以让我们用传统的赋值和读取的写法。它的本质还是一个函数,有点类似Object.defineProperty的存取器
class User{
//设置私有化_age变量
constructor(public name: string, private _age: number) {
}
set age(value: number) {
if(value < 0){
this._age = 0;
}else if(value >= 200){
this._age = 200;
}else{
this._age = value;
}
}
get age() {
return Math.floor(this._age);
}
}
const u = new User("aa", 22);
console.log(u.age)
u.age = 11;
console.log(u.age)
这个完全是可以达到一样的效果的
小练习
我们上期typescript回顾二里面分享了扑克牌的功能,现在希望使用ts的类对它进行一个改造
1. 目标:创建一幅扑克牌(不包括大小王),打印该扑克牌,增加洗牌和发牌功能
2. 使用枚举改造程序
3. 使用模块化
4. 用接口改造程序,加入大小王
5. 用类改造程序
目录结构
- src
- deck.ts
- enum.ts
- index.ts
- types.ts
enum.ts
定义枚举,形状以及标记
export enum Shape {
heart = "♥",
spade = "♠",
club = "♣",
diamond = "♦",
}
export enum Mark {
A = "A",
two = "2",
three = "3",
four = "4",
five = "5",
six = "6",
seven = "7",
eight = "8",
nine = "9",
ten = "10",
eleven = "J",
twelve = "Q",
king = "K"
}
type.ts
用来定义类型,普通牌的一个类型定义
import { Color, Mark } from "./enums";
//一副牌是个Card数组
export type Deck = Card[];
//定义每张牌
export interface Card {
getString(): string
}
//普通牌
export interface NormalCard extends Card {
color: Color,
mark: Mark,
}
//大小王
export interface Joker extends Card {
type: "big" | "small"
}
deck.ts
//用于定义具体的一个扑克牌游戏规则
import { Mark, Color } from "./enums";
import { Card, Joker } from "./types";
//发牌对象,每个玩家发xx张牌
interface PublishResult {
player1: Deck,
player2: Deck,
player3: Deck,
left: Deck
}
export class Deck {
private cards: Card[] = [];
constructor(cards?: Card[]) {
if (cards) {
this.cards = cards;
} else {
this.init();
}
}
//初始化
private init() {
const marks = Object.values(Mark);
const colors = Object.values(Color);
for (const m of marks) {
for (const c of colors) {
this.cards.push({
mark: m,
color: c,
getString() {
return this.color + this.mark;
}
} as Card);
}
}
let joker: Joker = {
type: "small",
getString() {
return "jo"
},
}
this.cards.push(joker);
joker = {
type: "big",
getString() {
return "Jo"
},
}
this.cards.push(joker);
}
print() {
let result = "\n";
this.cards.forEach((card, i) => {
result += card.getString() + "\t";
if ((i + 1) % 6 === 0) {
result += "\n";
}
});
console.log(result)
}
/**
* 洗牌
*/
shuffle() {
//[x1,x2,x3,x4,x5,x6,x7]
for (let i = 0; i < this.cards.length; i++) {
const targetIndex = this.getRandom(0, this.cards.length);
const temp = this.cards[i];
this.cards[i] = this.cards[targetIndex];
this.cards[targetIndex] = temp;
}
}
// 发完牌后,得到的结果有4个card[]
publish(): PublishResult {
let player1: Deck, player2: Deck, player3: Deck, left: Deck;
player1 = this.takeCards(17);
player2 = this.takeCards(17);
player3 = this.takeCards(17);
left = new Deck(this.cards);
return {
player1,
player2,
player3,
left
};
}
private takeCards(n: number): Deck {
const cards: Card[] = [];
for (let i = 0; i < n; i++) {
cards.push(this.cards.shift() as Card);
}
return new Deck(cards);
}
/**
* 无法取到最大值
* @param min
* @param max
* @returns
*/
private getRandom(min: number, max: number) {
const dec = max - min;
return Math.floor(Math.random() * dec + min)
}
}
index.ts
入口,引入扑克牌游戏
import { Deck } from "./deck";
const deck = new Deck();
deck.shuffle();
console.log("===========洗牌之后===========")
deck.print();
const result = deck.publish();
console.log("===========发牌之后===========");
console.log("===========玩家1===========");
result.player1.print();
console.log("===========玩家2===========");
result.player2.print();
console.log("===========玩家3===========");
result.player3.print();
console.log("===========玩家3===========");
result.player3.print();
console.log("===========地主牌===========");
result.left.print();