TypeScript

一、TS简介

1. TS是什么?

一门基于 JS,但又优于 JS 的语言。我们可以把它理解为是灌输了面向对象思想的JS。

2. 为什么会有 TS 的出现?

首先,JS的类型并不针对变量,而是针对数据,而在我们拿变量去对数据进行计算时,更重要的是变量的类型。其次,因为 JS 首屈一指的地位,我们并不可能直接将 JS 替换掉,更好的解决方案就是对 JS 进行优化。

TS的出现主要就是为了解决 JS 由于变量没有类型函数参数也没有类型,而产生的许多问题。例如在计算时,可能会意外地将数值类型的变量 和 字符串类型的变量进行混合计算,从而产生预料之外的结果。但是这种情况下产生了预料之外的结果,又不会报错,就会给后续的计算带来极其严重的问题。

学习 TypeScript 有一个极其需要注意的点,就是“ : ”和“ = ”的区分。很好区分,“:”基本上都用在类型的限制上,例如 a: number、function fun(a: number, b: number): number{return a;}。在其他位置很少会使用“:”。

3. TS的特征:

  1. TS 完全支持 JS,就比如 ES 新特性;
  2. TS可以在任何支持JS的平台中执行;
  3. TS扩展了JS,并添加变量类型;
  4. TS不能被解析器直接执行,因此就需要先对TS进行编译,编译成JS,再去执行。
  5. TS具有极强的灵活性,可以根据我们自己配置的参数来决定是严格类型还是松散类型。

TS支持所有JS的类型,同时又对类型进行了扩充,如:void、any(任意类型,当我们在TS中不指明数据类型,那么该变量就是隐式any类型)、never、tuple(元组)、emurme(枚举)、interface(接口)、抽象类等高级类型

TypeScript_第1张图片

 

4. 结构系统:

1)结构类型系统

通过类型的实际结构确定两个类型是否相等或兼容。如 TS、Go...

class Foo {
  public hi() {
    cosnole.log('hi~');
  }
}
class Bar {
  public hi() {
    cosnole.log('hello~');
  }
}

const a: Foo = new Foo();
cosnt b: Bar = a;  // Works!

b.hi(); // Works!

这里因为 Foo 与 Bar 有相同的结构,在这里,二者有相同的函数,那么就认为两种类型是可以兼容的。因此赋值操作可以生效而不报错。

2)名义类型系统 

通过类型的名称确定两个类型是否相等。如 C、C++、Java、C#、Rust...

#include 

class Foo {
  public: 
    void hi() { 
      std::cont << "Hi" << std::endl; 
    }
};
class Bar {
  public: 
    void hi() { 
      std::cont << "Hello" << std::endl; 
    }
};

int main() {
  Foo foo;
  Foo &fooRef = foo; // Works!

  Bar &bar = foo; // Error: type 'Bar' cannot bind to a value of unrelated type 'Foo'
  return 0;
}

在使用名义类型系统的语言中,两个类型只要名字不用,就是不兼容、不相等的。

5. 值空间 & 类型空间 

  • 类型空间:在编译期存在的各种类型。这部分由TS Compiler也就是TSC【TS编译器】创建。如:type、interface、enumclassnamespace等。它们会在类型空间中被引入类型、
  • 值空间:运行时的各种值,由JS引擎创建。如:const、let、var、enumclassnamespace等。它们会在值空间中被引入对应的值。

可以看到,enumclassnamespace 会同时为类型空间引入类型,为值空间引入值。

在实际中,可以用来帮助我们解决一些开发中遇到的一些bug,例如:

// 书写的 typescript 代码
interface IPoint {
  x: number;
  y: number;
}

const p: IPoint = {
  x: 0;
  y: 0;
}

if (p instanceof IPoint) {} // 报错

上面代码的最后一行会报错,原因可以根据上面的解析来判断,IPoint 属于处于编译时的类型空间,编译为 JS 后,该空间的内容会被移除,IPoint 自然也就不存在了。

// 编译后的 js
“use strict”;

const p = {
  x: 0;
  y: 0;
}

if (p instanceof IPoint) {} // 报错

正确的写法应该是:

// 书写的 typescript 代码
interface IPoint {
  x: number;
  y: number;
}

class Point implements IPoint {
  cosntructor(
    public x: number,
    public y: number
  ){}
}

const p: IPoint = {
  x: 0;
  y: 0;
}

