在當前的前端環境下,vue這種框架能夠算是一項基礎技能,能夠說不會vue很難找到工做,並且大多數的面試官都很喜歡問的一個問題就是,關於vue的雙向數據綁定原理,這個問題能夠說是耳熟能詳了,那拋開vue的設計思路,單單就是 Object.defineProperty() 這個api的話,說你寫過這個就夠了。前端
這是一個很是簡單的貪吃蛇的小遊戲(請忽略裏面很是多的細節bug。。。),這個小遊戲就是經過defineProperty這個api實現的。這個api的一些屬性就很少介紹了,相信你們都知道。
首先,先要分析一下這個遊戲,主體的組成成分就是三個類,背景,食物,和蛇,剩下的就是那個開始按鈕,暫且無論。接下來就開始一個一個來看,先說背景,這個背景能夠當作是一個相似於棋盤的東西,既然是棋盤就能夠把它當成一個平面直角座標系構成的網格, vue
class Qipan {
constructor(w, h, id){
this.w = w
this.h = h
this.box = document.getElementById(id)
}
init(){
var tpl = "<ul class='point clearfix'>"
var tem = ''
for (var i = 0;i < this.w;i++) {
tpl += '<li></li>'
}
tpl += '</ul>'
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。git
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)
github
這裏咱們監聽的屬性爲flag,flag表示的就是當前這個網格是哪一個對象所處的位置,若是是食物處於這個位置,那這個flag就是food,當這個字段改變的時候,根據這個字段來判斷,這個位置的狀態,並做出相應的改變。面試
再來分析第二個類,蛇,這個類應該具備的功能,初始化,移動,吃食物,變長,死亡。這裏初始化和死亡能夠當作是一個方法,這裏的移動就要根據上下左右和速度前進,吃食物和變長簡單理解就是下一個前進的格子若是食物的話,就直接變成蛇的一部分就行了。json
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()
}
}
複製代碼
最後是食物,這個就比較簡單了,只要能初始化就行了。api
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… 。不過這個代碼是很久以前寫的了,結構很模糊,耦合性也很高,以後會優化一下代碼,但願你們可以看的明白。。
盒盒盒盒。。。bash