TypeScript 学习笔记(二)
朋友们,本期内容是关于 TypeScript 的学习,如果您学习过 javaScript ,或 java 等后端语言,我认为您学习的过程将会非常轻松!本期内容包括
webpack
和面向对象
,后续会继续更新!本期参考TypeScript 中文手册
和李立超老师的讲解视频和学习笔记
,特别鸣谢❤️!
目录
2. 使用 webpack 打包 typeScript 文件
具体四个步骤:
- npm init -y 表示对项目进行初始化
- 安装 webpack 所使用的项目依赖
- 完成对 webpack 配置信息
- 配置 tsconfig.json 文件
- 修改 package.json 添加 build 命令
2.1 初始化项目
- 使用 webpack 要使用 npm 包管理器
- 进入项目根目录,执行命令
npm init -y
,创建 package.json 来管理项目,该 json 文件可以清楚知道项目有哪些依赖
- 安装 webpack 所使用的依赖 cnpm 是国内的镜像,更快一些
- cnpm:阿里巴巴为了众多开发者的便捷推出了淘宝镜像(即 cnpm),它把 npm 官方的“包”全部搬到国内,供广大开发者使用。
- 下载构建工具
-
npm i -D webpack webpack-cli typescript ts-loader
- i 表示 install,-D <完整为 (
--save-dev
) > 其意义表示配置开发环境依赖,将模块安装到项目目录下,并在 package文件的 devDependencies 节点写入依赖。 - webpack 是打包代码时依赖的核心内容,
- webpack-cli 是一个用来在命令行中运行 webpack 的工具。
- typescript 编译器
- ts-loader 是用于 webpack 的 TypeScript 加载器,将 TypeScript 编译成 JavaScript。
-
- 安装完成,模块依赖的模块列表如下:
{
//...
"devDependencies": {
"ts-loader": "^9.4.1",
"typescript": "^4.8.4",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
}
//...
}
2.2 配置 webpack.config.js
- 根目录下创建 webpack 的配置文件 webpack.config.js
- 引入相关包
- path 是一个和路径有关的模块,用于处理文件路径和目录路径
- 指定入口文件 entry
- 指定打包文件 output 所在目录,文件名,环境…
const path = require('path');
- webpack 中所有的配置选项都应该写入 module.exports 中,声明这个模块对外暴露具体内容
// webpack 中所有的配置选项都应该写入 module.exports 中
const path = require('path');
module.exports = {
//指定入口文件
entry: "./src/index.ts",
//打包文件 所在目录
output: {
//其路径
path: path.resolve(__dirname, 'dist'),
//打包后文件名
filename: 'bundle.js',
},
// 指定要加载的规则
// mode 指定构建模式 development 开发模式 production 生产模式
mode: 'development',
rules: [
{
//test 指定规则生效的文件
test: /\.ts$/,
//要使用的 loader 表示使用 ts-loader 去处理 ts 文件
use: 'ts-loader',
//要排除指定的文件
//node-modules 不需要编译
exclude: /node-modules/,
}
]
}
2.3 为 typescript 指定编译规范
- 根目录下创建 tsconfig.json,配置可以根据自己需要
- vscode 快捷生成 tsconfig.json 方式 ( tsc -init )
{
"compilerOptions": {
"target": "ES2015",
"module": "ES2015",
"strict": true
}
}
- 打包在 package.json 文件中在“script”中加入 "build"命令
- “build”: “webpack”,可通过 build 命令直接执行 webpack, 使用命令
npm run build
- webpack 编译成功
模块
问题情境:新增的 .ts 文件为 m1.ts,在 index.ts 引入(import)会发生报错。原因是 m1.ts 是引入模块,但 webpack 是“不知道” ts 可以作为模块,“不知道”哪些文件可以引入。
-
webpack 中使用 resolve 字段来配置模块的相关解析策略。本质上是通过对 resolve 库的使用来解析引入模块路径,帮助 webpack 找到 bundle.js 中以 require/import 引入的模块代码。
-
配置 webpack.config.js 来设置哪些文件可以作为模块使用引入
-
resolve 用于设置引入模块,其属性 extensions 使用
extensions: 扩展名
的语法格式,.ts 和 .js 作为扩展名都可以使用。
//...
//用来设置引用模块
resolve: {
extensions: ['.ts', '.js']
}
//...
babel
-
babel是一个 js 编译器,用来转换最新的 js 语法,比如把 ES6, ES7 等语法转化成 ES5 语法,从而能够在大部分浏览器中运行(处理兼容性问题),像箭头函数,也可以做转换。
-
需要安装 babel-core,babel-loader,babel-preset-env
当我们在 webpack 中使用 babel 的时候,首先要安装 babel-core,这是 babel 编译库的核心包。
然后 webpack 中对 js 文件,要进行编译就需要继续配置,在webpack 中,需要用到 babel-loader 帮你来使用 babel。
babel-preset-env 意思为 babel 预先设置环境,编译规则。
IE 浏览器无法兼容 es6 语法,我们可以使用 core-js 进行兼容性处理,从而使 IE 浏览器也能够正常的解析 es6 语法
这个年头应该没人使用 IE 吧 😂
-
安装依赖包:
npm i -D @babel/core @babel/preset-env babel-loader core-js
-
修改 webpack.config.js 配置文件
//...
//webpack 打包时使用的模块
module: {
//指定要加载的规则
rules: [
{
//test 指定规则生效的文件
test: /\.ts$/,
//要使用的loader 表示使用 ts-loader去处理 ts 文件
use: [
//配置 babel
{
//指定加载器
loader: "babel-loader",
options:{
//设置预定义环境
presets: [
[
"@babel/preset-env",
//配置信息
{
"targets":{
"chrome": "58",
"ie": "11"
},
//指定 corejs 的版本
//使用 corejs 的方式
// "usage" 按需加载
"corejs":"3",
"useBuiltIns": "usage"
}
]
]
}
},
{
loader: "ts-loader",
}
],
exclude: /node_modules/
}
]
}
//...
2.4 配置 Webpack 插件(注册)
问题情境:如果有个 html 文件 需要引入 index.js 文件,常规做法就在 html 文件中添加 script 标签并外部引入 js 文件,但是如果有多个 js 文件呢?手动引入不太现实,有点麻烦。
- 使用插件,使用命令
npm i -D html-webpack-plugin
- 该插件能生成一个 HTML5 文件, 在 body 中使用 script 标签引入你所有 webpack 生成的 bundle.js 文件,只需添加该插件到 webpack 配置中
- 回到 webpack.config.js,引入 html 插件
const HTMLWebpackPlugin = require('html-webpack-plugin');
- 生效 html 插件
plugins: [
new HTMLWebpackPlugin(),
]
- 如何自定义? 比如 titile 不想要默认的 Webpack app
- 传入参数 options
//配置 Webpack 插件(注册)
plugins: [
new HTMLWebpackPlugin({
title: '自定义title'
}),
],
问题情境:网页结构复杂,自定义较为麻烦,希望网页有个结构,指定一个模板 src/index.html
//配置 Webpack 插件(注册)
plugins: [
new HTMLWebpackPlugin({
template: './src/index.html'
}),
],
- webpack 开发服务器
- 使用命令
npm i -D webpack-dev-server
安装 webpack-dev-server - 相当于在项目里安装内置服务器,让我们的项目直接在这个服务器运,插件跟 webpack 关联,会根据项目改变自动刷新
- 打包在 package.json 文件中在“script”中加入 "start"命令
- “start”: “webpack serve --open”,可通过
npm start
命令开启服务器。
问题情境:编译不会删除原来的文件,只是替换相关文件,替换的问题:编译后的文件还在 dist 文件夹里,并没有一同删除。
现需求:编译前先将 dist 目录下文件全部清空,然后再生成新的文件,避免出现旧文件情况
- 使用插件 clean-webpack-plugin
- 使用命令安装
npm i -D clean-webpack-plugin
作用:更新代码时若文件不再被需要了,把其文件删除,清空就可以保证删除这些冗余文件。
- 回到 webpack.config.js,引入 clean-webpack-plugin 插件并注册
//引入clean插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
//...
plugins: [
//...
new CleanWebpackPlugin(),
],
//...
3. 面向对象
3.1 概述
-
面向对象是程序中一个非常重要的思想,简而言之就是程序之中所有的操作都需要通过对象来完成。
-
程序之中所有的操作都需要通过对象来完成。举例:
- 操作浏览器要使用 window 对象
- 操作网页要使用 document 对象
- 操作控制台要使用 console 对象
-
也就是所谓的面向对象,那么对象到底是什么呢?这就要先说到程序是什么,计算机程序的本质就是对现实事物的抽象,抽象的反义词是具体,比如:照片是对一个具体的人的抽象,汽车模型是对具体汽车的抽象等等。程序也是对事物的抽象,在程序中我们可以表示一个人、一条狗、一把枪、一颗子弹等等所有的事物。一个事物到了程序中就变成了一个对象。
-
-
在程序中所有的对象都被分成了两个部分数据和功能,以人为例,人的姓名、性别、年龄、身高、体重等属于数据,人可以说话、走路、吃饭、睡觉这些属于人的功能。数据在对象中被成为属性,而功能就被称为方法。所以简而言之,在程序中一切皆是对象。
3.2 类
-
要想面向对象,操作对象,首先便要拥有对象,那么下一个问题就是如何创建对象。要创建对象,必须要先定义类,所谓的类可以理解为对象的模型,程序中可以根据类创建指定类型的对象,举例来说:可以通过Person 类来创建人的对象,通过 Dog 类创建狗的对象,通过 Car 类来创建汽车的对象,不同的类可以用来创建不同的对象。
-
定义类
-
class 类名 { 属性名: 类型; //构造函数 constructor(参数: 类型){ this.属性名 = 参数; } 方法名(){ .... } }
-
class Person { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } sayHello() { console.log(`大家好,我是${this.name}`); } }
-
使用类 (创建类的实例)
-
const p = new Person('孙悟空', 18); p.sayHello();
3.3 面向对象特点
3.3.1 封装
-
对象实质上就是属性和方法的容器,它的主要作用就是存储属性和方法,这就是所谓的封装
-
默认情况下,对象的属性是可以任意的修改的,为了确保数据的安全性,在 TS 中可以对属性的权限进行设置
-
只读属性(readonly):
- 如果在声明属性时添加一个 readonly,则属性便成了只读属性,是无法修改
-
静态属性
-
静态属性(方法),也称为类属性。使用静态属性无需创建实例,通过类即可直接使用
-
静态属性(方法)使用 static 开头
-
-
//使用 class 关键字 定义一个类 class Person { /* 对象中主要包含两个部分 属性 方法 */ //实例属性 name: string = 'zhangsan'; //name = "zhangsan" 类型自动判断 age: number = 20; //在属性前使用 static 关键字 可以定义类属性 (静态属性) //不需要创建对象就可以使用 static gender: string = 'male'; readonly address: string = 'xxx';//只读实例属性 static readonly phone: string = 'xxx';//只读类属性 //定义实例方法 sayHi() { console.log('hi'); } //类方法 直接通过类调用 static sayHello() { console.log('hello'); } } const person = new Person(); console.log(person.age);//实例属性 可读可写 console.log(Person.gender); //console.log(Person.gender); //类属性 person.sayHi(); Person.sayHello();
-
构造函数
- 构造函数会在对象创建时调用
- this
- 在类中,使用 this 表示当前对象
-
class Dog { // constructor 被称为构造函数 // 构造函数会在对象创建时调用 name: String; age: number; constructor(name: String, age: number) { // 在实例方法中 this 表示当前实例 // 在构造函数中当前对象就是当前新建的那个对象 // 可以通过 this 向新建的对象添加属性 this.name = name;; this.age = age; } bark() { //alert('汪汪汪'); //在方法中可以通过 this 来表示当前调用方法的对象 console.log(this); } } const dog = new Dog("小黑", 4);//会调用constructor() 无参 const dog1 = new Dog("小白", 2); dog.bark(); dog1.bark();
3.3.2 继承
-
继承是面向对象中的一个特性
-
通过继承可以将其他类中的属性和方法引入到当前类中
-
重写
- 发生继承时,如果子类中的方法会替换掉父类中的同名方法,这就称为方法的重写
-
示例
-
(function () { class Animal { name: String; age: number; constructor(name: String, age: number) { this.name = name; this.age = age; } sayHello() { console.log('do Something'); } } //定义一个表示狗的类 // extends 继承 扩展 // Animal 父类 // Dog Cat 子类 // 继承后 子类会拥有父类所有的方法和属性(不严谨) public // 通过继承可以将多个类共有的代码写在父类中 // 如果希望在子类添加一些父类中没有的方法 或 属性 直接写入子类 // 如果在子类中添加了跟父类一样的方法,则子类方法会覆盖掉父类的方法 // 子类覆盖掉父类的方法 --- 方法重写 class Dog extends Animal { sayHello() { console.log(`${this.name}汪汪汪`); } run() { console.log(`${this.name}在跑`); } } class Cat extends Animal { sayHello() { console.log(`${this.name}喵喵喵`); } } const dog = new Dog("旺财", 3); console.log(dog); dog.sayHello(); dog.run(); const cat = new Cat("咪咪", 2); console.log(cat); cat.sayHello(); //发现 Cat Dog 类的 属性 构造函数 一样 唯一不同只是方法内部 //重复代码提取出来 //OCP 不能去修改 可以去扩展 })();
-
super
- 在子类中可以使用 super 来完成对父类的引用
-
示例:
-
(function () { class Animal { name: string; constructor(name: string) { this.name = name; } sayHello() { console.log('动物在叫'); } } class Dog extends Animal { age: number; //属性“age”没有初始化表达式,且未在构造函数中明确赋值。注意声明属性一定要初始化! //派生类的构造函数必须包含 "super" 调用。 constructor(name: string, age: number) { //如果在子类写了构造函数 在子类的构造函数进行对父类构造函数的调用 super(name);//调用父类的构造函数 this.age = age; } sayHello() { // super 表示当前类的父类 super.sayHello(); } } const dog = new Dog("旺财", 3); console.log(dog); dog.sayHello(); })();
3.3.3 多态
- 父类定义一个方法不去实现,让继承他的子类去实现,每一个子类有不同的表现
- 多态属于继承
-
抽象类(abstract class)
- 抽象类是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例
- 使用 abstract 开头的方法叫做抽象方法,抽象方法没有方法体只能定义在抽象类中,继承抽象类时抽象方法必须要实现
-
(function () { // 以 abstract --> 抽象类 // 抽象类 --> 用于被继承的类 // 抽象类可以添加抽象方法 // 抽象方法只能定义在抽象类,必须由子类去实现 abstract class Animal { name: string; constructor(name: string) { this.name = name; } abstract sayHello(): void; } class Dog extends Animal { sayHello() { console.log(`${this.name}汪汪`); } } const dog = new Dog("旺财"); console.log(dog); dog.sayHello(); //不希望 通过Animal类创建对象 //const animal = new Animal();//无法创建抽象类的实例 })();
-
接口(Interface)
-
接口的作用类似于抽象类,不同点在于接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法。
-
接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性。
-
示例:
-
(function () { //描述一个对象的类型 type myType = { name: string, age: number }; const obj: myType = { name: 'zhangsan', age: 20 }; //类型声明不可以重复 //接口 用于定义一个类的结构 interface myInterface { name: string, age: number } interface myInterface { gender: string; } // 接口用来定义一个类的结构,用来定义一个类应该包含哪些属性和方法 // 同时接口也可以当成类型声明来使用 // 接口是可以重复声明的 const o: myInterface = { name: 'zhangsan', age: 20, gender: 'male' } // 接口可以在定义类的时候去限制类的结构 // 接口中所有的属性都不能由实际的值 // 接口只定义对象的结构 而不考虑实际值 // 在接口中所有的方法都是抽象方法 interface myInter { name: string, sayHello(): void; } //定义类时 可以使类去实现一个接口 class MyClass implements myInter { name: string; constructor(name: string) { this.name = name; } sayHello() { } } })();
-
TS 中属性具有三种修饰符:
- public(默认值),可以在类、子类和对象中修改
- protected ,可以在类、子类中修改
- private ,可以在类中修改
-
属性存取器
-
对于一些不希望被任意修改的属性,可以将其设置为 private
-
直接将其设置为 private 将导致无法再通过对象修改其中的属性
-
我们可以在类中定义一组读取、设置属性的方法,这种对属性读取或设置的属性被称为属性的存取器
-
设置属性的方法叫做 setter 方法,读取属性的方法叫做 getter 方法
-
-
示例:
-
(function () { class Person { // TS 可以在属性面前添加属性的修饰符 /* public 修饰的属性可以在任意位置访问|修改 (默认值) private 私有属性 -- 私有属性只能在类内部访问 通过在类中添加方法使得私有属性可以在外部访问(暴露) protected 受包含的属性 只能在当前类和当前类的子类中访问 不能通过实例去访问 */ private name: string; private age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } //getter 获取name属性 getName() { return this.name; } //setter setAge(value: number) { if (value >= 0) { this.age = value; } } // TS 中 设置 setter 方法的方式 get _name() { return this.name; } set _name(value: string) { this.name = value; } } class A { protected num: number; constructor(num: number) { this.num = num; } } class B extends A { test() { console.log(this.num); } } class C { //相当于语法糖 //可以直接将属性定义在构造函数中 constructor(public name: string, public age: number) { } } // 属性在对象中设置 属性是可以任意被修改的 // 将对象中的数据变的非常不安全 const per = new Person('zhangsan', 18); console.log(per); //per.name = 'lisi';//属性“name”为私有属性,只能在类“Person”中访问 //per.age = 20; console.log(per.getName()); per.setAge(20); console.log(per); console.log(per._name); per._name = 'wangwu'; console.log(per); const b = new B(123); //b.num = 33;//属性“num”受保护,只能在类“A”及其子类中访问。 const c = new C('xxx', 20); })();
-
泛型(Generic)
-
定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时泛型便能够发挥作用。
-
举个例子:
-
function test(arg: any): any{ return arg; }
-
上例中,test 函数有一个参数类型不确定,但是能确定的时其返回值的类型和参数的类型是相同的。
-
由于类型不确定所以参数和返回值均使用了any,但是很明显这样做是不合适的,首先使用 any 会关闭 TS 的类型检查,其次这样设置也不能体现出参数和返回值是相同的类型。
-
使用泛型:
-
function test<T>(arg: T): T{ return arg; }
-
这里的
<T>
就是泛型,T 是我们给这个类型起的名字(不一定非叫 T,可以是 K,V …),设置泛型后即可在函数中使用 T 来表示该类型。所以泛型其实很好理解,就表示某个类型。 -
使用方式
-
方式一(直接使用)
-
test(10)
-
使用时可以直接传递参数使用,类型会由 TS 自动推断出来,但有时编译器无法自动推断时还需要使用下面的方式
-
方式二(指定类型):
-
test<number>(10)
-
可以在函数后手动指定泛型
-
-
可以同时指定多个泛型,泛型间使用逗号隔开:
-
function test<T, K>(a: T, b: K): K{ return b; } test<number, string>(10, "hello");
-
使用泛型时,完全可以将泛型当成是一个普通的类去使用
-
类中同样可以使用泛型:
-
class MyClass<T>{ prop: T; constructor(prop: T){ this.prop = prop; } }
-
可以对泛型的范围进行约束:
-
interface MyInter{ length: number; } function test<T extends MyInter>(arg: T): number{ return arg.length; }
-
使用 T extends MyInter 表示泛型 T 必须是 MyInter 的子类,不一定非要使用接口类和抽象类同样适用。
-
具体示例:
-
// function fn(a: number): number { // return a; // } //类型不明确可以使用泛型 //在定义函数或类时,如果遇到类型不明确可以使用泛型 // K T 表示任意类型 // T 只有在函数执行的时候才可以确定 function fn<T>(a: T): T { return a; } //可以直接调用具有泛型的函数 let result = fn(10);//function fn<10>(a: 10): 10 let res = fn<string>('hello');//function fn<string>(a: string): string // 不指定泛型 TS 可以自动对类型进行推断 function fn2<T, K>(a: T, b: K) { console.log(b); return a; } fn2(123, 'hello'); fn2<number, string>(123, 'hello'); interface Inter { length: number; } //T是泛型必须实现Inter接口 //T extends Inter 表示 泛型 T 必须使用 Inter 实现类(子类) function fn3<T extends Inter>(a: T): number { return a.length; } fn3('123'); fn3({ length: 10 }); //fn3({ name: 10 });//报错 //类型“{ name: number; }”的参数不能赋给类型“Inter”的参数。 //对象文字可以只指定已知属性,并且“name”不在类型“Inter”中。 class MyClass<T>{ name: T; constructor(name: T) { this.name = name; } } const mc = new MyClass<string>('xxx');
- 下期预告:使用 typescript 编写游戏<贪吃蛇>
配置项目文件是挺麻烦的,也致使 “Webpack 配置工程师” 的出现🤣,但不可否认的是,Webpack 是现较为主流的前端构建工具,所以想要深造前端的同学一定好好静下心来,好好学习💪。
第二期 TypeScript 学习内容就这么多啦,如果内容不错的话,望您能关注🤞点赞👍收藏❤️一键三连!