if (p instanceof Point) {} // Works!

编译后的js

class Point {
  cosntructor(x, y) (
    this.x = x,
    this.y = y
  )
}

const p = {
  x: 0;
  y: 0;
}

if (p instanceof Point) {} // Works!

可以正常运行~ 

二、TS中的解决方案

1. 基本类型:

1)变量的数据类型

TS中的声明方式:let a : number

数据类型在变量中的使用:

// 方式一:
let a: number;
a = 10;
// a = "test";  // false

// 方式二:
let b:boolean = false;
b = true;
// b = 3;      // false

// 方式三:
let c = 123;
c = 456;
// c = "test"; // false

数据类型在函数参数中的使用:

function fun(a:number, b:number):number{
//----------限制a的类型-----↑--------ʌ-------------
//--------------------限制b的类型----|-------------
//---------------------------限制fun返回值的类型----
  return  a+b;
}

当然如果没有返回值,就可以将返回值类型设置为void。这个时候理论上不允许有任何返回值,但是如果返回值写的是 return 或者是 return undefined、null,是不会报错的。

我们来看一下下面这个例子,如果我们依然要对a赋予number以外类型的值,那么将会报错。

TypeScript_第2张图片

但是现在一个松散状态,针对TS中不严格的错误,依旧可以编译成功。后面我们会一起学习,如何在这种出现错误的情况下,不再编译产生相应的JS文件。

TypeScript_第3张图片

2)联合类型:

let sex : "male" | "female";
sex = "male";
sex = "female";
// sex = "nan";  // false

做变量时,对数据类型的限制也可以使用符号:" | "

let doubleType : number | string;
doubleType = 3;
doubleType = "string";
// doubleType = false;  // false

3)非指定类型:any与unknown

例如在一个函数中,我们不知道传递的参数是什么类型,或者是什么类型都有可能,就可以将数据类型限制为any或unknown,但是我们要尽可能避免使用any,因为any将不再进行类型检查。

any与unknown的区别:

TypeScript_第4张图片

可以看到:我们将一个any类型的变量赋给一个已知类型的变量,不会有任何错误提示,这就表示,TS对数据类型做的限制将不再有任何意义,又回到了JS的时代。但是如果是unknown就不同,它会有错误提示,哪怕unknown最后一次赋值两个数据的类型相同,依旧会存在问题。

那么unknown是如何使用的呢?很简单,加一个if判断即可:

TypeScript_第5张图片

这个时候你可能会有一个想法,我如果不判断是否是string类型,而是判断是否是number类型,会报错吗?

TypeScript_第6张图片

显然,当然是会的。

还有一种简单的解决方法:类型断言

4)断言:as

用来告诉浏览器变量是什么类型。像下面给p的类型指定为 unknown,后面赋值后,浏览器依旧无法判断出真正的类型是什么,这个时候,就可以由我们告诉浏览器,这里就是xxx类型(两种写法):

TypeScript_第7张图片

当然也不允许随意做断言:

TypeScript_第8张图片

5)never

这个返回值类型很奇特,体现在什么地方呢?它不允许有任何的返回值,但是我们什么都不写,却又会报错。一般都用它来抛出错误

2. 引用类型:

1)声明一个对象

let person : {name: string, age?: number, [propsNames: string]: unknown};
person = {name: "光太郎", age: 18};
person = {name: "东光太郎", gender: "男"};

name:必须存在;

age:对age加了“ ?”,就表示这是一个可选参数,想要就要,不想要就不要;

propsNames:我们在这里加了一个数组的符号“[ ]”,就表示除了对前两个属性的限制外,可以向里面添加任意数量的属性,属性名必须为字符串类型,属性值为unknow任意类型,当然最好还是为属性值限定出具体的类型。

2)有趣的函数声明

// 先对fun函数的参数、返回值类型做限制
let fun : (a: number, b: number)=> number;
// 定义函数
fun = function (n1, n2){
  return n1+n2;
}

效果就和下面这串代码相同: 

function fun(n1: number, n2: number): number{
  return n1+n2;
}

 一定要注意,箭头函数的声明方式,是不允许增添参数的,也不允许修改参数的数据类型。

3)数组

let arr1: string[];  // 数组所有的元素都只能数string类型
let arr2: Array;  // 同上
arr1 = ["a", "b", "c"];

如果数组内的元素不是字符串,就会直接报错。

3. TS中新增的数据类型:

