JavaScript基础进阶
[Udemy课程|4~7章节内容]
[课程评价-⭐⭐⭐⭐❎]
课程带的几个项目十分有趣,项目中可以学习到一些有用的开发知识,如果第一次接触JavaScript但熟练其他语言可以跳过1~3直接开始项目。
像开发人员一样来调试
详细见这节课:056 Installing Node.js and Setting Up a Dev Environment
这节课将告诉我们如何用node.js+live-server模块来搭建一个小型服务器使我们可以动态调试我们的html、css和js文档。
-
在VScode中安装Live Server扩展。
-
在电脑上安装Node.js。
-
在终端上安装live-server包。
-
如果是Windows系统可能会在VScode上遇到一些问题,系统为了安全可能不会让你使用包中的脚本,这个时候需要以管理员权限打开Windows的PowerShell,并输入
Set-ExecutionPolicy -ExecutionPolicy UNRESTRICTED
才可以使用。
或许你在你的VScode上安装过Code Runner,Code Runner并不能在Node.js的环境上直接运行含有DOM操作的代码,VScode会告诉你document或者window没有被定义,那是因为Node.js这个环境不包含DOM和BOM,这些操作只有在浏览器上才可以运行,而Code Runner的配置就是Node.js,所以想运行你的js代码的话还是老老实实使用浏览器吧。
文档对象的基础方法
//选择器
document.querySelector('.message'); //选择了.message类
document.querySelector('#message'); //选择了id为message
console.log(document.querySelector('.label-score').textContent);
//可以调出.label-score类的控件的文本
DOM
DOM代表的是文档对象模型,是HTML文档的结构化表示,DOM允许我们使用JavaScript访问HTML元素和样式以操作它们。
我们可以利用DOM改变文本、HTML属性甚至是CSS样式。
HTML文档对象被抽象为树结构,一般在HTML加载的时候由浏览器生成。
!DOCUMENT
<html>
<head>
</head>
<body>
</body>
</html>
DOM总是以文档对象开头,这个文档对象作为DOM的入口点。
DOM其实是WebAPI的一部分,WebAPI就像库一样使我们可以访问API应用程序编程接口,所以DOM实际是用JavaScript编写的一个库,可以供我们使用。
DOM操作
对应课程第五章内容。
基本的取值改值操作:
//这里我们使用查询选择器选择了.message类的元素并更改了其文本为'Correct Number!'
document.querySelector('.message').textContent = 'Correct Number!';
//同理
document.querySelector('.number').textContent = 13;
document.querySelector('.score').textContent = 20;
//这里我们更改了input.guess的值并输出了它
document.querySelector('.guess').value = 23;
console.log(document.querySelector('.guess').value);
按钮被事件监视器观察并触发事件:
//事件监视器可以监视用户操作,比如:键盘按键,鼠标点击、移动...,等待事件的发生并执行相应的语句
document.querySelector('.check').addEventListener('click', function () {
const guess = Number(document.querySelector('.guess').value);
console.log(guess);
if (!guess) {
document.querySelector('.message').textContent = 'No number!';
}
});
//我们使用查询选择器选择了.btn .check并为它添加了一个事件监视器,当触发'click'事件(鼠标点击)时执行后面的函数,函数中我们输出了.guess中的值,当然我们前面学习过函数,这里我们也可以在外面定义函数然后添加在事件监视器中,因为从输入框得到的值是字符串,我们将其转换为数字类型,后面增加一个判断!guess,也就是当没有值的时候会得到0,!0为真执行下面语句。
通过DOM更改CSS样式:
//功能:当玩家猜对数字时改变背景颜色为绿色
document.querySelector('body').style.backgroundColor = '#60b347';
//使用查询选择器选择body元素,设置其style中的backgroundColor属性
//在JavaScript中属性都是使用的驼峰命名法
//当样式生效时,样式会直接添加到元素的内联样式中,不会影响到CSS文件的样式
基本完成了代码设计:
'use strict';
let secretNumber = Math.trunc(Math.random() * 20) + 1;
let score = 20;
let highscore = 0;
document.querySelector('.check').addEventListener('click', function () {
const guess = Number(document.querySelector('.guess').value);
console.log(guess);
if (!guess) {
//玩家没有输入值
document.querySelector('.message').textContent = 'No number!';
} else if (guess === secretNumber) {
//玩家赢得了游戏
document.querySelector('.message').textContent = 'Correct Number!';
document.querySelector('.number').textContent = secretNumber;
document.querySelector('body').style.backgroundColor = '#60b347';
document.querySelector('.number').style.width = '30rem';
if (score > highscore) {
highscore = score;
}
document.querySelector('.highscore').textContent = highscore;
} else if (guess > secretNumber) {
//玩家输入的值大于要猜的数字
if (score > 1) {
//分数大于1才可以继续进行游戏否则玩家就输掉了游戏
document.querySelector('.message').textContent = 'Too high!';
score--;
document.querySelector('.score').textContent = score;
} else {
document.querySelector('.message').textContent = 'You lose the game!';
document.querySelector('.score').textContent = 0;
}
} else if (guess < secretNumber) {
//玩家输入的值小于要猜的数字
if (score > 1) {
//分数大于1才可以继续进行游戏否则玩家就输掉了游戏
document.querySelector('.message').textContent = 'Too low!';
score--;
document.querySelector('.score').textContent = score;
} else {
document.querySelector('.message').textContent = 'You lose the game!';
document.querySelector('.score').textContent = 0;
}
}
});
document.querySelector('.again').addEventListener('click', function () {
//文本、样式重置
document.querySelector('.guess').value = '';
document.querySelector('.message').textContent = 'Start guessing...';
document.querySelector('.number').textContent = '?';
document.querySelector('body').style.backgroundColor = '#222';
document.querySelector('.number').style.width = '15rem';
//分数重置
score = 20;
document.querySelector('.score').textContent = score;
//秘密数字重置
secretNumber = Math.trunc(Math.random() * 20) + 1;
});
消除代码耦合(重构):
'use strict';
let secretNumber = Math.trunc(Math.random() * 20) + 1;
let score = 20;
let highscore = 0;
const displayMessage = function (message) {
document.querySelector('.message').textContent = message;
};
//这里只是演示了重构计算,但其实代码可以更加简洁
const displayItem = function (item, content) {
document.querySelector(item).textContent = content;
};
document.querySelector('.check').addEventListener('click', function () {
const guess = Number(document.querySelector('.guess').value);
console.log(guess);
if (!guess) {
//玩家没有输入值
displayMessage('No number!');
} else if (guess === secretNumber) {
//玩家赢得了游戏
displayMessage('Correct Number!');
displayItem('.number', secretNumber);
//上面两条代码使用函数降低了代码耦合度
document.querySelector('body').style.backgroundColor = '#60b347';
document.querySelector('.number').style.width = '30rem';
if (score > highscore) {
highscore = score;
}
document.querySelector('.highscore').textContent = highscore;
} else if (guess !== secretNumber) {
//玩家输入的值是否不同于秘密数字
if (score > 1) {
//分数大于1才可以继续进行游戏否则玩家就输掉了游戏
displayMessage(guess > secretNumber ? 'Too high!' : 'Too low!');
//判断是设置为Too high!还是Too low!,因为之前的代码在这里只有文本不一样
score--;
document.querySelector('.score').textContent = score;
} else {
displayMessage('You lose the game!');
document.querySelector('.score').textContent = 0;
}
}
});
document.querySelector('.again').addEventListener('click', function () {
//文本、样式重置
document.querySelector('.guess').value = '';
displayMessage('Start guessing...');
document.querySelector('.number').textContent = '?';
document.querySelector('body').style.backgroundColor = '#222';
document.querySelector('.number').style.width = '15rem';
//分数重置
score = 20;
document.querySelector('.score').textContent = score;
//秘密数字重置
secretNumber = Math.trunc(Math.random() * 20) + 1;
});
重构大大增加了代码的可读性,特别是在项目代码非常多的情况下,有序规则的重构代码可以大大提高调试程序的速度,所以在完成代码的功能性后,有时间记得要重构你的代码。
UI组件、模态窗口
课程:080 Working With Classes
当我们想要选择多个同类的元素时,我们会发现querySelector()方法只作用于第一个元素,所以我们引出第二个选择器querySelectorAll()查询选择全部。
const btnOpenModal = document.querySelectorAll('.show-modal');
它会生产一个NodeList变量,它类似于一个数组,存放着查询到的所有元素,当我们想要对这里面的所有元素进行操作时,我们需要对NodeList进行遍历。
for (let i = 0; i < btnOpenModal.length; i++) {
btnOpenModal[i].addEventListener('click', function () {
document.querySelector('.hidden').style.display = 'block';
});
}
我们也可以通过classList来删除modal和overlay元素的类hidden以显示这些元素。
for (let i = 0; i < btnOpenModal.length; i++) {
btnOpenModal[i].addEventListener('click', function () {
modal.classList.remove('hidden'); //方法内可以添加多个类以逗号分隔删除多个类
overlay.classList.remove('hidden');
});
}
通过classList当然也可以添加类。
btnCloseModal.addEventListener('click', function () {
modal.classList.add('hidden'); //一样可以添加多个
overlay.classList.add('hidden');
});
重构后的完整代码:
'use strict';
const modal = document.querySelector('.modal');
const overlay = document.querySelector('.overlay');
const btnCloseModal = document.querySelector('.close-modal');
const btnOpenModal = document.querySelectorAll('.show-modal');
const closeModal = function () {
modal.classList.add('hidden');
overlay.classList.add('hidden');
};
const openModal = function () {
modal.classList.remove('hidden');
overlay.classList.remove('hidden');
};
for (let i = 0; i < btnOpenModal.length; i++) {
btnOpenModal[i].addEventListener('click', openModal);
}
btnCloseModal.addEventListener('click', closeModal);
//这里的函数不能在后面加上括号,加上括号后JavaScript读到这条语句函数会在这里立即执行,而我们需要的是它在这里被点击时触发
overlay.addEventListener('click', closeModal);
按键触发
上面的代码我们通过鼠标点击的形式进行了触发,这里我们将尝试用按键触发。
document.addEventListener('keydown', closeModal);
//这里我们没有像之前一样使用查询选择器选择一个元素并添加事件监听,因为我们希望在整个文档中监听事件,在事件监听器中我们可以使用键盘操作的三种参数,'keydown'、'keyup'、'keypress',但这里我们按任何键都会触发
现在我们想要在按Esc时触发该事件:
document.addEventListener('keydown', function (e) {
//当按下按键我们在这里为函数传入了一个形式参数e,在e中包含了事件对象,事件对象中包含了按键信息等
if (e.key === 'Escape') closeModal();
});
再次提示一遍:函数后带括号是函数调用,直接执行函数;不带括号是绑定事件,事件触发再执行。
课程原文增加了一个条件判断,使操作只在modal类元素中不包含hidden类时才会执行,这增加了一定的安全性,但考虑人机交互我认为这种操作没有必要。
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
if (!modal.classList.contains('hidden')) {
closeModal();
}
}
});
Pig Game项目练习
课程:082 PROJECT #3_ Pig Game
这个项目作者创建了一个网站用于演示:https://pig-game-v2.netlify.app/
游戏机制:使用ROLL DICE骰一个点数如果是1则持有分数清零,如果是别的数就累计在持有分数中,使用HOLD按钮切换给对方游戏并将持有分数加载玩家当前游戏分数中,第一个拿到100分的玩家胜利。
程序逻辑:
游戏中按钮的主要行为:
User rolls dice(玩家骰色子):
生成一个随机的点数diceRoll
显示点数
if(diceRoll === 1){
切换玩家
}else{
将diceRoll添加到持有分数中currentScore
显示新的currentScore
}
User holds score(玩家保持分数):
添加持有分数currentScore到总分数中(totalScore)
if(totalScore>=100){
当前玩家胜利
}else{
切换玩家
}
User resets game(玩家重置游戏):
设置所有分数为0
设置玩家1作为开始玩家
这里我建议将全部工程自己完成后再观看教学视频,下面有可能需要的函数与方法:
document.querySelector('#id'); //或许你已经忘记了,但是查询选择器是可以用来查询id的
document.getElementById('id'); //与上面的方法相同,但是这个只能用id查询
document.querySelector('#id').src = 'dice-1.png'; //src属性是直接在对象中的
//如果要是使用player--active需要在roll到1时更新选择器选择的元素,不然还是之前的元素
这是我自己试出来的代码,可以不看,跟Jonas写出来的比就是一坨:
'use strict';
let score = 0;
//定义了score用来存放总分
let currentScore = 0;
//定义了currentScore用来存放持有分数
let activePlayer = 0;
//定义了当前活动玩家
let activePlayerCurrentScoreEl = document.querySelector(
'.player--active .current-score'
);
//定义了当前活动的玩家的持有分数
let activePlayerScoreEl = document.querySelector('.player--active .score');
//定义了当前活动的玩家的总分
const diceEl = document.querySelector('.dice');
//骰子的元素
const score0El = document.querySelector('#score--0');
//玩家0的总分的元素
const score1El = document.querySelector('#score--1');
//玩家1的总分的元素
const currentScore0El = document.querySelector('#current--0');
//玩家0的持有分数的元素
const currentScore1El = document.querySelector('#current--1');
//玩家1的持有分数的元素
const player0El = document.querySelector('.player--0');
//玩家0的盒子
const player1El = document.querySelector('.player--1');
//玩家1的盒子
const btnNew = document.querySelector('.btn--new');
//NEW GAME按钮
const btnRoll = document.querySelector('.btn--roll');
//ROLL DICE按钮
const btnHold = document.querySelector('.btn--hold');
//HOLD按钮
const changePlayer = function () {
//改变玩家的函数
if (!activePlayer) {
//条件判断当前活动玩家是谁
//player0
score = 0; //每次切换玩家总分归零,因为我们的总分数据存在每个玩家的总分元素中
currentScore = 0; //每次玩家切换手持分数归零,这是游戏的规则
currentScore0El.textContent = currentScore; //将手持分数的元素文本设置为零
player0El.classList.remove('player--active');
player1El.classList.add('player--active'); //上面两条语句就是将活动玩家的类切换,背景颜色会有一个变化
activePlayerCurrentScoreEl = document.querySelector(
'.player--active .current-score'
); //更新了当前持有分数的元素
activePlayerScoreEl = document.querySelector('.player--active .score'); //更新了当前玩家的总分
score = Number(score1El.textContent); //总分替换为玩家2的总分
} else {
//player1
score = 0;
currentScore = 0;
currentScore1El.textContent = currentScore;
player1El.classList.remove('player--active');
player0El.classList.add('player--active');
activePlayerCurrentScoreEl = document.querySelector(
'.player--active .current-score'
);
activePlayerScoreEl = document.querySelector('.player--active .score');
score = Number(score0El.textContent); //总分替换为玩家1的总分
}
activePlayer = !activePlayer; //切换当前玩家的变量,方便下次转换
};
const resetAll = function () {
//重置所有元素的函数
activePlayer = 0; //重置当前玩家变量,方便下次转换
document
.querySelector('.player--active')
.classList.remove('player--winner', 'player--active'); //重置玩家胜利和当前玩家的样式
player0El.classList.add('player--active'); //将1号玩家的背景设置为活动玩家背景
btnRoll.disabled = false;
btnHold.disabled = false; //按钮启用
score = 0; //玩家总分重置为0
currentScore = 0; //玩家持有分数重置为0
score0El.textContent = score;
score1El.textContent = score;
currentScore0El.textContent = currentScore;
currentScore1El.textContent = currentScore; //上面四条元素文本内容重置
diceEl.classList.add('hidden'); //隐藏骰子,因为骰子只在骰出第一个时出现
};
const rollDice = function () {
//骰色子的函数
diceEl.classList.remove('hidden'); //启用骰子,取消骰子的隐藏
let diceRandom = Math.trunc(Math.random() * 6) + 1; //定义一个1-6的随机数diceRandom
diceEl.src = `dice-${diceRandom}.png`; //将骰子的图片设置为对应点数的图片
if (diceRandom === 1) {
//条件判断,骰到点数1时切换玩家
changePlayer();
} else {
//否则将当前持有分数累计,分数多吗?贪来的。
currentScore = currentScore + diceRandom;
activePlayerCurrentScoreEl.textContent = currentScore; //显示当前玩家的持有分数
}
};
const holdScore = function () {
//保持分数的函数
score = score + currentScore; //保持分数按钮被按时持有分数加入总分中
activePlayerScoreEl.textContent = score; //显示当前玩家的总分
if (score >= 100) {
//当分数大于100时就赢得游戏的胜利
document.querySelector('.player--active').classList.add('player--winner'); //为当前玩家的元素添加一个胜利的背景样式
btnRoll.disabled = true;
btnHold.disabled = true; //使按钮失活,因为游戏已经决出胜负了
} else {
//否则切换玩家,你只是保持了分数,但分数不到100还不能胜利
changePlayer();
}
};
resetAll(); //游戏开局时重置所有样式
btnNew.addEventListener('click', resetAll); //给按钮添加功能
btnRoll.addEventListener('click', rollDice);
btnHold.addEventListener('click', holdScore);
这是Jonas的代码:
'use strict';
// Selecting elements
//元素的各种定义,全部整齐地放在变量中
const player0El = document.querySelector('.player--0');
const player1El = document.querySelector('.player--1');
const score0El = document.querySelector('#score--0');
const score1El = document.getElementById('score--1');
const current0El = document.getElementById('current--0');
const current1El = document.getElementById('current--1');
const diceEl = document.querySelector('.dice');
const btnNew = document.querySelector('.btn--new');
const btnRoll = document.querySelector('.btn--roll');
const btnHold = document.querySelector('.btn--hold');
let scores, currentScore, activePlayer, playing;
// Starting conditions
const init = function () {
scores = [0, 0]; //定义了一个分数的数组用于存放各个玩家的分数
currentScore = 0; //定义了一个持有分数,因为每次切换会归零所以就定义一个
activePlayer = 0; //定义当前玩家
playing = true; //定义一个布尔变量来判断游戏的进行
score0El.textContent = 0; //重置总分元素
score1El.textContent = 0;
current0El.textContent = 0; //重置持有分数元素
current1El.textContent = 0;
diceEl.classList.add('hidden'); //将骰子隐藏,因为骰子只在roll点的时候启用
player0El.classList.remove('player--winner');
player1El.classList.remove('player--winner'); //删除每个玩家的胜利背景样式,为了重置
player0El.classList.add('player--active');
player1El.classList.remove('player--active'); //删除每个玩家的活动背景样式,为了重置
};
init(); //重置所有元素
const switchPlayer = function () { //一个切换玩家的函数
document.getElementById(`current--${activePlayer}`).textContent = 0;
//这里将当前玩家的持有分数的元素设置为0(原来在这里面可以用自定义字符串吗)
currentScore = 0; //将持有分数设置为0
activePlayer = activePlayer === 0 ? 1 : 0;
//这里用了一个三相表达式,如果当前玩家是玩家1(0)就切换为玩家2(1),如果当前玩家是玩家2(1)就切换为玩家1(0),并将结果存放到当前玩家变量中
player0El.classList.toggle('player--active');
//如果玩家1的元素有player--active样式就删除,如果没有就添加(当时不知道这个方法)
player1El.classList.toggle('player--active');
//一个道理
};
// Rolling dice functionality roll点的按钮
btnRoll.addEventListener('click', function () {
if (playing) {
//判断游戏是否还在继续,如果不是则下面的语句不会被应用,按钮就将不会启用
// 1. Generating a random dice roll 生成一个随机数
const dice = Math.trunc(Math.random() * 6) + 1;
// 2. Display dice 显示随机的点数的骰子图片
diceEl.classList.remove('hidden');
diceEl.src = `dice-${dice}.png`;
// 3. Check for rolled 1 如果roll到1
if (dice !== 1) {
// Add dice to current score 添加骰子的点数到持有分数中
currentScore += dice;
document.getElementById(
`current--${activePlayer}`
).textContent = currentScore;
} else {
// Switch to next player 否则切换到下一个玩家
switchPlayer();
}
}
});
//保持分数的按钮
btnHold.addEventListener('click', function () {
if (playing) {
// 1. Add current score to active player's score 添加持有分数到当前玩游戏的玩家的总分中
scores[activePlayer] += currentScore;
// scores[1] = scores[1] + currentScore 他自己解释了
document.getElementById(`score--${activePlayer}`).textContent =
scores[activePlayer];
// 2. Check if player's score is >= 100 如果玩家分数大于等于100
if (scores[activePlayer] >= 100) {
// Finish the game 结束游戏
playing = false;
diceEl.classList.add('hidden'); //骰子隐藏
document
.querySelector(`.player--${activePlayer}`)
.classList.add('player--winner'); //当前玩家的胜利背景的添加
document
.querySelector(`.player--${activePlayer}`)
.classList.remove('player--active'); //当前玩家的活动背景的隐藏
} else {
// Switch to the next player 切换下一个玩家
switchPlayer();
}
}
});
btnNew.addEventListener('click', init); //重置按钮