文章目录
一,TypeScript简介
- 以JavaScript为基础构建的语言
- 可以在任何支持JavaScript的平台中执行
- 一个JavaScript的超集
- TypeScript扩展了JavaScript,并添加了类型
- TS不能被JS解析器直接执行
TypeScript增加了什么?
- 类型
- 添加ES不具有的新特性
- 支持ES的新特性
- 丰富的配置选项
- 强大的开发工具
编译/webstorm,vscode
console.log('hello ts');
二,TS的类型说明
//声明一个变量a,同时指定它的类型为number
let a: number;
//a 的类型设置为了number,在之后的使用过程中a的值只能是数字
a = 10;
a = 33;
//a = 'hello'; //此行代码会报错,因为变量a的类型是number,不能赋值字符串
//声明变量直接进行赋值
let c: boolean = true;
//如果变量的声明和赋值是同时进行的,TS可以自动对变量进行类型检测
let c = false;
c = true;
fuction sum(a: number, b: number): number{
return a + b;
}
let result = sum(123, 456);
三,字面量
//直接使用字面两进行类型声明
//赋值后不能修改
let a: 10;
//可以使用 | 来连接多个类型(联合类型)
let b: "male" | "female";
b = "male";
b = "female";
let c: boolean | string;
c = true;
c = 'hello'
//any 表示的是任意类型,一个变量类型为any后相当于对该变量关闭了 TS 的类型检测
//使用TS时,不建议使用any类型
//let d: any;
//声明变量如果不知道类型,则TS解析器会自动判断变量的类型为any (隐式的any)
let d;
d = 10;
d = 'hello';
d = true;
//unknow 表示位置类型的值
let e: unknown;
e = 10;
e = "hello";
e = true;
let s:string;
//d的类型是any,它可以赋值给任意变量
s = d;
e = 'hello';
//报错
//s = e;
// unknown 实际上就是一个类型安全的any
// unknown 类型的变量,不要直接赋值给其他变量
if(typeof e === "string"){
s = e;
}
//类型断言:可以用来告诉解析器变量的实际类型
/*
语法:
变量 as 类型
<类型>变量
*/
s = e as string;
s = <string>e;
//void 用来表示空,以函数为例,则表示没有返回值的函数
function fn(): void{
}
//never 表示永远不会返回结果
function fn2(): never{
throw new Error('报错了!');
}
//object 表示js对象
let a :object;
a = {};
a = function (){
};
// {} 用来指定对象中可以包含哪些属性
// 语法:{属性名:属性值,属性名:属性值}
// 在属性名后加上?,表示属性是可选的
let b: {name: string, age?:number};
b = {name: '孙悟空'};
//[propName: string]: any 表示任意类型的属性
let c:{name: string, [propName: string]: any};
c = {name:'猪八戒', age: 18, gender: '男'};
/*
设置函数结果的类型声明:
语法:(形参:类型,形参:类型...)=>返回值
*/
let d: (a:number ,b:number)=>number;
//报错
/*
d = function (n1:string, n2:string): number{
return 10;
}
*/
/*
数组的类型声明:
类型[]
Array<类型>
*/
//string[] 表示字符串数组
let e: string[];
e = ['a','b','c'];
//number[] 表示数值数组
let f: number[];
let g: Array<number>;
g = [1,2,3];
/*
元祖:元祖就是固定长度的数组
语法:[类型,类型,类型]
*/
let h: [string, number];
h = ['hello',123];
/*
枚举
*/
enum Gender{
Male,
Female
}
let i: {name: string, gender: Gender};
i = {
name: '孙悟空',
gender: Gender.Male //'male'
}
console.log(i.gender === Gender.Male)
//&表示同时
let j: { name: string } & {age:number};
j = { name: '孙悟空', age:18};
//类型的别名
type myType = 1 | 2 | 3 | 4 | 5;
let k: myType;
let l: myType;
let m: myType;
k = 2;
四,TS编译选项
-
tsc app.ts -w
监视文件变化,一旦变化,重新编译 -
tsc
重新编译目录下所有tsc文件(前提必须有配置文件)
新建 tsconfig.json File
内容:
{
}
同时 tsc app.ts -w
也可以直接监视所有文件的改变
- tsconfig.json文件介绍:
{
/*
tsconfig.json是ts编译器的配置文件,ts编译器可以根据它的信息来对代码进行编译
"include" 用来指定哪些ts文件需要被编译
路径:** 表示任意目录
* 表示任意文件
"exclude": 不需要被编译的文件目录
默认值:["node_modules", "bower_components", "jspm_packages"]
*/
"include": [
"./src/**/*"
],
"exclude": {
"./src/hello/**/*"
}
/*
compilerOptions 编译器的选项
*/
"compilerOptions": {
// target 用来指定ts被编译为的ES的版本
// es3,es5,es6,es2015,es2016,es2017,es2018,es2019,es2020,esnext
"target": "es2015"
// module 指定要是有的模块化的规范
// 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020','esnext'
"module": "es2015"
//lib 用来指定项目中要使用的库,默认就是浏览器的运行环境
// "lib": ["dom"]
//outDir 用来指定编译后文件所在的目录
"outDir": "./dist",
//将代码合并为一个文件
//设置outFile后,所有的全局作用域中的代码会合并到同一个文件中,如果要合并模块,模块化规范应该改成 amd 和 system
"outFile": "./dist/app.js"
//是否对js文件进行编译,默认是false
"allowJs": true,
//是否检查js代码是否符号语法规范,默认值是false
“checkJs": true,
//是否移除注释
"removeComments": true,
//不生成编译后的文件
//"noEmit": true,
//当有错误时不生成编译后的文件
"noEmitOnError": true,
//所有严格检查的总开关,设为true,以下全部打开,下面不用写了,建议true
"strict": true,
//用来设置编译后的文件是否使用严格模式,默认false
//js文件中有import,export默认进入严格模式
"alwaysStrict": true,
//不允许隐式的any类型
"noImplicitAny": true,
//不允许不明确类型的this
"noImplicitThis": true,
//严格的检查控制
"strictNullChecks": true,
}
}
五,webpack
npm init -y //生成package.json,管理项目
cnpm i -D webpack webpack-cli typescript ts-loader
- 新建 webpack.config.js
- 新建 tsconfig.json
- 编辑 package.json
"build": "webpack"
npm run build
cnpm i -D html-webpack-plugin //自动生成html文件,改webpack.config.js
cnpm i -D webpack-dev-server //内置服务器,自动刷新,改packge.json --- "start": "webpack serve --open"
cnpm i -D clean-webpack-plugin //清除dist目录,改webpack.config.js
版本兼容
cnpm i -D @babel/core @babel/preset-env babel-loader core-js
//修改 webpack.config.js
use: [
//配置babel
{
//指定加载器
loader: "babel-loader",
//配置bebel
options: {
//设置预定义的环境
presets: [
[
//指定环境的插件
"@babel/preset-env",
//配置信息
{
//要兼容的目标浏览器
targets: {
"chrome": "88",
"ie":"11"
},
//指定corejs的版本
"corejs": "3",
//使用corejs的方式 “usage” 表示按需加载
"useBuiltIns":"usage"
}
]
]
}
},
'ts-loader' //本来配置号的
],
//告诉webpack不使用箭头
environment: {
arrowFunction: false
}
六,面向对象
- 操作浏览器要使用window对象
- 操作网页要使用document对象
- 操作控制台要使用console对象
程序之中所有的操作都需要通过对象来操作。
1.类(class)
//使用class关键字来定义一个类
/*
对象中主要包含了两个部分:
属性
方法
*/
class Person{
/**
* 直接定义的属性是实例属性,需要通过对象的实例去访问
* const per = new Person();
* per.name
*
* 使用static开头的属性是静态属性(类属性),可以直接通过类名.访问
* Person.age
*
* readonly开头的属性表示一个只读的属性无法修改
*/
// 定义实例属性
// readonly name: string = '孙悟空';
// 在属性前使用static关键字可以定义类属性(静态属性)
// static readonly age: number = 18;
name = '孙悟空';
static age = 18;
//定义方法 如果方法以static开头则方法就是类方法,可以直接通过类去调用
static sayHello() {
console.log('Hello 大家好!');
}
}
const per = new Person();
console.log(per);
console.log(Person.age);
// Person.age = 33;
// per.sayHello();
Person.sayHello();
2. 构造函数
class Dog{
name : string;
age : number;
// constructor 被称为构造函数
// 构造函数会在在对象创建时调用
constructor(name: string, age: number) {
//在实例方法中,this就表示当前的实例
//在构造函数中当前对象就是当前新建的那个对象
//可以通过this向新建的对象中添加属性
// console.log(this)
this.name = name;
this.age = age;
}
bark() {
// alert('hihihi!');
// 在方法中可以通过this来表示当前调用方法的对象
console.log(this.name);
}
}
const dog = new Dog('lucky',4);
// console.log(dog)
dog.bark();
3. 继承
(function () {
//定义一个Animal类
class Animal{
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age
}
sayHello() {
console.log('动物在叫');
}
}
/**
* Dog extends Animal
* - 此时,Animal1被称为父类,Dog被称为子类
* - 使用继承后,子类将会拥有父类所有的方法和属性
* - 通过继承可以将多个类中共有的代码写在一个父类中
* 这样只需要写一次即可让所有的子类都同时拥有父类的属性和方法
* 如果希望在子类中添加一些父类中没有的属性或方法直接添加即可
* - 如果在子类中添加了和父类相同的方法,则子类方法会覆盖掉父类的方法
* 这种子类覆盖掉父类方法的形式,我们称为方法重写
*/
//定义一个表示狗的类
//使Dog类继承Animal类
class Dog extends Animal{
run() {
console.log(`${this.name}在跑~~`);
}
sayHello(): void {
console.log('汪汪汪');
}
}
//定义一个表示猫的类
//使Cat类继承Animal类
class Cat extends Animal{
}
const dog = new Dog('旺财', 5);
console.log(dog)
dog.sayHello();
dog.run();
})();
4. super关键字
(function () {
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHello() {
console.log('动物在叫~~~')
}
}
class Dog extends Animal{
age: number;
constructor(name:string, age: number) {
//如果在子类中写了构造函数,在子类构造函数中必须对父类的构造函数进行调用
super(name);//調用父类的构造函数
this.age = age;
}
sayHello(): void {
//在类的方法中 super就表示当前类的父类
super.sayHello();
}
}
const dog = new Dog("lucky",3);
dog.sayHello();
})();
5. 抽象类
(function () {
/**
* 以abstract开头的类是抽象类
* 抽象类和其他类区别不大,只是不能用来创建对象
* 抽象类就是专门用来被继承的类
*
* 抽象类中可以添加抽象方法
*/
abstract class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
//定义一个抽象方法
//抽象方法使用abstract开头,没有方法体
//抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
abstract sayHello():void;
}
class Dog extends Animal{
sayHello(): void {
//在类的方法中 super就表示当前类的父类
// super.sayHello();
console.log('汪汪汪')
}
}
const dog = new Dog("lucky");
dog.sayHello();
// const an = new Animal();
})();
6. 接口
(function () {
//描述一个对象的类型
type myType = {
name: string,
age: number
}
/**
* 接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法
* 同时接口也可以当成类型声明去使用
*/
interface myInterface{
name: string;
age: number;
}
interface myInterface{
gender: string;
}
// const obj: myInterface = {
// name: 'sss',
// age: 111,
// gender: '男'
// }
/**
* 接口可以在定义类的时候去现在类的结构,
* 接口中的所有的属性都不能有实际的值
* 接口只定义对象的结构,而不考虑实际值
* 在接口中所有的方法都是抽象方法
*/
interface myInter{
name: string;
sayHello(): void;
}
/**
* 定义类是,可以使类去实现一个接口
* 实现接口就是使类满足接口的要求
*/
class MyClass implements myInter{
name: string;
constructor(name: string) {
this.name = name;
}
sayHello(): void {
console.log('大家好')
}
}
})();
7. 属性的封装
(function () {
//定义一个表示人的类
/**
* public 修饰的属性可以在任意位置访问(修改)默认值
* 包括子类
* private 私有属性,私有属性只能在类内部进行访问(修改)
* 通过在类中添加方法使得私有属性可以被外部访问
* protect 受保护的属性,只能在当前类和当前类的子类中访问(修改)
*/
class Person{
private _name: string;
private _age: number;
constructor(name: string, age: number) {
this._name = name;
this._age = age;
}
/**
*
* getter方法用来读取属性
* setter方法用来设置属性
* - 他们被称为属性的存取器
*/
//定义方法,用来获取name属性
getName() {
return this._name;
}
//定义方法,用来设置name属性
setName(value: string) {
this._name = value;
}
setAge(value: number) {
//判断年龄是否合法
if (value >= 0) {
this._age = value
}
}
//TS设置getter方法的方式
get name() {
return this._name;
}
set name(value: string) {
this._name = value
}
}
const per = new Person('孙悟空', 18);
/**
* 现在属性是在对象中设置的,属性可以任意的被修改
* 属性可以任意被修改将会导致对象中的数据变得非常不安全
*/
// per._name = '猪八戒';
// per._age = 38;
per.setName('猪八戒');
console.log(per.getName());
per.name = '123';
console.log(per.name);
class C{
//可以直接将属性定义在构造函数中
constructor(public name:string,public age: number){}
}
const c = new C('xxx', 111);
console.log(c)
})();
8. 泛型
// function fn(a: any): any{
// return a;
// }
/**
* 在定义函数或者类时,如果遇到类型不明确就可以属于泛型
*
*/
function fn<T>(a: T): T{
return a;
}
//可以直接调用具有泛型的函数
let result = fn(10);//不知道泛型,TS可以自动对类型进行推断
let result2 = fn<string>('hello');//指定泛型
//泛型可以同时指定多个
function fn2<T, K>(a: T, b: K): T{
console.log(b);
return a;
}
fn2<number, string>(123, 'hello');
interface Inter{
length: number;
}
//表示泛型T必须是Inter实现类(子类)
function fn3<T extends Inter>(a: T): number{
return a.length;
}
fn3('123')
fn3({length:3})
class MyClass<T>{
name: T;
constructor(name: T) {
this.name = name;
}
}
const mc = new MyClass<string>('孙悟空');
七,贪吃蛇项目
npm i -D less less-loader css-loader style-loader
npm i -D postcss postcss-loader postcss-preset-env
index.ts
//引入样式
import './style/index.less';
// import Food from './moduls/Food';
// const food = new Food();
// console.log(food.X, food.Y);
// food.change();
// console.log(food.X, food.Y);
import GameControl from './moduls/GameControl';
const gameControl = new GameControl();
// setInterval(() => {
// console.log(gameControl.direction);
// }, 1000);
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>贪吃蛇</title>
</head>
<body>
<!-- 创建游戏的主容器 -->
<div id="main">
<!-- 设置游戏的舞台 -->
<div id="stage">
<!-- 设置蛇 -->
<div id="snake">
<!-- snake内部div 表示蛇的各部分 -->
<div></div>
</div>
<!-- 设置食物 -->
<div id="food">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<!-- 设置有效的积分牌 -->
<div id="score-panel">
<div>SCORE:<span id="score">0</span></div>
<div>level:<span id="level">1</span></div>
</div>
</div>
</body>
</html>
index.less
//设置变量
@bg-color: #b7d4a8;
//清除默认样式
*{
margin: 0;
padding: 0;
//改变盒子模型的计算方式
box-sizing: border-box;
}
body{
font:bold 20px "Courier";
}
//设置主窗口的样式
#main{
width: 360px;
height: 420px;
background-color: @bg-color;
margin: 100px auto;
border: 10px solid black;
border-radius: 40px;
display: flex;
flex-flow: column;
//侧轴
align-items: center;
//主轴
justify-content: space-around;
//游戏舞台
#stage{
width: 304px;
height: 304px;
border: 2px solid black;
position: relative;
//设置蛇的样式
#snake{
&>div{
width: 10px;
height: 10px;
background-color: #000;
border: 1px solid @bg-color;
position: absolute;
}
}
#food{
width: 10px;
height: 10px;
position: absolute;
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-content: space-between;
left: 40px;
top: 100px;
&>div{
width: 4px;
height: 4px;
background-color: black;
transform: rotate(45deg);
}
}
}
//记分牌
#score-panel{
width: 300px;
display: flex;
justify-content: space-between;
}
}
Snake.ts
class Snake{
head: HTMLElement;
bodies: HTMLCollection;
element: HTMLElement;
constructor() {
this.element = document.getElementById('snake')!;
this.head = document.querySelector('#snake > div') as HTMLElement;//断言有!的功能
this.bodies = this.element.getElementsByTagName('div');
}
get X() {
return this.head.offsetLeft;
}
get Y() {
return this.head.offsetTop;
}
set X(value) {
//如果新值和旧值相同,则直接返回不再修改
if (this.X === value) {
return;
}
if (value < 0 || value > 290) {
throw new Error('蛇撞墙了');
}
//修改x时,是在修改水平坐标,蛇在左右移动,蛇在向左移动时,不能向右掉头,反之亦然
if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
if (value > this.X) {
value = this.X - 10;
} else {
value = this.X + 10;
}
}
//移动身体
this.moveBody();
this.head.style.left = value + 'px';
this.checkHeadBody();
}
set Y(value) {
//如果新值和旧值相同,则直接返回不再修改
if (this.Y === value) {
return;
}
if (value < 0 || value > 290) {
throw new Error('蛇撞墙了');
}
//修改x时,是在修改垂直坐标,蛇在上下移动,蛇在向上移动时,不能向下掉头,反之亦然
if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
if (value > this.Y) {
value = this.Y - 10;
} else {
value = this.Y + 10;
}
}
//移动身体
this.moveBody();
this.head.style.top = value + 'px';
this.checkHeadBody();
}
addBody() {
this.element.insertAdjacentHTML("beforeend", "<div></div>");
}
//添加一个蛇身体移动的方法
moveBody() {
/**
* 将后边的身体设置为前边身体的位置
* 举例:
* 第3节 = 第2节的位置
* 第2节 = 蛇头的位置
*/
for (let i = this.bodies.length - 1; i > 0; i--){
let X = (this.bodies[i - 1] as HTMLElement).offsetLeft;
let Y = (this.bodies[i - 1] as HTMLElement).offsetTop;
(this.bodies[i] as HTMLElement).style.left = X + 'px';
(this.bodies[i] as HTMLElement).style.top = Y + 'px';
}
}
//检查是否撞到身体
checkHeadBody() {
//获取所有的身体,检测其是否和蛇头的坐标发生重叠
for (let i = 1; i < this.bodies.length; i++){
let bd = this.bodies[i] as HTMLElement;
if (this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
throw new Error('撞到自己了~~~');
}
}
}
}
export default Snake;
ScorePanel.ts
//定义表示记分牌的类
class ScorePanel{
score = 0;
level = 1;
scoreEle: HTMLElement;
levelEle: HTMLElement;
maxLevel: number;
upScore: number;
constructor(maxLevel: number = 10,upScore: number = 10) {
this.scoreEle = document.getElementById('score')!;
this.levelEle = document.getElementById('level')!;
this.maxLevel = maxLevel;
this.upScore = upScore;
}
addScore() {
this.scoreEle.innerHTML = ++this.score + '';
if (this.score % this.upScore === 0) {
this.levelUp();
}
}
levelUp() {
if (this.level < this.maxLevel) {
this.levelEle.innerHTML = ++this.level + '';
}
}
}
export default ScorePanel;
// const scorePanel = new ScorePanel(100,2);
// for (let i = 0; i < 10; i++){
// scorePanel.addScore();
// }
GameControl.ts
import Snake from "./Snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel";
class GameControl{
snake: Snake;
food: Food;
scorePanel: ScorePanel;
//创建一个属性来存储蛇的一定方向(也就是按键的方向)
direction: string = '';
//创建一个属性用来记录游戏是否结束
isLive = true;
constructor() {
this.snake = new Snake();
this.food = new Food();
this.scorePanel = new ScorePanel();
this.init();
}
init() {
document.addEventListener('keydown', this.keydownHandler.bind(this));
this.run();
}
/**
*
* ArrowUp ArrowDown ArrowRight ArrowLeft
*/
keydownHandler(event: KeyboardEvent) {
//修改direction属性
this.direction = event.key;
}
run() {
let X = this.snake.X;
let Y = this.snake.Y;
//根据按键方向来修改X值和Y值
switch (this.direction) {
case "ArrowUp":
case "Up":
Y -= 10;
break;
case "ArrowDown":
case "Down":
Y += 10;
break;
case "ArrowLeft":
case "Left":
X -= 10;
break;
case "ArrowRight":
case "Right":
X += 10;
break;
}
//检测蛇是否吃到了食物
this.checkEat(X, Y);
//修改蛇的X和Y值
try {
this.snake.X = X;
this.snake.Y = Y;
} catch (e) {
alert((e as Error).message + ' GAME OVER!');
this.isLive = false;
}
this.isLive && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1)*30);
}
//定义一个方法,用来检测蛇是否吃到食物
checkEat(X: number, Y: number) {
if (X === this.food.X && Y === this.food.Y) {
//食物的位置要进行重置
this.food.change();
//分数增加
this.scorePanel.addScore();
//蛇要增加一节
this.snake.addBody();
}
}
}
export default GameControl;
Food.ts
class Food{
element: HTMLElement;
constructor() {
//获取页面中的food元素并将其赋值给element
this.element = document.getElementById('food')!;//!不可能为空
}
get X() {
return this.element.offsetLeft;
}
get Y() {
return this.element.offsetTop;
}
change() {
let top = Math.round(Math.random() * 29) * 10;
let left = Math.round(Math.random() * 29) * 10;
this.element.style.left = left + 'px';
this.element.style.top = top + 'px';
}
}
export default Food;
// const food = new Food();
// console.log(food.X, food.Y);
// food.change();
// console.log(food.X, food.Y);