1)元组

元组:固定长度的数组。

其在定义上和数组十分相似,但是数据类型的设定并不在外部,而是在内部。

// 数组
let arr: string[];

// 元组
let arr: [string, string, number];
arr = ["string", "string", 123];

2)枚举

枚举主要应用于对简单非语义数据的替换

简单地说,就是我们想用0表示女,用1表示男,但是又担心后面可能会忘了0、1谁表示男,谁表示女,那么就使用枚举。

举个简单的例子:

// 没有使用枚举

let j:{name: string, gender: 0 | 1};  // 0:女、1:男

j = {
  name : "光太郎",
  gender: 0
}

对于我们自己来说, 可能很容易理解,0就是女生、1就是男生。但是如果将这串代码交给别的程序员,在没有辅助文档的帮助下,可能很难理解。

在这时,我们就可以借助枚举来完成:

// 使用了枚举

enum Gender{
  Male,  // 这里再后台其实存储的就是0
  Female   // 1
}

let i:{name: string, gender: Gender}

i = {
  name : "光太郎",
  gender: Gender.Male
}

console.log(i.gender === 0)   // true

这时,虽然我们对i的gender的属性值设置为了Gender.Male,但是在后台其实会将其转化为数字0。也就是说,其实并不存在Gender这个对象,仅仅只有0和1而已。

个人很喜欢这种用法,觉得还是挺有用的。

4. TS新增的句法:

1)对象的属性可以使用&连接

let person : {name: string} & {age: number};
person = {
  name: "光太郎",   // 报错
}

person = {
  name: "光太郎",   // 正常
  age: 18
}

虽然不知道这么设定有什么意义,不过灵活性确实增加了很多,例如一个人可能有职员的属性、家人的属性、个人的基本属性,那么就可以分开写三个对象,然后进行拼接。 

2)type:“类型” 的别名

当一个类型被频繁使用,我们就可以将其提取出来,来可以提高复用性。

例如:(注意哈,这里面的1-6是特殊的类型,下面这种的表示也就说取值只能是1~6,当然我们也可以在这里写上各种真实的数据类型)

// 没有使用类型别名

let m: 1 | 2 | 3 | 4 | 5 | 6;
let n: 1 | 2 | 3 | 4 | 5 | 6;
let p: 1 | 2 | 3 | 4 | 5 | 6;
let q: 1 | 2 | 3 | 4 | 5 | 6;

m = 3;
n = 4;
p = 5;
q = 6;

此时就会发现m所对应的类型被大量重复利用,那么我们就可以将其提取出来,并单独存储:

// 使用了类型别名

type allTypes = 1 | 2 | 3 | 4 | 5 | 6;
let m: allTypes;
let n: allTypes;
let p: allTypes;
let q: allTypes;

m = 3;
n = 4;
p = 5;
q = 6;

这样就极大地增加了复用率。

type ID = number;

type User = {
  id: ID;
  name: string;
}

注意!type 和 let 十分相似,例如:仅存在于块级作用域中,不能被重复定义。

5. 泛型:

在定义函数、接口(专门用来约束对象的属性有哪些,以及属性值的类型是什么)、类时,不去指定具体的类型,而是在使用时,再去指定类型的一种特性。

简单总结:泛型定义在函数名、类名、接口名后面(类似于形参);用在具体需要被该类型限制的地方;传递于函数、类实例化的时候。

// :定义泛型、arg: T 使用泛型
function fn(arg: T): T {
    return arg;
}
// 向泛型中传递具体的类型 
fn(12);
let a: Array = [1, 2, 1];

let a: Array = [1, '2', 3];
            // : 定义泛型
class Person{
    // private _value: 使用泛型 T
    private _value: T;
    constructor(val: T) {
        this._value = val;
    }
}

                  // :向泛型中传递的具体类型 
let p = new Person(12)

1)泛型约束

泛型约束概念:通过配置,限制泛型中被传入的类型

// 定义泛型约束
interface ILength {
  length: number;  // 类型必须拥有 length 属性,并且 length 属性值必须为 number 类型
}

// 对泛型使用约束,表示传入的类型必须拥有length属性,那么我们可以传入string、any[]
function fun(n: T): number{
  return n.length;
}
// 也可以写为这样,表示类型可以是string
function fun(n: T): number{
  return n.length;
}

fun('hello');

6. 编译TS:

1)配置tsconfig.json文件,以使用tsc快速编译ts文件

