TypeScript重点难点(一):类型的特殊性

前端小王hs:
清华大学出版社《后台管理实践——Vue.js+Express.js》作者 
网络工程师 前端工程师 项目经理 阿里云社区博客专家 

email: 337674757@qq.com
vx: 文章最下方有vx链接
资料/交流群: vx备注Typescript

写在前头

虽然早已阅读完《Programming TypeSctipt》这本书,但一直以来没有时间好好的总结阅读时学到的新知识,同时由于要写关于Nest的书籍中关于TypeScript部分,以及想录制一套关于TypeScript进阶的视频,所以趁现在花上几天时间总结一下TypeScript的一些细节

本教程内容不是TypeScript的入门教程,是对部分TypeScript的细节进行归纳总结

注:本总结中TStsTypeScript都表示TypeScriptjsJavaScript都表示JavaScript

为什么要学TypeScript

2025年,学习TypeScript不是一种趋势潮流TypeScript已成为前端开发必学科目

从学习的结果来看:

  • 大部分企业开发都需要用到TypeScript
  • 基于JavaScript的后端框架如Nest需要用到TypeScript
  • 鸿蒙artks是基于TypeScript的,而鸿蒙很大可能是未来国内手机端软件开发的主流
  • 加薪

从学习的过程来看:

  • TypeScript可以加深对面向对象开发的理解,涉及到了多态继承等内容
  • 学习TypeScript能够更好的理解MVC,或者说衔接MVC开发,因为需要从类型的角度去设计值
  • 提高自己的代码水平,即减少后续的维护成本

TypeScript本质上就是给代码添加类型

TypeScript本质上就是给代码添加类型,并没有什么难度

学习TypeScript的几个目标

  1. 看得懂TypeScript的提示,并且能够根据提示进行修改,当然这在AI时代不是什么难事
  2. 看得懂他人写的TypeScript代码(为什么他会这样写?这在维护他人代码的时候可是常见的问题)
  3. 自己能够写的出好的TypeScript代码(能够以类型的角度去考虑需要解决的问题,尽量规避掉可能由于类型带来的隐患问题)

例如:如果没学好(没学完)TypeScript,在面对大数值问题时,就可能会出现选择number类型而不是bigint类型

类型的特殊性

类型推导

unknown的"细化性"

当不确定某个值的类型时,应该选择unknown,符合尽量少用any的编程习惯,unknown类型的值需要进行再次判断后(使用typeof)才能执行,否则报错Object is of type 'unknown',代码如下所示:

let a:unknown = 30
if(typeof a === 'number'){ // 细化
 let d = a + 10
}

实际的场景

通过API获取的数据可能随着空间时间其他条件发生变化,那么应该考虑到要使用unknown

注意:unknown必须显示注解

const的"具体性"

使用const会将类型限制到取的具体值,当然这个取值是一定属于某个类型的,如下代码所示:

let a = true // boolean
var b = true // boolean
const c = true // true

为什么const会这样?

const表示值不可变TypeScript编译器知道这个值永远不会变,所以做出更为精确的类型推断

const同样适用于numberbigintstring、下面仅以number为例,代码如下:

let a = 123 // number
var b = Infinity * 0.10 // number
const c = 123 // 123

类型字面量(literal type)

const的这种让常量确定唯一一个类型的,就叫类型字面量(或称为是字面量类型),这个类型可以是除基本数据类型numberstringboolean等)外的其他类型,如下面的具体的值也可以作为类型

const SUCCESS = 'success';
const ERROR = 'error';

在上面的例子中,TypeScript会将SUCCESSERROR的类型推断为'success''error'这样的字面量类型,而不是更广泛的string类型

const的实际应用场景

  1. 结合联合类型 + 字面量类型实现类型收窄(Discriminated Unions)
const SUCCESS = 'success'; // SUCCESS 的类型被推断为 'success'
const ERROR = 'error';     // ERROR 的类型被推断为 'error'

// 定义一个联合类型
type Status = typeof SUCCESS | typeof ERROR; 
// 等价于 type Status = 'success' | 'error';

function setStatus(status: Status) {
    if (status === SUCCESS) {
        console.log('Operation was successful');
    } else if (status === ERROR) {
        console.log('Operation failed');
    }
}

