一篇文章带你JS-->TS,拜托,超快超酷的好吗!
如何使用TS来输出你的HelloWorld呢?
这一步对于很多人来说是最简单的一步,也是最难的一步,说简单是因为这确确实实仅是入门的一步,就是一个环境配置,说难则是因为很多人无法跨出这一步,当你跨出这一步之后,你会发现后面的真的学得很快很快,现在,就让我们一起跨出这一步吧~
step1:安装TS
cnpm i -g typescript // 查看是否安装成功,能输出对应的版本号就对了 tsc -v
step2:编写TS代码
console.log('Hello TS');
有小伙伴就问,这也是TS的代码吗?这不是JS的代码吗?当然是,TypeScript是JavaScript的超集,因为它扩展了JavaScript,有JavaScript没有的东西,那么JS有的东西,它也是有的,你就放心使用吧~
step3:编译TS为JS
TS在浏览器中是无法运行的,它的出现只是为了弥补开发人员在编写JS代码的痛苦,就是无类型,这个在小型demo中是无法体现的,但一上升到大项目中,你会发现JS的any类型难以维护,这也是TS出现的原因,大家都知道Vue3是全部拿TS构建了,那么Vue3+TS+...就是现在开发的一套可能是主流技术栈了,这也更加坚定了我们学习TS的原因!
tsc .\01_Hello.ts // 编译刚写的TS文件
之后会生成同名的JS文件,好了,现在,你可以运行你的第一句TS代码吧~
项目中如何编译多个TS文件呢?
上述的编译方式只能编译一个文件,多个文件难道需要我们一步步执行命令?这对于几乎成为主流的TS来说怎么可能会有如此低效率的编译方式,现在,让我们学习一下多个文件的编译方式:
tsc --init // 首先你需要使用这个命令初始化一个tsconfig.json文件
之后我们就可以在这个文件里面控制对我们整个项目中多个文件的编译方式了,哈~再也不用每个文件都执行一次tsc+hello.ts了[doge]
下面我列举了一些比较常用的tsconfig.json的配置信息,大家可以根据自己需要进行配置,也可以查看官网文档,点进官方文档之前记得给本篇文章点个赞支持一下哦,别过去了就回不来了😚;
{ /* 配置文件,根据该配置进行编译 "include": []-->哪些配置文件需要被编译 '*'表示任意文件 '**'表示任意目录 */ "include": [ "*" , "src/*" ], // 不包含 "exclude": [ ], // "extends": "" --> 继承于某个配置文件 // "files": [?] //文件,不能是目录 "compilerOptions": { // 编译为ES的版本 "target": "ES3", // 指定要使用的模块化方案 "module": "es2015", // "lib": []-->指定项目中需要用到的库-->一般不需要设置 "outDir": "./dist", //编译后的存放目录 // 将代码合并到为一个文件-->如果和ing多个模块,module-->system|amd // "outFile": "./dist/app.js" // 是否对js文件进行编译 "allowJs": false, // 是否检查js代码符合规范 "checkJs": false, // 是否移除注释 "removeComments": false, // 不生成编译后的文件 "noEmit": false, // 当有错误的时候就不生成编译后的文件 "noEmitOnError": false, // 所有严格模式的总开关 "strict": false, // 严格模式-->性能更好 "alwaysStrict": true, // 不允许隐式的any类型 "noImplicitAny": true, // 不允许不明确类型的this "noImplicitThis": true, /* function fn(this: window){ alert(this) } */ // 严格地检查空值-->比如某些获取dom元素 "strictNullChecks": false } }
然后记得终端输入下方命令就可以对整个项目中的多个文件进行编译和监控了
tsc -w
到这里,你已经跨出了学习TS中最难的一步了,接下来面对你的将是康庄大道=======>success
当然还有其他方式来控制整个项目对TS的编译方式,比如在webpack中的配置可能是这样的(这里就不过多赘述了,大家初始学习的话可以跳过这方面的知识,明白前两种编译方式就可以应对接下来的知识,后续你在编写大型项目中根据自己的需求再在网上找对应的配置就可以,毕竟知识的海洋是无限的,有些东西需要深入了解,但有些东西又得不求甚解,很多时候做一样东西你不一定会其中的技术栈,然后你再学-->使用,技术是学不完的,按需学习感觉才是这个时代的主流,当然,程序员的核心素质除外,这个挖再深都对你好处很大):
// tsconfig.json中 { "compilerOptions": { "module": "ES2015", "target": "ES2015", "strict": true } }
// package.json中 { "devDependencies": { "ts-loader": "^9.2.6", "typescript": "^4.5.2", "webpack": "^5.65.0", "webpack-cli": "^4.9.1" }, "scripts": { "build": "webpack" } }
// webpack.config.js中 // 引入一个拼接路径的包 const path = require('path'); // webpack中所有的配置信息 module.exports = { // 入口文件 entry: "./src/index.ts", // 指定输出文件所在目录 output: { // 指定目录 path: path.resolve(__dirname, 'dist'), // 打包以后的文件名 filename: "bundle.js" }, // 指定webpack打包时要使用的模块 module:{ // 指定加载的规则 rules: [{ // test指定的是规则生效的文件 test: /.ts$/, // 要使用的loader use: 'ts-loader', exclude:/node-modules/ }] } }
好了,刚才突然就想感叹一下,接下来我们一起走这条康庄大道吧
认识类型
类型是typescript中最重要的概念,毕竟人家就叫typescript
TS中是如何声明一个类型的呢?记住下面的格式:
let a: number;
上面的代码又和我们平常书写的JS有什么不同的?
JS中,我们输入如下的代码,不会报错:
let a; a = 'this is string'; a = 1234; a = true;
但是在TS中,不同类型的值是不能相互赋值的,否则将会报错,这个报错也是我们选择TS的重要原因,JS是动态类型语言,很多时候你都不会发现你的类型搞错了,编译器也不会给你报错,当这个错误影响到项目运行的时候,你又找不到,昂~
在初入计算机领域时,我们最怕的就是编写的代码被编译器提示报错,但是现在,我们更怕的是生产环境中报错,编译器最好多报一点错,因为编译器的提示真的太好解决了,就像你的坐姿,在小时候写字的时候是最好纠正的,但现在,害>程序员的腰酸背痛!
下方的代码中,编译器会提示报错:
let a: number; // a = 'hello' --> 报错
let b: string; b = "hello"; // 这样才行
当然你也可以这样let a: any;
,然后你也可以像JS那样赋值,千万不要这么做,除非万不得已,否则就违背了TS的初衷,对了,如果声明对象不指定类型let a;
也会导致隐式的any
类型,永远记得不要这么做🈲
除此之外,你也可以给函数的形参和返回值做类型的声明:
function sum_ts(a:number, b:number):number { return a + b; }
回想一下,js中的函数是不考虑参数的类型以及个数的,它的个数只是表面的个数,就算你不声明形参个数,你仍然可以给JS的函数传任意的参数个数,毕竟JS函数可以使用**arguments
** 这个类数组对象进行访问,这就导致很多时候你想编写一个万用的函数,需要考虑到是否收集剩余参数,而其他开发者在使用这个函数的时候也不能很清楚的知道这个函数的输入输出,这就完全违背了封装的思想-->拿来就用!
所以,好好学一下TS吧~ ( ^_^ )
深入类型
基本类型介绍
就是JS中的八种类型,你还记得吗?
😀面试官: 说一说ES6中的八种数据类型?
❓我: JavaScript共有八种数据类型,分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。
😀面试官: 哪两种类型是新增的?说一说你对它们的了解?
❓我: 其中 Symbol 和 BigInt 是ES6 中新增的数据类型:
- Symbol 代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题,可以很好地隔离用户数据与程序状态。
- BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。
TS中的基础类型也是这八种,写成TS的格式的话就是如下代码:
let str: string = "justin3go"; let num: number = 20; let bool: boolean = false; let u: undefined = undefined; let n: null = null; let obj: object = {justin: 3}; let big: bigint = 100n; let sym: symbol = Symbol("justin3go");
TS难吗,不难,难的是JS!
当然还有一些需要注意的地方,下面将详细阐述:
- 使用字面量形式进行类型声明:
let a1: 10; a1 = 10; // a1 = 20 --> 报错
// 作用: let b1: "male" | "female"; // 这样b1就只能在这两个字符串中选了 // 记得:现在做的限制是对以后的回馈,后续写代码才会更加的方便。 //--------------------------分界线-------------------------------------- // 联合类型 let c1: boolean | string;
关于TS中的任意类型:
// any-->和js一样关闭类型检测 let d1: any; // 注:声明变量如果不指定类型,默认为隐式的any let e1; // 赋值 let s1: string; //any可以赋值给任意类型,同时导致s1也变为any类型了 s1 = d1;
// 但是unknown就不一样-->实际上就是一个类型安全的any,不能直接赋值给其他的变量 let g1: unknown; g1 = 10; g1 = true; g1 = "hello"; // s1 = g1 --> 此时g1赋值会报错,不能赋值 if (typeof g1 === "string") { s1 = g1; //这种就不会报错 } //--------------------------分界线---------------------------------- // 当你确认g1就是字符串,一定要赋值的话,可以这么做: // 类型断言-->我知道它就是字符串 s1 = g1 as string; s1 = <string>g1;
关于函数中的空值:
// 空值 function fn1(): void { // return 123 报错 return; // 对 } // 没有值-->表示永远不会返回结果,空也不返回(return;也不行) function fn2(): never { //作用:可以用来指定专门的函数报错 throw new Error("error"); }
对象类型介绍
定义对象的结构
刚才,我们一起学习了八种基本类型,其中有一种叫做let obj: object;
,我们可以对其这样赋值:
// let obj: object; obj = {}; obj = function () {};
所以一般也不会使用object,因为js中一切都是对象,相当于没有限制,一般这样使用,主要是声明里面的属性,之后使用结构就必须一摸一样:
let b2: { name: string }; // b2 = {} --> 这种就会报错:因为需要且仅需要一个name属性 // b2 = {name: "孙悟空", age: 18} -->error b2 = { name: "孙悟空" };
但是,偶尔我们也许需要部分结构是固定的,还有一些key值不确定,这时候,我们可以使用?
来实现这种表示:
// ?代表这个属性有和没有都是可以的 let c2: { name: string; age?: number };
如果需要需要name这一个属性,其他的属性无所谓,我们可以这么做:
let d2: { name: string; [propName: string]: any }; // 代表必须包含name属性就可以了 d2 = { name: "猪八戒", xixi: "xixi", haha: "haha" };
既然对象可以提前定义其结构,那么理所当然函数也是可以提前定义其结构以及返回值的
限制函数结构的语法:
let e2: (a: number, b: number) => number;
数组
- 我们可以使用
string[]
来表示字符串数组;
let l2: string[]; l2 = ["a", "b", "c"];
还可以使用Array<string>
来表示
let i2: Array<string>;
元组
元组:相当于固定长度的数组-->效率比较高
let h: [string, string]; h = ["haha", "xixi"];
枚举
enum Gender { Male, Feamle, } let j2: { name: string; gender: Gender }; j2 = { name: "悟空", gender: Gender.Feamle, }
学一样东西必不可少的就是提问,新技术的出现必定是为了解决某一类问题,带有一定目的的,所以这种类型有什么好处吗?
其实上面的例子已经解释得非常清楚了,就是让某些标识符有语义。怎么理解这句话呢,比如如果没有该类型,我们表示男女一般会使用01来表示,那到底0是男还是1是男呢,当然01都可能是男/(ㄒoㄒ)/~~
开个玩笑,所以枚举就是为了解决这类问题的,它让我们在编程的时候更加方便地知道性别这个类包含哪种类型,简单来说是下面几点:
- 实际上这个值在转的时候就会转为0,1
- 与直接0,1比较更容易辨识
- 与直接字符串比较更节约存储
然后,再补充一些小知识:
// & 表示且 let g: { name: string } & { age: number }; // g = {name: "悟空"} --> 报错
// 类型的别名 type myType = 1 | 2 | 3 | 4 | 5; let k2: myType; let h2: myType;
到这里,你几乎已经完成TS学习的一半了,是不是非常简单,接下来的内容如何小伙伴接触过java、C++这种面向对象的语言,可能理解起来非常简单,接下来的知识就是TS中另外一个非常重要的概念类
类
当然,如果你熟悉JS中类的语法,对于接下来的理解也会非常简单
简介
还是和之前hello world
一样,我们先来手写定义一个类,然后实例化这个类,认识一下它,一回生,二回熟嘛,知识也是这样的:
// 使用class定义 class Person { // 实例属性 name: string = "孙悟空"; // 只读属性 readonly gender:boolean = true; // 在属性前使用static关键字可以定义类属性(静态属性) static age: number = 18; sayHello(params:string):void { console.log('Hello!',params) } }
const per = new Person(); console.log(per.name); //通过实例访问(duxier) console.log(Person.age); //通过对象名访问
构造函数
这部分也可以说和JS中ES6语法的构造函数一模一样:
class Dog { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } bark() { alert("汪汪汪!"); } }
继承
也是差不多的:
class animal { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } sayHello() { console.log("动物在叫~"); } } //共有的代码写在父类之中 class Dog extends animal { run(){ console.log("旺仔再跑"); } } class Cat extends animal {}
后继承都来了,super
关键字自然不会缺席
class Animal { name: string; age: number; constructor(name: string, age: number) { this.age = age; this.name = name; } sayHello() { console.log("动物在叫~"); } } class Dog extends Animal { sayHello(): void { //调用父类的方法 super.sayHello(); } } // 用在添加属性后构造函数中 class Cat extends Animal { color:string; constructor(name:string, age:number, color:string){ super(name, age) this.color = color } }
当然,super再在JS中是有一些限制的,这里默认是认为你们已经熟悉了JS了,还没熟悉的赶快去熟悉,来学什么TS呢?[推荐阅读红宝书的p260]
抽象类
再回想一下,JS中有抽象类吗?没有。能实现抽象类吗?可以:通过new.target
的判断来判断是非常容易实现的:
javascript
class animal { constructor(){ console.log(new.target); // 表示new关键字后面跟着的类型 if(new.target === animal){ throw new Error('xx不能被实例化') } } }
此时,你就不能直接实例化这个类了,只能通过继承,然后实例化其子类是没有问题的。
到这里,你可能会说别人其他语言都有abstract
关键字使用,为什么我JS还要自己手动判断呢?似乎一点也不优雅!
于是TS中引入了abstract
关键字,可以非常方便地帮助我们定义抽象基类:
除此之外,在JS中定义抽象方法也需要在构造器中多增加一个判断--this下是否有某方法,没有就抛出异常,而这里TS的abstract
关键字也可以使用在抽象方法上,非常方便
// 不能用来创建对象,这个类就是用来被别人继承的 // 抽象类中可以去添加抽象方法 abstract class animal{ // 1 name: string; constructor(name:string){ this.name = name } abstract sayHello():void; // 2 }
接口
type myType = { name: string; age: number; }; //接口就是用来定义一个类结构的 // 可以当作类型声明去使用 // 区别是重新再写一个myInterface不会报错,结果是合并 interface myInterface { name: string; age: number; } // 接口可以在定义类的时候限制结构 // 接口中所有的属性都不能有实际的值 // 接口只定义对象的结构而不考虑实际值 // 所有的方法都是抽象方法 // 去实现一个接口 class Mycalss implements myInterface { name: string; age: number; constructor(name: string, age: number) { this.age = age; this.name = name; } }
属性的封装
再想一下,我们实现一个私有变量有多麻烦,用闭包实现仅在内部作用域能访问该变量,即私有化,然后再使用weakmap解决闭包导致的垃圾回收的问题,当然ES6中的#也可以非常方便的添加私有变量,而这里TS中的private
似乎更符合我们在其他语言中见识的一致。
//属性可以任意修改将会导致对象中的数据变得非常不安全 class Person { // pubulic默认值,可以在任何地方修改 // private:只能在内部修改,当前类,子类也不行 // protected: 只能在当前类和当前类的子类中使用 public _name: string; private _age: number; constructor(name:string, age:number){ this._name = name; this._age = age; } // getName(){ // return this.name // } // setName(value:string){ // this.name = value // } //ts中设置getter方法的方式 get name(){ return this._name } } // 其他地方就可以这样调用name属性 const per= new Person("1",1) console.log(per.name);
最后,在补充一个泛型的概念,这个就和C++中的泛型几乎一致,就是写函数时可以不定参数的类型,使用函数时在确定参数的类型:
C++中是这样的vector<int> test
:向量中存储的类型是int,vector<string> test
:向量中存储的类型是string
TS中是这样使用的:
// 在定义函数或者是类时,如果遇到类型不明确就可以使用泛型 function fn<T>(a: T): T{ return a; } //这样可以避免使用any,同时保证我的参数以及返回值的类型时相同的 //使用 //可以直接使用具有泛型的函数 fn(10); //指定泛型 fn<string>('hello')
最后最后,写作不易,支持一下哦~
\