ts的特点:
安装ts命令:
npm install typescript -g
检查ts是否安装成功命令(查看ts版本):
tsc -v
// 01.第一个ts.ts
(() => {
function sayHi(str: string) {
return `hello ${str}`;
}
let str = 'lily';
console.log(sayHi(str));
})();
该文件需要通过tsc 01.第一个ts.ts
命令编译成对应的01.第一个ts.js
文件,然后可以在index.html文件中引入查看执行结果。
// index.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script src="./01.第一次的ts.js">script>
body>
html>
注意:
tsc ts文件名
命令编译ts为js,然后引入编译后的js文件。tsc --init
命令,此时会生成一个tsconfig.json文件// "outDir": "./", /* Specify an output folder for all emitted files. */
"outDir": "./js", /* 最终编译的js文件自动放到js文件夹中 */
// "strict": true, /* Enable all strict type-checking options. */
"strict": false, /* 不使用严格模式 */
通过上述操作就能在vscode中自动将ts文件编译成js文件。
TypeScript 里的类型注解是一种轻量级的为函数或变量添加约束的方式。TypeScript提供了静态的代码分析,它可以分析代码结构和提供的类型注解。
(() => {
function showMsg(str: string) {
return `hello ${str}`;
}
let msg = [1, 2, 3];
// let msg = 'lily';
console.log(showMsg(msg));
})();
// 此时将函数 参数msg变为数组时编译ts文件会报错,但是依然编译成了js文件,但是可能会出现一些无法预期的错误
01_Typescript/02.ts在vscode中自动编译/01.类型注解.ts:8:25 - error TS2345: Argument of type 'number[]' is not assignable to
parameter of type 'string'.
8 console.log(showMsg(msg));
~~~
[上午9:37:41] Found 1 error. Watching for file changes.
// 下面这个例子只是将上面例子中的函数参数由对象换成了类的实例
(() => {
interface IPerson {
firstName: string,
lastName: string,
};
class Person {
firstName: string;
lastName: string;
fullName: string;
constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
this.fullName = `${firstName} ${lastName}`;
}
}
function showFullName (person: IPerson) {
return `${person.firstName}_${person.lastName}`;
}
let person = new Person('欧阳', '夏丹');
console.log(showFullName(person));
})();
npm init -y
命令来初始化当前文件夹下的package.json文件。
- npm install --save typescript
- npm install --save webpack@4.41.5 webpack-cli@3.3.10 webpack-dev-server@3.10.2
- npm install --save html-webpack-plugin@4.4.1 clean-webpack-plugin
- npm install --save ts-loader@4.0.0 cross-env
src/main.js
public/index.html
build/webpack.config.js文件
// clean-webpack-plugin这个插件会在打包前删除原来打包的文件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// html-webpack-plugin这个插件会帮助我们在 webpack 打包结束后,自动生成一个 html 文件,并把打包产生文件引入到这个 html 文件中去。
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' // 指定以这个目录下的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
},
}
"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"
(() => {
// 布尔类型
let flag: boolean;
flag = true;
console.log(flag);
// 数字类型
let n1: number = 1; // 十进制
let n2: number = 0b01; // 以0b开头是二进制
let n3: number = 0o12; // 以0o开头是八进制
let n4: number = 0xa; // 以0x开头是十六进制
console.log(n1, n2, n3, n4);
// 字符串类型
let str1: string = '床前明月光';
let str2: string = '疑是地上霜';
console.log(`${str1},${str2}`);
// 字符串和数字的拼接
let num: number = 10;
let str: string = '这个数字是';
console.log(str + num); // 这个数字是10
let ss: string = str + num;
console.log(ss); // 这个数字是10
// null与undefined
let nul: null = null;
let udf: undefined = undefined;
console.log(nul, udf);
let num1: number = undefined;
// 注意:null与undefined都可以作为其他类型的子类型,也就是可以把null和undefined赋值给其他类型的变量
// 数组
// - 语法1:let 变量名: 成员项类型[] = [值1, 值2]
let arr1: number[] = [1, 2, 3];
// - 语法2:let 变量名: Array<成员项类型> = [值1, 值2]
let arr2: Array<number> = [1, 2, 3];
console.log(arr1, arr2);
// 注意:数组定义后,里面的数据类型必须和定义数组时的类型一致,否则有错误提示信息,也不能编译通过
// 元组:它类似数组,但是里面允许成员项是不同的数据类型,但是类型的数目和位置和赋值时数据的数目和位置必须一致
let arr3: [string, number, boolean] = ['lily', 10, true];
// 注意:元组类型在使用时数据位置和个数应该和在定义元组时数据类型及位置一致
// 枚举enum:当一组数据常用且个数固定此时可以定义成枚举类型,可以通过名字获取对应的值,使用枚举类型可以为一组数值赋予友好的名字
// 枚举里面的每个数据都可以叫元素,每个元素都有自己的编号,编号从0开始,依次加1
enum Color {
red,
green,
blue,
};
let red: Color = Color.red;
console.log(red, Color.red, Color.green, Color.blue); // 0 0 1 2
enum Color1 {
red = 1,
green,
blue,
};
console.log(Color1.red, Color1.green, Color1.blue); // 1 2 3
enum Color2 {
red = 1,
green = 10,
blue = 1000,
};
console.log(Color2.red, Color2.green, Color2.blue, Color2[1000]); // 1 10 1000 'blue'
// any类型:表示任意数据类型,在不确定当前数据是什么类型时可以用any类型的变量将数据存储起来
let str3: any = 100;
str3 = 'lily';
console.log(str3);
// 当一个数组中的数据个数不确定,类型不确定可以定义成any类型的数组
let arr4: any[] = ['lily', 100, true];
// console.log(arr4[0].toFixed()); // 这也是用any类型的弊端,在编译时通过,但在实际执行时会报错,因为此处数字类型没有toFixed这个方法
// void:表示没有任何数据类型,在函数声明时在小括号后面加上:void表示函数没有返回值
function fn (): void {
console.log('ceshi');
// return;
// return undefined;
// return null;
}
console.log(fn());
let vd: void = null; // 可以定义一个void类型的变量,用null或undefined对其进行赋值,因为null和undefined是任何数据类型的子类型
// object类型
function fn1(obj: object): object {
console.log(obj);
return {
name: 'lily',
age: 10,
};
}
console.log(fn1({ name: 'lilei', age: 10 }));
// console.log(fn1('111')); // 报错,因为要传入对象类型
console.log(fn1(new String(1))); // String {'1'}
console.log(fn1(String)); // String() { [native code] }
// 联合类型:表示取值可以是多种类型中的一种
// 需求:定义一个函数得到一个数字或字符串的字符串形式
function fn2(str: number | string): string { // 该函数的形参可以是数字类型或字符串类型
return str.toString();
}
console.log(fn2(123));
console.log(fn2('hello'));
// 类型断言
// - 语法1:<类型名称>变量名
// - 语法2:变量名 as 类型名称
// 但是上面的需求实现中可以区分下是否是字符串,如果是字符串直接返回,如果不是字符串调用toString
function fn3(str: number | string): string {
if ((str as string).length) { // 如果存在length属性说明是字符串可以直接返回,此时需要用类型断言
return str as string;
}
// if ((str).length) { // 如果存在length属性说明是字符串可以直接返回,此时需要用类型断言
// return str;
// }
return str.toString();
}
console.log(fn3(123));
console.log(fn3('hello'));
// 类型推断
let num2 = 10;
// num2 = '111'; // 此时ts的静态解析会报错,因为在初始化num2时由于赋值了number类型,所以经过类型推断该变量是number类型
let param; // 此时param在变量初始化时没有赋值则经过类型推断它的类型是any,所以下面可以给它赋任何类型的值
param = 10;
param = '111';
// 总结:ts中变量一开始是什么类型,那么后期赋值的时候,只能用这个类型的数据,是不允许用其他类型的数据赋值给当前的这个变量
})();
注意:
接口定义使用interface关键字,接口是一种对象的属性或行为的抽象。示例如下
(() => {
// 定义接口
interface IPerson {
firstName: string,
lastName: string,
};
// 限定函数参数类型,这样在函数中使用形参时会自动提示变量中的属性
function showFullName (person: IPerson) {
return `${person.firstName}_${person.lastName}`;
}
let person = {
firstName: '欧阳',
lastName: '夏丹',
};
// 在调用函数时如果传入的参数不含IPerson定义的结构的话ts编译会报错,但是代码执行时可能会出现未知错误
console.log(showFullName(person));
// 接口是一种对象的属性或行为的抽象
// 需求:id是number类型, 必须有, 只读的
// name是string类型, 必须有
// sex是string类型, 可以没有
interface IPerson {
readonly id: number, // 用readonly表示id字段是只读的
name: string,
sex?: string, // 用?表示sex字段可以可无
};
let person: IPerson = {
id: 1,
name: 'lily',
sex: 'female',
};
// person.id = 2; // 报错,id字段是只读的
delete person.sex; // 不报错,sex字段可有可无
console.log(person);
})();
注意:
为了用接口定义函数类型,需要在接口中定义一个调用签名。它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
interface IPersonInfo {
(name: string, age: number): string,
};
let fn: IPersonInfo = (name: string, age: number): string => {
return `${name} + ${age}`;
};
console.log(fn('lily', 10));
类可以通过implements实现一个接口或多个接口,或者说一个或多个接口可以同时约束一个类。
interface sport {
run(),
};
class Person implements sport {
run() {
console.log('我会跑');
}
}
let p = new Person;
p.run();
interface hobby {
myHobby(),
};
class Person1 implements sport, hobby {
run() {
console.log('我会跑');
}
myHobby() {
console.log('我的爱好是听音乐');
}
}
let p1 = new Person1;
p1.myHobby();
接口可以通过extends继承一个或多个接口。
interface IPersonNew extends sport, hobby {
food()
};
class Person2 implements IPersonNew {
run() {
console.log('我会跑');
}
myHobby() {
console.log('我的爱好是听音乐');
}
food() {
console.log('我爱吃的食物是香蕉');
}
}
let p2 = new Person2;
p2.food();
(() => {
class Person {
name: string
age: number
sex: string
constructor(name: string = 'lily', age: number = 10, sex: string = '女') {
this.name = name;
this.age = age;
this.sex = sex;
}
info(str: string): string {
return `${str},我的名字是${this.name},年龄是${this.age},性别是${this.sex}`;
}
}
let p = new Person;
console.log(p.info('hello'));
})();
继承是类与类之间的关系,通过extends关键字实现。最基本的继承:类从基类中继承了属性和方法。被继承的基类是父类也是超类,继承的类为子类,也称为派生类。
class Animal {
name: string
constructor(name: string) {
this.name = name;
}
run(distance: number) {
console.log(`${this.name}跑了${distance}米`);
}
}
class Horse extends Animal {
constructor(name: string) {
super(name);
}
run(distance: number = 5) {
console.log('我是horse中的run');
super.run(distance); //这是调用父类的run方法
}
}
注意:
类的多态就是父类型的引用指向子类型的对象,不同类型的对象针对相同的方法产生不同的行为。
class Animal {
name: string
constructor(name: string) {
this.name = name;
}
run(distance: number = 0) {
console.log(`${this.name}跑了${distance}米`);
}
}
class Horse extends Animal {
constructor(name: string) {
super(name);
}
run(distance: number = 5) {
console.log(`我是${this.name}中的run`);
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
run(distance: number = 10) {
console.log(`我是${this.name}中的run`);
}
}
// 父类型的引用指向不同的子类型对象
let h: Animal = new Horse('hourse');
let d: Animal = new Dog('dog');
function showMsg(a: Animal) {
a.run();
}
// 不同类型的对象的相同方法产生的行为不同
showMsg(h);
showMsg(d);
类中的修饰符:用可访问性于描述类中成员(属性、构造函数、方法)的可访问性
class Animal {
public name: string
public constructor (name: string) {
this.name = name;
}
public run (distance: number = 0) {
console.log(`${this.name} run ${distance}m`);
}
}
class Person extends Animal {
private age: number = 18
protected sex: string = '男'
run (distance: number = 5) {
console.log('Person jumping...');
super.run(distance);
}
}
class Student extends Person {
run (distance: number = 6) {
console.log('Student jumping...');
console.log(this.sex); // 子类能看到父类中受保护的成员
// console.log(this.age); // 子类看不到父类中私有的成员
super.run(distance);
}
}
console.log(new Person('abc').name); // 公开的可见
// console.log(new Person('abc').sex); // 受保护的不可见
// console.log(new Person('abc').age); // 私有的不可见
类使用 readonly 关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
class Person {
readonly name: string
constructor(name: string) {
this.name = name;
this.name = 'sanhao'; // 在构造函数中是可以修改name属性的
}
sayHi() {
// this.name = 'haha'; // 此处不能修改name属性,它是只读的
console.log('你好,' + this.name);
}
}
let p = new Person('lily');
// p.name = 'lilei'; // 此处不能修改name属性,它是只读的
console.log(p.name); // 'sanhao'
class Person {
// 在属性声明时直接赋值后还可以在构造函数中修改该属性值,但是在其他地方则不能修改该属性值
readonly name: string = 'lilei'
constructor(name: string) {
this.name = name; // 在构造函数中是可以修改name属性的
}
sayHi() {
// 类中的普通方法中不能修改readonly修饰的属性
// this.name = 'haha'; // 此处不能修改name属性,它是只读的
console.log('你好,' + this.name);
}
}
let p = new Person('lily');
// p.name = 'lilei'; // 此处不能修改name属性,它是只读的
console.log(p.name); // 'lily'
一旦constructor函数中的参数如果被readonly修饰后,则该参数就是参数属性,即类的属性成员并且是只读的。
class Person {
// 一旦constructor函数中的参数如果被readonly修饰后,则该参数就是参数属性,即类的属性成员并且是只读的
constructor(readonly name: string) {
this.name = name; // 在构造函数中是可以修改name属性的
this.name = 'sanhao';
}
sayHi() {
// this.name = 'haha'; // 此处不能修改name属性,它是只读的
console.log('你好,' + this.name);
}
}
let p = new Person('lily');
// p.name = 'lilei'; // 此处不能修改name属性,它是只读的
console.log(p.name); // 'sanhao'
class Person {
// 在构造函数中的参数用public修饰时相当于给类新增public属性成员
constructor(public name: string) {
this.name = name; // 在构造函数中是可以修改name属性的
this.name = 'sanhao';
}
sayHi() {
// this.name = 'haha'; // 此处不能修改name属性,它是只读的
console.log('你好,' + this.name);
}
}
let p = new Person('lily');
// p.name = 'lilei'; // 此处不能修改name属性,它是只读的
console.log(p.name);
通过getter/setter来截取对对象成员的访问。用于控制对对象中成员项的访问。
class Person {
firstName: string
lastName: string
constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {
return `${this.firstName}_${this.lastName}`;
}
set fullName(val) {
const names = val.split('_');
this.firstName = names[0];
this.lastName = names[1];
}
}
let p = new Person('东方', '不败');
console.log(p.fullName); // '东方_不败'
p.fullName = '诸葛_孔明';
注意: 如果只有get没有set,则fullName是只读属性
静态成员:类中通过static修饰的属性或方法称为静态成员,静态成员在使用时通过类名.成员名
的方式调用
class Person {
// 注意类中有一个默认的静态成员属性name
static nameClass: string
constructor() {}
static sayHi() {
console.log('hi');
}
}
Person.nameClass = 'xiaotiantian';
console.log(Person.nameClass);
Person.sayHi();
注意:类的构造函数constructor不能用static方法
通过abstract修饰的类称为抽象类,不能被实例化。它里面可以用abstract修饰定义抽象方法,不能有实现;也可以定义实例方法。抽象类的子类必须实现抽象类中的抽象方法。
// 抽象类用abstract修饰
abstract class Animal {
// 用abstract修饰的方法是抽象方法,没有实现
abstract run()
// 实例方法
eat() {
console.log('吃');
}
}
// let a = new Animal; // 报错,抽象类不能被实例化
class Dog extends Animal {
run() {
console.log('dog run');
}
}
const d = new Dog;
d.run();
d.eat(); // 调用的是抽象类中的实例方法
ts中的函数和js中的函数一样,分为具名函数(函数声明),匿名函数(函数表达式)。只是多了类型限制。
function add(x: number, y: number): number {
return x + y;
}
let add1 = function(x: number, y: number): number {
return x + y;
}
// 上面add1的完成形式可以写成下面的add2
let add2: (x: number, y:number) => number = function(x: number, y: number): number {
return x + y;
}
函数中用? 修饰的参数是可选参数
// 需求:定义一个函数,如果不传参数返回默认参数,如果只传姓氏返回姓氏,如果传姓氏和名字则返回姓氏名字
// 这个函数中用? 修饰的参数是可选参数
let getFullName = function(firstName: string = '东方', lastName?: string) {
if (lastName) {
return `${firstName}_${lastName}`;
}
return firstName;
}
console.log(getFullName()); // '东方'
console.log(getFullName('诸葛')); // '诸葛'
console.log(getFullName('诸葛', '孔明')); // '诸葛_孔明'
注意:剩余参数必须放在函数定义时的最后面
function add3 (x: string, ...args: string[]) {
console.log(x);
console.log(args);
}
add3('a', 'b', 'c'); // 'a' ['b', 'c']
函数重载就是函数名相同,但是函数参数类型或个数不同。
// 需求: 我们有一个add函数,它可以接收2个string类型的参数进行拼接,也可以接收2个number类型的参数进行相加
// 函数的重载声明
function add4(x: string, y: string): string
function add4(x: number, y: number): number
function add4(x: string | number, y: string | number): string | number {
if (typeof x === 'string' && typeof y === 'string') {
return x + y;
}
if (typeof x === 'number' && typeof y === 'number') {
return x + y;
}
}
console.log(add4(10, 10));
console.log(add4('你好', '世界'));
// console.log(add4(10, '世界')); // 报错,因为函数的重载声明中两个参数要么全部是string类型,要么是number类型
泛型就是在定义函数、接口、类是不确定数据类型,但在函数、接口、类使用时才能确定数据类型。使用
其中尖括号中的大写字母随意。
// 需求:根据指定的数量 count 和数据 value , 创建一个包含 count 个 value 的数组
// 下面这个是不用泛型的情况
function getArr(value: any, count: number): any[] {
let arr: any[] = [];
for(let i = 0; i < count; i++) {
arr.push(value);
}
return arr;
}
let arr = getArr(1, 3);
console.log(arr[0].toFixed(2)); // '1.00' 此处在调用toFixed的时候没有智能提示
console.log(getArr('hi', 3)); // ['hi', 'hi', 'hi']
// 下面使用泛型的方式
function getArr1<T>(value: T, count: number): T[] {
// let arr: T[] = [];
let arr: Array<T> = [];
for(let i = 0; i < count; i++) {
arr.push(value);
}
return arr;
}
let arr1 = getArr1<number>(2,3);
console.log(arr1[0].toFixed(2)); // '2.00' 此处有智能提示
function getArr2<K, V>(value1: K, value2: V): [K, V] {
return [value1, value2];
}
let arr2 = getArr2<string, number>('ha', 1);
console.log(arr2); // ['ha', 1]
class User {
id: number
name: string
age: number
constructor(id: number, name: string, age: number) {
this.id = id;
this.name = name;
this.age = age;
}
}
interface IPerson<T> {
data: T[],
add: (t: T) => void,
get: (v: number) => T,
}
class Person implements IPerson<User>{
data: User[] = [];
add(v: User): void {
this.data.push(v);
}
get(u: number): User {
return this.data.find(item => item.id === u);
}
}
let p: Person = new Person;
p.add(new User(1, 'lily', 10));
p.add(new User(2, 'lucy', 18));
console.log(p);
console.log(p.get(1));
// 定义泛型类
class Gener<T> {
val: T
add: (x: T, y: T) => T
}
// 在创建实例时指明类型
let g = new Gener<number>;
g.val = 10;
g.add = function(x: number, y: number): number {
return x + y;
};
console.log(g.add(1, 2));
let gs = new Gener<string>;
gs.val = 'hi';
gs.add = function(x: string, y: string): string {
return x + y;
}
console.log(gs.add('hi ', 'lily'));
interface ILength {
length: number,
}
function fn<T extends ILength>(x: T): number{
return x.length;
}
console.log(fn('what?')); // 5
// console.log(fn(11)); // 报错,因为函数参数是数字类型没有length属性
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能
/*
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
声明语句: 如果需要ts对新的语法进行检查, 需要要加载了对应的类型说明代码
declare var jQuery: (selector: string) => any;
声明文件: 把声明语句放到一个单独的文件(jQuery.d.ts)中, ts会自动解析到项目中所有声明文件
下载声明文件: npm install @types/jquery --save-dev
*/
jQuery('#foo');
// ERROR: Cannot find name 'jQuery'.
一般声明文件都会单独写成一个 xxx.d.ts 文件。创建 01_jQuery.d.ts, 将声明语句定义其中, TS编译器会扫描并加载项目中所有的TS声明文件
declare var jQuery: (selector: string) => any;
很多的第三方库都定义了对应的声明文件库, 库文件名一般为 @types/xxx, 可以在 https://www.npmjs.com/package/package 进行搜索
有的第三库在下载时就会自动下载对应的声明文件库(比如: webpack),有的可能需要单独下载(比如jQuery/react)
/* 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
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()