setStatus(SUCCESS); // 正确
setStatus('someOtherStatus'); // 错误:类型 '"someOtherStatus"' 不可分配给类型 'Status'
  1. 确定唯一环境变量
const API_BASE_URL = 'https://api.example.com/v1';
  1. 键值映射
const COLORS = {
  PRIMARY: '#3498db',
  SECONDARY: '#2ecc71'
} as const;

type ColorKey = keyof typeof COLORS; // "PRIMARY" | "SECONDARY"
type ColorValue = (typeof COLORS)[ColorKey]; // "#3498db" | "#2ecc71"

在这里可以看到const对象的使用又有区别,请继续看👇👇👇

在对象中使用const(难点)

对象中使用const,如下代码所示:

const Mode = {
  EDIT: 'edit',
  VIEW: 'view',
}

这种情况下,类型将会是:

{
  EDIT: string;
  VIEW: string;
}

原因在于TS编译器认为js的对象在创建之后也有可能发生变化地址不变),这其实很好理解,人的age会随着时间发生变化,但DNA不会

所以如果要将某个对象的值变为字面量类型,就需要加上类型断言,也就是as const,代码如下:

const Mode = {
  EDIT: 'edit',
  VIEW: 'view',
} as const;

现在类型即为:

{
  readonly EDIT: "edit";
  readonly VIEW: "view";
}

symbol的三种作用

私有属性/防止属性名冲突

// 模块A定义一个 symbol 属性
const key = Symbol('id');

const user = {
  name: 'Alice',
  [key]: 123 // 私有的 ID
};

// 模块B不知道这个 key,不会误操作
for (let prop in user) {
  console.log(prop); // 只输出 name
}

console.log(user[key]); // 输出 123

实现类的私有属性

const _secretKey = Symbol('secret');

class SecretBox {
  [_secretKey]: string;

  constructor(secret: string) {
    this[_secretKey] = secret;
  }

  revealSecret() {
    return this[_secretKey]; // 类内部拥有对_secretKey的引用,可以访问
  }
}

const box = new SecretBox('TopSecret123');

console.log(box.revealSecret()); // 输出 TopSecret123
console.log(box[_secretKey]);   // 类外部没有对_secretKey的引用,无法访问,'_secretKey' is not defined
console.log(box['secret']);     // undefined
private私有的缺陷(使用类型断言绕过限制)(进阶细节)

ts最终会被编译成js,而js并没有private,所以能够使用类型断言绕过private的私有限制访问到属性,如下示例代码所示:

class Person {
    private secret: string;

    constructor(secret: string) {
        this.secret = secret;
    }

    getSecret(): string {
        return this.secret;
    }
}

const person = new Person('My Secret');

// 正常情况下,无法直接访问 private 属性
// console.log(person.secret); // 编译错误: Property 'secret' is private and only accessible within class 'Person'.

// 但是可以通过类型断言绕过这个限制
console.log((person as any).secret); // 输出: My Secret

// 或者通过索引访问的方式
console.log(person['secret']); // 输出: My Secret

编译器运行
结果如下所示:
运行结果

