一门基于 JS,但又优于 JS 的语言。我们可以把它理解为是灌输了面向对象思想的JS。
首先,JS的类型并不针对变量,而是针对数据,而在我们拿变量去对数据进行计算时,更重要的是变量的类型。其次,因为 JS 首屈一指的地位,我们并不可能直接将 JS 替换掉,更好的解决方案就是对 JS 进行优化。
TS的出现主要就是为了解决 JS 由于变量没有类型,函数参数也没有类型,而产生的许多问题。例如在计算时,可能会意外地将数值类型的变量 和 字符串类型的变量进行混合计算,从而产生预料之外的结果。但是这种情况下产生了预料之外的结果,又不会报错,就会给后续的计算带来极其严重的问题。
学习 TypeScript 有一个极其需要注意的点,就是“ : ”和“ = ”的区分。很好区分,“:”基本上都用在类型的限制上,例如 a: number、function fun(a: number, b: number): number{return a;}。在其他位置很少会使用“:”。
TS支持所有JS的类型,同时又对类型进行了扩充,如:void、any(任意类型,当我们在TS中不指明数据类型,那么该变量就是隐式any类型)、never、tuple(元组)、emurme(枚举)、interface(接口)、抽象类等高级类型
通过类型的实际结构确定两个类型是否相等或兼容。如 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 有相同的结构,在这里,二者有相同的函数,那么就认为两种类型是可以兼容的。因此赋值操作可以生效而不报错。
通过类型的名称确定两个类型是否相等。如 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;
}
在使用名义类型系统的语言中,两个类型只要名字不用,就是不兼容、不相等的。
可以看到,enum、class、namespace 会同时为类型空间引入类型,为值空间引入值。
在实际中,可以用来帮助我们解决一些开发中遇到的一些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中的声明方式: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以外类型的值,那么将会报错。
但是现在一个松散状态,针对TS中不严格的错误,依旧可以编译成功。后面我们会一起学习,如何在这种出现错误的情况下,不再编译产生相应的JS文件。
let sex : "male" | "female";
sex = "male";
sex = "female";
// sex = "nan"; // false
做变量时,对数据类型的限制也可以使用符号:" | "
let doubleType : number | string;
doubleType = 3;
doubleType = "string";
// doubleType = false; // false
例如在一个函数中,我们不知道传递的参数是什么类型,或者是什么类型都有可能,就可以将数据类型限制为any或unknown,但是我们要尽可能避免使用any,因为any将不再进行类型检查。
any与unknown的区别:
可以看到:我们将一个any类型的变量赋给一个已知类型的变量,不会有任何错误提示,这就表示,TS对数据类型做的限制将不再有任何意义,又回到了JS的时代。但是如果是unknown就不同,它会有错误提示,哪怕unknown最后一次赋值两个数据的类型相同,依旧会存在问题。
那么unknown是如何使用的呢?很简单,加一个if判断即可:
这个时候你可能会有一个想法,我如果不判断是否是string类型,而是判断是否是number类型,会报错吗?
显然,当然是会的。
还有一种简单的解决方法:类型断言。
用来告诉浏览器变量是什么类型。像下面给p的类型指定为 unknown,后面赋值后,浏览器依旧无法判断出真正的类型是什么,这个时候,就可以由我们告诉浏览器,这里就是xxx类型(两种写法):
当然也不允许随意做断言:
这个返回值类型很奇特,体现在什么地方呢?它不允许有任何的返回值,但是我们什么都不写,却又会报错。一般都用它来抛出错误。
let person : {name: string, age?: number, [propsNames: string]: unknown};
person = {name: "光太郎", age: 18};
person = {name: "东光太郎", gender: "男"};
name:必须存在;
age:对age加了“ ?”,就表示这是一个可选参数,想要就要,不想要就不要;
propsNames:我们在这里加了一个数组的符号“[ ]”,就表示除了对前两个属性的限制外,可以向里面添加任意数量的属性,属性名必须为字符串类型,属性值为unknow任意类型,当然最好还是为属性值限定出具体的类型。
// 先对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;
}
一定要注意,箭头函数的声明方式,是不允许增添参数的,也不允许修改参数的数据类型。
let arr1: string[]; // 数组所有的元素都只能数string类型
let arr2: Array; // 同上
arr1 = ["a", "b", "c"];
如果数组内的元素不是字符串,就会直接报错。
元组:固定长度的数组。
其在定义上和数组十分相似,但是数据类型的设定并不在外部,而是在内部。
// 数组
let arr: string[];
// 元组
let arr: [string, string, number];
arr = ["string", "string", 123];
枚举主要应用于对简单非语义数据的替换。
简单地说,就是我们想用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而已。
个人很喜欢这种用法,觉得还是挺有用的。
let person : {name: string} & {age: number};
person = {
name: "光太郎", // 报错
}
person = {
name: "光太郎", // 正常
age: 18
}
虽然不知道这么设定有什么意义,不过灵活性确实增加了很多,例如一个人可能有职员的属性、家人的属性、个人的基本属性,那么就可以分开写三个对象,然后进行拼接。
当一个类型被频繁使用,我们就可以将其提取出来,来可以提高复用性。
例如:(注意哈,这里面的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 十分相似,例如:仅存在于块级作用域中,不能被重复定义。
在定义函数、接口(专门用来约束对象的属性有哪些,以及属性值的类型是什么)、类时,不去指定具体的类型,而是在使用时,再去指定类型的一种特性。
简单总结:泛型定义在函数名、类名、接口名后面(类似于形参);用在具体需要被该类型限制的地方;传递于函数、类实例化的时候。
// :定义泛型、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)
泛型约束概念:通过配置,限制泛型中被传入的类型
// 定义泛型约束
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');
前面我们已经说了,用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存在时才执行后面的内容。
Record的内部定义,接收两个泛型参数;Record后面的泛型就是对象键和值的类型
作用 :义一个对象的 key 和 value 类型
Record
Record // 空对象
Record // 任意对象
{} // 任何不为空的对象
作用:生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为可选项
interface Foo {
name: string
age: number
}
type Bar = Partial
// 相当于
type Bar = {
name?: string
age?: string
}
作用:生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为必选项
interface Foo {
name: string
age?: number
}
type Bar = Required
// 相当于
type Bar = {
name: string
age: string
}
作用:生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为必选 + 只读
readonly:只读,赋给对象属性,那么该属性就不可以被重新赋值。函数形参也是同理。
interface Foo {
name: string
age: number
}
type Bar = Required
// 相当于
type Bar = {
readonly name: string
readonly age: string
}
作用:生成一个新类型,继承 Foo 中 age、gender 属性
interface Foo {
name: string
age?: number
gender: string
}
type Bar = Pick
// 相当于
type Bar = {
age?: string
gender: string
}
作用:继承A在B中不具有的类型
type A = number | string | boolean
type B = number | boolean
type Foo = Exclude
// 相当于
type Foo = string
作用: 和 Exclude 相反
type A = number | string | boolean
type B = number | boolean
type Foo = Exclude
// 相当于
type Foo = number | boolean
作用:生成一个新类型,该类型拥有 Foo 中除了 age 属性以外的所有属性
type Foo = {
name: string
age: number
}
type Bar = Omit
// 相当于
type Bar = {
name: string
}
作用:从泛型 T 中排除掉 null 和 undefined
type t = NonNullable<'name' | undefined | null>;
// 得到的结果为
type t = 'name'
作用:获得构造函数返回值的类型
type h = InstanceType {name: string, age: number}>
// 得到的结果
type h = {
name: string;
age: number;
}
在实际的开发中,一般不直接使用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
其中:
然后添加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']
}
}
实例属性由对象访问,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 );
class Person{
tellTheTrueth(){
console.log( "小哥哥你真帅" )
};
static tellTheRealTrueth(){
console.log( "小哥哥你帅爆了" )
}
}
const Lisa = new Person;
Lisa.tellTheTrueth();
Person.tellTheRealTrueth();
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中的构造函数(构造器)。我们就可以在这个构造函数中做传参的操作。
关键字: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();
在子类中,该关键字可以指向父类:
(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(),然后将父类构造函数的参数传进去即可。注意,我们同时还要在子类的构造函数中加上父类的参数。
抽象类:
一般应用于父类,我们不想让父类被实例化,就可以直接在父类class前加上一个关键字:abstract
//抽象类
// 父类 此时父类就无法被实例化
abstract class EachBeauty{...}
// 子类
class BeautyfromKorea extends EachBeauty{...}
抽象方法:
抽象方法定义在父类中,没有函数体,用于让子类继承
//抽象类
// 父类 此时父类就无法被实例化
abstract class EachBeauty{
...
abstract laugh():void;
}
// 子类
class BeautyfromKorea extends EachBeauty{
...
laugh(){
console.log( "Korea女孩笑得很开心" )
}
}
我们需要注意,抽象方法不同于普通的方法,我们在写抽象方法时,必须指明返回值类型。
接口概念:对 “对象” 进行一个描述。
// 定义一个接口规范
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 为数字,对象只能包含这两个属性,不能多,不能少。
在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){}
}