TypeScript
1,TypeScript概述
1,JavaScript是什么
javascript是一种运行在客户端中的编程语言。当应用于浏览器时,为网站提供动态交互特性,让网页动起来。
运行环境:浏览器,nodejsTypeScript
1,TypeScript概述
1,JavaScript是什么
javascript是一种运行在客户端中的编程语言。当应用于浏览器时,为网站提供动态交互特性,让网页动起来。
运行环境:浏览器,nodejs
TypeScript是什么
typescript是javascript的超集
为JS添加类型系统
TS相比JS的优势
JS的类型系统存在”先天缺陷“,绝大部分错误都是类型错误
- 优势一:类型化思维方式,使得开发更加严谨,提前发现错误,减少改bug时间
- 优势二:类型系统提高代码可读性,并使维护和重构代码更加容易
- 优势三:补充了接口、枚举等开发大型应用时js缺失的功能
2,安装解析TS的工具包
npm i -g typescript
TS代码在nodejs中运行
-
TS代码-》JS代码
tsc hello.ts
-
执行JS
node hello.js
简化执行TS的步骤
使用ts-node包,”直接“在node.js中执行TS代码
ts-node hello.ts
3,输出语句
console.log('Hello TS')
- console表示控制台,在node.js中,指的就是终端
- log表示日志
- 小括号内表示要打印的信息
2,TS变量和数据类型
1,什么是变量
变量,是用来存储数据的容器,并且可以变化的。
2,变量的使用
-
声明变量并指定类型
let age: number;
- let是TS的关键字,用来声明变量
- age: 自定义变量名称
- : number 用来指定变量age为数值变量
-
给变量赋值
age = 18
-
简化操作
let age: number = 18
3,类型注解
类型注解:是一种为变量添加类型约束的方式
4,变量的命名规则
数字,字母,下划线,美元符号,不能以数字开头
5,数据类型概述
-
原始类型(基本数据类型)
-
number
包含整数值和浮点型(小数)值
-
string
由零个或者多个字符串联而成的,用来表示文本信息
-
boolean
用来表示真或假(true或false)
-
undefined
表示声明但未赋值的变量值
let u: undefined = undefined
-
null
let n: null = null
-
-
对象类型(复杂数据类型)
3,运算符
1,运算符概述
也叫操作符,用来实现赋值(=)、算术运算、比较等功能的符号
2,算术运算符
加(+)减(-)乘(*)除(/)
加号的其他作用
还可以实现字符串拼接
除了加号以外,其他 算术运算符只能跟数字类型一起使用
3,赋值运算符
将等号右边的值赋值给它左边的变量
加等(+=)、减等(-=)、乘等(*=)、除等(/=)
4,自增和自减运算符
自增(++)是+= 1的简化形式
自减(–)是 -= 1的简化形式
5,比较运算符
用于比较两个数据的值,并返回其比较的结果,结果为布尔类型
大于(>)小于(<)大于等于(>=)小于等于(<=)等于(=)不等于(!)
6,逻辑运算符
逻辑运算符用于布尔类型计算,返回结果也是布尔类型
与(&&)两边的值同时为true,结果才为true
或(||)两边的值只要有一个为true,结果为true,否则为false
非(!)表示取反,true->false,false->true
4,TS语句
1,条件语句
根据判断条件的结果,来执行不同的代码,从而实现不同的功能
if语句
if (判断条件) {
条件满足时,要做的事
}
- 判断条件:布尔类型
- 如果判断条件为真,要执行要做的事
- 否则,如果判断条件为假,则不执行花括号中的代码
else语句
else语句必须配合if语句来使用
条件不满足时要做的事情(if语句的对立面)
if (判断条件) {
条件满足时,要做的事
} else {
条件不满足时,要做的事
}
- 否则,如果判断条件为假,就执行条件不满时要做的事情
2,三元运算符
根据判断条件的真假,得到不同的结果
结果 = 判断条件 ? 值1 : 值2
- 如果判断条件为真,结果为值1
- 否则如果判断条件为假,结果为值2
- 值1和值2的类型相同
3,循环语句
for循环
实现重复做某种事情的循环语句
for循环的组成:
- 初始化语句:声明计数器变量用来记录循环次数(执行一次)
- 判断条件:判断循环次数是否达到目标次数
- 计数器更新:完成一次循环让计数器数量加1
- 循环体:循环代码,也就是要重复做的事情
for (初始化语句; 判断条件; 计数器更新) {
循环体
}
- 初始化语句:声明计数器变量,记录循环次数
- 判断条件:判断循环次数是否达到目标次数
- 计数器更新:计数器数量加1
- 循环体:重复执行代码,也就是要重复做的事情
break和continue
用来改变循环的执行过程
break让循环提前结束(终止循环)
continue让循环间断执行(跳过本次循环,继续下一次循环)
断点调试
调试配置
安装调试用到的包
npm i ts-node typescript
修改launch.json的设置
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "调试TS代码",
"runtimeArgs": ["-r", "ts-node/register"],
"args": ["${workspaceFolder}/hello.ts"]
}
]
}
5,TS数组
1,数组的概述
用于存放多个数据的集合(数组中,通常都是相同类型的数据)
2,创建数组
let names: string[] = ['aa', 'bb', 'cc']
多个元素之间使用逗号(,)分隔
3,数组长度与索引
数组长度:表示数组中元素的个数,通过数组的length属性获取
let names: string[] = ['aa', 'bb', 'cc']
console.log(names.length)
》3
我们把数组中元素的序号,称为:索引(下标),数组中元素与索引一一对应
数组索引是从0开始
最大索引=length-1
4,数组取值和存值
从数组中,获取到某一个元素的值,就是从数组中取值
数组名称[索引]
let names: string[] = ['aa', 'bb', 'cc']
console.log(names[0])
》aa
修改数组中某个元素的值,就是使用数组存值。
先获取到要修改的元素,然后再存值
数组名称[索引] = 新值
let names: string[] = ['aa', 'bb', 'cc']
names[0] = 'dd'
console.log(names)
》[ 'dd', 'bb', 'cc' ]
添加元素
数组名称[索引] = 新值
索引存在为修改元素,索引不存在为添加元素
let names: string[] = ['aa', 'bb', 'cc']
names[3] = 'dd'
console.log(names)
》[ 'aa', 'bb', 'cc', 'dd' ]
5,遍历数组
就是把数组中的所有元素挨个获取一次
let names: number[] = [100, 200, 300]
for (let i: number = 0; i < names.length; i++) {
console.log(names[i])
}
》100
》200
》300
6,TS的函数基础
1,声明函数
function 函数名称() {
函数体
}
- 函数名称:推荐以动词开头,因为函数表示做一件事情,实现一个功能
- 函数体:表示要实现功能的代码,复用的代码
2,调用函数
函数名()
只用调用函数后,函数内的代码才会运行
3,函数参数
增加函数的灵活性、通用性,针对相同的功能,能够适应更多的数据
function sum(num1: number, num2: number) {
console.log('两个数的和:'+ (num1+num2))
}
let num1: number = 10
let num2: number = 20
sum(num1, num2)
》两个数的和:30
形参和实参
-
形参:声明函数时指定的参数,放在声明函数的小括号
function sing(songName: string) {}
- 语法: 形参名称:类型注解,类似于变量声明,但是没有赋值
- 作用:指定函数可接收的数据
-
实参:调用函数时传入的参数,放在调用函数的小括号中
sing('哈哈')
- 实参是一个具体的值,用来赋值给形参
根据具体的功能,函数参数可以有多个,参数之间使用逗号,隔开
4,函数返回值
函数返回值的作用:将函数内部计算的结果返回,以便于使用该结果继续参与其他的计算
function sum(nums: number[]): number {
let sum: number = 0
for(let i: number = 0; i < nums.length; i++) {
sum += nums[i]
}
console.log(sum)
return sum
}
let nums1: number[] = [1, 20 ,30]
let nums2: number[] = [1, 20 ,40]
console.log(sum(nums1) + sum(nums2))
》51
》61
》112
注意:如果没有指定函数的返回值,那么,函数返回值的默认类型为void(空,什么都没有)
指定返回值类型
function fn(): 类型注解 {
return 返回值
}
- 在函数体中,使用return关键字来返回函数执行结果
- 返回值必须符合返回值类型的类型要求,否则会报错
基本使用
使用变量接收函数返回值
let result: 类型注解 = fn()
变量result的类型与函数返回值类型相同
return的说明
- 将函数内部的计算结果返回。
- 终止函数代码执行,即:return后面的代码不会执行
- return不能在函数外使用
- return可以单独使用(后面可以不跟内容),用来刻意终止函数的执行
7,TS函数进阶
1,函数的执行过程
- 函数里面,还可以继续调用其他函数
- 函数,按照顺序一行行的执行代码,当遇到调用其他函数时,先完成该函数调用,再继续执行代码
2,变量作用域
一个变量的作用域是:代码中定义变量的区域,它决定了变量的使用范围
- 局部变量:表示在函数内部声明的变量,该变量只能在函数内部使用
- 全局变量:表示在函数外部声明的变量,该变量在当前ts文件的任何地方都可以使用。
8,TS对象
1,对象概述
是对生活中具体事务的抽象,使得我们可疑通过代码来描述具体的事物
TS中的对象,也是由特征和行为组成的,他们有各自专业的名称:属性(特征)和方法(行为)
一组相关属性和方法的集合,并且是无序的
2,创建对象
let person = {}
- 此处{}(花括号、大括号)表示对象。而对象中没有属性或方法时,称为L空对象
- 对象中属性和方法,采用键值对的形式,键值之间使用冒号(:)来配对
let person = {
name: '坤坤',
age: 99
}
- 属性和方法的区别:值是不是函数,如果是,就称为方法;否则,就是普通属性
3,接口
1,对象的类型注解
TS中的对象是结构化的,结构简单来说就是对象有什么属性或方法
let person: {
name: string;
age: number;
}
person = {name: '坤坤', age: 99}
console.log(person)
2,对象方法的类型注解
let person: {
sayHi: () => void
sing: (name: string) => void
sum: (num1: number, num2: number) => number
}
- 箭头(=>) 左边小括号中的内容:表示方法的参数类型
- 箭头(=>)右边的内容:表示方法的返回值类型
- 方法类型注解的关键点:1参数 2返回值
3,接口的使用
接口:为对象的类型注解命名,并为你的代码建立契约来约束对象的结构
interface Person {
name: string
age: number
}
let p1: Person = {
name: '坤坤',
age: 99
}
console.log(p1)
》{ name: '坤坤', age: 99 }
- interface表示接口,接口名称约定以大写字母开头
- 推荐使用接口来作为对象的类型注解
4,对象的取值和存值
取值:拿到对象中的属性或方法并使用
获取对象中的属性,称为:访问属性
获取对象中的方法并调用,称为:调用方法
通过点语法(.)获取对象属性
interface Person {
name: string
age: number
}
let p1: Person = {
name: '坤坤',
age: 99
}
console.log(p1.name)
console.log(p1.age)
》坤坤
》99
interface Person {
name: string
age: number
sayHi: () => void
}
let p1: Person = {
name: '坤坤',
age: 99,
sayHi: function() {
console.log('唱跳rap')
}
}
p1.sayHi()
》唱跳rap
存值:修改(设置)对象中属性的值
interface Person {
name: string
age: number
sayHi: () => void
}
let p1: Person = {
name: '坤坤',
age: 99,
sayHi: function() {
console.log('唱跳rap')
}
}
p1.name = '割割'
console.log(p1)
》{ name: '割割', age: 99, sayHi: [Function: sayHi] }
先通过点语法获取到对象属性,然后将新值赋值到该属性
5,内置对象
内置对象是TS/JS自带的一些基础对象,提供了TS开发时所需的函数和方法
查询文档:
数组对象:
-
push:添加数据0000000000
let animals: string[] = ['pigs', 'goats', 'sheep'] console.log(animals.push('dog')) console.log(animals) 》4 》[ 'pigs', 'goats', 'sheep', 'dog' ]
-
forEach:遍历数组
let animals: string[] = ['pigs', 'goats', 'sheep'] animals.forEach(function (item, index) { console.log(index, item ) }) 》0 pigs 1 goats 2 sheep
forEach方法,可以根据当前数组的类型,自动推导出回调函数中参数的类型
回调函数中的参数可以用任意名称,并且没有用到可以省略
-
some:数组对象
遍历数组,查找是否有一个满足条件的元素(如果有,可以停止循环)
some方法的返回值:布尔值。如果找到满足条件的元素,结果为true,否则为false。
6,TS的类型推论
由于类型推论的存在,有些地方,类型注解可以省略不写。
- 声明变量并初始化时
- 决定函数返回值时
let age = 19
let setName = 'aaa'
let person = {
name: 'jack'
}
function sum(num1: number, num2: number) {
return num1 + num2
}
sum(1 ,2)
9,web开发基础
- (结构)HTML负责创建页面结构
- (样式)CSS负责美化页面结构
- (行为)javascript负责让页面动起来,解锁更多动效
1,浏览器中运行TS
浏览器中只能运行JS,无法直接运行TS,需要将TS转化为JS然后再运行
-
使用命令tsc index.ts将TS文件转化为JS文件。
-
在页面中,使用script标签引入生成的js文件
<script src="./index.js"></script>
解决每次都要手动转化js:
tsc --watch index.ts
2,DOM操作
文档对象模型
DOM是浏览器提供的,专门用来操作网页内容的一些JS对象
html和DOM的关系:浏览器根据HTML内容创建相应的DOM对象,也就是:每个HTML标签都有对应的DOM对象。
document.title = 'DOM操作1'
常用的DOM操作:
- 获取DOM元素
- 设置样式
- 设置内容
- 绑定(解除)事件
1,获取元素
常用方法
-
获取一个DOM元素
document.querySelector(selector)
- document对象:文档对象(整个对象),是操作页面内容的入口对象
- selector参数:是一个CSS选择器(标签、类、ID选择器等)
- 作用:查询(获取)与选择器参数匹配的DOM元素,但是只能获取到第一个。
-
类型断言
因为无法根据id来确定元素的类型,所以querySelector()方法返回了一个宽泛的类型:元素(Element)类型。
Element类型只包含所有元素共有的属性和方法(比如id属性)
使用类型断言,来手动指定更加具体的类型。
let title = document.querySelector('.c1') as HTMLParagraphElement
通过console.dir()打印DOM对象,来查看该元素的类型
-
获取多个DOM元素
document.querySelectorAll(selector)
作用:获取所有与选择器参数匹配的DOM元素,返回值是一个列表
推荐:使用class选择器
2,操作文本内容
读取:
dom.innerText
设置:
dom.innerText = '坤坤'
注意:需要通过类型断言来指定DOM元素的具体类型,才可以使用innerText属性
3,操作样式
- dom.style属性:行内样式操作,可以设置每一个样式属性
- dom.classList属性:类样式操作,就是操作类型,比如添加类名,移除类名
1,style属性
读取
dom.style.样式名称
设置:
dom.style.样式名称 = 样式值
所有的样式名称都与CSS相通,但命名规则为驼峰命名法
2,classList属性
添加
dom.classList.add(类名1, 类名2, ...)
移除
dom.classList.remove(类名1, 类名2, ...)
判断类名是否存在
let has = dom.classList.contains(类名)
4,操作事件
1,addEventListener添加事件
dom.addEventListener(事件名称, 事件处理程序)
事件名称:字符串,比如:‘click’(鼠标点击事件)、‘mouseenter’(鼠标进入事件)
事件处理程序:回调函数、指定要实现的功能,该函数会在出发事件时调用
2,事件对象(event)是事件处理程序(回调函数)的参数
表示:与当前事件相关的信息,比如:事件类型(type)、触发事件的DOM事件(target)
3,removeEventListener移除事件
作用:移除给DOM元素添加的事件,移除后,事件就不再触发了
dom.removeEventListener(事件名称, 事件处理程序)
事件处理程序:必须要跟添加事件时事件处理程序是同一个,否则无法移除!
btn.addEventListener('click', function() {}, { once: true })
once:如果值为true,会在触发事件后,自动将事件移除,达到只触发一次的目的。
5,函数声明形式的事件处理程序说明
1,可以先使用函数,再声明函数
原因:函数声明在当前ts文件中的任意位置都有定义
2,使用事件对象参数时,应该指定类型注解,否则,访问事件对象的属性时没有任何提示。
技巧:使用原始方式(匿名回调函数)查看参数类型。
10,实例(下棋游戏)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Tic Tac Toe</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>Tic Tac Toe</h1>
<div class="container">
<!-- 游戏面板(棋盘) -->
<div id="bord" class="game-board x">
<div class="row">
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
</div>
<div class="row">
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
</div>
<div class="row">
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
</div>
</div>
<!-- 游戏获胜信息面板 -->
<div id="message" class="game-message">
<p id="winner">X 赢了!</p>
<button id="restart">重新开始</button>
</div>
</div>
<script src="./index.js"></script>
</body>
</html>
style.css
p {
margin: 0;
}
body {
background-color: #f9f2e7;
}
/* 标题 */
h1 {
text-align: center;
font-size: 60px;
color: #477998;
}
/* 游戏内容容器 */
.container {
position: relative;
width: 471px;
height: 471px;
margin: 0 auto;
}
/* 游戏获胜信息面板 */
.game-message {
display: none;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(69, 133, 136, 0.4);
text-align: center;
}
/* winner 获胜者 */
.game-message p {
margin: 180px 0 40px 0;
color: #fff;
font-size: 50px;
}
/* 重新开始游戏按钮 */
.game-message button {
color: #517304;
border-color: #517304;
width: 110px;
height: 40px;
font-size: 20px;
cursor: pointer;
}
/* 游戏面板棋盘 */
.game-board {
width: 471px;
height: 471px;
}
.game-board.x .cell:not(.x):not(.o):hover::before {
content: 'X';
color: lightgray;
}
.game-board.o .cell:not(.x):not(.o):hover::before {
content: 'O';
color: lightgray;
}
/* 棋盘 - 行 */
.row {
display: flex;
}
.row:last-child .cell {
border-bottom: 0;
}
/* 棋盘 - 单元格 */
.cell {
flex: 1;
box-sizing: border-box;
width: 157px;
height: 157px;
line-height: 157px;
border-right: 6px solid #546363;
border-bottom: 6px solid #546363;
text-align: center;
cursor: pointer;
font-size: 88px;
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, sans-serif;
}
.cell:last-child {
border-right: 0;
}
/* x 玩家 */
.cell.x::before {
content: 'X';
color: #01a8c6;
}
/* o 玩家 */
.cell.o::before {
content: 'O';
color: #8fbe01;
}
1,实现单元格点击
效果:点击棋盘的任意单元格,单元格显示X(默认)
- 获取到所有的单元格列表
- 遍历单元格列表,给每一个单元格添加点击事件
- 给当前被点击的单元格添加类名x
let cells = document.querySelectorAll('.cell')
cells.forEach(function(item) {
// console.log(item)
let cell = item as HTMLDivElement
cell.addEventListener('click', function(event) {
// console.log('click', event.target)
let target = event.target as HTMLDivElement
target.classList.add('x')
})
})
优化:
- 防止单元格重复点击,在添加事件时,使用once属性,让单元格只能被点击一次
- 使用函数声明形式的事件处理程序(代码多了后,代码结构会更清晰)
let cells = document.querySelectorAll('.cell')
cells.forEach(function(item) {
// console.log(item)
let cell = item as HTMLDivElement
cell.addEventListener('click', clickCell, { once: true })
})
// 棋盘中单元格的click事件处理程序
function clickCell(event: MouseEvent) {
let target = event.target as HTMLDivElement
target.classList.add('x')
}
2,切换玩家
玩家X和玩家O的交替运行
- 创建一个存储当前玩家的变量(currentPlayer),默认值为:X。
- 将添加给单元格时写死的类名X,替换为变量(currentPlayer)的值
- 切换到另一个玩家:在添加类名(下棋完成一步)后,根据当前玩家,得到另外一个玩家。
- 处理下一步提示:移除游戏面板中的x和o类名,添加下一个玩家对应的类名
let cells = document.querySelectorAll('.cell')
let gameBord = document.querySelector('#bord')
let currentPlayer = 'x'
cells.forEach(function(item) {
// console.log(item)
let cell = item as HTMLDivElement
cell.addEventListener('click', clickCell, { once: true })
})
// 棋盘中单元格的click事件处理程序
function clickCell(event: MouseEvent) {
let target = event.target as HTMLDivElement
target.classList.add(currentPlayer)
// 根据当前玩家,得到另外一个玩家
currentPlayer = currentPlayer === 'x' ? 'o' : 'x'
// 处理下一步提示:移除游戏面板中的x和o类名,添加下一个玩家类名
gameBord.classList.remove('x', 'o')
gameBord.classList.add(currentPlayer)
}
枚举:
使用变量(currentPlayer)处理当前玩家,存在的问题:
- 变量的类型是string,它的值可以是任意字符串
- 如果不小心写错(o -> 0),代码不会报错,但功能就无法实现了,并且很难找错
枚举是组织有关联数据的一种方式(比如,x和o就是有关联的数据)
使用场景:当变量的值,只能是几个固定值的一个,应该使用枚举来实现。
创建枚举的语法:
enum 枚举名称 { 成员1, 成员2, ...}
- 约定枚举名称、成员名称以大写字母开头
- 多个成员之间使用逗号(,)分隔
- 枚举中的成员,根据功能自己指定
- 枚举中的成员不是键值对
使用枚举:
枚举是一种类型,因此,可以其作为变量的类型注解。
enum Gender { Female, Male }
let userGender: Gender
访问枚举(Gender)中的成员,作为变量(userGender)的值:
userGender = Gender.Female
userGender = Gender.Male
注意:枚举成员是只读的,也就是说,枚举中的成员可以访问,但是不能赋值
枚举成员是有值的,默认为:从0开始自增的数值。
我们把枚举成员的值为数字的枚举称为数字枚举
也可以给枚举中的成员初始化值
字符串枚举:枚举成员的值是字符串
注意:字符串枚举没有自增长行为,因此,每个成员必须有初始值。
3,使用枚举修改当前玩家
效果:使用枚举代替原来的字符串类名(x和o)
- 创建字符串枚举(Player),提供x和o两个成员
- 讲成员x的值设置为:‘x’(类名);将成员o的值设置为‘o’(类名)。
- 将变量(currentPlayer)的类型设置为Player枚举类型,默认值为Player.x
- 将所有用到的x和o的抵挡全部使用枚举成员代替
enum Player {
X = 'x',
O = 'o'
}
// 单元格列表
let cells = document.querySelectorAll('.cell')
// 游戏面板
let gameBord = document.querySelector('#bord')
// 当前玩家
let currentPlayer: Player = Player.X
cells.forEach(function(item) {
// console.log(item)
let cell = item as HTMLDivElement
cell.addEventListener('click', clickCell, { once: true })
})
// 棋盘中单元格的click事件处理程序
function clickCell(event: MouseEvent) {
let target = event.target as HTMLDivElement
target.classList.add(currentPlayer)
// 根据当前玩家,得到另外一个玩家
currentPlayer = currentPlayer === Player.X ? Player.O : Player.X
// 处理下一步提示:移除游戏面板中的x和o类名,添加下一个玩家类名
gameBord.classList.remove(Player.X, Player.O)
gameBord.classList.add(currentPlayer)
}
4,游戏判赢
思路:判断棋盘中,横、竖、斜是否存在三个相同x或o。
只要有一个满足条件,就说明x或者o获胜
如果所有单元格都有内容,但没有获胜的情况,就说明是平局
使用单元格索引,来表示每种获胜情况,使用数组来存储,比如: [0, 1, 2]
获胜的所有情况(8种):
[0, 1, 2] [3, 4, 5] [6, 7, 8] 横
[0, 3, 6] [1, 4, 7] [2, 5, 8] 竖
[0, 4, 8] [2, 4, 6] 斜
单元格元素列表说明:
单元格元素列表(cells),实际上是一个伪数组。
伪数组的特征:具有长度(length)属性和索引
伪数组的操作:
- 通过索引获取元素
- 使用for循环遍历(推荐使用forEach方法)
封装判赢函数:
说明:判赢,就是在判断当前玩家下棋后是否获胜
- 声明函数(checkWin),指定参数(player),类型注解为:Player枚举
- 指定返回值:现在函数中写死返回true或false。
- 在给单元格添加类名后(下棋后),调用函数checkWin,拿到函数返回值
- 判断函数返回值是否为true,如果是,说明当前玩家获胜了。
思路:
遍历判赢数组,分别判断每种情况对应的3个单元格元素,是否同时包含当前玩家的类名。
使用数组的some方法:
- 遍历数组时可终止
- 方法返回值为true或false
判赢的实际步骤:
- 使用some方法遍历数组,并将some方法的返回值作为判赢函数的返回结果
- 在some方法的回调函数中,获取到每种获胜情况对应的3个单元格元素
- 判断这三个单元格元素是否同时包含当前玩家的类名
- 如果包含(玩家获胜),就在回调函数中返回true停止循环;否则,返回false,继续下一次循环
enum Player {
X = 'x',
O = 'o'
}
// 单元格列表
let cells = document.querySelectorAll('.cell')
// 游戏面板
let gameBord = document.querySelector('#bord')
// 当前玩家
let currentPlayer: Player = Player.X
// 判赢数组
let winsArr = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]
]
cells.forEach(function (item) {
// console.log(item)
let cell = item as HTMLDivElement
cell.addEventListener('click', clickCell, { once: true })
})
// 棋盘中单元格的click事件处理程序
function clickCell(event: MouseEvent) {
let target = event.target as HTMLDivElement
target.classList.add(currentPlayer)
// 调用判赢函数是否获胜
let isWin = checkWin(currentPlayer)
if (isWin) {
console.log('当前玩家获胜了', currentPlayer)
}
// 根据当前玩家,得到另外一个玩家
currentPlayer = currentPlayer === Player.X ? Player.O : Player.X
// 处理下一步提示:移除游戏面板中的x和o类名,添加下一个玩家类名
gameBord.classList.remove(Player.X, Player.O)
gameBord.classList.add(currentPlayer)
}
// 封装判赢函数
function checkWin(player: Player) {
let isWin = winsArr.some(function (item) {
let cellIndex1 = item[0]
let cellIndex2 = item[1]
let cellIndex3 = item[2]
let cell1 = cells[cellIndex1]
let cell2 = cells[cellIndex2]
let cell3 = cells[cellIndex3]
// 判断这三个单元格元素是否同时包含当前玩家的类名
if (
cell1.classList.contains(player) &&
cell2.classList.contains(player) &&
cell3.classList.contains(player)
) {
return true
}
return false
})
return isWin
}
优化后:
enum Player {
X = 'x',
O = 'o'
}
// 单元格列表
let cells = document.querySelectorAll('.cell')
// 游戏面板
let gameBord = document.querySelector('#bord')
// 当前玩家
let currentPlayer: Player = Player.X
// 判赢数组
let winsArr = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]
]
cells.forEach(function (item) {
// console.log(item)
let cell = item as HTMLDivElement
cell.addEventListener('click', clickCell, { once: true })
})
// 棋盘中单元格的click事件处理程序
function clickCell(event: MouseEvent) {
let target = event.target as HTMLDivElement
target.classList.add(currentPlayer)
// 调用判赢函数是否获胜
let isWin = checkWin(currentPlayer)
if (isWin) {
console.log('当前玩家获胜了', currentPlayer)
}
// 根据当前玩家,得到另外一个玩家
currentPlayer = currentPlayer === Player.X ? Player.O : Player.X
// 处理下一步提示:移除游戏面板中的x和o类名,添加下一个玩家类名
gameBord.classList.remove(Player.X, Player.O)
gameBord.classList.add(currentPlayer)
}
// 封装判赢函数
function checkWin(player: Player) {
return winsArr.some(function (item) {
// 判断这三个单元格元素是否同时包含当前玩家的类名
if (
// cells[item[0]].classList.contains(player) &&
// cells[item[1]].classList.contains(player) &&
// cells[item[2]].classList.contains(player)
hasClass(cells[item[0]], player) &&
hasClass(cells[item[1]], player) &&
hasClass(cells[item[2]], player)
) {
return true
}
return false
})
}
// 判断单元格元素是否同时包含当前玩家的类名函数
function hasClass(el: Element, name: string ) {
return el.classList.contains(name)
}
5,判断平局
思路:创建变量(steps),记录已下棋的次数,判断steps是否等于9,如果等于9,则为平局
- 创建变量steps,默认值为0
- 在玩家下棋后,让steps加1
- 在判赢的代码后面,判断steps是否等于9
- 如果等于9说明平局,游戏结束,就直接return,不再执行后续代码
enum Player {
X = 'x',
O = 'o'
}
// 单元格列表
let cells = document.querySelectorAll('.cell')
// 游戏面板
let gameBord = document.querySelector('#bord')
// 当前玩家
let currentPlayer: Player = Player.X
// 判赢数组
let winsArr = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]
]
// 记录已下棋的次数
let steps = 0
cells.forEach(function (item) {
// console.log(item)
let cell = item as HTMLDivElement
cell.addEventListener('click', clickCell, { once: true })
})
// 棋盘中单元格的click事件处理程序
function clickCell(event: MouseEvent) {
let target = event.target as HTMLDivElement
target.classList.add(currentPlayer)
steps++
// 调用判赢函数是否获胜
let isWin = checkWin(currentPlayer)
if (isWin) {
console.log('当前玩家获胜了', currentPlayer)
// 因为游戏已经结束,所以此处直接return,来刻意阻止后续代码执行
return
}
// 判断平局
if(steps === 9) {
// 因为游戏已经结束,所以此处直接return,来刻意阻止后续代码执行
return
}
console.log('平局之后')
// 根据当前玩家,得到另外一个玩家
currentPlayer = currentPlayer === Player.X ? Player.O : Player.X
// 处理下一步提示:移除游戏面板中的x和o类名,添加下一个玩家类名
gameBord.classList.remove(Player.X, Player.O)
gameBord.classList.add(currentPlayer)
}
// 封装判赢函数
function checkWin(player: Player) {
return winsArr.some(function (item) {
// 判断这三个单元格元素是否同时包含当前玩家的类名
if (
// cells[item[0]].classList.contains(player) &&
// cells[item[1]].classList.contains(player) &&
// cells[item[2]].classList.contains(player)
hasClass(cells[item[0]], player) &&
hasClass(cells[item[1]], player) &&
hasClass(cells[item[2]], player)
) {
return true
}
return false
})
}
// 判断单元格元素是否同时包含当前玩家的类名函数
function hasClass(el: Element, name: string ) {
return el.classList.contains(name)
}
6,展示获胜信息
效果:在获胜或平局时,展示相应信息
- 获取到与获胜信息相关得两个DOM元素:1#message 2#winner
- 显示获胜信息面板(通过style属性实现)
- 展示获胜信息:如果获胜,展示“x赢了”或者”y赢了“;如果是平局,展示”平局“
enum Player {
X = 'x',
O = 'o'
}
// 单元格列表
let cells = document.querySelectorAll('.cell')
// 游戏面板
let gameBord = document.querySelector('#bord')
// 获胜信息面板
let message = document.querySelector('#message') as HTMLDivElement
// 获胜者
let winner = document.querySelector('#winner') as HTMLParagraphElement
// 当前玩家
let currentPlayer: Player = Player.X
// 判赢数组
let winsArr = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]
]
// 记录已下棋的次数
let steps = 0
cells.forEach(function (item) {
// console.log(item)
let cell = item as HTMLDivElement
cell.addEventListener('click', clickCell, { once: true })
})
// 棋盘中单元格的click事件处理程序
function clickCell(event: MouseEvent) {
let target = event.target as HTMLDivElement
target.classList.add(currentPlayer)
steps++
// 调用判赢函数是否获胜
let isWin = checkWin(currentPlayer)
if (isWin) {
message.style.display = 'block'
winner.innerText = currentPlayer + '赢了'
// 因为游戏已经结束,所以此处直接return,来刻意阻止后续代码执行
return
}
// 判断平局
if(steps === 9) {
message.style.display = 'block'
winner.innerText = '平局'
// 因为游戏已经结束,所以此处直接return,来刻意阻止后续代码执行
return
}
// 根据当前玩家,得到另外一个玩家
currentPlayer = currentPlayer === Player.X ? Player.O : Player.X
// 处理下一步提示:移除游戏面板中的x和o类名,添加下一个玩家类名
gameBord.classList.remove(Player.X, Player.O)
gameBord.classList.add(currentPlayer)
}
// 封装判赢函数
function checkWin(player: Player) {
return winsArr.some(function (item) {
// 判断这三个单元格元素是否同时包含当前玩家的类名
if (
// cells[item[0]].classList.contains(player) &&
// cells[item[1]].classList.contains(player) &&
// cells[item[2]].classList.contains(player)
hasClass(cells[item[0]], player) &&
hasClass(cells[item[1]], player) &&
hasClass(cells[item[2]], player)
) {
return true
}
return false
})
}
// 判断单元格元素是否同时包含当前玩家的类名函数
function hasClass(el: Element, name: string ) {
return el.classList.contains(name)
}
7,重新开始
效果:点击重新开始按钮,重新开始下棋游戏
说明:重新开始游戏,实际就是重置游戏中的所有数据,恢复到初始状态
- 获取到重新开始按钮(#restart),并绑定点击事件
- 在点击事件中,重置游戏数据
- 隐藏获胜信息,清空棋盘,移除单元格点击事件,重新给单元格绑定点击事件
- 重置下棋次数,重置默认玩家为x,重置下棋提示为x
enum Player {
X = 'x',
O = 'o'
}
// 单元格列表
let cells = document.querySelectorAll('.cell')
// 游戏面板
let gameBord = document.querySelector('#bord')
// 获胜信息面板
let message = document.querySelector('#message') as HTMLDivElement
// 获胜者
let winner = document.querySelector('#winner') as HTMLParagraphElement
// 当前玩家
let currentPlayer: Player = Player.X
// 重新开始按钮
let restart = document.querySelector('#restart') as HTMLButtonElement
// 判赢数组
let winsArr = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]
]
// 记录已下棋的次数
let steps = 0
// 给重新开始绑定点击事件
restart.addEventListener('click', function() {
// 隐藏获胜信息
message.style.display = 'none'
// 重置下棋次数
steps = 0
// 重置默认玩家为x
currentPlayer = Player.X
// 重置下棋提示为x
gameBord.classList.remove(Player.X, Player.O)
gameBord.classList.add(Player.X)
// 清空棋盘
cells.forEach(function(item) {
let cell = item as HTMLDivElement
cell.classList.remove(Player.X, Player.O)
// 移除单元格点击事件,重新给单元格绑定点击事件
cell.removeEventListener('click', clickCell)
cell.addEventListener('click', clickCell, { once: true})
})
})
cells.forEach(function (item) {
// console.log(item)
let cell = item as HTMLDivElement
cell.addEventListener('click', clickCell, { once: true })
})
// 棋盘中单元格的click事件处理程序
function clickCell(event: MouseEvent) {
let target = event.target as HTMLDivElement
target.classList.add(currentPlayer)
steps++
// 调用判赢函数是否获胜
let isWin = checkWin(currentPlayer)
if (isWin) {
message.style.display = 'block'
winner.innerText = currentPlayer + '赢了'
// 因为游戏已经结束,所以此处直接return,来刻意阻止后续代码执行
return
}
// 判断平局
if(steps === 9) {
message.style.display = 'block'
winner.innerText = '平局'
// 因为游戏已经结束,所以此处直接return,来刻意阻止后续代码执行
return
}
// 根据当前玩家,得到另外一个玩家
currentPlayer = currentPlayer === Player.X ? Player.O : Player.X
// 处理下一步提示:移除游戏面板中的x和o类名,添加下一个玩家类名
gameBord.classList.remove(Player.X, Player.O)
gameBord.classList.add(currentPlayer)
}
// 封装判赢函数
function checkWin(player: Player) {
return winsArr.some(function (item) {
// 判断这三个单元格元素是否同时包含当前玩家的类名
if (
// cells[item[0]].classList.contains(player) &&
// cells[item[1]].classList.contains(player) &&
// cells[item[2]].classList.contains(player)
hasClass(cells[item[0]], player) &&
hasClass(cells[item[1]], player) &&
hasClass(cells[item[2]], player)
) {
return true
}
return false
})
}
// 判断单元格元素是否同时包含当前玩家的类名函数
function hasClass(el: Element, name: string ) {
return el.classList.contains(name)
}
优化重新游戏功能:
将第一次游戏,也看作是“重新开始游戏”,就可以去掉第一次游戏时重复的初始化操作了。
- 将重新开始按钮的事件处理程序修改为:函数声明形式(startGame)
- 直接调用函数(startGame),来开始游戏
- 移除变量steps、currentPlayer的默认值,并添加明确的类型注解
- 移除给单元格绑定事件的代码
enum Player {
X = 'x',
O = 'o'
}
// 单元格列表
let cells = document.querySelectorAll('.cell')
// 游戏面板
let gameBord = document.querySelector('#bord')
// 获胜信息面板
let message = document.querySelector('#message') as HTMLDivElement
// 获胜者
let winner = document.querySelector('#winner') as HTMLParagraphElement
// 当前玩家
let currentPlayer: Player
// 重新开始按钮
let restart = document.querySelector('#restart') as HTMLButtonElement
// 判赢数组
let winsArr = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]
]
// 记录已下棋的次数
let steps: number
// 给重新开始绑定点击事件
restart.addEventListener('click', startGame)
// 调用函数初始化,开始游戏
startGame()
function startGame() {
// 隐藏获胜信息
message.style.display = 'none'
// 重置下棋次数
steps = 0
// 重置默认玩家为x
currentPlayer = Player.X
// 重置下棋提示为x
gameBord.classList.remove(Player.X, Player.O)
gameBord.classList.add(Player.X)
// 清空棋盘
cells.forEach(function(item) {
let cell = item as HTMLDivElement
cell.classList.remove(Player.X, Player.O)
// 移除单元格点击事件,重新给单元格绑定点击事件
cell.removeEventListener('click', clickCell)
cell.addEventListener('click', clickCell, { once: true})
})
}
// cells.forEach(function (item) {
// // console.log(item)
// let cell = item as HTMLDivElement
// cell.addEventListener('click', clickCell, { once: true })
// })
// 棋盘中单元格的click事件处理程序
function clickCell(event: MouseEvent) {
let target = event.target as HTMLDivElement
target.classList.add(currentPlayer)
steps++
// 调用判赢函数是否获胜
let isWin = checkWin(currentPlayer)
if (isWin) {
message.style.display = 'block'
winner.innerText = currentPlayer + '赢了'
// 因为游戏已经结束,所以此处直接return,来刻意阻止后续代码执行
return
}
// 判断平局
if(steps === 9) {
message.style.display = 'block'
winner.innerText = '平局'
// 因为游戏已经结束,所以此处直接return,来刻意阻止后续代码执行
return
}
// 根据当前玩家,得到另外一个玩家
currentPlayer = currentPlayer === Player.X ? Player.O : Player.X
// 处理下一步提示:移除游戏面板中的x和o类名,添加下一个玩家类名
gameBord.classList.remove(Player.X, Player.O)
gameBord.classList.add(currentPlayer)
}
// 封装判赢函数
function checkWin(player: Player) {
return winsArr.some(function (item) {
// 判断这三个单元格元素是否同时包含当前玩家的类名
if (
// cells[item[0]].classList.contains(player) &&
// cells[item[1]].classList.contains(player) &&
// cells[item[2]].classList.contains(player)
hasClass(cells[item[0]], player) &&
hasClass(cells[item[1]], player) &&
hasClass(cells[item[2]], player)
) {
return true
}
return false
})
}
// 判断单元格元素是否同时包含当前玩家的类名函数
function hasClass(el: Element, name: string ) {
return el.classList.contains(name)
}
gameBord.classList.add(Player.X)
// 清空棋盘
cells.forEach(function(item) {
let cell = item as HTMLDivElement
cell.classList.remove(Player.X, Player.O)
// 移除单元格点击事件,重新给单元格绑定点击事件
cell.removeEventListener('click', clickCell)
cell.addEventListener('click', clickCell, { once: true})
})
}
// cells.forEach(function (item) {
// // console.log(item)
// let cell = item as HTMLDivElement
// cell.addEventListener('click', clickCell, { once: true })
// })
// 棋盘中单元格的click事件处理程序
function clickCell(event: MouseEvent) {
let target = event.target as HTMLDivElement
target.classList.add(currentPlayer)
steps++
// 调用判赢函数是否获胜
let isWin = checkWin(currentPlayer)
if (isWin) {
message.style.display = 'block'
winner.innerText = currentPlayer + '赢了'
// 因为游戏已经结束,所以此处直接return,来刻意阻止后续代码执行
return
}
// 判断平局
if(steps === 9) {
message.style.display = 'block'
winner.innerText = '平局'
// 因为游戏已经结束,所以此处直接return,来刻意阻止后续代码执行
return
}
// 根据当前玩家,得到另外一个玩家
currentPlayer = currentPlayer === Player.X ? Player.O : Player.X
// 处理下一步提示:移除游戏面板中的x和o类名,添加下一个玩家类名
gameBord.classList.remove(Player.X, Player.O)
gameBord.classList.add(currentPlayer)
}
// 封装判赢函数
function checkWin(player: Player) {
return winsArr.some(function (item) {
// 判断这三个单元格元素是否同时包含当前玩家的类名
if (
// cells[item[0]].classList.contains(player) &&
// cells[item[1]].classList.contains(player) &&
// cells[item[2]].classList.contains(player)
hasClass(cells[item[0]], player) &&
hasClass(cells[item[1]], player) &&
hasClass(cells[item[2]], player)
) {
return true
}
return false
})
}
// 判断单元格元素是否同时包含当前玩家的类名函数
function hasClass(el: Element, name: string ) {
return el.classList.contains(name)
}