今天看面向对象的应用做一个贪吃蛇小游戏,结构很简单,index.html , index.css ,index,js
端详了一下js代码,感觉有点奇怪 代码中若干个这样子的函数
(function (window, undefined) {
// 模块内的代码
})(window, undefined);
1. 代码风格
为何若干个匿名函数?
2. 函数参数
为何匿名函数参数为undefined?
带着这两个疑问请教Google
第一个问题
这是 ES6 之前的老写法,是早期 JavaScript 模块化的常用方案,虽然现在有了 ES6 模块、CommonJS 等更现代的模块化方案,但这种方式仍然是一种有效的封装方式。
看一下这么写的优点
1. 安全性
-
变量不会泄露到全局作用域
-
防止外部代码意外修改内部变量
2. 模块化
-
每个功能都是独立的模块
-
通过 window 对象只暴露必要的接口
3. 可维护性
-
代码结构清晰
-
模块之间的依赖关系明确
4. 性能优化
-
局部变量查找比全局变量快
-
压缩代码时可以安全地压缩局部变量名
第二个问题
在 IIFE 中传入 undefined 参数是一个历史遗留的安全性考虑。
- undefined 的特殊性
在老版本的 JavaScript 中(ES5 之前),undefined 是可以被重写的:
undefined = "我被改变了"; // 可以重写全局的 undefined
- 使用参数形式的保护
(function (window, undefined) {
// 这里的 undefined 一定是真正的 undefined
// 因为没有传第二个参数,所以 undefined 参数的值就是 undefined
})(window);
- 为什么这样做是安全的
即使外部的 undefined 被修改了:
window.undefined = "被改变了";
(function (window, undefined) {
console.log(undefined); // 仍然是真正的 undefined
})(window);
- 函数参数如果没有传值,默认就是 undefined
- 参数作用域是本地的,不受外部影响
- 无法从外部修改函数内的参数值
- 额外好处
代码压缩:
// 原代码
(function (window, undefined) {
// 使用很多次 undefined
})(window);
// 压缩后
(function(w,u){
// u 代替 undefined,节省字节
})(window);
现代开发中
在现代 JavaScript 中(ES5 及以后),undefined 已经不可被重写了,所以这种写法主要是历史遗留和兼容性考虑。但它仍然提供了:
-
更好的代码压缩机会
-
更快的 undefined 值查找(局部变量比全局变量快)
-
更好的代码可读性
源码附上
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>贪吃蛇</title>
<link rel="stylesheet" href="css/index.css" />
</head>
<body>
<header><span>游戏说明:←↑→↓控制方向,空格暂停</span><button id="btn">重新开始</button></header>
<div class="map" id="map"></div>
<script src="js/index.js"></script>
</body>
</html>
index.css
* {
margin: 0;
padding: 0;
}
header{
width: 100%;
height: 100px;
background-color: #fff;
text-align: center;
}
header span{
width: 800px;
height: 100px;
line-height: 100px;
}
header button{
margin-left: 50px;
padding: 10px 20px;
border-radius: 10px;
background-color: rgb(188, 228, 241);
font-size: 16px;
cursor: pointer;
}
.map {
position: relative;
width: 800px;
height: 600px;
margin: 0 auto;
background-color: #eee;
}
index.js
// tool;
(function (window, undefined) {
const Tools = {
getRandom: (min, max) => {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
},
getColor: () => {
var r = this.getRandom(0, 255);
var g = this.getRandom(0, 255);
var b = this.getRandom(0, 255);
return 'rgb(' + r + ',' + g + ',' + b + ')';
},
};
window.Tools = Tools;
})(window, undefined);
// food
(function (window, undefined) {
function Food(option) {
option = option instanceof Object ? option : {};
this.width = option.width || 20;
this.height = option.height || 20;
this.x = option.x || 0;
this.y = option.y || 0;
this.color = option.color || 'green';
this.elements = [];
}
const position = 'absolute';
Food.prototype.render = function (map) {
let ele = document.createElement('div');
this.x = Tools.getRandom(0, map.clientWidth / this.width - 1) * this.width;
this.y = Tools.getRandom(0, map.clientHeight / this.height - 1) * this.height;
ele.style.width = this.width + 'px';
ele.style.height = this.height + 'px';
ele.style.backgroundColor = this.color;
ele.style.position = position;
ele.style.top = this.y + 'px';
ele.style.left = this.x + 'px';
map.appendChild(ele);
this.elements.push(ele);
};
Food.prototype.remove = function (map, i) {
map.removeChild(this.elements[i]);
this.elements.splice(i, 1);
};
window.Food = Food;
})(window, undefined);
// snake
(function (window, undefined) {
function Snake(option) {
option = option instanceof Object ? option : {};
this.width = option.width || 20;
this.height = option.height || 20;
this.body = [
{
x: 2,
y: 0,
color: 'red',
},
{
x: 1,
y: 0,
color: 'blue',
},
{
x: 0,
y: 0,
color: 'blue',
},
];
this.direction = 'right';
this.elements = [];
}
const position = 'absolute';
Snake.prototype.render = function (map) {
for (let i = 0, len = this.body.length; i < len; i++) {
const piece = this.body[i];
const ele = document.createElement('div');
ele.style.width = this.width + 'px';
ele.style.height = this.height + 'px';
ele.style.left = this.width * piece.x + 'px';
ele.style.top = this.height * piece.y + 'px';
ele.style.position = position;
ele.style.backgroundColor = piece.color;
map.appendChild(ele);
this.elements.push(ele);
}
};
Snake.prototype.move = function () {
for (let i = this.body.length - 1; i > 0; i--) {
this.body[i].x = this.body[i - 1].x;
this.body[i].y = this.body[i - 1].y;
}
const head = this.body[0];
switch (this.direction) {
case 'right':
head.x += 1;
break;
case 'left':
head.x -= 1;
break;
case 'up':
head.y -= 1;
break;
case 'down':
head.y += 1;
break;
}
};
Snake.prototype.remove = function (map) {
for (var i = this.elements.length - 1; i >= 0; i--) {
map.removeChild(this.elements[i]);
}
this.elements = [];
};
window.Snake = Snake;
})(window, undefined);
// game
(function (window, undefined) {
let that;
function Game(map) {
this.food = new Food();
this.snake = new Snake();
this.map = map;
that = this;
}
Game.prototype.start = function () {
const num = prompt('请输入食物数量(1~10)\n点击任意按钮开始游戏');
const n = parseInt(Number(num));
if (n === NaN || n === 0) {
this.food.render(this.map);
} else if (n > 10) {
for (i = 0; i < 10; i++) {
this.food.render(this.map);
}
} else {
for (i = 0; i < n; i++) {
this.food.render(this.map);
}
}
this.snake.render(this.map);
runSnake();
bindKey();
};
let timer;
function runSnake() {
timer = setInterval(function () {
that.snake.move();
that.snake.remove(that.map);
that.snake.render(that.map);
const hX = that.snake.width * that.snake.body[0].x;
const hY = that.snake.height * that.snake.body[0].y;
for (let i = 0; i < that.food.elements.length; i++) {
if (that.food.elements[i].offsetLeft == hX && that.food.elements[i].offsetTop == hY) {
that.food.remove(that.map, i);
that.food.render(that.map);
const body = that.snake.body;
const last = body[body.length - 1];
that.snake.body.push({
x: last.x,
y: last.y,
color: last.color,
});
}
}
const maxX = that.map.offsetWidth / that.snake.width;
const maxY = that.map.offsetHeight / that.snake.height;
const headX = that.snake.body[0].x;
const headY = that.snake.body[0].y;
if (headX < 0 || headX >= maxX || headY < 0 || headY >= maxY) {
clearInterval(timer);
alert('Game Over');
}
}, 150);
}
let isgoing = true;
function bindKey() {
document.onkeydown = function (e) {
switch (e.keyCode) {
case 38:
that.snake.direction = 'up';
break;
case 40:
that.snake.direction = 'down';
break;
case 37:
that.snake.direction = 'left';
break;
case 39:
that.snake.direction = 'right';
break;
case 32:
if (isgoing) {
isgoing = false;
clearInterval(timer);
} else {
runSnake();
isgoing = true;
}
break;
}
};
}
window.Game = Game;
})(window, undefined);
// main
(function () {
const btn = document.getElementById('btn')
btn.onclick=()=>{
location.reload();
}
const map = document.getElementById('map');
const game = new Game(map);
game.start();
})();