hello world欢迎来到前端的新世界
当前文章系列专栏:Typescript
博主在前端领域还有很多知识和技术需要掌握,正在不断努力填补技术短板。(如果出现错误,感谢大家指出)
感谢大家支持!您的观看就是作者创作的动力
ts和js的区别?
在TypeScript中,方法重载(Method Overloading)是一种允许函数在不同参数数量或参数类型下具有不同的返回类型或行为的特性。这允许您以一种更灵活的方式定义函数,并根据传入的参数类型或数量来选择适当的行为或返回类型。
方法重载通常用于提供更加严格的类型检查和更好的类型推断,以及在代码中提供更清晰的接口。它使得函数可以根据不同的参数签名,提供不同的实现方式,而无需使用额外的运行时检查。
要定义方法重载,您需要按照以下步骤进行:
function greet(name: string): string;
function greet(age: number): string;
function greet(value: string | number): string {
if (typeof value === "string") {
return `Hello, ${value}!`;
} else {
return `You are ${value} years old!`;
}
}
console.log(greet("Lydia")); // Output: "Hello, Lydia!"
console.log(greet(30)); // Output: "You are 30 years old!"
上面定义了greet函数的两个不同的签名:一个接受string类型参数,另一个接受number类型参数。然后,我们实现了一个函数体,根据传入的参数类型进行相应的处理。
使用方法重载,TypeScript能够更好地检查函数调用,以确保传递的参数类型与预期的类型相符,并提供适当的类型推断,从而增加代码的类型安全性和可读性。
TypeScript 中的 is 关键字用于类型保护,可以在运行时判断一个对象是否属于某个类型,并根据不同的类型执行不同的逻辑。
具体来说,is 关键字通常和 instanceof 运算符一起使用,用于判断一个对象是否是某个类的实例。例如:
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
}
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
}
function isDog(animal: Animal): animal is Dog {
return (animal as Dog).breed !== undefined;
}
let a1 = new Animal("Tom");
let d1 = new Dog("Tony", "Poodle");
console.log(isDog(a1)); // false
console.log(isDog(d1)); // true
在上面的代码中,我们定义了一个 isDog 函数,它接受一个 Animal 类型的参数,返回值是一个布尔值。如果这个参数是 Dog 类型的实例,则返回 true;否则返回 false。注意,这里我们使用 animal is Dog 语法来显式地指定返回值类型为布尔值,表示这个函数就是一个类型谓词函数。
在 isDog 函数中,我们通过判断传入的 animal 参数是否含有 breed 属性,来判断它是否是 Dog 类型的实例。如果是,则返回 true;否则返回 false。
最后,我们可以通过调用 isDog 函数来判断一个对象是否是 Dog 类型的实例,并根据不同的类型执行相应的逻辑。
公共(public),类的所有成员,其子类以及该类的实例都可以访问。
受保护(protected),该类及其子类的所有成员都可以访问它们。 但是该类的实例无法访问。
私有(private),只有类的成员可以访问它们。
// 方法1:
let arr1: (number | string)[] = [1]
arr1.push(1)
arr1.push('3')
// 方法2:
let arr2 : Array<string | number> = [1, '2']
arr2.push(1)
arr2.push('3')
// 方法3:
type newType = number|string
let arr3:newType []= [3]
arr3.push(1)
arr4.push('5')
泛型程序设计(generic programming)是程序设计语言的一种风格或范式
泛型允许我们在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型 在typescript中,定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性
假设我们用一个函数,它可接受一个 number 参数并返回一个 number 参数,如下写法:
function returnItem (para: number): number {
return para
}
如果我们打算接受一个 string 类型,然后再返回 string类型,则如下写法:
function returnItem (para: string): string {
return para
}
上述两种编写方式,存在一个最明显的问题在于,代码重复度比较高
虽然可以使用 any类型去替代,但这也并不是很好的方案,因为我们的目的是接收什么类型的参数返回什么类型的参数,即在运行时传入参数我们才能确定类型
这种情况就可以使用泛型,如下所示:
function returnItem<T>(para: T): T {
return para
}
可以看到,泛型给予开发者创造灵活、可重用代码的能力
使用方式
函数
接口
类
函数声明
声明函数的形式如下:
function returnItem<T>(para: T): T {
return para
}
定义泛型的时候,可以一次定义多个类型参数,比如我们可以同时定义泛型 T 和 泛型 U:
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7,'seven']);//['seven', 7]
接口声明
声明接口的形式如下:
interface ReturnItemFn<T> {
(para: T): T
}
那么当我们想传入一个number作为参数的时候,就可以这样声明函数:
const returnItem: ReturnItemFn<number> = para => para
类声明
使用泛型声明类的时候,既可以作用于类本身,也可以作用与类的成员函数
下面简单实现一个元素同类型的栈结构,如下所示:
class Stack<T> {
private arr: T[] = []
public push(item: T) {
this.arr.push(item)
}
public pop() {
this.arr.pop()
}
}
使用方式如下:
const stack = new Stacn<number>()
如果上述只能传递 string 和 number 类型,这时候就可以使用 的方式猜实现约束泛型,如下所示:
除了上述的形式,泛型更高级的使用如下:
例如要设计一个函数,这个函数接受两个参数,一个参数为对象,另一个参数为对象上的属性,我们通过这两个参数返回这个属性的值
这时候就设计到泛型的索引类型和约束类型共同实现
索引类型、约束类型
索引类型 keyof T 把传入的对象的属性类型取出生成一个联合类型,这里的泛型 U 被约束在这个联合类型中,如下所示:
function getValue<T extends object, U extends keyof T>(obj: T, key: U) {
return obj[key] // ok
}
上述为什么需要使用泛型约束,而不是直接定义第一个参数为 object类型,是因为默认情况 object 指的是{},而我们接收的对象是各种各样的,一个泛型来表示传入的对象类型,比如 T extends object
使用如下图所示:
多类型约束
例如如下需要实现两个接口的类型约束:
interface FirstInterface {
doSomething(): number
}
interface SecondInterface {
doSomethingElse(): string
}
可以创建一个接口继承上述两个接口,如下:
interface ChildInterface extends FirstInterface, SecondInterface {
}
正确使用如下:
class Demo<T extends ChildInterface> {
private genericProperty: T
constructor(genericProperty: T) {
this.genericProperty = genericProperty
}
useT() {
this.genericProperty.doSomething()
this.genericProperty.doSomethingElse()
}
}
除此之外,TypeScript 还支持以下复合类型:
unknown 和 any 的主要区别是 unknown 类型会更加严格:在对 unknown 类型的值执行大多数操作之前,我们必须进行某种形式的检查。而在对 any 类型的值执行操作之前,我们不必进行任何检查。
举例说明:
let foo: any = 123;
console.log(foo.msg); // 符合TS的语法
let a_value1: unknown = foo; // OK
let a_value2: any = foo; // OK
let a_value3: string = foo; // OK
let bar: unknown = 222; // OK
console.log(bar.msg); // Error
let k_value1: unknown = bar; // OK
let K_value2: any = bar; // OK
let K_value3: string = bar; // Error
因为bar是一个未知类型(任何类型的数据都可以赋给 unknown 类型),所以不能确定是否有msg属性。不能通过TS语法检测;而 unknown 类型的值也不能将值赋给 any 和 unknown 之外的类型变量
any 和 unknown 都是顶级类型,但是 unknown 更加严格,不像 any 那样不做类型检查,反而 unknown 因为未知性质,不允许访问属性,不允许赋值给其他有明确类型的变量。
方法一
function unknownToString(value: unknown): string {
if (typeof value === "string") {
return value;
}
return String(value);
}
方法二
const value: unknown = "Hello World";
const foo: string = value; // Error
const bar: string = value as string; // OK
断言错了时语法能通过检测,但是运行的时候就会报错了!
const value: unknown = "Hello World";
const bar: number = value as number; // runtime Error
模块
TypeScript 与 ECMAScript 2015 一样,任何包含顶级 import 或者 export 的文件都被当成一个模块
相反地,如果一个文件不带有顶级的import或者export声明,那么它的内容被视为全局可见的
例如我们在在一个 TypeScript 工程下建立一个文件 1.ts,声明一个变量a,如下:
const a:number = 1;
然后在另一个文件同样声明一个变量a,这时候会出现错误信息
提示重复声明a变量,但是所处的空间是全局的
如果需要解决这个问题,则通过import或者export引入模块系统即可,如下:
const a:number = 10;
export default a
在typescript中,export关键字可以导出变量或者类型,用法与es6模块一致,如下:
export const a = 1
export type Person = {
name: String
}
通过import 引入模块,如下:
import { a, Person } from './export';
命名空间
命名空间一个最明确的目的就是解决重名问题
命名空间定义了标识符的可见范围,一个标识符可在多个名字空间中定义,它在不同名字空间中的含义是互不相干的
这样,在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中
TypeScript 中命名空间使用 namespace 来定义,语法格式如下:
namespace SomeNameSpaceName {
export interface ISomeInterfaceName { }
export class SomeClassName { }
}
以上定义了一个命名空间 SomeNameSpaceName,如果我们需要在外部可以调用 SomeNameSpaceName 中的类和接口,则需要在类和接口添加 export 关键字
使用方式如下:
SomeNameSpaceName.SomeClassName
命名空间本质上是一个对象,作用是将一系列相关的全局变量组织到一个对象的属性,如下:
namespace Letter {
export let a = 1;
export let b = 2;
export let c = 3;
// ...
export let z = 26;
}
编译成js如下:
var Letter;
(function (Letter) {
Letter.a = 1;
Letter.b = 2;
Letter.c = 3;
// ...
Letter.z = 26;
})(Letter || (Letter = {}));
区别
命名空间是位于全局命名空间下的一个普通的带有名字的 JavaScript 对象,使用起来十分容易。但就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中
像命名空间一样,模块可以包含代码和声明。 不同的是模块可以声明它的依赖
在正常的TS项目开发过程中并不建议用命名空间,但通常在通过 d.ts 文件标记 js 库类型的时候使用命名空间,主要作用是给编译器编写代码的时候参考使用
在 TypeScript 中,枚举(Enum)类型是一种用来定义命名常量集合的数据类型。枚举类型可以帮助开发者更清晰地表达代码中的意图,提高代码的可读性和可维护性。
enum Gender {
Male,
Female
Other
}
console.log(Gender.Male); // Output: 0
//We can also access an enum value by it's number value.
console.log(Gender[1]); // Output: Female
如果子类(子类)具有与父类中声明的相同的方法,则称为方法覆盖。换句话说,在派生类或子类中重新定义基类方法。
方法重写的规则
例子
class NewPrinter extends Printer {
doPrint(): any {
super.doPrint();
console.log("Called Child class.");
}
doInkJetPrint(): any {
console.log("Called doInkJetPrint().");
}
}
let printer: new () => NewPrinter;
printer.doPrint();
printer.doInkJetPrint();
interface
interface User {
name: string
age: number
}
interface SetUser {
(name: string, age: number): void;
}
type
type User = {
name: string
age: number
};
type SetUser = (name: string, age: number)=> void;
都允许拓展(extends)
interface 和 type 都可以拓展,并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 extends interface 。 虽然效果差不多,但是两者语法不同。
interface extends interface
interface Name {
name: string;
}
interface User extends Name {
age: number;
}
type extends type
type Name = {
name: string;
}
type User = Name & { age: number };
interface extends type
type Name = {
name: string;
}
interface User extends Name {
age: number;
}
type extends interface
interface Name {
name: string;
}
type User = Name & {
age: number;
}
总结
语法形式:
扩展:
兼容性:
适用范围:
综合来说,interface 更倾向于用于描述对象的形状,能够进行合并和扩展,并且在大多数情况下更符合直觉。而 type 则更适合用于定义联合类型、交叉类型等较为复杂的类型操作。在实际开发中,可以根据具体需求和场景来选择使用 interface 还是 type。
TypeScript 引入了类,以便它们可以利用诸如封装和抽象之类的面向对象技术的好处。
TypeScript 编译器将 TypeScript 中的类编译为普通的 JavaScript 函数,以跨平台和浏览器工作。
一个类包括以下内容:
class Employee {
empID: number;
empName: string;
constructor(ID: number, name: string) {
this.empName = name;
this.empID = ID;
}
getSalary(): number {
return 40000;
}
}
类的其他特性有:
装饰器是一种特殊的声明,它允许你通过使用@注释标记来一次性修改类或类成员。每个装饰器都必须引用一个将在运行时评估的函数。
例如,装饰器@sealed将对应于sealed函数。任何标有 的@sealed都将用于评估sealed函数。
function sealed(target) {
// do something with 'target' ...
}
它们可以附加到:
类声明
注意:默认情况下不启用装饰器。要启用它们,你必须experimentalDecorators从tsconfig.json文件或命令行编辑编译器选项中的字段。
Mixin 本质上是在相反方向上工作的继承。Mixins 允许你通过组合以前类中更简单的部分类设置来构建新类。
相反,类A继承类B来获得它的功能,类B从类A需要返回一个新类的附加功能。
创作不易,要是本文章对广大读者有那么一点点帮助 不妨三连支持一下,您的鼓励就是博主创作的动力