类型系统
-
类型安全
-
强类型:语言层面限制函数的实参类型必须与形参类型相同;
-
弱类型: 语言层面不会限制实参的类型;
function foo (num) { console.log(num); } foo(100);//不会报错 -> 100 foo('100');//不会报错 -> 100
-
强类型和弱类型根本不是某一个权威机构的定义:
强类型有更强的类型约束, 而弱类型中几乎没有什么约束;
强类型语言中不允许任何的隐式类型转换, 而弱类型语言则允许任意的数据隐式类型转换;
强类型在代码编译的阶段就会报错, 而不会在允许阶段通过逻辑判断去限制(JavaScript的所有的类型错误都是在运行阶段, 通过逻辑判断手动抛出的);
-
-
类型检查
-
静态类型语言: 一个变量声明时它的类型就是明确的, 变量声明后, 它的类型就不允许在修改;
-
动态类型语言: 在运行阶段才能够明确一个变量的类型, 而且变量的类型随时可以改变; 动态语言类型中的变量是没有类型的, 变量中存放的值是有类型的;
var foo = 100; foo = bar;//javaScript 是动态类型语言
-
-
JavaScript弱类型的部分问题:
-
某些类型异常需要等到运行时才能发现;
const obj = {}; //如果代码过大, 不去运行这行代码, 代码就不会有错误, 这个我们的代码安全带来了隐患; obj.foo();//会报错
-
类型不明确, 有可能造成代码功能错误;
function sun (a, b) { return a + b; } console.log(sum(100, 100));//200 //类型不明确, 代码的功能出现错误 console.loe(sum(100, '100'));//100100
-
使我们对对象的索引器用法产生错误;
const obj = {}; obj[true] = 100; //这里的使用对象的索引器(obj['true'])是错误的 console.log(obj['true']);//100
-
-
强类型的优势
-
错误更早的暴露: 可以在编码阶段提前去消灭一大波可能会出现的类型异常;
-
代码更智能, 编码更准确:
function render(element) { element.className = 'container',//这里智能提示不起作用(开发工具定义不到element的类型), 只能通过记忆书写代码, 很可能出现错误代码 }
-
重构更牢靠: 删除某个对象的成员, 或者修改某个成员的名称 更加的牢靠;
const util = {//如果代码中大量使用该对象, 当我们要修改aaa属性名的时候, 修改了之后要运行代码才能显示出来; 如果是强类型语言, 修改完以后, 在编译的时候就会爆出错误, 我们就可以找到所有用到aaa的地方加以修改; aaa: {} => { console.log('util func'); } };
-
减少不必要的类型判断
function sun (a, b) { //如果是强类型, 这个判断就不需要写 if ('number' !=== typeof a || 'number' !=== typeof b) { throw new TypeError('arguments must be a number'); } retrun a + b; }
-
Flow: JavaScript 的类型检查器
-
代码通过类型注解的方式来标注变量或者参数的数据类型, Flow通过类型注解来检查代码中变量或者参数是否存在类型使用异常, 从而实现在开发阶段对类型的检查;
function sum (a: number, b: number) {//:number 为类型注解 return a + b; }
-
在生产环境中: 可以通过Babel/Flow提供的模块去除类型注解, 所以不会对生产环境造成影响;
-
不要求所有的变脸或参数都加注解, 不加的为any类型;
-
-
Flow具有类型推断的特征: 可以根据代码中的使用情况, 去推断出变量的类型;
-
Flow的快速上手
-
安装: 这里通过yarn 安装
- yarn init --yes(npm init -y): Flow是以一个npm 模块工作的, 所以在项目中要初始化 package.json
- yarn add flow-bin --dev(npm i flow-bin --dev): 作为项目的依赖模块安装;
-
使用
-
在文件的开头使用注释
@flow的标记, 这样Flow才会检查这个文件
-
在文件中使用类型注解
function sum (a: number, b: number) {//:number 为类型注解 return a + b; }
-
运行yarn flow init(npm 在这之前需要在package.json的scripts中添加"flow": "flow"语句, 然后再执行 npm run flow init): 成功后文件目录下就会多出一个.flowconfig的文件(这是flow的配置文件);
-
运行yarn flow(npm run flow): 执行flow 命令, 对文件进行检查;
-
运行yarn flow stop(npm run flow stop): 关闭flow命令;
-
-
-
Flow编译
-
flow-remove-types: 官方提供的模块, 移除Js代码中的类型注解
-
安装:
//npm环境: npm i flow-remove-types --dev yarn add flow-remove-types
-
运行
//npm环境: 需先在package.json -> scripts中添加: "flowRemove": "flow-remove-types src/ -d dist/" //npm环境: npm run flowRemove //src 为生产环境下代码文件夹 dist 为输出代码文件夹 yarn flow-remove-types src -d dist
-
-
Babel: 使用Babel移除Js代码中的类型注解
-
安装
//babel/core: babel的核心模块; babel/cli:babel的cli工具, 使我们直接在命令行中使用babel命令完成编译; babel/preset-flow: 包含了转换flow类型注解的一个插件; yarn add @babel/core @babel/cli @babel/preset-flow --dev //npm环境: npm i @babel/core @babel/cli @babel/preset-flow --dev
-
在src同级目录下添加 .babelrc 文件, 增加配置
{ "presets": ["@babel/preset-flow"] } //npm环境: 一样的操作
-
使用
//src 为生产环境下代码文件夹 dist 为输出代码文件夹 yarn babel src -d dist //npm环境: 在package.json -> scripts中添加 "babel": "babel src/ -d dist/" //运行: npm run babel
-
-
-
Flow 插件 - Flow Language Supper (Flow 官方提供)
异常不显示在终端, 开发工具中直接显示;
https://flow.org/en/docs/editors/ : 官网给出的插件支持情况;
-
类型注解:
-
函数参数可以类型注解
function square(n: number) { return n * n; }
-
定义变量可以类型注解
let num: number = 100; num = 'string';//重新赋值为其他类型会报语法错误;
-
函数返回值可以类型注解
function foo (): number {//此函数只能number类型的数据 return 100; } //没有返回值的函数,其实返回的是undefined, 所以没有返回值的函数, 函数类型为void function noReturn (): void { }
-
-
Flow 类型
-
原始类型
const a: string = 'string'; const b: number = Infinity // NaN // 100 const c: boolean = false // true const d: null = null; const e: void = undefined;// Flow中undefined类型定义为void const f: symbol = Symbol();
-
数组类型
//array<number>: 表示泛型, 表示定的数组的成员只能为number类型 const arr1: Array<number> = [1, 2, 3]; //数组的成员只能为number类型 const arr2: number[] = [4, 5, 6]; //数组的成员是混合型, 但是数组的 length 只能等于2, 第一个必须为number, 第二个必须为字符串 -> 这种固定长度的数组 叫 元祖, 一般在函数中要返回多个返回值的时候,会用到元祖 const arr3: [number, string] = [6, 'lc'];
-
对象类型
//obj1必须为对象类型, obj1内部必须要有foo、bar两个成员, foo的值必须为String类型, bar的值必须为number类型 const obj1: {foo: string, bar: number} = {foo: 'string', bar: 100, csa: 100}; //obj2中的foo是可有可无的 const obj2: {foo?: string, bar: number} = {bar: 100}; //obj3可以添加任意多的成员, 但是成员的键的类型和值的类型都必须为string类型 const obj3: {[string]: string} = {}; obj3.key1 = 'value1'; obj3.key2 = 100;//这里100报错, 不允许数组类型的值
-
函数类型
函数类型除了参数和函数返回值可以类型注解, 但参数为函数的时候也可以类型注解;
//用箭头函数的形式表示函数为参数的时候的类型注解 function csa (callback: (string, number) => void) { callback('string', 100); } csa(function (str, n) { //str => string: tr只能为 string 类型 //n => number: n 只能为 number类型 //此函数不能有返回值 });
-
特殊类型
//字面量类型: 限制变量必须为某个特定的值 const foo: 'foo' = 'foo';// foo变量的值只能为foo, 为其他值就会报错; // 联合类型(或类型): returnMsg 的值可以是error、success、warning 三个值中的任意一个, 不能为其他值; const returnMsg: 'error' | 'success' | 'warning' = 'success'; //typeVariate 的值可以为 number, 可以为 string const typeVariate: number | string = 'string' // 100 //Flow 可以用type关键字声明一个 联合类型 type StringOrNumber = string | number; const definition: StringOrNumber = 'uniteType' //Maber 类型: 有可能,在基础的类型之上扩展了 null 和 undefined 两个类型, 相当于 基础类型 | null | undefined //const gender: number = null;//此时gender不能为空 const genderGood: ?number = null;//现在genderGood 可以为空, 也可以为undefined
-
mixed 和 any 类型
- mixed 类型 和 any 类型都可以表示任意类型;
- mixed 类型是强类型, any 类型是弱类型
- any 类型存在的意义: 兼容以前的老代码;
//Mixed 类型: 可以用来接收任意类型的值 //强类型: 必须要明确后才能调用方法 function passMixed (value: mixed) { //value * value;//这里语法报错 //value.slice(0, -1);//这里语法报错 if('number' === typeof value) { value * value } if('string' === typeof value) { value.slice(0, -1); } } passMixed('string'); passMixed(100); //any 类型: 可以用来接收任意类型的值 //弱类型: 语法上既可以调用 string 的方法, 也可以调用 number 的方法 function passAny(value: any) { value * value; value.slice(0, -1); } passAny('string'); passAny(100);
-
https://flow.org/en/docs/types: 官方文档给出的所有类型的描述文档
-
https://www.saltycrane.com/cheat-sheets/flow-type/latest/: 第三方类型手册
-
-
Flow 运行环境 API
因为 JavaScript 不是独立工作的, 必须要运行到某个特定的运行环境中, 代码必然会使用到这些环境所提供的API 或者一些对象, 这些API 或者对象一样会有类型限制;
//document.getElementById 要求传入的参数必须是一个字符串, 不能为数字, element 变量就必须为 html 或者 null 格式 const element: HTMLElement | null = document.getElementById('app');
-
运行环境API 声明函数的链接
https://github.com/facebook/flow/blob/master/lib/core.js: JavaScript 自身标准库中的成员;
https://github.com/facebook/flow/blob/master/lib/dom.js
https://github.com/facebook/flow/blob/master/lib/bom.js
https://github.com/facebook/flow/blob/master/lib/cssom.js
https://github.com/facebook/flow/blob/master/lib/node.js
-
TypeScript: JavaScript的超集(扩展集)
TypeScript是在JavaScript的基础上增加了一套强大的类型系统以及对ES6+的支持,最终会被编译为原始的JavaScript;
TypeScript的文件以 .ts结尾;
-
TS优缺点
-
优点:
-
任何一种JavaScript运行环境都支持;
-
功能更为强大, 生态也更健全、更完善;
-
TypeScript属于 渐进式的
-
-
缺点:
- 语言本身多了很多概念;
- 项目初期, TypeScript会增加一些成本: 项目初期, 会编写很多的类型声明;
-
-
TS快速上手
-
安装
- yarn init --yes(npm init -y): TypeScript是以一个npm 模块工作的, 所以在项目中要初始化 package.json
- yarn add typescript --dev(npm i typescript --dev): 作为项目的依赖模块安装;
- 安装成功过后, 会在node_modules -> .bin 目录下多一个 tsc(TypeScriptCompile -> 作用: 编译TypeScript代码) 命令
-
编译: yarn tsc 文件名;
- 检查代码的类型使用异常;
- 移除类型注解等一些扩展的语法;
- 转换 ECMAScript 的新特性;
-
-
TS配置文件
tsc: tsc命令不仅仅可以去编译指定 ts 文件, 还可以去编译整个项目(工程), 在编译整个项目的时候需要给项目创建 TypeScript 配置文件;
-
初始化 TypeScript 配置文件
yarn tsc --init
-
配置文件(tsconfig.json): 说明
- target: 设置编译后的JavaSC 所采用的 ECMAScript 标准
- module: 设置输出的代码会采用什么方式进行模块化;
- outDir: 设置编译结果输出到的文件夹;
- rootDir: 设置源代码所在的文件夹;
- sourceMap: 设置是否开启源代码映射, 一般为true;
- strict: 设置是否开启所有的严格模式(TS严格模式下需要类型注解);
- strictNullChecks: 设置变量不能为空;
- lib: 设置代码所引用的标准库;
-
-
TS标准库
定义: 内置对象所对应的声明; 我们在代码中使用内置对象就必须引用相对应的标准库, 否则就会报错;
-
TS作用域问题
解决变量重复定义的问题(原因: 变量是定义在全局作用域上的, TS去编译项目的时候就会出现这个错误): 在实际开发中一般不会遇到, 因为每个文件都会以模块的形式去工作;
-
利用立即执行函数:
(function () { const a:number = 123;//在这里变量的作用域是函数的作用域 })();
-
使用 export {}: 这样文件就可以作为一个模块, 作用域为模块作用域;
-
-
TS数据类型
-
原始类型
在严格模式下, string、number、boolean 三种类型不允许为 null, 在默认模式下允许为 null;
const a: string = 'foobar'; const b: number = 100; //NaN //Infinity const c: boolean = true; // false const d: void = undefined; const e: null = null; const f: undefined = undefined; const g: symbol = Symbol();
-
Objcect 类型: 并不单指普通的对象类型, 泛指所有的非原始类型(对象、数组、函数);
const foo: object = function () {} // [] // {} //TS 中这样定义对象类型, 不能多于类型注解中的长度; const obj: {foo: string, bar: number} = { foo: 'string', bar: 123 }; //专业的定义对象类型 需要用接口;
-
数组类型:
//表示数组的成员全为数字 const arr1: Array<number> = [1, 2]; //number[] -> 表示数组的成员全为数字 const arr2: number[] = [3, 4, 5]; //利用元祖的形式 Array<number>, 明确数组的长度和每个元素的类型 const arr3:[number, string] = [1, 'lc']; //数组类型的使用 let sum = (...args: number[]) => { return args.reduce((prev, current) => prev + current, 0); } //当调用sum函数的时候, 传入字符串就会报错; sum(1, 3, 5, 7/* , 'lc' */);
-
元祖类型
定义: 明确每个元素类型以及元素数量的数组
const tuple: [number, string] = [18, 'lc']; const [age, name] = tuple; //元祖类型的使用: entries 返回的都是元祖类型 Object.entries({ foo: 123, bar: 456 });
-
枚举类型
-
弊端:
在开发中需要用某几个数值去代表某种状态, 我们一般用数字去表示, 但是时间久了以后, 可能会不清楚数字所代表所代表的类型; 这种情况下就需要用枚举来表示状态; -
特点
- 可以给一组数值分别取上有意义的名字;
- 一个枚举中只会出现几个固定的值, 不会出现超出范围的可能性;
-
JavaScript 中的枚举: 使用对象模拟 枚举
const PostStatus = { Draft: 0, Unpublished: 1, Published: 2 };
在代码中可以用对象的属性来表示某种状态
const articlePost = { title: 'hello TS', content: 'TS is a type superset of JavaScript', status: PostStatus.Draft //这里就用枚举类型定义的属性 };
-
TS 中有专门的枚举类型: 使用一个 enum 去声明一个枚举, enum 后面跟枚举名称, 名称后跟一个对象, 对象内就是具体的枚举值
-
特点:
-
TS枚举对象中使用的是 等号(=) 赋值, 而不是使用对象中的 冒号(😃, 使用方法和对象的使用方法一样;
-
TS枚举对象中可以不用 等号的方式去指定, 不指定的情况, 枚举对象中的值就会默认从 0 开始累加, 如果给枚举对象中第一个值去指定了值, 后面的值就会在第一个的基础上去累加
enum PostStatus1 { Draft /* = 0 */ /* = 6 */,//这里可以不用指定值, 默认从0 开始累加, 如果指定了值, 后面的值就会从指定的值开始累加 Unpublished /* = 1 */, Published/* = 2 */ };
-
TS 枚举的值除了可以是数字以外, 还可以是字符串, 就是字符串枚举, 字符串枚举不能像数字一样去自增长, 所以定义字符串枚举时需要给每个成员指定相对应的值
enum PostStatus2 { Draft = 'aaa', Unpublished = 'bbb', Published = 'ccc' };
-
TS 枚举类型会入侵到我们运行时的代码(会影响我们编译后的结果): TS中大部门的类型, 经过编译转换后基本上都会被移除掉, 但是枚举类型不会, 枚举类型会被编译为双向的键值对对象;
/* PostStatus1 通过编译后的代码: var PostStatus1; (function (PostStatus1) { PostStatus1[PostStatus1["Draft"] = 0] = "Draft"; PostStatus1[PostStatus1["Unpublished"] = 1] = "Unpublished"; PostStatus1[PostStatus1["Published"] = 2] = "Published"; })(PostStatus1 || (PostStatus1 = {})); */
-
如果确定代码中不会通过索引器的方式去访问枚举, 我们就使用常量枚举;
//常量枚举: 就是在 enum 的前面加上 const const enum PostStatus3 { Draft, Unpublished, Published };
编译后的PostStatus3 的枚举都会被移除掉, 被应用的地方都会被替换为相对应的值, 枚举的名称会注释的状态标注;
-
-
-
函数类型
函数的类型约束: 对函数的输入、输出做类型限制;
-
通过函数声明定义的函数
function func1 (a: number, b: number): string { return 'func1'; };
-
调用时参数的个数必须和声明的参数个数一样: 实参和形参个数必须相同,如果需要某个参数是可选的:
-
用可选参数: 在类型注解的冒号(😃 前添加问号(?);
-
用ES6 参数默认值, 有参数默认值的参数可有可无;
-
可选参数或者默认参数, 都必须放在参数列表的最后(原因: 参数会按照位置进行传递, 如果可选参数在必传参数前面, 必传参数不能正常拿到所对应的值);
//默认参数 //可选参数 function func2 (a: number = 10, b?: number): string { return 'func2'; }
-
-
如果要接收任意个数的参数, 用ES6 的 rest 操作符
function func3(...rest: number[]): string { return 'func3'; }
-
-
函数表达式
函数表达式最终是放到一个变量中的, 接收这个函数的变量也需要类型注解, 如果该函数作为参数传递时, 可以用箭头函数的形式进行类型注解 -> (a: number, b: number) =>string;
const func4 = function (a: number, b: number): string { return 'func4'; }
-
-
-
任意类型
任意类型是弱类型且不安全;
function stingify (value: any) { return JSON.stingify(value); } stringify('string');//value 可以是字符串类型 stringify(100); //value 可以是数字类型
-
-
TS隐式类型推断
定义: 在TS中, 如果我们没有用类型注解去明确标明变量的类型, TS 就会通过这个变量的使用情况, 去推断这个变量的类型;
let age = 16;//age没有类型注解, 被隐式类型推断为 number 类型; /* age = 'string'; */ //这里age重新赋值为 string 类型, 语法上回报错;
TS如果无法推断变量的类型, TS就会把变量做为 any 类型;
let foo; //foo 会被隐式推断为 any 类型; foo = 100; foo = 'string';
建议书写代码时, 为每个变量添加明确的类型注解, 便于更直观的去理解代码;
-
TS类型断言
在某些特定的环境下, TS无法推断变量的具体类型, 而我们作为开发者, 可以明确知道变量的类型, 开发者就可以为这个变量进行类型断言;
//假定 nums 来自一个明确的接口 const nums = [110, 120, 119, 112];
cosnt res = nums.find(i => i > 0);//这种情况下, TS无法明确 res 的类型: TS推断出 res 的类型为 -> number | undefined;
上面这种情况, 作为开发者, 就知道 res 一定会返回 number 类型的数据, 所以我们就可为 res 做类型断言;
类型断言有两种方式:
1. 使用 as 关键字:
```typescript
let num1 = res as number;
```
2. 使用尖括号(<>): 在被断言的变量前面使用: 这种方式会与 JSX 中的标签产生语法冲突, 建议使用第一种;
```typescript
let num2 = <number>res
```
- #### TS接口
- 定义: 用来约定一个对象的结构, 去使用接口就必须遵守接口的全部约定;在TS中, 接口用来约定对象中有哪些成员, 并且约定成员所对应的数据类型;
```typescript
function printPost (Article) {//参数 Article 要求必须要有 title、content 属性, 但是这个要求是隐式的; 这里可以定义一个 Article 接口, 来表现出这个要求;
console.log(Article.title);
console.log(Article.content);
}
```
- 声明接口:
使用 interface 关键字来声明接口, 后面跟上接口名称, 名称后面是一个对象, 对象里面的键值用分号(;)分割
```
interface Article {
title: string;
content: string;
};
```
接口中的成员分类:
1. 普通成员
```typescript
interface DefaultMember {
title: string;//title为普通成员
content: string;//content 为普通成员
};
//接口的使用
let defaultMember: DefaultMember = {
title: 'Hello TS',
content: 'A JavaScript superset'
};
```
2. 可选成员: 在成员后添加问号(?) -> 表示成员可有可无
```typescript
interface ChoosableMember {
title: string;
subTitle?: string;//subTitle 为可选成员
};
//接口的使用
let choosableMember: ChoosableMember = {
title: 'Hello Ts'// subTitle 可有可无
};
```
3. 只读成员: 在成员添加 readonly, 只读成员在初始化后就不能修改;
```typescript
interface ReadonlyMember {
title: string;
readonly summary: string
};
//接口的使用
let readonlyMember: ReadonlyMember = {
title: 'Hello Ts',
summary: 'A JavaScript Superset'
};
/* readonlyMember.summary = 'csa'; */ //这里语法报错;
```
4. 动态成员: 可以向接口中动态添加成员
```typescript
interface Cache {
[key: string]: string;//Cache 接口内的成员必须是 string 类型的键值;
};
//接口的使用
const cache: Cache = {};
cache.foo = 'value1';
cache.bar = 'value2';
```
- #### TS 类
- 作用: 描述一类具体事物(方法)的抽象特征;
- 特征: 类的下面还可以细分很多子类: 子类一定会满足父类的所有特征, 然后再多出一些额外的特征;
- 使用: 我们不能去直接使用类, 而是去使用类的一些具体事物(方法);
1. 在TS中, 需要明确 在类型中去声明类中所拥有的一些属性;
2. 类中的属性必须要初始化: 可以用等号(=)的方式去赋值一个初始值, 也可以在构造函数 constructor 中动态赋值, 二者必须选其一;
3. 在类的方法中, 可以使用 this.属性名 访问类中定义的属性;
```typescript
class Person {
name: string /* = 'lc' */; //name属性 通过等号直接初始化赋值;
age: number;
constructor (name: string, age: number) {
this.name = name;//name属性 在构造函数 constructor 中动态赋值
this.age = age;
}
sayHi (msg: string): void {
//通过 this.属性名 访问类中定义的属性
consle.log(`I am ${this.name}, ${this.age}`);
}
};
```
- ###### TS类的访问修饰符
类中的每一个成员都可以用访问修饰符去修饰;
1. public 修饰符: 表示被修饰的对象为共有的, 在TS中默认的访问修饰符为public;
2. private 修饰符: 表示被修饰的对象为私用的, 私有的: 只能在类的内部去访问;
3. protected 修饰符: 表示被修饰的对象为受保护的, 受保护的: 只能在此类中去访问该对象;
```typescript
class Person {
public name: string;//public 修饰符: 默认修饰符;
private age: number;//private 修饰符: 私有的;
protected gender: boolean;//protected 修饰符: 受保护的;
constructor (name: string, age: number) {
this.name = name;
this.age = age;
this.gender = true;
}
sayHi (msg: string): void {
console.log(`I an ${this.name}, ${msg}`);
console.log(this.age);//age是私有属性, 只能在类中使用;
}
};
const Tom = new Person('tom', 22);
console.log(Tom.name);//tom
console.log(Tom.age);//报错: 私有属性只能在内部使用;
console.log(Tom.gender);//报错: 受保护的属性只能在此类中使用;
```
4. private 修饰符 和 protected 修饰符的区别: 被 protected 修饰的对象是可以被继承的;
```typescript
//这里的person 为上一个代码段的 Person
class Student extends Person {
constructor (name: string, age: number) {
super(name, age);
console.log(gender);//true //受保护的属性在此类中使用;
}
};
```
5. 构造函数 constructor 的修饰符默认也是 public, 如果构造函数 constructor 被设置为私有的 private , 那么构造函数 constructor 就不能在外面用 new 实例化; 也不能被继承; 这种情况我们只能在类中创建一个静态方法, 让这个静态方法来实现实例化;
```typescript
//这里的person 为上一个代码段的 Person
class Student1 extends Person {
private constructor (name: sting, age: num) {//现在的构造函数不能在外面 new 实例化
super(name, age);
console.log(this.gender);//true //这里是可以访问的
}
//创建一个静态方法来实现实例化
static create (name: string, age: number) {
return new Student1(name, age);
}
};
const Jack = new Student1('jack', 19);//报错 //构造函数 constructor 是私有的 private;
const Jack1 = Student1.create('jack', 19);//通过类中的静态方法实现实例化
```
- ###### 类的只读属性
对于类的属性成员, 除了可以用访问修饰符来修饰, 还可以用 readonly 关键词设置类的属性成员为只读;
如果类的属性成员已经被访问修饰符修饰, readonly 关键词只能跟在访问修饰符的后面;
被 readonly 声明的属性, 等号初始化 和 构造函数 constructor 动态赋值只能选其一, 赋值后, 该属性就不允许被修改;
```typescript
class newPerson {
protected readonly height: number;//readonly 必须跟在protected 后面
constructor (height: number) {
this.height = height;//动态赋值 和 初始化赋值 只能选其一;
}
};
```
- ###### TS类与TS接口
我们用接口来约束多个类的共同方法的类型, 不做方法的实现;
```typescript
class MyPerson {
eat (food: string): void {
console.log(`优雅的进餐: ${food}`);
}
run (distance: number) {
console.log(`直立行走: ${distance}`);
}
};
class Animal {
eat (food: string): void {
console.log(`咕噜咕噜的吃: ${food}`);
}
run (distance: number) {
console.log(`爬行: ${distance}`);
}
};
//MyPerson 类和 Animal 类, 有相同的特性(方法), 我们可以用接口来对这两个特性(方法)做约束;
interface EatAndRun {
eat (food: string): void;
run (distance: number): void;
};
//类用 implements 来实现接口的约束;
//上面的代码可以修改为
class MyPerson1 implements EatAndRun {
eat (food: string): void {
console.log(`优雅的进餐: ${food}`);
}
run (distance: number): void {
console.log(`自立行走: ${distance}`);
}
};
```
- ###### 抽象类
- 定义: 可以用来约束子类中必须要有某一个成员; 和接口的不同 -> 抽象类可以包含方法的具体实现, 接口不能包含方法的实现;
- 语法: 声明抽象类: 在class 前面添加 abstract 关键词
- 特征:
1. 只能被继承, 不能用 new 实例化;
2. 必须要用子类去继承抽象类;
```typescript
abstract class NewAnimal {
eat (food: string) {
console.log(`咕噜咕噜的吃: ${food}`);
}
//在抽象类中还可以去定义一些抽象方法: 抽象方法需要用 abstract 关键词修饰, 抽象方法没有方法体;
abstract run (distance: number): void;
};
class Dog extends NewAnimal {
//继承了抽象类, 必须实现抽象类中的抽象方法, 不然后报错;
run (distance): void {
console.log(`四脚爬行: ${distance}`);
}
};
//抽象类的使用
const d = new Dog();
d.eat('大骨头');
d.run(100);
```
- #### TS 泛型
- 定义: 在定义函数、接口、类的时候没有去指定具体的类型, 等到我们具体去使用时才指定类型的这样的特征;
```typescript
function createNumberArray (length: number, value: number): number[] {
const arr = Array(length).fill(value);
//由于Array 默认创建的是 any 类型的数组; 这里的 Array 是一个泛型类;
//这里就需要用泛型参数去传递一个类型去指定 Array(泛型类) 的数据类型;
const arr1 = Array<number>(length).fill(value);
return arr1;
}
const ResNumberArr = createNumberArray(3, 100);
console.log(ResNumberArr);//[100, 100, 100]
//这里的 createNumberArray 只能创建一个 number 类型的数组, 如果要创建一个 string 类型的数组, createNumberArray 做不到;
```
- 泛型使用: 在函数名后面去使用一对尖括号(<>), 尖括号内定义泛型参数(一般泛型参数用 T 作为名称), 函数中不明确类型的参数的类型注解就用 T 代替;
```typescript
function createArray<T> (length: number, value: T): T[] {
const returnArr = Array<T>(length).fill(value);
return returnArr;
}
const stringArr = createArray<string>(3, 'foo');
console.log(stringArr);//['foo', 'foo', 'foo']
const numberArr = createArray<number>(3, 100);
console.log(numberArray);//[100, 100, 100]
```
- #### TS类型声明
在实际项目开发中, 我们会用到第三方的 npm 模块, 而这些模块并不一定是通过 TS 编写的, 所有有些模块所提供的成员就不会有强类型的体验;
```typescript
import { camelCase } from 'lodash';
//使用 camelCase 函数时没有类型注解; 这里可以使用 declare 关键词进行类型声明;
- 语法: declare + 函数声明(function) + 要声明的函数 + 参数列表 + 函数的类型注解;
declare function camelCase (input: string): string;
//类型声明后 camelCase 就有类型限制了;