TypeScript学习笔记

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中运行

  1. TS代码-》JS代码

    tsc hello.ts
    
  2. 执行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,变量的使用

  1. 声明变量并指定类型

    let age: number;
    
    • let是TS的关键字,用来声明变量
    • age: 自定义变量名称
    • : number 用来指定变量age为数值变量
  2. 给变量赋值

    age = 18
    
  3. 简化操作

    let age: number = 18
    

3,类型注解

类型注解:是一种为变量添加类型约束的方式

4,变量的命名规则

数字,字母,下划线,美元符号,不能以数字开头

5,数据类型概述

  1. 原始类型(基本数据类型)

    • number

      包含整数值和浮点型(小数)值

    • string

      由零个或者多个字符串联而成的,用来表示文本信息

    • boolean

      用来表示真或假(true或false)

    • undefined

      表示声明但未赋值的变量值

      let u: undefined = undefined
      
    • null

      let n: null = null
      
  2. 对象类型(复杂数据类型)

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. 初始化语句:声明计数器变量用来记录循环次数(执行一次)
  2. 判断条件:判断循环次数是否达到目标次数
  3. 计数器更新:完成一次循环让计数器数量加1
  4. 循环体:循环代码,也就是要重复做的事情
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])
}100200300

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

形参和实参

  1. 形参:声明函数时指定的参数,放在声明函数的小括号

    function sing(songName: string) {}
    
    • 语法: 形参名称:类型注解,类似于变量声明,但是没有赋值
    • 作用:指定函数可接收的数据
  2. 实参:调用函数时传入的参数,放在调用函数的小括号中

    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))5161112

注意:如果没有指定函数的返回值,那么,函数返回值的默认类型为void(空,什么都没有)

指定返回值类型

function fn(): 类型注解 {
	return 返回值
}
  • 在函数体中,使用return关键字来返回函数执行结果
  • 返回值必须符合返回值类型的类型要求,否则会报错

基本使用

使用变量接收函数返回值

let result: 类型注解 = fn()

变量result的类型与函数返回值类型相同

return的说明

  1. 将函数内部的计算结果返回。
  2. 终止函数代码执行,即:return后面的代码不会执行
  3. return不能在函数外使用
  4. 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的类型推论

由于类型推论的存在,有些地方,类型注解可以省略不写。

  1. 声明变量并初始化时
  2. 决定函数返回值时
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然后再运行

  1. 使用命令tsc index.ts将TS文件转化为JS文件。

  2. 在页面中,使用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操作:

  1. 获取DOM元素
  2. 设置样式
  3. 设置内容
  4. 绑定(解除)事件
1,获取元素

常用方法

  1. 获取一个DOM元素

    document.querySelector(selector)
    
    • document对象:文档对象(整个对象),是操作页面内容的入口对象
    • selector参数:是一个CSS选择器(标签、类、ID选择器等)
    • 作用:查询(获取)与选择器参数匹配的DOM元素,但是只能获取到第一个。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  1. 类型断言

    因为无法根据id来确定元素的类型,所以querySelector()方法返回了一个宽泛的类型:元素(Element)类型。

    Element类型只包含所有元素共有的属性和方法(比如id属性)

    使用类型断言,来手动指定更加具体的类型。

    let title = document.querySelector('.c1') as HTMLParagraphElement
    

    通过console.dir()打印DOM对象,来查看该元素的类型
    在这里插入图片描述

  2. 获取多个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,实例(下棋游戏)

在这里插入图片描述

image-20201013112617576.png

在这里插入图片描述

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(默认)

  1. 获取到所有的单元格列表
  2. 遍历单元格列表,给每一个单元格添加点击事件
  3. 给当前被点击的单元格添加类名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')
  })
})

优化:

  1. 防止单元格重复点击,在添加事件时,使用once属性,让单元格只能被点击一次
  2. 使用函数声明形式的事件处理程序(代码多了后,代码结构会更清晰)
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的交替运行

  1. 创建一个存储当前玩家的变量(currentPlayer),默认值为:X。
  2. 将添加给单元格时写死的类名X,替换为变量(currentPlayer)的值
  3. 切换到另一个玩家:在添加类名(下棋完成一步)后,根据当前玩家,得到另外一个玩家。
  4. 处理下一步提示:移除游戏面板中的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)

  1. 创建字符串枚举(Player),提供x和o两个成员
  2. 讲成员x的值设置为:‘x’(类名);将成员o的值设置为‘o’(类名)。
  3. 将变量(currentPlayer)的类型设置为Player枚举类型,默认值为Player.x
  4. 将所有用到的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)属性和索引

伪数组的操作:

  1. 通过索引获取元素
  2. 使用for循环遍历(推荐使用forEach方法)

封装判赢函数:

说明:判赢,就是在判断当前玩家下棋后是否获胜

  1. 声明函数(checkWin),指定参数(player),类型注解为:Player枚举
  2. 指定返回值:现在函数中写死返回true或false。
  3. 在给单元格添加类名后(下棋后),调用函数checkWin,拿到函数返回值
  4. 判断函数返回值是否为true,如果是,说明当前玩家获胜了。

思路:

遍历判赢数组,分别判断每种情况对应的3个单元格元素,是否同时包含当前玩家的类名。

使用数组的some方法:

  1. 遍历数组时可终止
  2. 方法返回值为true或false

判赢的实际步骤:

  1. 使用some方法遍历数组,并将some方法的返回值作为判赢函数的返回结果
  2. 在some方法的回调函数中,获取到每种获胜情况对应的3个单元格元素
  3. 判断这三个单元格元素是否同时包含当前玩家的类名
  4. 如果包含(玩家获胜),就在回调函数中返回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,则为平局

  1. 创建变量steps,默认值为0
  2. 在玩家下棋后,让steps加1
  3. 在判赢的代码后面,判断steps是否等于9
  4. 如果等于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,展示获胜信息

效果:在获胜或平局时,展示相应信息

  1. 获取到与获胜信息相关得两个DOM元素:1#message 2#winner
  2. 显示获胜信息面板(通过style属性实现)
  3. 展示获胜信息:如果获胜,展示“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,重新开始

效果:点击重新开始按钮,重新开始下棋游戏

说明:重新开始游戏,实际就是重置游戏中的所有数据,恢复到初始状态

  1. 获取到重新开始按钮(#restart),并绑定点击事件
  2. 在点击事件中,重置游戏数据
  3. 隐藏获胜信息,清空棋盘,移除单元格点击事件,重新给单元格绑定点击事件
  4. 重置下棋次数,重置默认玩家为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)
}

优化重新游戏功能:

将第一次游戏,也看作是“重新开始游戏”,就可以去掉第一次游戏时重复的初始化操作了。

  1. 将重新开始按钮的事件处理程序修改为:函数声明形式(startGame)
  2. 直接调用函数(startGame),来开始游戏
  3. 移除变量steps、currentPlayer的默认值,并添加明确的类型注解
  4. 移除给单元格绑定事件的代码
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)
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值