前面我们已经说了,用TS写的代码,是无法直接被浏览器运行的,那么我们就需要将其编译为JS文件。使用到的命令就是tsc,但是tsc只能编译一个文件,我们那么多的文件,不可能一个一个编译,那么就需要我们配置一下 tsconfig.json,就可以很轻松地实现:一个tsc命令就能编译所有的TS文件,或者是tsc -w自动实时编译所有的TS文件。

首先,配置 tsconfig.json 文件:

include / exclude:需要/不需要进行编译的文件,直接写上文件的路径即可。其中,路径中**表示任意目录表示任意文件。举个例子,下面的例子表示hello目录下的任意目录下的任意文件,都被忽略。其中,默认被忽略的文件包括:node_modules、bower_components、jspm_packages:

"exclude": [
  "./src/hello/**/*"
]

② extends:继承指定的 tsconfig.json

"extends": "./src/configs/base"

files:设置需要进行编译的文件,效果类似于 include,但是 include 可以指目录,而 files 仅可以指文件。

 ⭐compilerOptions:编译器选项

        4.1) target:目标语言的版本,如ES5

        4.2) mudule:es6、commonjs等,指定要使用的模块化的规范

        4.3) lib:指定项目中需要使用到的库,默认是不改变的,例如我们将它的值设为[],就表示什么都不用,那么就无法对代码进行检查,doucument来自dom库,我们置空后,输入dou就不会提示doucument。

        4.4) outDir:编译后的文件所存放的目录。

        4.5) outFile:用于合并全局作用域中的ts文件,当我们想要将多个模块合并到一起,我们必须要使用一个统一的规范:amd、system(这个工作一般交给打包工具来完成)

        4.6) allowJs:是否对js文件进行编译,默认是false。

        4.7) checkJs:是否对JS进行检查。

        4.8) removeComments:是否移除注释,默认也是false,不移除。

        4.9) noEmit:不执行编译功能,也就不会生成最终的js文件。主要用于单纯想用tsc对代码进行一个校验。

        4.10) noEmitOnError:有错误的时候不生成编译文件。

严格检查:

        4.11) strict:严格检查的总开关。设置完一个值之后,可以对值不同的检查选项进行单独设置。

        4.12) alwaysStrict:是否对生成的文件启用严格模式。

        4.13) noImplicitAny:不允许隐式any类型。

        4.14) noImplicitThis:不允许使用无明确类型的this。

        4.15) strictNullChects:对空值进行严格的检查。

var box1 = document.getElementById('box1');
box1.addEventListenter('click', function(){
  alert("hello");
})

在这里可能会由于box1不存在而产生错误,在加上strictNullChects之后,就可以对空值检查,于是就在box1下面产生波浪线,目的是要我们对这里做一个判断。

我们有两种处理方式:

var box1 = document.getElementById('box1');
if(box1 !== null){
  box1.addEventListenter('click', function(){
    alert("hello");
  });
}

这样,就对box1的值做了判断,只有值不为null时才能进行相应的处理。

还有一种方式,更简便:

var box1 = document.getElementById('box1');
box1?.addEventListenter('click', function(){
  alert("hello");
})

意思是,只有在box1存在时才执行后面的内容。

7. 常用的内置工具类型:

1)Record

Record的内部定义,接收两个泛型参数;Record后面的泛型就是对象的类型

作用 :义一个对象的 key 和 value 类型

Record 

Record   // 空对象
Record // 任意对象
{}                      // 任何不为空的对象

2)Partial

作用:生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为可选项

interface Foo {
    name: string
    age: number
}
type Bar = Partial

// 相当于
type Bar = {
    name?: string
    age?: string
}


3)Required

作用:生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为必选项

interface Foo {
    name: string
    age?: number
}
type Bar = Required

// 相当于
type Bar = {
    name: string
    age: string
}


4)Readonly

作用:生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为必选  + 只读

readonly:只读,赋给对象属性,那么该属性就不可以被重新赋值。函数形参也是同理。

interface Foo {
    name: string
    age: number
}
type Bar = Required

// 相当于
type Bar = {
    readonly name: string
    readonly age: string
}


5)Pick

作用:生成一个新类型,继承 Foo 中 age、gender 属性

interface Foo {
    name: string
    age?: number
    gender: string
}
type Bar = Pick

// 相当于
type Bar = {
    age?: string
    gender: string
}


