TypeScript学习问题及解决(附贪吃蛇源码)


前言

typescript学习总结,之后应该边学边记


一、入门

1、TypeScript简介

  • TypeScript是JavaScript的超集。
  • 它对JS进行了扩展,向JS中引入了类型的概念,并添加了许多新的特性。
  • TS代码需要通过编译器编译为JS,然后再交由JS解析器执行。
  • TS完全兼容JS,换言之,任何的JS代码都可以直接当成JS使用。
  • 相较于JS而言,TS拥有了静态类型,更加严格的语法,更强大的功能;TS可以在- 代码执行前就完成代码的检查,减小了运行时异常的出现的几率;TS代码可以编译为任意版本的JS代码,可有效解决不同JS运行环境的兼容问题;同样的功能,TS的代码量要大于JS,但由于TS的代码结构更加清晰,变量类型更加明确,在后期代码的维护中TS却远远胜于JS。

2、TypeScript 开发环境搭建

  • 下载Node.js,在vscode中使用npm全局安装typescript
npm i -g typescript
  • 创建ts文件,使用tsc对ts文件进行编译
tsc xxx.ts

3、基本类型(相对于JS独有)

  • 类型声明
    • 类型声明是TS非常重要的一个特点

    • 通过类型声明可以指定TS中变量(参数、形参)的类型

    • 指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则报错

    • 简而言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值

    • 语法:

	  let 变量: 类型;
      
      let 变量: 类型 =;
      
      function fn(参数: 类型, 参数: 类型): 类型{
          ...
      }
  • 自动类型判断
    • TS拥有自动的类型判断机制
    • 当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型
    • 所以如果你的变量的声明和赋值时同时进行的,可以省略掉类型声明
      在这里插入图片描述
  • 类型断言(会用到)
    • 有些情况下,变量的类型对于我们来说是很明确,但是TS编译器却并不清楚,此时,可以通过类型断言来告诉编译器变量的类型,断言有两种形式:
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;`
let someValue: unknown = "this is a string";
let strLength: number = (<string>someValue).length;

4、编译选项

tsconfig.json,是一个JSON文件,添加配置文件后,只需tsc 命令即可完成对整个项目的编译
主要配置:

  • exclude:定义需要排除在外的目录
    • 默认值:[“node_modules”, “bower_components”, “jspm_packages”]
  • compilerOptions
    • target:设置ts代码编译的目标版本,可解决浏览器兼容问题
    • 。。。

5、webpack

打包工具
步骤:

  • 初始化:进入项目根目录,执行命令
    npm init -y
    主要作用:创建package.json文件
  • 下载构建工具
npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader clean-webpack-plugin
  • 根目录下创建webpack的配置文件webpack.config.js
       const path = require("path");
       const HtmlWebpackPlugin = require("html-webpack-plugin");
       const { CleanWebpackPlugin } = require("clean-webpack-plugin");
       
       module.exports = {
           optimization:{
               minimize: false // 关闭代码压缩,可选
           },
       
           entry: "./src/index.ts",
           
           devtool: "inline-source-map",
           
           devServer: {
               contentBase: './dist'
           },
       
           output: {
               path: path.resolve(__dirname, "dist"),
               filename: "bundle.js",
               environment: {
                   arrowFunction: false // 关闭webpack的箭头函数,可选
               }
           },
       
           resolve: {
               extensions: [".ts", ".js"]
           },
           
           module: {
               rules: [
                   {
                       test: /\.ts$/,
                       use: {
                          loader: "ts-loader"     
                       },
                       exclude: /node_modules/
                   }
               ]
           },
       
           plugins: [
               new CleanWebpackPlugin(),
               new HtmlWebpackPlugin({
                   title:'TS测试'
               }),
           ]
       
       }代码片
  • 根目录下创建tsconfig.json,配置可以根据自己需要
       {
           "compilerOptions": {
               "target": "ES2015",
               "module": "ES2015",
               "strict": true
           }
       }
  • 修改package.json添加相应配置
	{
         ......
         "scripts": {
           "test": "echo \"Error: no test specified\" && exit 1",
           "dev": "webpack --mode development",
           "build": "webpack --mode production",
           "start": "webpack serve --open"
         },
         ......
       }
  • 在src下创建ts文件,并在并命令行执行npm run build对代码进行编译,或者执行npm start来启动开发服务器

6、Babel

  • 安装依赖包:
    npm i -D @babel/core @babel/preset-env babel-loader core-js
  • 修改webpack.config.js配置文件
......
       module: {
           rules: [
               {
                   test: /\.ts$/,
                   use: [
                       {
                           loader: "babel-loader",
                           options:{
                               presets: [
                                   [
                                       "@babel/preset-env",
                                       {
                                           "targets":{
                                               "edge": "105",
                                               "chrome": "88",
                                               'ie': '11'
                                           },
                                           "corejs":"3",
                                           "useBuiltIns": "usage"
                                       }
                                   ]
                               ]
                           }
                       },
                       {
                           loader: "ts-loader",
       
                       }
                   ],
                   exclude: /node_modules/
               }
           ]
       }
       ......

如此一来,使用ts编译后的文件将会再次被babel处理,使得代码可以在大部分浏览器中直接使用,可以在配置选项的targets中指定要兼容的浏览器版本。

二、面向对象

数据在对象中被成为属性,而功能就被称为方法。所以简而言之,在程序中一切皆是对象。

1、类(class)

要创建对象,必须要先定义类,所谓的类可以理解为对象的模型,程序中可以根据类创建指定类型的对象。

  • 定义类:
 class 类名 {
    	属性名: 类型;
    	
    	constructor(参数: 类型){
    		this.属性名 = 参数;
    	}
    	
    	方法名(){
    		....
    	}
    
    }
  • 使用类
	const p = new Person('孙悟空', 18);
	p.sayHello();

2、面向对象的特点

  • 封装
    对象实质上就是属性和方法的容器,它的主要作用就是存储属性和方法,这就是所谓的封装。

  • 只读属性(readonly):

    • 如果在声明属性时添加一个readonly,则属性便成了只读属性无法修改
  • TS中属性具有三种修饰符:

    • public(默认值),可以在类、子类和对象中修改
    • protected ,可以在类、子类中修改
    • private ,可以在类中修改
  • 属性存取器:对于一些不希望被任意修改的属性,可以将其设置为private,直接将其设置为private将导致无法再通过对象修改其中的属性,我们可以在类中定义一组读取、设置属性的方法,这种对属性读取或设置的属性被称为属性的存取器

    • 读取属性的方法叫做setter方法,设置属性的方法叫做getter方法
		class Person{
            private _name: string;
        
            constructor(name: string){
                this._name = name;
            }
        
            get name(){
                return this._name;
            }
        
            set name(name: string){
                this._name = name;
            }
        
        }
        
        const p1 = new Person('孙悟空');
        console.log(p1.name); // 通过getter读取name属性
        p1.name = '猪八戒'; // 通过setter修改name属性
  • 静态属性

    • 静态属性(方法),也称为类属性。使用静态属性无需创建实例,通过类即可直接使用

    • 静态属性(方法)使用关键字static开头

  • this

    • 在类中,使用this表示当前对象
  • 继承(extends):通过继承可以在不修改类的情况下完成对类的扩展

    • 继承时面向对象中的又一个特性

    • 通过继承可以将其他类中的属性和方法引入到当前类中

  • 重写

    • 发生继承时,如果子类中的方法会替换掉父类中的同名方法,这就称为方法的重写
    • 在子类中可以使用super来完成对父类的引用
  • 抽象类(abstract class)

    • 抽象类是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例
    • 使用abstract开头的方法叫做抽象方法,抽象方法没有方法体只能定义在抽象类中,继承抽象类时抽象方法必须要实现

3、接口(Interface)

接口的作用类似于抽象类,不同点在于接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法。接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性。
示例:

    interface Person{
        name: string;
        sayHello():void;
    }
    
    class Student implements Person{
        constructor(public name: string) {
        }
    
        sayHello() {
            console.log('大家好,我是'+this.name);
        }
    }

4、泛型(Generic)

定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时泛型便能够发挥作用。

    function test<T>(arg: T): T{
    	return arg;
    }
  • 这里的<T>就是泛型,T是我们给这个类型起的名字(不一定非叫T),设置泛型后即可在函数中使用T来表示该类型。所以泛型其实很好理解,就表示某个类型。
  • 可以同时指定多个泛型,泛型间使用逗号隔开:
      function test<T, K>(a: T, b: K): K{
          return b;
      }
      
      test<number, string>(10, "hello");
  • 类中同样可以使用泛型:
      class MyClass<T>{
          prop: T;
      
          constructor(prop: T){
              this.prop = prop;
          }
      }
  • 除此之外,也可以对泛型的范围进行约束
      interface MyInter{
          length: number;
      }
      
      function test<T extends MyInter>(arg: T): number{
          return arg.length;
      }

使用T extends MyInter表示泛型T必须是MyInter的子类,不一定非要使用接口类和抽象类同样适用。

遇到过的bug报错及解决

  • tsc : 无法加载文件 C:\Users\MI\AppData\Roaming\npm\tsc.ps1,因为在此系统上禁止运行脚本请参阅 https:/go.microsoft.c
    解决:
    1. 在win11系统中进去PowerShell的路径,依次点击“C:\Windows\System32\WindowsPowerShell”
    2. 找到V1.0文件夹继续点击,找到powershell.exe,鼠标左键,再右键以以管理员身份打开PowerShell
    3. 输入set-executionpolicy remotesigned
    4. 输入A
  • webpack4.0报错The ‘mode’ option has not been set, webpack will fallback to ‘production’ for thisvalue.
    解决:
    1. package.json中设置:
	"scripts": {
    "dev": "webpack --mode development",  // 开发环境
     "build": "webpack --mode production",  // 生产环境
  },
  • Error: Unknown option: .preset
    解决:
    presets写成了preset
  • tsconfig.json在配置文件中找不到任何输入
    解决:
    在我们创建tsconfig.json文件的时候,VSCode会自动检测当前项目当中是否有TS文件;如果没有的话,就会报这个错提示我们去创建一个文件,再去使用。
    在项目里创建一个.ts文件,空文件也可以,VSCode便不会再报此错误。
  • ValidationError: Invalid options object. Less Loader has been initialized using an options object
    解决:
    这个实际上是 Less Loader 的版本导致的兼容性问题,需要更换 Less Loader 的版本。
    把 Less Loader 进行降级,通过 npm remove less-loader命令卸载 Less Loader ,通过 npm i less-loader@5.0.0 命令安装新版本,然后问题就可以解决了。

三、贪吃蛇

  • 正常情况
    在这里插入图片描述
  • 撞到自己,游戏结束
    在这里插入图片描述
  • 撞墙,游戏结束
    在这里插入图片描述

1、遇到的问题(重点)

  1. css样式
    flex弹性盒模型不熟,很重要,多用flex-flow、align-items、justify-content等属性,明天这部分需要补课。
  2. 子绝父相定位
  3. 食物随机位置问题
    随机food的offsetLeft和offsetTop属性即可。需要注意随机数范围0~29且为10的整数倍。
class Food{
    element:HTMLElement;
    
    constructor(){
        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;
  1. 贪吃蛇移动问题
    通过设置keydown监听+定位实现,this.direction上下左右结果分别为:ArrowUpArrowDownArrowLeftArrowRight
    init(){
        document.addEventListener('keydown',this.keydownHandler.bind(this))//this:gameControl
        this.run()
    }

    keydownHandler(event:KeyboardEvent){
        // console.log(this);document
       
        this.direction=event.key
        // console.log(event.key);
        // console.log(this.food.X,this.food.Y);
        
    }
run(){
        let X=this.snake.X
        let Y=this.snake.Y

        switch (this.direction) {
            case 'ArrowUp':
                Y-=10
                break; 
            case 'ArrowDown':
                Y+=10
                break;
            case 'ArrowLeft':
                X-=10
                break; 
            case 'ArrowRight':
                X+=10
                break;
        }
        ...省略...
    }

  1. 贪吃蛇撞墙问题
    坐标与蛇头坐标相比较,若不在范围内则抛出异常,再由gamecontrol alert异常信息,游戏中断。
    set X(value){
        if (this.X===value) {
            return
        }
        if (value<0||value>290) {
           throw new Error("小蛇蛇撞墙辣!GameOver!");
           
        }        
        ...省略...
    }   
  1. 贪吃蛇撞自己问题
    检测坐标是否重合
    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("小蛇蛇撞到自己辣~GameOver!");
            }
            
        }
    }
  1. 贪吃蛇身体如何move问题
    关键点:先动尾巴再动头,若从头开始移动则第2节身体后会出现位置空白。
    moveBody(){
        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';
            
        }
    }
  1. 贪吃蛇调头问题
    以水平方向掉头为例:
    (前提:蛇头X坐标和第2身体的X坐标相同,即发生调头)
    一种情况:向右调头了,但是我们要保持向左走,所以this.X - 10,这样left值就会减小,蛇就继续向左走了。另一种情况,就是向右调头,所以是X+10。竖直方向同理。
    这部分我觉得画图更好理解一点:
    在这里插入图片描述

  2. 待总结…

2、源码

链接:https://pan.baidu.com/s/1zgGKNsnEce3pDwBScFDAZg?pwd=fob4
提取码:fob4

四、总结

typescript暂告一段落,今天学习状态还不错,继续加油。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值