学习课程:尚硅谷TypeScript教程(李立超老师TS新课)
感谢尚硅谷及李立超老师的无私分享😘😘
TypeScrip简介
TypeScript简写是ts,是微软公司做出来的,兼容JavaScript(js),是js的超集,即ts是以js的基础上拓展构建的。它的出现是为了解决js的一些缺点。
ts特点:
- 变量具有类型
- 能用js的平台都能用ts,但是ts并不能直接被js解析器执行,要先编译.ts为.js才能被js解析器执行
搭建开发环境
- 去Node管网下载并安装Node.js
- 安装cnpm,使用cnpm全局安装typescript:
cnpm i -g typescript
- 创建一个ts文件并编写ts代码
- 使用命令
tsc xxx.ts
编译上一步编写的ts代码为js,此时会生成一个js文件,如果没有右键刷新即可 - 注:使用WebStrom或VScode编写ts代码都可以
ts中的类型
1. 类型的声明
let a: number; // 声明数字变量a
a = 1; // 变量赋值
let b: number = 5; // 声明变量b并赋值
let c = 5; // 声明变量并赋值,初始值的类型就是变量类型
c = 'abc'; // 此时类型不一致会报错
let d1: 10; // 使用字面量赋值给d1,会自动指定类型为字面量的类型,且该变量之后不可再赋值(即这样定义的变量不能再修改了),类似于常量。
let d2: 'male'|'female'; // 限制只能给变量d2赋值'male'或'male'。
let d3: string|boolean; // 限制只能给变量d3赋值的类型
// 用|来连接多个类型的变量的类型的叫联合类型
function sum(a: number,b: number){ // 声明函数时指明形参的变量类型
return a+b;
}
function sum(a: number,b: number): number{ // 声明函数时指明形参的变量类型,并指定函数返回值类型
return a+b;
}
2. 类型说明
类型 | 例子 | 描述 |
---|---|---|
number | 1,-33,2.5 | 任意数字 |
string | "hi', | "hi", |
boolean | true、false | 布尔值true或false |
字面量T | 其本身 | 限制变量的值就是该字面量的值 |
any | * | 任意类型,该变量没有类型的限制 |
unknown | * | 类型安全的any,类型不确定时尽量用unknown而尽量不用any |
void | 空值(undefined) | 没有值(或undefined) |
never | 没有值 | 不能是任何值 |
object | {name:'孙悟空) | 任意的JS对象 |
array | [1,2,3] | 任意jS数组 |
tuple | [4,5] | 就是固定的长度数组 |
enum | enum{A,|B} | 枚举,TS中新增类型 |
let a; // 声明变量时没有指定变量类型,则默认该变量为any类型
// object类型表示一个js对象
let a: object;
a = {};
// 使用{}方式来指定为object类型,限定变量的对象的结构,只能给该变量的指定属性赋值且不能增减属性值
let b: {name: string, age: number};
b = {name: '孙悟空', age:18}
// 或者另一种写法,等同于上面的两行代码:
let b: {name: string} & {age: number};
b = {name: '孙悟空', age:18}
// 在对象中的属性后使用"?"表示该属性是可选的,不赋值则该对象没有该属性
let c: {name: string, age?: number};
c = {name: '孙悟空', age:18}
c = {name: '孙悟空'}
// 限定属性名的类型和属性值的类型。属性名类型一般都是string
let d: {name: string, [xxx: string]: number};
d = {name:'孙悟空', age=18, student_number:1}
// 上面代码意思:必须要有name属性,其他属性的属性名为string类型,属性值为number类型即可
// 限定变量类型为函数,且限定函数形参类型和返回值类型
// 语法:(a: 形参类型, b: 形参类型)=>返回值类型
let e: (a: number, b: number)=>number;
e = function (n1: number, n2: number): number{
return n1+n2;
}
// 数组类型
let f: string[];
f = ['a','ab','abc'];
// 或
let g: number[];
// 或
let g: Array<number>;
// 元组:固定长度的数组,限定元素个数
let h: [string,string,number];
h = ['a', 'ab', 5];
// 枚举,定义一个枚举类型并使用
enum Gender{
Female=0, // 女
Male=1 // 男
}
let i: {name: string, gender: Gender};
i = {name:"孙悟空", gender:Gender.Male};
// 类型的别名/自定义类型
type my_type1 = string;
type my_type2 = 1 | 2 | 3 | 4 | 5;
let j: my_type1; // 相当于let j: string;
let k: my_type2; // 相当于let k: 1 | 2 | 3 | 4 | 5;
3. 函数返回值类型
// 显式指明函数无返回值
function fn(): void{
}
// 显式指明函数返回值类型为string
function fn(): string{
}
// 显式指明函数返回值类型为string或number
function fn(): string | number{
}
any类型的变量可以赋值给其他任意类型,但unkown类型不能随意赋值给其他任意类型的变量,除非赋值前做类型判断,或者使用类型断言,demo如下:
let a_string: string;
let b_any: any;
let c_unkown: unkown;
a_string = 'a';
b_any= 'b';
c_unkown= 'c';
a_string = b_any; // 不报错
a_string = c_unkown; // 报错
// 类型判断,如果a_string这个变量的类型是string类型
if(typeof c_unkown === "string"){
a_string = c_unkown;
}
// 也可以使用"类型断言"来实现上面的功能;
a_string = c_unkown as string;
// 或
a_string = <string>c_unkown;
从其他文件导入变量/从其他模块导入变量:import { hi } from './m.js'
ts编译选项/ts配置文件
创建ts配置文件
关键字:typescript的配置文件/TypeScript的配置文件详解
- 单个文件自动编译(了解即可):
tsc file_name.ts -w
-w(watch),执行之后会编译且编译器会一直监听当前文件的变化,有变化会自动编译 - 当前目录下所有文件自动编译:
- 在项目目录新建文件"tsconfig.json",并在里面写入空对象
{}
- 在当前目录下执行命令
tsc
,该目录内的所有文件都会被编译 - 执行
tsc -w
,编译且监听该目录内所有ts文件自动编译
- 在项目目录新建文件"tsconfig.json",并在里面写入空对象
tsconfig.json配置详解
{
/*
include: 包含在此列表中目录的.ts文件都要编译,**表示任意目录,*表示任意文件
'./src/**/*' 代表配置文件目录下的src目录下的任意目录下的任意文件,都要被编译,src下的文件也会被编译。如果指定了,则该目录必须要存在,否则会报错。比如这里这么配置后,如果src目录不存在,则会编译报错
exclude: 排除在外的,不需要被编译的目录。exclude的默认值: ["node_modules","bower_commponents","jspm_packages"]
extends: 定义被继承的配置文件,即本文件配置。
"extends": "./configs/base"表示本配置文件会继承./configs/base.json中的配置
files: 要编译的文件列表
compilerOptions: 编译器的选项
target: 用来指定.ts被编译为的ES版本,默认是ES3。也可以写ES2017,ES2020,ESNext(最新版),如果需要target的可选列表,随便写一个错误值进行编译,报错会提醒并给出可选列表
module: 指定要使用的模块化规范
lib: 库,一般不用改,比如需要dom操作需要写["dom",]才能写document.xxx
outDir: 指定编译后生成的文件的目录
outFile: 设置该配置后,所有全局作用域中的代码都会合并到同一个文件中。即指定编译时把所有文件都合并到一个文件中的文件路径。本配置用的少,了解即可。
allowJs: 默认值是false。是否对.js进行编译,是的话,会把.js文件编译,并在outDir指定的编译文件夹生成编译后的.js文件
checkJs: 默认值是false。是否检查.js文件的语法是否符合.ts的语法规则,
removeComments: 默认值是false。编译时是否移除注释
noEmit: 默认值是false。编译时不编译后的.js文件
noEmitOnError: 默认值是false。当有错误时不生成编译后的文件
alwaysStrict: 默认值是false。用来设置编译后的文件是否使用严格模式,true的话会自动在编译后的文件首行添加一行: "use strict";代表开启严格模式,js的语法模式更严格,性能更好
noImplicitAny: 默认值是false。是否禁止出现隐式的any类型(默认定义一个变量如果没有设置类型,则默认该变量为any类型,即定义了一个隐式类型为any的变量,如果本配置为true,则禁止定义隐式类型为any的变量)
noImplicitThis: 默认值是false。是否禁止出现不明确类型的this
strictNullChecks: 默认值是false。是否严格检查空值,即是否严格检查调用对象是否是空值,如果为true,则编译报错。应该先用if检查对象是否为空再对对象操作,或比如使用obj?.addEventListener(...)的方式,如果obj对象不为空,则添加事件监听器或其他调用
strict: 相当于所有严格检查的配置项的总开关,如果为true,则alwaysStrict、noImplicitAny、noImplicitThis、strictNullChecks都会开启,一般设置为true
*/
"include": [
"./src/**/*",
],
"exclude": [
],
"extends": "./configs/base",
"files": [
"a.ts",
"b.ts",
],
"compilerOptions": {
"target": "ES6",
"module": "",
"lib": ["dom"],
"outDir": "./dist",
"outFile": "./xxx/xx.js",
"allowJs": true,
"checkJs": true,
"removeComments": true,
"noEmit": true,
"noEmitOnError": true,
"strict": true,
"alwaysStrict": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
}
}
使用webpack打包ts代码
使用webpack初始化配置
一般大型项目中,不会直接使用编译命令对项目编译,而是通过webpack对项目进行编译打包。以下是一个demo
- 新建一个空项目
- 在空项目路径下执行
cnpm init -y
,初始化项目,生成"package.json" cnpm i -D webpack webpack-cli typescript ts-loader // i:install -D:--save-dev(即现在安装的依赖是开发用的依赖) ts-loader:webpack加载器,用来整合webpack和ts的,装了它才能在webpack中使用ts
- 安装成功后,第二步生成的"package.json"文件中的依赖项列表(devDependencies)就会新增第三步安装的依赖
- 编写webpack配置文件"webpack.config.js"
// 引入一个包
const path = require("path")
//webpack中的所有配置信息都应写在module.exports中
module.exports = {
entry: "./src/index.ts", // 指定入口文件,即项目的主文件,从该文件开始执行,该文件和文件名都是随意自定义的
output: { // 指定打包文件所在的目录
path: path.resolve(__dirname, 'dist'), // 指定路径,值是拼接成完路径,等同于项目目录下的dist的绝对路径的全路径
filename: "bundle.js", // 定义打包后的文件的文件名,任意自定义的文件名都可以
enviroment: {
arrowFunction: false, // 告诉webpack不使用箭头函数,因为ie不支持箭头函数
const: false, // 告诉webpack不使用const关键字,因为ie10不支持const关键字
}
},
module: { // 指定webpack打包时要使用的模块
rules: [ // 指定要加载的规则
{
test: /\.ts$/,// test指定的是规则生效的文件,此处表示所有.ts结尾的文件
use: 'ts-loader', // 指定用什么loader来处理test的文件。(此处即使用ts-loader来处理所有的.ts文件)
exclude: /node-modules/ //排除要被use的loader处理的文件目录,此处配置即该目录下不会被ts-loader编译
}
]
}
} // 如果报错,查看项目目录中是否有该包,有的话是否IDE已经识别,如果没有识别就reload一下当前目录,还没有就重启IDE
- 编写ts的编译文件
{
"compilerOptions": {
"module": "ES2015",
"target": "ES2015", // 等同于ES6
"strict": true,
},
}
- 修改根目录下的"package.json",在根属性
scripts
中添加属性build:"webpack"
- 执行
cnpm run build
编译并打包项目,打包成功后会生成dist目录
安装webpack插件
html-webpack-plugin
插件作用:自动将编译好的js文件引入html
cnpm i -D html-webpack-plugin
- 在"webpack.config.js"配置第一步安装的插件,在该文件中添加:
const HTMLWebpackPlugin(变量名任意自定义) = require("html-webpack-plugin")
- 在webpack.config.js中的module.exports对象中添加属性:
plugins: [
new HTMLWebpackPlugin(), // 第二步定义的变量名
]
新建对象也可以写为:
new HTMLWebpackPlugin({
title: "我的title", // 指定编译后生成的html的title
template: "./src/index.html", // 指定html模板
})
- 编译并打包:
cnpm run build
webpack-dev-server
插件作用:在项目里安装一个内置服务器,如果修改了代码,可以试试更新在浏览器中
cnpm i -D webpack-dev-server
- 修改根目录下的"package.json",在根属性
scripts
中添加属性"start":"webpack serve --open chrome.exe",意思是启动webpack serve,并用chrome.exe打开网页 cnpm start
- 此时如果代码有修改。浏览器中就会实时更新
clean-webpack-plugin
插件作用: 编译时自动清除dist(编译文件目录)内的文件,使得编译目录内的文件都是最新的
cnpm i -D clean-webpack-plugin
- 和html-webpack-plugin配置一样
- 在"webpack.config.js"中添加:
const {CleanWebpackPlugin} = require("clean-webpack-plugin")
- 在webpack.config.js中的module.exports.plugins属性的值列表中添加对象new CleanWebpackPlugin():
plugins: [
new CleanWebpackPlugin(), // 第二步定义的变量名
]
- 编译并打包:
cnpm run build
时,会先清空编译目录再生成编译后文件
babel
插件作用:
- 把新语法转换成旧语法
- 让新的技术,兼容旧的浏览器,解决兼容性问题
cnpm i -D @babel/core @bable/preset-env bablel-loader core-js//preset-env:预先设置的环境 core-js:js的一个运行环境
- 安装完成后,去"package.json"检查是否安装完成,完成的话会在devDependencies下有相关的依赖名称
- 在"webpack.config.js"的
module.rules.use
的值(列表,如果不是列表则手动改为列表)中添加值
{// 配置babel的对象
loader: "babel-loader", // 指定加载器
options: { // babel的配置
preset: [
"@babel/preset-env", // 指定环境的插件
{
tagets: { // 设置要兼容的浏览器
"chrome": "88", // 向下兼容到chrome的第88个版本
"ie": "11",
},
"corejs": "3", // 指定corejs的版本
"useBuiltIns": "usage" // 使用corejs的方式,"usage"表示按需加载,用到那些代码,编译后才放进去,使用该方式能保证编译后的文件大小是最小的
}
]
}
}
注意:需要将'ts-loader'放在列表的最末尾,因为代码执行是从列表最后面往前面执行,ts-loader先将ts转为js代码后,在通过前面的babel-loader将代码转为其他tagets中指定的兼容版本(可能是旧版本)的代码
模块定义与引入
如果在变量前使用export关键字,则其他文件可以引入该文件内用export关键字装饰的变量,
比如m1.js内容为export const hi = 'abc';
其他文件可以import \{ hi \} from './m1.js'
,但是这么直接使用会报错,需要在"webpack.config.js"添加根属性
resolve: {
extensions: ['.ts', '.js'] // 凡是扩展名为该列表中的文件,都可以作为模块使用
}
面向对象
类的定义和使用
类的结构
class 类名{
属性名: 类型;
constructor(){ // 构造方法
}
}
定义类,并通过类创建对象
(function(){
class Person{
// 定义实例属性。即实例化后,通过实例的对象.属性才可访问。如果是Person.属性就能访问叫静态属性
name: string;
// 定义 类属性/静态属性。使用static修饰的属性,只能通过类名.属性名访问
static age: number = 18;
// 定义只读属性,不能再次给只读属性再次赋值
readonly hair_color: string = "black";
static readonly hair_color: string = "black";
name = "孙悟空" // 让它自动辨别属性
// 构造函数和this,构造函数会在创建对象时执行
constructor(){
this.name = "猪八戒";
}
// 有参构造函数
constructor(name: string, age: number){
// this.name = "猪八戒";
this.name = name;
this.age = age;
}
sayHello(){ // 定义 实例方法
console.log("hello!");
}
static sayHello(){ // 定义 静态方法/类方法
console.log("hello!");
}
}
})();
类的继承
(function(){
class Student extends Person{ // 继承Person类,子类Student类会拥有Person所有方法和属性
// 如果在子类中写构造函数,则必须super.调用父类的构造函数,否则会报错
learning(){
console.log("学习中");
}
sayHello(){ // 子类的方法和父类一样,则覆盖父类的方法,只运行子类的这个方法,不运行父类的方法
console.log("hello!我是学生");
super.sayHello(); // super代表当前类的父类。即此处的super=Person这个类
}
}
class Teacher extends Person{
}
const person = new Person(); // 创建对象
})();
抽象类和抽象方法
- 抽象类:如果不希望一个类被创建对象,则使用abtract来修饰该类,则该类就是抽象类,不能被实例化,比如:
abtract class Person{}
。它是专门用来被继承的。 - 抽象方法:在抽象类中可以定义抽象方法(使用abtract来修饰的方法),且抽象方法只能被定义在抽象类中,如果继承了抽象类,则抽象类中的抽象方法必须被重写。抽象方法的写法:
abstract sayHello():void;
抽象方法没有方法体,即抽象方法内是空的没有任何代码执行,只有在子类实现时才写方法体。
接口的定义与使用
接口:接口是用来定义(或限制)一个类的结构(包含哪些属性和方法),同时也可以当成类型声明去使用。但是接口可以有重名的,如果接口重名,则会自动合并成一个,属性都会合并到一起。如果在接口里写方法,也是不能有方法体,即接口中的所有属性都不能有实际的值,只能定义对象的结构。接口编译成js后就没了,不会保留在js文件中
(function(){
interface myInterface{
name: string,
age: number
}
// 如果有重名,最后接口的属性就是name、age、gender
// interface myInterface{
// gender: string
// }
const obj: myInterface = {
name: "孙悟空",
age: 18
}
// 效果和下面是一样的
type myType = {
name: string,
age: number
}
const obj: myType = {
name: "孙悟空",
age: 18
}
// 接口demo,定义一个接口,并实现该接口:
interface myInter{
name: string;
sayHello(): void;
}
class MyClass impletments myInter{
name: string;
costructor(name:string){
this.name = name;
}
sayHello(){
console.log("大家好");
};
}
})();
接口和抽象类的区别
接口实现"有没有"的问题,抽象类是实现"必须有"的问题,比如必须有吃饭睡觉忠诚的才叫狗狗,但有的狗狗可以有算数的技能这就可以用接口来实现。所以会算数的狗狗可以继承"必须有"的特点(即继承抽象类),然后实现"有没有"的特点(有没有算数技能)。这就是区别
(function(){
abstract class Dog{
abstract eat();
abstract sleep();
abstract 忠诚();
}
interface 算数技能{
算数();
}
class 会算数的狗 extends Dog impletments 算数技能{ // 必须有Dog的特点,而且可以有算数的技能。单继承多实现,共有的特点使用继承,特有的特点使用接口,此时即拥有了算数,又拥有了其他狗狗共有的特点
eat();
sleep();
忠诚();
算数();
}
})();
封装
封装:对类属性的封装。用于禁止对象/类外部直接访问对象的属性。
做法:给属性前面使用private修饰,然后用get、set方法访问(get、set方法也被称为属性存储器),如果不想属性被赋值就取消set方法就行了,获取属性值同理。
封装修饰词(权限同java):public、protect(只能在当前类或子类中使用。比如父类属性使用protect,子类可以访问,但不能通过实例.访问)、private。
(function(){
class Person{
private name: string;
private age: number;
getName(){
return this.name;
}
setName(value: string){
this.name = value;
}
getAge(){
return this.age;
}
setAge(value: number){
if(value>=0){
this.age = value;
}
}
// ts中设置getter方法的方式: get 属性名(){}
get name(){ // 使用此方式外部要调用时,使用"对象.name"就可以调用此方法了
return this.name
}
set name(value: string){ // 同上
this.name = value;
}
}
class A{
constructor(public name: string, public age: number){
// 可以把属性写在构造函数中,这样等同于单独把属性写出来
}
}
// 等价于下面class A
class A{
name: string;
age: number;
constructor(name: string, age: number){
this.name=name;
this.age=age;
}
}
const a = new A("xx",11);
})();
泛型
泛型:在定义函数或类时,如果遇到类型不明确的时候使用泛型
(function(){
function fn<T>(a: T): T{// 定义了一个泛型T(可以任意字母),然后参数是T,返回值也和参数相同也是T。
return a;
}
// 调用带有泛型的函数
fn(10); // 不指定泛型时,ts自动识别泛型类型
fn<string>("abc"); // 手动指定泛型类型
// 指定多个泛型
function fn2<T,K>(a: T, b: K): T{
return a;
}
fn2<string, number>("abc");
interface Inter{
length: number;
}
function fn3<T extends Inter>(a: T){// 定义了一个泛型,且规定该泛型必须实现/继承Inter这个接口/类,即规定了a参数必须有length这个属性
return a.length;
}
})();
项目实战:贪吃蛇
搭建开发环境
- 使用WebStorm创建一个空项目
- 复制"package.json"、"tsconfig.json"、"webpack.config.js"到项目的根目录后(这几个文件在上面有讲到),在
package.json
中修改项目名,即修改name的值。 - 执行
npm i
安装配置文件中的依赖(如果用cnpm i
安装很久,就使用npm i
,李立超老师说在WebStorm中尽量避免使用cnpm),依赖都会安装在根目录的node_modules目录中 - 在项目根目录中,创建文件"./src/index.html"、"./src/index.ts"
- 在"index.ts"中写console.log("123"),执行
npm run build
来编译测试是否正常,没错误的话会在./dist目录下生成编译后的文件 - 安装less:
npm i -D less less-loader css-loader style-loader
,css-loader是用来处理css的代码的,style-loader是用来将css代码引入到项目中的 - 安装postcss:
npm i -D postcss postcss-loader postcss-preset-env // 实现对css代码版本的控制,以及css对浏览器兼容的设置
- 在"webpack.config.js"中的rules列表中添加一个对象,用于配置对less文件的处理
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env",
{
browers: "last 2 version" // 兼容2个最新版本
}
]
]
}
}
},
"less-loader",
]
}
- 新建文件"./src/style/index.less",写入内容
body\{background-color:green;\}
- "index.ts"中引入less文件
import "./src/style/index.less"
- 使用
npm start
编译项目并测试
编写"分数面板"类
创建文件"./src/moduls/ScorePanel.ts"
class ScorePanel{
score = 0;
scoreEle: HTMLElement;
maxLevel: number;
...
constructor(maxLevel: number = 10){// 给形参设置默认值
this.maxLevel = maxLevel;
}
addScore(){
this.score++;
this.scoreEle.innerHTML = this.score + ""; // 给元素赋值
}
}
编写"食物"类
创建文件"./src/moduls/Food.ts"
class Food{
element: HTMLElement
constructor(){
this.element = document.getElementById("food")!; // 最后的"!"是告诉编译器获取的该元素不可能为空,否则编译器会因为获取不到该元素,然后报错
}
// 定义获取食物X坐标的方法
get X(){
return this.element.offsetLeft;
}
// 定义获取食物Y坐标的方法
get Y(){
return this.element.offsetTop;
}
change(){
this.element.style.left = "80px"; // 修改x坐标,移动食物位置
this.element.style.top = "80px"; // 修改y坐标
}
}
export default Food; // 最后一行把Food类作为默认模块暴露出去,外部的其他文件就可以通过`import Food from './moduls/Food';`的方式来引用Food类了。
// export default的用法在别的文章有详解,搜索即可
编写"蛇"类
创建文件"./src/moduls/Snacke.ts"
class Snacke{
X: number;
Y: number;
head: HTMLElement; // 蛇头
bodies: HTMLCollection; // 蛇身(好多div)
element: HTMLElement; // 蛇的容器(包含好多div的那个容器)
constructor(){
this.element = document.getElementById("snake")!;
this.head = document.querySelector("#snake > div") as HTMLElement; // 选择id为snake元素中的第一个div标签,且该标签必须是一个HTMLElement
this.bodies = element.getElementByTagName("div");
}
addBody(){
this.element.insertAdjacentHTML("beforend","<div></div>"); // 在element元素标签的结束之前,添加一个div(即在element之中的最后添加一个div元素)
}
set X(value: number){
if(value<0 || value>290){
throw new Error("蛇撞墙了"); // 创建异常并抛出异常
}
// 在别的文件中捕获异常:
// try{
// this.snake.X = X;
// }catch (e){
// alert(e.message);
// }
}
...
moveBody(){
// 每刷新一次时,将最后一节身体设置为和前一节一样,蛇就移动起来了
for(let i=this.bodies.lenth-1; i>0; i--){
// 获取前一节身体的位置并赋值给当前一节
lastBodyElement = this.bodies[i-1] as HTMLElement;
thisBodyElement = this.bodies[i] as HTMLElement;
let X = lastBodyElement.offsetLeft;
let Y = lastBodyElement.offsetTop;
thisBodyElement.style.left = X + "px";
thisBodyElement.style.top = Y + "px";
}
}
}
编写"游戏控制"类
创建文件"./src/moduls/GameControl.ts"
class GameControl{
isLive = true;
...
keydownHandler(event: KeyBoardEvent){
console.log(event.key);
}
init(){
document.addEventListener("keydown", this.keydownHandler.bind(this));
// 绑定事件-绑定按钮按下时的监听事件。
// 注意:keydownHandler中使用this时,this指的是document,因为是document调用的keydownHandler,所以如果直接传keydownHandler,在keydownHandler中是不能通过this获取到GameControl这个对象的。
// 解决方法:使用bind创建一个新的keydownHandler给addEventListener当回调使用,再将当前对象作为this传给新建的keydownHandler,所以这个新的keydownHandler就可以使用当前对象作为this
}
run(){ // 让蛇跑起来的方法
// 修改XY坐标
isLive && setTimeout(this.run.bind(this), 300); // 如果没有死,就定时每300毫秒执行一下当前对象的run方法
}
...
}
Q.E.D.