ts基础学习(一)

下周要去实习了,公司现在都是用ts,拟准备重新学习一下,此篇对基础知识点进行记录。

学习typescript之前,要明白JavaScript的缺点,主要是维护成本高、不报错(如类型不明确)等。微软设计出ts,就是为了“曲线救国”。

一、基础知识点

1.1 ts简介

ts是js的超集,在js的基础上进行了扩展,并添加了类型,从动态类型语言变成静态类型语言。

注意的是,ts可以在任何支持js的平台执行,但不能被js解析器直接执行。

1.2 开发环境

  1. 下载安装node.js(https://nodejs.org/en),使用node -v看版本。
  2. 使用npm全局安装ts,npm i -g typescript,使用tsc查看。
  3. 新建完ts文件后,编译为js,执行tsc xxx.ts。

1.3 类型

类型 例子 描述
number
string
boolean
字面量 其本身 限制变量为该字面量的值
any * 任意类型,相当于关闭类型,默认
unknown * 类型安全的any
void 空值(undefined) 没有值(或undefined)
never 没有值 不能是任何值
object
array
tuple 元组,固定长度的数组
enum 枚举
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//可以使用 | 进行联合类型
let a: boolean | string;

//使用 & 表示条件同时
let str: {name: string} & {age: number};
str = {name: "yjl", age: 24};

//类型断言,告诉解析器变量的实际类型
/*
变量 as 类型
<类型>变量
*/
s = e as string;
s = <string>e;
1
2
3
4
5
6
7
8
9
10
11
12
13
//类型如果是对象,有两种形式
//第一种直接用object
let a: object;
a = {name: "yjl"};

//第二种是 {} 来指定对象中可以包含哪些属性
//语法:{属性名: 属性值, 属性名: 属性值}
//属性名后面加 ? ,表示属性可选
let b: {name: string, age?: number};

//若不确定属性的数量
//[propName: string]: any 表示任意类型的属性
let c: {name: string, [propName: string]: any};
1
2
3
//设置函数结构的类型声明
//语法:(形参: 类型, 形参: 类型) => 返回值类型
let d: (a: number, b: number) => number;
1
2
3
4
5
//数组
//类型[] / Array<类型>
let e: string[];
let f: number[];
let g: Array<number>;
1
2
3
4
5
6
7
8
9
10
11
12
13
//元组
//语法:[类型, 类型, 类型]

//枚举
enum Gender{
Male = 0,
Female = 1
}
let h: {name: string, gender: Gender};
h = {
name: "yjl",
gender: Gender.Male
}
1
2
//类型的别名
//type 别名 = 属性名

二、编译与打包

  • 编译:tsc xxx.ts

  • 对单个文件进行动态修改编译:tsc xxx.ts -w

但是这样对多个文件进行监视就很麻烦,同时这样会出现报错,提示变量无法重新声明。

解决方式:在终端执行命令 “ tsc –init ” 初始化tsconfig.json 文件

  • 编译:tsc
  • 监视:tsc -w

2.1 tsconfig.json配置文件

作用:是ts编译器的配置文件,编译器可以根据它的信息来对代码进行编译。

  • include

    作用:指定哪些ts文件需要被编译

    路径:**表示任意目录

    ​ *表示任意文件

    1
    "include": ["./src/**/*"],
  • exclude

    作用:指定排除在外的目录

    1
    2
    //默认值
    "exclude": ["node_modules", "bower_components", "jspm_packages"],
  • extends

    作用:定义被继承的配置文件

  • files

    作用:指定被编译文件的列表,只有需要编译的文件少时才会用到

  • compilerOptions

    作用:编译器选项

    常用属性 作用 例子
    target 指定ts被编译为ES的版本 es6、es2106、esnext
    module 指定要使用的模块化规范 commonjs、es6
    lib 项目中要使用的库,在浏览器一般默认 [“dom”, “webworker”]
    outDir 指定编译后文件所在目录 ./dist
    outFile 将全局作用域的代码合并为一个文件 ./dist/xxx.js
    allowJs 是否对js文件进行编译 false(默认)
    checkJs 是否检查js是否符合语法规范 false(默认)
    removeComments 是否移除注释 true
    noEmit 不生成编译后的文件 false
    noEmitError 当有错误时,不生成编译后的文件 true
    alwaysStrict 设置编译后的文件是否使用严格模式 false(默认)
    noImplicitAny 不允许隐式的any类型 true
    noImplicitThis 不允许不明确类型的this true
    strictNullCheck 严格检查空值 true
    strict 所有严格检查的总开关 true

2.2 webpack打包ts代码

  1. npm init -y

    首先得生成package.json,配置文件

  2. npm i -D webpack webpack-cli typescript ts-loader

    新建webpack.config.js文件,进行相关配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    const path = require("path");

    // webpack的配置信息
    module.exports = {
    entry: "./src/index.ts",
    output: {
    // 指定打包文件的目录
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js"
    },
    module: {
    rules: [
    {
    test: /\.ts$/,
    use: "ts-loader",
    exclude: /node_modules/
    }
    ]
    },
    mode: "development"
    }
  3. tsc –init来配置tsconfig.json文件

    这里可能会遇到大括号报错问题(Problems loading reference ‘https://json.schemastore.org/tsconfig‘: Unable to load schema from ‘https://json.schemastore.org/tsconfig‘: getaddrinfo ENOTFOUND json.schemastore.org.)

    解决方式,在tsconfig.json文件中添加一个字段。

    1
    "$schema": "http://json-schema.org/draft-04/schema#",

    在这里面可以对target、module、strict等常规属性进行配置。

  4. package.json配置

    在scripts中配置build。

    1
    "build": "webpack"

    以后直接执行npm run build即可打包。

  5. 自动引入html文件

    此时其实已经完成了webpack打包工作,但是后续引入需要在html文件中手动引入,比较麻烦。

    npm i -D html-webpack-plugin

    此插件可以自动生成html文件,进行相关操作,安装完毕后,需要在webpack.config.js进行对应配置。

    1
    2
    3
    4
    5
    6
    7
    8
    // 引入html插件
    const HTMLWebpackPlugin = require("html-webpack-plugin");

    module.exports = {
    plugins: [
    new HTMLWebpackPlugin(),
    ]
    }

    如果有对应html模板,可以配置属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 引入html插件
    const HTMLWebpackPlugin = require("html-webpack-plugin");

    module.exports = {
    plugins: [
    new HTMLWebpackPlugin({
    template: "./src/index.html"
    }),
    ]
    }
  6. 动态更新

    为了每次修改完代码,自动构建来展示页面,需要安装webpack-dev-server插件。

    npm i -D webpack-dev-server

    接着在package.json进行配置,在scripts中配置start。

    1
    "start": "webpack serve --open"

    以后直接执行npm run start即可自动更新效果。

  7. 清空目录

    npm i -D clean-webpack-plugin

    此插件可以自动清空dist目录文件,保证目录文件是最新的,安装完毕后,需要在webpack.config.js进行对应配置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 引入插件
    const HTMLWebpackPlugin = require("html-webpack-plugin");
    const { CleanWebpackPlugin } = require("clean-webpack-plugin");

    module.exports = {
    plugins: [
    new HTMLWebpackPlugin(),
    new CleanWebpackPlugin(),
    ]
    }

    同时,当我们需要将ts文件模块化引入时,需要进行相关配置。

    1
    2
    3
    4
    // 用来设置引用模块
    resolve: {
    extensions: [".ts", ".js"]
    }
  8. 兼容性

    为了对不同浏览器都可以正常使用,需要考虑兼容性问题。

    npm i -D @babel/core @babel/preset-env babel-loader core-js

    在webpack.config.js进行对应配置babel-loader

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    module: {
    rules: [
    {
    test: /\.ts$/,
    use: [
    {
    loader: "babel-loader",
    // 设置babel
    options: {
    // 设置预定义的环境
    presets: [
    [
    // 指定环境的插件
    "@babel/preset-env",
    // 配置信息
    {
    // 要兼容的目标浏览器
    targets: {
    "chrome": "58",
    "ie": "11"
    },
    // 指定corejs的版本
    "corejs": "3",
    // 使用corejs的方式:usage表示按需加载
    "useBuiltIns": "usage"
    }
    ]
    ]
    }
    },
    "ts-loader"
    ],
    exclude: /node_modules/,
    }
    ]
    },

三、面向对象

js本质是基于对象的语言,也可以简单的说是面向对象(当然我觉得不太准确),简而言之,是围绕对象的,程序之中所有的操作都需要通过对象来完成。

计算机程序的本质就是对现实事务的抽象,抽象的反义词是具体,事务到了程序中就变成了对象。在程序中所有对象都被分成了两个部分:数据和功能,数据在对象中叫属性,功能被称之为方法

3.1 类

要创建对象,必须要先定义类,类可以理解为对象的模型。

1
2
3
4
5
6
7
8
9
10
11
class Person{
//实例属性
readonly name: string = "yjl";
age: number = 18;
//静态属性
static title: string = "人";

sayHello(){
console.log(this.name);
}
}

属性分为实例属性与静态属性(类属性),实例属性需要实例化后才能访问,静态属性直接通过类名.属性即可访问,如上述Peroson.title。readonly是一个只读限制。

方法同样如此,分为实例方法与类方法,前一个通过构造实例调用,后一个通过直接类名.方法名调用。

3.2 构造函数和this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person{
name: string;
age: number;

constructor(name: string, age: number){
this.name = name;
this.age = age;
}

sayHello(){
console.log(this.name);
}
}

const per = new Person("yjl", 18);

3.3 继承

使用继承后,子类会拥有父类所有的属性和方法,实际开发经常会用到,遵循OCP原则。

1
2
3
4
5
6
7
8
9
10
11
12
class Person{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}

sayHello(): void{
console.log(this.name);
}
}

对Person进行继承,super就是对父类的属性和方法进行继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Male extends Person{
gender: string;
constructor(name: string, age: number, gender: string){
super(name, age);
this.gender = gender;
}
sayHello(): void {
super.sayHello();
}
sayGender(): void{
console.log(this.gender);
}
}

这里我们如果不希望Person被实例化,那可以通过abstract使其变为抽象类,抽象类可以定义抽象方法,也是使用abstract。

1
2
3
abstract class Person{
abstract sayName(): void;
}
1
2
3
4
5
class Male extends Person{
sayName(){
...
}
}

注意:抽象方法没有方法体,子类必须对抽象方法进行重写。

3.4 接口

作用:定义一个类的结构,去定义一个类中应该包含哪些属性,同时接口也可以当类型声明去使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface myInterface{
name: string;
age: number;
}
const obj: myInterface = {
name: "yjl",
age: 18
}

//跟类型声明一样
let b: {name: string, age?: number};
b = {
name: "yjl",
age: 18
}

不过interface可以重复声明,那么实例化的时候就是要包含所有属性,且interface只定义结构,也就是都是类属性和类方法,跟类还是有很大区别的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface myInterface{
name: string;
age: number;
}
interface myInterface{
sayHello(): void;
}
class MyClass implements myInterface{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
sayHello(): void {
console.log(`${this.name},你好!`);
}
}

这里看起来用interface定义类,好像比常规定义类的方法比较麻烦。但是用interface定义有限制,所以当满足接口的要求时,可以具有某些特性。

3.5 属性的封装

现在属性是在对象中限制的,属性可以被任意的修改,可能会存在安全隐患。因此,可以添加属性的修饰符(public、private、protected)来针对这类问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Person{
protected _name: string = "yjl";
private _age: number;
private _title: string = "人";
constructor(name: string, age: number, title: string){
this._name = name;
this._age = age;
this._title = title;
}

//第一种
getTitle(){
return this._title;
}
setTitle(val: string){
this._title = val;
}

//第二种
get age(){
return this._age;
}
set age(val: number){
if(val >= 0){
this._age = val;
}
}

sayHello(): void{
console.log(this._name);
}
}

const per = new Person("lgy", 18, "成年人");
per.setTitle("学生");
per.age = 23;

一旦添加属性修饰符,就需要通过getter、setter方法来对属性进行读取和修改操作,方法有两种,如上所示。

同时,属性修饰符还有个语法糖

1
2
3
4
class test{
constructor(public name: string, public age: number){}
}
const t = new test("lgy", 24);

等价于:

1
2
3
4
5
6
7
8
9
class test{
public name: string;
public age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
}
const t = new test("lgy", 24);

3.6 泛型

当我们遇到类型不明确时候,可以使用泛型,避免使用any关闭类型检查的问题。

1
2
3
function fn<T>(a: T): T{
return a;
}

使用方式有两种,第一种:可以直接调用具有泛型的函数,不指定泛型,TS可以自动推断类型;第二种:指定泛型。

1
2
3
let res1 = fn(10);

let res2 = fn<string>("hello");

当然泛型不仅仅定义一个。

1
2
3
4
5
6
function fn2<T, K>(a: T, b: K): T{
console.log(b);
return a;
}

fn2<number, string>(123, "hello");

如果要指定泛型的具体情况,可以使用interface配合。

1
2
3
4
5
6
7
interface Inter{
length: number;
}

function fn3<T extends Inter>(a: T): number{
return a.length;
}

类中也可以使用泛型。

1
2
3
4
5
6
7
class MyClass<T>{
name: T;
constructor(name: T){
this.name = name;
}
}
const mc = new MyClass<string>("yjl");