1.初始TypeScript
2.安装TypeScript
3.第一个TypeScript程序
3.1编写TS程序
hello.ts
(()=>{
// str这个参数是string类型的---str:string
function sayHi(str){
return '您好啊'+str
}
let text='小甜甜'
console.log(sayHi(text));
})
3.2 手动编译代码
我们使用了
.ts
扩展名,但是这段代码仅仅是 JavaScript 而已。在命令行上,运行 TypeScript 编译器:
tsc hello.ts
输出结果为一个 helloworld.js
文件,它包含了和输入文件中相同的 JavsScript 代码。
总结:ts的文件中如果直接书写js的代码,那么在html文件中直接引入ts文件,在谷歌浏览器中是可以直接使用的
如果ts文件中有ts的语法代码,那么就需要把这个ts文件编译成js文件,在html文件中引入js的文件来使用
ts文件中的函数中的形参,如果使用某一个类型进行修饰,那么最终在编译的js文件中是没有这个类型的
ts文件中的变量使用的是let进行修饰,编译的js文件中的修饰符就变成了var了
3.3 vscode自动编译
1)生成配置文件tsconfig.json
2)修改tsconfig.json配置文件(将其注释也进行修改)
"outDir": "./js", /* 把ts文件最终编译后放在js的目录中 */
"strict": false, /* 不使用严格模式 */
3)启动监视任务:终端-->运行任务--->监视tsconfig.json
在文件夹中新建一个html页面,在新建一个index.ts文件,在里面写入ts代码,一保存就出现js文件夹,此文件夹中包含一个index.js文件
3.4 类型注解:一种轻量级的为函数或变量添加约束的方式
给 showMsg 函数的参数添加 : string
类型注解,如下:
(()=>{
// str是string类型的
function showMsg(str:string){
return '床前明月光'+str
}
let msg='疑是地上霜'
console.log(showMsg(msg));
})()
在这个例子里,我们希望 showMsg
函数接收一个字符串参数。 然后尝试把showMsg 的调用改成传入一个数组:
(()=>{
// str是string类型的
function showMsg(str:string){
return '床前明月光'+str
}
// let msg='疑是地上霜'
let msg=[1,2,3]
console.log(showMsg(msg));
})()
重新编译,你会看到产生了一个错误:
类似地,尝试删除 showMsg
调用的所有参数。 TypeScript 会告诉你使用了非期望个数的参数调用了这个函数。 在这两种情况中,TypeScript提供了静态的代码分析,它可以分析代码结构和提供的类型注解。
要注意的是尽管有错误,.js
文件还是被创建了。 就算你的代码里有错误,你仍然可以使用 TypeScript。但在这种情况下,TypeScript 会警告你代码可能不会按预期执行。
3.5 接口
这里我们使用接口来描述一个拥有 firstName
和 lastName
字段的对象。 在 TypeScript
里,只在两个类型内部的结构兼容,那么这两个类型就是兼容的。 这就允许我们在实现接口时候只要保证包含了接口要求的结构就可以,而不必明确地使用 implements
语句。
// 接口:是一种能力,一种约束
(()=>{
// 定义一个接口
interface IPerson{
firstName:string//姓氏
lastName:string//名字
}
// 输出名字
function showFullName(person:IPerson){
return person.firstName+'_'+person.lastName
}
// 定义一个对象
const person={
firstName:'东方',
lastName:'拜拜'
}
console.log(showFullName(person));
})()
3.6 类
让我们创建一个 User
类,它带有一个构造函数和一些公共字段。因为类的字段包含了接口所需要的字段,所以他们能很好的兼容。
还要注意的是,我在类的声明上会注明所有的成员变量,这样比较一目了然。
class User {
fullName: string
firstName: string
lastName: string
constructor (firstName: string, lastName: string) {
this.firstName = firstName
this.lastName = lastName
this.fullName = firstName + ' ' + lastName
}
}
interface Person {
firstName: string
lastName: string
}
function greeter (person: Person) {
return 'Hello, ' + person.firstName + ' ' + person.lastName
}
let user = new User('Yee', 'Huang')
console.log(greeter(user))
重新运行
tsc greeter.ts
,你会看到 TypeScript 里的类只是一个语法糖,本质上还是JavaScript
函数的实现。
4.使用webpack打包TS
分别初始化产生package.json和tsconfig.json文件
下载依赖
npm i -D typescript
npm i -D webpack@4.41.5 webpack-cli@3.3.10
npm i -D webpack-dev-server@3.10.2
npm i -D html-webpack-plugin@4.5.0 clean-webpack-plugin@3.0.0
npm i -D ts-loader@8.0.11
npm i -D cross-env@7.0.2
入口JS: src/main.ts
// import './01_helloworld'
document.write('Hello Webpack TS!')
index页面: public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>webpack & TS</title>
</head>
<body>
</body>
</html>
build/webpack.config.js
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
const isProd = process.env.NODE_ENV === 'production' // 是否生产环境
function resolve (dir) {
return path.resolve(__dirname, '..', dir)
}
module.exports = {
mode: isProd ? 'production' : 'development',
entry: {
app: './src/main.ts'
},
output: {
path: resolve('dist'),
filename: '[name].[contenthash:8].js'
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
include: [resolve('src')]
}
]
},
plugins: [
new CleanWebpackPlugin({
}),
new HtmlWebpackPlugin({
template: './public/index.html'
})
],
resolve: {
extensions: ['.ts', '.tsx', '.js']
},
devtool: isProd ? 'cheap-module-source-map' : 'cheap-module-eval-source-map',
devServer: {
host: 'localhost', // 主机名
stats: 'errors-only', // 打包日志输出输出错误信息
port: 8081,
open: true
},
}
配置打包命令:在package.json文件中的scripts节点中进行修改
"dev": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.config.js",
"build": "cross-env NODE_ENV=production webpack --config build/webpack.config.js"
在配置打包TS的时候出现的问题:Module build failed (from ./node_modules/ts-loader/index.js)
是安装版本出现了问题
运行
npm run dev
导包
npm run build
5.TypeScript常用语法
1.基础类型
TypeScript 支持与 JavaScript 几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用。
布尔值
最基本的数据类型就是简单的 true/false 值,在JavaScript 和 TypeScript 里叫做 boolean
(其它语言中也一样)。
// 布尔类型--->boolean
// 基础语法
// let 变量名:数据类型=值
let flag:boolean =true
console.log(flag);
数字
和 JavaScript 一样,TypeScript 里的所有数字都是浮点数。 这些浮点数的类型是 number。 除了支持十进制和十六进制字面量,TypeScript 还支持 ECMAScript 2015中引入的二进制和八进制字面量。
let a1: number = 10 // 十进制
let a2: number = 0b1010 // 二进制
let a3: number = 0o12 // 八进制
let a4: number = 0xa // 十六进制
字符串
JavaScript 程序的另一项基本操作是处理网页或服务器端的文本数据。 像其它语言里一样,我们使用 string
表示文本数据类型。 和 JavaScript 一样,可以使用双引号("
)或单引号('
)表示字符串。
// 字符串类型---->string
let str1='床前明月光'
let str2='疑是地上霜'
console.log(`${str1},${str2}`);
ts中变量一开始是什么类型,那么后期赋值的时候,只能用这个类型的数据,是不允许用其他类型的数据赋值给当前这个变量中
undefined 和 null
let und:undefined=undefined
let nll:null=null
console.log(und);
console.log(nll);
// 默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给 number 类型的变量。
let num2:number=undefined
let num3:number=null
console.log(num2);
console.log(num3);
默认情况下
null
和undefined
是所有类型的子类型(记得修改tsconfig,js文件中的严格模式)。 就是说你把null
和undefined
赋值给number
类型的变量。
数组
TypeScript 像 JavaScript 一样可以操作数组元素。 有两种方式可以定义数组。 第一种,可以在元素类型后面接上[]
,表示由此类型元素组成的一个数组:
// 数组定义方式1:let 变量名:数据类型[]=[值1,值2,值3]
let arr1:number[]=[1,2,3,4]
console.log(arr1);
第二种方式是使用数组泛型,Array<元素类型>
:
// 数组定义方式2:let 变量名 :Array<数据类型>=[值1,值2,值3]
let arr2:Array<number>=[1,2,3,4]
console.log(arr2);
数组定义后,里面的数据的类型必须和定义数组的时候类型是一致的,否则有错误提示信息,也不会编译通过的
元组 Tuple
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
。
// 元组类型:在定义数组的时候,类型和数据的个数一开始就已经限定了
let arr3:[string,number,boolean]=['小林',100,true]
console.log(arr3);
当访问一个已知索引的元素,会得到正确的类型:
console.log(arr3[0].split(''));
console.log(arr3[1].toFixed(2))
元组类型在使用的时候,数据的类型的位置和数据的个数 应该和在定义元组的时候的数据类型及位置应该是一致的
枚举
enum
类型是对 JavaScript 标准数据类型的一个补充。 使用枚举类型可以为一组数值赋予友好的名字
。
// 枚举类型:枚举里面的某一个数据值都可以叫元素,每一个元素都有自己的编号,编号是从0开始的,依次增加1
enum Color{
Red,
Green,
Blue
}
let myColor: Color = Color.Green //1
console.log(myColor, Color.Red, Color.Blue)//0 2
默认情况下,从 0
开始为元素编号。 你也可以手动的指定成员的数值。 例如,我们将上面的例子改成从 1
开始编号:
enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green
或者,全部都采用手动赋值:
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green
枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如,我们知道数值为 2,但是不确定它映射到 Color 里的哪个名字,我们可以查找相应的名字:
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2]
console.log(colorName) // 'Green'
枚举中的元素可以是中文的数据值,但是不推荐
any
有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any
类型来标记这些变量:
// any类型
let str:any=100
str='小林'
console.log(str);
在对现有代码进行改写的时候,any
类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。并且当你只知道一部分数据的类型时,any
类型也是有用的。 比如,你有一个数组,它包含了不同的类型的数据:
// 当一个数组中要存储多个数据,个数不确定,类型不确定,此时也可以使用any类型来定义数组
let arr:any[]=[100,'少年',true]
console.log(arr);
// 这种情况下也没有错误的提示信息,any类型有优点,也有缺点
console.log(arr[0].split(''));
void
某种程度上来说,void
类型像是与 any
类型相反,它表示没有任何类型
。 当一个函数没有返回值时,你通常会见到其返回值类型是 void
:
/* 表示没有任何类型, 一般用来说明函数的返回值不能是undefined和null之外的值 */
function fn(): void {
console.log('fn()')
// return undefined
// return null
// return 1 // error
}
声明一个 void
类型的变量没有什么大用,因为你只能为它赋予 undefined
和 null
:
let unusable: void = undefined
object
object
表示非原始类型,也就是除 number
,string
,boolean
之外的类型。使用 object
类型,就可以更好的表示像 Object.create
这样的 API
。
// object类型
// 定义一个函数,参数是object类型,返回值是object类型
function getObj(obj:object):object{
console.log(obj);
return {
name:'aabb',
age:23
}
}
console.log(getObj({name:'佐助',age:13}));
// 也可以
console.log(getObj(String));
联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种
需求1: 定义一个一个函数得到一个数字或字符串值的字符串形式值
function getString(str:number|string):string{
return str.toString()
}
console.log(getString('123'));
需求2: 定义一个一个函数得到一个数字或字符串值的长度
function getLength(x: number | string) {
// return x.length // error
if (x.length) { // error
return x.length
} else {
return x.toString().length
}
}
类型断言
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript 会假设你,程序员,已经进行了必须的检查。
类型断言有两种形式。 其一是“尖括号”语法, 另一个为 as
语法
/*
类型断言(Type Assertion): 可以用来手动指定一个值的类型
语法:
方式一: <类型>变量名
方式二: 值 as 类型 tsx中只能用这种方式
*/
/* 需求: 定义一个函数得到一个字符串或者数值数据的长度 */
function getLength(x: number | string) {
if ((<string>x).length) {
return (x as string).length
} else {
return x.toString().length
}
}
console.log(getLength('abcd'), getLength(1234))
类型推断
类型推断: TS会在没有明确的指定类型的时候推测出一个类型
有下面2种情况:
1. 定义变量时赋值了, 推断为对应的类型.
2. 定义变量时没有赋值, 推断为any类型
// 类型推断:TS会在没有明确的指定类型的时候推测出一个类型
let txt=100 //number类型
// txt='小康'//报错
console.log(txt);
let txt2; //any类型
txt2=100
txt2='小林啊'
console.log(txt2);
2.接口
TypeScript 的核心原则之一是对值所具有的结构进行类型检查。我们使用接口(Interfaces)来定义对象的类型。接口是对象的状态(属性)和行为(方法)的抽象(描述)
接口初探
需求: 创建人的对象, 需要对人的属性进行一定的约束
id是number类型, 必须有, 只读的
name是string类型, 必须有
age是number类型, 必须有
sex是string类型, 可以没有
下面通过一个简单示例来观察接口是如何工作的:
/*
在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型
接口: 是对象的状态(属性)和行为(方法)的抽象(描述)
接口类型的对象
多了或者少了属性是不允许的
可选属性: ?
只读属性: readonly
*/
/*
需求: 创建人的对象, 需要对人的属性进行一定的约束
id是number类型, 必须有, 只读的
name是string类型, 必须有
age是number类型, 必须有
sex是string类型, 可以没有
*/
(()=>{
// 定义一个接口,该接口作为person对象的类型使用,限定或者是约束对象中的属性数据
interface IPerson{
// readonly id 是只读的,是number类型,const修饰属性,想要设置该属性是只读的,是不能使用的
readonly id:number
name:string
age:number
// sex这个属性没有也是可以的
// 可有可无
sex?:string
}
// 定义一个对象,该对象的类型就是我定义的接口IPerson
const person:IPerson={
id:1,
name:'小林',
age:19,
// 可有可无
sex:'女'
}
console.log(person);
// id属性此时是可读可写的---未在id前加上readonly
// person.id=100
// console.log(person);
})()
类型检查器会查看对象内部的属性是否与IPerson接口描述一致, 如果不一致就会提示类型错误。
可选属性:在名字定义后面加上一个“?”
接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。
interface IPerson {
id: number
name: string
age: number
sex?: string
}
带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个 ?
符号。
可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。
const person2: IPerson = {
id: 1,
name: 'tom',
age: 20,
// sex: '男' // 可以没有
}
只读属性
一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 readonly
来指定只读属性:
interface IPerson {
readonly id: number
name: string
age: number
sex?: string
}
一旦赋值后再也不能被改变了。
const person2: IPerson = {
id: 2,
name: 'tom',
age: 20,
// sex: '男' // 可以没有
// xxx: 12 // error 没有在接口中定义, 不能有
}
person2.id = 2 // error
readonly vs const
最简单判断该用
readonly
还是const
的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用const
,若做为属性则使用readonly
。
函数类型
接口能够描述 JavaScript 中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。
为了使用接口表示函数类型,我们需要给接口定义一个调用签名。它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
/*
接口可以描述函数类型(参数的类型与返回的类型)
*/
interface SearchFunc {
(source: string, subString: string): boolean
}
这样定义后,我们可以像使用其它接口一样使用这个函数类型的接口。 下例展示了如何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量。
// 为了使用接口表示函数类型,我们需要给接口定义一个调用签名。
//它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
(()=>{
// 函数类型:通过接口的方式作为函数的类型来使用
// 定义一个接口,用来作为某一个函数的类型使用
interface ISearchFunc{
// 定义一个调用签名
(source:string,subString:string):boolean
}
// 定义一个函数,该类型就是上面定义的接口
const searchString:ISearchFunc=function (source:string,subString:string):boolean{
// 在source字符串中查找subString这个字符串
return source.search(subString)>-1
}
// 调用函数
console.log('哈哈哈','哈');
})()
类类型:接口和接口之间的叫继承(extends关键字),类和接口之间的实现(implements关键字)
与 C# 或 Java 里接口的基本作用一样,TypeScript 也能够用它来明确的强制一个类去符合某种契约。
// 类类型 :类的类型 ,类的类型可以通过接口来实现
(()=>{
// 定义有一个接口
interface IFly{
// 该方法没有任何的实现(方法中什么都没有)
fly()
}
// 定义一个类,这个类的类型就搜索上面定义的接口(实际上可以理解为LIFly接口约束了当前的这个Person类)
class Person implements IFly{
fly() {
// 实现接口中的方法
console.log('我会飞了');
}
}
// 实例化对象
const person=new Person()
person.fly()
})()
一个类可以实现多个接口
类可以通过接口的方式,来定义当前这个类的类型
类可以实现一个接口,类也可以实现多个接口,要注意,接口中的内容要真正的实现
// 类类型 :类的类型 ,类的类型可以通过接口来实现
(()=>{
// 定义有一个接口
interface IFly{
// 该方法没有任何的实现(方法中什么都没有)
fly()
}
// 定义一个接口
interface ISwim{
swim()
}
// 定义一个类,这个类的类型就是IFly和ISwim(当前这个类可以实现多个接口,一个类同时也可以被多个接口进行约束)
class Person2 implements IFly,ISwim{
fly() {
console.log('我飞了2');
}
swim() {
console.log('我会游泳');
}
}
// 实例化对
const person2=new Person2()
person2.fly()
person2.swim()
})()
接口继承接口
和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。
interface LightableAlarm extends Alarm, Light {
}
// 定义一个接口,继承其他的多个接口
interface IMyFlyAndSwim extends IFly,ISwim{ }
// 定义一个类,直接实现IMyFlyAndSwim
class Person3 implements IMyFlyAndSwim{
fly() {
console.log('我飞了3');
}
swim() {
console.log('我会游泳3');
}
// 实例化
const person3=new Person3()
person3.fly()
person3.swim()
3.类
对于传统的 JavaScript 程序我们会使用函数
和基于原型的继承
来创建可重用的组件,但对于熟悉使用面向对象方式的程序员使用这些语法就有些棘手,因为他们用的是基于类的继承
并且对象是由类构建出来的。 从 ECMAScript 2015,也就是 ES6 开始, JavaScript 程序员将能够使用基于类的面向对象的方式。 使用 TypeScript,我们允许开发者现在就使用这些特性,并且编译后的 JavaScript 可以在所有主流浏览器和平台上运行,而不需要等到下个 JavaScript 版本。
(()=>{
// ts中类的定义及其使用
class Person{
// 定义属性
name:string
age:number
gender:string
// 定义规则函数:为了将来实例化对象的时候,可以直接对属性进行初始化
constructor(name:string='小团体',age:number=12,gender:string='女'){
// 更新对象中的属性数据
this.name=name
this.age=age
this.gender=gender
}
// 定义实例方法
sayHi(str:string){
console.log(`大家好,我是${this.name},今年已经${this.age},是个${this.gender}孩子`,str);
}
}
// ts中使用类,实例化对象,可以直接进行初始化操作
const person=new Person()
person.sayHi('你叫什么啊')
})()
继承
在 TypeScript 里,我们可以使用常用的面向对象模式。 基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类。
继承:类与类之间的关系
继承后类与类之间的叫法:
A类继承了B这个类,那么此时A类叫子类,B类叫基类
子类--->派生类
基类---->超类(父类)
一旦发生了继承的关系,就出现了父子类的关系
类的继承:super(name,age,gender)
(()=>{
// 定义一个类,作为基类(超类/父类)
class Person{
// 定义属性
name:string//名字
age:number //年龄
gender:string //性别
// 定义构造函数
constructor(name:string,age:number,gender:string){
// 更新属性数值
this.name=name
this.age=age
this.gender=gender
}
// 定义实例方法
sayHi(str:string){
console.log(`我是${this.name},${str}`);
}
}
// 定义一个类,继承自Person
class Student extends Person{
constructor(name:string,age:number,gender:string){
// 调用的是父类中的构造函数,使用的是super
super(name,age,gender)
}
}
})()
在子类中调用父类
(()=>{
// 定义一个类,作为基类(超类/父类)
class Person{
// 定义属性
name:string//名字
age:number //年龄
gender:string //性别
// 定义构造函数
constructor(name:string='小米',age:number=10,gender:string='男'){
// 更新属性数值
this.name=name
this.age=age
this.gender=gender
}
// 定义实例方法
sayHi(str:string){
console.log(`我是${this.name},${str}`);
}
}
// 定义一个类,继承自Person
class Student extends Person{
constructor(name:string,age:number,gender:string){
// 调用的是父类中的构造函数,使用的是super
super(name,age,gender)
}
// 可以调用父类中的方法
sayHi(){
console.log('我是学生类中的sayHi方法');
// 调用父类中的sayHi方法
super.sayHi('哈哈哈')
}
}
// 实例化Person
const person=new Person()
person.sayHi('嘎嘎')
// 实例化Student
const stu=new Student('小甜甜',19,'女')
stu.sayHi()
})()
1.类和类之间如果要有继承关系,需要使用extends关键字
2.子类中可以调用父类中的构造函数,使用的是super关键字(包括调用父类中的实例方法,也可以使用super)
3.子类中可以重写父类的方法
多态
// 多态:父类型的引用指向子类型的对象,不同类型的对象针对相同的方法,产生了不同的行为
(()=>{
// 定义一个父类
class Animal{
// 定义一个属性
name:string
// 定义一个构造函数
constructor(name:string){
// 更新属性值
this.name=name
}
// 实例方法
run(distance:number=0){
console.log(`跑了${distance}这么远`,this.name);
}
}
// 定义一个子类
class Dog extends Animal{
// 构造函数
constructor(name:string){
// 调用父类的构造函数,实现子类中属性的初始化操作
super(name)
}
// 实例方法,重写父类中的实例方法
run(distance:number=5){
console.log(`跑了${distance}这么远`,this.name);
}
}
// 定义一个子类
class Pig extends Animal{
// 构造函数
constructor(name:string){
// 调用父类的构造函数,实现子类中属性的初始化操作
super(name)
}
// 实例方法,重写父类中的实例方法
run(distance:number=10){
console.log(`跑了${distance}这么远`,this.name);
}
}
// 实例化父类对象
const ani:Animal=new Animal('动物')
ani.run()
// 实例化子类对象
const dog:Dog=new Dog('大黄')
dog.run()
// 实例化子类对象
const pig:Pig=new Pig('八戒')
pig.run()
// 父类和子类的关系:父子关系,此时,父类类型创建子类的对象
const dog1:Animal=new Dog('小黄')
dog1.run()
const pig1:Animal=new Pig('小猪')
pig1.run()
// 该函数需要的参数的Animal类型的
function showRun(ani:Animal){
ani.run()
}
showRun(dog1)
showRun(pig1)
})()
类中的成员修饰符
默认为 public(公共的)
类中的成员都有自己的默认的访问修饰符---->public
public修饰符,类中成员默认的修饰符,代表的是公共的,任何位置都是可以访问类中的成员
// 修饰符(类中的成员的修饰符):注意是描述类中的成员(属性,构造函数)的可以访问性
// 类中的成员都有自己的默认的访问修饰符---->public
// public修饰符,类中成员默认的修饰符,代表的是公共的,任何位置都是可以访问类中的成员
(()=>{
// 定义一个父类
class Person{
// 定义一个属性
name:string
// 定义一个构造函数
constructor(name:string){
// 更新属性值
this.name=name
}
// 实例方法
eat(){
console.log('好吃',this.name);
}
}
// 实例化子类对象
const per =new Person('八戒')
// 类的外部可以访问类中的属性成员
console.log(per.name);
per.eat()
})()
理解 private
private 修饰符,类中的成员如果要使用private来修饰,那么外部是无法访问这个成员数据的,当然子类也是无法访问该成员数据的
理解 protected
protected修饰符,类中的成员如果protected来修饰,那么外部是无法访问这个成员数据的,当然,子类中是可以访问该成员数据的
(()=>{
// 定义一个父类
class Person{
// 定义一个属性
// 属性public修饰了属性成员
//public name:string
// 属性private 修饰了属性成员
// private name:string
protected name:string
// 定义一个构造函数
constructor(name:string){
// 更新属性值
this.name=name
}
// 实例方法
eat(){
console.log('好吃',this.name);
}
}
// 定义一个子类
class Student extends Person{
constructor(name:string){
// 调用的是父类中的构造函数,使用的是super
super(name)
}
play(){
console.log('我就喜欢',this.name);
}
}
// 实例化子类对象
const per =new Person('八戒')
// 类的外部可以访问类中的属性成员
console.log(per.name);
per.eat()
const stu=new Student('红豆')
stu.play()
console.log(stu.name);
})()
readonly修饰符
readonly修饰类中的成员属性操作
你可以使用 readonly
关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
类中的普通方法中,也是不能修改readonly修饰的成员属性值
构造函数中,可以对只读的属性成员的数据进行修改
如果构造函数中没有任何参数,类中的属性成员此时已经使用readonly进行修饰了,那么外部也不能对这个属性值进行更改的
readonly修饰类中的构造函数中的参数(参数属性)
构造函数中的name参数,一旦使用readonly进行修饰后,那么该name参数可以叫参数属性
构造函数中的name参数,一旦使用readonly进行修饰后,那么Person中就有一个name属性
构造函数中的name参数,一旦使用readonly进行修饰后,外部也是无法修改类中的name属性成员值的
(()=>{
// readonly修饰类中的构造函数中的参数(参数属性)
class Person{
// 定义构造函数
// 构造函数中的name参数,一旦使用readonly进行修饰后,那么该name参数可以叫参数属性
// 构造函数中的name参数,一旦使用readonly进行修饰后,那么Person中就有一个name属性
// 构造函数中的name参数,一旦使用readonly进行修饰后,外部也是无法修改类中的name属性成员值的
constructor(readonly name:string='小廖'){
// 更新属性数值
// this.name=name
}
}
const person=new Person('小林')
console.log(person);
// person.name='小吴'//报错
console.log(person.name);
// 此时无法修改,因为name属性是只读的
// person.name='小无'
// console.log(person.name);
})()
使用public对构造函数中的属性进行修饰
(()=>{
class Person{
// 构造函数中的name参数,一旦使用public进行修饰后,那么Person类中就有一个公共的name属性成员了
constructor(public name:string='小廖'){
// 更新属性数值
// this.name=name
}
}
const person=new Person('小林')
console.log(person);
// person.name='小吴'//报错
console.log(person.name);
// 此时无法修改,因为name属性是只读的
person.name='小无'
// console.log(person.name);
})()
使用private对构造函数中的属性进行修饰
(()=>{
// readonly修饰类中的构造函数中的参数(参数属性)
class Person{
// 构造函数中的name参数,一旦使用private进行修饰后,那么Person类中就有一个私有的name属性成员了
constructor(private name:string='小廖'){
// 更新属性数值
// this.name=name
}
}
const person=new Person('小林')
console.log(person);
// person.name='小吴'//报错
//console.log(person.name); 无法访问
// 此时无法修改,因为name属性是只读的
//person.name='小无' 无法修改
// console.log(person.name);
})()
使用protected对构造函数中的属性进行修饰
(()=>{
// readonly修饰类中的构造函数中的参数(参数属性)
class Person{
// 构造函数中的name参数,一旦使用protected进行修饰后,那么Person类中就有一个受保护的name属性成员了(只能在本类和派生类中访问及使用)
constructor(protected name:string='小廖'){
// 更新属性数值
// this.name=name
}
}
const person=new Person('小林')
console.log(person);
// person.name='小吴'//报错
console.log(person.name);
// 此时无法修改,因为name属性是只读的
person.name='小无' 无法访问
// console.log(person.name);
})()
构造函数中的参数可以使用readonly进行修饰,一旦修饰了,那么该类中就有了这个只读的成员属性了,外部可以访问,但是不能修改
构造函数中的参数可以使用public,private,protected进行修饰,无论是哪一个进行修饰,该类中都有自动的添加这么一个属性成员
存取器
TypeScript
支持通过 getters/setters
来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
// 存储器:TypeScript 支持通过 getters/setters 来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
(()=>{
// 外部可以传入姓氏和名字数据,同时使用set和get控制姓名的数据,外部也可以进行修改操作
class Person {
firstName: string = 'A'
lastName: string = 'B'
// 姓名的成员属性(外部可以访问,也可以修改)
// 读取器:负责读取数据的
get fullName () {
// 姓名====>姓氏和名字的拼接
return this.firstName + '-' + this.lastName
}
// 设置器:负责设置数据的(修改)
set fullName (value) {
const names = value.split('-')
this.firstName = names[0]
this.lastName = names[1]
}
}
// 实例化对象
const p = new Person()
// 获取该属性成员属性
console.log(p.fullName)
p.firstName = 'C'
p.lastName = 'D'
// 设置该属性的数据
console.log(p.fullName)
p.fullName = 'E-F'
console.log(p.firstName, p.lastName)
})()
静态属性
我们也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上。
静态成员:在类中通过static修饰的属性或者方法,那么就是静态的属性及静态的方法,也称为:静态成员
静态成员在使用的时候是通过类名.的这种语法来调用的
构造函数是不能通过static来进行修饰的
(()=>{
// 定义一个类
class Person {
name1: string
constructor(name1:string){
this.name1=name1
}
sayHi1(){
console.log('萨瓦迪卡');
}
// 类中默认有一个内置的name属性,所以此时会出现错误的提示
// 静态属性
static name2:string='小廖'
// 静态方法
static sayHi2(){
console.log('萨瓦迪卡');
}
}
// 实例化对象
const person:Person=new Person('小林')
// 通过实例对象调用的属性(实例属性)
console.log(person.name1);
// 通过实例对象调用的方法(实例方法)
person.sayHi1()
// 使用类名调用静态属性
console.log(Person.name2);
// 修改静态属性
Person.name2='佐助'
console.log(Person.name2);
// 使用类名调用静态方法
Person.sayHi2()
})()
抽象类
抽象类做为其它派生类的基类使用。 它们不能被实例化。不同于接口,抽象类可以包含成员的实现细节。 abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法。
在一个抽象类中定义的抽象方法和抽象属性,在继承了该抽象类的子类中必须对该抽象方法和抽象属性进行“重写”
// 抽象类:包含抽象方法(抽象方法一般没有任何的具体内容的实现),也可以包含实例方法,抽象类不能被实例化,为了让子类进行实例化及实现内部的抽象方法
// 抽象类的目的或者是作用最终都是为了子类服务的
(()=>{
// 定义一个抽象类
abstract class Animal{
// 抽象属性
abstract name:string
// 抽象方法
abstract eat()
// 报错的,抽象方法不能有具体的实现
// abstract eat(){
// console.log('趴着吃');
// }
// 实例方法
sayHi(){
console.log('您好啊');
}
}
// 定义一个子类(派生类)Dog
class Dog extends Animal{
name:string='小林'
// 重新的实现抽象类中的方法,此时这个方法就是当前Dog类的实例方法
eat() {
console.log('真好吃');
}
}
// 实例化Dog对象
const dog:Dog=new Dog()
dog.eat()
// 调用的是抽象类中的实例方法
dog.sayHi()
console.log(dog.name);
// 不能实例化抽象类的对象
})()
4.函数
函数是 JavaScript 应用程序的基础,它帮助你实现抽象层,模拟类,信息隐藏和模块。在 TypeScript 里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义行为的地方。TypeScript 为 JavaScript 函数添加了额外的功能,让我们可以更容易地使用。
基本示例
和 JavaScript 一样,TypeScript 函数可以创建有名字的函数和匿名函数。你可以随意选择适合应用程序的方式,不论是定义一系列 API 函数还是只使用一次的函数。
通过下面的例子可以迅速回想起这两种 JavaScript 中的函数:
// 命名函数
function add(x, y) {
return x + y
}
// 匿名函数
let myAdd = function(x, y) {
return x + y;
}
为函数定义类型
// 函数声明,命名函数
// 函数中的x和y参数的类型都是string类型的,小括号后面的:string,代表的是该函数的返回值也是string类型的
function add(x: string, y: string): string {//求和的函数
return x + y
}
const result1:string=add('111','2222')
console.log(result1);
// 函数表达式,匿名函数
// 函数中的x和y参数的类型都是number类型的,小括号后面的:number,代表的是该函数的返回值也是number类型的
let myAdd = function(x: number, y: number): number {
return x + y
}
我们可以给每个参数添加类型之后再为函数本身添加返回值类型。TypeScript 能够根据返回语句自动推断出返回值类型。
书写完整函数类型
// 函数的完整写法
// myAdd2--->变量名--->函数myAdd2
// (x: number, y: number) => number 当前的这个函数的类型
// function(x: number, y: number): number { return x + y } 就相当于符合上面的这个函数类型的值
let myAdd2: (x: number, y: number) => number =
function(x: number, y: number): number {
return x + y
}
可选参数和默认参数
TypeScript 里的每个函数参数都是必须的。 这不是指不能传递 null
或 undefined
作为参数,而是说编译器检查用户是否为每个参数都传入了值。编译器还会假设只有这些参数会被传递进函数。 简短地说,传递给一个函数的参数个数必须与函数期望的参数个数一致。
JavaScript 里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是 undefined
。 在TypeScript 里我们可以在参数名旁使用 ?
实现可选参数的功能。 比如,我们想让 lastName
是可选的:
在 TypeScript 里,我们也可以为参数提供一个默认值当用户没有传递这个参数或传递的值是 undefined
时。 它们叫做有默认初始化值的参数。
// 可选参数:函数在声明的时候,内部的参数使用了?进行修饰,那么就表示该参数可以传入也可以不用传入
// 默认参数:函数在声明的时候,内部的参数有自己的默认值,此时这个参数叫默认参数
(()=>{
// 定义一个函数:传入姓氏和姓名,可以得到姓名(姓氏+名字=姓名)
// 需求:如果传入任何内容,那么就返回默认的姓氏,如果只传入姓氏,那么就返回姓氏,如果传入姓氏和名字,那么返回的就是姓名·
const getFullName=function (firstName:string='但凡',lastName?:string):string {
// 判断名字是否传入
if(lastName){
return firstName+'_'+lastName
}else{
return firstName
}
}
// 函数调用
// 什么也不传入
console.log(getFullName());
// 只传入姓氏
console.log(getFullName('诸葛'));
// 传入姓氏和名字
console.log('诸葛','孔明');
})()
剩余参数
必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。
在 JavaScript 里,你可以使用
arguments
来访问所有传入的参数。
在 TypeScript 里,你可以把所有参数收集到一个变量里:
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号( ...
)后面给定的名字,你可以在函数体内使用这个数组。
// 剩余参数(rest参数)
// 剩余参数是放在函数声明的时候所有的参数的最后
(()=>{
// ...args:string[]---->剩余的参数,放在一个字符串的数组中,args里面
function info(x: string, ...args: string[]) {
console.log(x)//abc
console.log(args);//cba
}
info('abc', 'c', 'b', 'a')
})()
函数重载
函数重载: 函数名相同, 而形参不同的多个函数
在JS中, 由于弱类型的特点和形参与实参可以不匹配, 是没有函数重载这一说的 但在TS中, 与其它面向对象的语言(如Java)就存在此语法
// 函数重载: 函数名相同, 而形参不同的多个函数
(()=>{
/*
函数重载: 函数名相同, 而形参不同的多个函数
需求: 我们有一个add函数,它可以接收2个string类型的参数进行拼接,也可以接收2个number类型的参数进行相加
*/
// 重载函数声明
function add (x: string, y: string): string
function add (x: number, y: number): number
// 定义函数实现
function add(x: string | number, y: string | number): string | number {
// 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 x + y
if (typeof x === 'string' && typeof y === 'string') {
return x + y
} else if (typeof x === 'number' && typeof y === 'number') {
return x + y
}
}
console.log(add(1, 2))
console.log(add('a', 'b'))
// 如果此时传入的是非法的数据,ts应该给我提示出错误的信息内容,报红色错误的信息
// console.log(add(1, 'a')) // error
})()
5.泛型
指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性。
引入
下面创建一个函数, 实现功能: 根据指定的数量 count
和数据 value
, 创建一个包含 count
个 value
的数组 不用泛型的话,这个函数可能是下面这样:
// 泛型:在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性。
(()=>{
// 需求:定义应该函数u,传入两个参数,第一个参数是数据,第二个参数是数量,函数的作用:根据数量产生对应个数的数据,存放在应该数组中
// 定义应该函数
function getArr1 (value:number,count:number):number[]{
// 根据数据和数量产生应该数组
const arr:number[]=[]
for(let i=0;i<count;i++){
arr.push(value)
}
return arr
}
const arr1=getArr1(100.123,3)
console.log(arr1);//100.123 , 100.123 , 100.123
// 定义一个函数,同上,只不过传入的是字符串类型
function getArr2 (value:string,count:number):string[]{
// 根据数据和数量产生应该数组
const arr:string[]=[]
for(let i=0;i<count;i++){
arr.push(value)
}
return arr
}
const arr2=getArr2('abc',3)
console.log(arr1);//abc abc abc
// 需求:可以传入任意类型的数据,返回的是存储这个任意类型数据的数组
function createArray(value: any, count: number): any[] {
const arr: any[] = []
for (let index = 0; index < count; index++) {
arr.push(value)
}
return arr
}
const arr3 = createArray(11, 3)
const arr4 = createArray('aa', 3)
console.log(arr1[0].toFixed(), arr2[0].split(''))
})()
使用函数泛型
// 使用泛型
function createArray2 <T> (value: T, count: number) {
const arr: Array<T> = []//等价于 const arr:T[]=[]
for (let index = 0; index < count; index++) {
arr.push(value)
}
return arr
}
const arr5 = createArray2<number>(11, 3)
console.log(arr3[0].toFixed())
// console.log(arr3[0].split('')) // error
const arr6 = createArray2<string>('aa', 3)
console.log(arr4[0].split(''))
// console.log(arr4[0].toFixed()) // error
多个泛型参数的函数
一个函数可以定义多个泛型参数
// 多个泛型参数的函数:函数中有多个泛型的参数
(()=>{
function swap <K, V> (a: K, b: V): [K, V] {
return [a, b]
}
const result = swap<string, number>('abc', 123)
console.log(result[0].length, result[1].toFixed())
})()
泛型接口
在定义接口时, 为接口中的属性或方法定义泛型类型
在使用接口时, 再指定具体的泛型类型
// 泛型接口:在定义接口时, 为接口中的属性或方法定义泛型类型 在使用接口时, 再指定具体的泛型类型
(()=>{
// 需求:定义一个类,用来存储用户的相关消息(id,姓名,年龄)
// 通过一个类的实例对象调用add方法可以添加多个用户信息对象,调用getUserId方法可以根据id获取某一个指定的用户信息对象
// 定义一个泛型接口
interface IbaseCRUD <T> {
data: T[]
add: (t: T) => void
getById: (id: number) => T
}
// 定义一个用户信息的类
class User {
id?: number; //id主键自增
name: string; //姓名
age: number; //年龄
constructor (name, age) {
this.name = name
this.age = age
}
}
// 定义一个类,可以针对用户的信息对象进行增加及查询的操作
class UserCRUD implements IbaseCRUD <User> {
// 用来保存多个User类型的用户信息对象
data: User[] = []
// add()用来存储用户信息对象的
add(user: User): void {
// 产生id
user = {...user, id: Date.now()}
// 把用户的信息对象添加到data数组中
this.data.push(user)
console.log('保存user', user.id)
}
// getUserId()根据id查询指定的用户信息对象
getById(id: number): User {
return this.data.find(item => item.id===id)
}
}
// 实例化添加用户信息对象的类UserCRUD
const userCRUD = new UserCRUD()
userCRUD.add(new User('tom', 12))
userCRUD.add(new User('tom2', 13))
console.log(userCRUD.data)
})()
泛型类
在定义类时, 为类中的属性或方法定义泛型类型 在创建类的实例时, 再指定特定的泛型类型
(()=>{
// 定义一个泛型类
class GenericNumber<T> {
// 默认的属性的值的类型是泛型类
zeroValue: T
add: (x: T, y: T) => T
}
// 在实例化类的对象的时候,在确定泛型的类型
let myGenericNumber = new GenericNumber<number>()
// 设置属性值
myGenericNumber.zeroValue = 0
// 相加的方法
myGenericNumber.add = function(x, y) {
return x + y
}
let myGenericString = new GenericNumber<string>()
myGenericString.zeroValue = 'abc'
myGenericString.add = function(x, y) {
return x + y
}
console.log(myGenericString.add(myGenericString.zeroValue, 'test'))
console.log(myGenericNumber.add(myGenericNumber.zeroValue, 12))
})()
泛型约束
如果我们直接对一个泛型参数取
length
属性, 会报错, 因为这个泛型根本就不知道它有这个属性
// 没有泛型约束
function fn <T>(x: T): void {
// console.log(x.length) // error
}
我们可以使用泛型约束来实现
interface Lengthwise {
length: number;
}
// 指定泛型约束
function fn2 <T extends Lengthwise>(x: T): void {
console.log(x.length)
}
我们需要传入符合约束类型的值,必须包含必须 length
属性:
fn2('abc')
// fn2(123) // error number没有length属性
// 如果我们直接对一个泛型参数取 length 属性, 会报错, 因为这个泛型根本就不知道它有这个属性
(()=>{
// 定义一个接口,用来约束将来的某一个类型中必须要有的length这个属性
interface ILength{
// 接口中有一个属性length
length: number
}
// 让T这个泛型继承ILength这个接口
function fn <T extends ILength>(x: T): number {
// console.log(x.length) // error
return x.length
}
console.log(fn<string>('wav'));
})()
6.其他
声明文件
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能
什么是声明语句
假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 <script>
标签引入 jQuery
,然后就可以使用全局变量 $
或 jQuery
了。
但是在 ts 中,编译器并不知道 $ 或 jQuery 是什么东西
/*
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
声明语句: 如果需要ts对新的语法进行检查, 需要要加载了对应的类型说明代码
declare var jQuery: (selector: string) => any;
声明文件: 把声明语句放到一个单独的文件(jQuery.d.ts)中, ts会自动解析到项目中所有声明文件
下载声明文件: npm install @types/jquery --save-dev
*/
jQuery('#foo');
// ERROR: Cannot find name 'jQuery'.
这时,我们需要使用 declare var 来定义它的类型
declare var jQuery: (selector: string) => any;
jQuery('#foo');
declare var 并没有真的定义一个变量,只是定义了全局变量 jQuery 的类型,仅仅会用于编译时的检查,在编译结果中会被删除。它编译结果是:
jQuery('#foo');
一般声明文件都会单独写成一个 xxx.d.ts
文件
创建 01_jQuery.d.ts
, 将声明语句定义其中, TS编译器会扫描并加载项目中所有的TS声明文件
declare var jQuery: (selector: string) => any;
很多的第三方库都定义了对应的声明文件库, 库文件名一般为 @types/xxx
, 可以在 https://www.npmjs/package/package
进行搜索
有的第三库在下载时就会自动下载对应的声明文件库(比如: webpack),有的可能需要单独下载(比如jQuery/react)
内置对象
JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。
内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。
1.ECMAScript 的内置对象
- Boolean
- Number
- String
- Date
- RegExp
- Error
/* 1. ECMAScript 的内置对象 */
let b: Boolean = new Boolean(1)
let n: Number = new Number(true)
let s: String = new String('abc')
let d: Date = new Date()
let r: RegExp = /^1/
let e: Error = new Error('error message')
b = true
// let bb: boolean = new Boolean(2) // error
2.BOM 和 DOM 的内置对象
- Window
- Document
- HTMLElement
- DocumentFragment
- Event
- NodeList
const div: HTMLElement = document.getElementById('test')
const divs: NodeList = document.querySelectorAll('div')
document.addEventListener('click', (event: MouseEvent) => {
console.dir(event.target)
})
const fragment: DocumentFragment = document.createDocumentFragment()
更多推荐
TypeScript【continue...】
发布评论