TypeScript入门指南
新系列深入浅出TypeScript来了,本系列至少20篇。本文为第一篇,来介绍一下TypeScript以及常见的类型。
一、为什么学习TypeScript?
TypeScript是一门由微软推出的开源的、跨平台的编程语言。它是JavaScript的超集,扩展了JavaScript的语法,最终会被编译为JavaScript代码。
TypeScript的主要特性:超集:TypeScript是JavaScript的超集;类型系统:TypeScript在JavaScript的基础上,包装了类型机制,使其变身为静态类型语言;编辑器功能:增强了编辑器和IDE功能,包括代码补全、接口提示、跳转到定义、重构等;错误提示:可以在编译阶段就发现大部分错误,帮助调试程序。
TypeScript主要是为了实现以下两个目标:为JavaScript提供可选的类型系统;兼容当前以及未来的JavaScript的特性。
下面就来看看这两个目标是如何实现的。1。TypeScript的类型系统
为什么要给JavaScript加上类型呢?
我们知道,JavaScript是一种轻量级的解释性脚本语言。也是弱类型、动态类型语言,允许隐式转换,只有运行时才能确定变量的类型。正是因为在运行时才能确定变量的类型,JavaScript代码很多错误在运行时才能发现。TypeScript在JavaScript的基础上,包装了类型机制,使其变身成为静态类型语言。在TypeScript中,不仅可以轻易复用JavaScript的代码、最新特性,还能使用可选的静态类型进行检查报错,使得编写的代码更健壮、更易于维护。
下面是JavaScript项目中最常见的十大错误,如果使用TypeScript,那么在编写阶段就可以发现并解决很多JavaScript错误了:
类型系统能够提高代码的质量和可维护性,经过不断的实践,以下两点尤其需要注意:类型有利于代码的重构,它有利于编译器在编译时而不是运行时捕获错误;类型是出色的文档形式之一,函数签名是一个定理,而函数体是具体的实现。
可以认为,在所有操作符之前,TypeScript都能检测到接收的类型(在代码运行时,操作符接收的是实际数据;在静态检测时,操作符接收的则是类型)是否被当前操作符所支持。当TypeScript类型检测能力覆盖到所有代码后,任意破坏约定的改动都能被自动检测出来,并提出类型错误。因此,可以放心地修改、重构业务逻辑,而不用担忧因为考虑不周而犯下低级错误。
在一些语言中,类型总是有一些不必要的复杂的存在方式,而TypeScript尽可能地降低了使用门槛,它是通过如下方式来实现的。(1)JavaScript即TypeScript
TypeScript与JavaScript本质并无区别,我们可以将TypeScipt理解为是一个添加了类型注解的JavaScript,为JavaScript代码提供了编译时的类型安全。
实际上,TypeScript是一门中间语言,因为它最终会转化为JavaScript,再交给浏览器解释、执行。不过TypeScript并不会破坏JavaScript原有的体系,只是在JavaScript的基础上进行了扩展。
准确的说,TypeScript只是将JavaScript中的方法进行了标准化处理:TypeScript只提供一种新的语法,并不会帮我们解决BUG;TypeScript是一种新的语言,它使我们远离运行时。(2)类型可以是隐式的
TypeScript会尽可能的去推断类型信息,以便在开发过程中以更小的成本为我们提供类型安全。通俗来讲就是,即使我们不去显式的声明变量的类型,TypeScript也会自动判断数据类型。比如下面的代码:leta1a996
这段代码在TypeScript中就会报错,因为TS会知道a是一个数字类型,不能将其他类型的值赋值给a,这种类型的推断是很有必要的。(3)类型可以是显式的
上面说了,TypeScript会尽可能安全的推断类型。我们也可以使用类型注释,以实现以下两件事:帮助编译器快速判断变量类型;强制编译器编译我们认为该去编译的内容,这可以让编译器对代码所做的算法分析与我们对代码的理解所匹配。(4)类型是结构化的
在一些语言中,类型总是有一些不必要的复杂的存在方式,而TypeScript的类型是结构化的。比如下面的例子中,函数会接受它所期望的参数:interfacePoint2D{x:number;y:number;}interfacePoint3D{x:number;y:number;z:number;}constpoint2D:Point2D{x:10,y:10}constpoint3D:Point3D{x:20,y:20,z:20}functiontakePoint2D(point:Point2D){}takePoint2D(point2D)正确,完全匹配takePoint2D(point3D)正确,可以有额外的信息takePoint2D({x:30})错误,缺少y(5)类型错误不会影响JS运行
为了便于把JavaScript代码迁移至TypeScript,即使存在编译错误,在默认的情况下,TypeScript也会尽可能的被编译为JavaScript代码。因此,我们可以将JavaScript代码逐步迁移至TypeScript。(6)类型可以由环境来定义
TypeScript的一大目标就是让我们能够安全、轻松的使用现有的JavaScript库,它通过声明做到了这一点。TypeScript提供了个动态的标准来衡量我们在声明中的投入,投入的越多,获得的类型安全和代码只能提示越多。需要注意的是,大多数的流行JavaScript库都有自己的声明文件。这里不再细说声明文件,后面会有一篇来专门介绍声明文件。2。支持未来JavaScript所有功能
虽然TypeScript是JavaScript的超集,但它始终紧跟ECMAScript标准,所以是支持ES6789等新语法标准的。并且,在语法层面上对一些语法进行了扩展。TypeScript团队也正在积极的添加新功能的支持,这些功能会随着时间的推移而越来越多,越来越全面。
虽然TypeScript比较严谨,但是它并没有让JavaScript失去其灵活性。TypeScript由于兼容JavaScript所以灵活度可以媲美JavaScript,比如可以在任何地方将类型定义为any(当然,并不推荐这样使用),毕竟TypeScript对类型的检查严格程度是可以通过tsconfig。json来配置的。
二、快速搭建TypeScript开发环境1。开发工具
在搭建TypeScript环境之前,先来看看适合TypeScript的IDE,这里主要介绍VisualStudioCode,笔者就一直使用这款编辑器。
VSCode可以说是微软的亲儿子了,其具有以下优势:在传统语法高亮、自动补全功能的基础上拓展了基于变量类型、函数定义,以及引入模块的智能补全;支持在编辑器上直接运行和调试应用;内置了GitComands,能大幅提升使用Git协同开发效率;有丰富的插件市场,可以根据需要选择适合的插件。
因为VSCode中内置了特定版本的TypeScript语言服务,所以它天然支持TypeScript语法解析和类型检测,且这个内置的服务与手动安装的TypeScript完全隔离。因此,VSCode支持在内置和手动安装版本之间动态切换语言服务,从而实现对不同版本的TypeScript的支持。
如果当前应用目录中安装了与内置服务不同版本的TypeScript,我们就可以点击VSCode底部工具栏的版本号信息,从而实现useVSCodesVersion和useWorkspacesVersion两者之间的随意切换。
除此之外,VSCode也基于TypeScript语言服务提供了准确的代码自动补全功能,并显示详细的类型定义信息,大大的提升了我们的开发效率。
2。搭建开发环境(1)代码初始化
1)全局安装TypeScript:npmnpminstallgtypescriptyarmyarnglobaladdtypescript查看版本tscv
2)初始化配置文件:tscinit
执行之后,项目根目录会出现一个tsconfig。json文件,里面包含ts的配置项(可能因为版本不同而配置略有不同)。{compilerOptions:{target:es5,指定ECMAScript目标版本:ES5module:commonjs,指定使用模块:commonjs,amd,system,umdores2015moduleResolution:node,选择模块解析策略experimentalDecorators:true,启用实验性的ES装饰器allowSyntheticDefaultImports:true,允许从没有设置默认导出的模块中默认导入。sourceMap:true,把ts文件编译成js文件的时候,同时生成对应的map文件strict:true,启用所有严格类型检查选项noImplicitAny:true,在表达式和声明上有隐含的any类型时报错alwaysStrict:true,以严格模式检查模块,并在每个文件里加入usestrictdeclaration:true,生成相应的。d。ts文件removeComments:true,删除编译后的所有的注释noImplicitReturns:true,不是函数的所有返回路径都有返回值时报错importHelpers:true,从tslib导入辅助工具函数lib:〔es6,dom〕,指定要包含在编译中的库文件typeRoots:〔nodemodulestypes〕,outDir:。dist,rootDir:。src},include:〔需要编译的ts文件表示文件匹配表示忽略文件的深度问题。src。ts〕,exclude:〔不需要编译的ts文件nodemodules,dist,。test。ts,〕}
可以在package。json中加入script命令:{name:tsdemo,version:1。0。0,description:,main:srcindex。ts,scripts:{build:tsc,执行编译build:w:tscw监听变化},author:,license:ISC,devDependencies:{TypeScript:4。1。2}}
3)编译ts代码:tscindex。ts(2)配置TSLint
TSLint是一个通过tslint。json进行配置的插件,在编写TypeScript代码时,可以对代码风格进行检查和提示。如果对代码风格有要求,就需要用到TSLint了。其使用步骤如下:(1)在全局安装TSLint:npminstalltslintg
(2)使用TSLint初始化配置文件:tslinti
执行之后,项目根目录下多了一个tslint。json文件,这就是TSLint的配置文件了,它会根据这个文件对代码进行检查,生成的tslint。json文件有下面几个字段:{defaultSeverity:error,extends:〔tslint:recommended〕,jsRules:{},rules:{},rulesDirectory:〔〕}
这些字段的含义如下;defaultSeverity:提醒级别,如果为error则会报错,如果为warning则会警告,如果设为off则关闭,那TSLint就关闭了;Tyextends:可指定继承指定的预设配置规则;jsRules:用来配置对。js和。jsx文件的校验,配置规则的方法和下面的rules一样;rules:TSLint检查代码的规则都是在这个里面进行配置,比如当我们不允许代码中使用eval方法时,就要在这里配置noeval:true;rulesDirectory:可以指定规则配置文件,这里指定相对路径。
三、简单基础类型
在说TypeScript数据类型之前,先来看看在TypeScript中定义数据类型的基本语法。
在语法层面,缺省类型注解的TypeScript与JavaScript完全一致。因此,可以把TypeScript代码的编写看作是为JavaScript代码添加类型注解。
在TypeScript语法中,类型的标注主要通过类型后置语法来实现:变量:类型letnum996letnum:number996
上面代码中,第一行的语法是同时符合JavaScript和TypeScript语法的,这里隐式的定义了num是数字类型,我们就不能再给num赋值为其他类型。而第二行代码显式的声明了变量num是数字类型,同样,不能再给num赋值为其他类型,否则就会报错。
在JavaScript中,原始类型指的是非对象且没有方法的数据类型,包括:number、boolean、string、null、undefined、symbol、bigInt。
它们对应的TypeScript类型如下:
JavaScript原始基础类型TypeScript类型numbernumberbooleanbooleanstringstringnullnullundefinedundefinedsymbolsymbolbigIntbigInt
需要注意number和Number的区别:TypeScript中指定类型的时候要用number,这是TypeScript的类型关键字。而Number是JavaScript的原生构造函数,用它来创建数值类型的值,这两个是不一样的。包括string、boolean等都是TypeScript的类型关键字,而不是JavaScript语法。1。number
TypeScript和JavaScript一样,所有数字都是浮点数,所以只有一个number类型。
TypeScript还支持ES6中新增的二进制和八进制字面量,所以TypeScript中共支持2、8、10和16这四种进制的数值:letnum:number;num123;num123;error不能将类型123分配给类型numbernum0b1111011;二进制的123num0o173;八进制的123num0x7b;十六进制的1232。string
字符串类型可以使用单引号和双引号来包裹内容,但是如果使用Tslint规则,会对引号进行检测,使用单引号还是双引号可以在Tslint规则中进行配置。除此之外,还可以使用ES6中的模板字符串来拼接变量和字符串会更为方便。letstr:stringHelloWorld;strHelloTypeScript;constfirstHello;constlastTypeScript;str{first}{last};console。log(str)结果:HelloTypeScript3。boolean
类型为布尔值类型的变量的值只能是true或者false。除此之外,赋值给布尔值的值也可以是一个计算之后结果为布尔值的表达式:letbool:booleanfalse;booltrue;letbool:boolean!!0console。log(bool)false4。null和undefined
在JavaScript中,undefined和null是两个基本数据类型。在TypeScript中,这两者都有各自的类型,即undefined和null,也就是说它们既是实际的值,也是类型。这两种类型的实际用处不是很大。letu:undefinedundefined;letn:nullnull;
注意,第一行代码可能会报一个tslint的错误:Unnecessaryinitializationtoundefined,就是不能给一个变量赋值为undefined。但实际上给变量赋值为undefined是完全可以的,所以如果想让代码合理化,可以配置tslint,将nounnecessaryinitializer设置为false即可。
默认情况下,undefined和null是所有类型的子类型,可以赋值给任意类型的值,也就是说可以把undefined赋值给void类型,也可以赋值给number类型。当在tsconfig。json的compilerOptions里设置为strictNullChecks:true时,就必须严格对待了。这时undefined和null将只能赋值给它们自身或者void类型。这样也可以规避一些错误。5。bigInt
BigInt是ES6中新引入的数据类型,它是一种内置对象,它提供了一种方法来表示大于21的整数,BigInt可以表示任意大的整数。
使用BigInt可以安全地存储和操作大整数,即使这个数已经超出了JavaScript构造函数Number能够表示的安全整数范围。
我们知道,在JavaScript中采用双精度浮点数,这导致精度有限,比如Number。MAXSAFEINTEGER给出了可以安全递增的最大可能整数,即21,来看一个例子:constmaxNumber。MAXSAFEINTEGER;constmax1max1constmax2max2max1max2true
可以看到,最终返回了true,这就是超过精读范围造成的问题,而BigInt正是解决这类问题而生的:constmaxBigInt(Number。MAXSAFEINTEGER);constmax1max1nconstmax2max2nmax1max2false
这里需要用BigInt(number)把Number转化为BigInt,同时如果类型是BigInt,那么数字后面需要加n。
在TypeScript中,number类型虽然和BigInt都表示数字,但是实际上两者类型是完全不同的:declareletfoo:number;declareletbar:bigint;foobar;error:Typebigintisnotassignabletotypenumber。barfoo;error:Typenumberisnotassignabletotypebigint。6。symbol
symbol我们平时用的比较少,所以可能了解也不是很多,这里就详细来说说symbol。(1)symbol基本使用
symbol是ES6新增的一种基本数据类型,它用来表示独一无二的值,可以通过Symbol构造函数生成。constsSymbol();typeofs;symbol
注意:Symbol前面不能加new关键字,直接调用即可创建一个独一无二的symbol类型的值。
可以在使用Symbol方法创建symbol类型值的时候传入一个参数,这个参数需要是一个字符串。如果传入的参数不是字符串,会先自动调用传入参数的toString方法转为字符串:consts1Symbol(TypeScript);consts2Symbol(Typescript);console。log(s1s2);false
上面代码的第三行可能会报一个错误:Thisconditionwillalwaysreturnfalsesincethetypesuniquesymbolanduniquesymbolhavenooverlap。这是因为编译器检测到这里的s1s2始终是false,所以编译器提醒这代码写的多余,建议进行优化。
上面使用Symbol创建了两个symbol对象,方法中都传入了相同的字符串,但是两个symbol值仍然是false,这就说明了Symbol方法会返回一个独一无二的值。Symbol方法传入的这个字符串,就是方便我们区分symbol值的。可以调用symbol值的toString方法将它转为字符串:consts1Symbol(Typescript);console。log(s1。toString());Symbol(Typescript)console。log(Boolean(s));trueconsole。log(!s);false
在TypeScript中使用symbol就是指定一个值的类型为symbol类型:leta:symbolSymbol()
TypeScript中还有一个uniquesymbol类型,它是symbol的子类型,这种类型的值只能由Symbol()或Symbol。for()创建,或者通过指定类型来指定变量是这种类型。这种类型的值只能用于常量的定义和用于属性名。需要注意,定义uniquesymbol类型的值,必须用const而不能用let来声明。下面来看在TypeScript中使用Symbol值作为属性名的例子:constkey1:uniquesymbolSymbol()letkey2:symbolSymbol()constobj{〔key1〕:value1,〔key2〕:value2}console。log(obj〔key1〕)value1console。log(obj〔key2〕)error类型symbol不能作为索引类型使用。(2)symbol作为属性名
在ES6中,对象的属性是支持表达式的,可以使用于一个变量来作为属性名,这对于代码的简化有很多用处,表达式必须放在大括号内:letpropname;constobj{〔prop〕:TypeScript};console。log(obj。name);TypeScript
symbol也可以作为属性名,因为symbol的值是独一无二的,所以当它作为属性名时,不会与其他任何属性名重复。当需要访问这个属性时,只能使用这个symbol值来访问(必须使用方括号形式来访问):letnameSymbol();letobj{〔name〕:TypeScript};console。log(obj);{Symbol():TypeScript}console。log(obj〔name〕);TypeScriptconsole。log(obj。name);undefined
在使用obj。name访问时,实际上是字符串name,这和访问普通字符串类型的属性名是一样的,要想访问属性名为symbol类型的属性时,必须使用方括号。方括号中的name才是我们定义的symbol类型的变量name。(3)symbol属性名遍历
使用Symbol类型值作为属性名,这个属性是不会被forin遍历到的,也不会被Object。keys()、Object。getOwnPropertyNames()、JSON。stringify()等方法获取到:constnameSymbol(name);constobj{〔name〕:TypeScript,age:18};for(constkeyinobj){console。log(key);}ageconsole。log(Object。keys(obj));〔age〕console。log(Object。getOwnPropertyNames(obj));〔age〕console。log(JSON。stringify(obj));{age:18}
虽然这些方法都不能访问到Symbol类型的属性名,但是Symbol类型的属性并不是私有属性,可以使用Object。getOwnPropertySymbols方法获取对象的所有symbol类型的属性名:constnameSymbol(name);constobj{〔name〕:TypeScript,age:18};constSymbolPropNamesObject。getOwnPropertySymbols(obj);console。log(SymbolPropNames);〔Symbol(name)〕console。log(obj〔SymbolPropNames〔0〕〕);TypeScript
除了这个方法,还可以使用ES6提供的Reflect对象的静态方法Reflect。ownKeys,它可以返回所有类型的属性名,Symbol类型的也会返回:constnameSymbol(name);constobj{〔name〕:TypeScript,age:18};console。log(Reflect。ownKeys(obj));〔age,Symbol(name)〕(4)symbol静态方法
Symbol包含两个静态方法,for和keyFor。1)Symbol。for()
用Symbol创建的symbol类型的值都是独一无二的。使用Symbol。for方法传入字符串,会先检查有没有使用该字符串调用Symbol。for方法创建的symbol值。如果有,返回该值;如果没有,则使用该字符串新创建一个。使用该方法创建symbol值后会在全局范围进行注册。constiframedocument。createElement(iframe);iframe。srcString(window。location);document。body。appendChild(iframe);iframe。contentWindow。Symbol。for(TypeScript)Symbol。for(TypeScript);true注意:如果你在JavaScript环境中这段代码是没有问题的,但是如果在TypeScript开发环境中,可能会报错:类型Window上不存在属性Symbol。因为这里编译器推断出iframe。contentWindow是Window类型,但是TypeScript的声明文件中,对Window的定义缺少Symbol这个字段,所以会报错,
上面代码中,创建了一个iframe节点并把它放在body中,通过这个iframe对象的contentWindow拿到这个iframe的window对象,在iframe。contentWindow上添加一个值就相当于在当前页面定义一个全局变量一样。可以看到,在iframe中定义的键为TypeScript的symbol值在和在当前页面定义的键为TypeScript的symbol值相等,说明它们是同一个值。
2)Symbol。keyFor()该方法传入一个symbol值,返回该值在全局注册的键名:constsymSymbol。for(TypeScript);console。log(Symbol。keyFor(sym));TypeScript
四、复杂基础类型
看完简单的数据类型,下面就来看看比较复杂的数据类型,包括JavaScript中的数组和对象,以及TypeScript中新增的元组、枚举、Any、void、never、unknown。1。array
在TypeScript中有两种定义数组的方式:直接定义:通过number〔〕的形式来指定这个类型元素均为number类型的数组类型,推荐使用这种写法。数组泛型:通过Array的形式来定义,使用这种形式定义时,tslint可能会警告让我们使用第一种形式定义,可以通过在tslint。json的rules中加入arraytype:〔false〕就可以关闭tslint对这条的检测。letlist1:number〔〕〔1,2,3〕;letlist2:Arraynumber〔1,2,3〕;
以上两种定义数组类型的方式虽然本质上没有任何区别,但是更推荐使用第一种形式来定义。一方面可以避免与JSX语法冲突,另一方面可以减少代码量。
注意,这两种写法中的number指定的是数组元素的类型,也可以在这里将数组的元素指定为其他任意类型。如果要指定一个数组里的元素既可以是数值也可以是字符串,那么可以使用这种方式:numberstring〔〕。2。object
在JavaScript中,object是引用类型,它存储的是值的引用。在TypeScript中,当想让一个变量或者函数的参数的类型是一个对象的形式时,可以使用这个类型:letobj:objectobj{name:TypeScript}obj123error不能将类型123分配给类型objectconsole。log(obj。name)error类型object上不存在属性name
可以看到,当给一个对象类型的变量赋值一个对象时,就会报错。对象类型更适合以下场景:functiongetKeys(obj:object){returnObject。keys(obj)会以列表的形式返回obj中的值}getKeys({a:a})〔a〕getKeys(123)error类型123的参数不能赋给类型object的参数3。元组
在JavaScript中并没有元组的概念,作为一门动态类型语言,它的优势是支持多类型元素数组。但是出于较好的扩展性、可读性和稳定性考虑,我们通常会把不同类型的值通过键值对的形式塞到一个对象中,再返回这个对象,而不是使用没有任何限制的数组。TypeScript的元组类型正好弥补了这个不足,使得定义包含固定个数元素、每个元素类型未必相同的数组成为可能。
元组可以看做是数组的扩展,它表示已知元素数量和类型的数组,它特别适合用来实现多值返回。确切的说,就是已知数组中每一个位置上的元素的类型,可以通过元组的索引为元素赋值::letarr:〔string,number,boolean〕;arr〔a,2,false〕;successarr〔2,a,false〕;error不能将类型number分配给类型string。不能将类型string分配给类型number。arr〔a,2〕;errorProperty2ismissingintype〔string,number〕butrequiredintype〔string,number,boolean〕arr〔1〕996
可以看到,定义的arr元组中,元素个数和元素类型都是确定的,当为arr赋值时,各个位置上的元素类型都要对应,元素个数也要一致。
当访问元组元素时,TypeScript也会对元素做类型检查,如果元素是一个字符串,那么它只能使用字符串方法,如果使用别的类型的方法,就会报错。
在TypeScript新的版本中,TypeScript会对元组做越界判断。超出规定个数的元素称作越界元素,元素赋值必须类型和个数都对应,不能超出定义的元素个数。
在新的版本中,〔string,number〕元组类型的声明效果上可以看做等同于下面的声明:interfaceTupleextendsArraynumberstring{0:string;1:number;length:2;}
这里定义了接口Tuple,它继承数组类型,并且数组元素的类型是number和string构成的联合类型,这样接口Tuple就拥有了数组类型所有的特性。并且指定索引为0的值为string类型,索引为1的值为number类型,同时指定length属性的类型字面量为2,这样在指定一个类型为这个接口Tuple时,这个值必须是数组,而且如果元素个数超过2个时,它的length就不是2是大于2的数了,就不满足这个接口定义了,所以就会报错;当然,如果元素个数不够2个也会报错,因为索引为0或1的值缺失。4。枚举
TypeScript在ES原有类型基础上加入枚举类型,使得在TypeScript中也可以给一组数值赋予名字,这样对开发者比较友好。枚举类型使用enum来定义:enumRoles{SUPERADMIN,ADMIN,USER}
上面定义的枚举类型的Roles,它有三个值,TypeScript会为它们每个值分配编号,默认从0开始,在使用时,就可以使用名字而不需要记数字和名称的对应关系了:enumRoles{SUPERADMIN0,ADMIN1,USER2}constsuperAdminRoles。SUPERADMIN;console。log(superAdmin);0console。log(Roles〔1〕)ADMIN
除此之外,还可以修改这个数值,让SUPERADMIN1,这样后面的值就分别是2和3。当然还可以给每个值赋予不同的、不按顺序排列的值:enumRoles{SUPERADMIN1,ADMIN3,USER7}5。any
在编写代码时,有时并不清楚一个值是什么类型,这时就需要用到any类型,它是一个任意类型,定义为any类型的变量就会绕过TypeScript的静态类型检测。对于声明为any类型的值,可以对其进行任何操作,包括获取事实上并不存在的属性、方法,并且TypeScript无法检测其属性是否存在、类型是否正确。
我们可以将一个值定义为any类型,也可以在定义数组类型时使用any来指定数组中的元素类型为任意类型:letvalue:any;value123;valueabc;valuefalse;constarray:any〔〕〔1,a,true〕;
any类型会在对象的调用链中进行传导,即any类型对象的任意属性的类型都是any,如下代码所示:letobj:any{};letzobj。x。y。z;z的类型是any,不会报错z();success
需要注意:不要滥用any类型,如果代码中充满了any,那TypeScript和JavaScript就毫无区别了,所以除非有充足的理由,否则应该尽量避免使用any,并且开启禁用隐式any的设置。6。void
void和any相反,any是表示任意类型,而void是表示没有类型,就是什么类型都不是。这在定义函数,并且函数没有返回值时会用到:constconsoleText(text:string):void{console。log(text);};
需要注意:void类型的变量只能赋值为undefined和null,其他类型不能赋值给void类型的变量。7。never
never类型指永远不存在值的类型,它是那些总会抛出异常或根本不会有返回值的函数表达式的返回值类型,当变量被永不为真的类型保护所约束时,该变量也是never类型。
下面的函数,总是会抛出异常,所以它的返回值类型是never,用来表明它的返回值是不存在的:consterrorFunc(message:string):never{thrownewError(message);};
never类型是任何类型的子类型,所以它可以赋值给任何类型;而没有类型是never的子类型,所以除了它自身以外,其他类型(包括any类型)都不能为never类型赋值。letneverVariable((){while(true){}})();neverVariable123;error不能将类型number分配给类型never
上面代码定义了一个立即执行函数,函数体是一个死循环,这个函数调用后的返回值类型为never,所以赋值之后neverVariable的类型是never类型,当给neverVariable赋值123时,就会报错,因为除它自身外任何类型都不能赋值给never类型。
基于never的特性,我们可以把never作为接口类型下的属性类型,用来禁止操作接口下特定的属性:constprops:{id:number,name?:never}{id:1}props。namenull;errorprops。namestr;errorprops。name1;error
可以看到,无论给props。name赋什么类型的值,它都会提示类型错误,这就相当于将name属性设置为了只读。8。unknown
unknown是TypeScript在3。0版本新增的类型,主要用来描述类型并不确定的变量。它看起来和any很像,但是还是有区别的,unknown相对于any更安全。
对于any,来看一个例子:letvalue:anyconsole。log(value。name)console。log(value。toFixed())console。log(value。length)
上面这些语句都不会报错,因为value是any类型,所以后面三个操作都有合法的情况,当value是一个对象时,访问name属性是没问题的;当value是数值类型的时候,调用它的toFixed方法没问题;当value是字符串或数组时获取它的length属性是没问题的。
当指定值为unknown类型的时候,如果没有缩小类型范围的话,是不能对它进行任何操作的。总之,unknown类型的值不能随便操作。那什么是类型范围缩小呢?下面来看一个例子:functiongetValue(value:unknown):string{if(valueinstanceofDate){returnvalue。toISOString();}returnString(value);}
这里由于把value的类型缩小为Date实例的范围内,所以进行了value。toISOString(),也就是使用ISO标准将Date对象转换为字符串。
使用以下方式也可以缩小类型范围:letresult:unknown;if(typeofresultnumber){result。toFixed();}
关于unknown类型,在使用时需要注意以下几点:任何类型的值都可以赋值给unknown类型:letvalue1:unknown;value1a;value1123;unknown不可以赋值给其它类型,只能赋值给unknown和any类型:letvalue2:unknown;letvalue3:stringvalue2;error不能将类型unknown分配给类型stringvalue1value2;unknown类型的值不能进行任何操作:letvalue4:unknown;value41;error对象的类型为unknown只能对unknown进行等或不等操作,不能进行其它操作:value1value2;value1!value2;value1value2;errorunknown类型的值不能访问其属性、作为函数调用和作为类创建实例:letvalue5:unknown;value5。age;errorvalue5();errornewvalue5();error
在实际使用中,如果有类型无法确定的情况,要尽量避免使用any,因为any会丢失类型信息,一旦一个类型被指定为any,那么在它上面进行任何操作都是合法的,所以会有意想不到的情况发生。因此如果遇到无法确定类型的情况,要先考虑使用unknown。文章来源于公众号前端充电宝
https:mp。weixin。qq。comslKe5WgRAHicgUM9zexYA
最全肾病药物服用方法大盘点,一文说清注意事项各种药物的吃法不太一样,如果不注意一些细节,不仅会影响药效的发挥,甚至对身体造成伤害!关于药物服用的时间,里面有很多讲究,今天就来给大家介绍一下不同药物……
7月16入伏,建议多吃1豆1苦1水多食物,安稳过三伏天七月的盛夏是独特的,蝉鸣声和蛙叫声此起彼伏,火辣辣的太阳挂在高空中,一出门就能感受到阵阵热浪袭来,这时候你是不是想随身携带空调呢?7月16日就入伏了,除了要多补充水分,还……
你吃过哪些不经常吃的水果?谢谢!由于南北方气温相差较大,种出的水果等就有所不同,南方长出的荔枝、龙眼等在北方就很少;而北方长出的苹果、梨等在南方也就很少。现在虽然交通方便,但靠运输的都比较昂贵,这……
网红酸辣柠檬鸡爪,追剧小吃炎热夏季开胃菜系,酸辣柠檬凤爪。网红级美食,巨好吃口零基础教程〔食材〕白鸡爪700克、蒜瓣10粒、小葱三根、姜5片、柠檬一个(大)、小米椒15根、青杭椒4根(……
为什么高原高海拔地区空气稀薄呢地球是一个质量很大的天体。牛顿的万有引力告诉我们,只要有质量,就会对周围的物体产生一个吸引力,这个吸引力就是万有引力,意即万物都存在的吸引力。既然万有引力可以应用在任何物……
歌尔的估值贵了?歌尔股份发布2022年报,营业收入增长34。10至1049亿元,归母及净利润下滑59。08至17。49亿元,四季度下滑较为严重,净利润直接亏损20。91亿元。营收增速看似……
济南有啥正宗好吃的清真餐厅推荐?新疆居在济南这是一家非常地道正宗的的新疆菜馆。大盘鸡真的很棒,做法也非常的正宗,特别的好吃,比三江源什么的好吃多了,不过量没有那么多。羊肉串也很好吃,特别正宗,但是价格稍……
建议中老年人,少吃腌肉咸菜,多吃这2道菜,营养足,简单易做春天是吃鱼的好季节,孩子多吃鱼能够补充身体所需要的蛋白质和钙,长高个。老人多吃鱼,也能多补充身体所需要的营养,补充骨骼所需要的微量元素,那么怎么把鱼做得好吃又味美?今天笔……
如何在家自制腐乳?前几天麻麻一直说要自己腌制豆腐乳,我也没太在意,没有想到做出来这么好吃,发到朋友圈问的人那个多呀就特意又去买了豆腐回来,这不一做好就立马飞奔来这里和大家分享啦,不要太感动哦不过……
武汉热干面是谁发明的?武汉坊间流传的通俗说法是:热干面的问世,纯属偶然。约在70年前,汉口长堤街关帝庙一带有个姓李的熟食小贩,因脖子上长了一个肉瘤,别人称他李包。他原来卖的是凉粉和汤面。……
你心目中,大连最有名的是什么?谢谢特邀!要问我大连最有名的是什么?我是土生土长大连人有知情权!我答:要说大连特产瓜果类:大樱桃。歇马杏、大苹果、蓝莓、大黄桃等。大连海鲜特产:长海岛屿刺参、獐子岛鲍鱼,庄河花……
TOPBRANDSpreadChefKart获融资贝尔集团收编辑阳子最新融资日本京都室内农场Spread融资3050万美元图片来源:品牌官方近日,总部位于日本京都的室内农场Spread宣布完成40亿日元(折合30……