绝对私有(ts3.8特性:#)(进阶)

TypeScript 3.8开始引入了#前缀用于声明真正的私有字段(Private Fields),这些字段不仅在类型检查阶段不可访问,在运行时也无法通过任何手段(包括类型断言和索引访问)访问到它们

class Person {
    #secret: string;

    constructor(secret: string) {
        this.#secret = secret;
    }

    getSecret(): string {
        return this.#secret;
    }
}

const person = new Person('My Private Secret');

console.log(person.#secret); // 编译错误: Private field '#secret' must be declared in an enclosing class.

元编程

元编程指的是定义对象的行为,换句话说,能够改变原本对象的自有行为,以下面代码为例:

class MyType {
  [Symbol.toStringTag] = 'MyCustomType';
}

const obj = new MyType();

console.log(Object.prototype.toString.call(obj));
// 输出: "[object MyCustomType]"
// 如果不加[Symbol.toStringTag] = 'MyCustomType';,那么输出的是"[object Object]"
// 清晰的看到改变了对象本身的属性

另外一种是给对象添加迭代器,让对象能够支持for...of,这在ECMAScript 6 入门关于Iterator-接口一节中有说,代码如下:

const myIterable = {
  data: [1, 2, 3],
  [Symbol.iterator]() {
    let index = 0;
    const data = this.data;
    return {
      next() {
        if (index < data.length) {
          return { value: data[index++], done: false };
        } else {
          return { done: true };
        }
      }
    };
  }
};

for (const item of myIterable) {
  console.log(item); // 输出 1, 2, 3
}

object与Objcet

object的无用性

书上有个例子特别有意思,如下代码所示:

let a: object = {
  b: 'x'
};

a.b; // ❌ 报错:类型“object”上不存在属性“b”。

定义了aobject类型,但是访问a.b报错,原因很简单,object只是说a是一个对象,但无法保证这个对象有b这个属性
那要object有什么用?

object的使用场景

例如某个通用函数需要接收一个对象参数,但不需要这个对象有什么属性,那么就可以用object了,代码如下:

function logObjectType(obj: object) {
  console.log('Received an object');
}

logObjectType({ name: 'Alice' });   // ✅ OK
logObjectType([1, 2, 3]);           // ✅ OK
logObjectType(Math);                // ✅ OK
logObjectType(123);                 // ❌ 错误:number 不是 object

这样做还有一个好处就是确定参数是object是类型,说起来像是一句废话,但看下面的例子:

typeof new String('hello') === 'object'  // true
typeof 'hello' === 'string'              // true

function foo(x: object) {}

foo({});         // OK
foo(new String('abc')); // OK
foo('abc');     // 错误:string 不是 object

这里还可以延申一点自动装箱的知识点:

function printLength(str) {
  console.log(str.length);
}

printLength('hello'); // 输出 5
printLength(new String('hello')); // 也能输出 5

我们知道对象才能调用方法字符串为什么也有.length?因为string被临时变成了new String('hello')这个对象,并在这个对象上调用了.length

object确保某个值是一个对象而不是原始类型(即不是stringnumberbooleanbigintsymbolnullundefined的值)

**注意:**在实际开发中更应该根据接收的对象类型来确定具体的类型,而不是使用object

Object

大写的Object是所有JavaScript对象的基础类型。它不仅包括普通对象,还包括所有继承自Object.prototype的对象,比如数组、函数、日期等,如下代码所示:

let obj: Object;

obj = {}; // ✅ 正确
obj = []; // ✅ 正确
obj = () => {}; // ✅ 正确
obj = new Date(); // ✅ 正确
obj = 'hello'; // ✅ 正确:String 对象也是 Object
obj = 123; // ✅ 正确:Number 对象也是 Object

而如果是object,就会爆红线了,如下图所示:
object
一般开发也不推荐用这么宽泛的类型

数字分隔符

let oneMillion = 1_000_000 // = 1000000

null、undefined、viod和never

  • null:表示缺少值
  • undefined:尚未定义
  • never:函数不返回(一直执行,如while(true){},或异常,如抛出throw
  • viod:函数没有显式的返回值(例如console.log
    除非是大公司,一般nullundefined混用十分常见

const enum和enum(面试)

首先是枚举都可以访问,如下图所示:
相互访问
但有隐患的是,访问不存在的,不会报错,如下图所示:
访问值不报错
这就需要const enum,加上之后就不允许这样了,不能够反向查找了,同时需要注意的是,运行时也不生成JS代码,只会在需要的地方进行替换,如下图所示:
不生成
面试可能会问const enum会不会生成js代码,那么上述就是答案了

TSC标志

strict家族

类似noImplicitAny之类的noImplicitXXX的都是strict家族,如果开启了strict,那么noImplicitXXX就不需要设置了,皆为true

noImplicitAny开启之后将会对隐式的any类型报错

静待补充…

一个关键的问题,为什么尽量让ts自行推导类型(面试)

任何关于TS的书籍都会提到诸如不要使用let num:number = 100这样显式注解方式去声明类型,为什么?

  • 大家都一眼能看懂就没必要写了
  • 写了还可能出错
  • TS的推断能力能强
  • 会被上司质疑一晚上加班就干这个?
  • 代码本身就很多了,还加这么多?
欢迎关注csdn前端领域博主: 前端小王hs,喜欢可以点个赞!您的支持是我不断更新的动力!

前端小王hs:
清华大学出版社《后台管理实践——Vue.js+Express.js》作者
网络工程师 前端工程师 项目经理 阿里云社区博客专家 

email: 337674757@qq.com
wx:最下方有联系方式
docker交流: 加v备注TypeScript
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端小王hs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值