【typescript】写给JS老鸟的TS速成教程
写给JS老鸟的TS速成教程
搭建基础开发环境
要准备的环境
node.js 14.14以上 ,vs code最新,vs code TS开发插件
开始开发
方式一:TS原生编译开发
补充知识:i是install的简写,-g是global的简写,除此外还有-D = --save-dev 、-S= --save,现在新版npm cli好像会默认执行—save
npm i -g typescript && tsc -init
vs code创建 文件夹/index.ts,
手动编译 tsc+文件名,改一次编译一次
自动编译 Ctrl+shift+B —> 监视模式,文件变动时自动编译 或 如下:
① tsc 文件名 -w,缺点是只能监视一个文件,除非开多个命令行。
② 创建tsconfig.json,只写一对{},使其符合json规范,然后直接执行 tsc,即可监视所有ts文件的改动。
运行结果:创建index.html, script.import 编译好的index.js,用浏览器打开index.html
开箱即用见 附件【TS_ori】
编译控制
tsconfig.json是ts编译器的配置文件,可以对编译进行自定义
不知道可选值可以先给一个错误的值,编译器会列出所有可选的正确配置值
tsconfig.json可以写注释,因为其是ts编译器的配置文件。
{
// 初步
"include":[ //指定哪些目录的ts文件需要被编译,包含目录
"./src/**/*" //.当前目录 src文件夹 **任意目录 *任意文件
],
"exclude":[],//指定哪些ts文件不被编译,排除目录,有默认值,比如node_modules
"extends":"./config/base",//继承某个配置文件,配置复用
"files":[],//指定ts文件编译,包含文件,与include类似
//进阶
"compilerOptions":{
"target":"ES6", //指定ts编译成何种js版本,即目标代码的版本
//ES2015(ES6),ES2016...ESNext(最新版ES)
"module":"ES6",//指定模块化解决方案
"lib":["dom"],//用来指定项目中使用到的库,一般不写此属性(有默认值),除非代码在nodejs中运行,缺少某些库,如dom。dom库为document
"outDir":"./dist",//指定编译后文件所在目录。dist即distribution,发行版
"outFile":"./dis/app.js",//将代码合并为一个文件,若需要把各模块合并为一个文件,只能支持amd and system模块方案
"allowJs":false, //是否对js进行编译,默认否 - false //合并文件一般不用outFile,而是结合打包工具来实现
"checkJs":false, //是否检查js代码是否符合语法规范
"removeCommentss":true,//是否移除注释
"noEmit":false,//执行编译但是不生成编译后的文件,场景是只用到ts编码和类型检查, 不须编译
"noEmitOnError":false,//发送错误时不生成编后的译文件,默认为false,为的是让js程序员缓慢过渡到ts,建议改为true
"strict":false,//以下三个检查的总开关,建议开发时设为true
"alwaysStrict":false,//指定编译后的js文件是否使用严格模式。严格模式:语法严格,在browser性能好一些
"noImplicitAny":false,//Implicit隐式的,开启隐式any的检查,不允许使用
"noImplicitThis":false,//不允许不明确类型的this
"strictNullChecks":false,//严格检查空值,若一个变量可能为null,则报错,除非先进行非空判断或box?.addEventListener()
}
}
方式二:自动化开发
补充知识:webpack-cli是通过命令行来使用webpack、ts-loader是webpack加载器,是ts和webpack的桥梁
补充知识:webpack这东西就是会沿着你给定的一个入口文件去遍历所有关联的文件,然后按照一定规则重新整理、压缩成新的一批文件,我们的文件格式是无穷无尽的,webpack不可能认识和处理全部格式,所以我们通过加入各种loader,称加载器,来帮助webpack认识它们。
创建空文件夹,执行npm init -y(完成项目初始化)
npm i -D webpack webpack-cli typescript ts-loader
撰写webpack配置。要注意的是,webpack打包必须配合tsconfig.json使用,也就是你的ts代码处要有这个tsconfig.json文件,因为ts程序的具体编译的工作还是由ts本身提供,而ts编译本身要用到tsconfig.json,现在看来,ts-loader真的仅仅是个桥梁。
在package.json中加入打包命令build:webpack,执行npm run build。
撰写配置
使用chrome来查看ts程序的运行结果
补充知识:webpack可以支持自动生成html文件并引入打包好的资源,以演示代码效果,这比自己手动写个html方便多了,生成这个html文件有两种方式,配置式,如传个title参数,其他由webpack自己决定,或者自己拟定一个html模板交给插件。
cnpm i -D html-webpack-plugin
自动化构建--所写即所见
cnpm i -D webpack-dev-server ,package.json 加入 "start":"webpack serve --open" ,npm run start
清楚旧的打包文件
打包默认模式是用新文件覆盖旧文件,可能存在残留问题,解决方法:clean-webpack-plugin
指定可引入的文件
指定哪些文件可以被其他文件作为模块来引入,这里的引入是代码文件之间的引入,这样我们就可以愉快地使用ESM(ES Module)了
解决目标代码的兼容性问题
补充知识:前端常见兼容性问题有两种,一种是浏览器内核类,一种是规范版本类。前者主要表现为在Chrome能运行的代码,在Firefox却出现问题,在iPhone默认浏览器能运行的代码在华为默认浏览器却有问题;后者主要表现为ES6的代码在浏览器中报错,因为ES6对比ES5变化是较大的,现在ES规范每年一个版本,浏览器跟进也比以前快了,这个问题正变得越来越不是问题。
TS的tsc仅仅能够实现把ts源码编译成不同ES规范版本的js代码而已。
babel/core是babel的核心库、present-env是预置环境,预置不同浏览器环境,帮助代码兼容不同浏览器,babel-loader是结合webpack和babel的桥梁、core-js(Modular standard library )可以使老版本的浏览器使用到新版本的js的一些技术,如promises等,由于这个库比较庞大,内含很多小库,且是模块化的,我们应按需使用,按需使用我们直接通过webpack来实现
webpack rules的use执行顺序是从下往上执行,我们先用ts-loader把ts转换为js,然后用babel-loader把新版本的js转换为老版本/兼容性高的js
- cnpm i -D @babel/core @babel/preset-env babel-loader core-js
关于兼容性打包后仍报错
补充知识:设置了targets.ie==11后,打包的代码拿到ie11运行依然报错,原因在于webpack打包后的代码用了一个箭头函数实现的自执行函数包裹,作用是创造一个代码作用域,防止全局变量污染等,它实际是webpack自动生成的,与babel无关,babel只能源文件内的箭头函数起作用,实际上,这可能是webpack故意为之,其本身就是不想兼容某些低版本浏览器,解决方法,out加上environment:{arrowFunction:false},取消箭头函数。
TS语言
报错信息,assign:赋值、指派、指定,resolve:解析、决定、解决
默认ts代码有错误,仍然可以编译生成js,在tsconfig中可更改
ts可编译成任意版本js
若赋初值,ts会根据值类型推算变量类型,这会使变量声明加上类型变得多此一举,没错,实际上,类型检测更多是用给函数传参(形参)和返回值的。
类型
补充知识:可用字面量代替类型名,如10,以后只能赋值10,有点常量的意思。除此之外,还有联合类型、任意类型等
talk is cheap,show me the code
export const zex: number = 1;
{
let a: number = 10;
let b: number = 20;
console.log(a, b)
//计算变量类型
let c = true;
//计算函数返回值类型
function sum(a: number, b: number): string {
return a + b + "";
}
let result = sum(a, b);
}
const zex:number=1;
{
// 字面量赋值
let a:10;
let a1:number;
let b:10=10;
a=10;
a1=10;
// 用 或符号 构成 联合类型
let c:"male"|"female";
c="male";
c="female";
let d:string|number;
d=10;
d="hello";
// 任意类型 - 关闭类型检测
let e:any;
e=10;
e="female";
e=true;
// 隐式any
let f;
f=10;
f="female";
f=true;
// 赋值
// 以下不报错,这导致a的类型检测失效
a1=e;
// unknown同any差不多,但是可解决以上问题,是一个类型安全的any
let g:unknown;
// a=g;报错,应改为
if(typeof g == "number"){
a1=g;
}
//断言:判断的语言,根据实际情况,把某个变量人为(自己)地断定为某种类型,跳过编译
a1 = g as number;
a1 = <number>g;
//函数的返回值
//返回值为number|string型
function sum(a:number,b:number){
if(a>b){
return a+b;
}else{
return a+b+"";
}
}
function sum2(a:number,b:number):number|string{
if(a>b){
return a+b;
}else{
return a+b+"";
}
}
// 空返回
function sum3(a:number,b:number):void{
return;
}
function sum4(a:number,b:number):void{}
function sum5(a:number,b:number):void{
return undefined;
}
// never:永远不会返回结果
// 没有返回值也是一种返回值,而never是空空
// 在程序报错时,代码立即停止执行,程序结束,函数结束,所以永远不会有值返回,事情不会发生
function err():never{
throw new Error("err");
}
}
{
// object其实是无用的,因为ts一且皆对象,并没有起到类型限制的作用
let a:object;
a={};
a=function(){};
//以下有效
let b:{name:string};
b={name:"John"};
// b={name:"John",age:12}结构不一致报错
// ?-可选属性
let c:{name:string,age?:number};
c={name:"John",age:12};
c={name:"John"}
// 任意属性:自由添加属性,新属性未知
// 新属性名为字符串,属性值为任意类型,propName命名随意
let d:{name:string,[propName:string]:any}
d={name:"John",str:123}
let d1:{name:string,[propName:string|number]:any}
d1={name:"John",str:123}
d1={name:"John",123:123}
// 限制函数,单Function无意义
let e:(a:number,b:number)=>number
// 数组
let f:string[];
f=["John"]
let f1:Array<number>;
f1=[123];
// 元组:固定长度的数组
let g:[String,String];
// 必须符合给定,不多不少
g=["Hello","Hello"];
//考虑数据存储与表示分离,数据库存储应简短、非字符串,此时Object并不满足要求
// 枚举,默认从0开始
enum Gender{
male=0,
female=1
};
let h:{name:string,gender:Gender};
h={name:"Jhon",gender:Gender.female};
console.log(h.gender,h.gender==Gender.female)
// & - 与,类型组合
let i:number&string //无意义
let i1:{name:string}&{age:number};
i1={name:"John",age:18};
// 类型的复用-别名
let j1:number;
let j2:1|2|3|4|5;
let j3:1|2|3|4|5|6;
type myType=number;
type myType2=1|2|3|4|5;
let k1:myType;
let k2:myType2;
let k3:myType2|6;
k3=3
k3=6
// k3=7 报错
}
// 类
class Person {
// 实例属性,通过实例访问
readonly name: string = "默认名字";
age: number = 18;
// 类属性,通过类访问
static avgAge: number = 18;
//只读属性
static readonly baseName: string = "张"
// 类方法
sayHello(name: string): string {
return "Hello";
}
static sayHi(name: string): string {
return "Hi";
}
};
const per = new Person();
Person.avgAge = 19;
per.age = 20;
// Person.name="三三"; // 报错
// Person.baseName="李"; // 报错
console.log(Person.avgAge, per.age);
// 构造器与this
class Dog {
name: string;
age: number;
constructor(name: string, age: number) {
// this表示当前实例
this.name = name;
this.age = age;
}
bark() {
// 当前调用方法的对象,如dog1.bark(),this为dog1
console.log(this, "旺旺旺");
}
}
const dog: Dog = new Dog("旺财", 3);
// 继承
{
class Animal {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
bark() {
}
}
class Dog extends Animal {
bark() {
console.log(this.name + this.age + "旺旺旺");
}
run() {
console.log("蹦蹦跳跳");
}
}
class Cat extends Animal {
sex: string;
constructor(name: string, age: number, sex: string) {
// 调用父类构造器
super(name, age);
this.sex = sex;
}
bark() {
// 引用父类的方法
super.bark();
}
}
const cat = new Cat("小喵", 3, "母");
//抽象类
//对于某些类,由于本身拿来实例化是不合适的,且我们也不希望被这样做
//因此我们就把他设为抽象类,只可以继承,不可以实例化
abstract class Food {
name: string;
color: string;
// 抽象类须有构造器
constructor(name: string, color: string) {
this.name = name;
this.color = color;
}
abstract eat(name: string): void;
abstract cook(name: string): number | string;
}
/**
* 在限制对象的类型上,以下两种方式功能一致
* 接口,定义了类的结构(属性、方法)
* 此接口非彼接口,它与Java的接口有点不同
*/
type myType = {
name: string,
age: number
};
interface myInterface {
name: string;
age: number;
}
// 接口可以重复定义,实际效果是同名接口的总和
interface myInterface {
sex: string;
}
const man: myInterface = {
name: "张三",
age: 18,
sex: "男"
}
// 限制类的结构
interface myInterface2 {
name: string;
sayHi(): void;
}
interface myInterface3 { }
class People implements myInterface2, myInterface3 {
name: string;
constructor(name: string) {
this.name = name;
}
sayHi(): void {
console.log(this.name);
}
}
/**
* 抽象类和接口
* 抽象类:
* 1、可以普通方法,也可以抽象方法
* 2、通过继承来使用,TS 类的设计为单继承
*
* 接口:
* 1、只有抽象方法
* 2、通过实现来使用,支持多继承
*/
}
// 属性的封装 - 保护类的属性
// 属性可被随意修改将导致对象中的数据变得不安全
// 通过类修饰符解决这个问题
// 默认为public,属性可被随意修改
{
class Person {
name: string;
public sex: string;
public age: number;
constructor(name: string, sex: string, age: number) {
this.name = name;
this.sex = sex;
this.age = age;
}
}
// 最强安全性
// private修饰的属性,只有两种修改方式:
// 1、构造器传入
// 2、调用实例方法修改,这种方法在java中被称为setter
// 此外,这种方式也给属性的访问带来麻烦,我们同样只能通过方法return该属性来访问,在java中称为getter
// 这种setter和getter模式在C#中被吸收为语法:
/**
* get{
return _name;
}
set{
_name = value;
}
*/
class Person2 {
private name: string;
private sex: string;
private age: number;
protected height: number;
constructor(name: string, sex: string, age: number, height: number) {
this.name = name;
this.sex = sex;
this.age = age;
this.height = height;
}
setName(name: string): void {
this.name = name;
}
getName(): string {
return this.name;
}
}
// 此外,private属性也不可以被子类访问,protected可以
class Person3 extends Person2 {
showName(): void {
// 无法访问name,可以访问height
// console.log(this.name);
console.log(this.height);
}
}
// 泛型
// 当一种类型是什么要在实际中才能确定是,我们使用泛型
// 泛型就是类型的抽象表示(代表)
// 当以下函数的参数与返回值类型一致,但是无法确定是什么具体类型时,用泛型
function fn(a: number): number {
return a;
}
// 能否使用any?不行,一是关掉了类型检查,这将埋下隐患,二是无法体现两者类型一致
function fn2<T>(a: T): T {
return a;
}
function fn3<T, K, I>(a: T, b: K, c: I): T {
return a;
}
function fn4<T, K>(a: T, b: K, c: number): number {
return c;
}
// 在调用含泛型函数时,一般会自动推定泛型的类型,我们也可手动指定
fn2(1);
fn4(1, "2", 3);
fn4<number, string>(1, "2", 3);
}
【附件】
https://github.com/heytheww/TSLenrning