在当前的前端环境下,vue这种框架可以算是一项基础技能,可以说不会vue很难找到工作,而且大多数的面试官都很喜欢问的一个问题就是,关于vue的双向数据绑定原理,这个问题可以说是耳熟能详了,那抛开vue的设计思路,单单就是 Object.defineProperty() 这个api的话,说你写过这个就够了。
这是一个非常简单的贪吃蛇的小游戏(请忽略里面非常多的细节bug。。。),这个小游戏就是通过defineProperty这个api实现的。这个api的一些属性就不多介绍了,相信大家都知道。
首先,先要分析一下这个游戏,主体的组成成分就是三个类,背景,食物,和蛇,剩下的就是那个开始按钮,暂且不管。接下来就开始一个一个来看,先说背景,这个背景可以看成是一个类似于棋盘的东西,既然是棋盘就可以把它当成一个平面直角坐标系构成的网格,
class Qipan {
constructor(w, h, id){
this.w = w
this.h = h
this.box = document.getElementById(id)
}
init(){
var tpl = ""
var tem = ''
for (var i = 0;i < this.w;i++) {
tpl += ''
}
tpl += ''
for(var j = 0;j < this.h;j++){
tem += tpl
}
this.box.innerHTML = tem
this.box.style.width = 20 * this.w +'px'
}
getQi () {
var arr = []
for (var i = 0;i < this.h; i++) {
arr.push(new Array())
for (var j = 0;j < this.w;j++){
arr[i].push({flag : false,newFlag: '', site:[i, j]})
}
}
return arr
}
}
复制代码
接下来就要开始劫持每个格子对应的对象的key。
function observer(arr) {
arr.forEach(arr1 => {
arr1.forEach( item => {
Object.defineProperty(item,'flag',{
enumerable: true,
configurable: true,
get: ()=>{
return item.newFlag
},
set:newVal=> {
if (newVal === 'snake') {
document.getElementsByClassName('point')[item.site[0]].getElementsByTagName('li')[item.site[1]].style.background = '#DB7093'
item.newFlag = 'snake'
} else if (newVal === 'food') {
document.getElementsByClassName('point')[item.site[0]].getElementsByTagName('li')[item.site[1]].style.background = 'red'
item.newFlag = 'food'
} else {
item.newFlag = ''
document.getElementsByClassName('point')[item.site[0]].getElementsByTagName('li')[item.site[1]].style.background = '#F5DEB3'
}
}
})
})
})
}
复制代码
介绍一下里面的参数
configurable
当且仅当该属性的configurable为true时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
enumerable
当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。
数据描述符同时具有以下可选键值: value
该属性对应的值。可以是任何有效的JavaScript值(数值,对象,函数等)。默认为 undefined。
writable
当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 false。 存取描述符同时具有以下可选键值:
get
一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。 默认为 undefined。
set
一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为 undefined。
(摘自MDN)
这里我们监听的属性为flag,flag表示的就是当前这个网格是哪个对象所处的位置,如果是食物处于这个位置,那这个flag就是food,当这个字段改变的时候,根据这个字段来判断,这个位置的状态,并作出相应的改变。
再来分析第二个类,蛇,这个类应该具有的功能,初始化,移动,吃食物,变长,死亡。这里初始化和死亡可以看成是一个方法,这里的移动就要根据上下左右和速度前进,吃食物和变长简单理解就是下一个前进的格子如果食物的话,就直接变成蛇的一部分就好了。
class Snake {
constructor(qipan,h,food){
this.qipan = qipan
this.h = h
this.h2 = JSON.parse(JSON.stringify(h))
this.direct = 'left'
this.que = []
this.food = food
}
init () {
this.qipan.forEach(item=>{
item.forEach(items=>{
items.flag = ''
})
})
this.direct = 'left'
if (this.que.length !== 0) {
this.h = JSON.parse(JSON.stringify(this.h2))
this.que = []
clearInterval(time)
time = null
}
this.qipan[this.h[0]][this.h[1]].flag = 'snake'
this.qipan[this.h[0]][this.h[1] + 1].flag = 'snake'
this.que.push(this.qipan[this.h[0]][this.h[1]])
this.que.push(this.qipan[this.h[0]][this.h[1] + 1])
}
getUp(){
this.direct = 'up'
}
getLeft() {
this.direct = 'left'
}
getRight(){
this.direct = 'right'
}
getDown(){
this.direct = 'down'
}
getGo (){
var _this = this
switch (this.direct) {
case 'left':
if (this.qipan[this.h[1] - 1]) {
wooDir(this.qipan[this.h[0]][this.h[1] - 1],'left')
} else {
this.init()
this.food.init()
}
break
case 'right':
if (this.qipan[this.h[1] + 1]) {
wooDir(this.qipan[this.h[0]][this.h[1] + 1], 'right')
} else {
this.init()
this.food.init()
}
break
case 'up':
if (this.qipan[this.h[0] - 1]) {
wooDir(this.qipan[this.h[0] - 1][this.h[1]], 'up')
} else {
this.init()
this.food.init()
}
break
case 'down':
if (this.qipan[this.h[0] + 1]) {
wooDir(this.qipan[this.h[0] + 1][this.h[1]], 'down')
} else {
this.init()
this.food.init()
}
break
}
function wooDir(oo, dir) {
if (oo) {
if (oo.flag === '') {
oo.flag = 'snake'
switch (dir) {
case 'left':
_this.h[1] -= 1
break
case 'right':
_this.h[1] += 1
break
case 'up':
_this.h[0] -= 1
break
case 'down':
_this.h[0] += 1
break
}
_this.que.unshift(oo)
_this.que[_this.que.length - 1].flag = ''
_this.que.pop()
} else if (oo.flag === 'snake') {
_this.init()
_this.food.init()
} else if (oo.flag === 'food') {
_this.eat(oo)
}
} else {
_this.init()
_this.food.init()
}
}
}
eat (oo) {
oo.flag = 'snake'
this.que.unshift(oo)
this.h = JSON.parse(JSON.stringify(oo.site))
this.food.init()
}
}
复制代码
最后是食物,这个就比较简单了,只要能初始化就好了。
class Food {
constructor (qipan) {
this.qipan = qipan
}
init() {
var _this = this
var arr = []
_this.qipan.forEach(item=>{
item.forEach(items=>{
if(items.flag !== 'snake') {
arr.push(items)
}
})
})
arr[Math.floor(Math.random()*arr.length)].flag = 'food'
}
}
复制代码
好,三个类都准备好了,剩下的就是实例化对象,然后设置一个蛇往前走的定时器,和改变方向的监听事件,就ok了。
var qi = new Qipan(10,10, 'box')
qi.init()
var pan = qi.getQi()
var time = null
observer(pan)
var food = new Food(pan)
food.init()
var sna = new Snake(pan, [0, 3],food)
sna.init()
var btn_s = document.getElementById('start')
btn_s.onclick = function () {
if (time === null) {
time = setInterval(function(){
sna.getGo()
}, 200)
}
}
document.onkeydown = function (ev) {
var e = event || window.event || arguments.callee.caller.arguments[0]
if(e && e.keyCode === 37 ){
sna.getLeft()
}
if(e && e.keyCode === 38 ){
sna.getUp()
}
if(e && e.keyCode === 39 ){
sna.getRight()
}
if(e && e.keyCode === 40 ){
sna.getDown()
}
}
复制代码
ok,这样基本的功能就实现了。完整版请看 github.com/whyjson/com… 。不过这个代码是好久之前写的了,结构很模糊,耦合性也很高,之后会优化一下代码,希望大家能够看的明白。。
盒盒盒盒。。。