TypeScript introduce
在我们使用JS的时候,有很多痛点,比如它是一个动态类型语言,没有变量类型的定义,没有严格的书写规范和IDE的错误提示。TS是在JS的基础上,可以理解为有类型的JS,这也是名字type script的由来。但是实际运行TS代码的时候,还是需要转化成为js运行。
TypeScript config.json detail
{ "include": ["./src/**/*"], //指定了文件的目录地址,ts会watch这个目录下的所有文件,并转化为js "compilerOptions": { //编译选项 "target": "ES6", //编译目标语言,可以转化为不同的版本,默认是ES3 "module": "ES6", "outDir": "./dist", // 编译转化成为JS后存放的目录 "strict": true, //是否开启严格模式 "allowJs": false, "checkJs": false, "removeComments": false, //编译的时候是否移除注释 "noEmit": false, "noEmitOnError": false, //存在错误的时候不进行编译 "alwaysStrict": true, //始终保持严格模式 "noImplicitAny": true, //禁止使用隐式的any类型 "noImplicitThis": true, //禁止使用隐式的this指针 "strictNullChecks": true, } }
我们需要使用tsc命令完成TS -》 JS语言的转化,但是这样每次都得手动执行编译,太麻烦了。所以出现了webpack,它能帮我们自动进行编译。并且检测到代码更新重新编译。
TypeScript compile via webpack
package.json file
{ "name": "ts-learn", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack", "start": "webpack-dev-server --open" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@babel/core": "^7.21.8", "@babel/preset-env": "^7.21.5", "@types/node": "^20.1.7", "babel-loader": "^9.1.2", "core-js": "^3.30.2", "css-loader": "^6.7.3", "html-webpack-plugin": "^5.5.1", "less": "^4.1.3", "less-loader": "^11.1.0", "postcss": "^8.4.23", "postcss-loader": "^7.3.0", "postcss-preset-env": "^8.3.2", "style-loader": "^3.3.2", "ts-loader": "^9.4.2", "typescript": "^5.0.4", "webpack": "^5.82.1", "webpack-clean-plugin": "^0.2.3", "webpack-cli": "^5.1.1", "webpack-dev-server": "^4.15.0" } }
const path = require('path'); //path是node js自带的path 包 const {nodeModules} = require("ts-loader/dist/constants"); const HTMLWebpackPlugin = require('html-webpack-plugin'); //可以自动将JS写入Html文件中运行在浏览器 const CleanWebPackPlugin= require('webpack-clean-plugin'); //可以在每次重新编译的时候清除上一次生成的文件 module.exports = { mode: "development", //运行模式,可以设置为development和production或者none entry: "./src/", //watch的文件的目录路径 output: { path: path.resolve(__dirname, 'dist'), //编译后文件的目录 filename: "bundle.js", //编译后文件的输出名称 }, module: { rules: [{ test: /\.ts$/, //选择文件的规则 use: [{ loader: "babel-loader", //babel可以将代码转化为不同的版本以支持不同的浏览器, options: { presets:[ [ "@babel/preset-env", //为了支持不同的浏览器进行的配置 { targets: { //浏览器类型 "chrome":"88", "ie": "11" }, "corejs":"3", //提供一种node js运行环境 "useBuiltIns": "usage", //按需build }, ] ] } },'ts-loader'], //加载器,在下面的加载器会被优先执行 exclude: /nodeModules/, //不编译的文件目录 }, { test: /\.less$/, //对于style的文件编译 use: [ "style-loader", //加载器 "css-loader", //加载器 { loader: "postcss-loader", //和babel同理,使得样式适应浏览器的 options: { plugins:[ [ "postcss-preset-env", { browsers:'last 2 versions' } ] ] } }, "less-loader" ] } ], }, plugins: [ new CleanWebPackPlugin(), new HTMLWebpackPlugin({ template: "./src/index.html" 编译后的js文件可以按照这个html模板生成html文件 }) ], resolve: { extensions: ['.ts', '.js'] } }
TypeScript class attribute and Encapsulation and Inheritance
TS具备其他面向对象语言的各种特性, 封装继承
interface animal_interface{ _name : string _age : number } class animal implements animal_interface{ _name : string _age : number constructor(name: string, age : number) { this._name = name this._age = age } getName(){ return this._name } setName(name: string){ this._name = name } getAge(){ return this._age } setAge(age: number){ this._age = age } Animal_Sounds(bark: string){ console.log(bark) } } class Dog extends animal{ run: string constructor(name : string, age: number, run : string) { super(name, age); this.run = run } } let dog = new Dog('小黄', 3 , "he is running") console.log(dog.getAge()); dog.Animal_Sounds('汪汪汪汪!') console.log(dog.getName());
TypeScript abstract class
抽象类就是只有方法不能实例,定义的时候需要增加abstract关键字。
abstract class cat{ face : string mouth : string beard : number constructor(face: string, mouth : string, beard: number) { this.face = face this.mouth = mouth this.beard = beard } cat_food(food_kinds : string){ if (food_kinds === 'aikendi'){ console.log("only" + this.face + "can eat" + food_kinds) } } } class toy_cat extends cat{ } let little_cat = new toy_cat("black", "little", 6) little_cat.cat_food("aikendi")
TypeScript super keyword
super的用法比较简单,我们可以把super认为是父类的一个实例,当我们想要使用父类的某些函数的时候,直接使用关键字super就可以了。最常用的地方在于,当我们在子类中重写构造函数的时候,Type Script要求我们调用父类的构造函数才能实现重写。这时候就可以使用super()
constructor(name : string, age: number, run : string) { super(name, age); this.run = run }
TypeScript interface
接口定义的形式和一个object的形式基本一致,但是接口需要声明关键字interface,接口和object的区别在于接口是可以作为限制类实现的工具被class实现的。类要实现接口的话需要使用关键字implements, 类中只能定义接口中存在的数据对象和类型。不能多也不能少。
interface animal_interface{ _name : string _age : number } class animal implements animal_interface{ _name : string _age : number
TypeScript practice
贪吃蛇设计整体思路以及要求
-
贪吃蛇场景的布局
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>zhao hansi-Gluttonous Snake</title> //标题 </head> <body> <div id ="main"> //主场景 <div id = "stage"> //游戏场景 <div id = "snake" > //蛇的主体 <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>
-
贪吃蛇食物的相关设置
对于食物而言,我们可以创建食物类来存储食物相关的属性和功能。
class Food { element: HTMLElement; //食物 constructor() { this.element = document.getElementById('food')!; } get_food_X(){ return this.element.offsetLeft; //食物的横坐标 } get_food_Y(){ return this.element.offsetTop; //食物的纵坐标 } change_food_position(){ //食物随机出现位置的函数 let X = Math.round(Math.random() * 29) * 10 ; let Y =Math.round(Math.random() * 29) * 10 ; this.element.style.left = X + 'px'; this.element.style.top = Y + 'px'; } } export default Food;
-
贪吃蛇级别和分数的设定
class ScorePanel{ score: number = 0; //分数 level: number = 1; //等级 scoreEle: HTMLElement; //分数按钮 levelEle: HTMLElement; //等级按钮 maxLevel: number; //最大等级 upScore : number //升级分数 constructor(maxLevel: number =10, upScore: number = 1) { //初始化构造函数 this.scoreEle = document.getElementById('score')!; this.levelEle = document.getElementById('level')!; this.maxLevel = maxLevel; this.upScore = upScore; } add_score(){ //加分函数,当分数/升级分数余数为0的时候,升级 this.scoreEle.innerHTML = ++ this.score + ''; if (this.score % this.upScore ===0){ this.level_up() } } level_up(){ //升级函数 if(this.level < this.maxLevel){ this.levelEle.innerHTML = ++ this.level + ''; } } } export default ScorePanel;
-
贪吃蛇自身的相关设定
class Snake{ snake_head: HTMLElement; //蛇头 bodies: HTMLCollection; //蛇身体 element: HTMLElement; //初始化蛇 constructor() { //构造函数 this.element = document.getElementById('snake')!; this.snake_head = document.querySelector(('#snake > div')) as HTMLElement; this.bodies = this.element.getElementsByTagName('div'); } get_X(){ //获取蛇横坐标 return this.snake_head.offsetLeft; } get_Y(){ //获取蛇纵坐标 return this.snake_head.offsetTop; } set_X(value: number){ //设置横坐标 if (this.get_X() === value){ //横坐标和当前的值一样就不重复设置 return; } if (value < 0 || value > 290){ //设置横坐标范围 throw new Error("This snake kit the wall, Game Over!"); //超出后直接抛出异常,并提示 } if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value){ //判断蛇是不是反方向移动,禁止这种操作 if(value > this.get_X()){ value = this.get_X() - 10; }else { value = this.get_X() + 10; } } this.moveBodies(); this.snake_head.style.left = value + 'px'; this.checkHeadBody(); } set_Y(value: number){ if (this.get_Y() === value){ return; } if (value < 0 || value > 290){ throw new Error("This snake hit the wall, Game Over!"); } if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value){ if(value > this.get_Y()){ value = this.get_Y() - 10; }else { value = this.get_Y() + 10; } } this.moveBodies(); this.snake_head.style.top = value + 'px'; this.checkHeadBody(); } addBodies(){ //增加蛇身体的函数 this.element.insertAdjacentHTML('beforeend', "<div></div>"); } moveBodies(){ //蛇身体移动的函数,遍历整个蛇的身体,让后面一节的坐标等于前面一个 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.get_X() === bd.offsetLeft && this.get_Y() ===bd.offsetTop){ throw new Error("This snake eat itself !!!") } } } } export default Snake;
-
游戏控制相关设定
import Food from "./Food"; import ScorePanel from "./ScorePanel"; import Snake from "./Snake"; import snake from "./Snake"; class GameControl{ snake: Snake; //蛇类的对象 food: Food; //食物类的对象 score: ScorePanel; //记分类的对象 direction: string = ''; //初始位置 isLive: boolean = true; //蛇是否存活的状态标记,控制游戏结束 constructor() { this.snake = new Snake(); this.food = new Food(); this.score = new ScorePanel(); this.init() } init(){ document.addEventListener('keydown',this.keyDownHandler.bind(this) ); //初始化接受键盘事件和蛇位移 this.run_snake(); } keyDownHandler(event: KeyboardEvent){ if (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'ArrowLeft' || event.key === 'ArrowRight') {//只接受方向键 this.direction = event.key; } } run_snake(){ //按不同的方向键蛇向不同的方向位移 let X = this.snake.get_X(); let Y = this.snake.get_Y(); switch (this.direction){ case "ArrowUp": Y -= 10; break; case "ArrowDown": Y += 10; break; case "ArrowLeft": X -= 10; break; case "ArrowRight": X += 10; break; } this.checkEat(X, Y); try{ this.snake.set_X(X); this.snake.set_Y(Y); }catch (e: any){ //捕获异常并抛出错误 alert(e.message); this.isLive = false; } this.isLive && setTimeout(this.run_snake.bind(this), 300 - (this.score.level -1) * 30) } checkEat(X: number, Y: number){ //当蛇头的位置和食物的位置重叠则吃到食物,食物更改位置,蛇增加身体,得分 if (X === this.food.get_food_X() && Y === this.food.get_food_Y()){ this.food.change_food_position(); this.score.add_score(); this.snake.addBodies(); } } } export default GameControl;