6)Exclude

作用:继承A在B中不具有的类型

type A = number | string | boolean
type B = number | boolean

type Foo = Exclude

// 相当于
type Foo = string


7)Extract

作用: 和 Exclude 相反

type A = number | string | boolean
type B = number | boolean

type Foo = Exclude

// 相当于
type Foo = number | boolean


8)Omit

作用:生成一个新类型,该类型拥有 Foo 中除了 age 属性以外的所有属性

type Foo = {
    name: string
    age: number
}

type Bar = Omit

// 相当于
type Bar = {
    name: string
}


9)NonNullable

作用:从泛型 T 中排除掉 null 和 undefined

type t = NonNullable<'name' | undefined | null>;

// 得到的结果为
type t = 'name'


10)InstanceType

作用:获得构造函数返回值的类型

type h = InstanceType {name: string, age: number}>

// 得到的结果
type h = {
    name: string;
    age: number;
}


三、webpack配置篇:

在实际的开发中,一般不直接使用TS编译器编译代码,大多数情况都是使用打包工具,很少会脱离打包工具去直接使用TS。

初始化一个webpack项目的过程:

1. npm init (-y) (自动)对当前文件夹做一个初始化

npm init

2. 安装基本的依赖:webpack / webpack-cli,可以选择性地安装typescript / ts-loader(可以将webpack和typescript进行一个整合) 

yarn add webpack webpack-cli typescript ts-loader -D

3. 创建webpack.config.js文件,做一些基本的配置

// webpack.config.js
const path = require("path");

// webpack中的所有配置信息都应该写在module.exports中,
module.exports = {

  // 指定入口文件
  entry: "./src/index.ts",

  // 指定打包文件所在目录
  output: {
    // 指定打包文件的目录
    path: path.resolve(__dirname, 'dist'),
    filename: "bundle.js",
  },

  // 指定webpack打包时使用的模块
  module: {
    // 打包规则
    rules: [
      {
        // 指定的是规则生效的文件
        test: /\.ts$/,
        use: 'ts-loader',
        // 这里要注意,这里的路径是一个正则表达式,因此我们需要在两侧添加“/”
        exclude: /node_modules/
      }
    ]
  }

}

4. 创建一个tsconfig.json,对ts做一个基本的配置:每一个配置的作用上面已经讲了

// tsconfig.json
{
  "compilerOptions": {
    "module": "ES6",
    "target": "ES6",
    "strict": true
  }
}

然后一个基本的可以满足ts开发的webpack项目就初始化完成了,但是,到了这里还有一些美中不足的地方,就是我们需要手动的创建一个index.html文件,然后引入由webpack打包的js文件,这是十分麻烦的一个过程,尤其对于js文件较多的情况。

5. 在package.json中做一个配置:这个配置的作用就是对我们写的项目进行一个打包的工作

// script中,配置:
    "build": "webpack"

6. 安装自动生成并配置html文件的插件html-webpack-plugin

 yarn add html-webpack-plugin -D

在安装完后,我们需要将这个插件配置进去:

        6.1) 在webpack.config.js中引入插件

// webpck.config.js
const HTMLWebpackPlugin = require('html-webpack-plugin')

         6.2) 在webpack.config.js中配置插件,然后可以通过我们自己的设定,修改生成的模板的格式,这其中有两种方式,第一种方式是我们需要修改什么,就配置什么;第二种就是我们先写一个模板,然后引入模板,根据模板来生成最终的文件

// 该配置和module同级

  // 配置webpack插件
  plugins: [
    // 自动生成html文件,并引入相关资源 
    new HTMLWebpackPlugin({
      // 方式一:直接自己一个一个来添加,自己添加哪些配置,就可以在生成的html文件中,添加对应的配置
      // title: "自定义title"   

      // 方式二:我们自己写一个模板
      template: "./src/index.html"
    }),  
  ]

7. 在项目中,安装一个内置的服务器

yarn add webpack-dev-server -D 

8. 然后在package.json的script中再做一个配置,这个配置可以让我们在输入npm start后自动用浏览器打开项目,还有一个非常优秀的部分,就是可以做到实时更新

    "start": "webpack server --open chrome.exe"

9. 我们每次打包,有可能原来在dist目录中的文件并没有被清除,所以,为了保证文件是一个最新的状态,我们每次可以将原来的文件先清除掉,这个时候可以借助clean-webpack-plugin插件

