TypeScript (JavaScript的超集)
带图的笔记
TypeScript的概述
TypeScript是JavaScript的超集,所谓超集就是在JavaScript原有的基础之上多了一些特性,包括类型系统和ES6+的支持,写完代码后编译成JavaScript
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ijfw8WdO-1597675674567)(8B9EB65EEE5548B589B75B76E1BC42E5)]
TypeScript最低能编译ES3版本的JavaScript,兼容性良好,任何一种JavaScript运行环境都支持,如浏览器应用,node应用,react-native,electron等等
TypeScript相比JavaScript功能更强大,生态更健全、更完善,特别是开发工具这块,微软对开发工具的支持都特别友好,angular和vue3.0都是用JavaScript开发的,适合长期维持的大型项目
TypeScript的缺点
- 语言本身多了很多概念,如接口、泛型、枚举等等,会提高我们的学习成本,好在TypeScript是渐进式的,什么是渐进式?就是即便我们什么都不知道也可以按照JavaScript的语言去编写代码,我们完全可以学一个新特性使用一个新特性
- 项目初期,TypeScript会增加一些项目成本,如类型声明需要单独编写
TypeScript的基本使用(快速上手)
TypeScript本身是node模块,可以安装到全局,但考虑到项目依赖的问题还是把它安装到项目当中更加合理
- 初始化package.json
yarn init --yes
2.按照typescript模块依赖
yarn add typescript --dev
安装成功后可以在/node_modules/bin/中找到tsc命令,作用是编译typescript代码
3. 创建01-getting-started.ts文件
const hello = (name:string)=>{
console.log(`hello ${name}`)
}
hello(`hcb`)
- 运行编译命令,在编译过程中首先会检查代码中类型使用的异常,然后会移除掉类型注解之类的扩展语法,并且在这一过程中会自动去转换ECMAscript的新特性
yarn tsc 01-getting-started.ts
生成一个01-getting-started.js文件,里面是ES3的代码
var hello = function (name) {
console.log("hello " + name);
};
hello("hcb");
即便类型注解报错,tsc命令也能编译出一个js文件,
同时就印证了即便使用typescript的最新系统,我们也可以使用最新的js语法标准
配置文件
tsc命令不仅可以编译某个指定的文件,也可以编译整个项目,需要创建一个typescript的配置文件
- 在命令行生成配置文件
yarn tsc --init
生成tsconfig.json
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es2015", //所有标准都会转换成es5代码 /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true, //开启源代码映射,这样在调试的时候就能使用sourceMap调试源代码了 /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "dist", //用于设置编译结果输出到的文件夹 /* Redirect output structure to the directory. */
"rootDir": "src", //配置源代码也就是typescript代码所在的文件夹 /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options *///以下类型检查配置
"strict": true, //开启严格模式 ,需要为每个成员指定明确的类型 /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}
- 创建src文件夹,把ts文件移入
- 对这个项目 执行ts命令
yarn ts
生成的文件时es2015标准的js文件同时还生成了sourceMap文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mjALqvv0-1597675674568)(F4B1A4907F2C459EB5DED9B341F651B5)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BgD8Io9i-1597675674569)(4E6F7BA546B44B5BA8B8F4B17D3DA83F)]
原始类型
/**
* 原始数据类型
*/
// 非严格模式下string number boolean都允许为空为null,严格模式下是不 允许的
//在tsconfig中我们可以设置"strictNullChecks": true,启用严格的空检查。
const a:string = 'hcb'
const b:number = 100
const c:boolean = true
//void在非严格模式下能够为undefined或null,在严格模式下只能为undefined
const d:void = null // undefined//null在严格模式下会报错
const e: null = null
const f:undefined = undefined
const syb:symbol = Symbol();//这里用Symbol会报错这是为啥呢?
标准库声明
symbol会报错这是为啥呢?在tsconfig我们的target引用的es5标准库,es5的标准库没有Symbol这样一个内置类型
有两种解决办法
- 修改tsconfig中的target为es2015,使用的是es2015标准库
"target": "es2015",
- 如果我们需要编译成es5,可以使用tsconfig的lib选项指定标准库,由于console对象是在浏览器中BOM对象提供的,ts把BOM和DOM归结到一个标准库文件上,所以我们就可以写一个DOM标准库就行了
"lib": ["es2015","DOM"],
所谓标准库实际上就是内置对象所对应的声明文件,在代码当中使用这个对象就必须引用这个标准库,否则typescript就找不到所对应的类型,就会报错
TypeScript 中文错误消息
typescript本身是支持多语言化的错误消息的,默认会根据你的开发系统和语言的设置选择一个错误消息语言,如果想要强制显示中文的错误消息可以执行以下命令
yarn tsc --locale zh-CN
这样绝大错误都是中文消息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DhuX4Fbw-1597675674569)(1C3539C7E7B546BDB09930F199DE0D5E)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-arJbl0JK-1597675674570)(22DDEA644C7E4A3ABB9AF0679C8F9C00)]
小知识:对于vscode的消息可以在配置选项中配置在vscode–file–preference–setting搜索typescript locale勾掉对勾可以关闭js的语法检查,但是我们不建议这样做,中文错误消息在Google很难搜索到相关消息,尽量使用英文的方式
作用域问题
在学习typescript当中,肯定要尝试不同的特性,这种情况下我们可能会遇到不同文件当中会有相同变量名称的情况,如下:
//01.ts
const a:string = 'hhh'
//02.ts
const a:string = 'hcb'
两个同名的变量都是全局变量这样就会报错:Cannot redeclare block-scoped variable ‘a’.ts(2451)
解决这样问题的办法就是把相同名称的变量装在不同的作用域当中,例如:
//作用域问题
(function(){
const a:string = 'hh'
})()
或者使用export 作为模块导出一下,模块是有模块作用域的
const a:string = 'hh'
export {}
//这里的{}只是一个语法并不是空对象
这样的话,模块当中的所有成员就变成了局部成员了,也就不会再出现冲突的问题了
Object类型
TypeScript中的对象类型并不单指普通的对象类型,而是泛指所有的非原始类型,也就是对象、数组、函数
//Object类型
export {}
// typescript中的对象类型不单指普通对象,更包括数组和函数
const foo:object = function(){}//[]//{}
// 需要普通对象类型,要用对象字面量语法去声明
const obj: {foo:string,bar:number} = {foo:'hcb',bar:123}
更专业的方式是接口,后面会介绍
数组类型
和flow十分类似
// 数组类型
// 自变量声明时限制数组类型
const arr1:Array<number> = [1,2,3];
const arr2:number[] = [123,456,789];
// --------------------------------
// 在函数参数限制数组类型
function sum(...arg:number[]){
return arg.reduce((prev,current)=>prev*current)
}
//参数定义为数值类型的数组,不能出现别的类型的值,如果出现字符串类型语法上就会报错
sum(1,2,3,'ffc');//报错:Argument of type '"ffc"' is not assignable to parameter of type 'number'.
元组类型
其实元组就是明确元素数量和元素类型的数组,下面使用数组字面量定义元组类型
//元组类型 Tuple
export {}
//对应类型不相符或者数量超出长度限制都会报错
const tuple:[number,string] = [12,'boo'];
// const tuple:[number,string] = [12,11];//报错
// const tuple:[number,string] = [12,'cc',11]//报错
// 通过数组解构的方式提取每个元素
const [age,number] = tuple;
//---------------------------------------------------------
//元组类型越来越常见
Object.entries({
foo:123,
bar:555
})//=>返回可枚举的键值对数组
//因为Object.entries是es2017出现的,需要在tsconfig的lib添加值"ES2017"
枚举类型
我们在应用开发中经常需要用某几个数值代表某种状态,如果在代码中直接使用012代表自变量状态的话,时间久了我们可能搞不清楚对应的数字是什么状态,而且时间长了还会混进了一些其他的值,去使用012以外的数字,这种情况使用枚举类型的值是最合适的了
//枚举(Enum)
export {}
const post = {
title:'hello TypeScript',
content:'ts是js的超集',
status:0,//状态:0草稿1未发布2发布//如果在代码中直接使用012代表自变量状态的话,时间久了我们可能搞不清楚对应的数字是什么状态,而且时间长了还会混进了一些其他的值,去使用012以外的数字
}
因为枚举类型有两个特点
- 能够给一组数值取上一个更好的名字
- 一个枚举中只会存在几个固定的值,不会出现超出范围的可能性
枚举类型的基本使用
//枚举(Enum)
export {}
// 传统的JavaScript使用对象去模拟实现枚举
const PostStatus = {
Draft:0,
UnPubilshed:1,
Published:2
}
// 然后我们就可以在代码当中使用这些对象的属性表示状态,
//这样就不会出现遗忘数字值代表什么的问题
const post = {
title:'hello TypeScript',
content:'ts是js的超集',
status:PostStatus.Draft,//状态:0草稿1未发布2发布//如果在代码中直接使用012代表自变量状态的话,时间久了我们可能搞不清楚对应的数字是什么状态,而且时间长了还会混进了一些其他的值,去使用012以外的数字
}
//-------------------------------------
//现代的TypeScript当中有一个专门的枚举类型可以使用enum关键词声明枚举
// enum PostStatus2 {
// Draft = 0,//需要注意的是这里使用的是等号而不是冒号
// UnPubilshed = 1,
// Published = 2
// }
// 需要注意的,在值为number时,是不用等号赋值的时候默认初始值是0,后面的属性在不设值的情况下是上一个属性值+1递增,如下
// enum PostStatus2 {
// Draft = 6,//6//0
// UnPubilshed,//7//1
// Published//8//2
// }
// 枚举的值除了是数字外还可以是字符串,如果是字符串枚举的话,那我们需要手动的给每个枚举一个初始化的值
enum PostStatus2 {
Draft = 'aaa',
UnPubilshed ='bbb',
Published = 'ccc'
}
const post2 = {
title:'hello TypeScript',
content:'ts是js的超集',
status:PostStatus2.Draft,//使用枚举和使用对象的方式是一样的
}
需要注意的是枚举类型会入侵到我们运行时的代码,通俗的将就是会影响我们编译后的结果,我们在typescript使用的大部分类型经过编译转换过后都会被移除,因为他只是帮助我们在编译过程中做类型检查,而枚举不是,他最终会编译成一个双向的键值对对象,下面执行编译命令
yarn tsc
执行后的代码:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// 传统的JavaScript使用对象去模拟实现枚举
var PostStatus = {
Draft: 0,
UnPubilshed: 1,
Published: 2
};
// 然后我们就可以在代码当中使用这些对象的属性表示状态,
//这样就不会出现遗忘数字值代表什么的问题
var post = {
title: 'hello TypeScript',
content: 'ts是js的超集',
status: PostStatus.Draft,
};
//-------------------------------------
//现代的TypeScript当中有一个专门的枚举类型可以使用enum关键词声明枚举
// enum PostStatus2 {
// Draft = 0,//需要注意的是这里使用的是等号而不是冒号
// UnPubilshed = 1,
// Published = 2
// }
// 需要注意的,在值为number时,是不用等号赋值的时候默认初始值是0,后面的属性在不设值的情况下是上一个属性值+1递增,如下
// enum PostStatus2 {
// Draft = 6,//6//0
// UnPubilshed,//7//1
// Published//8//2
// }
// 枚举的值除了是数字外还可以是字符串,如果是字符串枚举的话,那我们需要手动的给每个枚举一个初始化的值
var PostStatus2;
//这是一个双向的键值对对象,键中有值,值中有键
(function (PostStatus2) {
PostStatus2["Draft"] = "aaa";
PostStatus2["UnPubilshed"] = "bbb";
PostStatus2["Published"] = "ccc";
})(PostStatus2 || (PostStatus2 = {}));
var post2 = {
title: 'hello TypeScript',
content: 'ts是js的超集',
status: PostStatus2.Draft,
};
//# sourceMappingURL=07-enum-types.js.map
所谓双向键值对就是可以通过键获取值也可以通过值获取键,这样做的目的是让我们动态的根据枚举的值如012这样的值获取枚举的名称
enum PostStatus2 {
Draft = 0,//需要注意的是这里使用的是等号而不是冒号
UnPubilshed = 1,
Published = 2
}
PostStatus2[0];//通过索引器的方式根据枚举的值动态获取枚举的名称
常量枚举
// 常量枚举,在enum前声明const
const enum PostStatus2 {
Draft ,
UnPubilshed ,
Published
}
const post2 = {
title:'hello TypeScript',
content:'ts是js的超集',
status:PostStatus2.Draft,//使用枚举和使用对象的方式是一样的
}
yarn tsc
执行后的js代码:
var post2 = {
title: 'hello TypeScript',
content: 'ts是js的超集',
status: 0 /* Draft */,
};
这里我们会看到枚举会被移除掉,使用枚举值的地方都会被替换成原来的具体数值,枚举的名称会在后面以注释的方式标注
以上就是在typescript中枚举类型的主要特征
函数类型
函数的类型约束主要是对函数的输入输出进行类型限制,输入是指参数,输出指返回值,不过呢在JavaScript有两种函数定义的方式,分别是函数声明和函数表达式,我们需要了解下在这两种方式如何进行类型约束
函数声明
// 函数声明
// 函数的参数类型和参数个数都是指定的,不能改变
//如果想某个参数是可选的就在参数名后加问号
function funcl (a:number,b?:number):string {
return 'funcl'
}
funcl(100,200)
funcl(100)//语法错误参数个数少一个
// funcl(100,200,300)//语法错误参数个数多一个
// 或者使用es6添加默认值的特性来解决参数个数的问题
function funcl2 (a:number,b:number = 123):string {
return 'funcl'
}
// 注意不管是可选参数还是默认参数都必须放在参数队列的最后位置
funcl2(111)
// 如果需要接收任意个数的参数,那么使用es6的rest操作符
function funcl3 (a:number,b:number = 123,...rest:number[]):string {
return 'funcl'
}
// 以上就是函数类型声明的一个对应的限制
函数表达式
// 函数表达式
// 在把函数作为参数传递那种回调函数的形式,
// 对于回调函数我们必须约束回调函数的参数类型和返回值
// 这种情况我们就可以使用类似箭头函数的形式来去表示函数
const func2:(a:number,b:number)=>string = function(a:number,b:number):string {
return 'func2'
}
这种方式我们在以后定义接口的时候会经常用到
任意类型
any类型是不安全的,弱类型,语法上不会报错,不建议用,主要用于兼容老的代码
// 任意类型---any类型
export {}
function stringify(value:any):any{
return JSON.stringify(value)
}
stringify(100)
stringify('ccc')
stringify(true)
let hello: any = 'string';
hello = 123
hello = false
hello = undefined
隐式类型推断
在typescript当中,如果我们明确没有通过类型注解去标记变量类型,那typescript会根据变量的使用情况去推断变量的类型, 这样一种特性叫做隐式类型推断.
//隐式类型推断
export {}
let age = 18
age = "string";
//出现类型错误,因为这个时候age已经被推断成了number类型
//这种用法实际上相当于给age变量添加了number注解
// ---------------------------------
// 如果typescript无法推断变量的类型注解,就会给变量添加any类型注解
let foo//声明变量是并没有值就是any类型,也就是动态类型,这样的变量赋予任何值都不会报错,这就是隐式推断
foo = 100
foo = 'string'
//建议为每个变量添加明确的类型,便于以后更直观的了解代码
类型断言
在有些特殊的情况下,typescript无法推断出变量的具体类型,我们开发者可以根据代码的情况可以知道是什么变量类型的
/类型断言
export {}
//假定这个num,来自一个,明确的接口
const nums = [100,110,120,119,112]
const res = nums.find(i => i>0);
/**我们一目了然的知道那个数大于零,知道数组值得类型,
* 但是typescript不知道,
* 它只知道这个地方的返回值是const res: number | undefined,
* 有可能找不到,此时我们就不能把这个值当成一个数字去使用,
* 这种情况下我们就可以去断言这个数字是number类型的,
* 断言的 意思是明确告诉typescript,
* 相信我,这个值明确是number类型的
* */
// const square = res*res
// 断言的两种方式
// 1.使用as关键词,此时我们的编辑器就能明确知道它是一个数字了
const nums1 = res as number
// 2.在变量的前面使用<>去断言,但是由于与jsx的标签有冲突不建议使用
const nums2 = <number>res
需要注意的是,类型断言不是类型转换,类型断言是代码编辑时用来明确代码类型的,不是代码运行时的类型转换,并且编译后类型断言也会消失
接口 interfaces
接口也是一种规范,更是一种契约,是一种抽象的概念,可以约定对象的结构,我们使用一个接口就必须遵循这个接口全部的约定
在typescript当中,接口最直观的体现就是用来约定一个对象当中具体应该有哪些成员,而且这些成员的类型又是什么样的
接口的基本作用
// 接口
export {}
// 2.定义接口
interface Post {
title:string//键值对的结尾可以用逗号/分号或者省略
content:string
}
/**
* 3.在函数中可以给post对象的类型设置Post接口,
* 此时就是显示的要求我们的对象必须有title和content这两个成员了
*/
function printPost (post:Post){
/**
* 1.这是对于post所接收的对象就有一定的要求,
* 我们传入的对象必须存在一个title属性和一个content属性,
* 只不过这种要求是隐形的,它没有明确的表达出来
* 那这种情况下,我们就可以使用接口表现出这种约束
*/
console.log(post.title)
console.log(post.content)
}
printPost({
title:'hello hcb',
content:'ts是js的超集'
})
总结:接口就是约束对象的结构,一个对象去实现接口,就必须拥有这个接口当中的所有成员,
typescript中的接口是只是为我们有结构的数据做类型约束的,在实际运行阶段这个接口并没有意义
接口补充(可选成员,只读成员,动态成员)
对于接口中约定的成员还有一些特殊的用法
- 可选成员,如果某个对象的成员可有可无的话可以在接口使用可选成员的的特性,用?表示
interface Post {
title:string//键值对的结尾可以用逗号/分号或者省略
content:string,
subtitle?:string
}
- 只读成员,不允许外界去设置它,使用readonly关键词去修饰,添加完readonly,我们的对象在初始化之后就不能修改这个成员了,如果再次修改就会报错
interface Post {
title:string//键值对的结尾可以用逗号/分号或者省略
content:string,
subtitle?:string,
readonly summary:string
}
实例
// 接口补充(可选成员,只读成员)
export {}
// 2.定义接口
interface Post {
title:string//键值对的结尾可以用逗号/分号或者省略
content:string,
subtitle?:string,
readonly summary:string
}
/**
* 3.在函数中可以给post对象的类型设置Post接口,
* 此时就是显示的要求我们的对象必须有title和content这两个成员了
*/
function printPost (post:Post){
/**
* 1.这是对于post所接收的对象就有一定的要求,
* 我们传入的对象必须存在一个title属性和一个content属性,
* 只不过这种要求是隐形的,它没有明确的表达出来
* 那这种情况下,我们就可以使用接口表现出这种约束
*/
console.log(post.title)
console.log(post.content)
}
const post:Post = {
title:'hello hcb',
content:'ts是js的超集',
summary:'只读,不允许修改'
}
// post.summary = '我就改了';//报错
printPost(post)
- 动态成员,一般用于具有动态成员的对象,例如缓存对象
// 动态成员用法
interface Cache {
[key:string]:string
}
const cache:Cache = {}
cache.foo = 'foo'
cache.bae = 'bar'
类 Classes
- 类的作用:描述一类具体事物的抽象特征,例如手机就是一个类型,手机的特征就是能够打电话发短信上网,在这个类型下面还有一些细分的子类,这种子类会满足父类的所有特征还会多出一些额外的特征,如智能手机,除了打电话发短信还能使用app
- 类用来描述一类具体对象的抽象成员,在es6以前js是通过函数+原型模拟实现类,从es6开始js有了专门的class
- 在typescript中的class除了拥有es6的class所有用法还有额外的用法,例如对类成员有特殊的访问修饰符,还有抽象类的概念
typescript中类的基本使用
// 类class
export {}
class Preson {
// 在typescript中我们需要在类的当中明确的声明类的属性
// 不是直接在构造函数当中动态的去添加属性
name:string //= 'who'//这个语法时es2017定义的
age:number
// 需要注意的是类的属性必须具备初始值,要么在类的中声明属性值赋值,要么在类的构造函数中赋值
constructor(name:string,age:number){
this.name = name;
this.age = age;
}
sayHi(msg:string) :void{
console.log(`I am ${this.name},${this.age}`)
}
}
类的访问修饰符
类的访问修饰符的作用:可以控制类当中成员的访问级别
- public修饰符声明类的共有属性,在typescript中,类声明名的属性默认是公有属性,public关键词可以被省略
public name:string
- private修饰符声明类的私有属性,只能在类的内部去访问
private age:number
- protected修饰符声明类的受保护属性,同样在类实例化后不能被直接访问,只允许在子类访问对应的成员
protected gender:boolean
- constructor构造函数式默认的修饰符是public,如果改成private变成私有的,类就不能实例化访问了,只能通过静态方法去访问类的实例化
export {}
class Person {
//public修饰符声明类的共有属性,在typescript中,类声明的属性默认是公有属性,public关键词可以被省略
public name:string
// private修饰符声明类的私有属性,只能在类的内部去访问
private age:number
// protected修饰符声明类的受保护属性,同样来类实例化后不能被直接访问,只允许在子类访问对应的成员
protected gender:boolean
constructor(name:string,age:number){
this.name = name;
this.age = age;
this.gender = true
}
sayHi(msg:string) :void{
console.log(`I am ${this.name},${this.msg}`)
console.log(this.age)
}
}
// 创建子类继承Person类
class Student extends Person {
private constructor(name:string,age:number){
super(name,age)
console.log(this.gender);//Property 'age' is private and only accessible within class 'Person'.通过this直接访问父类中protected修饰过的属性
}
static create(name:string,age:number){
return new Student(name,age);
}
}
const hcb = new Person('hcb',25)
console.log(hcb.name)
// console.log(hcb.age);//语法报错Property 'age' is private and only accessible within class 'Person'.age是私有属性不能直接访问
// console.log(hcb.gender);//Property 'age' is private and only accessible within class 'Person'.
// const gl = new Student('gl',7);//Constructor of class 'Student' is private and only accessible within the class declaration.
const gl = Student.create('gl',7)
- constructor构造函数如果被标记protected修饰符,这个类也是不能被实例化的,但是相比private是允许继承的
// 构造函数被标记为protected,这个类不允许被实例化,但是相比private,它允许被继承的
class Teacher extends Person {
protected constructor(name:string,age:number){
super(name,age)
this.gender = false;
}
}
// const mm = new Teacher('mm',18);//Constructor of class 'Teacher' is protected and only accessible within the class declaration.
class Woman extends Teacher {
constructor(name:string,age:number){
super(name,age);
}
}
const wm = new Woman('luck',33)
类的只读属性(readonly)
readonly可以把属性设置 为只读属性,如果这个属性已经有了访问修饰符的话,readonly应该跟在访问修饰符的后面
// 类的只读属性
export {}
class Person {
public name:string
private age:number
protected readonly gender:boolean
constructor(name:string,age:number){
this.name = name
this.age = age
this.gender = true
}
sayHi(msg:string):void{
console.log(`I am ${this.name},${msg}`)
console.log(this.age)
}
}
const hcb = new Person('hcb',25)
console.log(hcb.name)
对于只读属性,我们可以在类型声明的时候直接通过等号的方式初始化,也可以在构造函数中初始化,两者只能选其一,初始化之后不允许被修改
类与接口
相比于类,接口的概念要更为抽象一点,我们可以接着之前手机的例子作比较,手机是一个类型,类型的实例都是能够打电话发短信的,因为手机这个类的特征就是打电话发短信,但是呢,我们能够打电话的不仅仅只有手机,那在以前呢还有比较常见的座机也能打电话,但是座机并不属于手机的类目,而是一个单独的类目因为它不能够发短信,也不能拿着到处跑,那在这种情况下也会出现不同的类与类之间也会出现一些共同的特征,那对于这些公共的特征我们会使用接口去抽象,那你可以理解为手机也能打电话,因为它实现了打电话的协议,而座机也能够打电话,因为它也实现了这个相同的协议,那这里的协议呢,我们在程序当中叫接口,当然如果你是第一次接触这种概念的话,可能理解起来有些吃力,那我个人的经验的就是多思考,多从生活的角度去想,那如果实在想不通的话,那就多用,在用的过程中慢慢的总结规律,时间长了自然也就好了
下面这两个类有相同的特性,如吃东西,会跑,这种 情况下就属于不同的类型实现了相同的接口,那可能有人会问我们为什么不给他们之间去抽象一个公共的父类,然后把公共的方法都定义到父类当中,原因是人和动物都会吃都会跑,但是人和动物吃东西是不一样的,他们只是都得有这个能力,而这个能力的实现是不一样的,那在这种情况下,我们就可以使用接口去约束这样的公共能力
// 类与接口
export {}
// 下面这两个例子有共同的特性
class Person {
eat(food:string):void{
console.log(`优雅的进餐:${food}`)
}
run(distance:number):void{
console.log(`直立行走:${distance}`)
}
}
class Animal {
eat(food:string):void{
console.log(`呼噜呼噜的吃:${food}`)
}
run(distance:number):void{
console.log(`爬行:${distance}`)
}
}
下面使用接口去约束这两个类公共的能力
// 类与接口
export {}
// 定义接口,添加成员约束,使用函数签名的方式约束方法的类型,而不做具体的方法实现
interface EatAndRun{
eat(food:string):void
run(distance:number):void
}
// 下面这两个例子有共同的特性
// 使用implements实现接口,此时在类型中必须要有对应的成员,
//没有就会报错,因为实现了这个接口就必须要有对用的成员
class Person implements EatAndRun{
eat(food:string):void{
console.log(`优雅的进餐:${food}`)
}
run(distance:number):void{
console.log(`直立行走:${distance}`)
}
}
class Animal implements EatAndRun{
eat(food:string):void{
console.log(`呼噜呼噜的吃:${food}`)
}
run(distance:number):void{
console.log(`爬行:${distance}`)
}
}
这里需要注意的是在java语言中,建议我们应该让每个接口的定义更加简单,更加细化,比如你的小摩托车会跑但不会吃,所以一个更合理的建议就是一个接口只去约束一个能力,然后让一个类型同时去实现多个接口,这样会更加合理一些
// 类与接口
export {}
// 定义接口,添加成员约束,使用函数签名的方式约束方法的类型,而不做具体的方法实现
interface Eat{
eat(food:string):void
}
interface Run {
run(distance:number):void
}
// 下面这两个例子有共同的特性
// 使用implements实现接口,此时在类型中必须要有对应的成员,
//没有就会报错,因为实现了这个接口就必须要有对用的成员
class Person implements Eat,Run{
eat(food:string):void{
console.log(`优雅的进餐:${food}`)
}
run(distance:number):void{
console.log(`直立行走:${distance}`)
}
}
class Animal implements Eat,Run{
eat(food:string):void{
console.log(`呼噜呼噜的吃:${food}`)
}
run(distance:number):void{
console.log(`爬行:${distance}`)
}
}
题外话:不要把自己约束在一门语言或者技术上面,最好是多接触多学习一些周边的语言或技术,因为这样的话会补充你的知识体系,简单的说,一个只了解JavaScript语言的程序员不可能设计出更高级的产品,例如wvvm最早出现在微软的wps当中,视野应该放宽,当你有更宽的知识面的时候你能够把多家的思想更好的融合在一起
抽象类
抽象类在某种程度上来说和接口有点类似,它也是可以约束子类当中必须有某一个成员,但是不同于接口的是,抽象类可以包含一些具体的实现,而接口只能是一个成员的抽象,它不包含具体的实现,一般比较大的类目我们都建议使用抽象类,例如我们刚刚所说的动物类,其实它就应该是抽象的,因为我们所说的动物它只是一个泛指,它并不够具体,那它的下面一定会有更细化的分离,比如说小狗小猫之类的,而且生活当中都会说买了一只狗或一只猫,不会说买了一只动物
定义抽象类就是在class前加一个关键词abstract,定义成抽象类过后就只能被继承,不能再用new的方式去创建实例化对象了,这种情况下必须使用子类去继承这个类型
// 抽象类(abstract)
export {}
// 定义抽象类
abstract class Animal{
eat(food:string):void{
console.log(`呼噜呼噜的吃:${food}`)
}
// 定义抽象类的抽象方法,需要注意的是抽象方法也不需要方法体
// 当我们的父类有这样一个抽象方法时子类必须实现这个抽象方法
abstract run(distance:number):void
}
// 可以使用vscode的代码修正功能自动的去生成实现
class Dog extends Animal {
run(distance: number): void {
console.log(`四只脚跑:${distance}`)
}
}
// 此时我们实现的类就能同时拥有父类实现的方法以及自身实现的方法
const d = new Dog()
d.eat('宠粮')
d.run(200)
泛型
泛型就是指我们在定义接口或类的时候我们没有去指定类型,等到我们使用的时候再去指定具体类型的这样一个特征,简单的说,泛型就是指在函数声明时不去指定类型,等在调用时再去指定类型,这样做的目的是为了极大程度的复用我们的代码
//泛型
export {}
// 创建数值数组
function createNumberArray(length:number,value:number):number[]{
const arr = Array<number>(length).fill(value)
return arr;
}
// 创建字符串数组
function createStringArray(length:number,value:string):string[]{
const arr = Array<string>(length).fill(value)
return arr;
}
const numAry = createNumberArray(10,100)
const strAty = createStringArray(10,'voo')
// 以上这种情况,createNumberArray和createStringArray函数就会有冗余
//更合理的办法就是使用泛型,说白了就是把number和string这些变成 参数
//把类型变成参数,让我们在调用的时候去传递类型
function createArray<T>(length:number,value:T): T[] {
const arr = Array<T>(length).fill(value)
return arr;
}
const ary = createArray<string>(10,'aaa');
总的来说,泛型就是把我们定义时不能确定类型变成参数,让我们在使用时再去传递这样一个类型参数
类型声明
在实际的开发过程中我们难免会用到一些第三方的npm模块,而这些第三方模块并不是通过typescript编写的,如lodash,所以说它提供的成员不会有强类型的体验
// 类型声明
import { camelCase } from 'lodash'
// 使用declare语句进行类型声明,declare + 函数签名
declare function camelCase (input:string): string
// 使用类型声明后再去访问这个函数就会有类型限制了
const res = camelCase('hello')
类型声明说白了就是一个成员在定义的时候由于种种原因没有进行类型声明,然后我们在使用它的时候再单独为它做出一个明确的声明,这种做法存在的原因就是为了兼容一下普通的js模块
由于typescript社区非常强大,目前绝多数的模块都已经提供了声明,我们只需要安装一些它所对应的类型模块就可以了,需要注意的是,类型声明模块只是一个开发依赖,里面不会提供任何集体的代码只是对一个模块做一些对应的声明
//lodash的类型声明模块
yarn add @types/lodash --dev
安装过后lodash模块就会有相应 的类型提示了
// 类型声明
import { camelCase } from 'lodash'//提示我们安装@types/lodash模块.Try `npm install @types/lodash` if it exists or add a new declaration (.d.ts) file containing `declare module 'lodash';`ts(7016)
// 使用declare语句进行类型声明,declare + 函数签名
// declare function camelCase (input:string): string
// 使用类型声明后再去访问这个函数就会有类型限制了
const res = camelCase('hello')
// 类型声明说白了就是一个成员在定义的时候由于种种原因没有进行类型声明,
//然后我们在使用它的时候再单独为它做出一个明确的声明
//这种做法存在的原因就是为了兼容一下普通的js模块
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vr0Y6aR8-1597675674572)(44F2C2369B9F46B9B63F4FEEBFAA26A7)]
这些.d.ts文件都是typescript中专门做类型声明的文件
现在大部分模块内部都包含类型声明模块文件,不需要再单独安装类型声明模块了如query-string模块,如果没有类型声明模块,只能使用declare语句去单独声明