数据类型和Typescript
作者:DSH
本文章由擎盾科技有限公司的前端公共知识组负责维护,用于分享一些前端的入门-进阶知识;文中部分内容参考自网络,如有侵权请告知 我会立即删除。其它知识分享请移步专栏
什么是数据类型?
如果你是一名程序员,那你一定使用过变量、函数参数或者函数返回值,它们都用来表示数据,在编程语言中无处不在; 但是,你是否知道它们的真正含义呢?又是否知道它们在后台如何控制计算机的呢?
在很多如 C/C++/C#、Java 编程语言中,定义变量时除了需要指明变量的名字,还需要告诉计算机它是什么类型,比如简单的整数、浮点数、字符串,还有复杂的类、结构体、数组。
编程语言中的数据最终都要放到内存中,在内存中存取数据要明确三件事情:数据存储地址、数据的长度以及数据的处理方式。
- 数据存储地址决定了数据放在哪里;
- 数据类型,决定了其数据长度,数据长度又决定了当前数据使用了多少个字节的内存;
- 数据类型与决定了数据处理方式 不仅让计算机能够正确转换数据的内容,不至于导致“乱码”,还让计算机知道如何处理基于该数据的各种运算,比如加减乘除。
变量名不仅仅是为数据起了一个好记的名字,还告诉我们数据的存储地址,使用数据时,只要提供变量名即可,变量名会自送转换成数据在内存中的地址。而数据类型则指明了数据的长度和处理方式,它确定了除地址以外的其它所有信息。
静/动态类型语言
静态类型和动态类型应该放在一起提及,它们从「如何得到数据的类型」这一维度来评价类型系统。
核心区分
静态类型语言:在定义的时候就要指定变量的类型,而且类型不会在之后变,然后根据类型为其分配分配长度、存储内存、和提供处理数据方式 动态类型语言:第一次赋值给变量时,根据其值推导出来,后续赋值类型若不同,这个变量的类型也会随之改变
let i = 123;
console.log(typeof i); // number 自动推导类型为number
i = '456';
console.log(typeof i); // string 后续赋值类型若不同 类型也会随之改变
int i = 123;
i = "456"; // 编辑器会提示报错,编译阶段也会提示报错 Type mismatch: cannot convert from String to int
对比好处
1、工具支持上,编辑器ide会根据静态语言定义的类型提供智能提示,但是动态语言基本很难做到 2、阅读友好性上,静态语言在二次开发或者阅读代码的时候比较友好,你可以很清楚的看到参数和返回类型进而推断出业务逻辑,但动态则是编写一时爽,重构火葬场
,尤其是设涉及到业务复杂的地方,代码惨不忍睹 3、在发现错误时机上,静态类型语言存在编译阶段,很多错误如 因类型错误而不能做的事情(语法错误)就可以发现。而动态类型语言不存在编译阶段,很多错误都是在运行阶段才发现 4、编写舒适性上,这个动态语言扳回一局,写起来不用考虑类型局限,编码会很流畅 Java程序员还在那里定义接口,定义数据类型。js工程师已经快速干完活,回家老婆孩子热炕头了
强/弱类型定义语言
强/弱类型指的是编程语言如何处理运算过程中的值。当值的类型不符合运算规则时,编程语言究竟是做出一些猜测,临时转换值的类型以帮助人们解决问题,还是停止运行,提醒人们不应该这样做。
核心区分
强类型定义语言:很少合理隐的隐式类型转换 弱类型定义语言:有较多过分的隐式类型转换
对比好处
1、若语言自动转换类型很方便,但是有时候会出现转换不是那么智能 2、强语言写起来很麻烦,不确定的很多地方都会报错。这就保证了代码稳定,先天健壮性强
var i = 2;
console.log(i+'1'); // 输出21 自动将i转换string类型
var j = '3';
console.log(j-'2'); // 输出1 自动将i转换number类型
相比之下,强类型语言这么做运算, 编译阶段就开始报错了,而且编辑器也会直接提示存在问题。
不过 很少合理隐的式类型转换 也不一定都是弱类型定义语言,如java
int i = 1/3; // 如果我们这里将变量i定义为int类型,则会丢失精度(此处是隐式转换,但是只能从高精度转向低精度,但并不能说java是弱类型语言)
System.out.println(i); // 输出0
ts的诞生
js是弱/动态语言。 随着前端项目的日益庞大,以及nodejs让js也开始处理大量业务逻辑,js的这些缺点被无限放大! 于是typescript就孕育而出,它的出现就是解决前端这些问题!
布尔值
最基本的数据类型就是简单的true/false值,在js和ts里叫做boolean。
const isDone: boolean = false;
数字
js只有一种数值型数据类型,不管是整数还是浮点数,js都把归为数字。 即js中的所有数字都是双精度浮点数。 和js一样,ts里的所有数字都是浮点数,这些浮点数的类型是 number。
const decLiteral: number = 6;
字符串
string表示文本数据类型。 和js一样,可以使用双引号( ")或单引号(')或模版字符串来表示字符串。
const str1: string = "bob";
const str2: string = 'smith';
const str3: string = `Gene`;
数组
有两种方式可以定义数组。 第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组。第二种方式是使用数组泛型,Array<元素类型>
const list: number[] = [1, 2, 3];
const list: Array<number> = [1, 2, 3];
元组
Tuple 类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 简而言之,就是一个可以 容纳不同数据类型的数组。 比如,你可以定义一对值分别为 string和number类型的元组。
let x: [string, number];
x = ['hello', 10]; // // 初始化成功
x = [10, 'hello']; // 初始化报错
访问或操作元素
// 当访问一个已知索引的元素,会得到正确的类型反馈:
let x: [string, number]= ['hello', 10];
console.log(x[0].substr(1)); // OK
console.log(x[1].substr(1)); // Error, '类型“number”上不存在属性“substr”
// 当访问一个越界的元素,会报错:
x[3] = 'world'; // Error, 长度为 "2" 的元组类型 "[string, number]" 在索引 "3" 处没有元素
枚举
enum类型是对js标准数据类型的一个补充。 像C#等其它语言一样,使用枚举类型可以为一组数值赋予友好的名字。 举个例子:我们为果汁店设计一个程序,它将限制果汁为小杯、中杯、大杯。这就意味着它不允许顾客点除了这三种尺寸外的果汁。
enum FreshJuiceSize { SMALL, MEDIUM , LARGE, PLUS = 10 } // 默认情况下,从0开始为元素编号。 你也可以手动的指定成员的数值
const sizeValue: FreshJuiceSize = FreshJuiceSize.MEDIUM; // 根据枚举名字获取m枚举值
const sizeLabel: string = FreshJuiceSize[2]; // LARGE 根据枚举值获取枚举名字
const testLabel: string = FreshJuiceSize[10]; // PLUS 根据枚举值获取枚举名字
console.log(sizeLabel,sizeValue, testLabel);
Any
任意类型,让编译器忽略类型检查直接通过编译。
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false;
Object
ts 2.2 引入了被称为 object 类型的新类型,它用于表示非原始类型。
/**
* object定义是一个对象类型,不能自动获取对象上的属性和方法
*/
const obj1: object = {name:'张三'}
obj1.name = 123; // 类型“object”上不存在属性“name”
/**
* object类型只能定义对象类型,不能定义其他类型
*/
const obj2: object = 123; // object类型只能定义对象类型,不能定义其他类型
你可能认为 与any有相似的作用,但并不是。
// Object类型的变量只是允许你给它赋任意值 - 但是却不能够在它上面调用任意的方法,即便它真的有这些方法
const notSure: any = 4;
notSure.toFixed(); // 正确, toFixed(不管是否) 存在,都 (会因为编译器不检查而)成功
const prettySure: Object = 4;
prettySure.toFixed(); // 错误: 类型“Object”上不存在属性“toFixed”.
另外注意的是 还有个Object
接口(类型)。它用于定义 JS Object 的原型对象Object.prototype,我们基本用不到这个类型。
Void
某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void
// 表示没有任何返回值
function warnUser(): void {
console.log("This is my warning message");
}
不过单纯声明一个void类型的变量却没有什么大用,因为你只能为它赋予undefined和null:
const unusable: void = undefined;
Null 和 Undefined
值undefined和null两者各自有自己的类型分别叫做undefined和null。 和 void相似,它们的本身的类型用处不是很大
let u: undefined = undefined;
let n: null = null;
默认情况下null和undefined是所有类型的子类型。 当你指定了--strictNullChecks标记,null和undefined只能赋值给void和它们各自。
const a:number = undefined;
const b:string = undefined;
const c:object = undefined;
const d:void = undefined;
Never
基本概念
一个很特殊的类型,表示的是那些永不存在的值的类型。 一般只在以下两种情况下使用。 (其实也很好理解,下边两个情况 一个永远会执行下去 一个会立刻发生错误 不是不返回 而是根本没有时间也来不及有返回类容)
// 函数永远不会有返回值时
function infiniteLoop(): never {
while (true) {
}
}
// 函数永远会抛出一个错误时
function error(message: string): never {
throw new Error(message);
}
never类型是任何类型的子类型,也可以赋值给任何类型; 然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never。
// never类型是任何类型的子类型,也可以赋值给任何类型;
const a:number = 123 as never;
const b:string = 'hello' as never;
// 然而,没有类型是never的子类型或可以赋值给never类型
const c:never = 123; // 错误: 不能将类型“number”分配给类型“never”
const d:never = 123 as never; // 正确:除了never本身之外
const f:never = 123 as any; // 即使 any也不可以赋值给never
用途
在 TypeScript 中,可以利用 never 类型的特性来实现详细的检查,具体示例如下
type Foo = string | number;
function controlFlowAnalysisWithNever(foo: Foo) {
if(typeof foo === "string") {
// 这里 foo 被收窄为 string 类型
} else if(typeof foo === "number") {
// 这里 foo 被收窄为 number 类型
} else {
// foo 在这里是 never
const check: never = foo;
}
}
注意在 else 分支里面,我们把收窄为 never 的 foo 赋值给一个显示声明的 never 变量。 如果一切逻辑正确,那么这里应该能够编译通过。 但是假如后来有一天你的同事修改了 Foo 的类型
type Foo = string | number | boolean;
而他忘记同时修改 controlFlowAnalysisWithNever
方法中的控制流程,这时候 else 分支的 foo 类型会被收窄为 boolean 类型,导致无法赋值给 never 类型,这时就会产生一个编译错误。
通过这个方式,我们可以确保 controlFlowAnalysisWithNever
方法总是穷尽了 Foo 的所有可能类型。
通过这个示例,我们可以得出一个结论: 使用 never 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。
和void区别
TypeScript 已经具有 never类型,为什么它需要一个 void类型。
尽管两者看起来很相似,但是它们代表了两个不同的概念:
没有显式返回值的函数会隐式返回 undefined。尽管我们通常说这样的函数 “什么也不返回”,但实际上它是会返回的。在这些情况下,我们通常忽略返回值。在 TypeScript 中这些函数的返回类型被推断为 void。 具有 never返回类型的函数 永不返回。它也不返回 undefined。该函数没有正常完成,这意味着它可能会抛出异常或根本无法退出执行。
never类型为 底部类型,也称为零类型或空类型。它通常表示为⊥,表示计算未将结果返回给调用方。void类型,在另一方面,是一个 单元类型(类型,它允许只有一个值),没有定义的操作。
类型断言
有时候你会比TypeScript更了解某个值的详细信息,通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。
类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。
类型断言有两种形式。 其一是“尖括号”语法,另一个为as语法
let someValue: any = "this is a string";
let strLength1: number = (<string>someValue).length;
let strLength2: number = (someValue as string).length;
类型推论
在有些没有明确指出类型的地方,ts的类型推论会帮助提供类型。 这种推断发生在初始化变量和成员,设置默认参数值和决定函数返回值时。
let x = 3; // 变量x的类型被推断为数字
最佳通用类型
当类型比较复杂时,ts会推断出一个最合适的通用类型。例如,
/**
* 为了推断arr的类型,我们必须考虑所有元素的类型。 这里有两种选择: number和string。
* 计算通用类型算法会考虑所有的候选类型,并给出一个兼容所有候选类型的类型。
* 即arr的每个元素的类型均为 number和string类型的交集部分
*/
let arr = [0, 1, 'hello'];
/**
* 有些时候找不到候选类型共享相同的通用类型
* 我们需要明确的指出类型
*/
let zoo1 = [new Cat(), new Dog(), new Snake()]; // 无法推断最佳通用类型的话,最终类型推断的结果为联合数组类型,(Cat | Dog | Snake)[]。
let zoo2: Animal[] = [new Cat(), new Dog(), new Snake()]; // 明确的指出类型,这是最好的,别偷懒
上下文类型
按照相反的方向进行来类型推论
/**
* 上下文归类会在很多情况下使用到
* 如 函数的参数和返回值,赋值表达式的右边,类型断言,对象成员和数组字面量等
*/
const getName = ()=>{
return 'hello'
}
getName().parseInt(); // 报错:类型“string”上不存在属性“parseInt” 因为直接根据上下文推导出是string类型的
转载自:https://juejin.cn/post/7172012682608902180