// webpack.config.js

// 执行命令
yarn add clean-webpack-plugin -D

// 在webpack.plugin.js中配置:
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

// 然后到webpack.plugin.js下的module.exports中的plugins中配置:
new CleanWebpackPlugin(),

10. 我们写一个模块,然后在一个文件中将其引入进去,再执行npm run build,这个时候,会出现一些问题,问题的原因是无法将ts识别为模块文件,这个时候,我们就需要做一些配置:

// webpack.config.js
  resolve: {
    // 告诉webpack以ts、js结尾的文件都可以作为模块使用
    extensions: ['.ts', '.js']
  }

11. 我们的浏览器还有可能存在一些兼容性问题,比如我们使用ES6的标准写的代码,但是被运行在了早起的ie浏览器中,因此就需要我们对低版本的浏览器做一个适配,这里可以借助babel

yarn add @babel/core @babel/preset-env babel-loader core-js -D

其中:

  • @babel/core:我们的核心工具,
  • @babel/preset-env:则包含了各种不同的环境,其中有各种浏览器不同的版本,当我们的代码运行在了不同的浏览器,只要这其中包含了对应的版本,就可以做一个很好的适配。
  • babel-loader:将babel与loader结合的工具。(所有的带loader后缀的库,作用都是与webpack相结合)
  • core-js:js的环境(模拟环境的代码),当我们的代码运行在一个较低版本的浏览器上时,我们可以不使用浏览器自身的环境,而是使用我们自己的环境,这样就可以让老版本的浏览器拥有较高的兼容性,core-js的功能比较多,我们在使用时,并不需要把所有的函数都引入进去,只需要按需加载。

然后添加bable相关的配置:

//webpack.config.js

        use: [

          // 配置加载器的两种方式:

          // 方式一:这种方式可以对加载器做一个更加复杂的配置
// ------------------------ 配置babel  ------------------------
          {
            // 指定加载器
            loader: 'babel-loader',
            // 设置babel
            options: {
              // 设置预定义的环境
              presets: [
                [
                  // 指定环境的插件
                  "@babel/preset-env",
                  // 配置信息
                  {
                    // 要兼容的目标浏览器,打包后的代码发生变化一部分的原因就是在这里产生的,
                    // 因为要兼容老版本的浏览器,因此在这里,就可能会使得打包代码不一样,
                    // 比如ie11就需要让const 变成 var
                    targets: {
                      "chrome":"88",
                      "ie":"11"
                    },
                    // 指定corejs的版本,主要作用体现在使用一些新的语法,例如Promise,
                    // 会使用我们自己携带的core.js来创建一个兼容老版本的效果相似语法,实现这一功能
                    "corejs":"3",
                    // 使用corejs的方式   usage:按需加载
                    "useBuiltIns": "usage"

                  }
                ]
              ]
            }
          },

          // 方式二:
// ------------------------ 配置ts-loader  ------------------------
          'ts-loader'
        ],

12. 上面说了,我们为了兼容老版本的浏览器,例如ie11,使出了浑身解数,先在target中指明兼容的版本:ie11,然后在corejs中,配置了一个可以做语法兼容的环境。

但是!在我们进行编译的过程中,依然有可能会产生问题,我们一起来看一下:

这是ts代码:

// index.ts  是我的入口文件
import {hi} from './m1'

function fun(s1: number, s2: number): number{
  return s1+s2;
}
console.log( fun(123, 456) );
console.log( hi );

const obj = {
  name: "光太郎",
  age: 18
}
console.log( obj );

obj.age = 19;

console.log( obj );

let fun2= (n1:number, n2:number) => n1+n2;
console.log( fun2(22, 33) );

这是被打包生成的js代码:

// bundle.js  打包生成的文件
(()=>{"use strict";console.log(579),console.log("你好");var o={name:"光太郎",age:18};console.log(o),o.age=19,console.log(o),console.log(55)})();

可以看到,打包后的代码,非常的简便,尽管我们在上买对ie11做了适配,包括各种语法格式适配,但是,生成的这个js文件,依然不可能适配ie11。

为什么呢?因为,我们做的适配,只是适用于从ts转化到js的过程,当我们的代码被转化为js,webpack为了避免变量的冲突,会生成一个匿名自调用的函数。这个匿名子调用的函数,并不受我们前面做的兼容适配的影响,所以,即使我们的代码再过于简便,也会产生问题。

怎么办呢?其实也很简单,只需要告诉webpack,不要使用箭头函数了,直接使用普通的函数就可以:我们只需要在webpack.config.js做一个简单的适配:

// webpack.config.js 下的 output
    environment: {
      // 告诉webpack不使用箭头函数
      arrowFunction: false
    }

然后来看一下成果:箭头函数就变成了普通的函数。

// bundle.js
!function(){"use strict";console.log(579),console.log("你好");var o={name:"光太郎",age:18};console.log(o),o.age=19,console.log(o),console.log(55)}();

来欣赏一下我们做的最终的模板,下次就可以直接拿着用了: 

// webpack.config.js

const path = require("path");

// 引入html插件
const HTMLWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

// webpack中的所有配置信息都应该写在module.exports中,
module.exports = {

  // 指定入口文件
  entry: "./src/index.ts",

  // 指定打包文件所在目录
  output: {
    // 指定打包文件的目录
    path: path.resolve(__dirname, 'dist'),
    filename: "bundle.js",
    // 配置打包环境
    environment: {
      // 告诉webpack不使用箭头函数
      arrowFunction: false
    }
  },

  // 指定webpack打包时使用的模块
  module: {
    // 打包规则
    rules: [
      {
        // 指定的是规则生效的文件
        test: /\.ts$/,
        // 这个规则是从下向上应用的,我们需要先让ts-loader将模块加载出来,然后借助babel将新标准的代码转化为老的标准
        use: [
          // 配置加载器的两种方式:

          // 方式一:这种方式可以对加载器做一个更加复杂的配置
          // 配置babel
          {
            // 指定加载器
            loader: 'babel-loader',
            // 设置babel
            options: {
              // 设置预定义的环境
              presets: [
                [
                  // 指定环境的插件
                  "@babel/preset-env",
                  // 配置信息
                  {
                    // 要兼容的目标浏览器
                    targets: {
                      "chrome":"88",
                      "ie":"11"
                    },
                    // 指定corejs的版本
                    "corejs":"3",
                    // 使用corejs的方式   usage:按需加载
                    "useBuiltIns": "usage"

                  }
                ]
              ]
            }
          },

          // 方式二:
          'ts-loader'
        ],
        // 这里要注意,这里的路径是一个正则表达式,因此我们需要在两侧添加“/”
        exclude: /node_modules/
      }
    ]
  },

  // 配置webpack插件
  plugins: [
    // 自动生成html文件,并引入相关资源 
    new HTMLWebpackPlugin({
      // 方式一:直接自己一个一个来添加,自己添加哪些配置,就可以在生成的html文件中,添加对应的配置
      // title: "自定义title"   

      // 方式二:直接引入对应的模板
      template: "./src/index.html"
    }),  
    new CleanWebpackPlugin(),
  ],

  // 用来设置引用模块,也就是告诉浏览器,哪些文件可以作为模块被引用
  resolve: {
    // 告诉webpack以ts、js结尾的文件都可以作为模块使用
    extensions: ['.ts', '.js']
  }

}

四、类与对象回顾

1. 实例属性静态属性

实例属性由对象访问,static静态属性由class直接访问。

实例属性、静态属性可以由readonly修饰,readonly修饰的属性只能读取,而不能修改。(注意,当我们想要将static与readonly相结合时,需要让static在前,readonly在后)

class Person{
  name: string = "Lisa";
  age: number = 18;
  static sex: string = "女";

}
const Lisa = new Person;
console.log( Lisa );
console.log( Person.sex )
console.log( Lisa.name );
console.log( Lisa.age );

2. 实例方法、静态方法

class Person{
  tellTheTrueth(){
    console.log( "小哥哥你真帅" )
  };
  static tellTheRealTrueth(){
    console.log( "小哥哥你帅爆了" )
  }

}
const Lisa = new Person;

Lisa.tellTheTrueth();
Person.tellTheRealTrueth();

3. 构造函数

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

}
const myLisa = new Beauty("Lisa", 18);

我们每次调用new Beauty就会调用class中的构造函数(构造器)。我们就可以在这个构造函数中做传参的操作。

4. 继承

关键字:extends,可以将父类中的所有属性、方法继承过来,可以在自己的类中添加新的方法、属性,也可以覆盖父类中的属性、方法。

class EachBeauty{
  name: string;
  age: number;

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

  tellTheTrueth(){
    console.log( this.name+" 说:哇~小哥哥你太帅了~" )
  };

}

class BeautyfromKorea extends EachBeauty{
  kiss(){
    console.log( "mu~a"+this.name+" 亲了你一下!" )
  }
}
class BeautyfromAmerica extends EachBeauty{
  hug(){
    console.log( "吧唧~"+this.name+" 抱了你一下!" )
  }
}

const isLisa = new BeautyfromKorea("Lisa", 18);
const KristenStewart = new BeautyfromAmerica("Kristen Stewart", 17);

console.log( isLisa.name );
console.log( KristenStewart.name );
isLisa.tellTheTrueth();
KristenStewart.tellTheTrueth();
isLisa.kiss();
KristenStewart.hug();

5. 关键字:super

在子类中,该关键字可以指向父类:

(function (){
  class EachBeauty{
    name: string;
    age: number;
  
    constructor(name: string, age: number){
      this.name = name;
      this.age = age;
    }
  
    tellTheTrueth(){
      console.log( "我妈妈喊你去我家吃饭" )
    };
  
  }
  
  class BeautyfromKorea extends EachBeauty{
    sex: string;

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

    kiss(){
      console.log( "mu~a"+this.name+" 亲了你一下!" )
    };
    tellTheTrueth(){
      super.tellTheTrueth();
    };
  }

  var Lisa = new BeautyfromKorea("Lisa", 18, "女");
  Lisa.tellTheTrueth();
  console.log( Lisa.sex );
})();

一定要注意,如果我们在子类中重写了父类的构造函数必须要在子类的构造函数中对父类的构造函数进行调用,方法很简单,直接在子类中写一个super(),然后将父类构造函数的参数传进去即可。注意,我们同时还要在子类的构造函数中加上父类的参数。

6. abstruct 抽象类 & 抽象方法

抽象类:

一般应用于父类,我们不想让父类被实例化,就可以直接在父类class前加上一个关键字:abstract

//抽象类

// 父类 此时父类就无法被实例化
abstract class EachBeauty{...}

// 子类
class BeautyfromKorea extends EachBeauty{...}

抽象方法:

抽象方法定义在父类中,没有函数体,用于让子类继承

//抽象类

// 父类 此时父类就无法被实例化
abstract class EachBeauty{
  ...
  abstract laugh():void;
}

// 子类
class BeautyfromKorea extends EachBeauty{
  ... 
  laugh(){
    console.log( "Korea女孩笑得很开心" )
  }
}

我们需要注意,抽象方法不同于普通的方法,我们在写抽象方法时,必须指明返回值类型

7. 接口:interface

接口概念:对 “对象” 进行一个描述。

// 定义一个接口规范
interface IPerson {
  name: string; // 确定属性
  age: number;
  id?: number; // 可选属性
  phoneNumber?: number;
  [propName: string]: any; // 任意属性(确定属性、可选属性都是它的子属性)
}

// 使用接口
let xiaoming: IPerson = {
  name: 'XiaoMing',
  age: 18,
  id: 1,
  sex: 'man',
}

在我们对 xiaoming 对象使用了接口规范后,对象中必须包含 name、age 属性,name 对应的 value 为字符串类型,age 对应的 value 为数字,对象只能包含这两个属性,不能多,不能少。 

8. 封装

 在TS中,也可以使用private对属性进行封装,然后搭配上"noEmitOnError": "true"。这样就可以保证在由错误时不去编译。

        ① TS中对setter&getter的优化替代:get、set

我们可以直接写:

get name(){
  return this._name
}

来获取被封装的隐式_name属性,获取这个属性也十分的简单,可以直接写:

console.log(per.name);

就可以获取被get设置的属性。这个方法用起来和属性一样,十分的方便。(一定要注意,方法名和属性名是不可以相同的,我们可以给私有属性名前加一个“_”)

set也同理:

set name(name: string){
  this._name = name
}

         protected修饰的属性只能由当前类和其子类访问。这相当于是对private的一个优化,可以由其子类访问。实例想访问也必须要有写一个get方法才可以。

        创建class的快速方法,我们可以不在class中单独写属性,而是直接写在constructor中

class A{
  public name: string;
  private age: number;
  constructor(name: string, age: number){
    this.name = name;
    this.age = age;
  }
}

// 等效于
class A{
  constructor(public name: string, private age: number){}
}

你可能感兴趣的:(TypeScript,typescript)